Kubernetes in Yandex Cloud
Прежде чем приступать к изучению Kubernetes в YC необходимо ответить на ряд вопросов.
- Что такое Kubernetes?
- Зачем он используется и в чем профит от его использования?
- Что такое микросервисная архитектура и в чем её отличие от монолита?
- Какие варианты развертывания кластера Kubernetes существуют?
- В чем плюс использования Kubernetes в облаке?
- Какой плюс использования Managed решения по сравнению со своей инсталляцией?
- Плюсы от использования решения mk8s в Yandex Cloud?
- Ради чего стоит изучать тему Kubernetes?
- Подготавливаем инфраструктуру для разворачивания кластера k8s в YC.
Что же такое Kubernetes, или как часто ещё пишут k8s.
Судя по тому что пишет по этому поводу Википедия это оркестратор контейнеров. Посмотрим на это определение чуть подробнее. Что такое оркестратор можно понять по наитию. Оркестратор-это обученный музыкальный профессионал, который назначает инструменты оркестру или другому музыкальному ансамблю из музыкального произведения, написанного композитором, или адаптирует музыку, написанную для другого носителя для оркестра.
Иными словами Kubernetes - это профессиональный инструмент который дирижирует контейнерами и помогает им работать вместе. Делая в конечном итоге нечто болшьее, чем каждый из них мог делать по одиночке или в минимальной кооперации.
Теперь поговорим подробнее про контейнеры. Для этого вкратце взглянем на причины их появления и генезис виртуализации. При всех плюсах виртуализации у неё есть один существенный недостаток - не оптимальное расходование ресурсов. Предположим есть две ВМ и некоторый набор приложений на каждой из них. Но помимо этих приложений на ВМ будут находиться и ОС, которые будут потреблять существенное количество CPU/RAM. Можно было бы переместить приложения на одну ВМ. Но это может быть не всегдя возможно или не безопасно.
В 2013 году компания Docker представила одноимённый продукт для работы с контейнерами. С его помощью можно в пределах одной ВМ и соответственно одной ОС хранить разные приложения несовместимые в обычном случае. Это уменьшило накладные ресурсы на работу нескольких ОС.
Docker->Docker Compose->Kubernetes.
Итак, docker позволил хранить на одной ВМ разные приложения в специальных изолированных областях - контейнерах. Где помимо самого приложения находятся все необходимые для его запуска библиотеки и зависимости. Также появилась возможность быстро создавать приложение не тратя время на его компиляцию/инсталляцию. Это решение пришлось в первую очередь по душе программистам, которым нужно было бы протестировать какую-то фичу. Создал контейнер с приложением, протестировал фичу, убил контейнер.
Но разработчики данного решения решили пойти дальше. А что если не просто упаковывать приложения в контейнеры, но и научить их взаимодействовать между собой? Так появилось решение Docker Compose. И микросервисный подход к разработке ПО. С его помощью можно уже настроить взаимодействие нескольких приложений друг с другом. Но и у этого решения есть недостаток. Все сервисы, запущенные с посощью docker compose, будут работать только на одной ВМ. И если она перезагрузится/выйдет из строя, то и сервис в этом контейнере будет недоступен. Здесь и выходит на сцену k8s, который позвляет размещать приложения на разных ВМ.
Зачем используется k8s и в чем профит от его использования.
- Возможность масштабировать приложение в зависимости от нагрузки;
- Независимость работы приложения от работоспособности ВМ;
- Независимость работы приложения от характеристик ВМ*;
- Автоматическое восстановление работы приложения в случае его сбоя;
- Цена поддержания инфраструктуры резко сокращается.
Существуют и другие оркестраторы помимо k8s. Это nomad, docker swarm. Но их комьюнити не сильно развито. А docker swarm вообще перестал поддерживаться и стал deprecated. Что же касается k8s, то количество контрибуций находится на втором месте в мире после Linux.
В чём отличия микросервисной и монолитной архитектур при создании ПО.
При рассмотрении темы кубера нельзя не упомятнуть про микросервисную архитектуру. Это подход при разработке ПО, когда решение разбивается на самостоятельные куски, которые можно поддерживать вполне себе отдельно от других компонентов.
В чём же плюсы такого подхода:
- Каждая команда может заниматься своим куском кода и апгрейдить его независимо от других команд;
- Это позволяет постоянно совершенствовать продукт;
- Уменьшает время на поиск ошибок в коде;
- Уменьшает время компиляции.
Приведу пример:
Есть решение для интернет коммерции. В качестве веб сервера используется Nginx, для хранения кеша (например, корзины) используется Redis, в качестве постоянного хранилища используется MongoDB и в качестве брокера используется Kafka. И логика, которая это всё будет связывать написана на python+js.
Каждое из этих решений можно конфигурировать/дорабатывать отдельно.
При монолитной архитектуре продукт компилируется целиком. Иными словами при возникновении на этом этапе какой-то ошибки влечёт за собой долгий её поиск. К решениям, использующим монолитную архитектуру можно отнести: 1C Предприятие, Microsoft Office, etc.
Выбор микросервисной архитектуры при проектировании софта диктуется потребностью рынка. Лучше первым выпустить продукт на рынок и далее его допиливать, чем выпустить сразу хорошо, но через несколько лет. С большой долей вероятности рынок в этом случае не получится отыграть.
История про кубер - это история про микросервисную архитектуру.
Варианты развёртывания кластера Kubernetes.
Существуют три варианта развёртывания кластера Kubernetes:
- На голом железе. В этом случае придётся вручную поднимать ОС, ВМ и настраивать кластер своими руками.
- Использовать облако для развёртывания кластера. Но использовать при этом свою собственную инсталляцию.
- Использовать managed решение, предоставляемое облаком.
Плюсы использования Kubernetes в облаке.
- Возможность изменения характеристик воркер нод, входящих в кластер, на лету. То есть без даунтайма.
- Возможность использовать балансировку запросов извне на воркеры.
- Поддержка ВМ и сети специалистами облака. Здесь стоит отметить, что на 100% это не избавит от проблем. Просто сделает этот шанс меньше.
Вообще плюсы от облака - это возможность изменять ресурсы по мере их надобности.
Плюсы использования managed решения.
- Всю работу по обновлению и поддержанию mk8s облако берёт на себя.
- Оказывает поддержку по этому решению.
Сделать какие-то вещи под себя (кастомизировать) не получится.
Плюсы от использования решения mk8s в Yandex Cloud.
В данном случае имеет смысл сравнивать с аналогичными решениями от AWS, GCP, OCI, Azure.
YC выигрывает по следующим параметрам:
Если же сравнивать с российскими аналогами такими как SberCloud, MTS cloud, VK Cloud, Selectel, Timeweb, то решение от YC появилось исторически раньше. И другие вендоры являются догоняющими.
Зачем вам изучать Kubernetes?
Также как и облака, это наиболее динамично развивающийся сегмент IT индустрии. Это интересно и прибыльно. Это возможность пойти работать в технологическую компанию на интересный проект и за немалые деньги.
Практика!
В начале практики подготовим всю необходимую в дальнейшем инфраструктуру.
Подготовка инфраструктуры.
На этом этапе мы создадим необходимые ресурсы в облаке для работы с кластером: SA, Network, Subnet, SG, ключ KMS. Также установим утилиту kubectl для работы с кластером.
export FOLDER=$(yc config get folder-id) export ZONE=ru-central1-d
Установим docker по следующему мануалу: https://docs.docker.com/engine/install/ubuntu/
Создадим SA и сгенерируем для него ключи.
yc iam service-account create \ --name kuber-sa \ --description "Main service account" \ --async
yc iam service-account list | grep kuber-sa
Теперь добавим ему необходимых прав доступа.
Добавим созданному аккаунту необходимых прав доступа.
export SA=$(yc iam service-account get kuber-sa --format json | jq -r '.id') yc resource folder add-access-binding $FOLDER --role editor --subject serviceAccount:$SA
yc resource folder list-access-bindings --id $FOLDER | grep $SA | editor | serviceAccount
Теперь сгенерируем необходимые для доступов ключи.
mkdir sa_key && cd sa_key
yc iam key create --service-account-id $SA --output key.json yc iam access-key create --service-account-id $SA --format=json > stat-key-to-s3.json
ls -l
Создадим Network, Subnet, SG.
yc vpc network create \ --folder-id $FOLDER \ --name institute \ --description "Institute network" \ --async
export NETWORK=$(yc vpc network get institute --format json | jq -r '.id')
yc vpc subnet create \ --folder-id $FOLDER \ --name sub-d \ --description "Subnet zone d" \ --network-id $NETWORK \ --zone $ZONE \ --range 10.130.0.0/24 \ --async
Запомним идентификатор подсети для зоны ru-central1-d:
export SUBNET=$(yc vpc subnet get sub-d --format json | jq -r '.id')
yc vpc sg create --name k8s-institute \ "--rule" "description=access all egress port,direction=egress,from-port=1,to-port=65535,protocol=any,v4-cidrs=[0.0.0.0/0]" \ "--rule" "description=access 22 port,direction=ingress,port=22,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \ "--rule" "description=access 443 port,direction=ingress,port=443,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \ "--rule" "description=access 6443 port,direction=ingress,port=6443,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \ "--rule" "description=balancer port,direction=ingress,from-port=1,to-port=65535,protocol=tcp,v4-cidrs=[198.18.235.0/24],v4-cidrs=[198.18.248.0/24]" \ "--rule" "description=service port,direction=ingress,from-port=30000,to-port=32767,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \ "--rule" "description=ICMP,direction=ingress,protocol=icmp,v4-cidrs=[10.0.0.0/8],v4-cidrs=[192.168.0.0/16]" \ "--rule" "description=PODtoService,direction=ingress,from-port=1,to-port=65535,protocol=any,v4-cidrs=[10.1.0.0/16],v4-cidrs=[10.2.0.0/16]" \ "--rule" "description=Self,direction=ingress,from-port=1,to-port=65535,protocol=any,predefined=self_security_group" \ --network-id $NETWORK --description "k8s access" --folder-id $FOLDER --async
export SG=$(yc vpc sg get k8s-institute --format json | jq -r '.id')
Создадим ключ шифрования KMS.
yc kms symmetric-key create \ --folder-id $FOLDER \ --name k8s-institute \ --labels institute=k8s \ --description "k8s symmetric key" \ --default-algorithm aes-256 \ --deletion-protection \ --rotation-period 24h \ --async
export KMS=$(yc kms symmetric-key get k8s-institute --format json | jq -r ".id")
Установка kubectl.
https://kubernetes.io/ru/docs/tasks/tools/install-kubectl/
Производится на вашем ноутбуке.
На этом подготовительный этап завершился!
Установка кластера Kubernetes.
Создадим кластер k8s с cni Calico.
yc k8s cluster create \ --folder-id $FOLDER \ --name institute-kuber \ --description "k8s institute" \ --network-id $NETWORK \ --zone $ZONE \ --subnet-id $SUBNET \ --public-ip \ --release-channel rapid \ --version 1.28 \ --cluster-ipv4-range 10.1.0.0/16 \ --service-ipv4-range 10.2.0.0/16 \ --auto-upgrade=false \ --security-group-ids $SG \ --enable-network-policy \ --node-service-account-id $SA \ --service-account-id $SA \ --kms-key-id $KMS \ --daily-maintenance-window start=22:00,duration=10h \ --async
Копируем ключ ssh в текстовый файлик. У меня это /Users/cameda/ssh-pairs.txt.
Создание масштабируемой нод группы для кластера.
yc k8s node-group create \ --folder-id $FOLDER \ --name cam-auto-group \ --cluster-name institute-kuber \ --description "autoupgrade group" \ --metadata serial-port-enable=1 \ --metadata-from-file=ssh-keys=/Users/cameda/ssh-pairs.txt \ --location zone=$ZONE \ --platform standard-v3 \ --preemptible \ --memory 8 \ --cores 2 \ --core-fraction 100 \ --disk-type network-ssd \ --disk-size 96 \ --network-acceleration-type standard \ --network-interface security-group-ids=$SG,subnets=$SUBNET,ipv4-address=nat \ --version 1.28 \ --container-runtime containerd \ --auto-scale min=1,max=3,initial=1 \ --auto-upgrade \ --auto-repair \ --max-expansion 2 \ --max-unavailable 1 \ --daily-maintenance-window start=22:00,duration=5h \ --allowed-unsafe-sysctls net.ipv4.tcp_timestamps \ --async
Готово! Теперь можно подключиться к кластеру и немного осмотреться внутри.
Для подключения необходимо зайти в UI и нажать на кнопку Подключиться. Появившуюся строчку копируем в консоль.
Посмотрим что внутри кластера.
kubectl get ns kubectl get po -A kubectl api-resources kubectl get no kubectl get svc -A kubectl get events -A kubectl describe po ip-masq-agent-f48gk -n kube-system
yc k8s cluster get catspqurstbl7rfcuvfj yc k8s cluster list-nodes catspqurstbl7rfcuvfj yc k8s cluster list-operations catspqurstbl7rfcuvfj yc k8s cluster list-node-groups catspqurstbl7rfcuvfj
Создадим deployment с nginx и получим к нему доступ извне.
Создадим отдельный namespace для нашей работы:
kubectl create ns prod
cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: cam-nginx namespace: prod labels: app: nginx-prod environment: prod spec: replicas: 2 selector: matchLabels: app: nginx strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 1 type: RollingUpdate template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:alpine imagePullPolicy: IfNotPresent ports: - containerPort: 80 resources: requests: cpu: 90m memory: 50Mi limits: memory: 100Mi restartPolicy: Always EOF
kubectl get po -n prod kubectl get deploy -n prod
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: namespace: prod name: cam-nginx-service spec: ports: # Порт сетевого балансировщика, на котором будут обслуживаться пользовательские запросы. - port: 80 name: plaintext # Порт контейнера, на котором доступно приложение. targetPort: 80 # Метки селектора, использованные в шаблоне подов при создании объекта Deployment. selector: app: nginx type: LoadBalancer EOF
kubectl get svc -A
Проверяем поле EXTERNAL-IP и там хранится IP адрес по которому мы можем подключиться к сервису.
curl -I http://158.160.45.149/ HTTP/1.1 200 OK Server: nginx/1.23.2 Date: Sun, 11 Dec 2022 16:16:33 GMT Content-Type: text/html Content-Length: 615 Last-Modified: Wed, 19 Oct 2022 07:56:21 GMT Connection: keep-alive ETag: "634fada5-267" Accept-Ranges: bytes
Поскейлим приложение.
yc k8s cluster list-nodes catspqurstbl7rfcuvfj kubectl scale deployment cam-nginx --replicas=20 -n prod
kubectl scale deployment cam-nginx --replicas=5 -n prod yc k8s cluster list-nodes catspqurstbl7rfcuvfj
3 ноды как было, так и осталось. Проблема здесь в том, что вверх нод группа скейлится легко, а вот вниз не очень. И это потому происходит, что на всех нодах кластера создаются в том числе и системные поды. Которые сами по себе не очень помирать любят. Поэтому их надо убить.
Для этого сначала надо ноду закардонить, чтобы более ничего на неё не шедулилось. Если не закардоним, то под опять пересоздастся после того как его удалили.
kubectl get no NAME STATUS ROLES AGE VERSION cl1mhmf0r2lehrd3s0fv-amas Ready <none> 63m v1.21.5 cl1mhmf0r2lehrd3s0fv-orul Ready <none> 120m v1.21.5 cl1mhmf0r2lehrd3s0fv-yzec Ready <none> 63m v1.21.5
kubectl cordon cl1mhmf0r2lehrd3s0fv-yzec
Посмотрим какие поды есть на этой ноде с помощью дескрайба:
kubectl describe no cl1mhmf0r2lehrd3s0fv-yzec
Non-terminated Pods: (5 in total) Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age --------- ---- ------------ ---------- --------------- ------------- --- kube-system calico-node-45wxp 250m (12%) 0 (0%) 0 (0%) 0 (0%) 64m kube-system ip-masq-agent-tpwtr 10m (0%) 0 (0%) 16Mi (1%) 0 (0%) 64m kube-system kube-proxy-d7lm8 100m (5%) 0 (0%) 0 (0%) 0 (0%) 64m kube-system npd-v0.8.0-xsrxf 20m (1%) 200m (10%) 20Mi (1%) 100Mi (7%) 64m kube-system yc-disk-csi-node-v2-sw5ff 30m (1%) 600m (31%) 96Mi (7%) 600Mi (47%) 64m
kubectl delete po calico-node-45wxp -n kube-system kubectl delete po ip-masq-agent-tpwtr -n kube-system kubectl delete po npd-v0.8.0-xsrxf -n kube-system
После этого нода удалилась сама:
kubectl get no NAME STATUS ROLES AGE VERSION cl1mhmf0r2lehrd3s0fv-amas Ready <none> 70m v1.21.5 cl1mhmf0r2lehrd3s0fv-orul Ready <none> 127m v1.21.5
Подробнее про работу кластер автоскейлера можно почитать здесь:
https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler
Подключение к поду.
Посмотрим какие поды у нас сейчас есть:
kubectl get po -n prod NAME READY STATUS RESTARTS AGE cam-nginx-6dc46855d8-84kp7 1/1 Running 0 141m cam-nginx-6dc46855d8-9k64q 1/1 Running 0 124m cam-nginx-6dc46855d8-hpljm 1/1 Running 0 124m cam-nginx-6dc46855d8-vvw4r 1/1 Running 0 124m cam-nginx-6dc46855d8-wn7t7 1/1 Running 0 141m
kubectl delete po cam-nginx-6dc46855d8-vvw4r -n prod
Посмотрим сколько сейчас подов:
kubectl get po -n prod NAME READY STATUS RESTARTS AGE cam-nginx-6dc46855d8-84kp7 1/1 Running 0 143m cam-nginx-6dc46855d8-9k64q 1/1 Running 0 126m cam-nginx-6dc46855d8-hpljm 1/1 Running 0 126m cam-nginx-6dc46855d8-nr2qk 1/1 Running 0 38s cam-nginx-6dc46855d8-wn7t7 1/1 Running 0 143m
Их всё равно 5. Дело в том, что после того как мы убили под он довольно быстро создался заново.
Подключимся к любому из подов:
kubectl exec --tty --stdin -n prod cam-nginx-6dc46855d8-9k64q -- /bin/bash
Внутри пода будет примерно ничего. Не будет таких утилит как ping, ip, ss. Но можно кое-что установить:
apt -y install htop vim htop cd /bin && ls nginx -t nginx -v exit
Marketplace Kubernetes
В Marketplace k8s мы рассмотрим одно решение для примера того как это работает. Это будет NodeLocalDNS. Это daemonset. Его функция - кешировать DNS запросы и экономить таким образом трафик и следовательно деньги. Также это сокращает время последующих запросов и хорошо ускоряет работу кластера.
Заходим в UI облака. Кластер->Marketplace. И выбираем там NodeLocalDNS. В качестве namespace указываем kube-system. После того как приложение перешло в статус Deployed, проверим как оно установилось.
kubectl get po -n kube-system
DaemonSet - это ресурс кластера k8s, который шедулит по одному поду на каждую ноду кластера.
Интеграция с Container Registry
До этого момента мы использовали стандартный образ из Docker Hub. Но если необходимо создать свой сайт, то лучше для этих целей создать свой собственный образ и запустить на его основе под в кубере.
Перед тем как создать свой собственный образ подготовим репозиторий для него. Репозиторий - это хранилище образов.
yc container registry create \ --folder-id $FOLDER \ --name pguti \ --labels pguti=cr \ --async
Посмотреть информацию о реестре можно так:
yc container registry list yc container registry get pguti
export REGISTRY=$(yc container registry get pguti --format=json | jq -r ".id")
Аутентификация с помощью Docker Credential helper.
yc container registry configure-docker
Создадим Dockerfile и отправим его в реджистри.
mkdir -p ~/docker/nginx && cd ~/docker/nginx touch index.html && echo 'Hello, Alex!' > index.html vi nginx.dockerfile
FROM nginx:latest LABEL maintainer="[email protected]" RUN apt update && apt -y install htop mc dstat COPY index.html /usr/share/nginx/html/index.html
docker build . -f nginx.dockerfile -t cr.yandex/$REGISTRY/nginx:a1 docker push cr.yandex/${REGISTRY}/nginx:a1
Создадим под на основе данного имиджа:
kubectl get po kubectl run --attach cam-test --image cr.yandex/${REGISTRY}/nginx:a1 kubectl get po kubectl logs cam-test kubectl describe po cam-test # Здесь нам нужно уточнить адрес пода для проверки доступности сервиса в следующей части.
kubectl exec --tty --stdin cam-test — /bin/bash dstat
Сервисный под.
kubectl run -i -t --rm --image amouat/network-utils test bash
curl 10.97.128.14 Hello, Alex!
Итого: мы создали под на основе подготовленного нами имиджа.
exit