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

Зачем это может понадобиться? Предоставляя услуги поддержки инфраструктуры нашим клиентам, нам часто приходится искать баланс между эффективностью и затраченными ресурсами. Когда есть потребность в нескольких рабочих окружениях для разработчиков (stage, dev, test, review и т.п.), как правило, ограничивающим фактором в их реализации являются деньги. Поэтому для решения такой задачи — в дополнение к динамическим окружениям (или вместо них) — задействуются локальные окружения прямо на рабочих местах разработчиков, о которых и пойдет речь далее.

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

Введение

Разработка под Kubernetes накладывает определенные правила на приложения в части сетевого взаимодействия сервисов между собой, использования постоянного хранилища файлов, распределения трафика с ingress’ов на конечные endpoint’ы и т.п. И в ситуации, когда разработчику нужно получать обратную связь о том, как приложение будет работать в «боевых» условиях, все эти инфраструктурные аспекты так или иначе влияют на конечный результат и скорость разработки. Для того, чтобы протестировать свои изменения, обычно разработчику необходимо сделать push своего commit’а в удаленный репозиторий и выкатить проект в какое-нибудь тестовое окружение. Метод традиционный и уже всеми используемый — работает хорошо. Но тут могут возникнуть определённые неудобства, например:

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

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

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

Про werf

Хочется также отдельно сказать несколько слов про наш Open Source-инструмент для выстраивания процесса CI/CD, в котором Git является единственным источником истины (этот принцип мы называем «гитерминизм»). Речь про CLI-утилиту под названием werf. С её помощью мы собираем контейнеры с кодом, публикуем их в registry, выкатываем Helm-чарты в Kubernetes и отслеживаем состояние выката до того момента, пока он не станет успешным.

Так как мы используем подход Infrastructure as a Code (описание всей имеющейся инфраструктуры в виде кода), основной паттерн при развёртывании приложений с использованием werf — хранение в одном Git-репозитории и описания используемых инфраструктурных компонентов, и самого кода проекта. Это позволяет гарантировать конечное состояние системы исходя из того, что находится в Git. Когда что-то изменяется в Git, эти изменения появятся и в рабочем окружении. Такую же задачу мы преследуем и в контексте локальной разработки, с чем нам поможет werf. Очевидно, это не единственный возможный путь, но тем, кто уже использует werf, он будет явно интересен.

Установка зависимостей

Возвращаясь к предмету повествования, рассмотрим локальный запуск приложения в Kubernetes на конкретном примере. Для этого будет использоваться упрощенный вариант K8s — minikube. Это инструмент для запуска одноузлового кластера Kubernetes на виртуальной машине (или в контейнере) на персональном компьютере.

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

Для начала нужно установить у себя все необходимые для работы компоненты. А это — Docker, minikube, werf и kubectl. Ниже приведены ссылки на инструкции по установке всех используемых утилит. Каждая утилита поддерживает несколько операционных систем: определённые Linux-дистрибутивы, а также macOS и Windows. Формально эта инструкция тестировалась только для Ubuntu 20.04, хотя пользователям других ОС не составит труда адаптировать её под свой случай (пример одной такой адаптации под macOS при организации стенда см. ниже).

Тестовый стенд

Теперь, когда базовые компоненты установлены, в качестве приложения, которое мы будем разрабатывать и деплоить, возьмём один из проектов-демонстраций возможностей werf, написанный на Node.js. Наша задача — адаптировать репозиторий под деплой в minikube. Для этого выполним следующие шаги:

  • изменим файл werf.yaml, описав в нём все стадии сборки проекта;

  • опишем Helm-чарты проекта;

  • разберемся с переменными окружения, передаваемыми в Deployment приложения;

  • запустим тестовый стенд и убедимся, что он работает.

Рассмотрим все шаги подробнее.

Сборка: werf vs Docker

Немного теории

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

Если же рассматривать ситуацию, когда процесс сборки запускается из pipeline, то он окажется на каком-нибудь из списка доступных worker’ов (например, в GitLab это gitlab-runner’ы), и все собранные слои останутся только на этом worker’е. Когда мы делаем commit с минимальными изменениями кода, и запускается pipeline, в результате которого задание на сборку образа попадёт на другой worker, образ будет собираться с нуля! И тут очень сильно теряется скорость сборки. Увы и ах! 

