bannerbannerbanner
Облачная экосистема

Евгений Сергеевич Штольц
Облачная экосистема

Для развёртывания будем использовать Kubernetes для противодействия vender lock, когда инфраструктура проекта завязана на API конкретного облачного провайдера и не позволят перейти на другие или собственные облака без существенных изменений в самом приложении. Kubernetes поддерживается Amazon AWS, Google Cloud, Microsoft Azure, локальной установкой одного инстанса с помощью MiniKube.

Воспользуемся Google Cloud, на текущий 2018 год он предоставляет бесплатное использование на один год ограниченных ресурсов (300 долларов США), при этом существуют лимиты, которые можно посмотреть в меню IAM и администрирование –> Квоты. Важно заметить, облачные провайдеры не предоставляют тарифов в современном диапазоне, а предоставляют тарифы на использовании определённых мощностей, то есть сайт мало посещаем – платим мало, сложно обрабатываем много данных – платим много. По этой причине, когда потребности в вычислительных мощностях у компании предсказуемы (не стартап) может быть целесообразно использовать собственные возможности для постоянной нагрузки, что может быть экономически целесообразно, не рискую ограниченностью вычислительными мощностями.

И так заходим на cloud.google.com регистрируется, привязываем дебетовую карту с минимальным балансом и переходим в консоль console.cloud.google.com, в котором можно пройти обучение по интерфейсу для общего ознакомления. В меню нажимаем пункт Оплата: у меня нетронутые демо-деньги 300 долларов США и осталось 356 дней (средства списываются не в режиме реального времени).

Если смотреть на как основу для Back-End для мобильной разработки (MBasS, Mobile backend as a service), то его предоставляют разные провайдеры: Google Firebase, AWS Mobile, Azure Mobile

Google App Engine

Создание кластера через WEB-интерфейс

Предварительно проверим ограничения (квоты) Меню –> Продукты –> IAM и администрирование –> Квоты, а если вы находитесь на тестовом аккаунте, то Static IP addresses будет равен 1, то не сможет создаться балансировщик и придётся удалять кластер. Создадим кластер в Меню – Ресурсы – Kubernetes Engine в тремя репликами микромашины и последней версией Kubernetes. В левом нижнем углу в пункте Marketplace создадим 2 инстанса NGINX. После создания кластера кликнем по вкладке Сервисы и перейдём по IP-адресу.

Marketplace: Сеть, Бесплатные, Приложения Kubernetes: NGINX Создадим кастомный кластер standard-cluster- NGINX, выбрав минимум CPU и RAM, 2 ноды вместо 3 и последнюю вер сию Kubernetes (я выбрал 1.11.3, а мой код будет совместим с – не ниже 1.10). В Меню – Ресурсы – Kubernetes Engine во кладке Кластера нажмём кнопку Подключиться. Управлением кластером в командной строке осуществляется с помощью команды cubectl, о ней можно почитать в документации: https://kubernetes.io/docs/reference/kubectl/overview/ и список по https://gist.github.com/ipedrazas/95391ffd88190bea94ca188d3d2c1cbe.

Создание виртуальной машины:

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

NAME_PROJECT=bitrix_12345;

NAME_CLUSTER=bitrix;

gcloud projects create $NAME_CLUSTER –name $NAME_CLUSTER;

gcloud config set project $NAME_CLUSTER;

gcloud projects list;

Несколько тонкостей: ключ –zone обязателен и ставится в конце, диска не должен быть меньше 10Gb, а тип машин можно взять из https://cloud.google.com/compute/docs/machine-types. Если реплика у нас одна, то по умолчанию создаётся минимальная конфигурация для тестирования:

gcloud container clusters create $NAME_CLUSTER –zone europe-north1-a

Вы можете увидеть в админке, развернув выпадающий список в шапке, и открыв вкладку Все проекты.

gcloud projects delete NAME_PROJECT;

, если больше – стандартная, параметры которой мы подредактируем:

$ gcloud container clusters create mycluster \

–-machine-type=n1-standard-1 –disk-size=10GB –image-type ubuntu \

–-scopes compute-rw,gke-default \

–-machine-type=custom-1-1024 \

–-cluster-version=1.11 –enable-autoupgrade \

