Поднимаем инфру в SberCloud с Terraform

Поднимаем инфру в SberCloud с Terraform

You’re going to need a bigger boat.

В 2020е годы стали активно развиваться новые SaaS платформы, в противовес широко используемой "большой тройке" AWS, GCP, Azure. В частности, в РФ появился целый ряд Cloud-провайдеров с разным функционалом и целевой аудиторией. Одним из примеров новой волны клаудов стал SberCloud. Наибольший интерес представляет его часть, называемая advanced, функционал которой выглядит наиболее похоже на привычные публичные клауды и имеет открытый API.

О чём пойдёт речь

Данный пост не является обзором функционала или сравнением с другими игроками. Не секрет, что под капотом SberCloud Advanced находится адаптированный дистрибутив HuaweiCloud, что легко заметить по практически идентичной документации (например Elastic Cloud Server в SberCloudи HuaweiCloud).

Будет рассмотрена следующая задача: - поднять минимальный стенд с managed Kubernetes, используя подход Infrastructure-as-Code. Под минимальным стендом подразумевается следующее: k8s-sc

Terraform, Sbercloud

Первое знакомство с SberCloud, как всегда в таких случаях, начинается с работы в веб-консоли, но для построения полноценного IaC, нам потребуется API клауда и некая автоматизация вокруг, например на базе Terraform. К счастью, инженеры из сберклауда тоже озаботились этим вопросом и поиск по гитхабу даёт следующий репозиторий с соответствующим терраформ провайдером. Репозиторий живой, выходят регулярые релизы с чейнджлогами, есть пулл-реквесты и закрытые ишуи. Отдельно стоит отметить директорию examples, в которой есть практически всё, что может пригодиться для быстрого старта. Однако, есть некоторые тонкости.

Начинать следует с конфигурации аутентификации терраформ клиента по инструкции отсюда. В случае, когда у вас есть учётные данные только для IAM юзера, то нужно посетать следующие переменные окружения:

    $ export SBC_ENTERPRISE_PROJECT_ID="YOUR_ENTERPRISE_UUID"
    $ export SBC_PROJECT_NAME="YOUR_ENTERPRISE_PROJECT_NAME"

При этом, в SBC_PROJECT_NAME нужно писать имя не в том виде, в котором его видно в веб-консоли. Формат должен быть следующий: ${SBC_REGION_NAME}_${PROJECT_NAME_FROM_WEBCONSOLE}. Например ru-moscow-1_my-awesome-project. На момент написания, данная информация отсутствовала в документации, однако можно было подсмотреть в исходниках провайдера.

В остальном, можно смело брать примеры из репозитория и компилировать из них своё решение, HCL код там хороший и показать его потом будет не стыдно.

Fuck you, Hashicorp

Печальный тренд на cancel culture не обошёл стороной и компанию Hashicorp. Выражается это в следующем: при запуске в терминале terraform init появляется сообщение об ошибке:

    
     Error: Failed to query available provider packages
     
     Could not retrieve the list of available versions for provider hashicorp/aws: could not connect to registry.terraform.io:
     Failed to request discovery document: 403 Forbidden
    

Тем не менее, существует несколько лазеек, позволяющих обойти такие ограничения. Первая и самая очевидная - использовать VPN с адресом где-нибудь за пределами подсанкционных стран.

Вторая - это завести свой собственный registry сервер для хостинга артефактов провайдеров. На данный момент, есть открытая спецификация Provider Registry Protocol, по которой можно самостоятельно реализовать сервер. Из готовых же opensource реализаций был найден только terralist, возможно со временем их будет появляться больше.

Обезьяна нашла третий путь

Третья лазейка - настроить терраформ на использование в качестве registry некую директорию на локальной машине, в которой положить заранее собранные\скачанные бинари провайдеров. В данном случае, понадобится два провайдера:

  1. Sbercloud v1.10.0
  2. Local v.2.2.2

Подготавливаем terraform:

Теперь terraform init отработает корректно можно будет двигаться дальше.

Даёшь Kubernetes!

Все переменные, использующиеся в HCL коде будут определены отдельно.

Начнём с создания VPC, подсетей и правил безопасности, потом бастион и, наконец, сам кластер k8s.

resource "sbercloud_vpc" "vpc" {                                                                                              
  name = var.vpc_name
  cidr = var.vpc_cidr
  tags = var.tags
}

resource "sbercloud_vpc_subnet" "subnet" {
  name       = var.subnet_name
  cidr       = var.subnet_cidr
  gateway_ip = var.subnet_gateway_ip

  primary_dns   = var.subnet_dns1
  secondary_dns = var.subnet_dns2

  vpc_id = sbercloud_vpc.vpc.id

  tags = var.tags
}
locals {
  default_rules = {
    http-rule = {
      description = "Allow HTTP from anywhere",
      protocol = "tcp",
      port = 80,
      source = "0.0.0.0/0"
    },
    https-rule = {
      description = "Allow HTTPS from anywhere",
      protocol = "tcp",
      port = 443,
      source = "0.0.0.0/0"
    },
    ssh-rule = {
      description = "Allow SSH from anywhere",
      protocol = "tcp",
      port = 22,
      source = "0.0.0.0/0"
    }
  }
}
resource "sbercloud_networking_secgroup" "sg_default" {
  name        = var.default_security_group_name
  description = "Default security group"
}
resource "sbercloud_networking_secgroup_rule" "default_ingress_icmp" {
  direction         = "ingress"
  ethertype         = "IPv4"
  description       = "Allow ICMP from anywhere"
  protocol          = "icmp"
  remote_ip_prefix  = "0.0.0.0/0"

  security_group_id = sbercloud_networking_secgroup.sg_default.id
}
resource "sbercloud_networking_secgroup_rule" "default_ingress" {
  for_each = local.default_rules

  direction         = "ingress"
  ethertype         = "IPv4"
  description       = each.value.description
  protocol          = each.value.protocol
  port_range_min    = each.value.port
  port_range_max    = each.value.port
  remote_ip_prefix  = each.value.source

  security_group_id = sbercloud_networking_secgroup.sg_default.id
}
resource "sbercloud_compute_instance" "bastion" {
  name              = var.hostname
  image_id          = var.ecs_image_id
  flavor_id         = var.ecs_flavor
  security_groups   = [var.default_security_group_name]
  availability_zone = var.vpc_az
  key_pair          = var.keypair_name
  system_disk_type  = var.ecs_system_disk_type
  system_disk_size  = var.ecs_system_disk_size
  # инстансы в SberCloud поддерживают cloud-init
  user_data         = file("${path.module}/setup.sh")
  tags              = var.tags
  network {
    uuid = var.subnet_id
  }
}
resource "sbercloud_vpc_eip" "bastion_eip" {
  tags = var.tags
  publicip {
    type = "5_bgp"
  }
  bandwidth {
    name        = "elb-bastion-bandwidth"
    size        = var.bandwidth_size
    share_type  = "PER"
    charge_mode = var.bandwidth_charge_mode
  }
}
resource "sbercloud_compute_eip_associate" "associated_01" {
  public_ip   = sbercloud_vpc_eip.bastion_eip.address
  instance_id = sbercloud_compute_instance.bastion.id
}
resource "sbercloud_cce_cluster" "default" {
  name                   = var.cce_cluster_name
  flavor_id              = var.cce_flavor
  container_network_type = "overlay_l2"
  container_network_cidr = var.subnet_cidr
  multi_az               = false
  vpc_id                 = var.vpc_id
  subnet_id              = var.subnet_id
}
resource "sbercloud_cce_node_pool" "default" {
  cluster_id               = sbercloud_cce_cluster.default.id
  name                     = var.cce_nodepool_name
  flavor_id                = var.cce_node_flavor
  availability_zone        = var.vpc_az
  key_pair                 = var.keypair_name
  scall_enable             = true
  min_node_count           = 1
  initial_node_count       = 1
  max_node_count           = var.max_node_count
  scale_down_cooldown_time = 100
  priority                 = 1
  type                     = "vm"
  os                       = "CentOS 7.6"
  tags                     = var.tags

  root_volume {
    size       = 50
    volumetype = "SAS"
  }

  data_volumes {
    size       = 100
    volumetype = "SAS"
  }
}
resource "local_file" "kubeconfig" {
  depends_on   = [sbercloud_cce_cluster.default]
  filename     = "./kubeconfig"
  # А вот тут нам и пригодился провайдер local - он нам сгенерит конфиг для kubectl
  content      = sbercloud_cce_cluster.default.kube_config_raw
}

Итого

На данном этапе мы подготовили примерный код, который можно аккуратно разложить по директориями и запустить terraform apply, после чего можно заходить по ssh на бастион и полноценно работать с кластером k8s (не забыв забрать сгенерённый kubeconfig). Готовый к использованию код можно найти тут. Подробнее про варианты конфигурации ресурсов в SberCloud можно почитать в документации.