В werf принцип сборки построен немного иначе. Сборочный процесс образов, описанных в файле конфигурации werf.yaml, разбивается на этапы с четкими функциями и назначением. Каждый такой этап соответствует промежуточному образу подобно слоям в Docker, но за тем исключением, что он публикуется в registry (и автоматически становится доступен всем worker’ам). В werf такой этап называется стадией, а конечный образ соответствует последней собранной стадии для определённого состояния Git и конфигурации werf.yaml.

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

Практика

Рассмотрим тестовое приложение. В проекте присутствует Dockerfile, который используется werf для сборки образа. Но, так как в результате сборки этой конфигурации werf.yaml будет опубликована только одна стадия (последняя), то выигрыша в скорости сборки разными worker’ами мы не получим. Поэтому вместо Dockerfile будет использовать альтернативный Stapel-синтаксис и перепишем инструкции в werf.yaml.

project: werf-guide-app     # Название проекта.
configVersion: 1            # Используемая конфигурация (на данный момент поддерживается только 1 версия).

---
image: builder              # Название собираемого образа.
from: node:12-alpine        # Базовый образ.
git:                        # Секция с директивами для добавления исходных файлов из git-репозитория.
- add: /                    # Исходный путь в репозитории.
  to: /app                  # Путь назначения в образе.
  excludePaths:             # Набор путей или масок, применяемых для исключения файлов и папок при добавлении в образ.
  - local
  - .helm
  - Dockerfile  
  stageDependencies:        # Настройка перевыполнения сборочных инструкций при изменениях определённых файлов в репозитории.
    install:                # Для стадии Install.
    - package.json 
    - package-lock.json
    setup:                  # Для стадии Setup.
    - "**/*"
shell:                      # Shell сборочные инструкции.
  install:                  # Для стадии Install.
  - cd /app
  - npm ci
  setup:                    # Для стадии Setup.
  - cd /app
  - npm run build

---
image: backend
from: node:12-alpine
docker:                     # Набор директив для конечного манифеста образа (aka Docker).
  WORKDIR: /app
git:
- add: /
  to: /app
  includePaths:
  - package.json 
  - package-lock.json
  stageDependencies: 
    install:
    - package.json 
    - package-lock.json
shell:
  beforeInstall:
  - apk update
  - apk add -U mysql-client
  install:
  - cd /app
  - npm ci --production
  setup:
  - mkdir -p /app/dist
import:                     # Импорт из образов (и артефактов).
- image: builder            # Имя образа, из которого выполняется импорт.
  add: /app                 # Абсолютный путь до файла или директории в образе, из которого осуществляется импорт.
  to: /app                  # Абсолютный путь в конечном образе.
  after: setup              # Выбор стадии импортирования файлов при сборке.

---
image: frontend
from: nginx:stable-alpine
docker:
  WORKDIR: /www
git:
- add: /.werf/nginx.conf
  to: /etc/nginx/nginx.conf
import:
- image: builder
  add: /app/dist
  to: /www/static
  after: setup

---
image: mysql
from: mysql:5.7

Подробное описание всех возможных директив можно посмотреть в описании конфигурационного файла werf.yaml.

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

Описание Helm-чартов

Для деплоя проекта в Kubernetes нужна декомпозиция отдельных его частей до строго декларативных объектов, с которыми взаимодействует Kubernetes. Таких объектов может быть очень много, но в данном случае нужно лишь несколько: Deployment, StatefulSet, Service, Job, Secret и Ingress. Каждый объект выполняет свою функцию. Подробнее с их описанием и назначением можно ознакомиться в документации Kubernetes и его API.

Для более гибкой настройки, установки и обновления приложений в Kubernetes был создан Helm — менеджер пакетов и шаблонизатор для Kubernetes. Он позволяет описать приложение в виде чарта (который может содержать один или несколько субчартов) со своими параметрами и шаблонами конфигураций. Экземпляр чарта со своими параметрами и настройками, установленный в Kubernetes, называется релизом.

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

В нашем примере инфраструктурный компонент всего один — это MySQL, но могли бы быть еще, например, Redis, RabbitMQ, PostgreSQL, Kafka и т. д.

.helm/
├── Chart.lock
├── charts
│   ├── app
│   │   ├── Chart.yaml
│   │   └── templates
│   │       ├── deployment.yaml
│   │       ├── _envs_app.tpl
│   │       ├── ingress.yaml
│   │       ├── job-db-setup-and-migrate.yaml
│   │       ├── secret.yaml
│   │       └── service.yaml
│   └── mysql
│       ├── Chart.yaml
│       └── templates
│           ├── _envs_database.tpl
│           ├── mysql.yaml
│           ├── secret.yaml
│           └── service.yaml
├── Chart.yaml
├── secret-values.yaml
└── values.yaml

5 directories, 16 files

Как вы могли заметить, в корне с чартом лежат два файла values. Если вы работали с Helm раньше, то вам сразу понятно, для чего используется values.yaml. Но вы можете задаться вопросом, зачем же нужен secret-values.yaml. Использование переменных окружения и сторонних решений (Vault, Consul и т. п.) не соответствует принципу гитерминизма (который лежит в основе работы werf), где Git является единственным источником истины. Именно поэтому был реализован механизм, благодаря которому  можно хранить секреты внутри Git-репозитория в зашифрованном виде. 

В субчартах компонентов приложения и их параметрах заложена логика, которая позволяет рендерить манифесты в зависимости от окружения, в которое выкатывается Helm-релиз. Реализовано это средствами Go-шаблонизатора, который использует Helm. Рассмотрим, как описаны в шаблоне .helm/charts/app/templates/_envs_app.tpl переменные окружения, которые передаются в манифест с Deployment’ом приложения.

- name: MYSQL_HOST
  value: "{{ pluck .Values.global.env .Values.envs.MYSQL_HOST | first | default .Values.envs.MYSQL_HOST._default }}"

В поле value подставляется значение, описанное в .Values.envs.MYSQL_HOST, в соответствующем переменной .Values.global.env ключе.

Пример:

Есть следующий сегмент глобального файла values.yaml:

...
app:
  envs:
    MYSQL_HOST:
      _default: mysql
      local: mysql2
      production: mysql3
...

Тут переменная MYSQL_HOST представлена в виде так называемого a string-keyed map — map-объекта, в котором находятся ключи с более простыми типами данных. В данном случае это string, но можно использовать и более сложные структуры. Например, в map-объекте envs находится map-объект MYSQL_HOST и т.п.

Если в манифесте используется конструкция:

- name: MYSQL_HOST
  value: "{{ pluck .Values.global.env .Values.envs.MYSQL_HOST | first | default .Values.envs.MYSQL_HOST._default }}"

… при его рендере командой werf render –env local –set app.enabled=true получится отрендеренный сегмент:

...
- name: MYSQL_HOST
          value: mysql
...

Примечание:

При рендере всего чарта в субчарт передается map-объект с именем, соответствующим названию соответствующего субчарта, из глобального контекста Values. Обратите внимание, что файл _envs_app.tpl находится внутри субчарта app, в котором отсутствует локальный values.yaml, и внутри него идёт обращение к переменной .Values.envs.MYSQL_HOST, которая отсутствует в глобальном контексте Values. Но в то же время в глобальном Values присутствует переменная .Values.app.envs.MYSQL_HOST. При рендере werf подставит глобальный map-объект .Values.app в локальный .Values субчарта app.

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

Если в проекте помимо values.yaml присутствует также и secret-values.yaml, то для расшифровки и рендера манифеста потребуется задать переменную окружения WERF_SECRET_KEY (подробно об этом можно прочитать в документации). После расшифровки werf объединит ключи с одинаковыми названиями в один. Условно, если в values.yaml у нас описано:

...
app:
  envs:
    MYSQL_HOST:
      _default: mysql
      local: mysql2
      production: mysql3
    MYSQL_PASSWORD:
      local: Qwerty123
...

А в secret-values.yaml:

...
app:
  envs:
    MYSQL_PASSWORD:
      _default: 100037f97cb2629e2cab648cabee0b33d2fe381d83a522e1ff2e5596614d50d3a055
      production: 1000b278b1a888b2f03aba0d21ad328007ab7a63199cb0f4d1d939250a3f6ab9d77d
...

… то werf расшифрует secret-values.yaml, объединит ключи в values.yaml, и на выходе получится следующее содержимое Values:

...
app:
  envs:
    MYSQL_HOST:
      _default: mysql
      local: mysql2
      production: mysql3
    MYSQL_PASSWORD:
      local: Qwerty123
      _default: Qwerty456
      production: NoQwerty321
...

Организация стенда для локальной разработки

Подробное описание возможностей werf даёт следующие рекомендации по организации Git-репозитория:

  • В переменных окружения проекта достаточно хранить лишь WERF_SECRET_KEY. Все остальные секретные переменные кладём в secret-values.yaml и шифруем их с помощью werf;

  • Определим название окружения, в которое мы поместим наш экземпляр проекта. Будем использовать окружение с именем local (оно же будет соответствовать названию namespace в Kubernetes);

  • Все переменные, используемые в чартах/субчартах для окружения local, описываем только в values.yaml в открытом виде, не шифруя. Это нужно для того, чтобы не предоставлять доступ к WERF_SECRET_KEY рядовому разработчику, тем самым лишая его возможности расшифровать secret-values и получить Secret’ы.

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

Клонируем репозиторий и переходим в каталог с проектом:

git clone https://github.com/flant/examples
cd examples/2022/01-werf-local-dev

Начинаем подготовку окружения.

Необходимо разрешить Docker’у pull’ить образы по протоколу HTTP:

echo \
'{
    "insecure-registries": ["registry.local.dev"]
}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker

Запустим minikube в Docker-контейнере и разрешим ему использовать registry по протоколу HTTP:

minikube start --driver=docker --insecure-registry="registry.local.dev"
Примечание для macOS

В случае macOS настройки Docker будут выглядеть так:

… а последующая команда — так:

 minikube start --driver='hyperkit' --insecure-registry="registry.local.dev"

Включаем расширения ingress и registry:

minikube addons enable ingress
minikube addons enable registry

Добавляем IP-адрес будущего ingress’а в /etc/hosts внутри контейнера с minikube:

minikube ssh -- "echo $(minikube ip) registry.local.dev | sudo tee -a /etc/hosts" 

Добавляем IP-адрес будущих ingress’ов registry и основного приложения к себе в /etc/hosts:

echo "$(minikube ip) registry.local.dev" | sudo tee -a /etc/hosts
echo "$(minikube ip) test.application.local" | sudo tee -a /etc/hosts

Создаём ingress для нашего локального registry:

kubectl create -f - <<EOF
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: registry
  namespace: kube-system
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
spec:
  rules:
  - host: registry.local.dev
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: registry
            port:
              number: 80
EOF

Разрешим werf использовать registry по протоколу HTTP, задав переменную окружения WERF_INSECURE_REGISTRY:

export WERF_INSECURE_REGISTRY=1

Активируем werf:

source $(trdl use werf 1.2 ea)

Переходим к сборке и деплою инфраструктурных компонентов:

werf converge --repo registry.local.dev/app --release infra --env local --namespace local --dev --set mysql.enabled=true --ignore-secret-key=true

После завершения проверяем результат:

kubectl -n local get pod

NAME      READY   STATUS    RESTARTS   AGE
mysql-0   1/1     Running   0          39s

Pod с базой данных запустился и работает.

Запустим сборку и деплой основного приложения:

werf converge --repo registry.local.dev/app --release app --env local --namespace local --dev --set app.ci_url=test.application.local --set app.enabled=true --ignore-secret-key=true

После завершения проверяем результат:

kubectl -n local get pod

NAME                                 READY   STATUS      RESTARTS   AGE
app-c7958d64d-5tmqp                  2/2     Running     0          66s
mysql-0                              1/1     Running     0          4m53s
setup-and-migrate-db-rev1--1-xtdsc   0/1     Completed   0          66s

Pod с миграциями отработал успешно, о чём говорит статус Completed. Pod с основным приложением запущен и работает.

