company_banner

Худшие из так называемых «лучших практик» для Docker

Автор оригинала: Itamar Turner-Trauring
  • Перевод
  • Tutorial

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

Часто пишут, что при сборке Docker-образа не нужно устанавливать обновления безопасности. Я написал несколько опровержений, так что этот совет теперь встречается чуть реже. Но ранее эта рекомендация была на официальной странице с лучшими практиками Docker:

Избегайте RUN apt-get upgrade и dist-upgrade, потому что многие «особые» пакеты из родительских образов не смогут обновиться внутри непривилегированного контейнера.

Поясню: команды RUN выполняются при сборке образа, а не запуске контейнера. В документации говорится: «собирайте образы без установки обновлений безопасности». Тот же совет встречается в hadolint Dockerfile linter (цитируется вышеприведённый текст). Оттуда его тоже скоро должны убрать. Для подавляющего большинства людей, создающих Docker-файлы, это абсолютно ужасный совет. И поскольку встречается он очень часто, давайте рассмотрим некоторые объяснения и разберёмся, почему они ошибочны.

Плохой аргумент №1: вы не можете обновляться внутри непривилегированного контейнера


Итак, собственная документация Docker до недавнего времени предлагала не ставить обновления безопасности, потому что вы «не можете обновляться внутри непривилегированных контейнеров». Для установки обновлений вам нужно работать как root или иной привилегированный пользователь. И да, не стоит исполнять свой Docker-образ из-под root. Но тот факт, что вы устанавливаете обновления безопасности, не означает, что образ должен исполняться с root-привилегиями. Внемлите не тайному, а вполне очевидному решению: сначала установите обновления безопасности, а затем переключитесь на другого пользователя:

FROM debian:buster
# Runs as root:
RUN apt-get update && apt-get -y upgrade

# Switch to non-root user:
RUN useradd --create-home appuser
WORKDIR /home/appuser
USER appuser

# Runs as non-root user:
ENTRYPOINT ["whoami"]

Что если ваш базовый образ уже переключается на не-root-пользователя? Не проблема, можете переключаться в своём Docker-файле туда-обратно между разными пользователями. Например, переключиться на root, установить обновления безопасности, а затем вернуться на не-root-пользователя. Просто для примера:

FROM debian:buster

# Switch to non-root user:
RUN useradd --create-home appuser
WORKDIR /home/appuser
USER appuser
RUN whoami

# Switch back to root.
USER root
RUN whoami

# Runs as non-root user:
USER appuser
RUN whoami

Если выполним это:

$ docker build .
Sending build context to Docker daemon  2.048kB
...
Step 4/9 : USER appuser
 ---> Running in bd9f962c3173
Removing intermediate container bd9f962c3173
 ---> 30c7b4932cfd
Step 5/9 : RUN whoami
 ---> Running in c763f389036f
appuser
Removing intermediate container c763f389036f
 ---> 305bf441eb99
Step 6/9 : USER root
 ---> Running in a7f1d6ae91b8
Removing intermediate container a7f1d6ae91b8
 ---> 5ac4d87a852f
Step 7/9 : RUN whoami
 ---> Running in 81f4bc596dad
root
Removing intermediate container 81f4bc596dad
 ---> 4bc187b4892a
Step 8/9 : USER appuser
 ---> Running in 08db9249418a
Removing intermediate container 08db9249418a
 ---> 651753d0a56e
Step 9/9 : RUN whoami
 ---> Running in c9fb60a9627d
appuser
...

Примечание: Docker-файлы в этой статье не являются примерами лучших практик, потому что добавленная сложность затмит суть статьи. Чтобы писать защищённые, корректные и быстрые Docker-файлы, почитайте мою книгу Python on Docker Production Handbook, в которой описан процесс упаковки и больше 70 лучших практик.

Плохой аргумент №2: устанавливать обновления безопасности должны те, кто сопровождает базовый образ


В документации по лучшим практикам Docker также говорилось, что «если пакет в родительском образе устарел, свяжитесь с его сопровождающими». Было бы здо̒рово, если бы базовые образы всегда содержали обновления безопасности. Но давайте посмотрим, как выглядят реальные базовые образы. Вот наш Docker-файл, в качестве базового образа использовался официальный образ Ubuntu. Сейчас это третий по популярности образ на Docker Hub, то есть пример вполне понятный.

FROM ubuntu:20.04
RUN apt-get update && apt-get -y upgrade

Подтяну самый свежий базовый образ, а затем запущу сборку:

$ docker pull ubuntu 20.04
...
$ docker build .
$ docker build -t updated-debian .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM ubuntu:20.04
 ---> 4dd97cefde62
Step 2/2 : RUN apt-get update && apt-get -y upgrade
 ---> Running in 6a8d1c8c73ac
Get:1 http://archive.ubuntu.com/ubuntu focal InRelease [265 kB]
...
Fetched 17.3 MB in 5s (3644 kB/s)
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
Calculating upgrade...
The following packages will be upgraded:
  libsystemd0 libudev1 libzstd1
