Кластер в облаке
Презентация
Ссылка на презентацию
Последняя редакция: 06.10.26
Теория
От minikube к настоящему кластеру
В 02-K8s-intro уже говорилось, что для курса используется локальный
кластер - minikube
У локального кластера есть жёсткие рамки: один узел (сама ваша машина), ресурсы ограничены
машиной, кластер не переживёт переустановку системы, и до него в принципе нельзя достучаться
с чужого компьютера
Управляемый кластер в облаке (Managed Service for Kubernetes в Yandex Cloud) снимает эти
ограничения - несколько настоящих узлов, LoadBalancer выдаёт настоящий внешний IP, диски
реально сетевые (см. StorageClass в 04-DB-deploy), кластер живёт
независимо от вашей машины
Взамен появляются две вещи, которых не было у minikube - деньги (управление кластером и узлы
в облаке платные) и IAM (без прав ничего создать и подключиться нельзя)
Что берёт на себя облако
В 03-K8s-basics кластер делился на Control Plane и Node
В managed-кластере Control Plane (мастера) полностью на стороне облака - вы не видите и не
администрируете эти машины напрямую, только настраиваете кластер через API/консоль/CLI
Сами вы управляете только node group (группой узлов) - это ваши обычные виртуальные машины,
на которых kubelet реально запускает поды, как и на локальном узле minikube
Устанавливаем yc
Все команды ниже - через официальный CLI Yandex Cloud, yc
На macOS (так же на Linux) он ставится одной командой
curl -sSL https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash
Скрипт сам определяет систему и прописывает yc в PATH - после установки нужно перезапустить
терминал (или обновить PATH в текущей сессии)
Дальше - разовая инициализация профиля
yc init
Команда открывает браузер для входа, а затем спрашивает, какое облако (cloud) и какой каталог
(folder) сделать выбранными по умолчанию, и в какой зоне доступности (ru-central1-a/b/d)
по умолчанию создавать ресурсы Compute Cloud - именно каталог, выбранный на этом шаге, дальше
нужен для ролей сервисных аккаунтов (ниже)
Проверить результат настройки можно командой yc config list
Что нужно, прежде чем создавать кластер
- Сеть (VPC) и подсеть в конкретной зоне доступности - мастера и узлы должны куда-то подключиться
- Группа безопасности - облачный firewall, правила, какой трафик разрешён к мастерам и узлам
- Сервисные аккаунты - см. ниже, отдельная и самая непривычная часть по сравнению с minikube
Сервисные аккаунты и роли: зачем они кластеру
На локальном minikube кластер работает от вашего же пользователя и может всё, что можете вы
на своей машине
В облаке кластер - это не вы, а отдельная сущность, которая сама создаёт диски, балансировщики
и виртуальные машины по вашему запросу, поэтому ей нужны собственные права - сервисный
аккаунт
Для managed-кластера в Yandex Cloud нужно как минимум два сервисных аккаун та
- Аккаунт ресурсов кластера - от его имени кластер создаёт и меняет узлы, сеть, диски,
балансировщики
Минимально нужна рольk8s.clusters.agent, для публичного IP дополнительноvpc.publicAdmin - Аккаунт узлов - от его имени узлы тянут образы контейнеров
Нужна рольcontainer-registry.images.puller(илиcloud-registry.artifacts.pullerдля Cloud Registry)
Это не то же самое, что роли для вас как пользователя, управляющего кластером - k8s.viewer
/ k8s.editor / k8s.admin дают доступ к самому ресурсу "кластер" в облаке (создать, посмотреть,
удалить), а k8s.cluster-api.viewer / k8s.cluster-api.editor / k8s.cluster-api.admin -
отдельная группа ролей, которая связывает IAM с RBAC уже внутри самого Kubernetes API
(подробнее про RBAC - в статье про безопасность в K8s)
Сеть, группа безопасности и сервисные аккаунты: команды
Сервисные аккаунты и роли для них - те же, что описаны выше:
yc iam service-account create --name k8s-resources-sa
yc iam service-account create --name k8s-nodes-sa
FOLDER_ID=$(yc config get folder-id)
yc resource-manager folder add-access-binding \
--id "$FOLDER_ID" \
--service-account-name k8s-resources-sa \
--role k8s.clusters.agent
yc resource-manager folder add-access-binding \
--id "$FOLDER_ID" \
--service-account-name k8s-resources-sa \
--role vpc.publicAdmin
yc resource-manager folder add-access-binding \
--id "$FOLDER_ID" \
--service-account-name k8s-nodes-sa \
--role container-registry.images.puller
Сеть и подсеть - обычные ресурсы VPC, --format json | jq -r .id сразу забирает id созданного
ресурса в переменную, без отдельного get после create (jq - на macOS brew install jq,
на Linux обычно уже стоит или apt install jq)
NETWORK_ID=$(yc vpc network create --name conduit-network --format json | jq -r .id)
SUBNET_ID=$(yc vpc subnet create \
--name conduit-subnet \
--zone ru-central1-a \
--network-id "$NETWORK_ID" \
--range 10.10.0.0/24 \
--format json | jq -r .id)
Группа безопасности - самая объёмная команда из четырёх: нужно явно открыть трафик между
мастером и узлами (predefined=self_security_group), пропустить health-check'и балансировщика
(predefined=loadbalancer_healthchecks), и отдельно - доступ к API-серверу и к
NodePort/LoadBalancer-сервисам снаружи, а также порт 10501 для проверок состояния
бэкендов, он понадобится балансировщику ALB в 07-K8s-cloud-ingress
Правило self_security_group пускает друг к другу только членов этой же группы, поэтому её нужно
навесить и на мастер, и на узлы - это и делают команды в следующем разделе (--security-group-ids
у кластера и security-group-ids в --network-interface у группы узлов)
SG_ID=$(yc vpc security-group create \
--name conduit-sg \
--network-id "$NETWORK_ID" \
--rule "direction=ingress,protocol=tcp,from-port=0,to-port=65535,predefined=loadbalancer_healthchecks" \
--rule "direction=ingress,protocol=any,from-port=0,to-port=65535,predefined=self_security_group" \
--rule "direction=egress,protocol=any,from-port=0,to-port=65535,predefined=self_security_group" \
--rule "direction=ingress,protocol=tcp,port=6443,v4-cidrs=[0.0.0.0/0]" \
--rule "direction=ingress,protocol=tcp,port=443,v4-cidrs=[0.0.0.0/0]" \
--rule "direction=ingress,protocol=tcp,from-port=30000,to-port=32767,v4-cidrs=[0.0.0.0/0]" \
--rule "direction=ingress,protocol=tcp,port=10501,v4-cidrs=[10.10.0.0/24]" \
--rule "direction=egress,protocol=any,from-port=0,to-port=65535,v4-cidrs=[0.0.0.0/0]" \
--format json | jq -r .id)
Правило на порт 10501 пускает только подсеть кластера (10.10.0.0/24 - тот же диапазон, что у
conduit-subnet выше): по этому порту ALB проверяет состояние бэкендов на узлах
Балансировщик появится только в 07-K8s-cloud-ingress, но открыть
порт проще сейчас, чем менять группу потом
Правила на порты 443 и 6443 (Yandex перечисляет оба порта для подключения к мастеру) с
v4-cidrs=[0.0.0.0/0] открывают API-сервер всему интернету - для продакшена это неприемлемо
(сузить до своего IP/CIDR), но для разового учебного кластера с адресом, который у ноутбука
меняется от сети к сети, проще открыть и удалить кластер сразу после проверки, чем гоняться за
точным CIDR
Полный список рекомендованных правил (в том числе отдельная группа под SSH к узлам, если он
понадобится) - в официальной документации Managed Service for Kubernetes по группам
безопасности, ссылка в "Дополнительно"
$NETWORK_ID, $SUBNET_ID и $SG_ID из этого раздела - ровно те переменные, что используются
в команде yc k8s cluster create в следующем разделе
Создание кластера и группы узлов
Кластер создаётся одной командой CLI (yc) - сеть, группа безопасности и сервисные аккаунты уже
созданы в предыдущем разделе
Здесь и ниже $SUBNET_ID/$SG_ID - те же переменные окружения, что были заданы там, а не текст
для буквальной вставки (угловые скобки <...> в самом терминале bash воспринимает как
перенаправление ввода-вывода, а не как заглушку)
yc k8s cluster create \
--name conduit-cluster \
--network-name conduit-network \
--master-location zone=ru-central1-a,subnet-id="$SUBNET_ID" \
--service-account-name k8s-resources-sa \
--node-service-account-name k8s-nodes-sa \
--security-group-ids "$SG_ID" \
--public-ip
Группа узлов - отдельная сущность и отдельная команда, кластер без неё создастся, но поды запускать будет негде
yc k8s node-group create \
--name conduit-nodes \
--cluster-name conduit-cluster \
--platform-id standard-v3 \
--cores 2 --core-fraction 20 --memory 2 \
--preemptible \
--fixed-size 2 \
--network-interface "subnets=$SUBNET_ID,ipv4-address=nat,security-group-ids=$SG_ID"
--network-interface задаёт сеть узлов - подсеть, группу безопасности и ipv4-address=nat,
то есть публичный IP у каждого узла
Он нужен не для того, чтобы к узлам кто-то подключался, а чтобы они сами выходили в интернет и
скачивали образы (quay.io, Docker Hub, cr.yandex) - без внешнего адреса поды застрянут в
ErrImagePull/ImagePullBackOff, а в событиях пода будет i/o timeout
Вместо публичных адресов выход в интернет можно дать NAT-шлюзом в подсети узлов - узлы тогда
остаются без внешних адресов, но шлюз и таблицу маршрутов нужно настроить отдельно
--core-fraction 20 - гарантированная доля vCPU меньше 100% (с возможностью кратковременно
использовать больше), --preemptible - узел, который облако может забрать в любой момент, без
гарантии отказоустойчивости и максимум на 24 часа - для продакшена не годится, но для разового
учебного упражнения обе опции заметно дешевле обычных узлов и вреда не наносят
Дороже всего в этой связке не узлы, а сам мастер - он не бывает preemptible и стоит
фиксированно (на момент написания - около ₽8.80/час за зональный мастер, вне зависимости от
того, что происходит с узлами)
Значит, главный рычаг экономии - не конфигурация узлов, а время: если оставить кластер работать
на несколько часов или дней вместо того, чтобы сразу после проверки его удалить, счёт растёт
независимо от того, насколько дёшево настроены узлы
Рассчитывать приходится только на личный пробный грант, без дополнительного курсового бюджета -
поэтому экономнее всего создавать кластер, проверять и сразу удалять в одном подходе, а не
растягивать на потом
--public-ip на кластере важен для следующего шага - без него у мастера не будет внешнего
адреса, и kubectl с вашей машины до него не достучится (та же тема "кластерный адрес vs адрес
снаружи", что и в 05-K8s-networking, только на уровне не Service,
а самого API-сервера)
Подключение kubectl: get-credentials
yc сам генерирует kubeconfig и добавляет в него новый контекст - вручную переписывать
~/.kube/config не нужно
yc managed-kubernetes cluster get-credentials conduit-cluster --external
# короткий алиас той же команды:
yc k8s cluster get-credentials conduit-cluster --external
--external - получить конфиг с внешним адресом API-сервера, до которого можно достучаться
с вашей локальной машины
Без этого флага yc пропишет внутренний адрес, доступный только из сети (VPC) самого облака -
опять ровно тот же принцип "внутри кластера/сети - один адрес, снаружи - другой", что и с
Service в 05-K8s-networking
Флагом --kubeconfig <путь> можно записать конфиг не в файл по умолчанию, а в другой - удобно,
когда конфигов для разных кластеров должно быть несколько отдельных файлов, а не один общий с
контекстами
Один kubeconfig, несколько кластеров
После get-credentials в ~/.kube/config появляется отдельный контекст для облачного
кластера рядом с контекстом minikube
kubectl всегда работает с тем кластером, который указан в текущем (current-context) -
если случайно остаться в контексте minikube, команды будут молча уходить не туда
kubectl config get-contexts # список всех известных kubectl кластеров
kubectl config current-context # какой контекст активен сейчас
kubectl config use-context minikube # вернуться на локальный кластер
kubectl config use-context yc-conduit-cluster # переключиться на облачный
Если поды не стартуют: сеть кластера
На новом кластере "странные" ошибки у подов чаще всего не в самих приложениях, а в сети между
мастером, узлами и интернетом
Два случая, которые уже встречались на практике
Образы не скачиваются
В событиях пода Failed to pull image ... i/o timeout, статус ErrImagePull или
ImagePullBackOff - у узлов нет исходящего интернета, а образы тянутся с внешних адресов
(quay.io, cr.yandex)
Если в yc compute instance list у узлов пусто в колонке EXTERNAL IP, группа узлов создана без
публичного IP (ipv4-address=nat)
Исправляется обновлением группы узлов, все узлы при этом пересоздаются
SUBNET_ID=$(yc vpc subnet get conduit-subnet --format json | jq -r .id)
SG_ID=$(yc vpc security-group get conduit-sg --format json | jq -r .id)
yc k8s node-group update conduit-nodes \
--network-interface "subnets=$SUBNET_ID,security-group-ids=[$SG_ID],ipv4-address=nat"
Подсеть передаётся при каждом обновлении, без неё API отвечает
zero subnets specified for node group
kubectl logs и exec не работают
В ответе dial tcp <IP-узла>:10250: connect: connection timed out - мастер не может достучаться
до kubelet на узле, хотя остальные команды kubectl работают
Причина на практике - мастер и узлы в разных группах безопасности, а conduit-sg пускает на
узлы только членов самой себя
Посмотреть, какая группа у мастера и какая у узлов
yc k8s cluster get conduit-cluster --format json | jq '.master.security_group_ids'
yc k8s node-group get conduit-nodes --format json \
| jq '.node_template.network_interface_specs[].security_group_ids'
Если у мастера null или другой id, его нужно перевести в ту же группу, что у узлов
Группа, созданная по этой статье, уже пускает API-сервер на порты 443 и 6443, а в более
старой группе правило на 443 добавляется отдельно - иначе после смены группы kubectl
потеряет доступ к кластеру
yc vpc security-group update-rules "$SG_ID" \
--add-rule "direction=ingress,port=443,protocol=tcp,v4-cidrs=[0.0.0.0/0]"
yc k8s cluster update conduit-cluster --security-group-ids "$SG_ID"
Третий похожий случай - под вебхука cert-manager в ContainerCreating - разобран в
07-K8s-cloud-ingress
Дальше: то же самое кодом
Команды yc выше хороши, чтобы один раз пройти по шагам и понять, что стоит за каждым из
них, но поднимать и сносить кластер ими на каждое занятие долго
Те же ресурсы можно описать декларативно в OpenTofu и поднимать и сносить одной командой - это
отдельная статья 08-K8s-opentofu, после
07-K8s-cloud-ingress
Примеры
Полный путь от нуля до kubectl get nodes на облачном кластере (сеть, группа безопасности и
сервисные аккаунты уже созданы заранее, их id подставлены как переменные)
yc k8s cluster create \
--name conduit-cluster \
--network-name conduit-network \
--master-location zone=ru-central1-a,subnet-id="$SUBNET_ID" \
--service-account-name k8s-resources-sa \
--node-service-account-name k8s-nodes-sa \
--security-group-ids "$SG_ID" \
--public-ip
yc k8s node-group create \
--name conduit-nodes \
--cluster-name conduit-cluster \
--platform-id standard-v3 \
--cores 2 --core-fraction 20 --memory 2 \
--preemptible \
--fixed-size 2 \
--network-interface "subnets=$SUBNET_ID,ipv4-address=nat,security-group-ids=$SG_ID"
yc k8s cluster get-credentials conduit-cluster --external
kubectl config use-context yc-conduit-cluster
kubectl get nodes
Дальше манифесты (Deployment/Service/StatefulSet) применяются командой kubectl apply -f
ровно так же, как на minikube - разница только в том, что Service типа LoadBalancer теперь
получит настоящий внешний IP, а не потребует minikube service --url/port-forward
Уборка после практики - обязательно в одном подходе с проверкой, не откладывая (см. "Итог")
Сначала удаляются Service типа LoadBalancer: сетевой балансировщик под ним создан в облаке
отдельно от кластера, и если удалить кластер раньше, он может остаться и тарифицироваться
kubectl get svc --all-namespaces -o json \
| jq -r '.items[] | select(.spec.type=="LoadBalancer") | "\(.metadata.namespace) \(.metadata.name)"' \
| while read -r ns name; do kubectl delete svc "$name" -n "$ns"; done
yc load-balancer network-load-balancer list
Список должен быть пустым, и только после этого удаляются группа узлов и кластер
yc k8s node-group delete conduit-nodes
yc k8s cluster delete conduit-cluster
Тот же результат через OpenTofu - в 08-K8s-opentofu, там и подъём, и уборка - одной командой каждая
Проверь себя
1. Чем управляемый (managed) кластер отличается от minikube с точки зрения Control Plane?
В managed-кластере Control Plane (мастера) полностью на стороне облака - вы их не видите и не администрируете напрямую, в отличие от кластера, который поднимаете сами (как тот же minikube)
2. Зачем кластеру в облаке собственный сервисный аккаунт, если на minikube такого не было?
В облаке кластер сам создаёт диски, балансировщики и виртуальные машины по вашему запросу, то
есть выполняет действия от своего имени, а не от вашего
Сервисный аккаунт с ролью k8s.clusters.agent - это и есть его собственные права на такие
действия
3. Чем роль k8s.editor отличается от роли k8s.cluster-api.editor?
k8s.editor даёт право редактировать сам облачный ресурс "кластер" (например, изменить размер
группы узлов через консоль или CLI)
k8s.cluster-api.editor - это IAM-роль, которая связывается с RBAC уже внутри Kubernetes API,
то есть даёт права на объекты (поды, Service и так далее) через kubectl
4. Для чего нужен --public-ip при создании кластера?
Без него у мастера не будет внешнего адреса, и kubectl с локальной машины не сможет до него
достучаться - придётся работать только изнутри облачной сети
5. Если поставить узлам --preemptible и маленький --core-fraction, кластер станет
ощутимо дешевле в целом - так ли это?
Не совсем - дороже всего в связке сам мастер, он не бывает preemptible и стоит фиксированно
независимо от узлов
Настройки узлов реально снижают их часть счёта, но погоды не делают - если оставить кластер
работать лишние часы или дни, счёт растёт из-за времени простоя, а не из-за конфигурации узлов
6. Что делает флаг --external у get-credentials?
Записывает в kubeconfig внешний адрес API-сервера, достижимый с вашей локальной машины
Без флага адрес будет внутренним - он резолвится и принимает соединения только из сети (VPC)
самого облака
7. У вас в kubeconfig есть контекст и для minikube, и для облачного кластера -
как понять, с каким из них сейчас работает kubectl?
kubectl config current-context показывает активный контекст
Если это не тот кластер, куда вы хотели попасть, переключиться можно через
kubectl config use-context <имя>
8. Что из уже знакомого по minikube меняется, когда Service имеет тип
LoadBalancer в облаке?
На minikube LoadBalancer фактически не работает как задумано - приходится пользоваться
minikube service --url или port-forward
В облаке LoadBalancer создаёт настоящий внешний балансировщик с реальным IP, и это уже не
обходной путь, а штатный способ доступа снаружи
9. Чем yc init отличается от самой установки yc через install-скрипт?
Установка кладёт бинарник yc в PATH - yc init идёт отдельным шагом уже после неё и
настраивает профиль: логин через браузер, облако и каталог по умолчанию, зону доступности для
Compute Cloud
Без этого шага yc физически есть, но не знает, от чьего имени и в каком каталоге работать
Практика
- Установи
ycи пройдиyc init - Прежде чем создавать что-либо реальное, проверь текущие условия своего пробного периода в Yandex Cloud - контроль-плейн и узлы там платные, а не бесплатный tier, и рассчитывать придётся только на личный пробный грант, без дополнительного курсового бюджета
- Создай сеть, подсеть, группу безопасности и два сервисных аккаунта (для ресурсов кластера и для узлов) с ролями из теории выше
- Разверни кластер и группу узлов из двух узлов командами
yc k8s cluster create/yc k8s node-group create - Получи kubeconfig (
yc k8s cluster get-credentials ... --external) и убедись, чтоkubectl get nodesвидит оба узла - Разверни в этом кластере тот же манифест nginx с
Serviceтип аLoadBalancer, что разворачивали на minikube, и сравни, как теперь выглядит внешний доступ - Удали всё - сначала
ServiceтипаLoadBalancer, затем кластер и группу узлов (yc k8s node-group delete,yc k8s cluster delete) - сразу после того, как проверил результат, в этом же подходе, не откладывая на потом: мастер стоит фиксированно (~₽8.80/час) вне зависимости от узлов
Если удалить кластер раньше, сетевой балансировщик подServiceможет остаться в облаке и тарифицироваться отдельно
Если сразу идёшь дальше по 07-K8s-cloud-ingress, кластер можно оставить до конца той практики, но удалить его всё равно нужно
Поднимать и сносить кластер одной командой можно в 08-K8s-opentofu
Итог / чек-лист
- Managed-кластер снимает ограничения minikube (один узел, только локальный доступ), но добавляет две новые вещи - деньги и IAM
- Control Plane в managed-кластере полностью на стороне облака, вы управляете только node group
- Перед созданием кластера нужны сеть/подсеть, группа безопасности и сервисные аккаунты
- Узлам нужен выход в интернет (публичный IP через
ipv4-address=natили NAT-шлюз), а мастер и узлы должны входить в одну группу безопасности - иначе образы не скачаются, аkubectl logsиexecбудут виснуть на порту10250 - Дороже всего в кластере - сам мастер, фиксированная стоимость, не зависит от узлов - поэтому главный способ не сжечь пробный грант - не оставлять кластер работать дольше, чем нужно для проверки, а не подбирать более дешёвые узлы
- Сервисному аккаунту кластера нужна роль
k8s.clusters.agent(иvpc.publicAdminдля публичного IP), сервисному аккаунту узлов -container-registry.images.puller - Роли
k8s.viewer/k8s.editor/k8s.admin- это доступ к облачному ресурсу "кластер", аk8s.cluster-api.*- отдельная связка IAM с RBAC внутри самого Kubernetes API yc k8s cluster get-credentials <cluster> --externalпишет kubeconfig с внешним, доступным снаружи адресом API-сервера - без--externalадрес внутренний, как и с Service в 05-K8s-networking- В одном kubeconfig уживаются несколько кластеров через контексты -
kubectl config current-context/use-contextдля проверки и переключения - Ручные
yc-шаги нужны, чтобы понять, что стоит за каждым ресурсом, а описать их кодом и поднимать и сносить одной командой можно в OpenTofu - 08-K8s-opentofu
Дополнительно
- Yandex Cloud: Начало работы с Managed Service for Kubernetes
- Yandex Cloud: Управление доступом в Managed Service for Kubernetes
- Yandex Cloud: группы безопасности для Managed Service for Kubernetes
- Yandex Cloud: сеть в Managed Service for Kubernetes
- Yandex Cloud: создание кластера через CLI
- Kubernetes Docs: Cloud Controller Manager
- Yandex Cloud: Установка Yandex Cloud CLI