ENTRYPOINT vs CMD: назад к основам

в 6:22, , рубрики: devops, docker, entrypoint, Блог компании Southbridge, виртуализация, Серверное администрирование, системное администрирование

Construction

Название 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… иногда.

Вот тут-то и начинается путаница. В руководстве есть таблица, цель которой – внести ясность в этот вопрос.

<img src=«https://habrastorage.org/web/ca2/564/592/ca25645927e9445c9a70475de1be7afc.png»" alt=«Entrypointscreen» />

Попытаюсь объяснить на пальцах.

Факт 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.

Ссылки:

  1. ENTRYPOINT vs CMD: Back to Basics

Автор: olemskoi

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js