Привет, Хабр! Мы перевели для вас свежую статью Джея Шмидта из блога Docker. Надеемся, что вам будет полезен этот материал. Приятного чтения!

RUN

Инструкция RUN используется в Dockerfile для выполнения команд, которые создают и конфигурируют образ контейнера Docker. Эти команды выполняются в процессе сборки образа, и каждая инструкция RUN создает новый слой в образе. Например, если вы создаете образ, для которого требуется установить определенные ПО или библиотеки, вы должны использовать RUN для выполнения необходимых команд установки.

В следующем примере мы показали, как процессу сборки Docker задать команду обновить apt cache и установить Apache во время создания образа:

RUN apt update && apt -y install apache2

Инструкцию RUN следует использовать с умом. Важно сводить количество слоев к минимуму, по возможности объединяя связанные команды в одну инструкцию RUN, чтобы уменьшить размер образа.

CMD

В инструкции CMD указывается команда, которая будет выполняться при запуске контейнера из образа Docker по умолчанию. То есть если при запуске контейнера (т.е. в команде docker run) команда не будет указана, то запустится та, которую мы установили в качестве команды по умолчанию. CMD можно переопределить, указав аргументы командной строки для docker run.

CMD полезен для настройки команд по умолчанию и легко переопределяемых параметров. Он часто используется в образах как способ определения параметров запуска по умолчанию и может быть переопределен из командной строки при запуске контейнера.

Например, по умолчанию может запускаться веб-сервер, но пользователи могут указать, чтобы вместо него запускалась оболочка:

CMD ["apache2ctl", "-DFOREGROUND"]

Пользователи могут запустить контейнер с помощью команды docker run -it <image> /bin/bash, чтобы получить оболочку Bash вместо запуска Apache.  

ENTRYPOINT

Инструкция ENTRYPOINT задает исполняемый файл для контейнера в качестве файла по умолчанию. Она похожа на CMD, но между ними есть различие: если CMD переопределяется аргументами командной строки, переданными в команду docker run, то для переопределения ENTRYPOINT все аргументы командной строки добавляются к самой инструкции ENTRYPOINT.

Примечание: используйте инструкцию ENTRYPOINT в том случае, когда вам нужно, чтобы контейнер всегда выполнял одну и ту же функцию, но так, чтобы пользователи имели возможность добавлять дополнительные команды в конце. 

ENTRYPOINT особенно полезна для преобразования контейнера в отдельный исполняемый файл. Например, предположим, что вы упаковываете пользовательский скрипт, для которого требуются аргументы (к примеру, “my_script extra_args”). В таком случае вы можете использовать ENTRYPOINT, чтобы всегда запускать процесс скрипта (“my_script”), а затем разрешить пользователям образа указывать “extra_args” в командной строке docker run. Как это будет выглядеть: 

ENTRYPOINT ["my_script"]

Объединяем CMD и ENTRYPOINT

Инструкция CMD в формате exed может использоваться для предоставления аргументов по умолчанию для ENTRYPOINT. Эта настройка позволяет использовать точку входа в качестве основного исполняемого файла, а CMD — указывать дополнительные аргументы, которые могут быть переопределены пользователем.

Например, у вас может быть контейнер, запускающий приложение на Python, в котором вы хотите всегда использовать один и тот же файл приложения, но с возможностью указывать пользователями разные аргументы командной строки:

ENTRYPOINT ["python", "/app/my_script.py"]
CMD ["--default-arg"]

Выполнение команды docker run myimage --user-arg приводит к выполнению другой команды python /app/my_script.py --user-arg.

Описания и примеры использования инструкций
Описания и примеры использования инструкций

Что такое PID 1 и почему это важно?

В контексте Unix и Unix-подобных систем, включая контейнеры Docker, PID 1 относится к первому процессу, запущенному при загрузке системы. Затем все остальные процессы запускаются с помощью PID 1, который в модели дерева процессов является родительским для каждого процесса в системе.

В контейнерах Docker процесс, выполняющийся под PID 1, имеет решающее значение, поскольку он отвечает за управление всеми остальными процессами внутри контейнера. Кроме того, PID 1 — это процесс, который просматривает и обрабатывает сигналы от хоста Docker. Например, сигнал SIGTERM, поступивший в контейнер, будет перехвачен и обработан PID 1, после чего работа контейнера будет успешно остановлена.

Когда команды выполняются в Docker с использованием формата shell, процессу shell (/bin/sh -c) обычно присваивается значение PID 1. Тем не менее в этом формате он обрабатывает эти сигналы не совсем правильно, что может привести к некорректному завершению работы контейнера. При использовании же формата exec команда выполняется непосредственно как PID 1 без участия оболочки, что позволяет ей напрямую получать и обрабатывать сигналы. Такое поведение гарантирует, что контейнер сможет корректно останавливать, перезапускать или обрабатывать прерывания. 

Shell и exec форматы

В предыдущих примерах мы использовали два способа передачи аргументов в инструкции RUN, CMD и ENTRYPOINT. Эти способы называются shell form и exec form

Важно: ключевое визуальное различие заключается в том, что форма exec передается в виде массива команд и аргументов, разделенных запятыми, с одним аргументом/командой на элемент. И наоборот, форма shell выражается в виде строки, объединяющей команды и аргументы. 

Каждый формат по-своему влияет на выполнение команд внутри контейнеров, воздействуя на всё — от обработки сигналов до расширения переменных окружения. 

Описания и примеры использования форматов shell и exec
Описания и примеры использования форматов shell и exec

В формате shell команда выполняется в подоболочке, обычно /bin/sh -c в системах Linux. Этот формат позволяет обрабатывать оболочку (например, расширять переменные, использовать подстановочные знаки и т. д.), таким образом делая её более гибкой для определённых типов команд. Однако это также означает, что процесс, выполняющий вашу команду, не является PID 1 контейнера, что может привести к проблемам с обработкой сигналов, поскольку сигналы, посылаемые Docker (например, SIGTERM для плавного завершения работы), получает оболочка, а не предполагаемый процесс.

Формат exec не вызывает командную оболочку. Это значит, что указанная вами команда будет выполняться непосредственно как PID 1 контейнера, что очень важно для корректной обработки сигналов, посылаемых контейнеру. Кроме того, эта форма не выполняет расширения оболочки, поэтому она более безопасна и прогнозируема, особенно при указании аргументов или команд из внешних источников.

Собираем всё вместе

Давайте рассмотрим несколько примеров, иллюстрирующих практическое применение и нюансы инструкций RUN, CMD и ENTRYPOINT в Docker, а также выбор между форматами shell и exec. Эти примеры покажут, как каждая инструкция может быть эффективно использована в реальных сценариях работы с файлами Dockerfile, и подчеркнут различия между shell и exec. 

Благодаря этим примерам вы лучше поймете, когда и как использовать каждую инструкцию, чтобы вы смогли адаптировать поведение контейнера к вашим потребностям, обеспечив надлежащую конфигурацию, безопасность и производительность ваших контейнеров. Этот подход поможет объединить теоретические знания, которые мы обсуждали выше, в практические идеи, что могут быть непосредственно применены к вашим проектам в Docker

Инструкция RUN

Для инструкции RUN, используемой в процессе сборки Docker для установки пакетов или изменения файлов, выбор между форматами shell и exec может зависеть от необходимости обработки оболочки. Форма shell необходима для команд, требующих функциональности оболочки, таких как пайплайны или глобализация файлов. Однако форма exec предпочтительнее для простых команд без функций оболочки, поскольку она снижает сложность и вероятность ошибок.

# Shell form, useful for complex scripting
RUN apt-get update && apt-get install -y nginx
 
# Exec form, for direct command execution
RUN ["apt-get", "update"]
RUN ["apt-get", "install", "-y", "nginx"]

Инструкции CMD и ENTRYPOINT

Эти инструкции управляют поведением контейнера во время выполнения. Использование формата exec с ENTRYPOINT гарантирует, что основное приложение контейнера будет обрабатывать сигналы напрямую, что очень важно для правильного поведения при запуске и завершении работы. CMD может предоставлять параметры по умолчанию для ENTRYPOINT в exec-формате.

# ENTRYPOINT with exec form for direct process control
ENTRYPOINT ["httpd"]
 
# CMD provides default parameters, can be overridden at runtime
CMD ["-D", "FOREGROUND"]

Гибкость и обработка сигналов

Использование ENTRYPOINT в формате exec и CMD для указания параметров гарантирует, что контейнеры смогут корректно обрабатывать сигналы операционной системы, оперативно реагировать на вводимые пользователем данные и поддерживать безопасную и предсказуемую работу.

Такая настройка особенно полезна для контейнеров, в которых запущены критически важные приложения, требующие корректного поведения при выключении и настройке. 

Ключевые различия между shell и exec
Ключевые различия между shell и exec

Ниже — таблицы, обобщающие всё вышесказанное.

Дерево решений — RUN, CMD, ENTRYPOINT
Дерево решений — RUN, CMD, ENTRYPOINT
Дерево решений — shell и exec
Дерево решений — shell и exec

Примеры

В следующем разделе будут рассмотрены различия на высоком уровне между CMD и ENTRYPOINT.

В этих примерах инструкция RUN не рассматривается, поскольку вы можете легко принять решение, сравнив два разных формата.

Тестовый Dockerfile

# Use syntax version 1.3-labs for Dockerfile
# syntax=docker/dockerfile:1.3-labs
 
# Use the Ubuntu 20.04 image as the base image
FROM ubuntu:20.04
 
# Run the following commands inside the container:
# 1. Update the package lists for upgrades and new package installations
# 2. Install the apache2-utils package (which includes the 'ab' tool)
# 3. Remove the package lists to reduce the image size
#
# This is all run in a HEREDOC; see
# https://www.docker.com/blog/introduction-to-heredocs-in-dockerfiles/
# for more details.
#
RUN <<EOF
apt-get update;
apt-get install -y apache2-utils;
rm -rf /var/lib/apt/lists/*;
EOF
 
# Set the default command
CMD ab

Первая сборка

Мы создадим этот образ и пометим его как ab.

$ docker build -t ab .
 
[+] Building 7.0s (6/6) FINISHED                                                               docker:desktop-linux
 => [internal] load .dockerignore                                                                              0.0s
 => => transferring context: 2B                                                                                0.0s
 => [internal] load build definition from Dockerfile                                                           0.0s
 => => transferring dockerfile: 730B                                                                           0.0s
 => [internal] load metadata for docker.io/library/ubuntu:20.04                                                0.4s
 => CACHED [1/2] FROM docker.io/library/ubuntu:20.04@sha256:33a5cc25d22c45900796a1aca487ad7a7cb09f09ea00b779e  0.0s
 => [2/2] RUN <<EOF (apt-get update;...)                                                                       6.5s
 => exporting to image                                                                                         0.0s
 => => exporting layers                                                                                        0.0s
 => => writing image sha256:99ca34fac6a38b79aefd859540f88e309ca759aad0d7ad066c4931356881e518                   0.0s
 => => naming to docker.io/library/ab 

Запуск с помощью CMD ab

Без каких-либо аргументов мы получим блок использования.

$ docker run ab
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds
<-- SNIP -->

Но если мы запускаем ab и включаю URL для проверки, то сначала получаем ошибку:

$ docker run --rm ab https://jayschmidt.us
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "https://jayschmidt.us": stat https://jayschmidt.us: no such file or directory: unknown.

Проблема заключается в том, что строка, передаваемая в командной строке — https://jayschmidt.us — переопределяет инструкцию CMD. Это недопустимая команда, которая приводит к возникновению ошибки. Поэтому нам нужно указать команду для запуска:

$ docker run --rm  ab ab https://jayschmidt.us/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
 
Benchmarking jayschmidt.us (be patient).....done
 
 
Server Software:        nginx
Server Hostname:        jayschmidt.us
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-ECDSA-AES256-GCM-SHA384,256,256
Server Temp Key:        X25519 253 bits
TLS Server Name:        jayschmidt.us
 
Document Path:          /
Document Length:        12992 bytes
 
Concurrency Level:      1
Time taken for tests:   0.132 seconds
Complete requests:      1
Failed requests:        0
Total transferred:      13236 bytes
HTML transferred:       12992 bytes
Requests per second:    7.56 [#/sec] (mean)
Time per request:       132.270 [ms] (mean)
Time per request:       132.270 [ms] (mean, across all concurrent requests)
Transfer rate:          97.72 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       90   90   0.0     90      90
Processing:    43   43   0.0     43      43
Waiting:       43   43   0.0     43      43
Total:        132  132   0.0    132     132

Запуск с помощью ENTRYPOINT

В этом запуске мы удаляем инструкцию CMD ab из Dockerfile, заменяем её на ENTRYPOINT ["ab"] и пересобираем образ.

Это похоже на команду CMD, но есть отличие: когда вы используете ENTRYPOINT, вы не можете переопределить эту команду (если только не используете флаг –entrypoint в команде docker run). Вместо этого все аргументы, переданные команде docker run, рассматриваются как аргументы ENTRYPOINT.

$ docker run --rm  ab "https://jayschmidt.us/"
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
 
Benchmarking jayschmidt.us (be patient).....done
 
 
Server Software:        nginx
Server Hostname:        jayschmidt.us
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-ECDSA-AES256-GCM-SHA384,256,256
Server Temp Key:        X25519 253 bits
TLS Server Name:        jayschmidt.us
 
Document Path:          /
Document Length:        12992 bytes
 
Concurrency Level:      1
Time taken for tests:   0.122 seconds
Complete requests:      1
Failed requests:        0
Total transferred:      13236 bytes
HTML transferred:       12992 bytes
Requests per second:    8.22 [#/sec] (mean)
Time per request:       121.709 [ms] (mean)
Time per request:       121.709 [ms] (mean, across all concurrent requests)
Transfer rate:          106.20 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       91   91   0.0     91      91
Processing:    31   31   0.0     31      31
Waiting:       31   31   0.0     31      31
Total:        122  122   0.0    122     122

Что насчёт синтаксиса?

В приведённом выше примере мы использовали синтаксис ENTRYPOINT ["ab"]. Но ведь ещё можно ввести ENTRYPOINT ab (без кавычек и скобок). Давайте посмотрим, что случится, если мы напишем именно так:

$ docker run --rm  ab "https://jayschmidt.us/"
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds
<-- SNIP -->

Скорее всего, у вас возникнет идея повторно запустить команду docker run, как мы ранее сделали для CMD ab, которая предоставляет как исполняемый файл, так и аргумент:

$ docker run --rm ab ab "https://jayschmidt.us/"
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds
<-- SNIP -->

Это связано с тем, что ENTRYPOINT можно переопределить, если вы добавите аргумент –entrypoint в docker run. Вывод: всегда используйте ENTRYPOINT когда вы хотите принудительно использовать указанный исполняемый файл в контейнере при его запуске.

Подведём итоги: основные выводы и best practices

Процесс принятия решений в выборе между RUN, CMD и ENTRYPOINT, а также между форматами shell и exec, демонстрирует сложную природу Docker. Каждая команда служит определенной цели в экосистеме Docker, влияя на то, как контейнеры создаются, работают и взаимодействуют со своими средами. 

Выбирая правильные инструкции и форматы для каждого конкретного сценария, разработчики могут создавать более надёжные и безопасные образы.

Внедрение этой передовой практики гарантирует, что приложения, развёрнутые в контейнерах Docker, достигают максимальной производительности в различных настройках, улучшая рабочие процессы разработки и развёртывания в производстве.


Спасибо, что дочитали до конца! Хотим предложить вам подписаться на наш блог Хабр, TG‑канал DevOps FM, VC.ru и познакомиться с YouTube. Везде выходит разный, но интересный и полезный контент.

Комментарии (6)


  1. omgiafs
    24.07.2024 00:52
    +6

    Зачем генерировать подобие информации, которая указана в руководстве Docker?

    У меня ощущение, что текстовая часть Интернета сейчас состоит на 40% из комментариев ботов, ещё на 40 - из подобных деривативов различных источников от инструкций до кулинарных рецептов, 10% (скорее всего меньше) - реальные результаты мозговой деятельности людей, и ещё 10 - всё остальное, включая контент для цветаевских "глотателей пустот".

    Жду стартап, который будет в результатах поиска будет выдавать только инструкции и изначальные источники информации, а не миллион раз пережёванный остаток смысла.


    1. Keirichs
      24.07.2024 00:52

      Так выдача готовых инструкций уже есть, ChatGPT с этим прекрасно справляется, ну для чего-то не сложного, разумеется.


    1. SITibekin Автор
      24.07.2024 00:52
      +1

      Спасибо за ваш комментарий!

      Мы придерживаемся такого мнения, что если сама команда Docker’а пишет такие статьи-разборы, значит, у её аудитории есть запрос на такую информацию.

      Ещё хочется отметить, что площадки вроде того же блога у Docker’а или Хабра — это своего рода фильтр: люди приходят сюда с определенными запросами и желаниями, надеясь получить уже готовую информацию, сконцентрированную в одном тексте. Конечно, можно и самому пройтись глазами по документации или вбить свой вопрос GPT-шке. Но если есть и такой способ найти нужную информацию, как чтение статей, то почему бы и нет?

      Мы в свою очередь просто хотели поделиться чем-то полезным :)

      А о чём бы вы хотели почитать?


      1. omgiafs
        24.07.2024 00:52
        +1

        По мотивам ответа выше.

        Прошу, сделайте статью про то, сколько энергии тратится на работу блокчейна Биткоина (т.е. потребление э/э майнерами) и сколько - на работу нейросетей.

        Я уже запасся попкорном и жду с нетерпением. В обсуждение такой статьи, если она будет содержать хоть сколько-нибудь достоверные порядки цифр, предвижу легендарную битву. Моё предположение по прожорливости: нейросети сейчас жрут гораздо больше, чем майнеры, но по этому поводу почему-то никто не визжит и не бегает с Гретой Тунбург подмышкой.


        1. Vlada_McGree
          24.07.2024 00:52

          Да, кстати, тема экологичности тех или иных IT-решений очень интересная. На InfoQ выходила статья от Сары Бергман из Майкрософт, посвященная экологичным решениям для разработки ПО. Там немного цифр есть. По вашей теме ещё не видела подобных материалов, но думаю, что какие исследования проводились (или сейчас проводятся).


  1. Chupaka
    24.07.2024 00:52
    +1

    Глобализация файлов — это мощно...