–-num-nodes=1 –enable-autoscaling –min-nodes=1 –max-nodes=2 \

–-zone europe-north1-a

Ключ –enable-autorepair запускаем работу мониторинга доступности ноды и в случае её падения – она будет пересоздана. Ключ требует версию Kubernetes не менее 1.11, а на момент написания книги версия по умолчанию 1.10 и поэтому нужно её задать ключом, например, –cluster-version=1.11.4-gke.12. Но можно зафиксировать только мажорную версия –cluster-version=1.11 и установить автообновление версии –enable-autoupgrade. Также зададим авто уверение количества нод, если ресурсов не хватает: –num-nodes=1 –min-nodes=1 –max-nodes=2 –enable-autoscaling.

Теперь поговорим об виртуальный ядрах и оперативной памяти. По умолчанию поднимается машина n1-standart-1, имеющая одно виртуальное ядро и 3.5Gb оперативной памяти, в трёх экземплярах, что совокупно даёт три виртуальных ядра и 10.5Gb оперативной памяти. Важно, чтобы в кластере было всего не менее двух виртуальных ядер процессора, иначе их, формально по лимитам на системные контейнера Kubernetes, не хватит для полноценной работы (могут не подняться контейнера, например, системные). Я возьму две ноды по одному ядру и общее количество ядер будет два. Такая же ситуация и с оперативной памятью, для поднятия контейнера с NGINX мне вполне хватало 1Gb (1024Mb) оперативной памяти, а вот для поднятия контейнера с LAMP (Apache MySQL PHP) уже нет, у меня не поднялся системный сервис kube-dns-548976df6c-mlljx, который отвечает за DNS в поде. Не смотря не то, что он не является жизненно важным и нам не пригодится, в следующий раз может не подняться уж более важный вместо него. При этом важно заметить, что у меня нормально поднимался кластер с 1Gb и было всё нормально, я общий объём в 2Gb оказался пограничным значением. Я задал 1080Mb (1.25Gb), учтя, что минимальная планка оперативной памяти составляет 256Mb (0.25Gb) и мой объём должен быть кратен ей и быть не меньше, для одно ядра, 1Gb. В результате в кластера 2 ядра и 2.5Gb вместо 3 ядре и 10.5Gb, что является существенной оптимизацией ресурсов и цены на платном аккаунте.

Теперь нам нужно подключиться к серверу. Ключ у нас уже есть на сервере ${HOME}/.kube/config и теперь нам нужно просто авторизоваться:

$ gcloud container clusters get-credentials b –zone europe-north1-a –project essch

$ kubectl port-forward Nginxlamp-74c8b5b7f-d2rsg 8080:8080

Forwarding from 127.0.0.1:8080 –> 8080

Forwarding from [::1]:8080 –> 8080

$ google-chrome http://localhost:8080 # это не будет работать в Google Shell

$ kubectl expose Deployment Nginxlamp –type="LoadBalancer" –port=8080

Для локального использования kubectl Вам нужно установить gcloud и с помощью него установить kubectl используя команду gcloud components install kubectl, но пока не будем усложнять первые шаги.

В разделе Сервисы админки будет доступен POD не только через сервис балансировщик front-end, но и через внутренний балансировщик Deployment. Хоть и после пересоздания и сохранится, но конфиг более поддерживаем и очевиден.

Также есть возможность дать возможность регулировать количество нод в автоматическом режиме в зависимости от нагрузки, например, количества контейнеров с установленными требованиями к ресурсам, с помощью ключей –enable-autoscaling –min-nodes=1 –max-nodes=2.

Простой кластер в GCP

Для создания кластера можно пойти двумя путями: через графический интерфейс Google Cloud Platform или через его API командой gcloud. Посмотрим, как это можно сделать через UI. Рядом с меню кликнем на выпадающей список и создадим отдельный проект. В разделе Kubernetes Engine выбираем создать кластер. Дадим название, 2CPU, зону europe-north-1 (дата-центр в Финляндии ближе всего к СПб) и последнюю версию Kubernetes. После создания кластера кликаем на подключиться и выбираем Cloud Shell. Для создания через API по кнопке в правом верхнем углу выведем консольную панель и введём в ней:

gcloud container clusters create mycluster –zone europe-north1-a

Через некоторое время, у меня это заняло две с половиной минуты, будут подняты 3 виртуальные машины, на них установлена операционная система и примонтирован диск. Проверим:

esschtolts@cloudshell:~ (essch)$ gcloud container clusters list –filter=name=mycluster

NAME LOCATION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS

mycluster europe-north1-a 35.228.37.100 n1-standard-1 1.10.9-gke.5 3 RUNNING

esschtolts@cloudshell:~ (essch)$ gcloud compute instances list

NAME MACHINE_TYPE EXTERNAL_IP STATUS

gke-mycluster-default-pool-43710ef9-0168 n1-standard-1 35.228.73.217 RUNNING

gke-mycluster-default-pool-43710ef9-39ck n1-standard-1 35.228.75.47 RUNNING

gke-mycluster-default-pool-43710ef9-g76k n1-standard-1 35.228.117.209 RUNNING

Подключимся к виртуальной машине:

esschtolts@cloudshell:~ (essch)$ gcloud projects list

PROJECT_ID NAME PROJECT_NUMBER

agile-aleph-203917 My First Project 546748042692

essch app 283762935665

esschtolts@cloudshell:~ (essch)$ gcloud container clusters get-credentials mycluster \

–-zone europe-north1-a \

–-project essch

Fetching cluster endpoint and auth data.

kubeconfig entry generated for mycluster.

У нас пока нет кластера:

esschtolts@cloudshell:~ (essch)$ kubectl get pods

No resources found.

Создадим кластер:

esschtolts@cloudshell:~ (essch)$ kubectl run Nginx –image=Nginx –replicas=3

deployment.apps "Nginx" created

Проверим его состав:

esschtolts@cloudshell:~ (essch)$ kubectl get deployments –selector=run=Nginx

NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE

Nginx 3 3 3 3 14s

esschtolts@cloudshell:~ (essch)$ kubectl get pods –selector=run=Nginx

NAME READY STATUS RESTARTS AGE

Nginx-65899c769f-9whdx 1/1 Running 0 43s

 

Nginx-65899c769f-szwtd 1/1 Running 0 43s

Nginx-65899c769f-zs6g5 1/1 Running 0 43s

Удостоверимся, что все три реплики кластера распределились равномерно на все три ноды:

esschtolts@cloudshell:~ (essch)$ kubectl describe pod Nginx-65899c769f-9whdx | grep Node:

Node: gke-mycluster-default-pool-43710ef9-g76k/10.166.0.5

esschtolts@cloudshell:~ (essch)$ kubectl describe pod Nginx-65899c769f-szwtd | grep Node:

Node: gke-mycluster-default-pool-43710ef9-39ck/10.166.0.4

esschtolts@cloudshell:~ (essch)$ kubectl describe pod Nginx-65899c769f-zs6g5 | grep Node:

Node: gke-mycluster-default-pool-43710ef9-g76k/10.166.0.5

Теперь поставим балансировщик нагрузки:

esschtolts@cloudshell:~ (essch)$ kubectl expose Deployment Nginx –type="LoadBalancer" –port=80

service "Nginx" exposed

Проверим, что он создался:

esschtolts@cloudshell:~ (essch)$ kubectl expose Deployment Nginx –type="LoadBalancer" –port=80

service "Nginx" exposed

esschtolts@cloudshell:~ (essch)$ kubectl get svc –selector=run=Nginx

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

Nginx LoadBalancer 10.27.245.187 pending> 80:31621/TCP 11s

esschtolts@cloudshell:~ (essch)$ sleep 60;

esschtolts@cloudshell:~ (essch)$ kubectl get svc –selector=run=Nginx

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

Nginx LoadBalancer 10.27.245.187 35.228.212.163 80:31621/TCP 1m

Проверим его работу:

esschtolts@cloudshell:~ (essch)$ curl 35.228.212.163:80 2>\dev\null | grep h1

< h1>Welcome to Nginx!< /h1>

