Это прикладной урок. Если ты ещё путаешься в terraform plan apply и не понимаешь, что такое terraform state - вернись на пару глав назад, тут уже считается, что база есть.
Docker и Kubernetes за пять минут (контекст)
Чтобы дальше не было каши в голове, договоримся о терминах. Docker упаковывает твоё приложение со всеми зависимостями в образ - неизменяемый слепок файловой системы. Из образа запускается контейнер - изолированный процесс. Образы хранятся в реестре (registry): это как Git, только для образов. Запушил образ туда - и любая машина может его скачать.
Kubernetes (он же k8s) - это оркестратор. Он берёт кучу серверов, объединяет их в кластер и сам решает, на какой ноде запустить твой контейнер, перезапускает упавшее, балансирует нагрузку и катит обновления. Минимальный кирпичик в k8s - это Pod (один или несколько контейнеров). Поды плодятся не вручную, а через Deployment - он держит заданное число реплик. Чтобы до подов можно было достучаться по сети, поверх вешается Service.
Кластер делится на две части: control plane (мастер) - мозги, которые принимают решения, и worker-ноды - рабочие лошадки, где реально крутятся твои контейнеры. В Managed Kubernetes от Yandex Cloud мастером управляет облако: ты его не админишь, не патчишь, не чинишь по ночам. Тебе остаётся описать ноды и приложение. Это и есть смысл managed-сервиса.

Поднимаем кластер: cluster + node group
В Yandex Cloud кластер описывается двумя основными ресурсами. Первый - сам кластер с мастером (yandex_kubernetes_cluster), второй - группа рабочих нод (yandex_kubernetes_node_group). Мастер бывает двух видов: zonal (один инстанс в одной зоне - дёшево, но если зона легла, легло всё) и regional (три инстанса в трёх зонах - отказоустойчиво, но дороже). Для обучения берём zonal, для прода - regional.
Перед кластером нужна сеть, подсеть, два сервисных аккаунта (один для самого кластера, второй для нод) и роли на них. Чтобы урок не растёкся на десять экранов, сетевую обвязку я обозначу ссылками на ресурсы, а сфокусируюсь на самом k8s. Сначала провайдер и реестр образов:
Код: Выделить всё
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
}
kubernetes = {
source = "hashicorp/kubernetes"
}
helm = {
source = "hashicorp/helm"
}
}
}
provider "yandex" {
zone = "ru-central1-a"
}
resource "yandex_container_registry" "app" {
name = "cyberlake-registry"
folder_id = var.folder_id
labels = {
env = "demo"
}
}
Код: Выделить всё
resource "yandex_kubernetes_cluster" "demo" {
name = "cyberlake-k8s"
network_id = yandex_vpc_network.k8s.id
master {
version = "1.30"
public_ip = true
zonal {
zone = yandex_vpc_subnet.k8s_a.zone
subnet_id = yandex_vpc_subnet.k8s_a.id
}
maintenance_policy {
auto_upgrade = true
maintenance_window {
start_time = "03:00"
duration = "3h"
}
}
}
service_account_id = yandex_iam_service_account.k8s.id
node_service_account_id = yandex_iam_service_account.k8s_nodes.id
release_channel = "REGULAR"
depends_on = [
yandex_resourcemanager_folder_iam_member.k8s_editor,
yandex_resourcemanager_folder_iam_member.k8s_nodes_puller,
]
}
Дальше группа нод. Мастер сам по себе контейнеры не запускает - ему нужны worker-ноды. Описываем шаблон инстанса и политику масштабирования:
Код: Выделить всё
resource "yandex_kubernetes_node_group" "workers" {
cluster_id = yandex_kubernetes_cluster.demo.id
name = "cyberlake-workers"
version = "1.30"
instance_template {
platform_id = "standard-v3"
network_interface {
nat = true
subnet_ids = [yandex_vpc_subnet.k8s_a.id]
}
resources {
cores = 2
memory = 4
}
boot_disk {
type = "network-ssd"
size = 64
}
container_runtime {
type = "containerd"
}
}
scale_policy {
fixed_scale {
size = 2
}
}
allocation_policy {
location {
zone = "ru-central1-a"
}
}
maintenance_policy {
auto_upgrade = true
auto_repair = true
}
}
Связываем всё: образ, реестр и деплой через провайдеры
Кластер есть, но он пустой. Логика пайплайна такая: собираем Docker-образ, пушим его в yandex_container_registry, а потом через k8s-провайдер раскатываем Deployment, который тянет этот образ. Сборка и пуш - это шаг вне Terraform (Terraform не собирает образы, не надо его об этом просить):
Код: Выделить всё
# логинимся в реестр Yandex Cloud
yc container registry configure-docker
# собираем и тегаем образ ID реестра из terraform output
docker build -t cr.yandex/$REGISTRY_ID/web:1.0 .
docker push cr.yandex/$REGISTRY_ID/web:1.0
Код: Выделить всё
data "yandex_client_config" "client" {}
provider "kubernetes" {
host = yandex_kubernetes_cluster.demo.master[0].external_v4_endpoint
cluster_ca_certificate = yandex_kubernetes_cluster.demo.master[0].cluster_ca_certificate
token = data.yandex_client_config.client.iam_token
}
resource "kubernetes_deployment_v1" "web" {
metadata {
name = "web"
}
spec {
replicas = 2
selector {
match_labels = { app = "web" }
}
template {
metadata {
labels = { app = "web" }
}
spec {
container {
name = "web"
image = "cr.yandex/${yandex_container_registry.app.id}/web:1.0"
port {
container_port = 8080
}
}
}
}
}
}
Helm-провайдер настраивается так же, через тот же host/token, только внутри блока kubernetes. Через него удобно ставить готовые чарты - ingress-контроллер, мониторинг, cert-manager - не описывая сотни строк манифестов руками.
Типичные грабли
- Провайдер k8s подключается раньше, чем готов кластер. Классика: Terraform пытается создать Deployment, а мастер ещё поднимается. Лекарство - depends_on на ресурсе кластера у деплоя, либо разнести инфраструктуру и приложение по разным конфигурациям (два state). Второе чище: смешивать создание кластера и деплой в него в одном apply - известный антипаттерн.
- IAM-токен живёт около 12 часов. iam_token из yandex_client_config протухает. Для разовых apply нормально, для CI лучше workload identity или отдельный сервисный аккаунт с ключом.
- Нодам нет доступа к реестру. Если поды не могут вытянуть образ (ImagePullBackOff) - проверь, что node_service_account_id имеет роль container-registry.images.puller. Кластер видит реестр, а ноды качают образы именно под своим аккаунтом.
- Перепутал zonal и regional. В одном блоке master может быть только что-то одно. Засунешь оба - Terraform ругнётся на конфликт.
Повтори руками, не копируя бездумно:
- Опиши yandex_container_registry и zonal-кластер yandex_kubernetes_cluster с node group на 2 ноды. Сеть и сервисные аккаунты добавь сам - это хорошая тренировка.
- Собери любой простой образ (хоть nginx), запушь в реестр через docker push на cr.yandex.
- Подключи провайдер kubernetes через master[0].external_v4_endpoint и раскатай Deployment с этим образом.
- Сделай terraform plan apply, дождись подов, проверь kubectl get pods. Потом измени replicas и снова apply - посмотри, как меняется только нужное.
- Финал - terraform destroy. Убедись, что благодаря depends_on всё сносится в правильном порядке.
- Чем zonal-мастер отличается от regional и когда какой выбирать?
- Зачем у кластера два разных сервисных аккаунта (service_account_id и node_service_account_id)?
- Откуда провайдер kubernetes берёт host и cluster_ca_certificate для подключения?
- Почему собирать и пушить Docker-образ - это шаг вне Terraform, а не его задача?
Managed Kubernetes в Yandex Cloud - это два ресурса: yandex_kubernetes_cluster (мастер, zonal или regional) плюс yandex_kubernetes_node_group (worker-ноды). Образы живут в yandex_container_registry, кластер подтягивает их под аккаунтом нод. Магия - в связке провайдеров: yandex поднимает железо, а kubernetes и helm поверх него катят приложение, получая kubeconfig прямо из атрибутов кластера. Пайплайн собрали образ - запушили - подняли кластер - задеплоили описывается целиком как код, в идеале с разделением инфраструктуры и приложения по разным state. И всё это одинаково работает что в Terraform, что в OpenTofu - синтаксис HCL и провайдеры те же.