Достаточно…. Скажем так — интересная схема билда и деплоя одного приложения.
Приложение включает в себя 6 контейнеров (5 — сервисы самого приложения, и один контейнер — Zuul discovery service).
Сама идея и архитектура — красивая и достаточно сложная. Но использовать такое для билда и деплоя 5 контейнеров…
Overhead, overengineering. Ещё один антипаттерн того, как надо делать.
Эта же схема отлично реализуется с помощью Maven для билда образов Docker и AWS CloudFormation + AWS ECS для развёртывания сервисов приложения.
Тем нее менее — в одном из проектов архитектура такая есть, о ней и пост.
К этому же посту относится запись AWS: AWS CLI и bash – blue/green деплой AutoScale группы за ELB — в ней описан сам деплой новых AMI в AutoScale группу.
Ниже — подробно сам процесс билда и обновления приложения.
В билде и деплое участвуют:
- AWS: как IaaS провайдер;
- Docker: для контейнеров с сервсисами;
- Maven: сборка Java-кода и Docker-образов;
- JFrog Artifactiory: private Docker registry для хранения собранных образов;
- Packer: сборка AMI;
- Terraform: обновление AWS-конфигурации;
bash, AWS CLI: деплой AutoScale групп.
Содержание
Архитектура
Немного об архитектуре.
В Amazon Web Services имеется три рабочих окружения — Development, Staging и собственно Production (ещё не запущен).
Каждое рабочее окружение включает в себя VPC с четырьмя подсетями (две публичные и две приватные), по два Load Balancer-a (LB, 1 на приложение, 1 на eureka — discovery service), и по 4 AutoScale группы (ASG): Blue и Green для API (само приложение), и аналогично для eureka-сервиса.
В каждую такую ASG входит 3 EC2 инстанса — два m3.medium для API за одним LB, и 1 t2.micro для eureka-сервиса за собственным LB:
К этому всему идёт по 8 security групп, NAT-инстансы (NAT-gateway не было видимо, когда эту схему начинали делать) и прочее по мелочи.
Сборка проекта
Процесс билда выглядит следующим образом:
- Maven
- сборка
jar-архивов с приложением (1 архив на один сервис); docker-maven-plugin— сборка Docker-образов с включенными jar-никами (1 образ на 1 сервис);docker-maven-plugin— push образов в JFrog Artifactory.
- сборка
- Packer
- Packer собирает новый AMI с предустановленным Docker, в который включает обранные Maven-ом образы.
- Terraform
- Terraform проверяет имеющуюся конфигурацию окружения;
- во время проверки — он обнаруживает новый AMI ID, и создаёт новый Launch Config для AutoScale группы.
- Деплой (
bash, AWS CLI)- в Blue ASG группу добавляет новый интанс из последнего AMI (используя Launch Config, обновлённый Terraform-ом);
- Blue ASG подклчюается к Elastic Load Balancer (ELB);
- Green ASG отключается от ELB, трафик передаётся на инстанс из Blue группы;
- В Green группе убиваются инстансы;
- ASG создаёт новые интансы в Green группе, используя последние Launch Config;
- Green группа подключается к ELB;
- Blue группа отключается;
- инстансы Blue группы останавливаются/уничтожаются.
Вся сборка осуществляется Jenkins-ом через Docker Pipeline Plugin (при этом сам Jenkins запущен в контейнере, и для билдов ноды запускает в Docker-контейнерах в Docker-контейнере, тоже как-то опишу развёртывание такого окружения для билдов).
Maven
Репозиторий выглядит следующим образом:
[simterm]
$ ls -l total 40 drwxr-xr-x 3 setevoy setevoy 4096 Jan 4 11:36 authserver drwxr-xr-x 3 setevoy setevoy 4096 Jan 4 11:36 eureka drwxr-xr-x 3 setevoy setevoy 4096 Jan 4 11:36 gateway -rw-r--r-- 1 setevoy setevoy 543 Jan 4 11:36 maven-settings.xml -rwxr-xr-x 1 setevoy setevoy 1390 Jan 4 11:36 pom.xml drwxr-xr-x 3 setevoy setevoy 4096 Jan 4 11:36 producers drwxr-xr-x 3 setevoy setevoy 4096 Jan 4 11:36 profile -rw-r--r-- 1 setevoy setevoy 2664 Jan 4 11:36 README.md -rw-r--r-- 1 setevoy setevoy 577 Jan 4 11:33 sonar-project.properties drwxr-xr-x 3 setevoy setevoy 4096 Jan 4 11:36 tag-api-configuration
[/simterm]
Собственно — authserver, eureka, gateway, producers, profile и tag-api-configuration — сервисы, которые будут собраны в Docker-образы.
Maven build
Сборка — стандартная, через pom.xml:
[simterm]
$ cat pom.xml | grep -A 5 module
<modules>
<module>authserver</module>
<module>eureka</module>
<module>gateway</module>
<module>producers</module>
<module>profile</module>
<module>tag-api-configuration</module>
</modules>
[/simterm]
Вызывается Maven в Jenkins-е из Groovy-скрипта.
Билд-скрипты разбиты на две части. Один скрипт — общий для всех билдов, в котором описаны функции (build.groovy). И вторая часть — зависящая от окружения: она вызывает функцию из основного билд-скрипта и передаёт ей параметры конкретного окружения:
[simterm]
$ ls -l ../../tag-deployment/api/ total 44 -rw-r--r-- 1 setevoy setevoy 2311 Dec 26 16:38 build.dev.groovy -rw-r--r-- 1 setevoy setevoy 7319 Feb 17 17:59 build.groovy -rw-r--r-- 1 setevoy setevoy 4563 Dec 26 16:38 build.production.groovy -rw-r--r-- 1 setevoy setevoy 2105 Dec 26 16:38 build.staging.groovy -rw-r--r-- 1 setevoy setevoy 994 Dec 26 16:38 cn-north-1-dev.groovy -rw-r--r-- 1 setevoy setevoy 1133 Dec 26 16:38 eu-west-1-dev.groovy -rw-r--r-- 1 setevoy setevoy 1207 Feb 20 12:43 eu-west-1-production.groovy -rw-r--r-- 1 setevoy setevoy 1207 Feb 20 12:19 eu-west-1-staging.groovy -rw-r--r-- 1 setevoy setevoy 1524 Dec 26 16:38 health-check.groovy
[/simterm]
Например, функция билда Maven в основном скрипте (build.groovy) выглядит так:
#!/usr/bin/env groovy
def maven() {
stage('Maven package') {
docker.image('maven:3.3.3-jdk-8').inside('-v /var/run/docker.sock:/var/run/docker.sock -v /maven:/root/.m2/') {
git branch: "${BRANCH}", credentialsId: 'git', url: "${REPO_API}"
sh "echo ${BRANCH}"
sh 'git branch'
sh 'mvn clean install -DskipDockerBuild'
sh "mvn verify sonar:sonar -s./maven-settings.xml -DskipDockerBuild -Dsonar.host.url=${env.SONAR_HOST} -Dsonar.projectName=\"Server API\" -Dsonar.projectKey=api"
withCredentials([[
$class: 'UsernamePasswordMultiBinding', credentialsId: 'docker',
passwordVariable: 'DOCKER_REGISTRY_PASSWORD', usernameVariable: 'DOCKER_REGISTRY_USERNAME']]) {
sh "mvn clean package -e -X \
-s./maven-settings.xml \
-Ddocker.registry.id=${DOCKER_REGISTRY_ID} \
-Ddocker.registry.host=${DOCKER_REGISTRY_HOST} \
-Ddocker.registry.url=${DOCKER_REGISTRY_URL} \
-Ddocker.registry.username=${DOCKER_REGISTRY_USERNAME} \
-Ddocker.registry.password=${DOCKER_REGISTRY_PASSWORD} \
-Ddocker.image.tag=\"${DOCKER_IMAGE_TAG}\" \
-DpushImage"
}
}
}
}
...
А её вызов в скрипте eu-west-1-staging.groovy — так:
#!/usr/bin/env groovy
node {
withEnv([
'ENVIRONMENT=staging',
'AWS_REGION=eu-west-1',
'BASE_AMI=ami-285e0b5b',
'REPO_API=https://bitbucket.company.net/scm/lontag/tag-server-api.git',
'REPO_INFRA=https://bitbucket.company.net/scm/lontag/tag-server-api-infrastructure.git',
'DOCKER_REGISTRY_ID=companyengineering-tag-docker.jfrog.io',
'DOCKER_REGISTRY_URL=https://companyengineering-tag-docker.jfrog.io/',
'DOCKER_REGISTRY_HOST=companyengineering-tag-docker.jfrog.io/',
"DOCKER_IMAGE_TAG=staging-${BRANCH}-${env.BUILD_NUMBER}",
'VPC_CIDR=10.5.0.0/16',
'VPC_SUBNET_PUBLIC_1=10.5.0.0/24',
'VPC_SUBNET_PUBLIC_2=10.5.1.0/24',
'VPC_SUBNET_PRIVATE_1=10.5.2.0/24',
'VPC_SUBNET_PRIVATE_2=10.5.3.0/24',
'SSH_KEY_NAME=tag-ci-key-pair',
'MIN_SIZE_GREEN=2'
]) {
git branch: "develop", credentialsId: 'git', url: "https://bitbucket.company.net/scm/lontag/tag-deployment.git"
def build = load 'api/build.groovy'
build.maven()
...
Результатами билда являются jar-архивы, например:
[simterm]
ubuntu@ip-10-0-2-254:/jenkins/workspace/EU-api-staging-build$ ls -l authserver/target/ | grep jar -rw-r--r-- 1 root root 24579486 Feb 20 08:26 oauth2-authserver-0.0.1-SNAPSHOT.jar -rw-r--r-- 1 root root 77105 Feb 20 08:26 oauth2-authserver-0.0.1-SNAPSHOT.jar.original
[/simterm]
Maven Docker
Следующим шагом — Maven собирает Docker-образы и пушит их в Artifactory.
Для сборки из Maven-а — используется плагин docker-maven-plugin.
Docker-сборка вызывается из той же функции maven() в build.groovy:
...
withCredentials([[
$class: 'UsernamePasswordMultiBinding', credentialsId: 'docker',
passwordVariable: 'DOCKER_REGISTRY_PASSWORD', usernameVariable: 'DOCKER_REGISTRY_USERNAME']]) {
sh "mvn clean package -e -X \
-s./maven-settings.xml \
-Ddocker.registry.id=${DOCKER_REGISTRY_ID} \
-Ddocker.registry.host=${DOCKER_REGISTRY_HOST} \
-Ddocker.registry.url=${DOCKER_REGISTRY_URL} \
-Ddocker.registry.username=${DOCKER_REGISTRY_USERNAME} \
-Ddocker.registry.password=${DOCKER_REGISTRY_PASSWORD} \
-Ddocker.image.tag=\"${DOCKER_IMAGE_TAG}\" \
-DpushImage"
...
А сам билд и вызов docker-maven-plugin описан в pom.xml модуля, например — сервис authserver и его файл authserver/pom.xml:
...
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.10</version>
<configuration>
<imageName>companyengineering-tag-docker.jfrog.io/${docker.image.project}-${project.artifactId}:${docker.image.tag}</imageName>
<dockerDirectory>${project.basedir}/src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
<serverId>company-artifactory</serverId>
<registryUrl>https://companyengineering-tag-docker.jfrog.io/</registryUrl>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
...
Dockerfile (параметр <dockerDirectory>${project.basedir}/src/main/docker</dockerDirectory>):
[simterm]
$ cat authserver/src/main/docker/Dockerfile FROM java:7 VOLUME /tmp ADD oauth2-authserver-0.0.1-SNAPSHOT.jar app.jar RUN bash -c 'touch /app.jar' ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
[/simterm]
Сборка и пуш Docker-образа в Jenkins-е выглядят так:
...
[INFO] Copying /jenkins/workspace/EU-api-dev-build/authserver/target/oauth2-authserver-0.0.1-SNAPSHOT.jar -> /jenkins/workspace/EU-api-dev-build/authserver/target/docker/oauth2-authserver-0.0.1-SNAPSHOT.jar
[INFO] Copying /jenkins/workspace/EU-api-dev-build/authserver/src/main/docker/Dockerfile -> /jenkins/workspace/EU-api-dev-build/authserver/target/docker/Dockerfile
[INFO] Building image companyengineering-tag-docker.jfrog.io/tag-oauth2-authserver:dev-develop-219
[DEBUG] Auth Config AuthConfig{username=****, password=****, [email protected], serverAddress=https://companyengineering-tag-docker.jfrog.io/}
[DEBUG] Registry Config Json {"https://companyengineering-tag-docker.jfrog.io/":{"serveraddress":"https://companyengineering-tag-docker.jfrog.io/","password":"****","auth":"","email":"[email protected]","username":"****"}}
[DEBUG] Registry Config Encoded eyJo***ifX0=
Step 1/5 : FROM openjdk:8-jdk-alpine
---> e40ba8c51bb2
Step 2/5 : VOLUME /tmp
---> Using cache
---> dca0ede529f8
Step 3/5 : ADD oauth2-authserver-0.0.1-SNAPSHOT.jar app.jar
---> 77f2dd1cdb86
Removing intermediate container f23d830f4995
Step 4/5 : RUN sh -c 'touch /app.jar'
---> Running in 5266ab468f85
---> b265dc7a0667
Removing intermediate container 5266ab468f85
Step 5/5 : ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -jar /app.jar
---> Running in bec5d43fa72e
---> b0133e38b21e
Removing intermediate container bec5d43fa72e
Successfully built b0133e38b21e
[INFO] Built companyengineering-tag-docker.jfrog.io/tag-oauth2-authserver:dev-develop-219
[INFO] Pushing companyengineering-tag-docker.jfrog.io/tag-oauth2-authserver:dev-develop-219
The push refers to a repository [companyengineering-tag-docker.jfrog.io/tag-oauth2-authserver]
f689adf70a7d: Preparing
7a2359d813a4: Preparing
bef6fa0c97dd: Preparing
da07d9b32b00: Preparing
7cbcbac42c44: Preparing
...
Собранным Docker-образам устанавливается тег в виде "DOCKER_IMAGE_TAG=envname-${BRANCH}-${env.BUILD_NUMBER}" (скрипт eu-west-1-staging.groovy).
Переменная $BRANCH «приходит» из Jenkins, где с помощью этого параметра можно заменить бранч для билда при старте сборки:
Docker-образы сохраняются в JFrog Artifactory:
Packer
Packer выполняет сборку новых AMI, используя которые позже будут запущены новые EC2-инстансы с новой версией приложения.
Вызывается он так же из билд-скрипта с параметрами для конкретного окружения:
...
stage('Packer API AMI') {
sh "packer build -force \
-var 'version=${env.BUILD_NUMBER}' \
-var 'access_key=${AWS_ACCESS_KEY_ID}' \
-var 'secret_key=${AWS_SECRET_ACCESS_KEY}' \
-var 'region=${AWS_REGION}' \
-var 'source_ami=${BASE_AMI}' \
-var 'ssh_username=ubuntu' \
-var 'docker_registry=${DOCKER_REGISTRY_ID}' \
-var 'profile_image=tag-profile:${DOCKER_IMAGE_TAG}' \
-var 'configuration_image=tag-configuration:${DOCKER_IMAGE_TAG}' \
-var 'gateway_image=tag-api-gateway:${DOCKER_IMAGE_TAG}' \
-var 'producers_image=tag-producers:${DOCKER_IMAGE_TAG}' \
-var 'auth_image=tag-oauth2-authserver:${DOCKER_IMAGE_TAG}' \
./packer/api/template.json"
}
...
Параметры из билд-скрипта передаются самому Packer-у, которые он далее использует в шаблоне packer/api/template.json:
...
"builders": [{
"type": "amazon-ebs",
"access_key": "{{user `access_key`}}",
"secret_key": "{{user `secret_key`}}",
"region": "{{user `region`}}",
"source_ami": "{{user `source_ami`}}",
"instance_type": "t2.medium",
"ssh_username": "{{user `ssh_username`}}",
"ami_name": "tag-api-{{user `version`}}",
"tags": {
"Name": "TAG API",
"Version": "{{user `version`}}"
}
}],
"provisioners": [{
"type": "shell",
"scripts": [
"{{template_dir}}/app.sh"
],
"environment_vars": [
"DOCKER_REGISTRY={{user `docker_registry`}}",
"PROFILE_IMAGE={{user `profile_image`}}",
"CONFIGURATION_IMAGE={{user `configuration_image`}}",
"GATEWAY_IMAGE={{user `gateway_image`}}",
"PRODUCERS_IMAGE={{user `producers_image`}}",
"AUTH_IMAGE={{user `auth_image`}}"
]
},
...
Он же включает скрипт для загрузки образов в собираемый AMI:
...
"scripts": [
"{{template_dir}}/app.sh"
],
...
Сам скрипт:
[simterm]
$ cat packer/api/app.sh #!/bin/sh -x sleep 30 docker pull $DOCKER_REGISTRY/$PROFILE_IMAGE docker pull $DOCKER_REGISTRY/$CONFIGURATION_IMAGE docker pull $DOCKER_REGISTRY/$GATEWAY_IMAGE docker pull $DOCKER_REGISTRY/$PRODUCERS_IMAGE docker pull $DOCKER_REGISTRY/$AUTH_IMAGE
[/simterm]
Для сборки — packer запускает новую EC2-машину из $BASE_AMI (BASE_AMI=ami-285e0b5b в скрипте eu-west-1-staging.groovy), выполняет docker pull (app.sh выше), загружает новые образы из Artifactory, собирает новый образ машины, который включает в себя эти Docker-образы, и сохраняет новые AMI в AWS аккаунте.
Билд в Jenkins выглядит так:
... [apiAmi] [1;32m==> amazon-ebs: Creating the AMI: tag-api-245[0m [apiAmi] [0;32m amazon-ebs: AMI: ami-4b66442d[0m [apiAmi] [1;32m==> amazon-ebs: Waiting for AMI to become ready...[0m [apiAmi] [1;32m==> amazon-ebs: Adding tags to AMI (ami-4b66442d)...[0m [apiAmi] [1;32m==> amazon-ebs: Tagging snapshot: snap-073e2b53b623975ce[0m [apiAmi] [1;32m==> amazon-ebs: Creating AMI tags[0m [apiAmi] [1;32m==> amazon-ebs: Creating snapshot tags[0m [apiAmi] [1;32m==> amazon-ebs: Terminating the source AWS instance...[0m [apiAmi] [1;32m==> amazon-ebs: Cleaning up any extra volumes...[0m [apiAmi] [1;32m==> amazon-ebs: No volumes to clean up, skipping[0m [apiAmi] [1;32m==> amazon-ebs: Deleting temporary security group...[0m [apiAmi] [1;32m==> amazon-ebs: Deleting temporary keypair...[0m [apiAmi] [1;32mBuild 'amazon-ebs' finished.[0m [apiAmi] [apiAmi] ==> Builds finished. The artifacts of successful builds are: [apiAmi] --> amazon-ebs: AMIs were created: [apiAmi] [apiAmi] eu-west-1: ami-4b66442d [Pipeline] [apiAmi] } [Pipeline] [apiAmi] // stage [Pipeline] [apiAmi] } [Pipeline] // parallel [Pipeline] } [Pipeline] // withCredentials [Pipeline] } ...
Terrafrom
Сама интересная часть, если не считать деплоя.
Terrafrom проверяет текущую инфраструктуру, сравнивая её со state-файлами, которые хранятся в S3-корзине:
...
stage('Terraform') {
docker.image('hashicorp/terraform:light').inside('-v /var/run/docker.sock:/var/run/docker.sock') {
git branch: "master", credentialsId: 'git', url: "${REPO_INFRA}"
withCredentials([[
$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID',
credentialsId: 'aws_terraform', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
sh "cd terraform && terraform remote config \
-backend=s3 \
-backend-config=\"bucket=tag-api-eu-terraform-state-${ENVIRONMENT}\" \
-backend-config=\"access_key=${AWS_ACCESS_KEY_ID}\" \
-backend-config=\"secret_key=${AWS_SECRET_ACCESS_KEY}\" \
-backend-config=\"key=api/terraform.tfstate\" \
-backend-config=\"region=${AWS_REGION}\" \
-backend-config=\"encrypt=true\""
...
Для деплоя — terraform обновляет Launch config.
Для этого он проверяет текущее состояние конфигурации, и находит несоответствие между имеющимися AMI ID в launch-конфгигах Blue и Green групп, и AMI ID, имеющимися в аккаунте (параметр most_recent = true):
...
data "aws_ami" "api_green" {
most_recent = true
owners = ["${lookup(var.ami_owners, var.region)}"]
filter {
name = "name"
values = ["tag-api*"]
}
}
data "aws_ami" "api_blue" {
most_recent = true
owners = ["${lookup(var.ami_owners, var.region)}"]
filter {
name = "name"
values = ["tag-api*"]
}
}
...
Видя это несоответсвие — Terrafrom создаёт новые Launch-конфиги, и переключает AutoScale группы на них.
В Jenkis-е это выглядит так:
... [0m[1mmodule.eurkea_autoscaling_group_green.aws_autoscaling_group.asg: Modifying...[0m launch_configuration: "staging-api-eureka-green-lc-00b2859f1512b5d8ea100c2004" => "staging-api-eureka-green-lc-00556cd434eb881b48aa7aa95a"[0m ...
Кроме того — Terraform добавляет userdata-скрипт, который запускает сервисы при старте EC2:
...
data "template_file" "api_userdata" {
template = "${file("./files/userdata/api.sh")}"
vars {
DOCKER_REGISTRY = "${lookup(var.docker_registry, var.region)}"
DOCKER_IMAGE_OAUTH = "${lookup(var.docker_image_oauth, var.region)}"
DOCKER_IMAGE_PROFILE = "${lookup(var.docker_image_profile, var.region)}"
DOCKER_IMAGE_CONFIGURATION = "${lookup(var.docker_image_configuration, var.region)}"
DOCKER_IMAGE_PRODUCERS = "${lookup(var.docker_image_producers, var.region)}"
DOCKER_IMAGE_GATEWAY = "${lookup(var.docker_image_gateway, var.region)}"
DOCKER_IMAGE_TAG = "${var.docker_image_tag}"
SPRING_PROFILE = "${var.spring_profile}"
}
}
...
Сам файл files/userdata/api.sh:
[simterm]
$ cat files/userdata/api.sh
#!/bin/bash -v
docker login -u username -p password companyengineering-tag-docker.jfrog.io
docker run \
-d \
-p 9999:9999 \
-e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} \
--restart unless-stopped \
${DOCKER_REGISTRY}/${DOCKER_IMAGE_OAUTH}:${DOCKER_IMAGE_TAG}
docker run \
-d \
-p 8083:8083 \
-e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} \
--restart unless-stopped \
${DOCKER_REGISTRY}/${DOCKER_IMAGE_PROFILE}:${DOCKER_IMAGE_TAG}
docker run \
-d \
-p 8084:8084 \
-e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} \
--restart unless-stopped \
${DOCKER_REGISTRY}/${DOCKER_IMAGE_CONFIGURATION}:${DOCKER_IMAGE_TAG}
docker run \
-d \
-p 8090:8090 \
-e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} \
--restart unless-stopped \
${DOCKER_REGISTRY}/${DOCKER_IMAGE_PRODUCERS}:${DOCKER_IMAGE_TAG}
docker run \
-d \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=${SPRING_PROFILE} \
--restart unless-stopped \
${DOCKER_REGISTRY}/${DOCKER_IMAGE_GATEWAY}:${DOCKER_IMAGE_TAG}
[/simterm]
Собственно, на этом — билд заканчивается.
Последним шагом — запускается bash-скрипт, который уже выполняет непосредственно деплой и описан в посте AWS: AWS CLI и bash – blue/green деплой AutoScale группы за ELB.
Очень сопротивляюсь тому, что бы выводить эту схему в production, настаивая на ECS, но — видимо придётся.
Jenkins verify step
Реализация шага «Vefiry» в Jenkins.
В билд-скрипте функция выглядит так:
...
def deploy_prod_verify() {
stage 'Verify'
input id: 'Deploy', message: 'Is Blue node fine? Proceed with Green node deployment?', ok: 'Deploy!'
}
...
А в билде — выглядит это так:









