
werf — наша Open Source-утилита для сборки и деплоя приложений. Сегодня мы с радостью сообщаем, что werf научилась работать в распределенном режиме, начиная с версии v1.1.10 (доступна в каналах v1.1 alpha, beta, ea и stable). Для его подключения требуется минимум усилий.
Вот некоторые из примечательных особенностей нового режима:
- хранение сборочных кэш-слоев (стадий) в реестре Docker-образов (stages storage);
- продвинутое распределенное кэширование сборочных кэш-слоев (стадий) для сборщика stapel;
- возможность использования произвольного количества runner'ов (постоянных или временных) для запуска werf;
- эффективный и оптимизированный алгоритм выбора стадий и сборки;
- подключение к любому экземпляру Kubernetes — единственная внешняя зависимость для этой функции (она необходима для синхронизации процессов werf, работающих в распределенном режиме, и для хранения внутренних кэшей).
Заметим, что сборщик Dockerfile также можно использовать в распределенном режиме, однако он пока не поддерживает продвинутое распределенное кэширование слоев.
Однако начнем с рассказа о том, что такое распределенный режим и какие компоненты необходимы для его реализации. Затем покажем, как включить этот режим и как перевести на него (с локального режима) существующие проекты, использующие werf. Наконец, рассмотрим демо-проект, который в полной мере задействует механизм распределенной сборки werf.
Общая информация
В werf имеется несколько ключевых концепций, связанных с процессом сборки: стадии, образы, хранилище стадий. В документации стадии и образы определяются следующим образом:
Мы предлагаем разделить сборочный процесс на этапы, каждый с четкими функциями и своим назначением. Каждый этап соответствует промежуточному образу, подобно слоям в Docker. В werf такой этап называется стадией, и конечный образ в итоге состоит из набора собранных стадий. Все стадии хранятся в хранилище стадий (stages storage).
Что за хранилище стадий? До версии v1.1.10 на этот вопрос можно было ответить так: «Это просто локальный сервер Docker». Однако начиная с текущего момента, werf позволяет хранить стадии в реестре Docker-образов. Более того, он поддерживает большинство реализаций Docker Registry, доступных сегодня.
Использование Docker Registry в качестве хранилища стадий позволяет проводить распределенную сборку образов на нескольких хостах. Для сборщика stapel, представляющего собой альтернативу Dockerfile со множеством полезных функций, доступно продвинутое кэширование слоев. Для него реализован эффективный и оптимизированный алгоритм выбора стадий и сборки:
- Уже собранные стадии, имеющиеся в хранилище стадий, будут использованы при сборке новой стадии.
- Стадия извлекается из хранилища стадий, когда это необходимо для сборки следующей стадии.
- Стадия, извлеченная из хранилища стадий, останется в локальном кэше Docker-образов (автоматический сборщик мусора удалит наименее востребованные образы).
- Публикация свежесобранных образов осуществляется гораздо быстрее, поскольку в момент, когда она происходит, Docker Registry (по совместительству хранящий и стадии) уже содержит все стадии образа. Они являются базовыми слоями для публикуемого образа.
- Для сохранения свежесобранных стадий в хранилище алгоритм сборки использует оптимистическую блокировку: тем самым гарантируется, что только один сборщик сможет сохранить новую стадию, после чего она станет доступной для других процессов сборщика.
Для сборки образов на нескольких хостах можно также использовать и сборщик Dockerfile, однако на данный момент для него не реализовано продвинутое распределенное кэширование слоев (впрочем, это будет сделано).
С появлением распределенного режима werf предлагает два уровня кэширования Docker-образов:
- Стадии, хранящиеся в хранилище стадий (Docker-образы в Docker registry).
- Локальные стадии (Docker-образы), лежащие на локальных Docker-серверах каждого узла сборки.
Кроме того, имеются образы, опубликованные в репозитории образов, — назовем их разновидность (3).
Кэши нуждаются в периодической очистке. В werf для этого встроена команда
werf cleanup
, удаляющая Docker-образы (2)-го и (3)-го типов.Локальные Docker-образы (1) пока приходится убирать вручную (только при использовании распределенного режима). Эти образы можно удалять с помощью любых инструментов (например,
docker rmi
). Будущие версии werf смогут автоматически удалять эти образы при выполнении связанных со сборкой команд (используя алгоритм на основе LRU с автоматической поддержкой заполненности места файловой системы сборочного узла на уровне 80%).Также следует отметить, что распределенный режим werf использует стадии из хранилища стадий (Docker Registry) и извлекает только те образы, которые необходимы для сборки нового слоя (скачивает только базовый образ). При этом во время холостых сборок (когда образы в действительности не собираются) образы вообще не извлекаются.
Более подробная информация об алгоритме работы сборщика и архитектуре доступна в документации:
Требования к узлам
Для работы в распределенном режиме werf'у требуется подключение к какому-либо кластеру Kubernetes. Kubernetes будет использоваться для координации множества процессов werf при:
- выборе и записи стадий в хранилище стадий;
- публикации образов в репозиторий;
- одновременном развертывании приложений с нескольких хостов.
Не имеет значения, используется ли данный кластер Kubernetes для деплоя приложения. Единственное требование — один и тот же экземпляр K8s должен использоваться для всего проекта.
Как именно он используется? werf создает ConfigMap
cm/werf-PROJECT_NAME
в пространстве имен werf-synchronization
для каждого проекта. Этот ConfigMap используется для хранения так называемого кэша хранилища стадий и для распределенных блокировок. Для реализации распределенной блокировки в кластере Kubernetes используется Open Source-библиотека lockgate, созданная нами специально для werf.Различные процессы werf, работающие с одним и тем же проектом, должны использовать единое хранилище стадий и один и тот же экземпляр кластера Kubernetes.
Дополнительную информацию о синхронизации можно найти в документации.
Новые команды для работы со стадиями
В werf реализованы новые команды для работы со стадиями:
-
werf stages sync
— копирует стадии между хранилищами. -
werf stages switch-from-local
— помогает перевести существующий проект в распределенный режим (подробнее об этом процессе см. ниже).
Как включить распределенный режим
Для использования распределенного режима достаточно указать параметр
--stages-storage=DOCKER_REPO_ADDRESS
для всех команд werf.Обратите внимание, что
DOCKER_REPO_ADDRESS
должен ссылаться на уникальный Docker-репозиторий для данного проекта. Этот репозиторий не может использоваться одновременно для нескольких проектов (хотя один и тот же Docker Registry, конечно, может использоваться несколькими проектами).Команда
werf ci-env
, интегрирующая werf в процесс CI/CD, экспортирует переменную WERF_STAGES_STORAGE
. Она содержит адрес Docker-репозитория, предназначенного для хранения стадий, и это хранилище по умолчанию будет использоваться при всех последующих вызовах werf. Вот пример этой переменной для GitLab CI/CD: WERF_STAGES_STORAGE=CI_REGISTRY_IMAGE/stages
.Если
--stages-storage
определён не :local
, а как адрес Docker Registry (DOCKER_REPO_ADDRESS
), werf автоматически задействует пространство имен Kubernetes werf-synchronization
и текущий контекст из kubeconfig'а по умолчанию для подключения к кластеру. При этом пользователь может явно указать произвольное пространство имен с помощью опции --synchronization=kubernetes://NAMESPACE
. Дополнительная информация доступна в документации.Миграция существующего проекта
Проект можно переключить в распределенный режим, если он уже использует werf с локальным хранилищем стадий. Новая версия werf поставляется со специальными инструментами, упрощающими этот процесс. Подробная информация об этом доступна в специальном руководстве по миграции.
Демо-проект
Мы также подготовили демо-проект, чтобы продемонстрировать, как с помощью распределенного режима werf можно собрать приложение в публичном GitLab: symfony-demo.
Ниже приведены шаги по использованию распределенного режима werf:
1. Подготовьте
werf.yaml
:
Его содержимое
(файл в репозитории)
project: symfony-demo
configVersion: 1
---
image: ~
from: ubuntu:16.04
docker:
WORKDIR: /app
# Non-root user
USER: app
EXPOSE: "80"
ENV:
LC_ALL: en_US.UTF-8
ansible:
beforeInstall:
- name: "Install additional packages"
apt:
state: present
update_cache: yes
pkg:
- locales
- ca-certificates
- name: "Generate en_US.UTF-8 default locale"
locale_gen:
name: en_US.UTF-8
state: present
- name: "Create non-root group for the main application"
group:
name: app
state: present
gid: 242
- name: "Create non-root user for the main application"
user:
name: app
comment: "Create non-root user for the main application"
uid: 242
group: app
shell: /bin/bash
home: /app
- name: Add repository key
apt_key:
keyserver: keyserver.ubuntu.com
id: E5267A6C
- name: "Add PHP apt repository"
apt_repository:
repo: 'deb http://ppa.launchpad.net/ondrej/php/ubuntu xenial main'
update_cache: yes
- name: "Install PHP and modules"
apt:
name: "{{`{{packages}}`}}"
state: present
update_cache: yes
vars:
packages:
- php7.2
- php7.2-sqlite3
- php7.2-xml
- php7.2-zip
- php7.2-mbstring
- php7.2-intl
- name: Install composer
get_url:
url: https://getcomposer.org/download/1.6.5/composer.phar
dest: /usr/local/bin/composer
mode: a+x
install:
- name: "Install app deps"
# NOTICE: Always use `composer install` command in real world environment!
shell: composer update
become: yes
become_user: app
args:
creates: /app/vendor/
chdir: /app/
setup:
- name: "Create start script"
copy:
content: |
#!/bin/bash
php -S 0.0.0.0:8000 -t public/
dest: /app/start.sh
owner: app
group: app
mode: 0755
- raw: echo `date` > /app/version.txt
- raw: chown app:app /app/version.txt
git:
- add: /
to: /app
owner: app
group: app
(файл в репозитории)
2. Для распределенного режима werf требуется экземпляр кластера Kubernetes — мы воспользуемся GKE. Подготовьте kube-config для кластера и установите секретную переменную
BASE64_KUBECONFIG
:cat .kube/config | base64 -w0 > /tmp/base64_kubeconfig
# copy /tmp/base64_kubeconfig content and set BASE64_KUBECONFIG variable in CI/CD

