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

Мы поговорим об общих принципах работы с werf при использовании ее разработчиками, поэтому в качестве примера приложения используем небольшой эхо-сервер на основе shell-скрипта, который будет возвращать в ответ на запрос по адресу /ping строку Hello, werfer!. В следующих материалах будет рассмотрена работа и с «настоящими» приложениями, основанными на распространенных фреймворках на разных языках, но для начала сфокусируемся на общем подходе к разработке с использованием утилиты werf.
NB. Все файлы тестового приложения можно посмотреть и скачать в этом репозитории. Статья подготовлена на основе недавно анонсированного самоучителя werf.
В качестве кластера Kubernetes мы используем minikube, что позволит легко и быстро попробовать поработать с werf прямо на рабочем компьютере.
werf
Для тех, кто слышит это название впервые, поясним, что werf — это CLI-утилита, организующая полный цикл доставки приложения в Kubernetes. Она использует Git как единый источник, хранящий код и конфигурацию приложения. Каждый коммит — это состояние приложения, которое во время доставки werf синхронизирует с container registry, дособирая несуществующие слои конечных образов, а затем с приложением в Kubernetes, перевыкатывая изменившиеся ресурсы. Также werf позволяет очищать container registry от неактуальных артефактов по уникальному алгоритму, опирающемуся на историю Git и пользовательские политики.
Почему werf? Что в ней такого полезного и особенного? Основная ее фишка — объединение привычных для разработчиков и DevOps-/SRE-инженеров инструментов, таких как Git, Docker, container registry, CI-система, Helm и Kubernetes под одной утилитой. Тесная интеграция компонентов совместно с заложенными в процесс работы лучшими практиками, накопленными нашей компанией за годы работы с кубернетизацией приложений разных клиентов и запуском их в Kubernetes, делает werf достойным претендентом для доставки вашего приложения в Kubernetes.
Подготовка системы
Перед началом работы установите в вашу систему последнюю стабильную версию werf (v1.2 из канала обновлений stable), воспользовавшись официальной документацией.
Все команды и действия, приводимые в статье, актуальны для операционной системы Linux, в частности Ubuntu 20.04.03. При работе с другими ОС, такими как Windows или macOS, команды аналогичны, но могут встречаться небольшие особенности для каждой из этих ОС. (Найти уже адаптированные инструкции для этих систем можно в разделе «Первые шаги» нашего самоучителя.)
Сборка образа
Для начала нужно написать сам скрипт, который будет представлять собой наше «приложение». Создадим каталог, в котором будем работать (у нас это каталог app в домашнем каталоге пользователя):
mkdir app
Создадим внутри каталога скрипт hello.sh, в котором опишем простую логику ответа на запрос:
#!/bin/sh RESPONSE="Hello, werfer!" while true; do printf "HTTP/1.1 200 OK\n\n$RESPONSE\n" | ncat -lp 8000 done
Инициализируем в созданном каталоге новый Git-репозиторий и закоммитим первые изменения — созданный скрипт:
cd ~/app git init git add . git commit -m initial
Т.к. собираться и работать наше приложение будет в Docker, создадим рядом со скриптом Dockerfile, в котором будет описана логика сборки приложения в образ:
FROM alpine:3.14 WORKDIR /app # Устанавливаем зависимости приложения RUN apk add --no-cache --update nmap-ncat # Добавляем в образ созданный скрипт для запуска эхо-сервера # и устанавливаем разрешение на выполнение COPY hello.sh . RUN chmod +x hello.sh
Для того, чтобы werf знала про используемый для сборки Dockerfile, создадим в корне проекта конфигурационный файл werf.yaml, в котором укажем его:
project: werf-first-app configVersion: 1 --- image: app dockerfile: Dockerfile
Уже готовое состояние репозитория с нужными сейчас файлами также можно забрать из этого каталога репозитория werf/first-steps-example.
Теперь мы готовы собрать наше приложение. Обратите внимание, что перед сборкой нужно зафиксировать все изменения в репозитории проекта (созданные нами Dockerfile и т. д.), поэтому для начала выполним следующие команды:
git add . git commit -m FIRST
Запустим сборку приложение командой:
werf build
При успешной сборке увидим примерно следующий лог:
┌ ⛵ image app │ ┌ Building stage app/dockerfile │ │ app/dockerfile Sending build context to Docker daemon 4.096kB │ │ app/dockerfile Step 1/13 : FROM alpine:3.14 │ │ app/dockerfile ---> 0a97eee8041e │ │ app/dockerfile Step 2/13 : WORKDIR /app │ │ app/dockerfile ---> Running in d4c535c0d754 │ │ app/dockerfile Removing intermediate container d4c535c0d754 │ │ app/dockerfile ---> 5a2a81813edc │ │ app/dockerfile Step 3/13 : RUN apk add --no-cache --update nmap-ncat │ │ app/dockerfile ---> Running in ce40513872fc │ │ app/dockerfile fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/main/x86_64/APKINDEX.tar.gz │ │ app/dockerfile fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/community/x86_64/APKINDEX.tar.gz │ │ app/dockerfile (1/3) Installing lua5.3-libs (5.3.6-r0) │ │ app/dockerfile (2/3) Installing libpcap (1.10.0-r0) │ │ app/dockerfile (3/3) Installing nmap-ncat (7.91-r0) │ │ app/dockerfile Executing busybox-1.33.1-r6.trigger │ │ app/dockerfile OK: 6 MiB in 17 packages │ │ app/dockerfile Removing intermediate container ce40513872fc │ │ app/dockerfile ---> 8fffbbd2f295 │ │ app/dockerfile Step 4/13 : COPY hello.sh . │ │ app/dockerfile ---> 157c374b6224 │ │ app/dockerfile Step 5/13 : RUN chmod +x hello.sh │ │ app/dockerfile ---> Running in e603d67b6a34 │ │ app/dockerfile Removing intermediate container e603d67b6a34 │ │ app/dockerfile ---> e6d8a8dcd424 │ │ app/dockerfile Step 6/13 : LABEL werf=werf-first-app │ │ app/dockerfile ---> Running in 42b37bd0b1cd │ │ app/dockerfile Removing intermediate container 42b37bd0b1cd │ │ app/dockerfile ---> 2f27cc2d99bc │ │ app/dockerfile Step 7/13 : LABEL werf-cache-version=1.2 │ │ app/dockerfile ---> Running in 2e003ef0ec0a │ │ app/dockerfile Removing intermediate container 2e003ef0ec0a │ │ app/dockerfile ---> f55707dc38e6 │ │ app/dockerfile Step 8/13 : LABEL werf-docker-image-name=f4059163-28e5-44fe-a53a-a110ed27db2d │ │ app/dockerfile ---> Running in 4240a40d6032 │ │ app/dockerfile Removing intermediate container 4240a40d6032 │ │ app/dockerfile ---> 1ae5fc802a07 │ │ app/dockerfile Step 9/13 : LABEL werf-image=false │ │ app/dockerfile ---> Running in 610b776441c8 │ │ app/dockerfile Removing intermediate container 610b776441c8 │ │ app/dockerfile ---> 1c9066dcea1f │ │ app/dockerfile Step 10/13 : LABEL werf-project-repo-commit=4959e91153d14e07133806398a001814b76f83bd │ │ app/dockerfile ---> Running in ec973f81f08d │ │ app/dockerfile Removing intermediate container ec973f81f08d │ │ app/dockerfile ---> c938b7f22f96 │ │ app/dockerfile Step 11/13 : LABEL werf-stage-content-digest=2c3a4d5dd59c112c3cba8d1ef5590dc16a618489bdcaf276f551f8ef │ │ app/dockerfile ---> Running in 9acc7cd494df │ │ app/dockerfile Removing intermediate container 9acc7cd494df │ │ app/dockerfile ---> 4568493d8f34 │ │ app/dockerfile Step 12/13 : LABEL werf-stage-digest=11b3f1b95dd6a39a9473a34ba0615c513d799be7e67e48a2deeed05c │ │ app/dockerfile ---> Running in ac1bf708647a │ │ app/dockerfile Removing intermediate container ac1bf708647a │ │ app/dockerfile ---> 1b4d163b491c │ │ app/dockerfile Step 13/13 : LABEL werf-version=v1.2.40 │ │ app/dockerfile ---> Running in a1f9e286d870 │ │ app/dockerfile Removing intermediate container a1f9e286d870 │ │ app/dockerfile ---> eb03f3a2c600 │ │ app/dockerfile Successfully built eb03f3a2c600 │ │ app/dockerfile Successfully tagged a0e1fc05-970b-4b4c-bb8c-31c51a1a991a:latest │ │ ┌ Store stage into :local │ │ └ Store stage into :local (0.02 seconds) │ ├ Info │ │ name: werf-first-app:11b3f1b95dd6a39a9473a34ba0615c513d799be7e67e48a2deeed05c-1638357228902 │ │ id: eb03f3a2c600 │ │ created: 2021-12-01 14:13:48.839066578 +0300 MSK │ │ size: 6.0 MiB │ └ Building stage app/dockerfile (7.45 seconds) └ ⛵ image app (7.47 seconds) Running time 7.53 seconds
Чтобы убедиться в результате сборки, запустим собранное приложение командой:
werf run app --docker-options="-ti --rm -p 8000:8000" -- /app/hello.sh
Рассмотрим подробнее команду запуска. В ней заданы параметры Docker’а при помощи опции --docker-options, а в самом конце указана команда для выполнения внутри контейнера через два дефиса.
Проверим, что все запустилось и работает как нужно. Перейдем в браузере по адресу http://127.0.0.1:8000/ping, либо запросим ответ в другом терминале при помощи утилиты curl:
curl http://127.0.0.1:8000/ping
В результате увидим строку Hello, werfer!, а в логах запущенного контейнера появится следующее:
GET /ping HTTP/1.1 Host: 127.0.0.1:8000 User-Agent: curl/7.68.0 Accept: */*
Подготовка к деплою
Собрать приложение — половина дела, если не его треть. Ведь еще нужно его задеплоить на боевые серверы. Для этого давайте сэмулируем «продакшн» у себя на машине, поставив минимальный кластер Kubernetes и настроив его на работу с werf. Для этого мы сделаем следующее:
установим и запустим minikube — минимальный дистрибутив Kubernetes, который можно использоваться для простой и быстрой установки на рабочий ПК;
установим NGINX Ingress Controller — специальный компонент кластера, который отвечает за маршрутизацию запросов снаружи вовнутрь;
настроим файл
/etc/hostsдля доступа к кластеру по доменному имени приложения;авторизуемся на Docker Hub и настроим секрет с нужными учетными данными;
непосредственно задеплоим приложение в K8s.
1. Установка и запуск minikube
Для начала установим minikube, следуя его официальной документации. Если у вас в системе он уже установлен, убедитесь, что его версия соответствует последней актуальной (v1.23.2 на момент публикации статьи).
Создадим Kubernetes-кластер с помощью minikube:
# удаляем существующий minikube-кластер, если он существует minikube delete # запускаем новый minikube-кластер minikube start --driver=docker
Зададим пространство имен в Kubernetes (namespace) по умолчанию, чтобы не указывать его явно каждый раз при использовании kubectl (здесь мы только задаем имя по умолчанию, но не создаём сам namespace, это будет сделано ниже):
kubectl config set-context minikube --namespace=werf-first-app
Если у вас не установлена kubectl, сделать это можно двумя способами:
Установить ее в систему отдельно, воспользовавшись официальной документацией.
Использовать поставляемый с minikube бинарник утилиты. Для этого достаточно выполнить команды:
alias kubectl="minikube kubectl --" echo 'alias kubectl="minikube kubectl --"' >> ~/.bash_aliases
Если вы выбрали второй вариант, то при первом обращении к
kubectlпо созданному alias’у утилита будет выкачана и доступна к использованию.
Давайте проверим, что все прошло успешно, и посмотрим на список всех Pod’ов, запущенных в свежесозданном кластере:
kubectl get --all-namespaces pod
Pod — это абстрактный объект Kubernetes, представляющий собой группу из одного или нескольких контейнеров приложения и совместно используемых ресурсов для этих контейнеров.
В результате выполнения команды мы увидим приблизительно следующую картинку:
NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-78fcd69978-qldbj 1/1 Running 0 14m kube-system etcd-minikube 1/1 Running 0 14m kube-system kube-apiserver-minikube 1/1 Running 0 14m kube-system kube-controller-manager-minikube 1/1 Running 0 14m kube-system kube-proxy-5hrfd 1/1 Running 0 14m kube-system kube-scheduler-minikube 1/1 Running 0 14m kube-system storage-provisioner 1/1 Running 1 (13m ago) 14m
Посмотрите внимательно на столбцы READY и STATUS: если все Pod’ы имеют статус Running, а их количество отображается в виде 1/1 (главное, чтобы число слева было равно числу справа), значит все прошло успешно, и наш кластер готов к использованию. Если картина не похожа на представленную выше, то попробуйте подождать и посмотреть чуть позже еще раз — возможно, что не все Pod’ы успели запуститься быстро, и в момент первого просмотра их состояние еще не было активным.
2. Установка NGINX Ingress Controller
Следующим шагом в подготовке будет установка и настройка Ingress-контроллера, основной задачей которого будет проброс внешних HTTP-запросов в наш кластер.
Установим его следующей командой:
minikube addons enable ingress
В зависимости от характеристик вашей машины этот процесс может занять довольно длительное время. Например, на моей машине этот процесс занял около четырех минут.
Если все прошло успешно, вы увидите сообщение о том, что аддон успешно установлен и активирован.
The 'ingress' addon is enabled
Немного подождем, чтобы он успел запуститься, и убедимся, что он работает:
kubectl -n ingress-nginx get pod
В результате будет отображено нечто похожее на картину ранее:
NAME READY STATUS RESTARTS AGE ingress-nginx-admission-create--1-xn4wr 0/1 Completed 0 6m19s ingress-nginx-admission-patch--1-dzxt6 0/1 Completed 0 6m19s ingress-nginx-controller-69bdbc4d57-bp27q 1/1 Running 0 6m19s
Нас интересует последняя строка — если статус стоит Running, значит все нормально, контроллер работает.
3. Внесение изменений в файл hosts
Последним шагом в настройке окружения будет внесение изменений в файл hosts, чтобы наша система при обращении на тестовый домен попадала на локальный кластер.
Для тестирования приложения мы будем использовать адрес werf-first-app.test. Убедимся, что команда minikube ip выдает валидный IP-адрес, выполнив ее в терминале. Если в результате вы видите сообщения, явно не похожие на IP-адрес (в моем случае это 192.168.49.2) — вернитесь на несколько шагов назад и пройдите установку и запуск кластера minikube еще раз.
Если все работает как надо, выполним следующую команду:
echo "$(minikube ip) werf-first-app.test" | sudo tee -a /etc/hosts
Проверить правильность выполнения можно, посмотрев на содержимое искомого файла — в самом конца должна появиться строка вида 192.168.49.2 werf-first-app.test.
Давайте убедимся, что все сделанное нами выше работает как надо. Выполним запрос на адрес http://werf-first-app.test/ping с использованием утилиты curl:
curl http://werf-first-app.test/ping
Если все работает правильно — NGINX Ingress Controller вернет страницу с кодом 404, сообщающую, что такого endpoint’а в системе нет:
<html> <head><title>404 Not Found</title></head> <body> <center><h1>404 Not Found</h1></center> <hr><center>nginx</center> </body> </html>
4. Авторизация в Docker Hub
Для дальнейшей работы понадобится хранилище собираемых образов, и мы предлагаем использовать для этого приватный репозиторий на Docker Hub. Для удобства используем такое же имя, как у приложения — werf-first-app.
Залогинимся на Docker Hub, выполнив следующую команду:
docker login Username: <ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB> Password: <ПАРОЛЬ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>
Если введено правильно, увидим сообщение Login Succeeded.
5. Создание Secret для доступа к registry
Чтобы иметь возможность в процессе работы пользоваться приватным container registry для хранения образов, необходимо создать Secret с учетными данными для входа в registry. Здесь есть одна особенность — Secret должен располагаться в том же namespace’е, что и приложение.
Поэтому необходимо заранее создать namespace для нашего приложения:
kubectl create namespace werf-first-app
В результате должно отобразится сообщение о том, что новое пространство имен создано — namespace/werf-first-app created.
Далее создадим Secret с именем registrysecret:
kubectl create secret docker-registry registrysecret \ --docker-server='https://index.docker.io/v1/' \ --docker-username='<ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>' \ --docker-password='<ПАРОЛЬ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>'
Все прошло успешно, если в результате выполнения команды отображается строка secret/registrysecret created. Если же по какой-то причине вы ошиблись при создании, то созданный секрет можно удалить командой kubectl delete secret registrysecret, а затем создать его заново.
Это стандартный способ создания Secret из официальной документации Kubernetes.
На этом шаге подготовку окружения к деплою можно считать завершенной.
Далее мы будем использовать созданный Secret для получения образов приложения из registry, указывая поле imagePullSecrets при конфигурации Pod’ов.
Деплой приложения в кластер
Для деплоя приложения в кластер необходимо подготовить Kubernetes-манифесты, которые описывают необходимые для работы ресурсы. Создавать их будем в формате Helm-чартов — пакетов Helm, содержащих все определения ресурсов, необходимых для запуска приложения, инструмента или службы внутри кластера Kubernetes.
Для нашего приложения понадобится три ресурса — Deployment, отвечающий за запуск приложений в контейнерах, а также Ingress и Service, отвечающие за доступ к запущенному приложению снаружи и изнутри кластера соответственно.
Для их создания в случае нашего приложения получится следующая структура файлов:
. ├── Dockerfile ├── .dockerignore ├── hello.sh ├── .helm │ └── templates │ ├── deployment.yaml │ ├── ingress.yaml │ └── service.yaml └── werf.yaml
Мы создали скрытый каталог .helm, в который поместим упомянутые выше манифесты (их содержимое будет описано ниже) в подкаталоге templates. Обратите внимание: чтобы исключить эти файлы из контекста сборки Docker-образа, каталог с манифестами мы добавляем в файл .dockerignore:
/.helm/
Рассмотрим манифесты используемых ресурсов более подробно.
1. Deployment
Ресурс Deployment отвечает за создание набора Pod’ов, запускающих приложение. Выглядит он так:
apiVersion: apps/v1 kind: Deployment metadata: name: werf-first-app spec: replicas: 1 selector: matchLabels: app: werf-first-app template: metadata: labels: app: werf-first-app spec: imagePullSecrets: - name: registrysecret containers: - name: app image: {{ .Values.werf.image.app }} command: ["/app/hello.sh"] ports: - containerPort: 8000
Здесь с помощью шаблонизации подставляется полное имя Docker-образа нашего приложения: {{ .Values.werf.image.app }}. Важно отметить, что для доступа к этому значению необходимо использовать имя компонента, которое используется в werf.yaml — в нашем случае это app.
Полные имена собираемых образов, так же как и другие сервисные данные, werf автоматически добавляет в параметры Helm-чарта (.Values), они все доступны по ключу werf.
werf пересобирает образы только при изменениях в добавляемых файлах (используемых в Dockerfile-инструкциях COPY и ADD), а также при изменении конфигурации образа в werf.yaml. При пересборке изменяется и тег образа, что автоматически приводит к обновлению Deployment’а. Если же изменений в этих файлах нет, то образ приложения и связанный с ним Deployment останутся без изменений — значит на данный момент состояние приложения в кластере актуальное.
2. Service
Этот ресурс позволяет другим приложениям внутри кластера обращаться к нашему приложению. Выглядит он так:
apiVersion: v1 kind: Service metadata: name: werf-first-app spec: selector: app: werf-first-app ports: - name: http port: 8000
3. Ingress
В отличие от предыдущего ресурса Service Ingress позволяет открыть доступ к нашему приложению снаружи кластера. В нем мы указываем, на какой Service внутри Kubernetes нужно перенаправлять трафик, поступающий на публичный домен werf-first-app.test. Выглядит ресурс так:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx name: werf-first-app spec: rules: - host: werf-first-app.test http: paths: - path: / pathType: Prefix backend: service: name: werf-first-app port: number: 8000
Деплой приложения в Kubernetes
Зафиксируем в Git изменения конфигурации — добавленные ресурсы для деплоя в Kubernetes, — как делали в начале статьи:
git add . git commit -m FIRST
Уже готовое состояние репозитория с нужными сейчас файлами также можно забрать из этого каталога репозитория werf/first-steps-example.
Запускаем деплой командой:
werf converge --repo <ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>/werf-first-app
Если все прошло успешно — вы увидите примерно такой лог:
│ │ app/dockerfile Successfully built 558e6bf23bb7 │ │ app/dockerfile Successfully tagged 96c8cfca-d7db-4bf0-8351-cbdc0777c5b5:latest │ │ ┌ Store stage into .../werf-first-app │ │ └ Store stage into .../werf-first-app (16.85 seconds) │ ├ Info │ │ name: .../werf-first-app:11b3f1b95dd6a39a9473a34ba0615c513d799be7e67e48a2deeed05c-1638438736538 │ │ id: 558e6bf23bb7 │ │ created: 2021-12-02 12:52:16 +0300 MSK │ │ size: 3.0 MiB │ └ Building stage app/dockerfile (31.61 seconds) └ ⛵ image app (39.62 seconds) Release "werf-first-app" does not exist. Installing it now. ┌ Waiting for release resources to become ready │ ┌ Status progress │ │ DEPLOYMENT REPLICAS AVAILABLE UP-TO-DATE │ │ werf-first-app 1/1 0 1 │ │ │ POD READY RESTARTS STATUS --- │ │ └── first-app-559d4f59b-j5ffv 0/1 0 ContainerCreating Waiting for: available 0->1 │ └ Status progress │ │ ┌ Status progress │ │ DEPLOYMENT REPLICAS AVAILABLE UP-TO-DATE │ │ werf-first-app 1/1 0->1 1 │ │ │ POD READY RESTARTS STATUS │ │ └── first-app-559d4f59b-j5ffv 1/1 0 ContainerCreating -> │ │ Running │ └ Status progress └ Waiting for release resources to become ready (9.40 seconds) NAME: werf-first-app LAST DEPLOYED: Thu Dec 2 12:52:40 2021 NAMESPACE: werf-first-app STATUS: deployed REVISION: 1 TEST SUITE: None Running time 60.24 seconds
Давайте убедимся, что все прошло успешно:
curl http://werf-first-app.test/ping
В ответ получим заветное:
Hello, werfer!
Мы успешно задеплоили приложение в Kubernetes-кластер!
Внесение изменений в приложение
Давайте попробуем внести изменения в наше приложение и посмотреть, как werf его пересоберёт и повторно задеплоит в кластер.
Масштабирование
Наш веб-сервер запущен в Deployment’е web-first-app. Посмотрим, сколько реплик запущено:
$ kubectl get pod NAME READY STATUS RESTARTS AGE werf-first-app-559d4f59b-j5ffv 1/1 Running 0 146m
Сейчас реплика одна (смотрим на количество строк, начинающихся на werf-first-app). Вручную заменим их количество на четыре:
kubectl edit deployment werf-first-app
Откроется консольный текстовый редактор с текстом манифеста. Найдем там строку spec.replicas и заменим число реплик на четыре: spec.replicas=4. Подождем немного и снова посмотрим на количество запущенных реплик приложения:
$ kubectl get pod NAME READY STATUS RESTARTS AGE werf-first-app-559d4f59b-j5ffv 1/1 Running 0 172m werf-first-app-559d4f59b-l4wxp 1/1 Running 0 10s werf-first-app-559d4f59b-xrnxn 1/1 Running 0 10s werf-first-app-559d4f59b-z48qm 1/1 Running 0 10s
Сейчас мы произвели масштабирование вручную, напрямую указав количество реплик внутри кластера, минуя при этом Git. Если сейчас снова запустим werf converge:
werf converge --repo <ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>/werf-first-app
А затем снова посмотрим на количество реплик приложения:
$ kubectl get pod NAME READY STATUS RESTARTS AGE werf-first-app-559d4f59b-j5ffv 1/1 Running 0 175m
То увидим, что количество снова соответствует тому, что указано в Git-репозитории — в файле-манифесте, который мы не редактировали. Так получилось потому, что werf снова привела состояние кластера к тому, что описано в текущем Git-коммите. Этот принцип называется гитерминизмом (giterminism, от англ. Git + determinism).
Чтобы соблюсти этот принцип и сделать все правильно, нужно изменить тот же параметр количества реплик в файлах проекта в репозитории. Для этого отредактируем наш файл deployment.yaml и закоммитим изменения в репозиторий.
Новое содержимое файла:
apiVersion: apps/v1 kind: Deployment metadata: name: werf-first-app spec: # Меняем количество реплик на 4 replicas: 4 selector: matchLabels: app: werf-first-app template: metadata: labels: app: werf-first-app spec: imagePullSecrets: - name: registrysecret containers: - name: app image: {{ .Values.werf.image.app }} command: ["/app/hello.sh"] ports: - containerPort: 8000
Закоммитим изменения и пересоберем приложение командой:
werf converge --repo <ИМЯ ПОЛЬЗОВАТЕЛЯ DOCKER HUB>/werf-first-app
Если теперь посмотреть количество запущенных реплик, их будет четыре:
$ kubectl get pod NAME READY STATUS RESTARTS AGE werf-first-app-559d4f59b-2fth5 1/1 Running 0 7m12s werf-first-app-559d4f59b-7jqnv 1/1 Running 0 7m12s werf-first-app-559d4f59b-j5ffv 1/1 Running 0 3h17m werf-first-app-559d4f59b-n9n64 1/1 Running 0 7m12s
Давайте снова вернём одну реплику. Снова исправляем файл deployment.yaml, коммитим изменения и перезапускаем converge.
$ kubectl get pod NAME READY STATUS RESTARTS AGE werf-first-app-559d4f59b-j5ffv 1/1 Running 0 3h24m
Меняем само приложение
Сейчас наше приложение отвечает Hello, werfer!. Давайте изменим эту строку и перезапустим обновленное приложение в кластере. Открываем наш hello.sh и меняем строку ответа на любую другую, например на Hello, Habr users!:
#!/bin/sh RESPONSE="Hello, Habr users!" while true; do printf "HTTP/1.1 200 OK\n\n$RESPONSE\n" | ncat -lp 8000 done
Действуем по старой схеме — коммитим изменения, перезапускаем converge и смотрим результат:
$ curl "http://werf-first-app.test/ping" Hello, Habr users!
Поздравляю, у нас все получилось и работает!
Выводы
В этой статье мы рассмотрели пример простейшего приложения, которое было собрано и задеплоено в кластер Kubernetes с помощью werf.
Эта статья основана на главе «Первые шаги» нашего онлайн-самоучителя. Представляя её как максимально лаконичный практический tutorial, мы не стали останавливаться на некоторых теоретических вопросах, которые раскрыты в полном руководстве: шаблоны и манифесты Kubernetes, основные ресурсы K8s для запуска приложений (Deployment, Service, Ingress), режимы работы werf и гитерминизм, использование Helm в werf и т. д. Ответы на них можно найти, проходя «Первые шаги» в полном самоучителе.
Надеемся, что эта статья поможет вам сделать ваши первые шаги с werf и приобрести немного опыта в части деплоя приложений в Kubernetes!
С любыми вопросами и предложениями ждем вас в комментариях к статье, а также в Telegram-каналe werf_ru, где уже более 700 участников и всегда рады помочь. Любым issues (и звёздам) всегда рад и GitHub-репозиторий werf.
P.S.
Полезные ссылки:
Чат по werf, в котором на вопросы пользователей отвечают разработчики — если вдруг что-то пойдет не так или не получится.
Самоучитель по Kubernetes от команды werf для DevOps-инженеров и разработчиков с уроками по CI/CD созданию приложений и разворачиванию окружения с примерами для node.js, Spring, django, Python, Go, Ruby on Rails, Laravel.
Читайте также в нашем блоге:
