Ostatnio jeden z czytelników bloga zadał mi pytanie: Co powinienem użyć w moim Dockerfile? ENTRYPOINT czy CMD? Postanowiłem, że odpowiem na to pytanie w formie artykułu – tak byś i Ty mógł/mogła z tego skorzystać.
Post dołącza do serii A vs B, gdzie w jednym z poprzednich artykułów omawialiśmy różnicę pomiędzy ADD i COPY. Link do tego artykułu znajdziesz TUTAJ.
Jak sam tytuł wskazuje, dzisiaj przyjrzymy się bardziej szczegółowo instrukcjom ENTRYPOINT oraz CMD.
Wprowadzenie
Kiedy uruchamiamy kontener, uruchamiamy go na podstawie obrazu. Dzięki temu jesteśmy w stanie przenosić zachowanie kontenerów pomiędzy środowiskami. To, co znajdzie się wewnątrz obrazu oraz jak będzie się zachowywać kontener po uruchomieniu, określamy w pliku Dockerfile. Plik Dockerfile to zbiór instrukcji, gdzie zwykle każda z instrukcji dodaje kolejną warstwę do finalnego obrazu.
Z reguły w pierwszej warstwie obrazu znajdują się pliki systemowe. Kolejne polecenia takie jak RUN, COPY, ADD tworzą następne warstwy, by finalnie stworzyć kompletny obraz naszej aplikacji.
Przykładowo — nasz obraz może być zbudowany na podstawie Ubuntu (polecenie FROM). W kolejnej warstwie instalujemy dodatkowe pakiety, które są niezbędne do działa naszej aplikacji. Następnie kopiujemy artefakty aplikacji i wskazujemy proces startowy kontenera za pomocą polecenia CMD.
TL;DR;
A czy możemy użyc tutaj ENTRYPOINT zamiast CMD?
Odpowiedź brzmi: TAK, możemy. Nawet powinniśmy!
FROM ubuntu:18.04 RUN apt update && apt install python3 COPY my_script.py / CMD ["python", "my_script.py"]
Instrukcje ENTRYPOINT oraz CMD pozwalają określić punkt startowy dla kontenera, ale istnieje znaczna różnica pomiędzy nimi. Może zatem pojawić się dylemat, którą instrukcję wybrać albo która instrukcja jest rekomendowana.
Shell vs Exec
Zarówno instrukcja CMD, jak i ENTRYPOINT mogą występować w dwóch formach. Shell
oraz Exec
.
Exec
Składnia formy Exec (rekomendowanej) jest następująca:
<instruction> ["executable", "param1", "param2"]
FROM ubuntu:18.04 CMD ["/bin/ping", "localhost"]
Gdy polecenie jest wykonywane, następuje odwołanie bezpośrednio do pliku wykonywalnego. W tym przypadku jest to /bin/ping localhost
.
Shell
Składnia formy Shell jest następująca:
<instruction> <command>
FROM ubuntu:18.04 CMD ping localhost
Podczas wykonywania instrukcji zbudowanej w formie Shell, zamiast bezpośredniego odwołania do pliku wykonywalnego, wywoływane jest procesowanie przez shell: /bin/sh -c 'ping localhost'
Abyś mógł lepiej zrozumieć różnicę, uruchomiłem dwa kontenery i nadałem im nazwy odpowiadające formom Shell
i Exec
. Całość wygląda następująco:
CONTAINER ID COMMAND STATUS NAMES dac330dd8ff6 "/bin/sh -c 'ping lo…" Up 30 seconds shell-form b83174ca6fb0 "/bin/ping localhost" Up 40 seconds exec-form
Jeżeli chcesz poznać najlepsze praktyki tworzenia Dockerfile, zachęcam do lektury
“10 Najlepszych Praktyk Tworzenia Dockerfile”
CMD
Instrukcja CMD pozwala na określenie domyślnego polecenia, które zostanie wykonane tylko podczas uruchamiania kontenera bez podawania dodatkowych argumentów. Jeżeli uruchomisz kontener z dodatkowym argumentem, domyślne polecenie zostanie zignorowane. Jeżeli Twój Dockerfile posiada więcej niż jedną instrukcję CMD, tylko ostatnia jest brana pod uwagę. Pozostałe są ignorowane.
CMD może występować w trzech formach:
CMD ["executable","param1","param2"]
(forma Exec, rekomendowana)CMD ["param1","param2"]
(pozwala na przekazanie dodatkowych parametrów do instrukcji ENTRYPOINT)CMD command param1 param2
(forma Shell)
Załóżmy następujący Dockerfile:
FROM ubuntu:18.04 CMD echo "Hello world"
Gdy uruchomimy kontener bez przekazywania dodatkowych argumentów, za pomocą polecenia:
docker run -it <image>
otrzymamy następujący rezultat:
Hello world
Jeżeli natomiast dodamy dodatkowy argument /bin/bash i uruchomimy kontener za pomocą polecenia docker run -it <image> /bin/bash
, polecenie echo “Hello world” zostaje zignorowane i uruchamiany jest terminal.
root@8cb0763a478d:/#
ENTRYPOINT
Instrukcja ENTRYPOINT również służy do określenia głównego procesu kontenera. Wygląda bardzo podobnie do CMD, ponieważ pozwala na określenie polecenia wraz z parametrami.
Istnieje jednak znacząca różnica w działaniu ENTRYPOINT. Mianowicie, po uruchomieniu kontenera z dodatkowymi parametrami, domyślne polecenie NIE jest pomijane, jak w przypadku CMD.
ENTRYPOINT występuje w dwóch formach:
ENTRYPOINT ["executable", "param1", "param2"]
(forma Exec)ENTRYPOINT command param1 param2
(forma Shell)
Exec
Exec jest rekomendowanym podejściem. Dlaczego tak jest?
Forma exec instrukcji ENTRYPOINT pozwala na zdefiniowanie dodatkowych parametrów polecenia, które w zależności od potrzeb mogą ulec zmianie. Odbywa się to za pomocą komendy CMD.
Jak to wygląda? Argumenty zawarte w instrukcji ENTRYPOINT zawsze się wykonają. Z kolei argumenty instrukcji CMD są opcjonalne i mogą zostać nadpisane podczas uruchamiania kontenera.
ENTRYPOINT ["/bin/echo", "Hej"] CMD ["Damian"]
Gdy uruchomimy kontener bez przekazywania dodatkowych argumentów, za pomocą polecenia:
docker run -it <image>
otrzymamy następujący rezultat:
Hej Damian
Jeżeli natomiast dodamy dodatkowy argument ‘Filip’ i uruchomimy kontener za pomocą polecenia docker run -it <image> Filip
, otrzymamy:
Hej Filip
Shell
Postać Shell instrukcji ENTRYPOINT nie bierze pod uwagę argumentów przekazanych przez CMD.
Omówmy sobie to na tym samym przykładzie:
ENTRYPOINT echo Hej CMD Damian
Gdy uruchomimy kontener bez przekazywania dodatkowych argumentów, za pomocą polecenia:
docker run -it <image>
otrzymamy następujący rezultat:
Hej
Dodając argument ‘Filip’ do polecenia docker run -it <image> Filip
, otrzymamy nadal to samo:
Hej
Podsumowanie
Jeżeli masz dylemat czy użyć CMD, czy ENTRYPOINT jako punkt startowy twojego kontenera, odpowiedz sobie na następujące pytanie.
Czy zawsze moje polecenie MUSI się wykonać?
Jeśli odpowiedź brzmi tak, użyj ENTRYPOINT. Co więcej, jeśli potrzebujesz przekazać dodatkowe parametry, które mogą być nadpisane podczas uruchomienia kontenera — użyj również instrukcji CMD.