В тази статия ще , , и организираме безпроблемно внедряване на уеб приложение. е техника, която позволява мигновено актуализиране на приложението, без да се отклонява нито една заявка. Това е една от стратегиите за zero downtime deployment и е най-подходяща за приложения с един инстанс, но с възможност за разполагане на втори, готов за работа инстанс.
Да предположим, че имате уеб приложение, с което активно работят множество клиенти, и то не може да бъде спряно дори за няколко секунди. А вие наистина трябва да внедрите актуализация на библиотеката, да поправите бъг или да добавите нова интересна функция. В обикновената ситуация, ще трябва да спирате приложението, да го замените и след това да го рестартирате. В случай на Docker, можете първо да замените, а след това да рестартирате, но все пак ще има период, в който заявките към приложението няма да бъдат обработени, тъй като обикновено приложението изисква известно време за първоначално зареждане. А ако то се стартира, но се окаже неработоспособно? Ето това е задачата, да я решим с минимални средства и максимална елегантност.
ОСНОВЕН ЗАБЕЛЕЖКА: По-голямата част от статията е представена в експериментален формат — под формата на запис на конзолна сесия. Надявам се, че няма да е много сложно за възприемане и този код сам се документира в достатъчна степен. За атмосфера, представете си, че това не са просто кодови фрагменти, а хартия от „желязна“ телетайп.
Интересни техники, които трудно можете да намерите само като четете кода, са описани в началото на всяка секция. Ако нещо остане неясно — гуглете и проверявайте в (слава богу, той отново работи, след отключването на Telegram). Това, което не може да се гугли — питайте в коментарите. С удоволствие ще допълня съответната секция "Интересни техники".
Да започваме.
$ mkdir blue-green-deployment && cd $_Сервиз
Ще създадем опитен сервис и ще го поставим в контейнер.
Интересни техники
cat < file-name( + ) — начин да създадете многострочен файл с една команда. Всичко, което bash прочете от/dev/stdinслед този ред и до редаEOFще бъде записано вfile-name.wget -qO- URL() — да изведете полученото документ по 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 < loading_seconds:
self.send_error(503)
иначе:
self.send_response(200)
self.send_header('Content-Type', 'text\/html')
self.end_headers()
response = f'<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
---> 8ecf5a48c789
Стъпка 2/4 : EXPOSE 8080
---> Използва кеш
---> cf92d174c9d3
Стъпка 3/4 : COPY uptimer.py app.py
---> a7fbb33d6b7e
Стъпка 4/4 : CMD [ "python", "-u", ".\/app.py" ]
---> Изпълнява се в 1906b4bd9fdf
Премахване на междинен контейнер 1906b4bd9fdf
---> 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->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Обратен прокси
За да може нашето приложение незабелязано да се смени, е необходимо пред него да има друга същност, която да скрие подмяната му. Това може да бъде уеб сървър. в . Обратният прокси се поставя между клиента и приложението. Той приема заявки от клиентите и ги пренасочва към приложението, а отговорите на приложението насочва обратно към клиентите.
Приложението и обратния прокси могат да бъдат свързани в Docker чрез . Така контейнерът с приложението дори не е необходимо да пробива порт в хост системата, което позволява максимална изолация на приложението от външни заплахи.
Ако обратния прокси ще работи на друг хост, ще трябва да се откажете от docker network и да свържете приложението с обратния прокси през хост мрежата, пробивайки порт на приложението параметър --publish, както при първоначалния старт и при обратния прокси.
Обратния прокси ще стартираме на порт 80, защото именно това е същността, която трябва да слуша външната среда. Ако 80-ият порт на вашия тестов хост е зает, променете параметъра --publish 80:80 на --publish ANY_FREE_PORT:80.
Интересни техники
- «В docker мрежите, създадени от потребителя, с контейнерите може да се свързва не само по IP адрес. Името на контейнера също се резолвира в неговия IP адрес» (, пункт 5 от docker кодекса).
Разпечатка
$ 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->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
---> 8ecf5a48c789
Стъпка 2/4 : EXPOSE 8080
---> Използване на кеша
---> cf92d174c9d3
Стъпка 3/4 : COPY uptimer.py app.py
---> 3eca6a51cb2d
Стъпка 4/4 : CMD [ "python", "-u", "../app.py" ]
---> Изпълнение в 8f13c6d3d9e7
Премахване на междинен контейнер 8f13c6d3d9e7
---> 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 > /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->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 е най-минималистичният метод, но не е единственият. Има и други:
- Container Registry (стандарт в индустрията).
- Свържете се с Docker демон на сървъра от друг хост:
- Променлива на средата
DOCKER_HOST. - Параметър на командния ред
-Hили--hostинструментаdocker-compose. docker context
- Променлива на средата
Вторият начин (с три варианта за реализиране) е добре описан в статията .
deploy.sh
Сега ще съберем всичко, което правихме ръчно, в един скрипт. Започваме с top-level функция, а след това ще разгледаме и останалите, използвани в нея.
Интересни техники
${parameter?err_msg}е едно от заклинанията на bash-манията (aka ). Ако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.
А трите условия са достатъчни, за да разграничат всички състояния ли?
Дори две са достатъчни, последното просто е за пълнота, за да не се пише. иначе.
Остана неопределена само функцията, която връща конфигурации за 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_SCRIPTEOF
$ chmod +x deploy.sh
$ ./deploy.sh localhost magic-porridge-pot
Свързване с localhost...
Ура! Функцията 'deploy' се изпълнява на 'hut' с аргумент 'magic-porridge-pot'Въпреки това, не можем да бъдем сигурни, че на отдалечения хост има адекватен bash, така че да добавим в началото малка проверка (това вместо ):
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
Успешно внедряване!Сега можете да отворите в браузъра, да стартирате внедряването отново и да се уверите, че то преминава безшевно, като обновите страницата по време на разгръщането.
Не забравяйте да се почистите след работа :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