Посмотрим, по какому ingress’у доступен проект:

kubectl -n local get ingress

NAME   CLASS    HOSTS                    ADDRESS     PORTS   AGE
app    <none>   test.application.local   localhost   80      1m

Откройте браузер и перейдите по ссылке http://test.application.local — вы должны увидеть стартовую страницу проекта.

Управление изменениями в проекте происходит с помощью двух режимов:

  1. Режим разработчика: позволяет ослабить ограничения гитерминизма и работать с некоммитнутыми изменениями. Он активируется опцией --dev или переменной окружения WERF_DEV;

  2. Режим отслеживания изменений: позволяет перезапускать команду при изменении состояния Git-репозитория. Активируется опцией --follow или переменной окружения WERF_FOLLOW. Команда перезапускается при появлении новых commit’ов. А при использовании совместно с реимом--dev — при любых изменениях.

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

werf converge --repo registry.local.dev/app --release app --env local --namespace local --dev --follow --set app.ci_url=test.application.local --set app.enabled=true --ignore-secret-key=true

После этого werf останется запущенной в терминале и будет следить за изменениями в репозитории. Попробуйте внести какие-нибудь изменения в код проекта и посмотрите, как werf на них отреагирует.

Выводы

Вот такой интересный опыт мы получили в процессе поиска оптимального баланса затраты/эффективность. Тема довольно непростая: чтобы самостоятельно использовать такую практику, нужно обладать базовыми знаниями в работе с Kubernetes и написанию Helm-чартов. Надеюсь, что вы не зря потратили своё время и, читая этот материал, получили такое же удовольствие, как и я в процессе его написания.

Выводы

Читайте также в нашем блоге:

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


  1. denaspireone
    14.01.2022 11:47

    когда будет этот пост на english? :)


    1. stratnevpy Автор
      14.01.2022 12:00

      Мы пока не решили :)


  1. GrigoryPerepechko
    14.01.2022 12:23

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

    Вроде docker из коробки сейчас умеет делать то же самое, нет?

    DOCKER_BUILDKIT=1 docker build --build-arg BUILDKIT_INLINE_CACHE=1


    1. stratnevpy Автор
      14.01.2022 12:28

      В статье в первую очередь рассматривается гитерминизм. И werf тут в первую очередь - это gitops утилита. Докер из коробки не реализует этот подход.


    1. tkir
      14.01.2022 13:00
      +2

      Docker не обеспечит иммутабельность публикуемых образов. werf обеспечивает гарантию, что если какой-то образ или отдельный stage связанный с коммитом однажды опубликован в regsitry, то он не будет перетёрт другим образом по тому же тегу. Это нужно для обеспечения воспроизводимости образов по коммиту.


      1. gecube
        14.01.2022 13:30
        +2

        werf обеспечивает гарантию, что если какой-то образ или отдельный stage связанный с коммитом однажды опубликован в regsitry, то он не будет перетёрт другим образом по тому же тегу. 

        окей. Это очень интересный вопрос. А как решается проблема с хэш коллизиями? Ну, очевидно же, что имя образа конечного или с промежуточным слоем на большом проекте может случайно задублироваться. Даже в git была проблема с коллизиями хэшей. https://github.blog/2017-03-20-sha-1-collision-detection-on-github-com/

        Вторая история - гитерминизм предполагает, что все исходные зависимости тоже полностью определены, так ведь? Тогда получается, что если я какие-то зависимости не зафиксировал (например, делаю apt upgrade в докерфайле), то при потере кэша я получу совершенно новый артефакт и весь гитерминизм сломается. Ну, реально - невозможно контролировать всю цепочку, только если не перенести ее полностью к себе (начиная с базовых образов и репозиториев для большинства пакетных менеджеров - pypi, npm, apt/yum etc.)


        1. tkir
          14.01.2022 13:54
          +3

          Попробую рассказать на примере.

          Каждая стадия и конечный образ имеют content-based tag. Он состоит из двух частей: content checksum и timestamp. Два сборщика начинают собирают стадию с одним и тем же content checksum на разных билдерах. Какой-то сборщик завершает сборку быстрее, чем первый. Этот сборщик сразу после сборки стадии пытается опубликовать стадию в container registry. Для этого он проверяет, что стадия ещё не опубликована по content checksum, если уже опубликована, то сборщик выбрасывает свой собранный стейж и использует далее то что нашёл в container registry. Если стадия не опубликована, то werf берёт короткоживущий distributed lock по content checksum и опять для корректности проверяет, что стадия не появилась в container registry. Если стадии нет, то мы генерируем полный content-based-tag подставляя content checksum и текущий timestamp и публикуем стадию. После отпускаем distributed lock и стадия становится моментально доступна для всех сборщиков.

          Далее там есть моменты, связанные с тем, что werf обеспечивает изоляцию сборочного кеша на основе git-истории. А это значит, что по одному и тому же content checksum в разных ветках может быть опубликована своя стадия (во-первых будет разный timestamp, во-вторых, даже если произойдёт коллизия timestamp, то werf заметит уже существующий тег и просто сгенерит timestamp ещё раз). Алгоритм подбора существующих стадий обеспечит изоляцию кеша до тех пор, пока ветка не будет смержена в текущую ветку. Как только такой мерж происходит, то и собранный кеш для смерженных коммитов начинает быть доступен в основной ветке (по факту среди нескольких образов с одним и тем же content checksum будет выбран тот, который собран раньше по timestamp).

          Это всё можно назвать mvcc с оптимистичными блокировками, это реализует werf под капотом с использованием сервера синхронизации.


          1. tkir
            14.01.2022 14:03

            docker принципиально работает по другому алгоритму. Сначала он попытается спуллить все стадии из container registry. Затем соберёт все стадии локально. Затем запушит результирующие стадии и результирующий образ.

            werf делает этот процесс _по одной стадии_ (собрали стадию, опубликовали стадию) и связывает кеш с коммитами, что позволяет сразу после сборки переиспользовать общий кеш.


        1. tkir
          14.01.2022 13:59

          Про вторую историю. Совсем не обязательно что зависимости строго определены. Т.к. кеш иммутабельный и не удаляется из container registry, то те версии зависимостей которые были когда-то собраны для коммита остаются закешированы навсегда. Обеспечивается воспроизводимость однажды собранного кеша для коммита. Если же зависимости необходимо апдейтнуть, то мы создаём коммит, в котором инициируем сброс соотв. стадии (для этого мы либо меняет в гите файл, на изменения которого завязана сборка стадии, либо меняем в werf.yaml спец. поле "версия кеша").


    1. aigrychev
      14.01.2022 13:21
      +5

      Есть ли быть точнее, то флоу должен выглядеть как-то так:

      docker build \
                  --cache-from $CACHE_IMAGE:latest \
                  --tag $CACHE_IMAGE:latest \
                  --build-arg BUILDKIT_INLINE_CACHE=1 \
                  "."
      docker push $CACHE_IMAGE:latest
      

      Грубо говоря, werf делает это из коробки, но поинтереснее:

      • Вместо сборки и публикации всего образа целиком, werf делает это стадиями (слоями). После сборки слой уходит в container registry и уже может использоваться остальными сборщиками.

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


  1. gecube
    14.01.2022 12:53
    +3

    Спасибо за статью - интересный вариант локальной работы, но, на мой взгляд, перспектива все-таки в другом сценарии - работа с удаленным кластером. В этом случае мы можем сделать как: завести под разработчика ns со всеми необходимыми компонентами (в этом случае мы не упремся в ситуацию, когда все необходимое окружение попросту НЕ ВЛЕЗАЕТ в ноутбук разраба), далее два варианта - либо запускать код локально и каким-то образом проксировать доступы до кластера (как будто отлично с этой задачей справляются решения вроде https://www.telepresence.io/), либо запускать код в удаленном поде и настраивать синхронизацию исходников (что-то вроде https://tilt.dev/)

    Окей - в конце-концов есть еще https://skaffold.dev/docs/pipeline-stages/

    Почему не minikube? Потому что, как я уже сказал, машинки разработчика обычно достаточно хилые по сравнению с продовыми кластерами. Но мощнее, чем тестовый кластер на фритир в оракл клауде :-) Все-таки на 6 ядер и 16-32ГБ ОЗУ можно хоть что-то развернуть. Но самое главное другое - миникуб представляет из себя только одну из возможностей для деплоя. А если в проде опеншифт? А если какая-то особая конфигурация vanilla? Да ты замучаешься подгонять манифесты. Т.е. миникуб дает базовый кубернетес, но не дает полную идентичность с production окружением за которое в статье активно ратуют... И вся модель рассыпается.


    1. stratnevpy Автор
      14.01.2022 12:56

      Да, вы правы! Вариаций может быть очень много. Одну из них я постарался описать в этой статье :)


    1. AlexGraff
      14.01.2022 14:07
      +1

      Миникуб тут как один из вариантов. По сути совсем не важно в какой kubernetes деплоить. Это может быть как любой из вариантов локальных кластеров, так и варианты с удалёнными кластерами. werf'у достаточно указать кубконфиг, с которым он подключается к кластеру и docker registry, в котором хранить образы. Должно лишь соблюдаться условие что kubernetes, в который происходит деплой имеет доступ в registry, где лежит финальный образ. При этом werf позволяет финальные и промежуточные образы пушить в разные registry.


  1. eggstream
    14.01.2022 13:25
    +1

    Всё-таки Кубер привносит в разработку слишком много сложности, жаль Сварм не взлетел…


    1. gecube
      14.01.2022 13:36
      +2

      swarm нафиг не уперся и слава богу, что он не взлетел... Если хочется попроще, чем кубер - есть же nomad. И для доставки-сборки они же напилили https://www.waypointproject.io/

      Честно - Хашики гениальны, реально


  1. dnbstd
    14.01.2022 17:41

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


    1. stratnevpy Автор
      14.01.2022 18:40

      А в чем собственно проблемы могут возникнуть? Если конечное окружение работает в кубе, то для него как минимум есть cert-manager.


      1. gecube
        14.01.2022 19:33

        А в чем собственно проблемы могут возникнуть? 

        локально нельзя выписать летсэнкрипт, потому что нет внешнего айпи. А если он есть - нет привязки домена к этому внешнему айпи. Можно выкрутиться сервисами типа no-ip или sslip.io, но выглядит как %$#&*%&#*$&#


        1. stratnevpy Автор
          14.01.2022 19:53

          А локально нужен https?


        1. stratnevpy Автор
          14.01.2022 19:56

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


          1. gecube
            14.01.2022 20:28

            Я просто привёл пример, когда локальное окружение хуже удаленного. Не более. Но если Вы хотите поговорить, то да, возможны ситуации, когда https нужен и не самоподписанный. Чтобы функционал проверить, погонять и эффективно разрабатывать


            1. stratnevpy Автор
              14.01.2022 21:14

              Что мешает купить сертификат и использовать локально? Я думаю обойдётся дешевле, чем держать лишнюю ноду в кластере.


              1. gecube
                14.01.2022 21:52

                Что мешает купить сертификат и использовать локально?

                а зачем? Вы первый начали про cert-manager. К тому же, если этот сертификат скоммуниздят (а вероятность этого существенно выше на машине разраба, чем на каком-никаком сервере), то денежка пропала.


                1. stratnevpy Автор
                  14.01.2022 22:14

                  Затем же, зачем вам нужен https локально.


  1. vs_starosta
    15.01.2022 15:25

    Добрый день. А вы планируете дать развёрнутый ответ Виктору Фарсику на вопросы, заданные на его канале?


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


    1. aigrychev
      15.01.2022 16:04
      +1

      У нас запланирована с ним встреча на этот четверг. Вероятнее всего обе стороны дадут комментарии по результатам.

      Если в двух словах, то с его стороны это обзор продукта на примитивном примере и без хорошего погружения в werf, а с нашей — плохое позиционирование продукта (особенно вне русскоязычного сообщества).


    1. gecube
      15.01.2022 20:39

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


  1. Strafe2108
    16.01.2022 10:02

    Поддерживает ли werf какое то решение из коробки для multi-repo проектов аля микросервисов? Или же конфиги нужно мержить ручками?