3. Подготовьте стадию сборки (
build
) в .gitlab-ci.yml
:stages:
- build
Build:
stage: build
script:
- export KUBECONFIG=$(mktemp -d)/kubeconfig
- echo $BASE64_KUBECONFIG | base64 -d -w0 > $KUBECONFIG
- type multiwerf && source $(multiwerf use 1.1 ea --as-file)
- type werf && source $(werf ci-env gitlab --as-file)
- werf build-and-publish
tags:
- werf-demo-runner
(файл в репозитории)
В этом примере мы реализовали только стадию сборки. Для полноценного CI/CD дополнительно понадобятся стадии развертывания (
deploy
), очистки (cleanup
) и удаления (dismiss
), однако их реализацию оставим за рамками статьи. С полным примером можно ознакомиться в руководстве по использованию GitLab.4. Убедитесь, что в вашем проекте явно не задан параметр
--stages-storage
и не прописана переменная окружения WERF_STAGES_STORAGE
. Команда werf ci-env
установит WERF_STAGES_STORAGE=CI_REGISTRY_IMAGE/stages
(в нашем примере это registry.gitlab.com/distorhead/symfony-demo/stages
).5. Проверьте реестр контейнеров на соответствующей странице проекта: symfony-demo/container_registry. Здесь показаны собранные стадии проекта в хранилище стадий Docker Registry:

