Название ENTRYPOINT
всегда меня смущало. Это название подразумевает, что каждый контейнер должен иметь определенную инструкцию ENTRYPOINT
. Но после прочтения официальной документации я понял, что это не соответствует действительности.
Факт 1: Требуется определить хотя бы одну инструкцию (ENTRYPOINT
или CMD
) (для запуска).
Если вы не определите ни одной из них, то получите сообщение об ошибке. Давайте попробуем запустить образ Alpine Linux, для которого не определены ни ENTRYPOINT
, ни CMD
.
$ docker run alpine
docker: Error response from daemon: No command specified.
See 'docker run --help'.
Факт 2: Если во время выполнения определена только одна из инструкций, то и CMD
и ENTRYPOINT
будут иметь одинаковый эффект.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ls /usr
$ docker build -t test .
$ docker run test
bin
lib
local
sbin
share
Мы получим те же результаты, если будем использовать CMD
вместо ENTRYPOINT
.
$ cat Dockerfile
FROM alpine
CMD ls /usr # Using CMD instead
$ docker build -t test .
$ docker run test
bin
lib
local
sbin
share
Хотя этот пример и показывает, что между ENTRYPOINT
и CMD
нет никакой разницы, её можно увидеть, сравнив метаданные контейнеров.
Например, первый файл Dockerfile (с определенной ENTRYPOINT
):
$ docker inspect b52 | jq .[0].Config
{
...
"Cmd": null,
...
"Entrypoint": [
"/bin/sh",
"-c",
"ls /"
],
...
}
Факт 3: И для CMD
, и для ENTRYPOINT
существуют режимы shell и exec.
Из руководства:
ENTRYPOINT
имеет два режима выполнения:
ENTRYPOINT ["executable", "param1", "param2"]
(исполняемая форма, предпочтительно)ENTRYPOINT command param1 param2
(форма оболочки)
До сих пор мы использовали режим shell, или оболочки. Это означает, что наша команда ls -l
запускается внутри /bin/sh -c
. Давайте попробуем оба режима и изучим запущенные процессы.
Режим "shell":
$ cat Dockerfile
FROM alpine
ENTRYPOINT ping www.google.com # "shell" format
$ docker build -t test .
$ docker run -d test
11718250a9a24331fda9a782788ba315322fa879db311e7f8fbbd9905068f701
Затем изучим процессы:
$ docker exec 117 ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh -c ping www.google.com
7 root 0:00 ping www.google.com
8 root 0:00 ps
Обратите внимание, что процесс sh -c
имеет PID, равный 1. Теперь то же самое, используя режим "exec":
$ cat Dockerfile
FROM alpine
ENTRYPOINT ["ping", "www.google.com"] # "exec" format
$ docker build -t test .
$ docker run -d test
1398bb37bb533f690402e47f84e43938897cbc69253ed86f0eadb6aee76db20d
$ docker exec 139 ps
PID USER TIME COMMAND
1 root 0:00 ping www.google.com
7 root 0:00 ps
Мы видим, что при использовании режима exec команда ping www.google.com
работает с идентификатором процесса PID, равным 1, а процесс sh -c
отсутствует. Имейте в виду, что приведенный выше пример работает точно так же, если использовать CMD
вместо ENTRYPOINT
.
Факт 4: Режим exec является рекомендуемым.
Это связано с тем, что контейнеры задуманы так, чтобы содержать один процесс. Например, отправленные в контейнер сигналы перенаправляются процессу, запущенному внутри контейнера с идентификатором PID, равным 1. Очень познавательный опыт: чтобы проверить факт перенаправления, полезно запустить контейнер ping и попытаться нажать ctrl + c для остановки контейнера.
Контейнер, определенный с помощью режима exec, успешно завершает работу:
$ cat Dockerfile
FROM alpine
ENTRYPOINT ["ping", "www.google.com"]
$ docker build -t test .
$ docker run test
PING www.google.com (172.217.7.164): 56 data bytes
64 bytes from 172.217.7.164: seq=0 ttl=37 time=0.246 ms
64 bytes from 172.217.7.164: seq=1 ttl=37 time=0.467 ms
^C
--- www.google.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.246/0.344/0.467 ms
$
При использовании режима "shell" контейнер работает не так, как ожидалось.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ping www.google.com
$ docker build -t test .
$ docker run test
PING www.google.com (172.217.7.164): 56 data bytes
64 bytes from 172.217.7.164: seq=0 ttl=37 time=0.124 ms
^C^C^C^C64 bytes from 172.217.7.164: seq=4 ttl=37 time=0.334 ms
64 bytes from 172.217.7.164: seq=5 ttl=37 time=0.400 ms
Помогите, я не могу выйти! Сигнал SIGINT
, который был направлен процессу sh
, не будет перенаправлен в подпроцесс ping
, и оболочка не завершит работу. Если по какой-то причине вы действительно хотите использовать режим shell, выходом из ситуации будет использовать exec
для замены процесса оболочки процессом ping
.
$ cat Dockerfile
FROM alpine
ENTRYPOINT exec ping www.google.com
Факт 5: Нет оболочки? Нет переменных окружения.
Проблема запуска НЕ в режиме оболочки заключается в том, что вы не можете воспользоваться преимуществами переменных среды (таких как $PATH
) и прочими возможностями, которые предоставляет использование оболочки. В приведенном ниже файле Dockerfile присутствуют две проблемы:
$ cat Dockerfile
FROM openjdk:8-jdk-alpine
WORKDIR /data
COPY *.jar /data
CMD ["java", "-jar", "*.jar"] # "exec" format
Первая проблема: поскольку вы не можете воспользоваться переменной среды $PATH
, нужно указать точное расположение исполняемого java-файла. Вторая проблема: символы подстановки интерпретируются самой оболочкой, поэтому строка *.jar
не будет корректно обработана. После исправления этих проблем итоговый файл Dockerfile выглядит следующим образом:
FROM openjdk:8-jdk-alpine
WORKDIR /data
COPY *.jar /data
CMD ["/usr/bin/java", "-jar", "spring.jar"]
Факт 6: Аргументы CMD присоединяются к концу инструкции ENTRYPOINT
… иногда.
Вот тут-то и начинается путаница. В руководстве есть таблица, цель которой – внести ясность в этот вопрос.
Попытаюсь объяснить на пальцах.
Факт 6a: Если вы используете режим shell для ENTRYPOINT
, CMD
игнорируется.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ls /usr
CMD blah blah blah blah
$ docker build -t test .
$ docker run test
bin
lib
local
sbin
share
Строка blah blah blah blah
была проигнорирована.
FACT 6b: При использовании режима exec для ENTRYPOINT
аргументы CMD
добавляются в конце.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ["ls", "/usr"]
CMD ["/var"]
$ docker build -t test .
$ docker run test
/usr:
bin
lib
local
sbin
share
/var:
cache
empty
lib
local
lock
log
opt
run
spool
tmp
Аргумент /var
был добавлен к нашей инструкции ENTRYPOINT
, что позволило эффективно запустить команду ls/usr/var
.
Факт 6c: При использовании режима exec для инструкции ENTRYPOINT
необходимо использовать режим exec и для инструкции CMD
. Если этого не сделать, Docker попытается добавить sh -c
в уже добавленные аргументы, что может привести к некоторым непредсказуемым результатам.
Факт 7: Инструкции ENTRYPOINT
и CMD
могут быть переопределены с помощью флагов командной строки.
Флаг --entrypoint
может быть использован, чтобы переопределить инструкцию ENTRYPOINT
:
docker run --entrypoint [my_entrypoint] test
Все, что следует после названия образа в команде docker run
, переопределяет инструкцию CMD
:
docker run test [command 1] [arg1] [arg2]
Все вышеперечисленные факты справедливы, но имейте в виду, что разработчики имеют возможность переопределять флаги в команде docker run
. Из этого следует, что ...
Достаточно фактов… Что же делать мне?
Ok, если вы дочитали статью до этого места, то вот информация, в каких случаях использовать ENTRYPOINT
, а в каких CMD
.
Это решение я собираюсь оставить на усмотрение человека, создающего Dockerfile, который может быть использован другими разработчиками.
Используйте ENTRYPOINT
, если вы не хотите, чтобы разработчики изменяли исполняемый файл, который запускается при запуске контейнера. Вы можете представлять, что ваш контейнер – исполняемая оболочка. Хорошей стратегией будет определить стабильную комбинацию параметров и исполняемого файла как ENTRYPOINT
. Для нее вы можете (не обязательно) указать аргументы CMD
по умолчанию, доступные другим разработчикам для переопределения.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ["ping"]
CMD ["www.google.com"]
$ docker build -t test .
Запуск с параметрами по умолчанию:
$ docker run test
PING www.google.com (172.217.7.164): 56 data bytes
64 bytes from 172.217.7.164: seq=0 ttl=37 time=0.306 ms
Переопределение CMD собственными параметрами:
$ docker run test www.yahoo.com
PING www.yahoo.com (98.139.183.24): 56 data bytes
64 bytes from 98.139.183.24: seq=0 ttl=37 time=0.590 ms
Используйте только CMD
(без определения ENTRYPOINT
), если требуется, чтобы разработчики могли легко переопределять исполняемый файл. Если точка входа определена, исполняемый файл все равно можно переопределить, используя флаг --entrypoint
. Но для разработчиков будет гораздо удобнее добавлять желаемую команду в конце строки docker run
.
$ cat Dockerfile
FROM alpine
CMD ["ping", "www.google.com"]
$ docker build -t test .
Ping – это хорошо, но давайте попробуем запустить контейнер с оболочкой вместо команды ping
.
$ docker run -it test sh
/ # ps
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 ps
/ #
Я предпочитаю по большей части этот метод, потому что он дает разработчикам свободу легко переопределять исполняемый файл оболочкой или другим исполняемым файлом.
Очистка
После запуска команд на хосте осталась куча остановленных контейнеров. Очистите их следующей командой:
$ docker system prune
Обратная связь
Буду рад услышать ваши мысли об этой статье ниже в комментариях. Кроме того, если вам известен более простой способ поиска в выдаче докера с помощью jq, чтобы можно было сделать что-то вроде docker inspect [id] | jq * .config
, тоже напишите в комментариях.
Джон Закконе
Капитан Докера и инженер по облачным технологиям в IBM. Специализируется на Agile, микросервисах, контейнерах, автоматизации, REST, DevOps.
Ссылки:
Комментарии (15)
grossws
30.05.2017 16:03-4Вы статью вычитывали хоть минимально? Все эти
Cat
,Docker
в шелле (хотя на mac'е из-за case-insensitive root fs будет работать),CMD [*java*, *-jar*, **.jar*]
, где " почему-то оказался заменён на *..
ngalayko
31.05.2017 10:01Обычно, если разработчику нужно переопределить исполняемую команду, то изменения внутри контейнера не ограничиваются одной лишь командой.
Тогда создается Dockerfile, унаследованный от базового с необходимыми приготовлениями и новым ENTRYPOINT. При таком подходе разницы не будет.ggo
31.05.2017 10:39Это если image пересобирать.
А если у вас есть готовый image, и вам хочется немного поменять его поведение, то при правильном подходе, это можно сделать через CMD.ngalayko
31.05.2017 10:41ну да, просто кажется, случай с небольшим изменением поведения встречается реже, и менее удобен, чем готовый image с вашими требованиями
но не исключаю, что это знание как-нибудь пригодится :)
Crandel
31.05.2017 10:44Это если у вас в контейнере запускается одна команда с одним набором параметров. А если набор параметров большой и нужно с разными параметрами запускать? Новый image для каждого набора параметров?
ngalayko
31.05.2017 10:47если нужно передать параметры в команду ENTRYPOINT это прекрасно делается так:
Dockerfile:
FROM ubuntu:latest ADD entrypoint.sh / RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh:
#!/bin/sh echo "$@"
а запуск:
docker run -it image/name param1 param2
Crandel
31.05.2017 10:52Зачем один лишний файл, когда докер может сделать тоже самое? И в сабже очень хорошо это описали
ngalayko
31.05.2017 10:55best practice, описанная даже в доках
https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#entrypoint
в примере, что я привел это неочевидно, но представьте, что логика запуска сложнее чем
echo
и требует (на самом деле почти всегда) некоторых приготовлений не на этапе сборки, а сразу после запуска контейнераCrandel
31.05.2017 12:03The ENTRYPOINT instruction can also be used in combination with a helper script, allowing it to function in a similar way to the command above, even when starting the tool may require more than one step
Тут описан немного другой случай, если для запуска процесса нужны дополнительные действия, не параметры
Crandel
Опечатка
olemskoi
Спасибо! Исправил.