AWS: AWS CLI и bash – blue/green деплой AutoScale группы за ELB

By | 01/06/2017
 

Имеется достаточно интересная ифраструктура одного проекта (UPD: описана в посте AWS: билд Java + Maven + Docker + Packer + Terraform), в котором деплой API-приложения выполняется скриптом, описанным ниже.

Его задача – запустить EC2 инстансы, объединённые в blue AutoScale группу, подключить её к Elastic Load Balancer-у (ELB), отключить от ELB green-группу, перезапустить в ней инстансы, подключить её обратно к ELB и отключить blue-группу.

Сам скрипт, как решение задачи, является скорее антипаттерном того, как надо делать – куда логичнее тут смотрелся бы Terraform, который используется во время деплоя для обновления launch-configuration AutoScale группы, с новыми AMI ID, которые являются результатом билда с новой версией приложения. Инстансы, запускаемые скриптом используют этот новый launch-configuration, и запускаются с новым приложением. Скрипт запускается groovy-скриптом из docker-контейнера в Jenkins, который сам работает в docker-контейнере.

Ещё лучше было бы мигрировать весь проект на AWS ECS, но выбор между Groovy, Terraform или bash + AWS CLI обуславливался временем, а ECS отложен на будущие фазы проекта, т.к. его (пока) нет в Китае, с которым предстоит работать.

Вообще про blue/green деплой AutoScale групп можно почитать интересные документы вот тут>>> и тут>>>.

Тем не менее – сам по себе скрипт выглядит любопытно и содержит много реальных примеров работы с AWS CLI.

В скрипте используются 10 основных глобальных переменных, из которых 5 в “боевой” версии передаются в докер-контейнер Jenkins-ом опциями командной строки, заданных в билде:

#!/usr/bin/env bash

#set -xe

###################################################
### See comments in main() block at the bottom. ###
###################################################

# $1 - for getopts() option
AWS_ACCESS_KEY_ID=$2
AWS_SECRET_ACCESS_KEY=$3
AWS_REGION=$4
ENVIRONMENT=$5
MIN_SIZE_GREEN=$6

#export AWS_DEFAULT_PROFILE=PROJECTNAME
#export AWS_DEFAULT_REGION=eu-west-1

#AWS_ACCESS_KEY_ID=
#AWS_SECRET_ACCESS_KEY=
#AWS_REGION=eu-west-1

BLUE_ASG="$ENVIRONMENT-api-blue-asg"
GREEN_ASG="$ENVIRONMENT-api-green-asg"
ELB="$ENVIRONMENT-api-elb"
SCALE_UP_RULE="$ENVIRONMENT-api-scale-up"
SCALE_DOWN_RULE="$ENVIRONMENT-api-scale-down"

export PATH=$PATH:"/home/aws/aws/env/bin/"
export AWS_DEFAULT_REGION=$AWS_REGION
...

Скрипт умеет принимать 3 опции, которые обрабатываются с помощью getopts():

...
HELP="Specify one of the following options: -d - simple deploy, -b - Production Blue deploy, -g - Production Green deploy"

get_params () {

    if [[ -z $* ]];then
        echo -e "\n$HELP\n"
        exit 1
    fi

    deploy=
    deploy_blue=
    deploy_green=

    while getopts "dbgh" opt; do
        case $opt in
            d)
                deploy=1
                ;;
            b)
                deploy_blue=1
                ;;
            g)
                deploy_green=1
                ;;
            h)
                echo "$HELP"
                ;;
        esac
    done
}
...
  • -dsimple deploy: выполняет Production Blue deploy и Production Green deploy, один за другим. Используется для Dev-деплоя, где не требуется подтверждение между переключениями в ELB blue и green групп.
  • -bProduction Blue deploy: устанавливает переменную deploy_blue == 1, в результате чего вызывается функция deploy_blue().
  • -gProduction Green deploy: аналогично – устанавливает переменную deploy_green == 1, в результате чего вызывается функция deploy_green().

Далее в скрипте описываются различные вспомогательные функции, которые вызывают AWS CLI для выполнения действий, а замыкающими описываются три основных функции – deploy_blue(), deploy_green(), и последняя – main(), которые последовательно вызывают все функции, описанные в скрипте выше.

Выполнение скрипта начинается в самом его низу, с вызова функций get_params() и main():

...
# check for -d (simple deploy), -b (deploy_blue()) or -g (deploy_green()) option first
get_params $@

# execute main() which will call appropriate function depending on variables specified by get_params()
main && echo -e "\nDeployment finished successfully.\n"
...

get_params() принимает переданные Jenkins-ом опцию ($@ – все аргументы, переданные скрипту), и глобально задаёт одну из трёх переменных (надо было сделать через аргумент в main()), в зависимости от чего в main() выполняется одна из трёх последовательностей:

...
    [[ $deploy ]] && { deploy_blue  && deploy_green; echo -e "Result code: $?\n"; }
    [[ $deploy_blue ]] && { deploy_blue; echo -e "Result code: $?\n"; }
    [[ $deploy_green ]] && { deploy_green; echo -e "Result code: $?\n"; }
...

Деплой blue-группы

main()

Полностью функция main() с кратким описанием процесса всего процесса билда и деплоя приложения выглядит так:

...
main () {

    # Build and deploy workflow
    #
    # 1 Maven
    #   1.1 Maven build jar archives with an application.
    #   1.2 Maven builds Docker images 1 per service with those jar-files included.
    #   1.3 Maven push those images to the Artifactory Private Docker registry.
    # 2 Packer
    #   2.1 Packer takes $base_ami ID and creates new EC2 instance.
    #   2.2 Installs Docker there and pulls Docker images with the latest code built by Maven.
    #   2.3 Builds new AMI with those images included.
    # 3 Terraform
    #   3.1 Terraform checks all its configs and current infrastructure state plus TF's state-files.
    #   3.2 Regarding to "most_recent = true" in api.tf - TF will see differences between current EC2 instances AMI ID 
    #       and ones found as "latest" in AWS account's AMIs list, as there are new AMIs created by Packer.
    #   3.4 Terraform updates Launch Configs (LC) for AutoScaling groups (ASG) with new AMI IDs.
    # 4 Deploy
    #   4.1 Add new EC2 instance to Blue AutoScale group.
    #   4.2 Attach Blue ASG to the Elastic Load Balancer (ELB).
    #   4.3 Detach Green ASG - traffic will be served by the Blue Group's EC2 instance, started with latest AMI ID.
    #   4.4 Terminate Green ASG instances.
    #   4.5 AutoScale will create an appropriate number of new instances.
    #   4.6 Attach Green ASG to the ELB.
    #   4.7 Detach Blue ASG.
    #   4.8 Terminate Blue ASG instances.

    [[ $deploy ]] && { deploy_blue  && deploy_green; echo -e "Result code: $?\n"; }
    [[ $deploy_blue ]] && { deploy_blue; echo -e "Result code: $?\n"; }
    [[ $deploy_green ]] && { deploy_green; echo -e "Result code: $?\n"; }

}
...

После чего вызывается сначала функция deploy_blue(), которая обновляет EC2-инстансы в AutoScale blue-группе, а за ней deploy_green(), которая обновляет инстансы в green-группе.

Собственно функция deploy_blue() выглядит так:

...
deploy_blue () {

    if blue_asg_status $BLUE_ASG; then

        # add 1 instance in Blue group wich will start with latest builded AMI
        echo -e "\n[BLUE] Blue ASG is empty, executing ScaleUp policy...\n"
        asg_scale_up $BLUE_ASG && echo -e "Scaled up to 1 instance.\n" || exit 1

        # to prevent new instances creation during deployment
        echo -e "[BLUE] Blue ASG updating MAX value to 1...\n"
        asg_set_max_size $BLUE_ASG 1 && echo -e "Done.\n" || exit 1

        # wait when Blue EC2 will start
        echo -e "[BLUE] Checking Blue ASG health...\n"
        asg_health $BLUE_ASG || exit 1

        # to prevent new instances creation during deployment
        echo -e "[GREEN] Green ASG updating MIN value to 1...\n"
        asg_set_min_size $GREEN_ASG 1 && echo -e "Done.\n" || exit 1

        # to prevent new instances creation during deployment
        echo -e "[GREEN] Green ASG updating MAX value to 1...\n"
        asg_set_max_size $GREEN_ASG 1 && echo -e "Done.\n" || exit 1

        # add Blue EC2 under ELB's traffic control
        echo -e "[BLUE] Attaching Blue to ELB...\n"
        asg_attach $BLUE_ASG && echo -e "Blue ASG attached.\n" || exit 1

        # to avoid instance termitation during it's "Green" role
        echo -e "[BLUE] Setting ScaleIn protection...\n"
        asg_set_protect $BLUE_ASG && echo -e "Done\n" || exit 1

        # sleep before check ELB - intanse will be added not immediately
        echo -e "[BLUE] Checking ELB intastances health for Blue instance Up...\n"
        sleep 30
        # ELB health checks :8080/health
        # Thus - it will check untill Gateway service will not start
        elb_health $BLUE_ASG || exit 1

        echo -e "\n[GREEN] Detaching Green from ELB.\n"
        asg_detach $GREEN_ASG && echo -e "Done.\n" || exit 1

        # sleep before check ELB - Green intanse will be removed from ELB not immediately
        # ask Verify after
##        sleep 30

        echo -e "Blue ASG deploy finished successfully.\n"

    else
        echo -e "ERROR - all instances in Blue ASG must be terminated and Desired value == 0. Exit.\n"
        exit 1
    fi

}
...

blue_asg_status()

Ход выполнения deploy_blue() достаточно простой, и начинается с проверки результата выполнения функции blue_asg_status(), которой аргументом передаётся имя AutoScale группы со значением dev, stage или prod, в зависимости от переменой $ENVIRONMENT, заданной из аргументов Jenkins-а:

...
BLUE_ASG="$ENVIRONMENT-api-blue-asg"
...

Вызов blue_asg_status():

...
    if blue_asg_status $BLUE_ASG; then
...

Задача функции blue_asg_status() – проверить, не имеется ли запущенных инстансов в blue-группе (которая после деплоя остаётся пустой).

Выглядит она так:

...
blue_asg_status () {

    local asg_name=$1
    local instances_running=$(get_instances_running $asg_name)

    if [[ ! -z $instances_running ]]; then
        echo -e "\nThere is running instances in the Blue ASG: $instances_running\n"
        return 1
    fi
}
...

Если переменная $instances_running не равна нулю – то билд останавливается с ненулевым результатом выполнения. Значение $instances_running зависит от результата выполнения функции $get_instances_running(), которая вызывает AWS CLI, подключется к AWS-аккаунту, и проверяет количество запущенных инстансов, а результат передаёт обратно в blue_asg_status():

...
get_instances_running () {

    local asg_name=$1
    local instances_running=$(aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $asg_name --query '[AutoScalingGroups[*].Instances[*].InstanceId]' --output text)
    echo $instances_running
}
...

Если же $instances_running == 0, то deploy_blue() продолжает выполнение, и вызывает следующую функцию – asg_scale_up():

...
        # add 1 instance in Blue group wich will start with latest builded AMI
        echo -e "\n[BLUE] Blue ASG is empty, executing ScaleUp policy...\n"
        asg_scale_up $BLUE_ASG && echo -e "Scaled up to 1 instance.\n" || exit 1
...

Собственно ход выполнения main() и deploy_blue() в output Jenkins-а выглядит так:

...
[EU-api-staging-build] Running shell script
+ ./scripts/api_eu_deploy.sh -b **** **** eu-west-1 staging 2

[BLUE] Blue ASG is empty, executing ScaleUp policy...
...

Функция asg_scale_up() применяет к blue AutoScale группе политику масштабирования из переменной $SCALE_UP_RULE:

...
SCALE_UP_RULE="$ENVIRONMENT-api-scale-up"
...

Правило с именем $ENVIRONMENT-api-scale-up создаётся, обновляется или не изменяется Terraform-ом во время деплоя.

Политика $ENVIRONMENT-api-scale-up увеличивает количество инстансов в blue-группе (которая “приходит” в деплой пустой) на число, зависящее от окружения (dev – 1, staging, prod – 2):

...
asg_scale_up () {

    local asg_name=$1
    aws autoscaling execute-policy --auto-scaling-group-name $asg_name --policy-name $SCALE_UP_RULE
}
...

asg_set_max_size()

Следующей вызывается asg_set_max_size():

...
       # to prevent new instances creation during deployment
        echo -e "[BLUE] Blue ASG updating MAX value to 1...\n"
        asg_set_max_size $BLUE_ASG 1 && echo -e "Done.\n" || exit 1
...

Сама функция:

...
asg_set_max_size () {

    local asg_name=$1
    local max_size=$2
    aws autoscaling update-auto-scaling-group --auto-scaling-group-name $asg_name --max-size $max_size
}
...

Она задаёт масксимальное количество EC2-инстансов в blue группе равным единице, после чего AWS AutoScale запускает новую машину из последнего AMI с приложением. Так как это blue-группа – то для экономии времени в ней достаточно одной машины.

asg_health()

После этого – deploy_blue() вызывает функцию asg_health():

...
        # wait when Blue EC2 will start
        echo -e "[BLUE] Checking Blue ASG health...\n"
        asg_health $BLUE_ASG || exit 1
...

Задача её – дождаться запуска машины (статус == InService) за $max * $timeout секунд, после чего выполнение цикла прерывается оператором break, и управление выполнения возвращается к deploy_blue() с кодом 0:

...
asg_health () {

    local at=0
    local max=10
    local timeout=10
    local asg_name=$1

    while [ $at -lt $max ]; do
        local health=$(aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $asg_name --query [AutoScalingGroups[*].Instances[*].LifecycleState] --output text)
        if [[ $health == "InService" ]]; then
            echo -e "\n\n$asg_name instance(s) OK, ready to attach to ELB: $health\n"
            break
        else
            echo "($at/$max $timeout sec) $asg_name instance(s) not ready yet: $health"
        fi
        ((at++))
        sleep $timeout
    done
}
...

Выполнение asg_scale_up(), asg_set_max_size() и asg_health() выглядит так:

...
Scaled up to 1 instance.

[BLUE] Blue ASG updating MAX value to 1...

Done.

[BLUE] Checking Blue ASG health...

(0/10 10 sec) staging-api-blue-asg instance(s) not ready yet: 
(1/10 10 sec) staging-api-blue-asg instance(s) not ready yet: 
(2/10 10 sec) staging-api-blue-asg instance(s) not ready yet: Pending
(3/10 10 sec) staging-api-blue-asg instance(s) not ready yet: Pending
(4/10 10 sec) staging-api-blue-asg instance(s) not ready yet: Pending


staging-api-blue-asg instance(s) OK, ready to attach to ELB: InService
...

asg_set_min_size()

Далее вызывается asg_set_min_size():

...
        # to prevent new instances creation during deployment
        echo -e "[GREEN] Green ASG updating MIN value to 1...\n"
        asg_set_min_size $GREEN_ASG 1 && echo -e "Done.\n" || exit 1
...

Она уменьшает текущее количество инстасов в green-группе до 1 (во избежаение проблем с таймаутами в циклах далее):

...
asg_set_min_size () {

    local asg_name=$1
    local min_size=$2
    aws autoscaling update-auto-scaling-group --auto-scaling-group-name $asg_name --min-size $min_size
}
...

asg_set_max_size()

Аналогично вызывается функция asg_set_max_size(), но для значения Max instances number для green-группы:

...
asg_set_max_size () {

    local asg_name=$1
    local max_size=$2
    aws autoscaling update-auto-scaling-group --auto-scaling-group-name $asg_name --max-size $max_size
}
...

asg_attach()

Вызов:

...
        # add Blue EC2 under ELB's traffic control
        echo -e "[BLUE] Attaching Blue to ELB...\n"
        asg_attach $BLUE_ASG && echo -e "Blue ASG attached.\n" || exit 1
...

asg_attach() подключает blue-группу с инстансом к Elastic Load Balancer-у, который начинает перенаправлять трафик на новую машину:

...
asg_attach () {

    local asg_name=$1
    aws autoscaling attach-load-balancers --auto-scaling-group-name $asg_name --load-balancer-names $ELB
}
...

asg_set_protect()

После подключения blue-группы – можно отключать green, но сначала – лучше установить Scale in Protection для инстанса в blue-группе, что выполняется с помощью asg_set_protect():

...
        # to avoid instance termitation during it's "Green" role
        echo -e "[BLUE] Setting ScaleIn protection...\n"
        asg_set_protect $BLUE_ASG && echo -e "Done\n" || exit 1
...

Тело функции:

...
asg_set_protect () {

    local asg_name=$1
    local instance_to_protect=$(get_instances_running $asg_name)

    aws autoscaling set-instance-protection --instance-ids $instance_to_protect --protected-from-scale-in --auto-scaling-group-name $asg_name 
}
...

Она получает Instance ID из функции get_instances_running()

...
get_instances_running () {

    local asg_name=$1
    local instances_running=$(aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $asg_name --query '[AutoScalingGroups[*].Instances[*].InstanceId]' --output text)
    echo $instances_running
}
...

elb_health()

Вызов:

...
        # sleep before check ELB - intanse will be added not immediately
        echo -e "[BLUE] Checking ELB intastances health for Blue instance Up...\n"
        sleep 30
        # ELB health checks :8080/health
        # Thus - it will check untill Gateway service will not start
        elb_health $BLUE_ASG || exit 1
...

Упоминал о циклах и таймаутах чуть выше, пример такой функции (зачем два раза [ $at -lt $max ]? только сейчас заметил):

...
elb_health () {

    local out_state="OutOfService"
    local at=0
    local max=30
    local timeout=30

    while [ $at -lt $max ]; do
        local elb_health=$(aws elb describe-instance-health --load-balancer-name $ELB --query '[InstanceStates[*].State]' --output text)
        if [[ $elb_health =~ $out_state ]]; then
            echo "($at/$max $timeout sec) Some intances still $elb_health"
        else
            echo -e "\n\nIntance up and running: $elb_health"
            break
        fi
        ((at++))
        sleep $timeout
        if [ $at == $max ]; then
            echo "\nERROR: max attempts reached.\n"
            exit 1
        fi
    done

}
...

Так как только для иницилизации приложения на Java Spring Framework уходит порядка двух минут – то билд замирает в ожидании, пока в ELB не посчитает, что только что подключенный blue-инстанс уже готов обрабатывать запросы. Такое решение ELB принимает на основании результатов health-проверок к инстансу на порт 8080, после чего статус инстанса сменится с OutOfService на InService.

asg_detach()

Теперь ELB готов к отключению green-группы, что бы начать перенаправлять весь трафик на машину из blue-группы.

Отключение выполняется вызовом asg_detach():

...

        echo -e "\n[GREEN] Detaching Green from ELB.\n"
        asg_detach $GREEN_ASG && echo -e "Done.\n" || exit 1
...

В результатах билда asg_set_min_size(), asg_set_max_size(), asg_attach(), asg_set_protect(), elb_health() и asg_detach() выглядят так:

...
[GREEN] Green ASG updating MIN value to 1...

Done.

[GREEN] Green ASG updating MAX value to 1...

Done.

[BLUE] Attaching Blue to ELB...

Blue ASG attached.

[BLUE] Setting ScaleIn protection...

Done

[BLUE] Checking ELB intastances health for Blue instance Up...

(0/30 30 sec) Some intances still InService	OutOfService
...
(14/30 30 sec) Some intances still InService	OutOfService

Intance up and running: InService	InService

[GREEN] Detaching Green from ELB.

Done.

Blue ASG deploy finished successfully.

Result code: 0
...

Следующим шагом main() после подтверждения вызывает deploy_green().

Vefiry step для Jenkins реализован в groovy-скрипте:

...
def deploy_prod_verify() {

    stage 'Verify'
    input id: 'Deploy', message: 'Is Blue node fine? Proceed with Green node deployment?', ok: 'Deploy!'

}
...

И выглядит так:

Деплой green-группы

deploy_green()

deploy_green() аналогична debloy_blue() и использует те же функции плюс 2 дополнительных:

...
deploy_green () {

    # terminate Gren's instance
    echo -e "[GREEN] Updating Green ASG instances.\n"
    green_asg_terminate $GREEN_ASG

    echo -e "\n[GREEN] Waiting for Green ASG instance termination...\n"
    slee?p 30

    # new instances must be started here by ASG
    echo -e "[GREEN] Checking Green ASG health.\n"
    asg_health $GREEN_ASG || exit 1

    # add Green to ELB
    echo -e "[GREEN] Attaching Green to ELB.\n"
    asg_attach $GREEN_ASG && echo -e "Green ASG attached\n" || exit 1

    # wait for the Gateway service UP state (/health)
    echo -e "[GREEN] Checking ELB intastances health for Green instance Up...\n"
    sleep 30
    elb_health $GREEN_ASG || exit 1

    # detach Blue - Green now is Green
    echo -e "\n[BLUE] Detaching Blue...\n"
    asg_detach $BLUE_ASG && echo -e "Done.\n" || exit 1

    # remove protection before ScaleIn rule will be executed
    echo -e "[BLUE] Removing ScaleIn protection...\n"
    asg_remove_protect $BLUE_ASG && echo -e "Done\n" || exit 1

    # ScaleIn rule will be executed 🙂
    echo -e "[BLUE] Scaling Blue in...\n"
    asg_scale_down $BLUE_ASG && echo -e "Scale in to 0 instance - done.\n" || { echo "ERROR: can't execute ScaleDown policy. Exit."; exit 1; }

    # set Green ASG Max instances back to 8
    echo -e "[GREEN] Green ASG restoring MAX value to 8...\n"
    asg_set_max_size $GREEN_ASG 8 && echo -e "Done.\n" || exit 1

    # set Green ASG Min instances back to ${MIN_SIZE_GREEN} from eu-west-ENV.groovy
    echo -e "[GREEN] Green ASG restoring MIN value to $MIN_SIZE_GREEN...\n"
    asg_set_min_size $GREEN_ASG $MIN_SIZE_GREEN && echo -e "Done.\n" || exit 1

    echo -e "Green ASG deploy finished successfully.\n"

}
...

green_asg_terminate()

Вызов:

...
deploy_green () {

    # terminate Gren's instance
    echo -e "[GREEN] Updating Green ASG instances.\n"
    green_asg_terminate $GREEN_ASG
...

Она начинает своё выполнение с вызова green_asg_terminate(), задача которой – остановить инстансы green-группы со старым кодом, на место которых потом будут добавлены новые, из свежесобранных AMI:

...
green_asg_terminate () {

    local asg_name=$1
    local instances_running=$(get_instances_running $asg_name)

    instance_terminate "$instances_running"
    echo -e "\nChecking $instances_running state...\n"
    # be sure instance already terminated before proceed
    instance_state "$instances_running"
}
...

get_instances_running() уже упоминалась в deploy_blue(), а instance_terminate() выглядит так:

...
instance_terminate () {

    local instances_running=$1

    echo -e "Terminating instances $instances_running..."
    for instance in $instances_running; do
        echo -e "\nStopping instance $instance...\n"
        aws ec2 terminate-instances --instance-ids $instance || exit 1
    done

}
...

После того как старые инстансы были остановлены – AWS AutoScale увеличиет их количество, с новым кодом приложения.

Следующие функции уже упоминались:

...

    echo -e "\n[GREEN] Waiting for Green ASG instance termination...\n"
    sleep 30

    # new instances must be started here by ASG
    echo -e "[GREEN] Checking Green ASG health.\n"
    asg_health $GREEN_ASG || exit 1

    # add Green to ELB
    echo -e "[GREEN] Attaching Green to ELB.\n"
    asg_attach $GREEN_ASG && echo -e "Green ASG attached\n" || exit 1

    # wait for the Gateway service UP state (/health)
    echo -e "[GREEN] Checking ELB intastances health for Green instance Up...\n"
    sleep 30
    elb_health $GREEN_ASG || exit 1

    # detach Blue - Green now is Green
    echo -e "\n[BLUE] Detaching Blue...\n"
    asg_detach $BLUE_ASG && echo -e "Done.\n" || exit 1
...

asg_health() ожидает состояния всех инстансов в green-группе == InService, asg_attach() подключает её к ELB, elb_health() ожидает ответа от всех инстансов в ELB на запросы к порту 8080 а asg_detach() отключает blue-группу.

Результат в Jenkins:

...
[GREEN] Updating Green ASG instances.

Terminating instances i-064dbc31791a0ad2c...

Stopping instance i-064dbc31791a0ad2c...

{
    "TerminatingInstances": [
        {
            "InstanceId": "i-064dbc31791a0ad2c", 
            "CurrentState": {
                "Code": 32, 
                "Name": "shutting-down"
            }, 
            "PreviousState": {
                "Code": 16, 
                "Name": "running"
            }
        }
    ]
}

Checking i-064dbc31791a0ad2c state...

(0/10 20 sec) Instance i-064dbc31791a0ad2c still running...
(1/10 20 sec) Instance i-064dbc31791a0ad2c still running...
(2/10 20 sec) Instance i-064dbc31791a0ad2c still running...

Instance i-064dbc31791a0ad2c stopped.

[GREEN] Waiting for Green ASG instance termination...

[GREEN] Checking Green ASG health.

(0/10 10 sec) staging-api-green-asg instance(s) not ready yet: Pending
(1/10 10 sec) staging-api-green-asg instance(s) not ready yet: Pending
(2/10 10 sec) staging-api-green-asg instance(s) not ready yet: Pending

staging-api-green-asg instance(s) OK, ready to attach to ELB: InService

[GREEN] Attaching Green to ELB.

Green ASG attached

[GREEN] Checking ELB intastances health for Green instance Up...

(0/30 30 sec) Some intances still OutOfService	InService
...
(15/30 30 sec) Some intances still OutOfService	InService

Intance up and running: InService	InService

[BLUE] Detaching Blue...

Done.
...

asg_remove_protect()

...
    # remove protection before ScaleIn rule will be executed
    echo -e "[BLUE] Removing ScaleIn protection...\n"
    asg_remove_protect $BLUE_ASG && echo -e "Done\n" || exit 1
...

asg_remove_protect() снимает ScaleIn защиту с инстанса в blue-группе для её “зачистки” перед завершением деплоя:

...
asg_remove_protect () {

    local asg_name=$1
    local instance_to_protect=$(get_instances_running $asg_name)

    aws autoscaling set-instance-protection --instance-ids $instance_to_protect --no-protected-from-scale-in --auto-scaling-group-name $asg_name 
}
...

asg_scale_down()

Далее blue-группа уменьшается до 0 машин:

...
    # ScaleIn rule will be executed 
    echo -e "[BLUE] Scaling Blue in...\n"
    asg_scale_down $BLUE_ASG && echo -e "Scale in to 0 instance - done.\n" || { echo "ERROR: can't execute ScaleDown policy. Exit."; exit 1; }
...

asg_set_max_size() и asg_set_min_size() уже были – восстанавливаются исходные данные green-группы для Max intances до 8, а Min – до 2:

...
    # set Green ASG Max instances back to 8
    echo -e "[GREEN] Green ASG restoring MAX value to 8...\n"
    asg_set_max_size $GREEN_ASG 8 && echo -e "Done.\n" || exit 1

    # set Green ASG Min instances back to ${MIN_SIZE_GREEN} from eu-west-ENV.groovy
    echo -e "[GREEN] Green ASG restoring MIN value to $MIN_SIZE_GREEN...\n"
    asg_set_min_size $GREEN_ASG $MIN_SIZE_GREEN && echo -e "Done.\n" || exit 1

    echo -e "Green ASG deploy finished successfully.\n"

}
...

В Jenkins это выглядит так:

...
[BLUE] Removing ScaleIn protection...

Done

[BLUE] Scaling Blue in...

Scale in to 0 instance - done.

[GREEN] Green ASG restoring MAX value to 8...

Done.

[GREEN] Green ASG restoring MIN value to 2...

Done.

Green ASG deploy finished successfully.

Result code: 0

[Pipeline] }
[Pipeline] // withCredentials
[Pipeline] }
$ docker stop --time=1 e59c0fac136f8222e9b83da116931a4956cb9f4a80428b5844fdb271557de179
$ docker rm -f e59c0fac136f8222e9b83da116931a4956cb9f4a80428b5844fdb271557de179
[Pipeline] // withDockerContainer
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

Как-то так…