Чтобы каждый раз не копировать полные названия – сохраним их в переменных (подробнее о формате JSONpath в документации Go: https://golang.org/pkg/text/template/#pkg-overview):

esschtolts@cloudshell:~ (essch)$ pod1=$(kubectl get pods -o jsonpath={.items[0].metadata.name});

esschtolts@cloudshell:~ (essch)$ pod2=$(kubectl get pods -o jsonpath={.items[1].metadata.name});

esschtolts@cloudshell:~ (essch)$ pod3=$(kubectl get pods -o jsonpath={.items[2].metadata.name});

esschtolts@cloudshell:~ (essch)$ echo $pod1 $pod2 $pod3

Nginx-65899c769f-9whdx Nginx-65899c769f-szwtd Nginx-65899c769f-zs6g5

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

esschtolts@cloudshell:~ (essch)$ echo 1 > test.html;

esschtolts@cloudshell:~ (essch)$ kubectl cp test.html ${pod1}:/usr/share/Nginx/html/index.html

esschtolts@cloudshell:~ (essch)$ echo 2 > test.html;

esschtolts@cloudshell:~ (essch)$ kubectl cp test.html ${pod2}:/usr/share/Nginx/html/index.html

esschtolts@cloudshell:~ (essch)$ echo 3 > test.html;

esschtolts@cloudshell:~ (essch)$ kubectl cp test.html ${pod3}:/usr/share/Nginx/html/index.html

esschtolts@cloudshell:~ (essch)$ curl 35.228.212.163:80 && curl 35.228.212.163:80 && curl 35.228.212.163:80

3

2

1

esschtolts@cloudshell:~ (essch)$ curl 35.228.212.163:80 && curl 35.228.212.163:80 && curl 35.228.212.163:80

3

1

1

Проверим отказоустойчивость кластера удалением одного POD:

esschtolts@cloudshell:~ (essch)$ kubectl delete pod ${pod1} && kubectl get pods && sleep 10 && kubectl get pods

pod "Nginx-65899c769f-9whdx" deleted

NAME READY STATUS RESTARTS AGE

Nginx-65899c769f-42rd5 0/1 ContainerCreating 0 1s

Nginx-65899c769f-9whdx 0/1 Terminating 0 54m

Nginx-65899c769f-szwtd 1/1 Running 0 54m

Nginx-65899c769f-zs6g5 1/1 Running 0 54m

NAME READY STATUS RESTARTS AGE

Nginx-65899c769f-42rd5 1/1 Running 0 12s

Nginx-65899c769f-szwtd 1/1 Running 0 55m

Nginx-65899c769f-zs6g5 1/1 Running 0 55m

Как мы видим, сразу после того, как POD стал недоступен (начался процесс его удаления) начала создаваться его замена. Вскоре, кластер полностью восстановит свою структуру. После того как мы закончили наши эксперименты, удалим виртуальные машины с кластером:

esschtolts@cloudshell:~ (essch)$ gcloud container clusters delete mycluster –zone europe-north1-a;

The following clusters will be deleted.

– [mycluster] in [europe-north1-a]

Do you want to continue (Y/n)? Y

Deleting cluster mycluster…done.

Deleted [https://container.googleapis.com/v1/projects/essch/zones/europe-north1-a/clusters/mycluster].

esschtolts@cloudshell:~ (essch)$ gcloud container clusters list –filter=name=mycluster

Итого. Мы создали кластер и создали балансировщик нагрузки всего двумя командами run и expose, теперь мы может заходить по IP-адресу балансировщика и наблюдать в браузере приветствующую страницу NGINX. При этом кластер само восстанавливается, для этого мы эмулировали отказ пода его удалением – он был создан снова.

Воспроизводимость создания кластера

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

esschtolts@cloudshell:~ (essch)$ kubectl get deployment/Nginx –output=yaml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

annotations:

deployment.kubernetes.io/revision: "1"

creationTimestamp: 2018-12-16T10:23:26Z

generation: 1

labels:

run: Nginx

name: Nginx

namespace: default

resourceVersion: "1612985"

selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/Nginx

uid: 9fb3ad6a-011c-11e9-bfaa-42010aa60088

spec:

progressDeadlineSeconds: 600

replicas: 1

revisionHistoryLimit: 10

selector:

matchLabels:

run: Nginx

strategy:

rollingUpdate:

maxSurge: 1

maxUnavailable: 1

type: RollingUpdate

template:

metadata:

creationTimestamp: null

labels:

run: Nginx

spec:

containers:

– image: Nginx

imagePullPolicy: Always

name: Nginx

resources: {}

terminationMessagePath: /dev/termination-log

terminationMessagePolicy: File

dnsPolicy: ClusterFirst

restartPolicy: Always

schedulerName: default-scheduler

securityContext: {}

terminationGracePeriodSeconds: 30

status:

availableReplicas: 1

conditions:

– lastTransitionTime: 2018-12-16T10:23:26Z

lastUpdateTime: 2018-12-16T10:23:26Z

message: Deployment has minimum availability.

reason: MinimumReplicasAvailable

status: "True"

type: Available

– lastTransitionTime: 2018-12-16T10:23:26Z

lastUpdateTime: 2018-12-16T10:23:28Z

message: ReplicaSet "Nginx-64f497f8fd" has successfully progressed.

reason: NewReplicaSetAvailable

status: "True"

type: Progressing

observedGeneration: 1

readyReplicas: 1

replicas: 1

updatedReplicas: 1

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

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

labels:

run: Nginx

name: Nginx

spec:

selector:

matchLabels:

run: Nginx

template:

metadata:

labels:

run: Nginx

spec:

containers:

– image: Nginx

name: Nginx

Также можно создать шаблон:

gcloud services enable compute.googleapis.com –project=${PROJECT}

gcloud beta compute instance-templates create-with-container ${TEMPLATE} \

–-machine-type=custom-1-4096 \

–-image-family=cos-stable \

–-image-project=cos-cloud \

–-container-image=gcr.io/kuar-demo/kuard-amd64:1 \

–-container-restart-policy=always \

–-preemptible \

–-region=${REGION} \

–-project=${PROJECT}

gcloud compute instance-groups managed create ${TEMPLATE} \

–-base-instance-name=${TEMPLATE} \

–-template=${TEMPLATE} \

–-size=${CLONES} \

–-region=${REGION} \

–-project=${PROJECT}

Высокая доступность сервиса

Чтобы обеспечить высокую доступность нужно в случае падения приложения перенаправлять трафик на запасной. Также, часто важно, чтобы нагрузка была распределена равномерно, так как приложение в единичном экземпляре не способно обрабатывать весь трафик. Для этого создаётся кластер, для примера возьмём более сложный образ, чтобы разобрать большее количество нюансов:

esschtolts@cloudshell:~/bitrix (essch)$ cat deploymnet.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

name: Nginxlamp

spec:

selector:

matchLabels:

app: lamp

replicas: 1

template:

metadata:

labels:

app: lamp

spec:

containers:

– name: lamp

image: mattrayner/lamp:latest-1604-php5

ports:

– containerPort: 80

esschtolts@cloudshell:~/bitrix (essch)$ cat loadbalancer.yaml

apiVersion: v1

kind: Service

metadata:

name: frontend

spec:

type: LoadBalancer

ports:

– name: front

port: 80

targetPort: 80

selector:

app: lamp

esschtolts@cloudshell:~/bitrix (essch)$ kubectl get pods

NAME READY STATUS RESTARTS AGE

Nginxlamp-7fb6fdd47b-jttl8 2/2 Running 0 3m

esschtolts@cloudshell:~/bitrix (essch)$ kubectl get svc

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

frontend LoadBalancer 10.55.242.137 35.228.73.217 80:32701/TCP,8080:32568/TCP 4m

kubernetes ClusterIP 10.55.240.1 none> 443/TCP 48m

Теперь мы можем создать идентичные копии наших кластеров, например, для Production и Develop, но балансировка не будет работать должным образом. Балансировщик будет находить POD по метке, а этой метке соответствуют и POD в production, и в Developer кластере. Также не станет препятствием размещение кластеров в разных проектах. Хотя, для многих задач, это большой плюс, но не в случае кластера для разработчиков и продакшне. Для разграничения области видимости используются namespace. Мы незаметно их используем, когда мы выводим список POD без указания области видимости нам выводится область видимости по умолчанию default, но не выводятся POD из системной области видимости:

esschtolts@cloudshell:~/bitrix (essch)$ kubectl get namespace

NAME STATUS AGE

default Active 5h

kube-public Active 5h

kube-system Active

esschtolts@cloudshell:~/bitrix (essch)$ kubectl get pods –namespace=kube-system

NAME READY STATUS RESTARTS AGE

event-exporter-v0.2.3-85644fcdf-tdt7h 2/2 Running 0 5h

fluentd-gcp-scaler-697b966945-bkqrm 1/1 Running 0 5h

fluentd-gcp-v3.1.0-xgtw9 2/2 Running 0 5h

heapster-v1.6.0-beta.1-5649d6ddc6-p549d 3/3 Running 0 5h

kube-dns-548976df6c-8lvp6 4/4 Running 0 5h

kube-dns-548976df6c-mcctq 4/4 Running 0 5h

kube-dns-autoscaler-67c97c87fb-zzl9w 1/1 Running 0 5h

kube-proxy-gke-bitrix-default-pool-38fa77e9-0wdx 1/1 Running 0 5h

kube-proxy-gke-bitrix-default-pool-38fa77e9-wvrf 1/1 Running 0 5h

l7-default-backend-5bc54cfb57-6qk4l 1/1 Running 0 5h

metrics-server-v0.2.1-fd596d746-g452c 2/2 Running 0 5h

esschtolts@cloudshell:~/bitrix (essch)$ kubectl get pods –namespace=default

NAMEREADY STATUS RESTARTS AGE

Nginxlamp-b5dcb7546-g8j5r 1/1 Running 0 4h

Создадим область видимости:

esschtolts@cloudshell:~/bitrix (essch)$ cat namespace.yaml

apiVersion: v1

kind: Namespace

metadata:

name: development

labels:

name: development

esschtolts@cloudshell:~ (essch)$ kubectl create -f namespace.yaml

namespace "development" created

esschtolts@cloudshell:~/bitrix (essch)$ kubectl get namespace –show-labels

NAME STATUS AGE LABELS

default Active 5h none>

development Active 16m name=development

kube-public Active 5h none>

kube-system Active 5h none>

Суть работы с областью видимости в том, что для конкретных кластеров мы задаём область видимость и можем выполнять команды указывая её, при этом они будут распространяться только на них. При этом, кроме ключей в командах, таких как kubectl get pods области видимости не фигурирую, поэтому конфигурационные файлы контроллеров (Deployment, DaemonSet и других) и сервисов (LoadBalancer, NodePort и других) не фигурируют, позволяя беспроблемно переносить их между областью видимости, что особенно актуально для pipeline разработки: сервер разработчика, тестовый сервер и продакшн сервер. Области видимости прописываются в файле контекстов кластеров $HOME/ .kube/config с помощью команды kubectl config view. Так, у меня в записи контекста нашего кластера не фигурирует запись об области видимости (по умолчанию default ):

 

– context:

cluster: gke_essch_europe-north1-a_bitrix

user: gke_essch_europe-north1-a_bitrix

name: gke_essch_europe-north1-a_bitrix

Посмотреть можно примерно подобным образом:

esschtolts@cloudshell:~/bitrix (essch)$ kubectl config view -o jsonpath='{.contexts[4]}'

{gke_essch_europe-north1-a_bitrix {gke_essch_europe-north1-a_bitrix gke_essch_europe-north1-a_bitrix []}}

Создадим новый контекст для данного пользователя и кластера:

esschtolts@cloudshell:~ (essch)$ kubectl config set-context dev \

> –namespace=development \

> –cluster=gke_essch_europe-north1-a_bitrix \

> –user=gke_essch_europe-north1-a_bitrix

Context "dev" modified.

esschtolts@cloudshell:~/bitrix (essch)$ kubectl config set-context dev \

> –namespace=development \

> –cluster=gke_essch_europe-north1-a_bitrix \

> –user=gke_essch_europe-north1-a_bitrix

Context "dev" modified.

В результате был добавлен:

– context:

cluster: gke_essch_europe-north1-a_bitrix

namespace: development

user: gke_essch_europe-north1-a_bitrix

name: dev

Теперь осталось переключиться на него:

esschtolts@cloudshell:~ (essch)$ kubectl config use-context dev

Switched to context "dev".

esschtolts@cloudshell:~ (essch)$ kubectl config current-context

dev

esschtolts@cloudshell:~ (essch)$ kubectl get pods

No resources found.

esschtolts@cloudshell:~ (essch)$ kubectl get pods –namespace=default

NAMEREADY STATUS RESTARTS AGE

Nginxlamp-b5dcb7546-krkm2 1/1 Running 0 10h

Можно было добавить в существующий контекст пространство имён:

esschtolts@cloudshell:~/bitrix (essch)$ kubectl config set-context $(kubectl config current-context) –namespace=development

Context "gke_essch_europe-north1-a_bitrix" modified.

Теперь создадим кластер в новой области видимости dev(она теперь по умолчанию и её можно не указывать –namespace=dev) и удалим из области видимости по умолчанию default (она теперь не по умолчанию для нашего кластера и её нужно указывать –namespace =default):

esschtolts@cloudshell:~ (essch)$ cd bitrix/

esschtolts@cloudshell:~/bitrix (essch)$ kubectl create -f deploymnet.yaml -f loadbalancer.yaml

deployment.apps "Nginxlamp" created

service "frontend" created

esschtolts@cloudshell:~/bitrix (essch)$ kubectl delete -f deploymnet.yaml -f loadbalancer.yaml –namespace=default

deployment.apps "Nginxlamp" deleted

service "frontend" deleted

esschtolts@cloudshell:~/bitrix (essch)$ kubectl get pods

NAMEREADY STATUS RESTARTS AGE

Nginxlamp-b5dcb7546-8sl2f 1/1 Running 0 1m

Теперь посмотрим внешний IP-адресс и откроем страницу:

esschtolts@cloudshell:~/bitrix (essch)$ curl $(kubectl get -f loadbalancer.yaml -o json

| jq -r .status.loadBalancer.ingress[0].ip) 2>/dev/null | grep '< h2 >'

< h2>Welcome to github.com/mattrayner/docker-lamp" target="_blank">Docker-Lamp a.k.a mattrayner/lamp< /h2>

Кастомизация

Теперь нам нужно изменить стандартное решение под наши нужды, а именно добавить конфиги и наше приложение. Для простоты мы пометим (изменим стандартный) в корень нашего приложения файл .htaccess, сведя к простому помещения нашего приложения в папку /app. Первое, что напрашивается сделать, это создать POD и потом скопировать с хоста в контейнер наше приложение (я взял Bitrix):

Хотя это решение и работает, оно имеет ряд существенных недостатков. Первое, что то, что нам нужно дожидаться из вне, постоянным опросом POD, когда он поднимет контейнер и мы в него скопируем приложение и не должен этого делать, если контейнер не поднялся, а также обрабатывать ситуацию когда он сломает наш POD, внешние сервисы, могут опираться на статус POD, хоты сам POD будет ещё не готов, пока не будет выполнен скрипт. Вторым недостатком является то, что у нас появляется какой то внешний скрипт, который нужно логически не отделим от POD, но при этом его нужно вручную запускать из вне, где хранить и где-то должна быть инструкция по его использованию. И напоследок, этих POD у нас может быть множество. На первый взгляд, логичным решением поместить код в Dockerfile:

esschtolts@cloudshell:~/bitrix (essch)$ cat Dockerfile

FROM mattrayner/lamp:latest-1604-php5

MAINTAINER ESSch ESSchtolts@yandex.ru>

RUN cd /app/ && ( \

wget https://www.1c-bitrix.ru/download/small_business_encode.tar.gz \

&& tar -xf small_business_encode.tar.gz \

&& sed -i '5i php_value short_open_tag 1' .htaccess \

&& chmod -R 0777 . \

&& sed -i 's/#php_value display_errors 1/php_value display_errors 1/' .htaccess \

&& sed -i '5i php_value opcache.revalidate_freq 0' .htaccess \

&& sed -i 's/#php_flag default_charset UTF-8/php_flag default_charset UTF-8/' .htaccess \

) && cd ..;

EXPOSE 80 3306

CMD ["/run.sh"]

esschtolts@cloudshell:~/bitrix (essch)$ docker build -t essch/app:0.12 . | grep Successfully

Successfully built f76e656dac53

Successfully tagged essch/app:0.12

esschtolts@cloudshell:~/bitrix (essch)$ docker image push essch/app | grep digest

0.12: digest: sha256:75c92396afacefdd5a3fb2024634a4c06e584e2a1674a866fa72f8430b19ff69 size: 11309

esschtolts@cloudshell:~/bitrix (essch)$ cat deploymnet.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

name: Nginxlamp

namespace: development

spec:

selector:

matchLabels:

app: lamp

replicas: 1

template:

metadata:

labels:

app: lamp

spec:

containers:

– name: lamp

image: essch/app:0.12

ports:

– containerPort: 80

esschtolts@cloudshell:~/bitrix (essch)$ IMAGE=essch/app:0.12 kubectl create -f deploymnet.yaml

deployment.apps "Nginxlamp" created

esschtolts@cloudshell:~/bitrix (essch)$ kubectl get pods -l app=lamp

NAME READY STATUS RESTARTS AGE

Nginxlamp-55f8cd8dbc-mk9nk 1/1 Running 0 5m

esschtolts@cloudshell:~/bitrix (essch)$ kubectl exec Nginxlamp-55f8cd8dbc-mk9nk – ls /app/

index.php

Это происходит потому, что разработчик образов, что правильно и написано в его документации, ожидал, что образ будет примонтирован к хосту и в скрипте запускаемого в сам конце удаляется папка app. Также в таком подходе мы столкнёмся с проблемой постоянных обновлений образов, конфигом (мы не сможем задать номер образа переменной, так как он будет исполняться на нодах кластера) и обновлений контейнеров, также мы не сможем обновлять папку, так как при пересоздании контейнера изменения будут возвращены в изначальное состояние.

Правильным решением будет примонтировать папку и включение в состав жизненно цикла POD запуск контейнера, который стартует перед основным контейнером и производит подготовительные операции окружения, часто это скачивание приложения с репозитория, сборка, прогон тестов, создания пользователей и выставление прав. Под каждую операцию правильно запускать отдельный init контейнер, в котором эта операция является базовым процессом, которые выполняются последовательно – цепочкой, которая будет разорвана, если одна из операций будет выполнена с ошибкой (вернёт не нулевой код завершения процесса). Для такого контейнера предусмотрено отдельное описание в POD – InitContainer, перечисляя их последовательно они будет выстраивать цепочку запусков init контейнеров в том же порядке. В нашем случае мы создали неименованный volume и с помощью InitContainer доставили в него установочные файлы. После успешного завершения InitContainer, которых может быть несколько, стартует основной. Основной контейнер уже монтируется в наш том, в котором уже есть установочные файлы, нам остаётся лишь перейти в браузер и выполнить установку:

esschtolts@cloudshell:~/bitrix (essch)$ cat deploymnet.yaml

kind: Deployment

metadata:

name: Nginxlamp

namespace: development

spec:

selector:

matchLabels:

app: lamp

replicas: 1

template:

metadata:

labels:

app: lamp

spec:

initContainers:

– name: init

image: ubuntu

command:

– /bin/bash

– -c

– |

cd /app

apt-get update && apt-get install -y wget

wget https://www.1c-bitrix.ru/download/small_business_encode.tar.gz

tar -xf small_business_encode.tar.gz

sed -i '5i php_value short_open_tag 1' .htaccess

chmod -R 0777 .

sed -i 's/#php_value display_errors 1/php_value display_errors 1/' .htaccess

sed -i '5i php_value opcache.revalidate_freq 0' .htaccess

sed -i 's/#php_flag default_charset UTF-8/php_flag default_charset UTF-8/' .htaccess

volumeMounts:

– name: app

mountPath: /app

containers:

– name: lamp

image: essch/app:0.12

ports:

– containerPort: 80

volumeMounts:

– name: app

mountPath: /app

volumes:

– name: app

emptyDir: {}

Посмотреть события во время создания POD можно командой watch kubectl get events, а логи kubectl logs {ID_CONTAINER} -c init или более универсально:

kubectl logs $(kubectl get PODs -l app=lamp -o JSON | jq ".items[0].metadata.name" | sed 's/"//g') -c init

Целесообразно выбирать для единичных задач маленькие образа, например, alpine:3.5:

esschtolts@cloudshell:~ (essch)$ docker pull alpine 1>\dev\null

esschtolts@cloudshell:~ (essch)$ docker pull ubuntu 1>\dev\null

esschtolts@cloudshell:~ (essch)$ docker images

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35 
Рейтинг@Mail.ru