Непрерывное развертывание Jenkins с нулевым временем простоя с помощью Terraform на AWS
Опубликовано: 2022-03-11В современном мире Интернета, где буквально все должно работать круглосуточно и без выходных, надежность имеет ключевое значение. Это приводит к почти нулевому времени простоя ваших веб-сайтов, избежанию ужасной страницы ошибки «Не найдено: 404» или другим сбоям в работе службы во время развертывания вашего новейшего выпуска.
Предположим, вы создали новое приложение для своего клиента или, может быть, для себя, и вам удалось привлечь хорошую базу пользователей, которым нравится ваше приложение. Вы собрали отзывы от своих пользователей, и вы идете к своим разработчикам и просите их создать новые функции и подготовить приложение к развертыванию. Когда это будет готово, вы можете либо остановить все приложение и развернуть новую версию, либо создать конвейер развертывания CI/CD с нулевым временем простоя, который будет выполнять всю утомительную работу по доставке новой версии пользователям без ручного вмешательства.
В этой статье мы поговорим именно о последнем, как мы можем иметь непрерывный конвейер развертывания трехуровневого веб-приложения, построенного на Node.js в облаке AWS, используя Terraform в качестве оркестратора инфраструктуры. Мы будем использовать Jenkins для части непрерывного развертывания и Bitbucket для размещения нашей кодовой базы.
Репозиторий кода
Мы будем использовать демонстрационное трехуровневое веб-приложение, код которого вы можете найти здесь.
Репозиторий содержит код как для Интернета, так и для слоя API. Это простое приложение, в котором веб-модуль вызывает одну из конечных точек на уровне API, которая внутренне извлекает информацию о текущем времени из базы данных и возвращает ее на веб-уровень.
Структура репозитория следующая:
- API: код для слоя API
- Web: Код для веб-слоя
- Terraform: код для оркестровки инфраструктуры с использованием Terraform.
- Jenkins: код для оркестратора инфраструктуры для сервера Jenkins, используемого для конвейера CI/CD.
Теперь, когда мы понимаем, что нам нужно развернуть, давайте обсудим, что нам нужно сделать, чтобы развернуть это приложение на AWS, а затем поговорим о том, как сделать эту часть конвейера CI/CD.
Выпечка изображений
Поскольку мы используем Terraform для оркестратора инфраструктуры, имеет смысл иметь предварительно подготовленные образы для каждого уровня или приложения, которое вы хотите развернуть. И для этого мы будем использовать другой продукт от Hashicorp — Packer.
Packer — это инструмент с открытым исходным кодом, который помогает создать образ машины Amazon или AMI, который будет использоваться для развертывания на AWS. Его можно использовать для создания образов для различных платформ, таких как EC2, VirtualBox, VMware и других.
Вот фрагмент того, как файл конфигурации Packer ( terraform/packer-ami-api.json
) используется для создания AMI для уровня API.
{ "builders": [{ "type": "amazon-ebs", "region": "eu-west-1", "source_ami": "ami-844e0bf7", "instance_type": "t2.micro", "ssh_username": "ubuntu", "ami_name": "api-instance {{timestamp}}" }], "provisioners": [ { "type": "shell", "inline": ["mkdir api", "sudo apt-get update", "sudo apt-get -y install npm nodejs-legacy"], "pause_before": "10s" }, { "type": "file", "source" : "../api/", "destination" : "api" }, { "type": "shell", "inline": ["cd api", "npm install"], "pause_before": "10s" } ] }
И вам нужно запустить следующую команду для создания AMI:
packer build -machine-readable packer-ami-api.json
Мы будем запускать эту команду из сборки Jenkins позже в этой статье. Аналогичным образом мы будем использовать файл конфигурации Packer ( terraform/packer-ami-web.json
) и для веб-слоя.
Давайте пройдемся по приведенному выше конфигурационному файлу Packer и поймем, что он пытается сделать.
- Как упоминалось ранее, Packer можно использовать для создания образов для многих платформ, и, поскольку мы развертываем наше приложение на AWS, мы будем использовать сборщик «amazon-ebs», так как с ним проще всего начать работу.
- Вторая часть конфигурации содержит список поставщиков, которые больше похожи на сценарии или блоки кода, которые вы можете использовать для настройки своего образа.
- Шаг 1 запускает средство подготовки оболочки для создания папки API и установки Node.js на образ с помощью
inline
свойства, которое представляет собой набор команд, которые вы хотите запустить. - Шаг 2 запускает средство подготовки файлов, чтобы скопировать наш исходный код из папки API в экземпляр.
- Шаг 3 снова запускает средство подготовки оболочки, но на этот раз использует свойство сценария для указания файла (terraform/scripts/install_api_software.sh) с командами, которые необходимо выполнить.
- Шаг 4 копирует файл конфигурации в экземпляр, необходимый для Cloudwatch, который устанавливается на следующем шаге.
- Шаг 5 запускает средство подготовки оболочки для установки агента AWS Cloudwatch. Входными данными для этой команды будет файл конфигурации, скопированный на предыдущем шаге. Подробнее о Cloudwatch мы поговорим позже в статье.
- Шаг 1 запускает средство подготовки оболочки для создания папки API и установки Node.js на образ с помощью
Таким образом, по сути, конфигурация Packer содержит информацию о том, какой сборщик вам нужен, а затем набор средств обеспечения, которые вы можете определить в любом порядке в зависимости от того, как вы хотите настроить свой образ.
Настройка непрерывного развертывания Jenkins
Далее мы рассмотрим настройку сервера Jenkins, который будет использоваться для нашего конвейера CI/CD. Мы также будем использовать Terraform и AWS для настройки.
Код Terraform для настройки Jenkins находится внутри папки jenkins/setup
. Давайте пройдемся по некоторым интересным вещам, связанным с этой установкой.
- Учетные данные AWS: вы можете предоставить идентификатор ключа доступа AWS и секретный ключ доступа поставщику Terraform AWS (
instance.tf
) или указать расположение файла учетных данных в свойствеshared_credentials_file
в поставщике AWS. - Роль IAM: поскольку мы будем запускать Packer и Terraform с сервера Jenkins, они будут получать доступ к сервисам S3, EC2, RDS, IAM, балансировки нагрузки и автоматического масштабирования на AWS. Поэтому либо мы предоставляем свои учетные данные в Jenkins для Packer & Terraform для доступа к этим службам, либо мы можем создать профиль IAM (
iam.tf
), с помощью которого мы создадим экземпляр Jenkins. - Состояние Terraform: Terraform должен поддерживать состояние инфраструктуры где-то в файле, а с помощью S3 (
backend.tf
) вы можете просто поддерживать его там, чтобы вы могли сотрудничать с другими коллегами, и любой может изменить и развернуть, поскольку состояние поддерживается в удаленном месте. - Пара открытый/закрытый ключ: вам нужно будет загрузить открытый ключ вашей пары ключей вместе с экземпляром, чтобы вы могли подключиться к экземпляру Jenkins по ssh после его запуска. Мы определили ресурс
aws_key_pair
(key.tf
), в котором вы указываете местоположение своего открытого ключа с помощью переменных Terraform.
Шаги по настройке Jenkins:
Шаг 1: Для сохранения удаленного состояния Terraform вам необходимо вручную создать корзину в S3, которую может использовать Terraform. Это будет единственный шаг, сделанный за пределами Terraform. Перед выполнением приведенной ниже команды обязательно запустите AWS configure
, чтобы указать свои учетные данные AWS.
aws s3api create-bucket --bucket node-aws-jenkins-terraform --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-1
Шаг 2: Запустите terraform init
. Это инициализирует состояние и настроит его для хранения на S3, а также загрузит подключаемый модуль поставщика AWS.
Шаг 3: Запустите terraform apply
. Это проверит весь код Terraform, создаст план и покажет, сколько ресурсов будет создано после завершения этого шага.
Шаг 4: Введите yes
, а затем на предыдущем шаге начнется создание всех ресурсов. После завершения команды вы получите общедоступный IP-адрес сервера Jenkins.
Шаг 5: подключитесь к серверу Jenkins по SSH, используя свой закрытый ключ. ubuntu
— это имя пользователя по умолчанию для экземпляров AWS с поддержкой EBS. Используйте IP-адрес, возвращенный командой terraform apply
.
ssh -i mykey [email protected]
Шаг 6. Запустите веб-интерфейс Jenkins, перейдя по http://34.245.4.73:8080
. Пароль можно найти в /var/lib/jenkins/secrets/initialAdminPassword
.
Шаг 7: Выберите «Установить предлагаемые плагины» и создайте пользователя-администратора для Jenkins.
Настройка конвейера CI между Jenkins и Bitbucket
- Для этого нам нужно установить плагин Bitbucket в Jenkins. Перейдите в « Управление Jenkins» → «Управление плагинами» и в разделе « Доступные плагины » установите плагин Bitbucket.
- На стороне репозитория Bitbucket перейдите в «Настройки» → «Веб-хуки » и добавьте новый веб-хук. Этот хук отправит все изменения в репозиторий в Jenkins, и это запустит конвейеры.
Jenkins Pipeline для запекания/сборки изображений
- Следующим шагом будет создание пайплайнов в Jenkins.
- Первым конвейером будет проект Freestyle, который будет использоваться для создания AMI приложения с помощью Packer.
- Вам необходимо указать учетные данные и URL-адрес вашего репозитория Bitbucket.
- Укажите триггер сборки.
- Добавьте два этапа сборки: один для создания AMI для модуля приложения и другие для создания AMI для веб-модуля.
- Как только это будет сделано, вы можете сохранить проект Jenkins, и теперь, когда вы отправляете что-либо в свой репозиторий Bitbucket, он запускает новую сборку в Jenkins, которая создаст AMI и отправит файл Terraform, содержащий номер AMI этого образа, в Ведро S3, которое видно из двух последних строк на этапе сборки.
echo 'variable "WEB_INSTANCE_AMI" { default = "'${AMI_ID_WEB}'" }' > amivar_web.tf aws s3 cp amivar_web.tf s3://node-aws-jenkins-terraform/amivar_web.tf
Конвейер Jenkins для запуска сценария Terraform
Теперь, когда у нас есть образы AMI для API и веб-модулей, мы запустим сборку для запуска кода Terraform для настройки всего приложения, а затем пройдемся по компонентам в коде Terraform, благодаря чему этот конвейер развертывает изменения с нулевым временем простоя службы.

- Мы создаем еще один свободный проект Jenkins,
nodejs-terraform
, который будет запускать код Terraform для развертывания приложения. - Сначала мы создадим учетные данные типа «секретный текст» в глобальном домене учетных данных, которые будут использоваться в качестве входных данных для сценария Terraform. Поскольку мы не хотим жестко кодировать пароль для службы RDS внутри Terraform и Git, мы передаем это свойство, используя учетные данные Jenkins.
- Вам необходимо определить учетные данные и URL-адрес, аналогичные другому проекту.
- В разделе триггера сборки мы свяжем этот проект с другим таким образом, чтобы этот проект запускался после завершения предыдущего.
- Затем мы могли бы настроить учетные данные, которые мы добавили ранее в проект с помощью привязок, чтобы они были доступны на этапе сборки.
- Теперь мы готовы добавить шаг сборки, который загрузит файлы сценариев Terraform (
amivar_api.tf
иamivar_web.tf
), которые были загружены в S3 предыдущим проектом, а затем запустит код Terraform для сборки всего приложения на AWS.
Если все настроено правильно, теперь, если вы отправите какой-либо код в свой репозиторий Bitbucket, он должен запустить первый проект Jenkins, за которым следует второй, и ваше приложение должно быть развернуто на AWS.
Конфигурация Terraform Zero Downtime для AWS
Теперь давайте обсудим, что именно в коде Terraform позволяет этому конвейеру развертывать код с нулевым временем простоя.
Во-первых, Terraform предоставляет эти блоки конфигурации жизненного цикла для ресурсов, в которых у вас есть опция create_before_destroy
в качестве флага, что буквально означает, что Terraform должен создать новый ресурс того же типа перед уничтожением текущего ресурса.
Теперь мы используем эту функцию в aws_autoscaling_group
и aws_launch_configuration
. Таким образом, aws_launch_configuration
настраивает, какой тип инстанса EC2 должен быть предоставлен и как мы устанавливаем программное обеспечение на этот инстанс, а ресурс aws_autoscaling_group
предоставляет группу автоматического масштабирования AWS.
Интересным моментом здесь является то, что все ресурсы в Terraform должны иметь уникальное сочетание имени и типа. Поэтому, если у вас нет другого имени для новой aws_autoscaling_group
и aws_launch_configuration
, невозможно будет уничтожить текущую.
Terraform обрабатывает это ограничение, предоставляя свойство name_prefix
ресурсу aws_launch_configuration
. Как только это свойство будет определено, Terraform добавит уникальный суффикс ко всем ресурсам aws_launch_configuration
, после чего вы сможете использовать это уникальное имя для создания ресурса aws_autoscaling_group
.
Вы можете проверить код всего вышеперечисленного в terraform/autoscaling-api.tf
resource "aws_launch_configuration" "api-launchconfig" { name_prefix = "api-launchconfig-" image_ instance_type = "t2.micro" security_groups = ["${aws_security_group.api-instance.id}"] user_data = "${data.template_file.api-shell-script.rendered}" iam_instance_profile = "${aws_iam_instance_profile.CloudWatchAgentServerRole-instanceprofile.name}" connection { user = "${var.INSTANCE_USERNAME}" private_key = "${file("${var.PATH_TO_PRIVATE_KEY}")}" } lifecycle { create_before_destroy = true } } resource "aws_autoscaling_group" "api-autoscaling" { name = "${aws_launch_configuration.api-launchconfig.name}-asg" vpc_zone_identifier = ["${aws_subnet.main-public-1.id}"] launch_configuration = "${aws_launch_configuration.api-launchconfig.name}" min_size = 2 max_size = 2 health_check_grace_period = 300 health_check_type = "ELB" load_balancers = ["${aws_elb.api-elb.name}"] force_delete = true lifecycle { create_before_destroy = true } tag { key = "Name" value = "api ec2 instance" propagate_at_launch = true } }
И вторая проблема развертывания с нулевым временем простоя — убедиться, что ваше новое развертывание готово к началу получения запроса. В некоторых ситуациях недостаточно просто развернуть и запустить новый экземпляр EC2.
Чтобы решить эту проблему, в aws_launch_configuration
есть свойство user_data
, которое поддерживает родное свойство user_data
AWS, с помощью которого вы можете передать любой скрипт, который хотите запускать при запуске новых экземпляров в составе группы автомасштабирования. В нашем примере мы следим за журналом сервера приложений и ждем сообщения о запуске. Вы также можете проверить HTTP-сервер и посмотреть, когда он работает.
until tail /var/log/syslog | grep 'node ./bin/www' > /dev/null; do sleep 5; done
Наряду с этим вы также можете включить проверку ELB на уровне ресурсов aws_autoscaling_group
, что обеспечит добавление нового экземпляра для прохождения проверки ELB до того, как Terraform уничтожит старые экземпляры. Вот так выглядит проверка ELB для уровня API; он проверяет, чтобы конечная точка /api/status
вернула успех.
resource "aws_elb" "api-elb" { name = "api-elb" subnets = ["${aws_subnet.main-public-1.id}"] security_groups = ["${aws_security_group.elb-securitygroup.id}"] listener { instance_port = "${var.API_PORT}" instance_protocol = "http" lb_port = 80 lb_protocol = "http" } health_check { healthy_threshold = 2 unhealthy_threshold = 2 timeout = 3 target = "HTTP:${var.API_PORT}/api/status" interval = 30 } cross_zone_load_balancing = true connection_draining = true connection_draining_timeout = 400 tags { Name = "my-elb" } }
Резюме и следующие шаги
Итак, это подводит нас к концу этой статьи; надеюсь, к настоящему моменту либо ваше приложение уже развернуто и работает с конвейером CI/CD с нулевым временем простоя, использующим развертывание Jenkins и лучшие практики Terraform, либо вам немного удобнее исследовать эту территорию и сделать так, чтобы ваши развертывания требовали минимального ручного вмешательства, как возможно.
В этой статье используемая стратегия развертывания называется сине-зеленым развертыванием, при котором у нас есть текущая установка (синяя), которая получает живой трафик, пока мы развертываем и тестируем новую версию (зеленая), а затем мы заменяем их, как только новая версия будет готова. все готово. Помимо этой стратегии, существуют и другие способы развертывания вашего приложения, которые подробно описаны в этой статье «Введение в стратегии развертывания». Адаптировать другую стратегию теперь так же просто, как настроить конвейер Jenkins.
Кроме того, в этой статье я предполагал, что все новые изменения в API, веб-уровнях и уровнях данных совместимы, поэтому вам не нужно беспокоиться о том, что новая версия взаимодействует со старой версией. Но на самом деле так может быть не всегда. Чтобы решить эту проблему, при разработке нового релиза/функций всегда думайте об уровне обратной совместимости, иначе вам придется настроить свои развертывания, чтобы справиться с этой ситуацией.
Интеграционное тестирование также отсутствует в этом конвейере развертывания. Поскольку вы не хотите, чтобы что-либо было выпущено для конечного пользователя без тестирования, об этом определенно следует помнить, когда придет время применять эти стратегии к вашим собственным проектам.
Если вам интересно узнать больше о том, как работает Terraform и как вы можете развернуть его в AWS с помощью этой технологии, я рекомендую Terraform AWS Cloud: Sane Infrastructure Management , где коллега Toptaler Радослав Шальски объясняет Terraform, а затем показывает шаги, необходимые для настройки нескольких - среда и готовая к работе установка Terraform для команды