Привет, Хабр! Я Алексей Волков, менеджер продукта компании VK Cloud Solutions. Хочу рассказать о подходе IaC (Infrastructure as Code, инфраструктура как код), который позволяет управлять сетями, виртуальными машинами, подсистемами балансировки нагрузки и другими элементами инфраструктуры как кодом с помощью описательной модели. Поговорим о современных принципах управления инфраструктурой, инструментах IaC в облаке и вариантах построения CI/CD-пайплайна.
Традиционный процесс администрирования vs IaC
Чтобы лучше понять суть IaC, удобнее всего сравнить его с традиционным подходом к администрированию инфраструктуры.
При традиционном подходе:
- Устанавливают операционную систему (ОС).
- Добавляют пользователей.
- Ставят нужные пакеты.
- Вписывают конфигурации.
- Запускают приложение.
Если нужно расширить функциональность образа или перенастроить его, повторяют пункты 3–5. Такой подход еще называют Configuration Drain. Недостаток в том, что при неоднократном обновлении инстанса практически невозможно понять, какая версия пакетов установлена и какие настройки активны. Это нередко приводит к тому, что последующее обновление может не запуститься или сломать всю инфраструктуру.
При IaC-подходе алгоритм отличается:
- Готовят шаблон сервера со всеми настройками и пакетами.
- Описывают в тексте инфраструктуру с указанием сетей, серверов, прав доступа и других параметров.
- Применяют изменения.
В итоге получается «золотой образ», готовый к использованию. Как правило, перед запуском в продакшене его проверяют на стейдже — если он корректно работает во время теста, то и в реальных условиях проблем не будет.
«Золотой образ» можно использовать и в качестве шаблона — например, если нужно внести дополнительные изменения поверх уже активных настроек.
IaC-подход к администрированию повышает предсказуемость системы, ее воспроизводимость и контролируемость. Кроме того, он позволяет быстро исправлять возникающие ошибки: в любой момент можно перезапустить инстанс с корректными настройками из чистого образа. Одновременно с этим можно управлять инфраструктурой как кодом: версионировать, использовать возможности Git для контроля изменений, указывать этапы выкатки и другие нюансы.
На практике для реализации IaC-подхода к администрированию используют разные инструменты. Рассмотрим Packer и Terraform от компании HashiCorp.
Packer
Packer — инструмент для создания одинаковых образов ОС для различных платформ из одного описания. Образ, создаваемый Packer, включает в себя настроенную операционную систему (ОС) и набор программного обеспечения (ПО). Packer умеет создавать образы AMI для EC2, VMDK/VMX-файлы для VMware, OVF для VirtualBox и другие.
Packer не зависит от облачной инфраструктуры — он поддерживает несколько бэкендов и может работать с разными провайдерами «из коробки».
Алгоритм работы с Packer следующий:
- Создаем и описываем файл конфигурации базовой виртуальной машины.
- Запускаем Packer, который создает виртуальную машину.
- Обращаемся в провижинер, например Ansible, для провижинга виртуальной машины.
- После провижинга приводим виртуальную машину к нужному состоянию, со всеми настройками и наборами ролей.
- Packer идет в облачное хранилище в бэкенде и делает снапшот виртуальной машины, который потом загружает в облако в виде образа.
Схема работы Packer
В результате получается базовый образ, из которого можно развернуть любое количество виртуальных машин. Логика работы Packer похожа на логику Docker, когда из Docker-файла с описанием создают образ виртуальной машины.
Пример использования Packer
Покажу на примере. Возьмем директорию Nginx, она вложена в директорию Packer на GitHub:
# ls
README.md
packer
terraform
# cd packer/nginx
Далее соберем базовый образ с Nginx. Для этого:
- Берем стандартный образ из облака.
- С помощью Ansible ставим в образ Nginx.
- Изменяем конфигурацию заменой конфигурационного файла.
- Запускаем и включаем Nginx.
Nginx взят исключительно для примера, по аналогии можно сконфигурировать любое приложение, которое запускается на виртуальных машинах.
Приступаем к сборке образа:
1. Для примера я создал на GitHub директорию Packer. В ней лежит директория Nginx и файл nginx.pkr.hcl.
# ls
nginx
nginx.pkr.hcl
playbook.yml
# vim nginx.pkr.hcl
2. Файл nginx.pkr.hcl содержит конфигурацию для Packer, эта информация нужна, чтобы Packer понимал, в каком бэкенде и как нужно запустить виртуальную машину.
variable "image_tag" {
type = string
}
source "openstack" "nginx" {
source_image_filter {
filters {
name = "Centos-7.9-202107"
}
most_recent = true
}
flavor = "Basic-1-1-10"
ssh_username = "centos"
security_groups = ["all"]
volume_size = 10
config_drive = "true"
use_blockstorage_volume = "true"
networks = ["298117ae-3fa4-4109-9e08-8be5602be5a2"]
image_name = "nginx-${var.image_tag}"
}
build {
sources = ["source.openstack.nginx"]
provisioner "ansible" {
playbook_file = "playbook.yml"
}
}
3. В конфиге через
source "openstack" и source_image_filter
указываем, какой базовый образ использовать для создания нового. В данном случае — Centos-7.9-202107, который находится в публичном доступе в облаке VK Cloud Solutions.4. Через
flavor
указываем размер виртуальной машины, например: 1 ядро, 1 ГБ оперативной памяти, 10 ГБ диска. Эти параметры касаются только базовой виртуальной машины, с которой снимается образ. Из нового образа можно будет запускать ВМ любого размера.5. Здесь же указываем параметры подключения к ВМ по SSH и другие настройки. Доступ по SSH нужен, чтобы Packer мог запустить в облаке ВМ и получить доступ к Ansible, который подключается к ВМ и выполняет нужные действия. Только после этого создается снапшот виртуальной машины.
6. В переменных указываем название создаваемых образов. Здесь
nginx
— постоянный префикс,
$
— переменная версии. Например, nginx 0.0.1
. Для этого в начале описываем, что есть переменная image_tag
типа string
. При старте сборки образа мы запускаем Packer и указываем image_tag
. Алгоритм такой же, как при сборке Docker-образов, то есть у нас будет образ nginx 0.0.1, 0.0.2, 0.0.3, 1.0.0 и так далее — сразу версионируем образы.7. В блоке
build
описываем, как запускать провижинер и какой. В данном случае Ansible
. Тут же указываем файл, который нужен для запуска, — playbook.yml
. 8. В файле playbook.yml указываем, что на всех хостах нужно запустить роль
Nginx
.---
- hosts: all
become: true
roles:
- nginx
9. В файле роли
Nginx
все довольно просто: указываем минимальную установку Nginx, прикрепляем набор конфигураций, добавляем репозиторий с исходным Nginx и устанавливаем его. После этого копируем конфигурацию в виртуальную машину, стартуем и энейблим сервис с Nginx. ---
- name: Add nginx repo | Centos
yum_repository:
baseurl: http://nginx.org/packages/mainline/rhel/7/$basearch/
enabled: true
gpgcheck: false
description: Nginx repo
name: nginx
when: ansible_facts['os_family'] == "RedHat"
- name: Install Nginx | Centos
yum:
name: nginx
state: present
when: ansible_facts['os_family'] == "RedHat"
- name: Add Nginx config
template:
src: default.conf.j2
dest: /etc/nginx/conf.d/default.conf
mode: 0644
- name: Start and enable Nginx
service:
name: nginx
enabled: true
state: started
То есть после того, как из этого образа ВМ мы создадим всю инфраструктуру нескольких виртуальных машин, там изначально будет стоять Nginx, сконфигурированный, запущенный и заэнейбленый. После запуска этой виртуальной машины включится Nginx и будет готов обслуживать пользователей.
10. В файле конфига default.conf просим Nginx при обращении к корню возвращать «200» и свой hostname. Это поможет понять, как работают инстансы и выполняется балансировка.
server {
listen 80 default_server;
server_name _;
default_type text/plain;
location / {
return 200 '$hostname\n';
}
}
Это вся конфигурация, которая нужна для запуска Packer. Перед этим важно соблюсти два условия:
- Передать в Packer ключи для взаимодействия с API облака, то есть логин и пароль учетной записи.
- Локально установить и настроить Ansible.
Запустим Packer.
# packer build -var ‘image_tag=1.0.1' nginx.pkr.hcl
openstack: output will be in this color
==> openstack: Loading flavor: Basic-1-1-10
openstack: Verified flavor. ID: df3c499a-044f-41d2-8612-d303adc613cc
==> openstack: Creating temporary keypair: packer_621795d4-d237-1627-ef62-57d0952dl304 ...
==> openstack: Created temporary keypair: packer_621795d4-d237-1627-ef62-57d0952dl304
openstack: Found Image ID: 44709803-5ec2-496b-88b7-85a5250e51c4
==> openstack: Creating volume...
==> openstack: Waiting for volume packer_621795d4-d40a-0146-b30c-92f00elae3b4 (volume id: 9102e41e-15b7-4ef6-8c27-7a0de 49ce922) to become available...
openstack: Volume ID: 9102e41e-15b7-4ef6-8c27-7a0de49ce922
=> openstack: launching server...
==> openstack: Launching server... openstack: Server ID: f44e40eb-a907-49e3-8d7e-89b20eaa8df1
==> openstack: Waiting for server to become ready...
- Выполняем команду
packer build - var
, указываем переменнуюimage_tag
с индексом 1.0.1 и передаем конфигурационный файл nginx.pkr.hcl, который мы использовали раньше.
- После выполнения команды Packer идет в интерфейс облака и запускает виртуальную машину.
- При этом в личном кабинете облака начинает создаваться виртуальная машина с указанным типом. На этом этапе Packer ждет запуск ВМ и последующий запуск SSH. После этого на ВМ запускается Ansible, который снимает с ВМ образ.
- После завершения обработки в интерфейсе облака будет создан образ, из которого можно запустить любое количество виртуальных машин.
Вручную добавлять инстансы, конфигурировать и настраивать каждую ВМ в облаке опасно: даже из-за незначительной ошибки могут возникать глобальные сбои в инфраструктуре. Автоматизировать эти процессы и исключить ошибки позволяет Terraform.
Terraform
Terraform — инструмент от компании Hashicorp, который позволяет декларативно управлять инстансами, сетями, группами безопасности и другими компонентами инфраструктуры с помощью файлов конфигураций. Благодаря Terraform можно привести инфраструктуру к нужному состоянию декларативно, сразу указав нужные параметры системы.
Еще в Terraform предусмотрена функция «План». Благодаря ей инструмент сравнивает текущее состояние системы с будущим и отображает пользователю, что именно будет удалено, запущено или изменено в соответствии с новым конфигурационным файлом. Такая проверка помогает исключить ошибки при создании рабочей инфраструктуры.
Конфигурация Terraform
Конфигурация Terraform интереснее, чем у Packer. Для его работы файлы конфигурации должны находиться в директории, из который запускают инструмент. При этом оформление не имеет значения: конфигурацию можно описать как в одном файле, так и в нескольких, разделив на смысловые блоки.
# ls
nginx nginx.pkr.hcl playbook.yml
# cd ../../nginx/terraform
# ls
keys.tf network.tf terraform.tfstate.backup
loadbalancer.tf providers.tf variables.tf
main.tf terraform.tfstate vars.tfvars
Как правило, описание конфигурации содержит:
1. Файл с описанием провайдеров. Terraform может работать с разными облаками, поэтому в файле описываем параметры провайдера — название и версию. Подробнее про настройку Terraform-провайдера для VK Cloud Solutions можно посмотреть здесь.
terraform {
required_providers {
vkcs = {
source = "vk-cs/vkcs"
}
}
}
2. Файл с данными для подключения к облаку. Инструменту надо передать API endpoints, ключи и другие персональные идентификаторы.
3. Файл с описанием конфигурации виртуальных машин. Terraform работает с двумя типами объектов: Data и Resource. Data — то, что можно получить из облака. Например, конфигурация дефолтной сети облака. А Resource — то, что создаем сами.
resource "vkcs_compute_instance" "instance" {
count = var.node_count
name = "node-${count.index}"
image_name = "${var.image_name}-${var.image_tag}"
flavor_name = var.flavor_name
key_pair = vkcs_compute_keypair.ssh.name
config_drive = true
security_groups = [
vkcs_networking_secgroup.secgroup.name
]
network {
name = vkcs_networking_network.example_routed_private_network.name
}
lifecycle {
create_before_destroy = true
}
}
В описании Resource указываем тип ресурса:
vkcs_compute_instance
. Также указываем внутреннее имя ресурса, которое Terraform будет использовать для автоматического построения зависимостей. С помощью таких имен можно обращаться к свойствам других объектов.В этом файле конфигурации также указываем переменную с названием ВМ — с префиксом node и переменной
count.index
. Прописываем еще две переменные: image_name
и image_tag
, которые будут указывать на название и тег образа, созданного с помощью Packer. Также описываем и другие параметры.
4. Файл с описанием сетей. В файле с сетями используется другой тип объектов — Data. Он нужен для запроса данных из облака в переменную
ext-net
. Также в файле прописываем приватную сеть и подсеть, создаем роутер, конфигурируем Security-группы и настраиваем остальные сетевые параметры.data "vkcs_networking_network" "extnet" {
name = "ext-net"
}
resource "vkcs_networking_network" "example_routed_private_network" {
name = "example_routed_private_network"
}
resource "vkcs_networking_subnet" "example_routed_private_subnet" {
name = "example_routed_private_subnet"
network_id = vkcs_networking_network.example_routed_private_network.id
cidr = "10.0.2.0/24"
ip_version = 4
enable_dhcp = true
}
resource "vkcs_networking_router" "example_router" {
name = "example_router"
external_network_id = data.vkcs_networking_network.extnet.id
}
resource "vkcs_networking_router_interface" "example_router_interface" {
router_id = vkcs_networking_router.example_router.id
subnet_id = vkcs_networking_subnet.example_routed_private_subnet.id
}
resource "vkcs_networking_secgroup" "secgroup" {
name = "terraform__security_group"
description = "security group for terraform instance"
}
resource "vkcs_networking_secgroup_rule" "secgroup_rule22" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
remote_ip_prefix = "0.0.0.0/0"
security_group_id = "${vkcs_networking_secgroup.secgroup.id}"
}
resource "vkcs_networking_secgroup_rule" "secgroup_rule80" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 80
port_range_max = 80
remote_ip_prefix = "0.0.0.0/0"
security_group_id = "${vkcs_networking_secgroup.secgroup.id}"
}
resource "vkcs_networking_secgroup_rule" "secgroup_rule-1" {
direction = "ingress"
ethertype = "IPv4"
protocol = "icmp"
remote_ip_prefix = "0.0.0.0/0"
security_group_id = "${vkcs_networking_secgroup.secgroup.id}"
}
5. Файл с ключами доступа. В нем прописываем, что Terraform сам автоматически генерирует SSH-ключ: приватную часть после запуска сохранит локально, а публичную часть положит в облако. Впоследствии публичный ключ будет доставлен во все новые ВМ.
resource "tls_private_key" "ssh" {
algorithm = "RSA"
}
resource "vkcs_compute_keypair" "ssh" {
name = "terraform_ssh_key"
public_key = tls_private_key.ssh.public_key_openssh
}
output "ssh" {
value = tls_private_key.ssh.private_key_pem
sensitive = true
}
Ресурс
tls_private_key.ssh.private_key_pem
содержит приватный ключ. В выводе он помечен как sensitive = true
и будет маскирован. Используем следующую команду для сохранения приватного ключа:terraform.exe output ssh
6. Файл с конфигурацией load balancer. Балансировщик нагрузки нужен, если планируется запускать несколько виртуальных машин. Для подготовки в файле создаем Load Balancer и внешний Listener, добавляем в балансировщик виртуальные машины и определяем правила проверки. При правильной настройке балансировщика Terraform обеспечивает Zero Downtime Deployment с плавной для пользователей сменой версии приложения.
resource "vkcs_networking_floatingip" "example_floating_ip" {
pool = "ext-net"
port_id = vkcs_lb_loadbalancer.example_http_balancer.vip_port_id
}
resource "vkcs_lb_loadbalancer" "example_http_balancer" {
name = "example_http_balancer"
description = "An HTTP load balancer in a private network with 2 backends"
vip_subnet_id = vkcs_networking_subnet.example_routed_private_subnet.id
}
resource "vkcs_lb_listener" "example_http_listener" {
name = "example_http_listener"
description = "A load balancer frontend that listens on 80 prot for client traffic"
protocol = "HTTP"
protocol_port = 80
loadbalancer_id = vkcs_lb_loadbalancer.example_http_balancer.id
}
resource "vkcs_lb_pool" "example_http_pool" {
name = "example_http_pool"
description = "A load balancer pool of backends with Round-Robin algorithm to distribute traffic to pool's members"
protocol = "HTTP"
lb_method = "ROUND_ROBIN"
listener_id = vkcs_lb_listener.example_http_listener.id
}
resource "vkcs_lb_monitor" example_http_monitor {
name = "example_http_monitor"
delay = 5
max_retries = 3
timeout = 5
type = "HTTP"
url_path = "/"
http_method = "GET"
expected_codes = "200"
pool_id = vkcs_lb_pool.example_http_pool.id
}
resource "vkcs_lb_member" "example_http_member" {
count = var.node_count
name = "example_http_member-${count.index}"
address = vkcs_compute_instance.instance.*.access_ip_v4[count.index]
protocol_port = 80
weight = 10
pool_id = vkcs_lb_pool.example_http_pool.id
subnet_id = vkcs_networking_subnet.example_routed_private_subnet.id
lifecycle {
create_before_destroy = true
}
}
output "example_http_balancer_vip_address" {
value = vkcs_networking_floatingip.example_floating_ip.address
}
Для запуска Terraform и начала создания указанной инфраструктуры нужно выполнить
terraform apply
. В ответ на это Terraform выводит весь план создаваемой конфигурации, запрашивает подтверждение и начинает создавать в облаке всю заданную инфраструктуру с сетями, подсетями, роутерами, балансировщиками и другими компонентами.В итоге IaC-инструменты сводят всю подготовку инфраструктуры к простым действиям:
- Описание образа.
- Описание состояний виртуальных машин с помощью Ansible.
- Установка и настройка Nginx.
- Запуск виртуальных машин с помощью Terraform.
При таком подходе не нужно будет катать Ansible по всем создаваемым виртуальным машинам, что упрощает внесение любых изменений.
Обновленный CI/CD-пайплайн
C помощью Packer и Terraform можно реализовать все принципы Docker с неизменяемой инфраструктурой и Kubernetes с выкатыванием новых версий через перезапуск.
Связка инструментов отлично работает с инфраструктурой на виртуальных машинах в облаке и сохраняет привычный алгоритм:
- Разработчик выпускает новую версию приложения.
- С помощью Packer делаем новый образ ВМ на основе новой версии приложения.
- С помощью Terraform автоматизировано повторно разворачиваем всю инфраструктуру без даунтайма с Health Check от балансировщиков.
На нашей платформе VK Cloud Solutions можно протестировать Terraform. Для этого при регистрации мы начисляем пользователям 3000 бонусных рублей — приходите, пробуйте и оставляйте обратную связь.
amarao
Путаница какая-то. Golden artifact и baked image - два совершенно разных явления. Golden artifact - это такой анти-паттерн. Волшебное чудо, собранное один раз человеком. Мы знаем, что этот образ работает, но второй раз сделать точно такой же но с апдейтами не можем. Golden artifact используется, но не воспроизводится (кроме как копированием).
А baked image - вполне себе разумный паттерн, который кому-то ощутимо экономит время деплоя. Его можно воспроизвести, протестировать и переделать если что-то чуть-чуть не так.