Питання 5 · Розділ 14

В чому різниця між CMD та ENTRYPOINT?

Обидві інструкції визначають, що буде запущено при старті контейнера, але у них різні задачі:

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Просте пояснення

Обидві інструкції визначають, що буде запущено при старті контейнера, але у них різні задачі:

  • ENTRYPOINT — це основна програма контейнера (наприклад, java)
  • CMD — це аргументи за замовчуванням для цієї програми (наприклад, -jar app.jar)

RUN – виконується при ЗБІРЦІ образу. CMD/ENTRYPOINT – при ЗАПУСКУ контейнера.

Аналогія

Уявіть виклик функції в програмуванні:

  • ENTRYPOINT — це ім’я функції
  • CMD — це параметри за замовчуванням
java -jar app.jar
^^^^^ ^^^^^^^^^^^^
 |        |
ENTRYPOINT  CMD

Як вони працюють разом

ENTRYPOINT ["java", "-jar", "/app.jar"]
CMD ["--server.port=8080"]

При запуску docker run myapp виконається: java -jar /app.jar --server.port=8080

При запуску docker run myapp --server.port=9090 виконається: java -jar /app.jar --server.port=9090

Дві форми запису

Exec form (рекомендується) — у вигляді JSON-масиву:

ENTRYPOINT ["java", "-jar", "/app.jar"]

Shell form — як звичайний рядок:

ENTRYPOINT java -jar /app.jar

Завжди використовуйте Exec form — вона правильно обробляє сигнали завершення.

Що запам’ятати

  • ENTRYPOINT — фіксована команда, CMD — параметри за замовчуванням
  • Їх можна використовувати разом
  • CMD легко перевизначити при запуску, ENTRYPOINT — ні
  • Завжди використовуйте Exec form ["cmd", "arg1", "arg2"]
  • В Dockerfile може бути лише одна CMD і одна ENTRYPOINT

🟡 Middle Level

Детальна поведінка

ENTRYPOINT (Точка входу)

Визначає основну виконувану програму контейнера:

  • Стабільність: її складно перевизначити випадково (потрібен прапорець --entrypoint)
  • Призначення: перетворює контейнер на «виконуваний бінарник»
  • Аргументи: усі аргументи, передані в docker run, дописуються в кінець команди ENTRYPOINT

CMD (Команда за замовчуванням)

Визначає команду за замовчуванням або параметри для ENTRYPOINT:

  • Гнучкість: дуже легко перевизначити. Будь-які аргументи в кінці docker run повністю замінюють CMD
  • Призначення: надати стандартну поведінку, яку користувач може легко змінити

Три сценарії використання

Сценарій 1: Тільки CMD

CMD ["java", "-jar", "/app.jar"]

docker run myappjava -jar /app.jar docker run myapp echo helloecho hello (CMD повністю замінено)

Сценарій 2: Тільки ENTRYPOINT

ENTRYPOINT ["java", "-jar", "/app.jar"]

docker run myappjava -jar /app.jar docker run myapp --debugjava -jar /app.jar --debug

Сценарій 3: ENTRYPOINT + CMD (рекомендується)

ENTRYPOINT ["java", "-jar", "/app.jar"]
CMD ["--server.port=8080"]

docker run myappjava -jar /app.jar --server.port=8080 docker run myapp --server.port=9090java -jar /app.jar --server.port=9090

Сценарій 3 (ENTRYPOINT + CMD) – рекомендований. Сценарій 1 – для простих скриптів. Сценарій 2 – коли контейнер має бути «виконуваним файлом».

Типові помилки

Помилка Наслідок Як уникнути
Shell form ENTRYPOINT java -jar Сигнали не доходять до додатку Exec form ["java", "-jar"]
Кілька CMD в Dockerfile Враховується лише остання Одна CMD на файл
CMD без ENTRYPOINT CMD легко випадково замінити Використовуйте пару ENTRYPOINT + CMD
ENTRYPOINT без CMD Розробник не може перевизначити для налагодження Додайте CMD з параметрами за замовчуванням
Плутанина з перевизначенням Контейнер запускає не те Розумійте, що docker run myapp arg замінює CMD

Перевизначення при запуску

# Перевизначити CMD — просто передайте аргументи
docker run myapp --custom-arg

# Перевизначити ENTRYPOINT — використайте прапорець
docker run --entrypoint /bin/bash myapp

Shell form vs Exec form: критичний нюанс

Exec form — команда запускається безпосередньо як процес з PID 1. Правильно обробляє сигнали завершення. Важливо для Kubernetes і Docker Stop.

Shell form — команда запускається як підпроцес /bin/sh -c. Сигнали ОС приходять в оболонку sh, а не в додаток. Додаток може бути «вбито» жорстко без завершення транзакцій.

Що запам’ятати

  • Використовуйте Exec form для обох інструкцій
  • ENTRYPOINT для незмінної частини команди
  • CMD для параметрів, які можуть бути змінені
  • Лише одна CMD і одна ENTRYPOINT враховуються (останні у файлі)
  • Без proper signal handling додаток не зможе коректно завершитися

Коли НЕ використовувати ENTRYPOINT

Не використовуйте ENTRYPOINT, якщо образ призначений для різних команд (наприклад, образ curl/wget для різних запитів). В цьому випадку CMD гнучкіший.


🔴 Senior Level

Архітектурний дизайн ENTRYPOINT і CMD

Різниця між ENTRYPOINT і CMD — це не просто синтаксис, це патерн проєктування для створення перевикористовуваних контейнерних образів.

Патерн «Executable Container»

ENTRYPOINT перетворює контейнер на виконуваний файл:

ENTRYPOINT ["java"]
CMD ["-jar", "/app.jar"]

Контейнер поводиться як бінарник:

docker run myapp -version          # java -version
docker run myapp -jar other.jar    # java -jar other.jar

Проблема PID 1 і обробка сигналів

В Linux процес з PID 1 має особливу поведінку:

  • Не отримує сигнали за замовчуванням (ігнорує SIGTERM)
  • Відповідає за «усиновлення» осиротілих процесів
  • Не завершується автоматично при unhandled signals

PID 1 – головний процес в контейнері. Якщо він не вміє завершуватися коректно (обробляти SIGTERM), додаток буде «вбито» жорстко через SIGKILL.

Shell form проблема:

ENTRYPOINT java -jar /app.jar

Процеси в контейнері:

PID 1: /bin/sh -c "java -jar /app.jar"
PID 7: java -jar /app.jar

SIGTERM приходить в /bin/sh, який не знає, що робити з java. Результат: docker stop чекає 10 секунд і посилає SIGKILL. Додаток не виконує graceful shutdown — транзакції перериваються, з’єднання не закриваються.

Рішення:

  1. Exec form (рекомендується):
    ENTRYPOINT ["java", "-jar", "/app.jar"]
    

    Додаток — PID 1, отримує сигнали безпосередньо.

  2. Tini (init system):
    ENTRYPOINT ["tini", "--", "java", "-jar", "/app.jar"]
    

    Tini — мінімальний init, коректно forwarding сигнали і reap’ить zombie-процеси.

  3. Docker –init прапорець:
    docker run --init myapp
    

Взаємодія з Kubernetes

В Kubernetes ENTRYPOINT і CMD мапляться на поля контейнера:

Docker Kubernetes
ENTRYPOINT command
CMD args
containers:
- name: app
  image: myapp
  command: ["java", "-jar"]     # перевизначає ENTRYPOINT
  args: ["--server.port=9090"]   # перевизначає CMD

Важливо: якщо в Kubernetes вказані command і args, вони повністю замінюють Docker ENTRYPOINT і CMD.

Trade-offs

Підхід Плюс Мінус
Тільки CMD Гнучкість для розробника Легко випадково замінити
Тільки ENTRYPOINT Стабільність Складно перевизначити для налагодження
ENTRYPOINT + CMD Найкраще з обох Потрібно розуміти взаємодію
Entrypoint-скрипт Гнучка ініціалізація Ще один шар, потрібно підтримувати
Tini Правильні сигнали + zombie reap Додаткова залежність