3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Need to get 592 kB of archives.
After this operation, 2048 B of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 libsystemd0 amd64 245.4-4ubuntu3.5 [274 kB]
Get:2 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 libudev1 amd64 245.4-4ubuntu3.5 [81.2 kB]
Get:3 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 libzstd1 amd64 1.4.4+dfsg-3ubuntu0.1 [237 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 592 kB in 1s (741 kB/s)
...
Processing triggers for libc-bin (2.31-0ubuntu9.2) ...
Removing intermediate container 6a8d1c8c73ac
 ---> 268228101da0
Successfully built 268228101da0

Установилось три пакета, и последний из них — обновление безопасности. На момент написания статьи, 23 марта 2021, образ ubuntu:20.04 был пересобран 3 марта. 8 марта Ubuntu выпустила обновление безопасности для libzstd1, поэтому пришлось скачать и установить новую версию.

В общем, недостаточно полагаться на базовый образ. И помните, это официальный Docker-образ Ubuntu, благословлённый компанией Docker, сопровождаемый организацией с большими ресурсами. Кто знает, что будут делать маленькие open source-проекты, сопровождаемые кем-то в свободное время?

Плохой аргумент №3: если устанавливаете обновления безопасности, то получите самые свежие версии пакетов


Этот аргумент приведён в блоге Cloudberry Engineering: «Это может прозвучать немного притянуто, но причина вот в чём: вам нужно закрепить версии своих зависимостей, а если вы выполняете apt-get upgrade, то обновите их до последних версий».

Действительно притянуто.

Здесь предполагается неявный процесс, который не упоминается открыто, поэтому многие упускают его из виду. Но тем не менее, безопасный процесс выглядит так:

  1. Вы создаёте список всех закреплённых системных пакетов.
  2. Отслеживаете для них все обновления безопасности.
  3. При выходе обновления устанавливаете его вручную.

Если вы так и делаете — прекрасно. Но такой подход требует много работы, и многие его избегают.

Также не очевидно, что ручное закрепление системных пакетов действительно полезно для большинства из нас. Многие пользователи базовых Docker-образов наподобие python:3.9 никогда не устанавливают новые системные пакеты. Они ставят лишь базовую систему, которая в стабильной операционной системе в основном обновляется для исправлений незначительных ошибок и обновлений безопасности.

Знаете, какой есть хороший и простой способ убедиться, что вы установили обновления безопасности и исправили основные баги? apt-get upgrade. И да, вам не нужно под собой всё менять, поэтому используйте стабильный базовый образ, который меняется только раз в пару лет, вроде Debian Stable или Ubuntu LTS.

Плохой аргумент №4: обновления не работают


Hadolint — это линтер Docker-файлов, который жалуется при использовании apt-get upgrade (хотя скоро это исправят). На соответствующей вики-странице цитируются некоторые из рассмотренных выше аргументов: «[обновление пакетов] часто может сбоить, если что-то пытается модифицировать init или изменить устройство внутри контейнера». Справедливости ради, исходная статья была написана в 2014-м, а тогда было совсем другое время (сейчас статья удалена, и я нашёл оригинал с помощью Wayback Machine). Первый релиз Docker был в 2013-м, так что да, в те годы дистрибутивы Linux могли и не работать с Docker из коробки. Но сегодня уже не 2014-й, и дистрибутивы Linux очень хорошо понимают, что для работы с Docker им нужны обновления пакетов.

Пожалуйста, устанавливайте обновления безопасности


Итак, мнение, что вам не следует устанавливать обновления безопасности, основано на:

  • Устаревших проблема («обновления не работают»).
  • Теоретических идеальных условиях, в которых мы не живём («базовый образ установит обновления»).
  • Глупостях («вы не можете обновлять внутри непривилегированных контейнерах»).
  • Необходимости в тяжёлых процессах вроде фиксирования всех системных пакетов, что большинству из нас не нужно. Хотя вам нужно фиксировать свои Python-зависимости, потому что они значительно меняются гораздо чаще, чем пакеты в длительно поддерживаемых Linux-дистрибутивах.

Пожалуйста, выполняйте run dnf/apk/apt-get upgrade в своём Docker-файле, вам действительно стоит устанавливать обновления безопасности в Docker-образы. И после этого убедитесь, что кеширование Docker не сломало ваше обновление.
Mail.ru Group
Строим Интернет

Похожие публикации

Комментарии 9

    0
    Больше обновлений безопасности богу обновлений безопасности!
    Всё так, только потом apt-get clean не забываем делать
      –3

      Так зачем? Ну если я свой образ использую для своего проекта, все крутится внутри и не торчит наружу, зачем мне самому себе вставлять палки в колеса в виде нерелевантного кода (обновление не относится к прямым зависимостям моего приложения), невоспроизводимых билдов (ну если версии в apt-get install ещё можно запинить, то системные то не особо), увеличения размера образа? А все ради какой-то сомнительной "безопасности"

        0

        Если вы что-то через apt и ко ставите в докерфайле, то воспроизводимых билдов у вас и так нет, если версии не пините.

          +1

          Аргумент вида "все равно ничего нет, давайте ещё больше неопределенности внесём, какая разница".

            0

            Аргумент вида "не стоит усложнять себе и другим жизнь из-за стремления к идеалу, которого знаешь как достичь, но полностью не собираешься"

          +1
          Лично вам может и незачем, но статья то для всех, а среди всех совершенно точно есть проекты, смотрящие наружу, и там безопасность не так уж сомнительна
          0
          Что делать с тем что обновления увеличивают размер образа? Делать squash или свой базовый образ и обновлять все образы, по цепочке? Так получается весь мир хоть каждый день перестраивай.
            0
            многие «особые» пакеты из родительских образов не смогут обновиться внутри непривилегированного контейнера


            Здесь имеется ввиду не запуск команд внутри контейнера из под root, а запуск самого контейнера с ключом --privileged, поэтому ваш совет

            сначала установите обновления безопасности, а затем переключитесь на другого пользователя


            не сработает.

            К тому же стартовать контейнер и каждый раз стягивать обновления долго. Лучше держите образ с последними обновлениями, а еще лучше держите автоматизированный процесс который будет поддерживать обновления
              0
              Для устранения проблем безопасности можно воспользоваться
              — Snyk вполне не плохо себя зарекомендовал
              — Wazuh монстр для комплексной безопасности
              — inspec так же вполне себе не плох
              Ну и конечно pre-commit с хуками

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое