Русский
Русский
English
Статистика
Реклама

Dockerfile

Лучшие практики при написании безопасного Dockerfile

14.01.2021 10:07:09 | Автор: admin

В данной статье мы рассмотрим небезопасные варианты написания собственного Dockerfile, а также лучшие практики, включая работу с секретами и встраивание инструментов статического анализа. Тем не менее для написания безопасного Dockerfile наличия документа с лучшими практиками мало. В первую очередь требуется организовать культуру написания кода. К ней, например, относятся формализация и контроль процесса использования сторонних компонентов, организация собственных Software Bill-of-Materials (SBOM), выстраивание принципов при написании собственных базовых образов, согласованное использование безопасных функций, и так далее. В данном случае отправной точкой для организации процессов может служить модель оценки зрелости BSIMM. Однако в этой статьей пойдет речь именно о технических аспектах.

Безопасное написание Dockerfile

Задавать LABEL и не использовать тег latest

Использовать тег latest для базовых образов крайне нежелательно, так как это создает неопределенное поведение по мере того, как базовый образ будет обновляться. К тому же это не гарантирует, что последние версии базовых образов не будут уязвимы. Наиболее предпочтительный вариант можно увидеть на примере ниже:

FROM redis@sha256:3479bbcab384fa343b52743b933661335448f816LABEL version 1.0LABEL description "Test image for labels"

Для описания собственного образа также рекомендуется использовать инструкцию LABEL, чтобы исключить ошибку со стороны тех, кто будет использовать ваш образ в дальнейшем. Хорошей практикой также является определение LABEL securitytxt.

LABEL securitytxt="http://personeltest.ru/aways/www.example.com/.well-known/security.txt"

Ссылка в ярлыке security.txt позволяет предоставить контактную информацию для исследователей, которые могли обнаружить проблему безопасности в образе. Особенно это актуально, если образ является публично доступным . Больше деталей по этому ярлыку можно найти тут.

Не использовать автоматическое обновление компонентов

Использование apt-get upgrade,yum update может привести к тому, что внутри вашего контейнера будет произведена установка неизвестного вам ранее ПО, либо ПО уязвимой версии. Чтобы избежать этого, устанавливайте пакеты, четко указывая версию для каждого из них. Каждая версия должна проверяться на наличие в ней уязвимостей до того, как компонент окажется внутри вашего контейнера. Версия компонента может быть проверена с помощью инструментов класса Software Composition Analysis (SCA).

Пример установки компонента с точностью до версии:

RUN apt-get install cowsay=3.03+dfsg1-6

Производить скачивание пакетов безопасным образом

Использование curl и wget без мер предосторожности позволяет злоумышленнику выполнять скачивание нежелательных компонентов с неизвестных ресурсов (атака "человек-посередине", при которой злоумышленник может перехватить незащищенный трафик и подменить скачиваемый нами пакет на зловредный). Это полностью разрушает концепцию Zero trust, согласно которой необходимо проверять любое подключение или действие до предоставления доступа (или в данном случае установки компонента с неизвестного ресурса). Соответственно следующий сценарий скачивания будет являться грубейшей ошибкой, так как происходит выполнение скрипта, полученного из недоверенного источника по небезопасному каналу без должных проверок:

RUN wget http://somesite.com/some-packet/install.sh | sh

Чтобы убедиться, что скаченный компонент будет являться действительно тем, что мы ожидаем, в качестве решения может подойти использование GNU Privacy Guard (GPG). Разберемся, как это работает.

В большинстве случаев вендоры вместе с библиотекой или ПО предоставляют также хеш-сумму, которую мы можем проверить при скачивании. Данная хеш-сумма подписывается вендорами с помощью закрытого ключа в рамках GPG, а открытые ключи помещаются в репозитории. Следующий пример демонстрирует, как может выглядеть безопасное скачивание компонентов Node.js:

RUN gpg --keyserver pool.sks-keyservers.net \--recv-keys 7937DFD2AB06298B2293C3187D33FF9D0246406D \            114F43EE0176B71C7BC219DD50A3051F888C628DENV NODE_VERSION 0.10.38ENV NPM_VERSION 2.10.0RUN curl -SLO "http://nodejs.org/dist/v$NODE_VERSION/node-v \$NODE_VERSION-linux-x64.tar.gz" \&& curl -SLO "http://nodejs.org/dist/v$NODE_VERSION/\SHASUMS256.txt.asc" \&& gpg --verify SHASUMS256.txt.asc \&& grep " node-v$NODE_VERSION-linux-x64.tar.gz$" SHASUMS256.txt.asc | sha256sum -c -

Давайте разберемся, что здесь происходит:

  1. Получение открытых GPG-ключей

  2. Скачивание Node.js пакета

  3. Скачивание хеш-суммы Node.js пакета на базе алгоритма SHA256

  4. Использование GPG-клиента для проверки, что хеш-сумма подписана тем, кто владеет закрытыми ключами

  5. Проверяем, что вычисленная хеш-сумма от пакета совпадает со скаченной с помощью sha256sum

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

Иногда разработчикам бывает необходимо использовать сторонние репозитории для установки компонента с помощью deb или rpm. В данном случае мы также можем воспользоваться GPG, а проверка хеш-суммы будет проводиться менеджерами пакетов при скачивании.

Пример безопасного добавления GPG-ключей вместе с источником пакетов.

RUN apt-key adv --keyserver hkp://pgp.mit.edu:80 \--recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62RUN echo "deb http://nginx.org/packages/mainline/debian/\jessie nginx" >> /etc/apt/sources.list

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

Пример для SHA256:

RUN curl -sSL -o redis.tar.gz \http://download.redis.io/releases/redis-3.0.1.tar.gz \&& echo "0e21be5d7c5e6ab6adcbed257619897db59be9e1ded7ef6fd1582d0cdb5e5bb7 \*redis.tar.gz" | sha256sum -c -

Не использовать ADD

Инструкция ADD, получив в качестве параметра путь к архиву, автоматически распакует этот архив при своем выполнении. Это может привести, в свою очередь, к появлению zip-бомбы внутри контейнера. При распаковке zip-бомба может вызвать отказ в обслуживании (DoS) приложения, путем заполнения всего выделенного свободного места.

Еще одна небольшая особенность ADD команды заключается в том, что вы можете передать ей URL в качестве параметра, и она будет извлекать контент во время сборки, что также может привести к атаке "человек-посередине":

ADD https://cloudberry.engineering/absolutely-trust-me.tar.gz

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

Задавать USER в конце Dockerfile

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

RUN groupadd -r user_grp && useradd -r -g user_grp userUSER user

Использовать gosu вместо sudo в процессе инициализации

Утилита gosu будет полезна, когда необходимо предоставлять root доступ после сборки Dockerfile во время инициализации, но при этом приложение должно запускаться в непривилегированном режиме.

В примере ниже выполняется команда chown в рамках entrypoint-скрипта, требующая права root, после чего приложение продолжает выполняться из-под пользователя redis.

#!/bin/bashset -eif [ "$1" = 'redis-server' ];  then    chown -R redis .     exec gosu redis "$@"  fiexec "$@"

Основная цель инструмента - запуск процессов от определенного пользователя, но в отличие от sudo и su, gosu не делает fork процессов, как показано ниже:

$ docker run -it --rm ubuntu:trusty su -c 'exec ps aux'USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMANDroot         1  0.0  0.0  46636  2688 ?        Ss+  02:22   0:00 su -c exec ps aroot         6  0.0  0.0  15576  2220 ?        Rs   02:22   0:00 ps aux$ docker run -it --rm ubuntu:trusty sudo ps auxUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMANDroot         1  3.0  0.0  46020  3144 ?        Ss+  02:22   0:00 sudo ps auxroot         7  0.0  0.0  15576  2172 ?        R+   02:22   0:00 ps aux$ docker run -it --rm -v $PWD/gosu-amd64:/usr/local/bin/gosu:ro ubuntu:trusty gosu root ps auxUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMANDroot         1  0.0  0.0   7140   768 ?        Rs+  02:22   0:00 ps aux

Это сохраняет концепцию, согласно которой контейнер ассоциируется с единственным процессом. Тем не менее, из слов разработчиков gosu не является заменой sudo, так как в рамках данного fork'а происходит взаимодействие с Linux PAM через функции pam_open_session() и pam_close_session(). Использование gosu вместо sudo за пределами процесса инициализации может привести к некорректной работе приложения.

Distroless images и минимальные образы

Можно сильно сократить поверхность атаки злоумышленника отказавшись от базовых образов Linux-дистрибутивов (Ubuntu, Debian, Alpine) и перейдя на Disroless-образы. Это образы, которые содержат только само приложение и необходимые для него зависимости без использования лишних системных компонентов (например, bash). Одним из явных преимуществ, помимо сокращения поверхности атаки и возможностей злоумышленника, является снижение размера образа. Это в свою очередь уменьшает "шум" в результатах сканирования такими инструментами как Trivy, Clair и так далее.

Подобная стратегия имеет много преимуществ с точки зрения безопасности, но сильно затрудняет работу разработчиков, которым необходимо заниматься отладкой внутри контейнера. Альтернативным вариантом может являться использование минимального образа, например, на базе UNIX-сборки Alpine. При ответственном соблюдении данного подхода, образ разработчика будет содержать минимум зависимостей, что позволит сканерам образов сформировать меньший объем результатов, среди которых будет проще отсеять ложные срабатывания.

Цикл статей по созданию минимального образа:

Одним из вариантов, как можно эффективно сократить размер образа является multi-stage сборка, которая ко всему прочему поможет безопасно работать с секретами. Об этом будет в следующем подразделе.

Полезным инструментом также является Docker-slim, позволяющий уменьшить размер написанного образа.

Безопасная работа с секретами

Секреты могут по ошибке помещаться в качестве параметра инструкции ENV или передаваться внутрь образа как текстовый файл. Также секреты могут скачиваться через wget. Подобные сценарии недопустимы, так как злоумышленник может с легкостью получить доступ к секретам. Это можно сделать, например, получив доступ к API Docker:

# docker inspect ubuntu -f {{json .Config.Env}}["SECRET=mypassword", ...]

Также злоумышленник может получить доступ к секретам через логи, директорию /proc или при утечки файлов исходных кодов. В данном случае лучше всего может подойти использование решений класса Vault, например, HashiCorp Vault или Conjur, однако рассмотрим и другие методы.

Соблюдать принцип многоэтапных сборок

Многоэтапная (multi-stage) сборка поможет не только сократить размер вашего образа, но еще и организовать эффективное управление секретами. Основной принцип - извлекать и управлять секретами на промежуточном этапе сборки образа, который позже удалится. В итоге конфиденциальные данные не попадут в сборку конечного образа.

#builderFROM ubuntu as intermediateWORKDIR /appCOPY secret/key /tmp/RUN scp -i /tmp/key build@acme/files .#runnerFROM ubuntuWORKDIR /appCOPY --from=intermediate /app .

Один из минусов этого сценария - сложное кэширование, что ведет к замедлению сборки.

Работа с секретами через BuildKit

С версии Docker 18.09 появляется экспериментальная интеграция со сборщиком BuildKit, которая позволит кроме увеличения производительности, эффективно работать с секретами.

Пример синтаксиса:

# syntax = docker/dockerfile:1.0-experimentalFROM alpine# shows secret from default secret locationRUN --mount=type=secret,id=mysecret cat /run/secrets/mysecre# shows secret from custom secret locationRUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar

После выполнения сборки с помощью buildkit с указанием ключа --secret секретная информация не сохранится в конечном образе.

Пример:

$ docker build --no-cache --progress=plain --secret id=mysecret,src=mysecret.txt .

Подробнее можно прочитать в официальной документации Docker.

Остерегаться рекурсивного копирования

Команда из примера ниже может привести к тому, что файл секрета случайно окажется внутри вашего образа, если находится внутри той же директории. Если вы имеете подобные файлы, не забывайте использовать .dockerignore. Среди файлов внутри образа могут оказаться .git, .aws, .env.

COPY . .

Один из примеров подобных ошибок - кейс с утечкой исходных кодов Twitter Vine. Так в 2016 году специалист по информационной безопасности обнаружил на DockerHub образ vinewww, внутри которого обнаружились исходные коды сервиса Vine, ключи API и секреты сторонних сервисов.

Анализаторы Dockerfile

Используйте анализаторы Dockerfile, которые можно встроить в ваш пайплайн сборки. Это могут быть:

Hadolint - простой линтер Dockerfile. Большая часть проверок не относится к security и основана на официальных рекомендациях Docker (ссылка). Однако для наиболее распространенных ошибок таких проверок будет достаточно.

Conftest - анализатор файлов конфигураций, в том числе Dockerfile. Для проверки Dockerfile требуется предварительно написать правила на языке Rego, который, помимо этого, используется в быстро набирающей технологии Open Policy Agent для защиты облачных сред в процессе эксплуатации. Это позволит сохранить вариативность, возможность кастомизации и не потребует изучения ранее неизвестных языков. Conftest не содержит встроенный набор правил, поэтому подразумевается, что вы будете писать их самостоятельно. В качестве отправной точки можно использовать эту статью.

Для автоматизации процесса проверки можно встроить эти инструменты в пайплайн разработки. Пример встраивания:

Способы и примеры внедрения утилит для проверки безопасности Docker.

Практика

Вы можете попробовать проэксплуатировать распространенные уязвимости при написании Dockerfile с помощью образа Pentest-in-Docker. Пошаговое описание можно найти в репозитории. Одна из главных ошибок - использование debian:wheazy, старого образа Debian, на котором поддерживается не требующийся для работы приложения Bash, содержащий в себе уязвимость удаленного выполнения кода (RCE). Таким образом злоумышленник может получить доступ к сервисной учетной записи www-data, отправив запрос на подключение к reverse-shell. Вторая ошибка - использование sudo, что позволяет злоумышленнику повысить привилегии от www-data до root внутри контейнера. И наконец отсутствие USER в конце Dockerfile из-за чего злоумышленник может выполнять действия из-под root в случае, если получит доступ к API Docker.

Дополнительные ресурсы

Подробнее..

Перевод 10 Kubernetes Security Context, которые необходимо понимать

27.03.2021 20:12:03 | Автор: admin

Безопасно запускать рабочие нагрузки в Kubernetes может быть не простой задачей. Множество различных параметров могут повлиять на безопасность, что требует соответствующих знаний для правильной реализации. Одним из самых мощных инструментов, которые предоставляет Kubernetes в этой области, являются настройки securityContext, которые могут использоваться в каждом манифесте Pod и контейнера. В этой статье мы рассмотрим различные настройки контекста безопасности, выясним, что они означают и как их следует использовать.

  1. runAsNonRoot

  2. runAsUser / runAsGroup

  3. seLinuxOptions

  4. seccompProfile

  5. privileged / allowPrivilegeEscalation

  6. capabilities

  7. readonlyRootFilesystem

  8. procMount

  9. fsGroup / fsGroupChangePolicy

  10. sysctls

Подписывайтесь на телеграм-каналMops DevOps, чтобы не пропустить лучшие статьи, видео и митапы!

Pod vs Container settings

Параметры Kubernetes securityContext определены как в PodSpec, так и в ContainerSpec, а область действия указывается в этой статье с помощью аннотаций [P] и / или [C] рядом с каждым из них. Обратите внимание, что, если параметр доступен и настроен в обеих областях, параметр контейнера будет иметь приоритет.

Теперь, давайте рассмотрим на настройки securityContext:

1. runAsNonRoot[P/C]

Несмотря на то, что контейнер использует namespacesиcgroups для ограничения своих процессов, всего один неверный параметр развертывания, предоставит этим процессам доступ к ресурсам на хосте. Если этот процесс выполняется от имени пользователя root, он имеет тот же доступ, что и учетная запись root хоста, к этим ресурсам. Кроме того, если для уменьшения ограничений (например, procMountилиcapabilities) используются другие настройки модуля или контейнера, наличие корневого UID увеличивает риски их использования. Если у вас нет веской причины, вам никогда не следует запускать контейнер с правами root.

Итак, что делать, если у вас есть образ для развертывания, использующий root?

Часто базовые образы уже созданы и доступны для пользователей, но их использование остается на усмотрение групп разработки или развертывания. Например, официальный образ Node.js поставляется с пользователем node с UID 1000, от имени которого вы можете работать, но они явно не устанавливают его для текущего пользователя в своем Dockerfile. Нам нужно будет либо настроить его во время выполнения с помощью параметра runAsUser, либо изменить текущего пользователя в образе с помощью отдельного файла Dockerfile. Первый предполагает, что UID 1000 может читать файлы смонтированные в appvolume, также не очень распространенный вариант выполнение приложения в отдельном томе. Вместо этого давайте рассмотрим пример использования производного файла Dockerfile для создания собственного образа.

Не слишком углубляясь в создание образов, предположим, что у нас есть готовый пакет npm. Вот минимальный Dockerfile для создания образа на основе node: slim и запуска от имени заданного пользователя node.

FROM node:slimCOPY --chown=node . /home/node/app/   # <--- Copy app into the home directory with right ownershipUSER node                             # <--- Switch active user to nodeWORKDIR /home/node/app                # <--- Switch current directory to appENTRYPOINT ["npm", "start"]           # <--- This will now exec as the node user instead of root

Команда USER, делает node пользователем по умолчанию внутри любого контейнера, запущенного с этого образа.

Вариант 2: Пользователь не определен в базовом образе

Итак, чтобы мы сделали, если бы в базовом образе node пользователь не был бы определен? В большинстве случаем мы просто создаем его в новом Dockerfile и используем. Давайте расширим предыдущий пример, чтобы сделать это:

FROM node:slimRUN useradd somebody -u 10001 --create-home --user-group  # <--- Create a userCOPY --chown=somebody . /home/somebody/app/USER somebodyWORKDIR /home/somebody/appENTRYPOINT ["npm", "start"]

Как видите, единственное изменение - это команада RUN, которая создает нового пользователя - синтаксис может варьироваться в зависимости от дистрибутива базового образа.

ПРИМЕЧАНИЕ: это отлично работает для node.js и npm, но для других инструментов может потребоваться изменить владельца других объектов файловой системы. Если у вас возникнут какие-либо проблемы, обратитесь к документации по вашему инструменту.

2. runAsUser / runAsGroup[P/C]

Образы контейнеров могут иметь определенного пользователя и/или группу, настроенную для запуска процесса. Это можно изменить с помощью параметров runAsUser и runAsGroup. Часто они устанавливаются вместе с монтированием тома, содержащим файлы с одинаковыми идентификаторами владения.

...spec:  containers:  - name: web    image: mycorp/webapp:1.2.3  securityContext:    runAsNonRoot: true    runAsUser: 10001...

Использование этих настроек представляет опасность, поскольку вы изменяете параметры во время запуска контейнера, эти параметры могут быть несовместимы с исходным образом. Например, официальный образ сервера jenkins/jenkins CI работает как группа: пользователь с именем jenkins: jenkins и все его файлы приложений принадлежат этому пользователю. Если мы настроим другого пользователя, он не запустится, потому что этого образ не содержит этого пользователя в файле /etc/passwd. Даже если бы это было так, скорее всего, возникнут проблемы с чтением и записью файлов, принадлежащих jenkins: jenkins. Вы можете в этом убедиться, выполнив простую команду Docker:

$ docker run --rm -it -u eric:eric jenkins/jenkinsdocker: Error response from daemon: unable to find user eric: no matching entries in passwd file.

Как мы упоминали выше, это очень хорошая идея, чтобы процессы контейнера не запускались от имени пользователя root, но не стоит полагаться для этого на параметр runAsUser или runAsGroup. Что если кто-то удалить эти настройки? Не забудьте также установить для runAsNonRoot значение true.

3. seLinuxOptions[P/C]

SELinux - это система управления доступом к приложениям, процессам и файлам в системе Linux, настраиваемая через политики. Она реализует структуру модулей безопасности Linux в ядре Linux. SELinux основана на концепции меток и применяет эти метки ко всем элементам в системе, которые группируют элементы вместе. Эти метки известны как контекст безопасности - не путать с Kubernetes securityContext и состоят из пользователя, роли, типа и необязательного поля уровень - user: role: type: level.

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

Метки SELinux по умолчанию будут применяться средой выполнения контейнера при создании экземпляра контейнера. Параметр seLinuxOptions в securityContext позволяет применять пользовательские метки SELinux. Имейте в виду, что изменение маркировки SELinux для контейнера потенциально может позволить контейнерному процессу выйти из образа контейнера и получить доступ к файловой системе хоста.

По умолчанию метки SELinux будут применяться средой выполнения контейнера при создании экземпляра контейнера. Параметр seLinuxOptions в securityContext позволяет применять пользовательские метки SELinux. Имейте в виду, что изменение маркировки SELinux для контейнера потенциально может позволить контейнерному процессу выйти из образа контейнера и получить доступ к файловой системе хоста.

Обратите внимание, что эта функция будет применяться, только если операционная система хоста поддерживает SELinux.

4. seccompProfile[P/C]

Seccomp означает secure computing mode (безопасный режим вычислений) и является функцией ядра Linux, которая может ограничивать вызовы, которые конкретный процесс может делать из пользовательского пространства в ядро. Профиль seccomp - это определение JSON, обычно состоящее из набора системных вызовов и действия по умолчанию, предпринимаемого при возникновении одного из этих системных вызовов.

{    "defaultAction": "SCMP_ACT_ERRNO",    "architectures": [        "SCMP_ARCH_X86_64",        "SCMP_ARCH_X86",        "SCMP_ARCH_X32"    ],    "syscalls": [        {            "name": "accept",            "action": "SCMP_ACT_ALLOW",            "args": []        },        {            "name": "accept4",            "action": "SCMP_ACT_ALLOW",            "args": []        },        ...    ]}

Использован пример с сайтаhttps://training.play-with-docker.com/security-seccomp/

Kubernetes предоставляет механизм для использования настраиваемых профилей через параметр seccompProfile в securityContext.

seccompProfile:      type: Localhost      localhostProfile: profiles/myprofile.json

Доступно три значения для поля type:

  • Localhost- в дополнительном параметре localhostProfile указан путь к профилю seccomp

  • Unconfined- профиль не применяется

  • RuntimeDefault- используется значение по умолчанию для среды выполнения контейнера (это значение по умолчанию, если тип не указан)

Вы можете применить эти настройки либо в PodSecurityContext, либо в securityContext. Если установлены оба контекста, то используются настройки на уровне контейнера в securityContext. Обратите внимание, что эти параметры актуальны для Kubernetes v1.19 - если вы развертываете более ранние версии, существует другой синтаксис; за подробностями и примерами обратитесь к документации на официальном сайте Kubernetes.

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

5. Избегайте Privileged Containers / Escalations[C]

Предоставление привилегированного статуса контейнеру опасно и обычно используется как более простой способ получения определенных разрешений. Среда выполнения контейнеров контролирует наличие флага privileged, предоставляет контейнеру все привилегии, но снимает ограничения, налагаемые cgroup. Она также может изменить Linux Security Module и позволить процессам внутри контейнера выйти из него.

Контейнеры обеспечивают на хосте изоляцию процессов, поэтому даже если контейнер работает от имени пользователя root, существуют функции, которые container runtime не предоставляет контейнеру. При установленном флаге privileged среда выполнения контейнеров предоставляет полный доступ к файловой системе хоста, что делает этот параметр чрезвычайно опасным с точки зрения безопасности.

Избегайте использования флага privileged и, если вашему контейнеру нужны дополнительные возможности, добавьте только те, которые вам нужны. Если вашему контейнеру не требуется управлять настройками системного уровня в ядре хоста, такими как доступ к оборудованию или конфигурация сетей, и ему нужен доступ к файловой системе хоста, то ему не нужен флаг privileged.

Для более глубокого изучения Privileged Containers рекомендуем статьюPrivileged Docker containersdo you really need them?

6. Linux kernel capabilities[C]

Capabilities - это разрешения на уровне ядра, которые позволяют гранулярно управлять разрешениями на вызовы ядра, вместо того, чтобы запускать все от имени пользователя root. Capabilities позволяют изменять права доступа к файлам, управлять сетевой подсистемой и выполняет общесистемные функции администрирования. Вы можете управлять Capabilities через KubernetessecurityContext. Отдельные сapabilities или список, разделенный запятыми, могут быть представлены в виде массива строк. Кроме того, вы можете использовать сокращение -all для добавления или удаления всех capabilities. Эта конфигурация передается в среду выполнения контейнеров и настраивает capabilities при создании контейнера. Если в securityContext нет раздела capabilities, тогда контейнер создается с набором capabilities по умолчанию, который предоставляет среда выполнения контейнера.

securityContext:      capabilities:        drop:          - all        add: ["MKNOD"]

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

Обратите внимание, что при перечислении capabilities в securityContext вы удаляете префикс CAP_, который ядро использует в именах capabilities. Вы можете использовать утилиту capsh, она выводит информацию о включенных в контейнере capabilities в удобном формате о том, но оставляйте эту утилиту в итоговых контейнерах, так как это позволяет злоумышленнику легко определить, какие capabilities включены! Вы также можете проверить включенные capabilities в файле /proc/1/ status.

7. Запуск контейнеров с read-only filesystem[C]

Если ваш контейнер скомпрометирован и его файловая система доступна для чтения и записи, злоумышленник может изменить его конфигурацию, установить программное обеспечение и потенциально запустить другие эксплойты. Наличие файловой системы только для чтения помогает предотвратить подобные инциденты, ограничивая действия, которые может выполнить злоумышленник. Как правило, контейнеры не должны требовать записи в файловую систему контейнера. Если ваше приложение имеет данные с отслеживанием состояния, вы должны использовать внешний метод сохранения, например базу данных, volume или какую-либо другую службу. Кроме того, убедитесь, что все логи записываются в стандартный вывод и/или на сервер коллектор логов, где они обрабатываются централизованно.

8. procMount[C]

По умолчанию среда выполнения контейнеров маскирует определенные части файловой системы /proc изнутри контейнера, чтобы предотвратить возможные проблемы с безопасностью. Однако бывают случаи, когда к ним требуется доступ, особенно при использовании вложенных контейнеров, которые часто используются как часть процесса сборки в кластере. Есть только два допустимых параметра для этой записи: Default, который поддерживает стандартное поведение среды выполнения контейнера, или Unmasked, который удаляет все маскировки для файловой системы /proc.

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

Наконец, если вам все же нужен доступ к замаскированным участкам /proc, сделайте это только для вложенного контейнера; никогда не открывайте файловую систему /proc вашей хост-системы контейнеру.

9. fsGroup / fsGroupChangePolicy[P]

Параметр fsGroup определяет группу, в которой Kubernetes будет изменять разрешения для всех файлов в томах, когда тома монтируются в Pod. Поведение также контролируется fsGroupChangePolicy, для которого может быть установлено значение onRootMismatch или Always. Если установлено значение onRootMismatch, разрешения будут изменены только в том случае, если они еще не соответствуют разрешениям корневого каталога контейнера.

Будьте осторожны при использовании fsGroup. Изменение группового владения всем томом может вызвать задержки запуска Pod для медленных и/или больших файловых систем. Это также может нанести ущерб другим процессам, которые совместно используют тот же том, если их процессы не имеют разрешений на доступ к новому GID. По этой причине некоторые поставщики общих файловых систем, таких как NFS, не реализуют эту функцию. Эти настройки не влияют на ephemeral volume.

10. sysctls[P]

Sysctls - это функция ядра Linux, которая позволяет администраторам изменять конфигурацию ядра. В хостовой операционной системе Linux они определяются с помощью /etc/sysctl.conf, а также могут быть изменены с помощью утилиты sysctl.

Параметр sysctls в securityContext позволяет изменять определенные sysctls в контейнере. Существует лишь небольшое подмножество sysctls операционной системы, которые можно изменять для каждого контейнера, которые размещены в пространстве имен в ядре. Некоторые из них считаются безопасными. Но большинство считается небезопасным, в зависимости от возможности воздействия на другие модули. Небезопасные sysctls обычно отключены и должны быть специально включены администратором.

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

Примечание о securityContext в процессе запуска

Во многих случаях описанные здесь параметры безопасности сочетаются с контролем доступа на основе политик (policy-based admission control), чтобы гарантировать, что необходимые параметры действительно настроены перед запуском контейнеров в кластер. Комбинируя параметры securityContext с PodSecurityPolicy, вы можете гарантировать, что запускаются только контейнеры, которые соответсвуют политике, принудительно применения определенных параметров securityContext. Параметры securityContext также могут быть добавлены к конфигурации контейнера во время запуска с помощью динамического контроля допуска (Dynamic Admission Control) и использования mutating webhooks.

Заключение

Повышая безопасность при помощи параметров securityContext следует помнить о многом. При правильном использовании они являются очень эффективным инструментом, и мы надеемся, что этот список поможет вашим командам выбрать правильные варианты. Snyk может помочь вам с этим выбором, просканировав ваши yaml-файлы Kubernetes на предмет распространенных ошибок конфигурации.


Телеграм-каналMops DevOps- анонсы вебинаров и конференций, полезные статьи и видео, а также регулярные скидки на обучение!

Подробнее..

Перевод Компиляция контейнеров Dockerfiles, LLVM и BuildKit

05.04.2021 08:09:25 | Автор: admin

Эта статья конкатенация двух статей Адама Гордона Белла (Adam Gordon Bell) из Earthly. Добавил в основную статью про компиляцию контейнеров BuildKit выдержки из его другой статьи про BuildKit для понимания как это работает и как использовать BuildKit напрямую.

Тынис Тийги (Tnis Tiigi), сотрудник Docker и основной разработчик BuildKit, создал BuildKit, чтобы отделить логику построения образов от основного проекта moby и обеспечить возможность дальнейшего развития. BuildKit поддерживает подключаемые интерфейсы, которые позволяют создавать не только образы docker из Dockerfiles. С помощью BuildKit мы можем заменить синтаксис Dockerfile на hlb и заменить формат образа докера на вывод в виде чистого tar-файла. Это лишь одна из возможных комбинаций, которые открывает BuildKit с его подключаемыми бэкэндами и интерфейсами.

Введение

Как получаются контейнеры? Обычно из серии операторов, таких как RUN,FROM и COPY, которые помещаются в Dockerfile и собираются. Но как эти команды превращаются в образ, а затем в работающий контейнер? Понять это можно если пройти этапы создания образа контейнера самостоятельно. Мы создадим образ программно, а затем разработаем обычный синтаксический интерфейс и будем использовать его для создания образа.

docker build

Мы можем создавать образы контейнеров несколькими способами. Мы можем использовать пакеты сборки, мы можем использовать инструменты сборки, такие как Bazel или sbt, но, безусловно, наиболее распространенным способом создания образов является использование docker build с Dockerfile. Таким образом создаются знакомые базовые образы Alpine, Ubuntu и Debian.

Вот пример Dockerfile:

FROM alpineCOPY README.md README.mdRUN echo "standard docker build" > /built.txt"

В этом руководстве мы будем использовать вариации этого Dockerfile.

Мы можем собрать его так:

docker build . -t test

Но что происходит, когда вы вызываете docker build? Чтобы понять это, нам понадобится немного предыстории.

Background

Образ докера состоит из слоев. Эти слои образуют неизменную файловую систему. Образ контейнера также содержит некоторые описательные данные, такие как команда запуска, открываемые порты и монтируемые тома. Когда вы делаетеdocker run образа, он запускается внутри среды выполнения контейнера.

Мне нравится рассматривать образы и контейнеры по аналогии. Если образ похож на исполняемый файл, то контейнер похож на процесс. Вы можете запускать несколько контейнеров из одного образа, а работающий образ - это вовсе не образ, а контейнер.

Продолжая нашу аналогию, BuildKit - это компилятор, как например LLVM. Но в то время как компилятор берет исходный код и библиотеки и создает исполняемый файл, BuildKit берет Dockerfile и путь к файлу и создает образ контейнера.

Docker build использует BuildKit, чтобы превратить Dockerfile в образ докера, образ OCI или другой формат образа. Здесь мы в основном будем использовать BuildKit напрямую.

Как работают компиляторы?

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

Компиляция классического Hello, World на С в ассемблерный код x86 с использованием интерфейса Clang для LLVM выглядит так:

Создание образа из файла докеров работает аналогичным образом:

BuildKit передается Dockerfile и контексту сборки, который является текущим рабочим каталогом на приведенной выше схеме. Проще говоря, каждая строка в файле Dockerfile превращается в слой в итоговом изображении. Одним из существенных отличий построения образа от компиляции является контекст сборки. Входные данные компилятора ограничены исходным кодом, тогда как docker build принимает ссылку на файловую систему хоста в качестве входных данных и использует ее для выполнения таких действий, как COPY.

В чем подвох

На предыдущей диаграмме, где компиляции Hello, World отображена за один шаг, упущена важная деталь. Если бы каждый компилятор представлял собой вручную закодированный маппинг с языка высокого уровня на машинный код x86, то переход на процессор Apple M1 был бы довольно сложным, поскольку он имеет другой набор инструкций.

Авторы компилятора преодолели эту проблему, разбив компиляцию на фазы. Традиционные фазы - это frontend (интерфейс), backend и middle, которую иногда называют оптимизатором (optimizer), и она имеет дело в основном с внутренним представлением (IR).

Такой поэтапный подход означает, что вам не нужен новый компилятор для каждой новой машинной архитектуры. Вместо этого вам просто нужен новый backend. Вот пример того, как это выглядит в LLVM:

Промежуточные представления

Такой подход с использованием нескольких бэкэндов позволяет LLVM ориентироваться на ARM, X86 и многие другие компьютерные архитектуры, используя промежуточное представление LLVM (IR) в качестве стандартного протокола. LLVM IR - это удобочитаемый язык программирования, который серверная часть должна принимать в качестве входных данных. Чтобы создать новый бэкэнд, вам нужно написать переводчик из LLVM IR в целевой машинный код. Этот перевод - основная задача каждого бэкэнда.

Когда у вас есть этот IR, у вас есть протокол, который различные фазы компилятора могут использовать в качестве интерфейса, и вы можете создавать не только множество бэкэндов, но и множество интерфейсов (frontend). LLVM имеет интерфейсы для множества языков, включая C, Julia, Objective-C, Rust и Swift.

Если вы можете написать перевод со своего языка на LLVM IR, LLVM сможет перевести этот IR в машинный код для всех поддерживаемых им бэкэндов. Эта функция трансляции - основная задача внешнего интерфейса компилятора.

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

BuildKit

Образы, в отличие от исполняемых файлов, имеют собственную изолированную файловую систему. Тем не менее, задача создания образа очень похожа на компиляцию исполняемого файла. Они могут иметь различный синтаксис (dockerfile1.0, dockerfile1.2), и результат должен быть нацелен на несколько архитектур компьютеров (arm64 против x86_64).

LLB для Dockerfile так же как LLVM IR для C BuildKit Readme

Это сходство не ускользнуло от внимания создателей BuildKit. BuildKit имеет собственное промежуточное представление LLB. И там, где LLVM IR имеет такие вещи, как вызовы функций и стратегии сборки мусора, LLB имеет монтируемые файловые системы и выполнение операторов.

LLB определяется как буфер протокола, а это означает, что внешние интерфейсы BuildKit могут делать запросы GRPC к buildkitd для непосредственного создания контейнера.

Программное создание образа

Давайте программно сгенерируем LLB для образа, а затем соберем образ.

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

Импортирeм LLB:

import ("github.com/moby/buildkit/client/llb")

Создадим LLB для образа Alpine:

func createLLBState() llb.State {    return llb.Image("docker.io/library/alpine").           File(llb.Copy(llb.Local("context"), "README.md", "README.md")).          Run(llb.Args([]string{"/bin/sh", "-c", "echo \"programmatically built\" > /built.txt"})).      Root()  }

Мы выполняем эквивалент FROM, используя lib.Image. Затем мы копируем файл из локальной файловой системы в образ, используя FILE и COPY. Наконец, мы запускаем RUN для вывода текста в файл. LLB имеет гораздо больше операций, но вы можете воссоздать множество стандартных образов с помощью этих трех блоков.

Последнее, что нам нужно сделать, это превратить это в протокол-буфер и выдать его в стандартный вывод:

func main() {dt, err := createLLBState().Marshal(context.TODO(), llb.LinuxAmd64)if err != nil {panic(err)}llb.WriteTo(dt, os.Stdout)}

Давайте посмотрим что сгенерируется, используя параметр dump-llb в buildctl:

go run ./writellb/writellb.go |  buildctl debug dump-llb |  jq .

Мы получаем этот LLB в формате JSON:

{  "Op": {    "Op": {      "source": {        "identifier": "local://context",        "attrs": {          "local.unique": "s43w96rwjsm9tf1zlxvn6nezg"        }      }    },    "constraints": {}  },  "Digest": "sha256:c3ca71edeaa161bafed7f3dbdeeab9a5ab34587f569fd71c0a89b4d1e40d77f6",  "OpMetadata": {    "caps": {      "source.local": true,      "source.local.unique": true    }  }}{  "Op": {    "Op": {      "source": {        "identifier": "docker-image://docker.io/library/alpine:latest"      }    },    "platform": {      "Architecture": "amd64",      "OS": "linux"    },    "constraints": {}  },  "Digest": "sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7",  "OpMetadata": {    "caps": {      "source.image": true    }  }}{  "Op": {    "inputs": [      {        "digest": "sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7",        "index": 0      },      {        "digest": "sha256:c3ca71edeaa161bafed7f3dbdeeab9a5ab34587f569fd71c0a89b4d1e40d77f6",        "index": 0      }    ],    "Op": {      "file": {        "actions": [          {            "input": 0,            "secondaryInput": 1,            "output": 0,            "Action": {              "copy": {                "src": "/README.md",                "dest": "/README.md",                "mode": -1,                "timestamp": -1              }            }          }        ]      }    },    "platform": {      "Architecture": "amd64",      "OS": "linux"    },    "constraints": {}  },  "Digest": "sha256:ba425dda86f06cf10ee66d85beda9d500adcce2336b047e072c1f0d403334cf6",  "OpMetadata": {    "caps": {      "file.base": true    }  }}{  "Op": {    "inputs": [      {        "digest": "sha256:ba425dda86f06cf10ee66d85beda9d500adcce2336b047e072c1f0d403334cf6",        "index": 0      }    ],    "Op": {      "exec": {        "meta": {          "args": [            "/bin/sh",            "-c",            "echo "programmatically built" > /built.txt"          ],          "cwd": "/"        },        "mounts": [          {            "input": 0,            "dest": "/",            "output": 0          }        ]      }    },    "platform": {      "Architecture": "amd64",      "OS": "linux"    },    "constraints": {}  },  "Digest": "sha256:d2d18486652288fdb3516460bd6d1c2a90103d93d507a9b63ddd4a846a0fca2b",  "OpMetadata": {    "caps": {      "exec.meta.base": true,      "exec.mount.bind": true    }  }}{  "Op": {    "inputs": [      {        "digest": "sha256:d2d18486652288fdb3516460bd6d1c2a90103d93d507a9b63ddd4a846a0fca2b",        "index": 0      }    ],    "Op": null  },  "Digest": "sha256:fda9d405d3c557e2bd79413628a435da0000e75b9305e52789dd71001a91c704",  "OpMetadata": {    "caps": {      "constraints": true,      "platform": true    }  }}

Просматривая вывод, мы видим, как наш код отображается на LLB.

Вот наша COPY как часть FileOp:

 "Action": {              "copy": {                "src": "/README.md",                "dest": "/README.md",                "mode": -1,                "timestamp": -1              }

Вот отображение контекста нашей сборки для использования в нашей команде COPY:

"Op": {      "source": {        "identifier": "local://context",        "attrs": {          "local.unique": "s43w96rwjsm9tf1zlxvn6nezg"        }      }

Точно так же вывод содержит LLB, который соответствует нашим командам RUN и FROM.

Создание нашего LLB

BuildKit состоит из двух основных компонентов: buildctl и buildkitd. buildctl - это контроллер BuildKit, который взаимодействует с buildkitd.

Buildkitd отвечает за создание образа, но фактическое выполнение каждого шага выполняет runc. Runc выполняет каждую команду RUN в вашем файле докеров в отдельном процессе. Runc требует ядра Linux 5.2 или более поздней версии с поддержкой cgroups, поэтому buildkitd не может работать изначально в macOS или Windows.

Как устанавливать описано тут - не будем останавливаться. В примере ниже описан способ через docker как более универсальный, так как запустить buildcid в macOS или Windows.

docker run --rm --privileged -d --name buildkit moby/buildkitexport BUILDKIT_HOST=docker-container://buildkitgo run ./writellb/writellb.go | buildctl build --local context=. --output type=image,name=docker.io/agbell/test,push=true

Флаг вывода (output) позволяет нам указать, какой бэкэнд мы хотим использовать в BuildKit. Мы попросим его создать образ OCI и отправить его в docker.io. По умолчанию результат сборки остается внутренним для BuildKit поэтому мы указали тип вывода. BuildKit поддерживает несколько типов вывода. Мы можем вывести tar: --output type=tar,dest=out.tar (этот файл не является образом. Нет никаких слоев или манифестов, только полная файловая система, которую будет содержать созданный образ).

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

Мы можем запустить с тем же результатом:

docker run -it --pull always agbell/test:latest /bin/sh

Затем мы можем увидеть результаты наших программных команд COPY и RUN:

cat built.txt   programmatically builtls README.md  README.md

Пример полного кода может стать отличной отправной точкой для создания вашего собственного программного образа докера.

frontend для BuildKit

Настоящая клиентская часть компилятора делает больше, чем просто выдает жестко запрограммированный IR. Правильный интерфейс принимает файлы, токенизирует их, анализирует их, генерирует синтаксическое дерево, а затем опускает это синтаксическое дерево во внутреннее представление. Mockerfiles - пример такого интерфейса:

#syntax=r2d4/mockerapiVersion: v1alpha1images:- name: demo  from: ubuntu:16.04  package:    install:    - curl    - git    - gcc

А поскольку сборка Docker поддерживает команду #syntax, мы даже можем создать Mockerfiles напрямую с помощью docker build.

docker build -f mockerfile.yaml

Для поддержки команды #syntax все, что нужно, - это поместить интерфейс в образ докера, который принимает запрос gRPC в правильном формате, опубликовать это изображение где-нибудь. На этом этапе любой может использовать ваш интерфейс docker build, просто используя #syntax = yourimagename.

Создание собственного примера внешнего интерфейса для docker build

Создание токенизатора и парсера как службы gRPC выходит за рамки этой статьи. Но мы можем научится этому, извлекая и изменяя существующий интерфейс. Стандартный интерфейс Dockerfile легко отделить от проекта moby. Я вытащил соответствующие части в отдельный репозиторий. Давайте внесем в него несколько изменений и проверим.

До сих пор мы использовали только команды докера FROM, RUN и COPY. Поверхностно синтаксис Dockerfile с его командами, начинающимися с заглавной буквы, очень похож на язык программирования INTERCAL. Давайте изменим эти команды на их эквивалент INTERCAL и разработаем наш собственный формат Ickfile.

Dockerfile

Ickfile

FROM

COME FROM

RUN

PLEASE

COPY

STASH

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

В этом руководстве мы собираемся внести только тривиальные изменения во внешний интерфейс. Мы оставим все этапы нетронутыми и сосредоточимся на настройке существующих команд под свой вкус. Для этого все, что нам нужно сделать, это изменить command.go:

package command// Define constants for the command stringsconst (Copy        = "stash"Run         = "please"From        = "come_from"...)

Затем мы можем увидеть результаты наших команд STASH и PLEASE:

Я отправил этот образ в dockerhub. Любой может начать создавать образы, используя наш формат ickfile, добавив #syntax = agbell/ick к существующему Dockerfile. Никакой ручной установки не требуется!

Еще пару примеров использования BuildKit напрямую

Если у нас есть образ локально на нашей машине, можем ли мы использовать его в FROM для создания чего-либо на его основе? Давайте узнаем, изменив наш FROM для использования локального образа:

FROM agbell/test:localRUN echo "BuildKit built">  fileCMD ["/bin/sh"] 
> docker tag alpine agbell/test:local> buildctl build \    --frontend=dockerfile.v0 \    --local context=. \    --local dockerfile=. \------ > [internal] load metadata for docker.io/agbell/test:local:------error: failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to create LLB definition: docker.io/agbell/test:local: not found

Это не работает. Похоже, он пытается получить изображение из docker.io, реестра docker-хаба по умолчанию.

Мы можем проверить это, быстро перехватив запросы от buildkitd:

 cat ~\DockerfileFROM moby/buildkit RUN apk update && apk add curlWORKDIR /usr/local/share/ca-certificatesCOPY mitmproxy.crt mitmproxy.crtRUN update-ca-certificates docker build . -t buildkit:mitm ... docker run --rm --privileged -d --name buildkit buildkit:mitm6676dc0109eb3f5f09f7380d697005b6aae401bb72a4ee366f0bb279c0be137b

Мы видим ошибку 404, и это подтверждает, что buildkitd ожидает доступа к реестру по сети с помощью docker registry v2 api.

Мы можем наблюдать за выполнением нашей сборки, используя pstree и watch. Откройте два терминала, запустите в одном:

docker exec -it buildkit "/bin/watch" "-n1" "pstree -p"

В другом:

buildctl build ...

Вы увидите, что buildkitd запускает процесс buildkit-runc, а затем отдельный процесс для каждой команды RUN.

Заключение

BuildKit используется внутри сборки докеров в современных версиях docker, но его использование напрямую открывает некоторые дополнительные возможности.

Мы рассмотрели одно использование - изменение типа вывода. Мы можем использовать BuildKit для экспорта tar и локальных файловых систем. Мы также используем pstree и mitmProxy, чтобы наблюдать, как buildkitd обрабатывает процессы и делает сетевые запросы.

Мы узнали, что трехэтапная структура, заимствованная у компиляторов, обеспечивает создание образов, что промежуточное представление, называемое LLB, является ключом к этой структуре. Опираясь на эти знания, мы создали два интерфейса для создания образов.

BuildKit стоит за функцией мультиплатформенной сборки docker buildx и поддерживает возможность параллельного выполнения сборок несколькими рабочими процессами. BuildKit также поддерживает кеширование, различные внешние интерфейсы, сборки для создания докеров, более быстрые многоступенчатые сборки и несколько других функций.

Анонсы других публикаций и возможность комментирования (если тут не можете) в телеге.

Подробнее..

Перевод Написание Dockerfile. Лучшие практики

30.12.2020 18:06:06 | Автор: admin
Публикуем новый перевод и надеемся, что рекомендации автора помогут вам оптимизировать образ Docker.

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



Если вы новичок в Docker, вы можете выбрать шаблон (базовый образ) и определить свои инструкции (команды Docker file), чтобы разместить свой код внутри образа и запустить его.

Хотя простота Docker помогает выполнять работу, его оптимизация приходит с опытом и обычно требует времени.

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

1. Определите кэшируемые юниты


Знаете ли вы, что каждая команда RUN, включенная в Dockerfile, влияет на уровень кэширования?

Использование нескольких команд RUN для установки пакетов повлияет на производительность и эффективность процесса сборки. Использование одной команды RUN для установки всех пакетов и зависимостей поможет создать один кэшируемый юнит вместо нескольких.

RUN apt-get update && apt-get install -y \    aufs-tools \    automake \    build-essential \    curl \    dpkg-sig \    libcap-dev \    libsqlite3-dev \    mercurial \    reprepro \    ruby1.9.1 \    ruby1.9.1-dev \    s3cmd=1.1.*

2. Уменьшите размер образа


Размер образа играет важную роль в создании хорошего Dockerfile. Использование образов меньшего размера приведет к более быстрому развертыванию и меньшей возможности для атак.

Удалите ненужные зависимости

Избегайте установки в образ ненужных инструментов, таких как инструменты отладки.

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

RUN apt-get update && apt-get -y install --no-install-recommends

Совет: делитесь компонентами, которые используете повторно, между проектами с помощью Bit (Github).

Bit упрощает документирование, совместное и повторное использование независимых компонентов между проектами. Применяйте его для повторного использования кода, сохранения единообразия дизайна, совместной работы в команде, увеличения скорости доставки и создания масштабируемых приложений.

Bit поддерживает Node, TypeScript, React, Vue, Angular и др.


Изучите компоненты, опубликованные на Bit.dev

3. Поддержка образа


Выбор правильного базового образа для приложения очень важен.

Используйте официальный образ Docker

Использование официального образа Docker снижает нагрузку на ненужные зависимости, которые делают образ больше. Использование официального образа дает 3 основных преимущества:

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

# загрузить официальный базовый образFROM node:13.12.0-alpine# установить рабочий каталогWORKDIR /app# добавить `/app/node_modules/.bin` в $PATHENV PATH /app/node_modules/.bin:$PATH

Использовать определенные теги

При выборе базового образа рекомендуется использовать определенный тег. Не используйте для образа latest тег. Latest тег со временем может претерпевать критические изменения.

# загрузить официальный базовый образFROM node:13.12.0-alpine

Используйте минимальные Flavors

Минимальные Flavors уменьшают размер образа. Это помогает разворачивать приложения быстрее и безопаснее.



Как видно из приведенного выше изображения, при использовании минимального flavors образ занимает меньший объём. Большинство образов используют alpine flavor. Alpine это очень легкий образ со стандартным размером 2 МБ.

Используя образ на основе alpine, мы можем значительно уменьшить размер образа.

4. Воспроизводимость


Сборка из исходного кода в согласованной среде

При создании приложения с помощью Docker лучше создавать приложение в управляемой среде для обеспечения согласованности.

Мы должны избегать создания приложений в локальной среде и добавлять их в реестр.

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

Использование многоэтапных сборок для удаления зависимостей

Рекомендуется использовать многоэтапный способ развертывания приложений.

Это исключает использование зависимостей сборки в работающем контейнере.

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

# Stage 0, "build-stage", based on Node.js, to build and compile the frontendFROM node:13.12.0 as build-stageWORKDIR /appCOPY package*.json /app/RUN npm installCOPY ./ /app/RUN npm run build# Stage 1, based on Nginx, to have only the compiled app, ready for production with NginxFROM nginx:1.15COPY --from=build-stage /app/build/ /usr/share/nginx/html

В приведенном выше Dockerfile есть два отдельных этапа. Этап 0 используется для сборки node-приложения из исходного образа node, а этап 1 используется для копирования бинарных файлов из образа сборки в образ веб-сервера (Nginx), который в конечном итоге обслуживает приложение.

Заключение


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

Если вам известны другие крутые практики, делитесь в комментариях. Спасибо за прочтение!
Подробнее..

Перевод Ускоряем CICD-пайплайн с помощью Kubernetes в Docker (KinD)

30.12.2020 20:10:58 | Автор: admin
В нашей новой переводной статье разбираемся с KinD на практическом примере.

Создание кластера Kubernetes со временем становится все проще. На рынке доступно несколько решений под ключ, и сейчас никто не выбирает сложный путь!



Стоит отметить, что Minikube был одним из основных кластеров, которые разработчики использовали для быстрой разработки и тестирования контейнеров. Хотя Minikube в настоящее время поддерживает многоузловой кластер на экспериментальной основе, его еще нет в общем доступе (GA).

Следовательно, это ограничивает возможности интеграции и тестирования компонентов, поэтому большинство организаций используют для этого управляемые облачные сервисы Kubernetes.
Для интеграции Kubernetes в конвейер CI/CD (непрерывная интеграция и развертывание) и выполнения тестирования необходимы следующие инструменты: Terraform, в зависимости от облачного провайдера и, конечно же, инструмент для CI/CD, например Jenkins, GitLab или GitHub.

Для крупных компаний с достаточным бюджетом это подходящие варианты, однако разработчики часто ищут что-то, что поможет им быстро начать работу. Развертывание кластера Kubernetes в облаке также занимает некоторое время (~ 10 минут), что может быть препятствием для CI, где нужно быстро получать сборки.
Kubernetes в Docker или KinD это реализация подхода Docker-in-Docker (DinD) для Kubernetes. Этот инструмент создает контейнеры, которые действуют как узлы Kubernetes, и вам необходимо только установить Docker на своем компьютере.

Он позволяет развернуть многоузловой кластер за пару минут без зависимости от других инструментов или облачных провайдеров. Благодаря этому он полезен не только для локальной разработки, но и для CI/CD.

Архитектура KinD


Kubernetes в Docker использует подход Docker-in-Docker (DinD) для запуска кластера Kubernetes. Он запускает несколько Docker контейнеров, которые функционируют как узлы Kubernetes. Контейнеры Docker монтируют том docker.sock в Docker, запущенный на вашем компьютере, чтобы обеспечить взаимодействие с базовой средой выполнения контейнера.



KinD прошел проверку на соответствие и получил сертификат CNCF. Он использует Kubeadm для начальной загрузки кластера, а также генерирует файлы конфигурации Kube для пользователя, через которого вы управляете вашим кластером, что позволяет использовать kubectl для взаимодействия с кластерами. Другие компоненты Kubernetes, такие как Helm и Istio, также прекрасно работают в кластерах KinD.

Недостаток KinD заключается в том то, что он не работает со службами LoadBalancer, поэтому вам придется использовать NodePort, чтобы пробрасывать свои службы извне.

Кроме того, DinD в настоящее время является не самым безопасным решением, поэтому используйте кластеры KinD только на локальных машинах разработки и CI/CD конвейерах. Никогда не используйте KinD в производственной среде!

Установка KinD


KinD состоит из простой утилиты командной строки, которую вы можете загрузить и переместить на свой путь. Затем вы можете взаимодействовать с KinD, используя команды kind:

sudo curl -sL https://kind.sigs.k8s.io/dl/v0.9.0/kind-linux-amd64 -o /usr/local/bin/kindsudo chmod +x /usr/local/bin//kind


Затем вы можете создать свой кластер, при помощи следующей команды:

kind create cluster --wait 10m


Эта команда создаст одноузловой кластер. Но если вы хотите определить многоузловой кластер, можно использовать файл конфигурации кластера, подобный приведенному ниже:

# three node (two workers) cluster configkind: ClusterapiVersion: kind.x-k8s.io/v1alpha4nodes:- role: control-plane- role: worker- role: worker


Затем создайте кластер с файлом конфигурации, используя следующую команду:

kind create cluster --wait 10m --config kind-config.yaml


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

Так как KinD автоматически создает файл конфигурации Kube, вы можете использовать команды kubectl, как и в случае с другими кластерами.

Удалить кластер KinD также просто. Запустите следующую команду:

kind delete cluster


Приступаем к практике


Без лишних слов, давайте на практике разберемся, как CI/CD конвейер использует KinD. Мы возьмем GitHub Actions в качестве инструмента для CI/CD, поскольку он прост в использовании, не требует дополнительной инфраструктуры, а запустить его может любой, у кого есть ноутбук и подключение к интернету.

Давайте создадим простое приложение NGINX с надписью Hello World.

Выполняем следующие действия:

1. Создаем дев-версию приложения.

2. Запускаем тестирование компонентов в кластере KinD.

3. Если тест проходит успешно, мы переводим образ в релиз и отправим его в Docker Hub.

Необходимые условия


  • Аккаунт GitHub
  • Аккаунт Docker Hub


Краткое руководство


1. Разветвите этот репозиторий.

2. Зайдите в репозиторий и создайте два secret: DOCKER_USER и DOCKER_PW. Они должны содержать ваше имя пользователя в Docker Hub и пароль от аккаунта соответственно.

3. Перейдите в GitHub Actions и повторно запустите задачи. Как вариант, вы можете внести изменения в файл README.md и нажать на него чтобы запустить действие.

Длинная версия


Давайте рассмотрим файл build-pipeline.yml на GitHub Actions чтобы понять, как он работает:

name: Docker Image CIon: [push]     # Environment variables available to all jobs and steps in this workflowenv: # Or as an environment variable      docker_username: ${{ secrets.DOCKER_USER }}      docker_password: ${{ secrets.DOCKER_PW }}jobs:  build-docker-image:    runs-on: ubuntu-latest    steps:    - uses: actions/checkout@v2      with:        fetch-depth: 0    - name: Build the Docker image      run: docker build -t $docker_username/nginx:dev .    - name: Login to Docker      run: echo "$docker_password" | docker login -u "$docker_username" --password-stdin    - name: Push the docker image      run: docker push $docker_username/nginx:dev  kubernetes-component-test:    runs-on: ubuntu-latest    needs: build-docker-image    steps:    - uses: actions/checkout@v2      with:        fetch-depth: 0    - name: Run KIND Test      run: sudo sh build-test.sh $docker_username    promote-and-push-docker-image:    runs-on: ubuntu-latest    needs: kubernetes-component-test    steps:    - uses: actions/checkout@v2      with:        fetch-depth: 0    - name: Pull the Docker image      run: docker pull $docker_username/nginx:dev    - name: Tag the Docker image      run: docker tag $docker_username/nginx:dev $docker_username/nginx:release    - name: Login to Docker      run: echo "$docker_password" | docker login -u "$docker_username" --password-stdin    - name: Push the docker image      run: docker push $docker_username/nginx:release


Пайплайн сборки запускает последовательно три задания:

1. Задача build-docker-image создает Docker образ для разработки и отправляет его в Docker Hub при успешной сборке. В этой задаче вы можете запустить своё модульное тестирование.

2. Задача kubernetes-component-test настраивает кластер KinD и запускает компонентный тест для приложения.

3. Задача promote-and-push-docker-image извлекает образ для разработки, маркирует его до релизной версии и отправляет релизную версию в Docker Hub.

Давайте рассмотрим Dockerfile, чтобы понять, что он создает:

FROM nginxRUN echo 'Hello World' > /usr/share/nginx/html/index.html


Второй шаг является ключевым, он запускает скрипт build-test.sh. Сейчас давайте посмотрим на скрипт:

#! /bin/bashdocker_username=$1set -xecurl -sL https://kind.sigs.k8s.io/dl/v0.9.0/kind-linux-amd64 -o /usr/local/bin/kindchmod 755 /usr/local/bin//kindcurl -sL https://storage.googleapis.com/kubernetes-release/release/v1.17.4/bin/linux/amd64/kubectl -ochmod 755 /usr/local/bin//kubectlcurl -LO https://get.helm.sh/helm-v3.1.2-linux-amd64.tar.gztar -xzf helm-v3.1.2-linux-amd64.tar.gzmv linux-amd64/helm /usr/local/bin/rm -rf helm-v3.1.2-linux-amd64.tar.gzkind versionkubectl version --client=truehelm versionkind create cluster --wait 10m --config kind-config.yamlkubectl get nodesdocker build -t $docker_username/nginx:dev .kind load docker-image $docker_username/nginx:devkubectl apply -f nginx-deployment.yamlkubectl apply -f nginx-service.yamlNODE_IP=$(kubectl get node -o wide|tail -1|awk {'print $6'})NODE_PORT=$(kubectl get svc nginx-service -o go-template='{{range.spec.ports}}{{if .nodePort}}{{.nodesleep 60SUCCESS=$(curl $NODE_IP:$NODE_PORT)if [[ "${SUCCESS}" != "Hello World" ]];then kind -q delete clusterexit 1;else kind -q delete clusterecho "Component test succesful"fi


Что делает скрипт:

1.Скачивает и устанавливает утилиту kind, kubectl и helm на CI-сервер.
2.Создает многоузловой кластер с помощью файла kind-config.yaml.
3.Создает Docker образ для разработки с помощью docker build.
4.Загружает Docker образ в кластер KinD. Благодаря загрузке обеспечивается доступ к образу для всех узлов KinD, чтобы им не приходилось извлекать образ из Docker Hub.
5.Разворачивает контейнер в deployment и пробрасывает его через сервис NodePort NodePortservice.
6.Получает IP-адрес и порт узла и и запускает тест, чтобы проверить, возвращает ли приложение фразу Hello World.
7.Если проверка проходит успешно, удаляет кластер KinD, выводит Component test successful (успешное выполнение компонентного теста) и возвращает код успеха. Если проверка не пройдена, удаляет кластер KinD и возвращает код ошибки.

Результаты


Когда мы начинаем работу с пайплайном, GitHub Actions автоматически запускает весь пайплайн:



Это, несомненно улучшение и удобный способ для выполнения непрерывной интеграции и развертывания с использованием Docker и Kubernetes. Kubernetes в Docker не только упрощает локальную разработку, но и является отличным инструментом для CI/CD.

Спасибо, прочитали статью! Надеюсь, она вам понравилась!
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru