Blue-Green Deployment на минимуми

В тази статия ще bash, ssh, docker и nginx организираме безпроблемно внедряване на уеб приложение. Blue-green deployment е техника, която позволява мигновено актуализиране на приложението, без да се отклонява нито една заявка. Това е една от стратегиите за zero downtime deployment и е най-подходяща за приложения с един инстанс, но с възможност за разполагане на втори, готов за работа инстанс.

Да предположим, че имате уеб приложение, с което активно работят множество клиенти, и то не може да бъде спряно дори за няколко секунди. А вие наистина трябва да внедрите актуализация на библиотеката, да поправите бъг или да добавите нова интересна функция. В обикновената ситуация, ще трябва да спирате приложението, да го замените и след това да го рестартирате. В случай на Docker, можете първо да замените, а след това да рестартирате, но все пак ще има период, в който заявките към приложението няма да бъдат обработени, тъй като обикновено приложението изисква известно време за първоначално зареждане. А ако то се стартира, но се окаже неработоспособно? Ето това е задачата, да я решим с минимални средства и максимална елегантност.

ОСНОВЕН ЗАБЕЛЕЖКА: По-голямата част от статията е представена в експериментален формат — под формата на запис на конзолна сесия. Надявам се, че няма да е много сложно за възприемане и този код сам се документира в достатъчна степен. За атмосфера, представете си, че това не са просто кодови фрагменти, а хартия от „желязна“ телетайп.

Blue-Green Deployment на минимуми

Интересни техники, които трудно можете да намерите само като четете кода, са описани в началото на всяка секция. Ако нещо остане неясно — гуглете и проверявайте в explainshell (слава богу, той отново работи, след отключването на Telegram). Това, което не може да се гугли — питайте в коментарите. С удоволствие ще допълня съответната секция "Интересни техники".

Да започваме.

$ mkdir blue-green-deployment && cd $_

Сервиз

Ще създадем опитен сервис и ще го поставим в контейнер.

Интересни техники

  • cat < file-name (Here Document + I/O Redirection) — начин да създадете многострочен файл с една команда. Всичко, което bash прочете от /dev/stdin след този ред и до реда EOF ще бъде записано в file-name.
  • wget -qO- URL (explainshell) — да изведете полученото документ по HTTP в /dev/stdout (аналог на curl URL).

Разпечатка

Специално разделям сниппета, за да активирам подсветка за Python. В края ще има още един такъв фрагмент. Смятайте, че на тези места хартията е била разрязана, за да се предаде на отдела за хайлайтинг (където кодът е бил оцветяван ръчно с маркери), а след това тези парчета са били залепени обратно.

$ cat < uptimer.py
от http.server импортиране на BaseHTTPRequestHandler, HTTPServer
от time импортиране на monotonic

app_version = 1
app_name = f'Uptimer v{app_version}.0'
loading_seconds = 15 - app_version * 5

клас Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        ако self.path == '\/':
            опитайте:
                t = monotonic() - server_start
                ако t &lt; loading_seconds:
                    self.send_error(503)
                иначе:
                    self.send_response(200)
                    self.send_header(&#039;Content-Type&#039;, &#039;text\/html&#039;)
                    self.end_headers()
                    response = f&#039;<h2>{app_name} работи вече {t:3.1f} секунди.</h2>n'
                    self.wfile.write(response.encode('utf-8'))
            освен:
                self.send_error(500)
        иначе:
            self.send_error(404)

httpd = HTTPServer(('', 8080), Handler)
server_start = monotonic()
print(f'{app_name} (зарежда за {loading_seconds} сек.) стартиране.')
httpd.serve_forever()
EOF

$ cat << EOF > Dockerfile
FROM python:alpine
EXPOSE 8080
COPY uptimer.py app.py
CMD [ "python", "-u", ".\/app.py" ]
EOF

$ docker build --tag uptimer .
Изпращане на контекста на обекта към Docker демона  39.42kB
Стъпка 1/4 : FROM python:alpine
 ---&gt; 8ecf5a48c789
Стъпка 2/4 : EXPOSE 8080
 ---&gt; Използва кеш
 ---&gt; cf92d174c9d3
Стъпка 3/4 : COPY uptimer.py app.py
 ---&gt; a7fbb33d6b7e
Стъпка 4/4 : CMD [ "python", "-u", ".\/app.py" ]
 ---&gt; Изпълнява се в 1906b4bd9fdf
Премахване на междинен контейнер 1906b4bd9fdf
 ---&gt; c1655b996fe8
Успешно построен c1655b996fe8
Успешно етикетиран uptimer:latest

$ docker run --rm --detach --name uptimer --publish 8080:8080 uptimer
8f88c944b8bf78974a5727070a94c76aa0b9bb2b3ecf6324b784e782614b2fbf

$ docker ps
ID НА КОНТЕЙНЕРА        ОБРАЗ               КОМАНДА                СЪЗДАДЕН             СТАТУС              ПОРТОВЕ                    ИМЕНА
8f88c944b8bf        uptimer             "python -u .\/app.py"   преди 3 секунди       Изпълнява се 5 секунди        0.0.0.0:8080-&gt;8080\/tcp   uptimer

$ docker logs uptimer
Uptimer v1.0 (зарежда за 10 сек.) стартирано.

$ wget -qSO- http:\/\/localhost:8080
  HTTP\/1.0 503 Службата е недостъпна
  Сървър: BaseHTTP\/0.6 Python\/3.8.3
  Дата: събота, 22 август 2020 19:52:40 GMT
  Връзка: затвори
  Моля, свържете ни: текст\/html;charset=utf-8
  Дължина на съдържанието: 484

$ wget -qSO- http:\/\/localhost:8080
  HTTP\/1.0 200 ОК
  Сървър: BaseHTTP\/0.6 Python\/3.8.3
  Дата: събота, 22 август 2020 19:52:45 GMT
  Моля, свържете ни: текст\/html
<h2>Uptimer v1.0 работи вече 15.4 секунди.</h2>

$ docker rm --force uptimer
uptimer

Обратен прокси

За да може нашето приложение незабелязано да се смени, е необходимо пред него да има друга същност, която да скрие подмяната му. Това може да бъде уеб сървър. nginx в в режим на обратен прокси. Обратният прокси се поставя между клиента и приложението. Той приема заявки от клиентите и ги пренасочва към приложението, а отговорите на приложението насочва обратно към клиентите.

Приложението и обратния прокси могат да бъдат свързани в Docker чрез docker network. Така контейнерът с приложението дори не е необходимо да пробива порт в хост системата, което позволява максимална изолация на приложението от външни заплахи.

Ако обратния прокси ще работи на друг хост, ще трябва да се откажете от docker network и да свържете приложението с обратния прокси през хост мрежата, пробивайки порт на приложението параметър --publish, както при първоначалния старт и при обратния прокси.

Обратния прокси ще стартираме на порт 80, защото именно това е същността, която трябва да слуша външната среда. Ако 80-ият порт на вашия тестов хост е зает, променете параметъра --publish 80:80 на --publish ANY_FREE_PORT:80.

Интересни техники

Разпечатка

$ docker network create web-gateway
5dba128fb3b255b02ac012ded1906b7b4970b728fb7db3dbbeccc9a77a5dd7bd

$ docker run --detach --rm --name uptimer --network web-gateway uptimer
a1105f1b583dead9415e99864718cc807cc1db1c763870f40ea38bc026e2d67f

$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer:8080
<h2>Uptimer v1.0 работи в продължение на 11.5 секунди.</h2>

$ docker run --detach --publish 80:80 --network web-gateway --name reverse-proxy nginx:alpine
80695a822c19051260c66bf60605dcb4ea66802c754037704968bc42527bf120

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                NAMES
80695a822c19        nginx:alpine        "/docker-entrypoint.…"   27 секунди назад      Up 25 секунди          0.0.0.0:80-&gt;80/tcp   reverse-proxy
a1105f1b583d        uptimer             "python -u ./app.py"     Преди около минута   Up Преди около минута   8080/tcp             uptimer

$ cat << EOF > uptimer.conf
server {
    listen 80;
    location / {
        proxy_pass http://uptimer:8080;
    }
}
EOF

$ docker cp ./uptimer.conf reverse-proxy:/etc/nginx/conf.d/default.conf

$ docker exec reverse-proxy nginx -s reload
2020/06/23 20:51:03 [notice] 31#31: signal process started

$ wget -qSO- http://localhost
  HTTP/1.1 200 OK
  Server: nginx/1.19.0
  Date: Събота, 22 Авг 2020 19:56:24 GMT
  Content-Type: text/html
  Transfer-Encoding: chunked
  Connection: keep-alive
<h2>Uptimer v1.0 работи в продължение на 104.1 секунди.</h2>

Безшевен деплоймент

Изпълняваме нова версия на приложението (с двоен буст на производителността при стартиране) и ще се опитаме да я деплойнем безшевно.

Интересни техники

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' — Запишете текста my text в файла /my-file.txt в контейнера my-container.
  • cat > /my-file.txt — Запишете в файл съдържанието на стандартния вход /dev/stdin.

Разпечатка

$ sed -i "s/app_version = 1/app_version = 2/" uptimer.py

$ docker build --tag uptimer .
Изпращане на контекста на изграждане на Docker демон  39.94kB
Стъпка 1/4 : ОТ python:alpine
 ---&gt; 8ecf5a48c789
Стъпка 2/4 : EXPOSE 8080
 ---&gt; Използване на кеша
 ---&gt; cf92d174c9d3
Стъпка 3/4 : COPY uptimer.py app.py
 ---&gt; 3eca6a51cb2d
Стъпка 4/4 : CMD [ "python", "-u", "../app.py" ]
 ---&gt; Изпълнение в 8f13c6d3d9e7
Премахване на междинен контейнер 8f13c6d3d9e7
 ---&gt; 1d56897841ec
Успешно изграждане 1d56897841ec
Успешно обозначен uptimer:latest

$ docker run --detach --rm --name uptimer_BLUE --network web-gateway uptimer
96932d4ca97a25b1b42d1b5f0ede993b43f95fac3c064262c5c527e16c119e02

$ docker logs uptimer_BLUE
Uptimer v2.0 (зарежда се за 5 сек.) стартира.

$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 работи от 23.9 секунди.</h2>

$ sed s/uptimer/uptimer_BLUE/ uptimer.conf | docker exec --interactive reverse-proxy sh -c 'cat &gt; /etc/nginx/conf.d/default.conf'

$ docker exec reverse-proxy cat /etc/nginx/conf.d/default.conf
сървър {
    слушане 80;
    местоположение / {
        proxy_pass http://uptimer_BLUE:8080;
    }
}

$ docker exec reverse-proxy nginx -s reload
2020/06/25 21:22:23 [notice] 68#68: сигналният процес е стартиран

$ wget -qO- http://localhost
<h2>Uptimer v2.0 работи от 63.4 секунди.</h2>

$ docker rm -f uptimer
uptimer

$ wget -qO- http://localhost
<h2>Uptimer v2.0 работи от 84.8 секунди.</h2>

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                NAMES
96932d4ca97a        uptimer             "python -u ./app.py"     Преди около минута   Работи около минута   8080/tcp             uptimer_BLUE
80695a822c19        nginx:alpine        "docker-entrypoint.…"   8 минути назад        Работи 8 минути        0.0.0.0:80-&gt;80/tcp   reverse-proxy

На този етап образът се билдва директно на сървъра, което изисква наличието на изходния код на приложението там, а също така натоварва сървъра с излишна работа. Следващата стъпка ще бъде отделянето на билднa на образа на отделна машина (например, в CI система) с последваща прехвърляне на сървъра.

Прехвърляне на образи

За съжаление, прехвърлянето на образ от localhost на localhost няма смисъл, така че този раздел може да бъде изпробван само с два хоста с Docker под ръка. Минимално това изглежда по следния начин:

$ ssh production-server docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

$ docker image save uptimer | ssh production-server 'docker image load'
Loaded image: uptimer:latest

$ ssh production-server docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
uptimer             latest              1d56897841ec        5 minutes ago       78.9MB

Екип docker save запазва данните на образа в .tar архив, тоест той е приблизително 1.5 пъти по-голям, отколкото би могъл да бъде в компресиран вид. Нека го компресираме в името на спестяване на време и трафик:

$ docker image save uptimer | gzip | ssh production-server 'zcat | docker image load'
Loaded image: uptimer:latest

И още, можете да наблюдавате процеса на прехвърляне (въпреки че за това е необходима външна утилита):

$ docker image save uptimer | gzip | pv | ssh production-server 'zcat | docker image load'
25,7MiB 0:01:01 [ 425KiB/s] [                       ]
Loaded image: uptimer:latest

Съвет: Ако за свързване със сървъра по SSH са ви нужни много параметри, вероятно не използвате файла ~/.ssh/config.

Прехвърлянето на образа чрез docker image save/load е най-минималистичният метод, но не е единственият. Има и други:

  1. Container Registry (стандарт в индустрията).
  2. Свържете се с Docker демон на сървъра от друг хост:
    1. Променлива на средата DOCKER_HOST.
    2. Параметър на командния ред -H или --host инструмента docker-compose.
    3. docker context

Вторият начин (с три варианта за реализиране) е добре описан в статията How to deploy on remote Docker hosts with docker-compose.

deploy.sh

Сега ще съберем всичко, което правихме ръчно, в един скрипт. Започваме с top-level функция, а след това ще разгледаме и останалите, използвани в нея.

Интересни техники

  • ${parameter?err_msg} е едно от заклинанията на bash-манията (aka parameter substitution). Ако parameter не е зададен, да се изведе err_msg и да се излезе с код 1.
  • docker --log-driver journald по подразбиране, драйверът за логиране на Docker е текстов файл без никаква ротация. С този подход логовете бързо запълват целия диск, така че за производствена среда е необходимо да смените драйвера с по-умен.

Скрипт за внедряване

deploy() {
    local usage_msg="Използване: ${FUNCNAME[0]} image_name"
    local image_name=${1?$usage_msg}

    ensure-reverse-proxy || return 2
    if get-active-slot $image_name
    then
        local OLD=${image_name}_BLUE
        local new_slot=GREEN
    else
        local OLD=${image_name}_GREEN
        local new_slot=BLUE
    fi
    local NEW=${image_name}_${new_slot}
    echo "Деплойваме '$NEW' на мястото на '$OLD'..."
    docker run 
        --detach 
        --restart always 
        --log-driver journald 
        --name $NEW 
        --network web-gateway 
        $image_name || return 3
    echo "Контейнерът стартира. Проверка на здравето..."
    for i in {1..20}
    do
        sleep 1
        if get-service-status $image_name $new_slot
        then
            echo "Новият '$NEW' сервиз изглежда ОК. Превключване на глави..."
            sleep 2  # Убедете се, че сервизът е готов
            set-active-slot $image_name $new_slot || return 4
            echo "'$NEW' сервизът е активен!"
            sleep 2  # Убедете се, че всички заявки са обработени
            echo "Убивам '$OLD'..."
            docker rm -f $OLD
            docker image prune -f
            echo "Деплойването успешно!"
            return 0
        fi
        echo "Новият '$NEW' сервиз все още не е готов. Очакване ($i)..."
    done
    echo "Новият '$NEW' сервиз не се е активирал, убивам го. Деплойването неуспешно T_T"
    docker rm -f $NEW
    return 5
}

Използвани функции:

  • ensure-reverse-proxy — Убедете се, че реверс-прокси работи (полезно за първоначален деплой)
  • get-active-slot service_name — Определя кой слот е активен за зададения сервиз (BLUE или GREEN)
  • get-service-status service_name deployment_slot — Определя дали сервизът е готов да обработва входящи заявки
  • set-active-slot service_name deployment_slot — Променя конфигурацията на nginx в контейнера на реверс-прокси

По ред:

ensure-reverse-proxy() {
    is-container-up reverse-proxy && return 0
    echo "Извършване на разгръщане на reverse-proxy..."
    docker network create web-gateway
    docker run 
        --detach 
        --restart always 
        --log-driver journald 
        --name reverse-proxy 
        --network web-gateway 
        --publish 80:80 
        nginx:alpine || return 1
    docker exec --interactive reverse-proxy sh -c " > /etc/nginx/conf.d/default.conf"
    docker exec reverse-proxy nginx -s reload
}

is-container-up() {
    local container=${1?"Употреба: ${FUNCNAME[0]} container_name"}

    [ -n "$(docker ps -f name=${container} -q)" ]
    return $?
}

get-active-slot() {
    local service=${1?"Употреба: ${FUNCNAME[0]} service_name"}

    if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
        echo "Обнаружен конфликт! Спирка ${service}_GREEN..."
        docker rm -f ${service}_GREEN
        return 0  # BLUE
    fi
    if is-container-up ${service}_BLUE && ! is-container-up ${service}_GREEN; then
        return 0  # BLUE
    fi
    if ! is-container-up ${service}_BLUE; then
        return 1  # GREEN
    fi
}

get-service-status() {
    local usage_msg="Употреба: ${FUNCNAME[0]} service_name deployment_slot"
    local service=${1?usage_msg}
    local slot=${2?$usage_msg}

    case $service in
        # Добавете конкретни пътища за проверка на здравето на вашите услуги тук
        *) local health_check_port_path=":8080/" ;;
    esac
    local health_check_address="http://${service}_${slot}${health_check_port_path}"
    echo "Изпращане на заявка до '$health_check_address' в мрежата на docker 'web-gateway':"
    docker run --rm --network web-gateway alpine 
        wget --timeout=1 --quiet --server-response $health_check_address
    return $?
}

set-active-slot() {
    local usage_msg="Употреба: ${FUNCNAME[0]} service_name deployment_slot"
    local service=${1?$usage_msg}
    local slot=${2?$usage_msg}
    [ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1

    get-nginx-config $service $slot | docker exec --interactive reverse-proxy sh -c "cat > /etc/nginx/conf.d/$service.conf"
    docker exec reverse-proxy nginx -t || return 2
    docker exec reverse-proxy nginx -s reload
}

Функция get-active-slot изисква малко пояснения:

Защо тя връща число, а не извежда стринг?

Въпреки това, в извикващата функция проверяваме резултата от нейното изпълнение, а проверката на exit code с bash е много по-лесна от стринг. Освен това, получаването на стринг от нея е много просто:
get-active-slot service && echo BLUE || echo GREEN.

А трите условия са достатъчни, за да разграничат всички състояния ли?

Blue-Green Deployment на минимуми

Дори две са достатъчни, последното просто е за пълнота, за да не се пише. иначе.

Остана неопределена само функцията, която връща конфигурации за nginx: get-nginx-config service_name deployment_slot. Подобно на здравната проверка, тук може да се зададе всяка конфигурация за всяка услуга. От интересното — само cat <<- EOF, което позволява да премахнем всички табулации в началото. Обаче цената на добрата форматиране е смесени табулации с интервали, което днес се счита за много лошо. Но bash принуждава табулации, а в конфигурацията на nginx също би било добре да имаме нормално форматиране. Коротко казано, тук смесването на табулации с интервали изглежда наистина най-доброто решение от най-лошите. Въпреки това, в снипета по-долу няма да го видите, тъй като хабр „прави добре“, сменяйки всички табулации на 4 интервала и правейки EOF невалиден. А ето тук е забележимо.

За да не повтарям два пъти, веднага ще разкажа за cat << 'EOF', който ще се срещне по-късно. Ако просто напишете cat << EOF, то вътре в heredoc се извършва интерполация на стринга (разкриват се променливи ($foo), команди ($(bar)) и т.н.), а ако заключите признака за край на документа в единични кавички, интерполацията се изключва и символът $ се извежда както е. Това, което е необходимо за вмъкване на скрипт вътре в друг скрипт.

get-nginx-config() {
    local usage_msg="Използване: ${FUNCNAME[0]} име_на_услугата слот_на_разгръщане"
    local service=${1?$usage_msg}
    local slot=${2?$usage_msg}
    [ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1

    local container_name=${service}_${slot}
    case $service in
        # Добавете специфични nginx конфигурации за вашите услуги тук
        *) nginx-config-simple-service $container_name:8080 ;;
    esac
}

nginx-config-simple-service() {
    local usage_msg="Използване: ${FUNCNAME[0]} proxy_pass"
    local proxy_pass=${1?$usage_msg}

cat << EOF
server {
    listen 80;
    location / {
        proxy_pass http://$proxy_pass;
    }
}
EOF
}

Това е целият скрипт. И ето гист с този скрипт за сваляне чрез wget или curl.

Изпълнение на параметризирани скриптове на отдалечен сървър

Време е да се свържете с целевия сървър. Този път localhost съвсем подхожда:

$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: опитва се да влезе с новия ключ(ове), за да филтрира всякакви, които вече са инсталирани
/usr/bin/ssh-copy-id: INFO: 1 ключ(ове) остава да бъдат инсталирани -- ако сега получите подканване, то е за инсталиране на новите ключове
himura@localhost's password: 

Брой на добавените ключ(ове): 1

Сега опитайте да влезете в машината с: "ssh 'localhost'"
и проверете дали само ключ(овете), които искате, са добавени.

Написахме скрипт за деплой, който прехвърля предварително събрания образ на целевия сървър и безпроблемно замества контейнера на услугата, но как да го изпълним на отдалечен компютър? Скриптът има аргументи, тъй като е универсален и може да деплойва няколко услуги под един реверс-прокси (с конфигурациите на nginx може да се определи по кой url коя ще бъде услугата). Скриптът не може да се съхранява на сървъра, тъй като в този случай няма да можем да го актуализираме автоматично (за поправки на бъгове и добавяне на нови услуги), а и вообще, съхранението = зло.

Решение 1: Така да се съхранява скриптът на сървъра, но да се копира всеки път чрез scp. След това да се свържем по ssh и да изпълним скрипта с необходимите аргументи.

Недостатъци:

  • Две действия вместо едно
  • Местата, където копирате, може да нямат наличност, или да нямате достъп до тях, или скриптът може да се изпълнява в момент на замяна.
  • Препоръчително е да се прибра (изтриете скрипта).
  • Вече три действия.

Решение 2:

  • В скрипта да се държат само определения на функции и да не се изпълнява нищо
  • С помощта на sed да се добави в края повикване на функцията
  • Да се изпрати всичко това директно в shh чрез pipe (|)

Плюсове:

  • Наистина бездържавен
  • Без шаблонни единици
  • Изглежда готино

Но давай без Ansible. Да, всичко вече е измислено. Да, велосипед. Вижте, колко прост, елегантен и минималистичен велосипед:

$ cat < deploy.sh
#!/bin/bash

usage_msg="Usage: $0 ssh_address local_image_tag"
ssh_address=${1?$usage_msg}
image_name=${2?$usage_msg}

echo "Connecting to '$ssh_address' via ssh to seamlessly deploy '$image_name'..."
( sed "$a deploy $image_name" | ssh -T $ssh_address ) << 'END_OF_SCRIPT'
deploy() {
    echo "Yay! The '${FUNCNAME[0]}' function is executing on '$(hostname)' with argument '$1'"
}
END_OF_SCRIPT
EOF

$ chmod +x deploy.sh

$ ./deploy.sh localhost magic-porridge-pot
Свързване с localhost...
Ура! Функцията 'deploy' се изпълнява на 'hut' с аргумент 'magic-porridge-pot'

Въпреки това, не можем да бъдем сигурни, че на отдалечения хост има адекватен bash, така че да добавим в началото малка проверка (това вместо shellbang):

if [ "$SHELL" != "/bin/bash" ]
then
    echo "Шелът '$SHELL' не се поддържа от 'deploy.sh'. Задайте 'bin/bash' шел за '$USER@$HOSTNAME'."
    exit 1
fi

А сега всичко наистина:

$ docker exec reverse-proxy rm /etc/nginx/conf.d/default.conf

$ wget -qO deploy.sh https://git.io/JUURc

$ chmod +x deploy.sh

$ ./deploy.sh localhost uptimer
Изпращане на компресиран имидж 'uptimer' до 'localhost' чрез ssh...
Зареден имидж: uptimer:latest
Свързване с 'localhost' чрез ssh за безшевно внедряване на 'uptimer'...
Внедряване на 'uptimer_GREEN' вместо 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Контейнерът стартира. Проверка на здравето...
Изискване на 'http://uptimer_GREEN:8080/' в docker мрежата 'web-gateway':
  HTTP/1.0 503 Службата не е налична
wget: сървърът върна грешка: HTTP/1.0 503 Службата не е налична
Новата услуга 'uptimer_GREEN' все още не е готова. Изчакване (1)...
Изискване на 'http://uptimer_GREEN:8080/' в docker мрежата 'web-gateway':
  HTTP/1.0 503 Службата не е налична
wget: сървърът върна грешка: HTTP/1.0 503 Службата не е налична
Новата услуга 'uptimer_GREEN' все още не е готова. Изчакване (2)...
Изискване на 'http://uptimer_GREEN:8080/' в docker мрежата 'web-gateway':
  HTTP/1.0 200 ОК
  Сървър: BaseHTTP/0.6 Python/3.8.3
  Дата: съб, 22 авг 2020 20:15:50 GMT
  Тип на съдържанието: text/html

Новата услуга 'uptimer_GREEN' изглежда ОК. Превключване на главите...
nginx: конфигурационният файл /etc/nginx/nginx.conf синтаксиса е ок
nginx: тестът на конфигурационния файл /etc/nginx/nginx.conf е успешен
2020/08/22 20:15:54 [notice] 97#97: сигналният процес стартира
Услугата 'uptimer_GREEN' е активна!
Убиване на 'uptimer_BLUE'...
uptimer_BLUE
Общо възстановено пространство: 0B
Успешно внедряване!

Сега можете да отворите http://localhost/ в браузъра, да стартирате внедряването отново и да се уверите, че то преминава безшевно, като обновите страницата по време на разгръщането.

Не забравяйте да се почистите след работа :3

$ docker rm -f uptimer_GREEN reverse-proxy 
uptimer_GREEN
reverse-proxy

$ docker network rm web-gateway 
web-gateway

$ cd ..

$ rm -r blue-green-deployment

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster