Рекомендации по запуску Buildah внутри контейнера

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



    Многих привлекает идея выполнять сборку контейнерных OCI-образов в рамках Kubernetes или подобной системы. Допустим, у нас есть CI/CD, которая постоянно собирает образы, тогда что-то типа Red Hat OpenShift/Kubernetes было бы весьма полезно с точки зрения распределения нагрузки при сборке. До недавних пор большинство людей просто давали контейнерам доступ к Docker-сокету и разрешали выполнять команду docker build. Мы уже несколько лет назад показывали, что это очень небезопасно, фактически, это даже хуже, чем давать беспарольный root или sudo.

    Поэтому люди постоянно пытаются запускать Buildah в контейнере. Короче, мы создали пример того, как, на наш взгляд, лучше всего запускать Buildah внутри контейнера, и выложили соответствующие образы на quay.io/buildah. Приступим…

    Настройка


    Эти образы собраны из Dockerfiles, которые можно найти в репозитории Buildah в папке buildahimage.
    Здесь мы рассмотрим стабильную версию Dockerfile.

    # stable/Dockerfile
    #
    # Build a Buildah container image from the latest
    # stable version of Buildah on the Fedoras Updates System.
    # https://bodhi.fedoraproject.org/updates/?search=buildah
    # This image can be used to create a secured container
    # that runs safely with privileges within the container.
    #
    FROM fedora:latest
    
    # Don't include container-selinux and remove
    # directories used by dnf that are just taking
    # up space.
    RUN yum -y install buildah fuse-overlayfs --exclude container-selinux; rm -rf /var/cache /var/log/dnf* /var/log/yum.*
    
    # Adjust storage.conf to enable Fuse storage.
    RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
    

    Вместо OverlayFS, реализованной на уровне Linux-ядра хоста, мы используем внутри контейнера программу fuse-overlay, поскольку на текущий момент OverlayFS может выполнять монтирование, только если дать ей полномочия SYS_ADMIN средствами Linux capabilities. А мы хотим запускать свои Buildah-контейнеры без каких-либо привилегий уровня root. Fuse-overlay работает довольно быстро и по производительности лучше, чем storage-драйвер VFS. Обратите внимание, что при запуске Buildah-контейнера, использующего Fuse, требуется предоставить устройство /dev/fuse.

    podman run --device /dev/fuse quay.io/buildahctr ...
    RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
    

    Далее мы создаем каталог для дополнительных хранилищ. Container/storage поддерживает концепцию подключения дополнительных read-only хранилищ образов. Например, можно настроить overlay storage area на одной машине, а затем средствами NFS подмонтировать это хранилище на другой машине и использовать образы из него без скачивания через pull. Нам это хранилище нужно для того, чтобы иметь возможность подключить в качестве тома какое-нибудь хранилище образов с хоста и использовать его внутри контейнера.

    # Set up environment variables to note that this is
    # not starting with user namespace and default to
    # isolate the filesystem with chroot.
    ENV _BUILDAH_STARTED_IN_USERNS="" BUILDAH_ISOLATION=chroot
    

    И наконец, используя переменную окружения BUILDAH_ISOLATION, мы говорим, что по умолчанию Buildah-контейнер должен запускаться с изоляцией chroot. Дополнительная изоляция здесь не требуется, поскольку мы и так уже работаем в контейнере. Для того, чтобы Buildah создавал свои собственные контейнеры с разделением пространств имен, требуется привилегия SYS_ADMIN, а для этого придется ослабить для контейнера правила SELinux и SECCOMP, что противоречит нашей установке выполнять сборку из безопасного контейнера.

    Запускаем Buildah внутри контейнера


    Рассмотренная выше схема образа Buildah-контейнера позволяет гибко варьировать способы запуска таких контейнеров.

    Скорость против безопасности


    Компьютерная безопасность – это всегда компромисс между скоростью выполнения процесса и тем, сколько защиты вокруг этого накручено. Это утверждение справедливо и при сборке контейнеров, поэтому ниже мы рассмотрим варианты такого компромисса.

    Рассмотренный выше контейнерный образ будет держать свое хранилище в /var/lib/containers. Поэтому нам нужно подмонтировать контент в эту папку, и то, как мы это сделаем, будет сильно влиять на скорость сборки контейнерных образов.

    Рассмотрим три варианта.

    Вариант 1. Если требуется максимальная безопасность, то для каждого контейнера можно создавать свою папку для containers/image и подключать ее к контейнеру через volume-mount. А кроме того, размещать context directory в самом контейнере, в папке /build:

    # mkdir /var/lib/containers1
    # podman run -v ./build:/build:z -v /var/lib/containers1:/var/lib/containers:Z quay.io/buildah/stable\
    buildah  -t image1 bud /build
    # podman run -v /var/lib/containers1:/var/lib/containers:Z quay.io/buildah/stable buildah  push \ image1 registry.company.com/myuser
    # rm -rf /var/lib/containers1
    

    Безопасность. Работающий в таком контейнере Buildah имеет максимальную безопасность: ему не дают никаких root-привилегий средствами capabilities, и к нему применяются все ограничения SECOMP и SELinux.Такой контейнер даже можно запускать с изоляцией User Namespace, добавив опцию вроде --uidmap 0:100000:10000.

    Производительность. А вот производительность здесь минимальна, поскольку любые образы из контейнерных реестров каждый раз копируются на хост, и кэширование не работает от слова «никак». Завершая свою работу, Buildah-контейнер должен отправлять образ в реестр и уничтожать контент на хосте. Когда контейнерный образ будет собираться в следующий раз, его придется заново скачивать из реестра, поскольку на хосте к тому моменту уже ничего не останется.

    Вариант 2. Если нужна производительность уровня Docker, то можно подмонтировать container/storage хоста прямо в контейнер.

    # podman run -v ./build:/build:z -v /var/lib/containers:/var/lib/containers --security-opt label:disabled quay.io/buildah/stable buildah  -t image2 bud /build
    # podman run -v /var/lib/containers:/var/lib/containers --security-opt label:disabled \ quay.io/buildah/stable buildah push image2 registry.company.com/myuser
    

    Безопасность. Это наименее безопасный способ сборки контейнеров, поскольку здесь контейнеру разрешено модифицировать хранилище на хосте, и потенциально он может подсунуть Podman’у или CRI-O вредоносный образ. Кроме того, потребуется отключить SELinux separation, чтобы находящиеся в Buildah-контейнере процессы могли взаимодействовать с хранилищем на хосте. Обратите внимание, что этот вариант все равно лучше Docker-сокета, поскольку контейнер блокируется оставшимися функциями безопасности и не может просто взять и запустить какой-нибудь контейнер на хосте.

    Производительность. Здесь она максимальна, поскольку полностью задействуется кэширование. Если Podman или CRI-O уже успели скачать нужный образ на хост, то Buildah-процессу внутри контейнера не придется скачивать его заново, а последующие сборки на основе этого образа также смогут взять нужное из кэша.

    Вариант 3. Суть этого способа в том, чтобы объединить несколько образов в один проект с общей папкой для контейнерных образов.

    # mkdir /var/lib/project3
    # podman run --security-opt label:level=s0:C100, C200 -v ./build:/build:z \
    -v /var/lib/project3:/var/lib/containers:Z quay.io/buildah/stable buildah  -t image3 bud /build
    # podman run --security-opt label:level=s0:C100, C200 \
    -v /var/lib/project3:/var/lib/containers quay.io/buildah/stable buildah push image3 \ registry.company.com/myuser
    

    В этом примере мы не удаляем папку проекта (/var/lib/project3) между запусками, поэтому все последующие сборки в рамках проекта пользуются преимуществами кэширования.

    Безопасность. Нечто среднее между вариантами 1 и 2. С одной стороны, контейнеры не имеют доступа к контенту на хосте и, соответственно, не могут подсунуть что-то плохое в хранилище образов Podman/CRI-O. С другой стороны, в рамках своего проекта контейнер может вмешиваться в сборку других контейнеров.

    Производительность. Здесь она хуже, чем при использовании общего кэша на уровне хоста, поскольку нельзя использовать образы, уже скаченные ранее средствами Podman/CRI-O. Однако после того, как Buildah скачает образ, этот образ можно использовать в любых последующих сборках в рамках проекта.

    Дополнительные хранилища


    У containers/storage есть такая классная штука как дополнительные хранилища (additional stores), благодаря которой при запуске и сборке контейнеров контейнерные движки могут использовать внешние хранилища образов в режиме read-only оверлея. По сути, в файл storage.conf можно добавить одно или несколько хранилищ «только для чтения», чтобы затем при запуске контейнера контейнерный движок искал в них нужный образ. Причем он будет скачивать образ из реестра только в том случае, если не найдет его ни в одном из этих хранилищ. Контейнерный движок сможет писать только в доступные для записи хранилища…

    Если прокрутить вверх и посмотреть Dockerfile, который мы используем для сборки образа quay.io/buildah/stable, то там есть такие строки:

    # Adjust storage.conf to enable Fuse storage.
    RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
    RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
    

    В первой строке мы модифицируем /etc/containers/storage.conf внутри контейнерного образа, говоря storage-драйверу использовать “additionalimagestores” в папке /var/lib/shared. А в следующей строке создаем общую папку и добавляем пару lock-файлов, чтобы не было ругани со стороны containers/storage. По сути, мы просто создаем пустое хранилище контейнерных образов.

    Если смонтировать containers/storage уровнем выше этой папки, что Buildah сможет использовать образы.

    Теперь вернемся к рассмотренному выше Варианту 2, когда Buildah-контейнер может читать и писать в containers/store на хостах и, соответственно, имеет максимальную производительности за счет кэширования образов на уровне Podman/CRI-O, но дает минимум безопасности, поскольку может писать прямо в хранилища. А теперь прикрутим сюда дополнительные хранилища и получим лучшее из двух миров.

    # mkdir /var/lib/containers4
    # podman run -v ./build:/build:z -v /var/lib/containers/storage:/var/lib/shared:ro -v \ /var/lib/containers4:/var/lib/containers:Z  quay.io/buildah/stable \
     buildah  -t image4 bud /build
    # podman run -v /var/lib/containers/storage:/var/lib/shared:ro  \
    -v >/var/lib/containers4:/var/lib/containers:Z quay.io/buildah/stable buildah push image4 \ registry.company.com/myuser
    # rm -rf /var/lib/continers4
    

    Обратите внимание, что /var/lib/containers/storage хоста смонтирована в /var/lib/shared внутри контейнера в режиме read-only. Поэтому работая в контейнере, Buildah может использовать любые образы, которые ранее уже были скачаны средствами Podman/CRI-O (привет, скорость), но писать при этом может только в свое собственное хранилище (привет, безопасность). Также обратите внимание, что это делается без отключения SELinux separation для контейнера.

    Важный нюанс


    Ни в коем случае не следует удалять никакие образы из нижележащего хранилища. В противном случае Buildah-контейнер может вылететь.

    И это отнюдь не все преимущества


    Возможности дополнительных хранилищ не ограничиваются только вышеописанным сценарием. Например, можно разместить все контейнерные образы в общем сетевом хранилище и дать к нему доступ всем Buildah-контейнерам. Допустим, у нас есть сотни образов, которые наша система CI/CD регулярно использует для сборки контейнерных образов. Концентрируем все эти образы на каком-то одном хосте-хранилище и затем, используя предпочтительные средства сетевого хранения (NFS, Gluster, Ceph, ISCSI, S3...), открываем общий доступ к этому хранилищу всем нодам Buildah или Kubernetes.

    Теперь достаточно подмонтировать это сетевое хранилище в контейнер Buildah на /var/lib/shared и все – Buildah-контейнерам больше вообще не придется скачивать образы через pull. Таким образом мы выбрасываем фазу предварительного наполнения (pre-population) и сразу готовы выкатывать контейнеры.

    И конечно же, это можно использовать в рамках действующей системы Kubernetes или контейнерной инфраструктуры, чтобы запускать и выполнять контейнеры где угодно без какого-либо скачивания образов через pull. Более того, реестр контейнеров, получая push-запрос на загрузку в него обновленного образа, может автоматически отправлять этот образ в общее сетевое хранилище, где он мгновенно становится доступен всем нодам.

    Размеры контейнерных образов иногда могут достигать многих гигабайт. Функционал дополнительных хранилищ позволяет обойтись без клонирования таких образов по нодам и делает запуск контейнеров практически мгновенным.

    Кроме того, в данный момент мы работаем над новой функцией overlay volume mounts, которая сделает сборку контейнеров еще быстрее.

    Заключение


    Выполнять Buildah внутри контейнера в среде Kubernetes/CRI-O, Podman или даже в Docker вполне реально, к тому же это просто и гораздо безопаснее, чем использовать docker.socket. Мы значительно повысили гибкость работы с образами, и теперь вы можете запускать их различными способами для оптимального баланса между безопасностью и производительностью.

    Функционал дополнительных хранилищ позволяет ускорить или даже полностью устранить скачивание образов на ноды.
    Red Hat
    Программные решения с открытым исходным кодом

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

      0

      Спасибо. Очень интересно. Но это все теория. А Интер сна практика. Меня очень заинтересовала фраза, что buildah может вылететь, если нижележащий образ был удален в процессе. А ведь такое может действительно происходить. Например я заканчивалось место на ноде и произошла чистка образов. Есть ли способ определить, что сейчас образ используется buildah и заблокировать его от удаления? Что думаете на этот счёт?

        0

        Есть же kaniko!

          0

          Вот это я 2 года ждал, объяснял коллегам как на самом же деле решить вопрос доставки контейнеров. Теперь благодаря вам плюс к аргументации! Спасибо! Всегда знал, что на нодах должно быть минимальное количество данных (логи, контейнеры, конфигурации) и на них не надо никогда заливать контейнеры, а потом вычищать старые. Для геораспределенных систем можно поддерживать локальное хранилище, доступ к которому можно давать своим геоднс, бгп, и кучей других вариантов на вкус и цвет!

            +1
            Какое-то время уже гоняю podman, теперь стоит попробовать и buildah.
            Да у канико есть хороший аспект под капотом, который позволяет построить сборочную линию без содержания условного обслуживающего узла (worker'а) 24/7; но и у buildah есть свои сильные стороны.

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

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