образ сервера, содержащий полностью самодостаточный «снимок» операционной системы (ОС), программного обеспечения, файлов и любых других важных деталей. Затем, как показано на рис. 1.3, этот образ можно будет установить на все ваши серверы, используя другие инструменты IaC.
Рис. 1.3. С помощью таких средств шаблонизации, как Packer, можно создавать самодостаточные образы серверов. Затем, используя другие инструменты, такие как Ansible, эти образы можно установить на все ваши серверы
Рис. 1.4. Существует два вида образов: ВМ (слева) и контейнеры (справа). ВМ виртуализируют оборудование, тогда как контейнеры — только пользовательское пространство
Как видно на рис. 1.4, средства для работы с образами можно разделить на две общие категории.
•Виртуальные машины эмулируют весь компьютер, включая аппаратное обеспечение. Для виртуализации (то есть симуляции) процессора, памяти, жесткого диска и сети запускается гипервизор, такой как VMWare, VirtualBox или Parallels. Преимущество подхода — любой образ ВМ, который работает поверх гипервизора, может видеть только виртуальное оборудование, поэтому он полностью изолирован от физического компьютера и любых других образов ВМ. И он выполняется аналогично во всех средах (например, на вашем компьютере, сервере проверки качества и боевом сервере). Недостаток в том, что виртуализация всего этого оборудования и запуск совершенно отдельной ОС для каждой ВМ требует большого количества ресурсов процессора и памяти, что влияет на время запуска. Образы ВМ можно описывать в виде кода, применяя такие инструменты, как Packer и Vagrant.
•Контейнеры эмулируют пользовательское пространство ОС5. Для изоляции процессов, памяти, точек монтирования и сети запускается среда выполнения контейнеров, такая как Docker, CoreOS rkt или cri-o. Преимущество этого подхода в том, что любой контейнер, который выполняется в данной среде, может видеть только собственно пользовательское пространство, поэтому он изолирован от основного компьютера и других контейнеров. При этом он ведет себя одинаково в любой среде (например, на вашем компьютере, сервере проверки качества, боевом сервере и т. д.). Но есть и недостаток: все контейнеры, запущенные на одном сервере, одновременно пользуются ядром его ОС и его оборудованием, поэтому достичь того уровня изоляции и безопасности, который вы получаете в ВМ, намного сложнее6. Поскольку применяются общие ядро и оборудование, ваши контейнеры могут загружаться в считаные миллисекунды и практически не будут требовать дополнительных ресурсов процессора или памяти. Образы контейнеров можно описывать в виде кода, используя такие инструменты, как Docker и CoreOS rkt.
Например, ниже представлен шаблон Packer под названием web-server.json, создающий Amazon Machine Image (AMI) — образ ВМ, который можно запускать в AWS:
{
"builders": [{
"ami_name": "packer-example",
"instance_type": "t2.micro",
"region": "us-east-2",
"type": "amazon-ebs",
"source_ami": "ami-0c55b159cbfafe1f0",
"ssh_username": "ubuntu"
}],
"provisioners": [{
"type": "shell",
"inline": [
"sudo apt-get update",
"sudo apt-get install -y php apache2",
"sudo git clone https://github.com/brikis98/php-app.git /var/www/html/app"
],
"environment_vars": [
"DEBIAN_FRONTEND=noninteractive"
]
}]
}
Шаблон Packer настраивает тот же веб-сервер Apache, который мы видели в файле setup-webserver.sh, и использует тот же код на bash7. Единственное отличие от предыдущего кода в том, что Packer не запускает веб-сервер Apache (с помощью команды вроде sudoserviceapache2start). Дело в том, что шаблоны серверов обычно применяются для установки ПО в образах, а запуск этого ПО должен происходить во время выполнения образа (например, когда он будет развернут на сервере).
Вы можете создать AMI из этого шаблона, запустив команду packerbuildwebserver.json. Когда сборка завершится, полученный образ AMI можно будет установить на все ваши серверы в AWS и сконфигурировать Apache для запуска во время загрузки компьютера (пример этого см. в подразделе «Средства оркестрации» на с. 35). В результате все они будут запущены абсолютно одинаково.
Имейте в виду, что разные средства шаблонизации серверов имеют различное назначение. Packer обычно используется для создания образов, выполняемых непосредственно поверх боевых серверов, таких как AMI (доступных для работы в вашей промышленной учетной записи). Образы, созданные в Vagrant, обычно запускаются на компьютерах для разработки. Это, к примеру, может быть образ VirtualBox, который работает на вашем ноутбуке под управлением Mac или Windows. Docker обычно делает образы для отдельных приложений. Их можно запускать на промышленных или локальных компьютерах при условии, что вы сконфигурировали на них Docker Engine, используя какой-то другой инструмент. Например, с помощью Packer часто создают образы AMI, у которых внутри установлен Docker Engine; дальше эти образы развертываются на кластере серверов в вашей учетной записи AWS, и затем в этот кластер доставляются отдельные контейнеры Docker для выполнения ваших приложений.
Шаблонизация серверов — это ключевой аспект перехода на неизменяемую инфраструктуру. Идея навеяна функциональным программированием, которое предполагает наличие «неизменяемых переменных». То есть после инициализации переменной ее значение больше нельзя изменить. Если нужно что-то обновить, вы создаете новую переменную. Благодаря этому код становится намного более понятным.
Неизменяемая инфраструктура работает по тому же принципу: если сервер уже развернут, в него больше не вносятся никакие изменения. Если нужно что-то обновить (например, развернуть новую версию кода), вы создаете новый образ из своего шаблона и развертываете его на новый сервер. Поскольку серверы никогда не меняются, вам намного проще следить за тем, что на них развернуто.
Средства оркестрации
Средства шаблонизации серверов отлично подходят для создания ВМ и контейнеров, но как ими после этого управлять? В большинстве реальных сценариев применения вам нужно выбрать какой-то способ выполнения следующих действий.
• Развертывать ВМ и контейнеры с целью эффективного использования ресурсов оборудования.
• Выкатывать обновления для своих многочисленных ВМ и контейнеров, используя такие стратегии, как скользящие, «сине-зеленые» и канареечные развертывания.
• Следить за работоспособностью своих ВМ и контейнеров, автоматически заменяя неисправные (автовосстановление).
• Масштабировать количество ВМ и контейнеров в обе стороны в зависимости от нагрузки (автомасштабирование).
• Распределять трафик между своими ВМ и контейнерами (балансировка нагрузки).
• Позволять своим ВМ и контейнерам находить друг друга и общаться между собой по сети (обнаружение сервисов).
Выполнение этих задач находится в сфере ответственности средств оркестрации, таких как Kubernetes, Marathon/Mesos, Amazon Elastic Container Service (Amazon ECS), Docker Swarm и Nomad. Например, Kubernetes позволяет описывать и администрировать контейнеры Docker в виде кода. Вначале развертывается кластер Kubernetes, который представляет собой набор серверов для выполнения ваших контейнеров Docker. У большинства облачных провайдеров есть встроенная поддержка развертывания управляемых кластеров Kubernetes: вроде Amazon Elastic Container Service for Kubernetes (Amazon EKS), Google Kubernetes Engine (GKE) и Azure Kubernetes Service (AKS).
Подготовив рабочий кластер, вы можете описать развертывание своего контейнера Docker в виде кода внутри YAML-файла:
apiVersion: apps/v1
# Используем объект Deployment для развертывания нескольких реплик вашего
# Docker-контейнера (возможно, больше одного) и декларативного выкатывания
# обновлений для него
kind: Deployment
# Метаданные этого развертывания, включая его имя
metadata:
name: example-app
# Спецификация, которая конфигурирует это развертывание
spec:
# Благодаря этому развертывание знает, как искать ваш контейнер
selector:
matchLabels:
app: example-app
# Приказываем объекту Deployment развернуть три реплики Docker-контейнера
replicas: 3
# Определяет способ обновления развертывания. Здесь указываем скользящие обновления
strategy:
rollingUpdate:
maxSurge: 3
maxUnavailable: 0
type: RollingUpdate
# Этот шаблон описывает, какие контейнеры нужно развернуть
template:
# Метаданные контейнера, включая метки
metadata:
labels:
app: example-app
# Спецификация контейнера
spec:
containers:
# Запускаем Apache на порту 80
- name: example-app
image: httpd:2.4.39
ports:
- containerPort: 80
Этот файл говорит Kubernetes, что нужно создать развертывание, которое декларативно описывает следующее.
• Один или несколько контейнеров Docker для совместного запуска. Эта группа контейнеров называется подом, или под-оболочкой. Под, описанный в приведенном выше коде, содержит единственный контейнер, который запускает Apache.
• Настройки каждого контейнера Docker в под-оболочке. В нашем примере под-оболочка настраивает Apach для прослушивания порта 80.
• Сколько копий (реплик) под-оболочки должно быть в вашем кластере. У нас указано три реплики. Kubernetes автоматически определяет, в какой области кластера их следует развернуть, используя алгоритм планирования для выбора оптимальных серверов с точки зрения высокой доступности (например, каждая под-оболочка может оказаться на отдельном сервере, чтобы сбой на одном из них не остановил работу всего приложения), ресурсов (скажем, выбираются серверы с доступными портами, процессором, памятью и другими ресурсами, необходимыми вашему контейнеру), производительности (в частности, выбираются наименее загруженные серверы) и т. д. Кроме того, Kubernetes постоянно следит за тем, чтобы в кластере всегда было три реплики. Для этого автоматически заменяется любая под-оболочка, вышедшая из строя или переставшая отвечать.