AWS: билд Java + Maven + Docker + Packer + Terraform

Автор: | 20/02/2017
 

Достаточно…. Скажем так – интересная схема билда и деплоя одного приложения.

Приложение включает в себя 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 не было видимо, когда эту схему начинали делать) и прочее по мелочи.

Сборка проекта

Процесс билда выглядит следующим образом:

  1. Maven
    1. сборка jar-архивов с приложением (1 архив на один сервис);
    2. docker-maven-plugin – сборка Docker-образов с включенными jar-никами (1 образ на 1 сервис);
    3. docker-maven-plugin – push образов в JFrog Artifactory.
  2. Packer
    1. Packer собирает новый AMI с предустановленным Docker, в который включает обранные Maven-ом образы.
  3. Terraform
    1. Terraform проверяет имеющуюся конфигурацию окружения;
    2. во время проверки – он обнаруживает новый AMI ID, и создаёт новый Launch Config для AutoScale группы.
  4. Деплой (bash, AWS CLI)
    1. в Blue ASG группу добавляет новый интанс из последнего AMI (используя Launch Config, обновлённый Terraform-ом);
    2. Blue ASG подклчюается к Elastic Load Balancer (ELB);
    3. Green ASG отключается от ELB, трафик передаётся на инстанс из Blue группы;
    4. В Green группе убиваются инстансы;
    5. ASG создаёт новые интансы в Green группе, используя последние Launch Config;
    6. Green группа подключается к ELB;
    7. Blue группа отключается;
    8. инстансы 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] ==> amazon-ebs: Creating the AMI: tag-api-245
[apiAmi]     amazon-ebs: AMI: ami-4b66442d
[apiAmi] ==> amazon-ebs: Waiting for AMI to become ready...
[apiAmi] ==> amazon-ebs: Adding tags to AMI (ami-4b66442d)...
[apiAmi] ==> amazon-ebs: Tagging snapshot: snap-073e2b53b623975ce
[apiAmi] ==> amazon-ebs: Creating AMI tags
[apiAmi] ==> amazon-ebs: Creating snapshot tags
[apiAmi] ==> amazon-ebs: Terminating the source AWS instance...
[apiAmi] ==> amazon-ebs: Cleaning up any extra volumes...
[apiAmi] ==> amazon-ebs: No volumes to clean up, skipping
[apiAmi] ==> amazon-ebs: Deleting temporary security group...
[apiAmi] ==> amazon-ebs: Deleting temporary keypair...
[apiAmi] Build 'amazon-ebs' finished.
[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-е это выглядит так:

...
module.eurkea_autoscaling_group_green.aws_autoscaling_group.asg: Modifying...
  launch_configuration: "staging-api-eureka-green-lc-00b2859f1512b5d8ea100c2004" => "staging-api-eureka-green-lc-00556cd434eb881b48aa7aa95a"
...

Кроме того – 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!'

}
...

А в билде – выглядит это так: