Blue-Green Deployment minimalistično

V tem članku bomo s pomočjo bash, ssh, docker in nginx organizirali brezšivno nameščanje spletne aplikacije. Blue-green deployment je tehnika, ki omogoča takojšnje posodobitve aplikacije brez zavračanja nobene zahteve. Je ena od strategij zero downtime deployment in najbolje deluje za aplikacije z enim instancom, a s sposobnostjo nalaganja drugega, pripravljenga za delo, poleg.

Recimo, da imate spletno aplikacijo, s katero aktivno dela veliko strank, in je nikakor ne morete ustaviti niti za nekaj sekund. Potrebujete pa nujno posodobiti knjižnico, odpraviti napako ali dodati novo funkcionalnost. V običajnem scenariju bi bilo treba ustaviti aplikacijo, jo zamenjati in znova zagnati. V primeru Dockerja lahko najprej zamenjate, nato pa ponovo zaženete, vendar bo vseeno prišlo do obdobja, ko zahteve za aplikacijo ne bodo obdelane, saj običajno aplikaciji traja nekaj časa, da se sprva naloži. Kaj pa, če se ponovno zažene, a se izkaže za neoperativno? Takšno nalogo bomo rešili z minimalnimi sredstvi in maksimalno eleganco.

ZGODBA O ODLAGANJU: Večina članka je predstavljena v eksperimentalnem formatu — kot zapis konzolne seje. Upam, da to ne bo preveč težko za razumevanje, in da se ta koda sama dokumentira v zadostni meri. Za vzdušje si predstavljajte, da to niso le kosi kode, temveč papir iz „železnega“ teleprinterja.

Blue-Green Deployment minimalistično

Zanimive tehnike, ki jih je težko najti zgolj z branjem kode, so opisane na začetku vsakega razdelka. Če še kaj ni jasno, iščite in preverite v explainshell (na srečo zdaj spet deluje, ker je Telegram odklenjen). Tisto, kar se ne da najti, vprašajte v komentarjih. Z veseljem bom dopolnil ustrezen razdelek „Zanimive tehnike“.

Začnimo.

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

Storitev

Ustvarili bomo poskusno storitev in jo postavili v kontejner.

Zanimive tehnike

  • cat < file-name (Here Document + I/O Redirection) — način za ustvarjanje večvrstičnega dokumenta z eno samo ukazom. Vse, kar bash prebere iz /dev/stdin po tej vrstici in do vrstice EOF bo zapisano v file-name.
  • wget -qO- URL (explainshell) — za izpis pridobljenega dokumenta preko HTTP v /dev/stdout (podobno curl URL).

Izpis

Namerno prekinjam snipet da uključim označavanje za Python. Na kraju će biti još jedan takav deo. Smatrajte da su u tim mestima papiri isečeni radi prenosa u odeljenje za obeležavanje (gde je kod ručno obeležen markerima), a zatim su ti delovi ponovo zalepili.

$ cat < uptimer.py
z http.server import BaseHTTPRequestHandler, HTTPServer
z time import monotonic

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

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '\/':
            try:
                t = monotonic() - server_start
                if t &lt; loading_seconds:
                    self.send_error(503)
                else:
                    self.send_response(200)
                    self.send_header(&#039;Content-Type&#039;, &#039;text\/html&#039;)
                    self.end_headers()
                    response = f&#039;<h2>{app_name} deluje že {t:3.1f} sekunde.</h2>n'
                    self.wfile.write(response.encode('utf-8'))
            except Exception:
                self.send_error(500)
        else:
            self.send_error(404)

httpd = HTTPServer(('', 8080), Handler)
server_start = monotonic()
print(f'{app_name} (naloži v {loading_seconds} sek.) začel.)
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 .
Sending build context to Docker daemon  39.42kB
Step 1\/4 : FROM python:alpine
 ---&gt; 8ecf5a48c789
Step 2\/4 : EXPOSE 8080
 ---&gt; Using cache
 ---&gt; cf92d174c9d3
Step 3\/4 : COPY uptimer.py app.py
 ---&gt; a7fbb33d6b7e
Step 4\/4 : CMD [ "python", "-u", ".\/app.py" ]
 ---&gt; Running in 1906b4bd9fdf
Removing intermediate container 1906b4bd9fdf
 ---&gt; c1655b996fe8
Successfully built c1655b996fe8
Successfully tagged uptimer:latest

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

$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                    NAMES
8f88c944b8bf        uptimer             "python -u .\/app.py"   3 seconds ago       Up 5 seconds        0.0.0.0:8080-&gt;8080\/tcp   uptimer

$ docker logs uptimer
Uptimer v1.0 (loads in 10 sec.) started.

$ wget -qSO- http:\/\/localhost:8080
  HTTP\/1.0 503 Service Unavailable
  Server: BaseHTTP\/0.6 Python\/3.8.3
  Date: Sat, 22 Aug 2020 19:52:40 GMT
  Connection: close
  Content-Type: text\/html;charset=utf-8
  Content-Length: 484

$ wget -qSO- http:\/\/localhost:8080
  HTTP\/1.0 200 OK
  Server: BaseHTTP\/0.6 Python\/3.8.3
  Date: Sat, 22 Aug 2020 19:52:45 GMT
  Content-Type: text\/html
<h2>Uptimer v1.0 работает в течение 15.4 секунд.</h2>

$ docker rm --force uptimer
uptimer

Reverzni proksi

Da bi naša aplikacija mogla tiho da se zameni, potrebno je da ispred nje postoji neka entitet koji će sakriti njenu zamenu. To može biti veb server nginx u u režimu reverznog proksija. Reverzni proksi se postavlja između klijenta i aplikacije. Prihvata zahteve od klijenata i preusmerava ih na aplikaciju, a odgovore aplikacije šalje klijentima.

Aplikaciju i reverzni proksi možemo povezati unutar dokera pomoću docker network. Tako da kontejneru s aplikacijom čak i ne treba preusmeriti port na host sistemu, što omogućava maksimalnu izolaciju aplikacije od spoljašnjih pretnji.

Ako će reverzni proksi biti na drugom hostu, moraćemo da se odrekne docker mreže i povežemo aplikaciju s reverznim proksijem putem mreže hosta, preusmerivši port aplikacije parametrom --publish, kao pri prvom pokretanju i kao kod reverznog proksija.

Reverzni proksi pokrećemo na portu 80, jer je to entitet koji treba da sluša spolja. Ako je port 80 na vašem test hostu zauzet, promenite parametar --publish 80:80 na --publish ANY_FREE_PORT:80.

Zanimive tehnike

Izpis

$ 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 секунд назад       В работе 25 секунд       0.0.0.0:80-&gt;80/tcp   reverse-proxy
a1105f1b583d        uptimer             "python -u ./app.py"     Минуту назад       В работе около минуты   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: сигнал процесса начат

$ 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>

Nepristrasni deploy

Izdavaćemo novu verziju aplikacije (sa dvostrukim povećanjem performansi pri pokretanju) i pokušaćemo da je neometano implementiramo.

Zanimive tehnike

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' — Upisati tekst my text u datoteku /my-file.txt unutar kontejnera my-container.
  • cat > /my-file.txt — Upisati u datoteku sadržaj standardnog ulaza /dev/stdin.

Izpis

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

$ docker build --tag uptimer .
Sending build context to Docker daemon  39.94kB
Step 1/4 : FROM python:alpine
 ---&gt; 8ecf5a48c789
Step 2/4 : EXPOSE 8080
 ---&gt; Using cache
 ---&gt; cf92d174c9d3
Step 3/4 : COPY uptimer.py app.py
 ---&gt; 3eca6a51cb2d
Step 4/4 : CMD [ "python", "-u", "./app.py" ]
 ---&gt; Running in 8f13c6d3d9e7
Removing intermediate container 8f13c6d3d9e7
 ---&gt; 1d56897841ec
Successfully built 1d56897841ec
Successfully tagged uptimer:latest

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

$ docker logs uptimer_BLUE
Uptimer v2.0 (loads in 5 sec.) started.

$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 je aktivan već 23.9 sekundi.</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
server {
    listen 80;
    location / {
        proxy_pass http://uptimer_BLUE:8080;
    }
}

$ docker exec reverse-proxy nginx -s reload
2020/06/25 21:22:23 [notice] 68#68: signal process started

$ wget -qO- http://localhost
<h2>Uptimer v2.0 je aktivan već 63.4 sekundi.</h2>

$ docker rm -f uptimer
uptimer

$ wget -qO- http://localhost
<h2>Uptimer v2.0 je aktivan već 84.8 sekundi.</h2>

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                NAMES
96932d4ca97a        uptimer             "python -u ./app.py"     Prije otprilike jedne minute   Up prije jedne minute   8080/tcp             uptimer_BLUE
80695a822c19        nginx:alpine        "docker-entrypoint.…"   Prije 8 minuta        Up 8 minuta        0.0.0.0:80-&gt;80/tcp   reverse-proxy

U ovoj fazi, slika se gradi direktno na serveru, što zahteva prisutnost izvora aplikacije, kao i dodatno opterećuje server. Sledeći korak biće odvajanje izrade slike na poseban mašinu (na primer, u CI sistemu) uz kasniju distribuciju na server.

Prenos slika

Žal, prenos slike z localhost na localhost nima smisla, zato lahko ta razdelek preizkusite le, če imate na voljo dva gostitelja z Dockerjem. Na minimalni ravni to izgleda približno tako:

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

$ docker image save uptimer | ssh production-server 'docker image load'
Naložena slika: uptimer:latest

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

Komanda docker save shranjuje podatke slike v .tar arhiv, kar pomeni, da je približno 1.5-krat večji, kot bi bil v stisnjeni obliki. Torej ga stisnimo v imenu varčevanja s časom in prometom:

$ docker image save uptimer | gzip | ssh production-server 'zcat | docker image load'
Naložena slika: uptimer:latest

Prav tako lahko spremljate potek prenosa (seveda potrebujete zunanje orodje):

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

Nasvet: Če potrebujete veliko parametrov za povezavo s strežnikom prek SSH, morda ne uporabljate datoteke ~/.ssh/config.

Prenos slike preko docker image save/load je najbolj minimalistična metoda, vendar ni edina. Obstajajo tudi druge:

  1. Container Registry (standard industrije).
  2. Povezovanje z docker daemon strežnika iz drugega gostitelja:
    1. Spremenljivka okolja DOCKER_HOST.
    2. Parameter ukazne vrstice -H ili --host orodja docker-compose.
    3. docker context

Drugi način (s tremi različicami njegove izvedbe) je dobro opisan v članku Kako izvajati na oddaljenih Docker gostiteljih z docker-compose.

deploy.sh

Zdaj bomo vse, kar smo počeli ročno, združili v en skript. Začnimo z vrhnjo funkcijo, nato pa si bomo ogledali ostale funkcije, ki jih uporablja.

Zanimive tehnike

  • ${parameter?err_msg} je ena od čarovnij bash magije (aka parameter substitution). Če parameter ni določen, izpiši err_msg in izstopi s kodo 1.
  • docker --log-driver journald je privzeto gonilo beleženja Dockerja besedilna datoteka brez kakršnekoli rotacije. Takšen pristop hitro napolni vse diske z logi, zato je za produkcijska okolja nujno spremeniti gonilo v pametnejše.

Skripta za namestitev

deploy() {
    local usage_msg="Usage: ${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 "Deploying '$NEW' in place of '$OLD'..."
    docker run 
        --detach 
        --restart always 
        --log-driver journald 
        --name $NEW 
        --network web-gateway 
        $image_name || return 3
    echo "Container started. Checking health..."
    for i in {1..20}
    do
        sleep 1
        if get-service-status $image_name $new_slot
        then
            echo "New '$NEW' service seems OK. Switching heads..."
            sleep 2  # Ensure service is ready
            set-active-slot $image_name $new_slot || return 4
            echo "'$NEW' service is live!"
            sleep 2  # Ensure all requests were processed
            echo "Killing '$OLD'..."
            docker rm -f $OLD
            docker image prune -f
            echo "Deployment successful!"
            return 0
        fi
        echo "New '$NEW' service is not ready yet. Waiting ($i)..."
    done
    echo "New '$NEW' service did not raise, killing it. Failed to deploy T_T"
    docker rm -f $NEW
    return 5
}

Uporabljene funkcije:

  • ensure-reverse-proxy — Preveri, ali deluje reverzni proxy (koristno za prvo namestitev)
  • get-active-slot service_name — Ugotovi, kateri slot je trenutno aktiven za dane storitve (BLUE ili GREEN)
  • get-service-status service_name deployment_slot — Ugotovi, ali je storitev pripravljena za obdelavo vhodnih zahtev
  • set-active-slot service_name deployment_slot — Spremeni konfiguracijo nginx v kontejnerju reverznega proxyja

Po vrsti:

ensure-reverse-proxy() {
    is-container-up reverse-proxy && return 0
    echo "Развёртывание обратного прокси..."
    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]} имя контейнера"}

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

get-active-slot() {
    local service=${1?"Использование: ${FUNCNAME[0]} имя сервиса"}

    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]} имя сервиса слот развертывания"
    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' в сети '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]} имя сервиса слот развертывания"
    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
}

Funkcija get-active-slot требует небольших пояснений:

Почему она возвращает число, а не выводит строку?

Всё равно в вызывающей функции мы проверяем результат её работы, а проверять exit code средствами bash намного проще, чем строку. К тому же, получить из неё строку очень просто:
get-active-slot service && echo BLUE || echo GREEN.

А трёх условий точно хватает, чтобы различить все состояния?

Blue-Green Deployment minimalistično

Даже двух хватит, последнее тут просто для полноты, чтобы не писать else.

Осталась неопределённой только функция, возвращающая конфиги nginx: get-nginx-config service_name deployment_slot. По аналогии с хелсчеком, тут можно задать любой конфиг для любого сервиса. Из интересного — только cat <<- EOF, ki omogoča odstranitev vseh tabulatorjev na začetku. Res je, da cena lepega oblikovanja — mešani tabulatorji s presledki, kar danes velja za zelo slabo noto. Vendar bash prisili tabulatorje, v nginx konfiguraciji pa bi bilo prav tako dobro imeti normalno oblikovanje. Skratka, tukaj se mešanje tabulatorjev s presledki zdi res najboljša rešitev med najslabšimi. Vendar pa tega v spodnjem odlomku ne boste videli, saj Habr 'naredi dobro', tako da vse tabulatorje spremeni v 4 presledke in naredi EOF neveljavnega. Tukaj je to opazno.

Da se ne bi dvakrat vračali, takoj povem o cat << 'EOF', ki se bo še pojavil naprej. Če preprosto napišete cat << EOF, se v heredoc izvaja interpolacija nizov (odprte so spremenljivke ($foo), klici ukazov ($(bar)) itd.), če pa oznako konca dokumenta obdajate z enojnimi narekovaji, se interpolacija izklopi in znak $ se izpiše, kot je. Kar potrebujete za vstavljanje skripta v drug skript.

get-nginx-config() {
    local usage_msg="Uporaba: ${FUNCNAME[0]} ime_storitve delovna_mesta"
    local service=${1?$usage_msg}
    local slot=${2?$usage_msg}
    [ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1

    local container_name=${service}_${slot}
    case $service in
        # Dodajte specifične nginx nastavitve za vaše storitve tukaj
        *) nginx-config-simple-service $container_name:8080 ;;
    esac
}

nginx-config-simple-service() {
    local usage_msg="Uporaba: ${FUNCNAME[0]} proxy_pass"
    local proxy_pass=${1?$usage_msg}

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

To je celoten skript. In tu gist s tem skriptom za prenos preko wget ali curl.

Izvajanje parameteriziranih skriptov na oddaljenem strežniku

Prišel je čas, da potrkamo na ciljni strežnik. Tokrat localhost kulturno ustreza:

$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: poskus prijave z novim ključem, da filtrirate vse, ki so že nameščeni
/usr/bin/ssh-copy-id: INFO: 1 ključ(ov) ostane za namestitev -- če vas zdaj pozove, je to za namestitev novih ključev
himura@localhostova geslo: 

Število dodanih ključev: 1

Zdaj poskusite prijavo v napravo z:   "ssh 'localhost'"
 in preverite, da so bili dodani samo želeni ključi.

Napisali smo skript za implementaciju koji prenosi prethodno sastavljenu sliku na ciljni server i bespravno zamenjuje kontejner usluge, ali kako ga izvršiti na udaljenoj mašini? Skript ima argumente jer je univerzalan i može implementirati više usluga pod jednim reverz-proksijem (konfiguracije nginx mogu rešiti koji će servis biti na kojem url-u). Skript ne može biti sačuvan na serveru, jer u tom slučaju ne možemo automatski ažurirati (radi ispravki grešaka i dodavanja novih usluga), a uopšte, stanje = zlo.

Rešenje 1: Držite skript na serveru, ali svaki put ga kopirajte preko scp. Zatim se povezati preko ssh i izvršiti skript sa neophodnim argumentima.

Mane:

  • Dve akcije umesto jedne
  • Mesto gde kopirate možda ne postoji, ili nemate pristup njemu, ili se skript može izvršavati u trenutku zamene.
  • Poželjno je ukloniti (izbrisati skript) nakon sebe.
  • Već tri akcije.

Rešenje 2:

  • U skriptu držati samo definicije funkcija i uopšte ništa ne pokretati
  • S pomočjo sed dodavati na kraj poziv funkcije
  • Sve to direktno slati u shh preko pipe-a (|)

Prednosti:

  • Zaista bez stanja
  • Bez standardnih entiteta
  • Zabavno je

Samo da to bude bez Ansibla. Da, sve je već izmišljeno. Da, bicikl. Pogledajte, kakav jednostavan, elegantan i minimalistički bicikl:

$ 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
Povezivanje na localhost...
Ura! Funkcija 'deploy' se izvršava na 'hut' sa argumentom 'magic-porridge-pot'

Međutim, ne možemo biti sigurni da na udaljenom hostu postoji adekvatan bash, tako da ćemo dodati malu proveru na početak (to umesto shellbang):

if [ "$SHELL" != "/bin/bash" ]
then
    echo "Skripta '$SHELL' nije podržana od strane 'deploy.sh'. Postavite 'bash' na '$USER@$HOSTNAME'."
    exit 1
fi

A sada sve zaista:

$ 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
Pos sending gzipped image 'uptimer' to 'localhost' via ssh...
Loaded image: uptimer:latest
Connecting to 'localhost' via ssh to seamlessly deploy 'uptimer'...
Deploying 'uptimer_GREEN' instead of 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Container started. Checking health...
Requesting 'http://uptimer_GREEN:8080/' within the 'web-gateway' docker network:
  HTTP/1.0 503 Service Unavailable
wget: server returned error: HTTP/1.0 503 Service Unavailable
New 'uptimer_GREEN' service is not ready yet. Waiting (1)...
Requesting 'http://uptimer_GREEN:8080/' within the 'web-gateway' docker network:
  HTTP/1.0 503 Service Unavailable
wget: server returned error: HTTP/1.0 503 Service Unavailable
New 'uptimer_GREEN' service is not ready yet. Waiting (2)...
Requesting 'http://uptimer_GREEN:8080/' within the 'web-gateway' docker network:
  HTTP/1.0 200 OK
  Server: BaseHTTP/0.6 Python/3.8.3
  Date: Sat, 22 Aug 2020 20:15:50 GMT
  Content-Type: text/html

New 'uptimer_GREEN' service seems OK. Switching heads...
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
2020/08/22 20:15:54 [notice] 97#97: signal process started
The 'uptimer_GREEN' service is live!
Killing 'uptimer_BLUE'...
uptimer_BLUE
Total reclaimed space: 0B
Deployment successful!

Т الآن можно открыть 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

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster