В этой статье мы рассмотрим, как с помощью 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 OKnn$RESPONSEn" | 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 OKnn$RESPONSEn" | 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.
Читайте также в нашем блоге:
Автор: Константин