Edge Cases

  • Override в Kubernetes: command в K8s маніфесті повністю замінює ENTRYPOINT. Якщо розробник очікує, що entrypoint-скрипт виконає міграції, а K8s перевизначив command — міграції не запустяться.
  • Наслідування ENTRYPOINT: якщо базовий образ має ENTRYPOINT, а дочірній Dockerfile — ні, наслідується ENTRYPOINT батька. Це може призвести до неочікуваної поведінки.
  • JSON array parsing: ENTRYPOINT ["java", "-jar", "/app.jar"] — це JSON. Помилка в лапках або комах призведе до падіння збірки.
  • CMD як shell form + ENTRYPOINT як exec form: ENTRYPOINT ["java"] + CMD -jar app.jar — CMD виконається як /bin/sh -c "java -jar app.jar", ENTRYPOINT буде проігноровано.

Патерн «Entry Point Script»

Для складних додатків використовують entrypoint-скрипт:

COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["java", "-jar", "/app.jar"]
#!/bin/sh
# docker-entrypoint.sh

# Виконуємо міграції
if [ "$RUN_MIGRATIONS" = "true" ]; then
    java -jar /app.jar --migrate
    exit 0
fi

# Запускаємо додаток (exec замінює процес скрипта)
exec "$@"

exec "$@" замінює процес shell-скрипта на цільову команду, зберігаючи PID 1 і сигнали. Без exec скрипт залишиться PID 1 і сигнали не дійдуть до додатку.

Антипатерни

  1. Shell form без обробки сигналів: ENTRYPOINT java -jar /app.jar — graceful shutdown не працює.
  2. Кілька CMD/ENTRYPOINT: враховується лише остання.
  3. ENTRYPOINT без CMD в образах для розробників: неможливо перевизначити для налагодження.
  4. Entrypoint-скрипт без exec: скрипт залишається PID 1, сигнали втрачаються.
  5. Секрети в CMD/ENTRYPOINT: CMD ["java", "-jar", "app.jar", "--db-password=secret"] — видно в docker inspect і ps.

Продуктивність

Підхід Час shutdown Graceful shutdown Zombie reap
Shell form 10s (SIGKILL timeout) Ні Ні
Exec form < 1s (SIGTERM) Так Ні
Exec form + tini < 1s Так Так
Docker –init < 1s Так Так

Резюме

  • Використовуйте Exec form для обох інструкцій — критично для signal handling.
  • ENTRYPOINT + CMD разом створюють гнучкий і перевикористовуваний образ.
  • Розумійте маппінг на Kubernetes command/args.
  • Проблема PID 1 реальна: використовуйте exec form або tini.
  • Entrypoint-скрипти з exec "$@" — стандарт для складних сценаріїв ініціалізації.
  • В Dockerfile може бути лише одна інструкція CMD і одна ENTRYPOINT (враховується остання).

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • ENTRYPOINT — фіксована команда (PID 1), CMD — параметри за замовчуванням
  • ENTRYPOINT + CMD разом — рекомендований патерн («executable container»)
  • Exec form ["cmd", "arg"] обов’язкова для коректної обробки сигналів
  • Docker CMD легко перевизначити (docker run image args), ENTRYPOINT — через --entrypoint
  • В K8s: Docker ENTRYPOINT → command, CMD → args (повна заміна)
  • Проблема PID 1: процес без обробника сигналів не робить graceful shutdown
  • Рішення PID 1: exec form, tini, або docker run --init

Часті уточнюючі питання:

  • «Що відбувається при docker run myapp argarg замінює CMD, дописується до ENTRYPOINT
  • «Чому shell form небезпечна в K8s?» — Сигнали приходять в /bin/sh, не в додаток; graceful shutdown не працює
  • «Що робить exec "$@" в entrypoint-скрипті?» — Замінює процес скрипта на цільову команду, зберігаючи PID 1
  • «Чи можна мати кілька CMD в Dockerfile?» — Так, але враховується лише остання

Червоні прапорці (НЕ говорити):

  • «CMD і ENTRYPOINT — одне і те ж» (різна семантика і перевизначаємість)
  • «Використовую shell form для зручності» (graceful shutdown зламаний)
  • «В K8s command і args доповнюють Docker ENTRYPOINT/CMD» (повністю замінюють!)
  • «PID 1 не має значення в контейнері» (критичний для signal handling)

Пов’язані теми:

  • [[Які основні інструкції використовуються в Dockerfile]] — усі інструкції Dockerfile
  • [[Що таке Pod в Kubernetes]] — контейнери в K8s (signal handling важливий)
  • [[Як організувати rolling update в Kubernetes]] — graceful shutdown при оновленні