dapp — наша Open Source-утилита, помогающая DevOps-инженерам сопровождать процессы CI/CD (подробнее о ней читайте в анонсе). В русскоязычной документации к ней приведён пример сборки простого приложения, а подробнее этот процесс (с демонстрацией основных возможностей dapp) был представлен в первой части статьи. Теперь, на основе того же простого приложения, покажу, как dapp работает с кластером Kubernetes.
Как и в первой статье, все дополнения для кода приложения symfony-demo есть в нашем репозитории. Но обойтись Vagrantfile
в этот раз не получится: Docker и dapp придётся поставить локально.
Чтобы пройти всё по шагам, нужно начать с ветки dapp_build
, куда был добавлен Dappfile
в первой статье.
$ git clone https://github.com/flant/symfony-demo.git
$ cd symfony-demo
$ git checkout dapp_build
$ git checkout -b kube_test
$ dapp dimg build
Запуск кластера с помощью Minikube
Теперь нужно создать кластер Kubernetes, где dapp запустит приложение. Для этого будем использовать Minikube как рекомендуемый способ запуска кластера на локальной машине.
Установка проста и заключается в скачивании Minikube и утилиты kubectl. Инструкции доступны по ссылкам:
Примечание: Читайте также наш перевод статьи «Начало работы в Kubernetes с помощью Minikube».
После установки нужно запустить minikube setup
. Minikube скачает ISO и запустит из него виртуальную машину в VirtualBox.
После успешного старта можно посмотреть, что есть в кластере:
$ kubectl get all
NAME READY STATUS RESTARTS AGE
po/hello-minikube-938614450-zx7m6 1/1 Running 3 71d
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/hello-minikube 10.0.0.102 <nodes> 8080:31429/TCP 71d
svc/kubernetes 10.0.0.1 <none> 443/TCP 71d
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/hello-minikube 1 1 1 1 71d
NAME DESIRED CURRENT READY AGE
rs/hello-minikube-938614450 1 1 1 71d
Команда покажет все ресурсы в пространстве имён (namespace) по умолчанию (default
). Список всех namespaces можно посмотреть через kubectl get ns
.
Подготовка, шаг №1: реестр для образов
Итак, мы запустили кластер Kubernetes в виртуальной машине. Что ещё понадобится для запуска приложения?
Во-первых, для этого нужно загрузить образ туда, откуда кластер сможет его получить. Можно использовать общий Docker Registry или же установить свой Registry в кластере (мы так делаем для production-кластеров). Для локальной разработки тоже лучше подойдет второй вариант, а реализовать его с dapp совсем просто — для этого есть специальная команда:
$ dapp kube minikube setup
Restart minikube [RUNNING]
minikube: Running
localkube: Running
kubectl: Correctly Configured: pointing to minikube-vm at 192.168.99.100
Starting local Kubernetes v1.6.4 cluster...
Starting VM...
Moving files into cluster...
Setting up certs...
Starting cluster components...
Connecting to cluster...
Setting up kubeconfig...
Kubectl is now configured to use the cluster.
Restart minikube [OK] 34.18 sec
Wait till minikube ready [RUNNING]
Wait till minikube ready [OK] 0.05 sec
Run registry [RUNNING]
Run registry [OK] 61.44 sec
Run registry forwarder daemon [RUNNING]
Run registry forwarder daemon [OK] 5.01 sec
После её выполнения в списке системных процессах появляется такое перенаправление:
username 13317 0.5 0.4 57184 36076 pts/17 Sl 14:03 0:00 kubectl port-forward --namespace kube-system kube-registry-6nw7m 5000:5000
… а в namespace под названием kube-system
создаётся Registry и прокси к нему:
$ kubectl get -n kube-system all
NAME READY STATUS RESTARTS AGE
po/kube-addon-manager-minikube 1/1 Running 2 22m
po/kube-dns-1301475494-7kk6l 3/3 Running 3 22m
po/kube-dns-v20-g7hr9 3/3 Running 9 71d
po/kube-registry-6nw7m 1/1 Running 0 3m
po/kube-registry-proxy 1/1 Running 0 3m
po/kubernetes-dashboard-9zsv8 1/1 Running 3 71d
po/kubernetes-dashboard-f4tp1 1/1 Running 1 22m
NAME DESIRED CURRENT READY AGE
rc/kube-dns-v20 1 1 1 71d
rc/kube-registry 1 1 1 3m
rc/kubernetes-dashboard 1 1 1 71d
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/kube-dns 10.0.0.10 <none> 53/UDP,53/TCP 71d
svc/kube-registry 10.0.0.142 <none> 5000/TCP 3m
svc/kubernetes-dashboard 10.0.0.249 <nodes> 80:30000/TCP 71d
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/kube-dns 1 1 1 1 22m
NAME DESIRED CURRENT READY AGE
rs/kube-dns-1301475494 1 1 1 22m
Протестируем запущенный Registry, выложив в него наш образ командой dapp dimg push --tag-branch :minikube
. Используемый здесь :minikube
— это встроенный в dapp алиас специально для Minikube, который будет преобразован в localhost:5000/symfony-demo
.
$ dapp dimg push --tag-branch :minikube
symfony-demo-app
localhost:5000/symfony-demo:symfony-demo-app-kube_test [PUSHING]
pushing image `localhost:5000/symfony-demo:symfony-demo-app-kube_test` [RUNNING]
The push refers to a repository [localhost:5000/symfony-demo]
0ea2a2940c53: Preparing
ffe608c425e1: Preparing
5c2cc2aa6663: Preparing
edbfc49bce31: Preparing
308e5999b491: Preparing
9688e9ffce23: Preparing
0566c118947e: Preparing
6f9cf951edf5: Preparing
182d2a55830d: Preparing
5a4c2c9a24fc: Preparing
cb11ba605400: Preparing
6f9cf951edf5: Waiting
182d2a55830d: Waiting
5a4c2c9a24fc: Waiting
cb11ba605400: Waiting
9688e9ffce23: Waiting
0566c118947e: Waiting
0ea2a2940c53: Layer already exists
308e5999b491: Layer already exists
ffe608c425e1: Layer already exists
edbfc49bce31: Layer already exists
5c2cc2aa6663: Layer already exists
0566c118947e: Layer already exists
9688e9ffce23: Layer already exists
182d2a55830d: Layer already exists
6f9cf951edf5: Layer already exists
cb11ba605400: Layer already exists
5a4c2c9a24fc: Layer already exists
symfony-demo-app-kube_test: digest: sha256:5c55386de5f40895e0d8292b041d4dbb09373b78d398695a1f3e9bf23ee7e123 size: 2616
pushing image `localhost:5000/symfony-demo:symfony-demo-app-kube_test` [OK] 0.54 sec
Видно, что тег образа в Registry составлен из имени dimg и имени ветки (через дефис).
Подготовка, шаг №2: конфигурация ресурсов (Helm)
Вторая часть, необходимая для запуска приложения в кластере, — это конфигурация ресурсов. Стандартной утилитой управления кластером Kubernetes является kubectl
. Если нужно создать новый ресурс (Deployment, Service, Ingress и т.д.) или изменить свойства существующего ресурса, то на вход утилите передаётся YAML-файл с конфигурацией.
Однако dapp не использует напрямую kubectl
, а работает с так называемым пакетным менеджером — Helm, — который предоставляет шаблонизацию YAML-файлов и сам управляет выкатом в кластер.
Поэтому наш следующий шаг — это установка Helm. Официальную инструкцию можно найти в документации проекта.
После установки необходимо запустить helm init
. Что она делает? Helm состоит из клиентской части, которую мы установили, и серверной. Команда helm init
устанавливает серверную часть (tiller
). Посмотрим, что появилось в namespace kube-system
:
$ kubectl get -n kube-system all
NAME READY STATUS RESTARTS AGE
po/kube-addon-manager-minikube 1/1 Running 2 1h
po/kube-dns-1301475494-7kk6l 3/3 Running 3 1h
po/kube-dns-v20-g7hr9 3/3 Running 9 71d
po/kube-registry-6nw7m 1/1 Running 0 1h
po/kube-registry-proxy 1/1 Running 0 1h
po/kubernetes-dashboard-9zsv8 1/1 Running 3 71d
po/kubernetes-dashboard-f4tp1 1/1 Running 1 1h
!!! po/tiller-deploy-3703072393-bdqn8 1/1 Running 0 3m
NAME DESIRED CURRENT READY AGE
rc/kube-dns-v20 1 1 1 71d
rc/kube-registry 1 1 1 1h
rc/kubernetes-dashboard 1 1 1 71d
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/kube-dns 10.0.0.10 <none> 53/UDP,53/TCP 71d
svc/kube-registry 10.0.0.142 <none> 5000/TCP 1h
svc/kubernetes-dashboard 10.0.0.249 <nodes> 80:30000/TCP 71d
!!! svc/tiller-deploy 10.0.0.196 <none> 44134/TCP 3m
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/kube-dns 1 1 1 1 1h
!!! deploy/tiller-deploy 1 1 1 1 3m
NAME DESIRED CURRENT READY AGE
rs/kube-dns-1301475494 1 1 1 1h
!!! rs/tiller-deploy-3703072393 1 1 1 3m
(Здесь и далее знаком «!!!» вручную выделены строки, на которые стоит обратить внимание.)
То есть: появился Deployment под названием tiller-deploy
с одним ReplicaSet и одним подом (Pod). Для Deployment сделан одноимённый Service (tiller-deploy
), который открывает доступ через порт 44134.
Подготовка, шаг №3: IngressController
Третья часть — сама конфигурация для приложения. На данном этапе нужно понять, что требуется выложить в кластер, чтобы приложение заработало.
Предлагается следующая схема:
- приложение — это Deployment. Для начала это будет один ReplicaSet из одного пода, как сделано для Registry;
- приложение отвечает на порту 8000, поэтому нужно определить Service, чтобы под смог отвечать на запросы извне;
- у нас веб-приложение, поэтому нужен способ получать пакеты от пользователей на 80-м порту. Это делается ресурсом Ingress. Для работы таких ресурсов нужно настроить IngressController.
IngressController — это дополнительный компонент кластера Kubernetes для организации веб-приложений с балансировкой нагрузки. По сути это nginx, конфигурация которого зависит от ресурсов Ingress, добавляемых в кластер. Компонент нужно ставить отдельно, а для minikube существует addon. Подробнее о нём можно почитать в этой статье на английском, а пока просто запустим установку IngressController:
$ minikube addons enable ingress
ingress was successfully enabled
… и посмотрим, что появилось в кластере:
$ kubectl get -n kube-system all
NAME READY STATUS RESTARTS AGE
!!! po/default-http-backend-vbrf3 1/1 Running 0 2m
po/kube-addon-manager-minikube 1/1 Running 2 3h
po/kube-dns-1301475494-7kk6l 3/3 Running 3 3h
po/kube-dns-v20-g7hr9 3/3 Running 9 72d
po/kube-registry-6nw7m 1/1 Running 0 3h
po/kube-registry-proxy 1/1 Running 0 3h
po/kubernetes-dashboard-9zsv8 1/1 Running 3 72d
po/kubernetes-dashboard-f4tp1 1/1 Running 1 3h
!!! po/nginx-ingress-controller-hmvg9 1/1 Running 0 2m
po/tiller-deploy-3703072393-bdqn8 1/1 Running 0 1h
NAME DESIRED CURRENT READY AGE
!!! rc/default-http-backend 1 1 1 2m
rc/kube-dns-v20 1 1 1 72d
rc/kube-registry 1 1 1 3h
rc/kubernetes-dashboard 1 1 1 72d
!!! rc/nginx-ingress-controller 1 1 1 2m
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
!!! svc/default-http-backend 10.0.0.131 <nodes> 80:30001/TCP 2m
svc/kube-dns 10.0.0.10 <none> 53/UDP,53/TCP 72d
svc/kube-registry 10.0.0.142 <none> 5000/TCP 3h
svc/kubernetes-dashboard 10.0.0.249 <nodes> 80:30000/TCP 72d
svc/tiller-deploy 10.0.0.196 <none> 44134/TCP 1h
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/kube-dns 1 1 1 1 3h
deploy/tiller-deploy 1 1 1 1 1h
NAME DESIRED CURRENT READY AGE
rs/kube-dns-1301475494 1 1 1 3h
rs/tiller-deploy-3703072393 1 1 1 1h
Как проверить? IngressController в своём составе имеет default-http-backend
, который отвечает ошибкой 404 на все страницы, для которых нет обработчика. Это можно увидеть такой командой:
$ curl -i $(minikube ip)
HTTP/1.1 404 Not Found
Server: nginx/1.13.1
Date: Fri, 14 Jul 2017 14:29:46 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 21
Connection: keep-alive
Strict-Transport-Security: max-age=15724800; includeSubDomains;
default backend - 404
Результат положительный — приходит ответ от nginx со строкой default backend - 404
.
Описание конфигурации для Helm
Теперь можно описать конфигурацию приложения. Базовую конфигурацию поможет сгенерировать команда helm create имя_приложения
:
$ helm create symfony-demo
$ tree symfony-demo
symfony-demo/
├── charts
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── ingress.yaml
│ ├── NOTES.txt
│ └── service.yaml
└── values.yaml
dapp ожидает эту структуру в директории под названием .helm
(см. документацию), поэтому нужно переименовать symfony-demo
в .helm
.
Мы сейчас создали описание chart'а. Chart — это единица конфигурации для Helm, можно думать о нём как о неком пакете. Например, есть chart для nginx, для MySQL, для Redis. И с помощью таких chart'ов можно собрать нужную конфигурацию в кластере. Helm выкладывает в Kubernetes не отдельные образы, а именно Chart'ы (официальная документация).
Файл Chart.yaml
— это описание chart'а нашего приложения. Здесь нужно указать как минимум имя приложения и версию:
$ cat Chart.yaml
apiVersion: v1
description: A Helm chart for Kubernetes
name: symfony-demo
version: 0.1.0
Файл values.yaml
— описание переменных, которые будут доступны в шаблонах. Например, в сгенерированном файле есть image: repository: nginx
. Эта переменная будет доступна через такую конструкцию: {{ .Values.image.repository }}
.
Директория charts
пока пуста, потому что наш chart приложения пока не использует внешние chart'ы.
Наконец, директория templates
— здесь хранятся шаблоны YAML-файлов с описанием ресурсов для их размещения в кластере. Сгенерированные шаблоны не сильно нужны, поэтому с ними можно ознакомиться и удалить.
Для начала опишем простой вариант Deployment для нашего приложения:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ .Chart.Name }}-backend
spec:
replicas: 1
template:
metadata:
labels:
app: {{ .Chart.Name }}-backend
spec:
containers:
- command: [ '/opt/start.sh' ]
image: {{ tuple "symfony-demo-app" . | include "dimg" }}
imagePullPolicy: Always
name: {{ .Chart.Name }}-backend
ports:
- containerPort: 8000
name: http
protocol: TCP
env:
- name: KUBERNETES_DEPLOYED
value: "{{ now }}"
В конфигурации описано, что нам нужна пока что одна реплика, а в template
указано, какие поды нужно реплицировать. В этом описании указывается образ, который будет запускаться и порты, которые доступны другим контейнерам в поде.
Упомянутое в конфиге .Chart.Name
— это значение из charts.yaml
.
Переменая KUBERNETES_DEPLOYED
нужна, чтобы Helm обновлял поды, если мы обновим образ без изменения тега. Это удобно для отладки и локальной разработки.
Далее опишем Service:
apiVersion: v1
kind: Service
metadata:
name: {{ .Chart.Name }}-srv
spec:
type: ClusterIP
selector:
app: {{ .Chart.Name }}-backend
ports:
- name: http
port: 8000
protocol: TCP
Этим ресурсом мы создаём DNS-запись symfony-demo-app-srv
, по которой другие Deployments смогут получать доступ к приложению.
Эти два описания объединяются через ---
и записываются в .helm/templates/backend.yaml
, после чего можно разворачивать приложение!
Первый деплой
Теперь всё готово, чтобы запустить dapp kube deploy
(подробнее о команде см. в документации):
$ dapp kube deploy :minikube --image-version kube_test
Deploy release symfony-demo-default [RUNNING]
Release "symfony-demo-default" has been upgraded. Happy Helming!
LAST DEPLOYED: Fri Jul 14 18:32:38 2017
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
symfony-demo-app-backend 1 1 1 0 7s
==> v1/Service
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
symfony-demo-app-srv 10.0.0.173 <none> 8000/TCP 7s
Deploy release symfony-demo-default [OK] 7.02 sec
Видим, что в кластере появляется под в состоянии ContainerCreating
:
po/symfony-demo-app-backend-3899272958-hzk4l 0/1 ContainerCreating 0 24s
… и через некоторое время всё работает:
$ kubectl get all
NAME READY STATUS RESTARTS AGE
po/hello-minikube-938614450-zx7m6 1/1 Running 3 72d
!!! po/symfony-demo-app-backend-3899272958-hzk4l 1/1 Running 0 47s
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/hello-minikube 10.0.0.102 <nodes> 8080:31429/TCP 72d
svc/kubernetes 10.0.0.1 <none> 443/TCP 72d
!!! svc/symfony-demo-app-srv 10.0.0.173 <none> 8000/TCP 47s
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deploy/hello-minikube 1 1 1 1 72d
deploy/symfony-demo-app-backend 1 1 1 1 47s
NAME DESIRED CURRENT READY AGE
rs/hello-minikube-938614450 1 1 1 72d
!!! rs/symfony-demo-app-backend-3899272958 1 1 1 47s
Создан ReplicaSet, Pod, Service, то есть приложение запущено. Это можно проверить «по старинке», зайдя в контейнер:
$ kubectl exec -ti symfony-demo-app-backend-3899272958-hzk4l bash
root@symfony-demo-app-backend-3899272958-hzk4l:/# curl localhost:8000
Открываем доступ
Теперь, чтобы приложение стало доступно по $(minikube ip)
, добавим ресурс Ingress. Для этого опишем его в .helm/templates/backend-ingress.yaml
так:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ .Chart.Name }}
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: {{ .Chart.Name }}-srv
servicePort: 8000
serviceName
должно совпадать с именем Service, которое было объявлено в backend.yaml
. Разворачиваем приложение ещё раз:
$ dapp kube deploy :minikube --image-version kube_test
Deploy release symfony-demo-default [RUNNING]
Release "symfony-demo-default" has been upgraded. Happy Helming!
LAST DEPLOYED: Fri Jul 14 19:00:28 2017
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Service
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
symfony-demo-app-srv 10.0.0.173 <none> 8000/TCP 27m
==> v1beta1/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
symfony-demo-app-backend 1 1 1 1 27m
==> v1beta1/Ingress
NAME HOSTS ADDRESS PORTS AGE
symfony-demo-app * 192.168.99.100 80 2s
Deploy release symfony-demo-default [OK] 3.06 sec
Появился v1beta1/Ingress
! Попробуем обратиться к приложению через IngressController. Это можно сделать через IP кластера:
$ curl -Lik $(minikube ip)
HTTP/1.1 301 Moved Permanently
Server: nginx/1.13.1
Date: Fri, 14 Jul 2017 16:13:45 GMT
Content-Type: text/html
Content-Length: 185
Connection: keep-alive
Location: https://192.168.99.100/
Strict-Transport-Security: max-age=15724800; includeSubDomains;
HTTP/1.1 403 Forbidden
Server: nginx/1.13.1
Date: Fri, 14 Jul 2017 16:13:45 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Host: 192.168.99.100
X-Powered-By: PHP/7.0.18-0ubuntu0.16.04.1
Strict-Transport-Security: max-age=15724800; includeSubDomains;
You are not allowed to access this file. Check app_dev.php for more information.
В целом можно считать, что развёртывание приложения в Minikube удалось. Из запроса видно, что IngressController перебрасывает на 443-й порт и приложение отвечает, что нужно проверить app_dev.php
. Это уже специфика выбранного приложения (symfony), потому что в файле web/app_dev.php
легко заметить:
// This check prevents access to debug front controllers that are deployed by
// accident to production servers. Feel free to remove this, extend it, or make
// something more sophisticated.
if (isset($_SERVER['HTTP_CLIENT_IP'])
|| isset($_SERVER['HTTP_X_FORWARDED_FOR'])
|| !(in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', 'fe80::1', '::1']) || php_sapi_name() === 'cli-server')
) {
header('HTTP/1.0 403 Forbidden');
exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
}
Чтобы увидеть нормальную страницу приложения, нужно развернуть приложение с другой настройкой либо для тестов закомментировать этот блок. Повторный деплой в Kubernetes (после правок в коде приложения) выглядит так:
$ dapp dimg build
...
Git artifacts: latest patch ... [OK] 1.86 sec
signature: dimgstage-symfony-demo:13a2487a078364c07999d1820d4496763c2143343fb94e0d608ce1a527254dd3
Docker instructions ... [OK] 1.46 sec
signature: dimgstage-symfony-demo:e0226872a5d324e7b695855b427e8b34a2ab6340ded1e06b907b165589a45c3b
instructions:
EXPOSE 8000
$ dapp dimg push --tag-branch :minikube
...
symfony-demo-app-kube_test: digest: sha256:eff826014809d5aed8a82a2c5cfb786a13192ae3c8f565b19bcd08c399e15fc2 size: 2824
pushing image `localhost:5000/symfony-demo:symfony-demo-app-kube_test` [OK] 1.16 sec
localhost:5000/symfony-demo:symfony-demo-app-kube_test [OK] 1.41 sec
$ dapp kube deploy :minikube --image-version kube_test
$ kubectl get all
!!! po/symfony-demo-app-backend-3438105059-tgfsq 1/1 Running 0 1m
Под пересоздался, можно зайти браузером и увидеть красивую картинку:
Итог
С помощью Minikube и Helm можно тестировать свои приложения в кластере Kubernetes, а dapp поможет в сборке, развёртывании своего Registry и самого приложения.
В статье не упомянуты секретные переменные, которые можно использовать в шаблонах для приватных ключей, паролей и прочей закрытой информации. Об этом напишем отдельно.
P.S.
Читайте также в нашем блоге:
- Первая часть статьи: «Практика с dapp. Часть 1: Сборка простых приложений»;
- «Официально представляем dapp — DevOps-утилиту для сопровождения CI/CD»;
- «Собираем Docker-образы для CI/CD быстро и удобно вместе с dapp (обзор и видео с доклада)»;
- «Начало работы в Kubernetes с помощью Minikube» (перевод);
- «Наш опыт с Kubernetes в небольших проектах» (видео доклада, включающего в себя знакомство с техническим устройством Kubernetes).
Автор: diafour