6. Теперь можно попробовать внести изменения в исходники своего приложений (
src/Kernel.php
с помощью merge_requests/2) и пересобрать его. Сборочный процесс (build job) берет существующие стадии из Docker Registry (хранилища стадий) и пересобирает только стадию gitLatestPatch
:
Подобный вывод означает, что все работает правильно!
Заключение
Распределенный режим — очередная важная веха в развитии проекта werf. Он приносит масштабируемость, требуя при этом минимальных усилий от пользователя.
Распределенный режим доступен с версии werf v1.1.10. В этой статье мы описали, как его включить и как на него перевести существующий проект (уже использующий локальный режим werf). Распределенный режим рекомендуется для систем CI/CD и включается для них по умолчанию.
Попробуйте werf, если еще не сделали это! И следите за новостями: скоро мы опубликуем руководство по полной интеграции werf c GitHub Actions.
P.S.
Читайте также в нашем блоге:
- «Полная поддержка популярных реализаций Docker Registry в werf»;
- «Content-based tagging в сборщике werf: зачем и как это работает?»;
- «Релиз werf 1.1: улучшения в сборщике сегодня и планы на будущее»;
- «3-way merge в werf: деплой в Kubernetes с Helm „на стероидах“»;
- «Использование werf для выката комплексных Helm-чартов».