Blue-Green Deployment na minimálkach

V tomto článku sa pomocou bash, ssh, docker a nginx organizuje bezproblémové nasadenie webovej aplikácie. Blue-green nasadenie je technika, ktorá umožňuje okamžité aktualizovanie aplikácie, bez odmietania akýchkoľvek požiadaviek. Je to jedna zo stratégií zero downtime deployment a najlepšie sa hodí pre aplikácie s jedným inštancom, ale s možnosťou uložiť vedľa druhý, pripravený na prácu inštanc.

Predstavte si, že máte webovú aplikáciu, s ktorou aktívne pracuje množstvo klientov, a nemôže si dovoliť na pár sekúnd prerušiť činnosť. A vy veľmi potrebujete zaviesť aktualizáciu knižnice, opravu chyby alebo novú skvelú funkciu. V bežnej situácii by bolo potrebné zastaviť aplikáciu, nahradiť ju a znovu spustiť. V prípade Dockeru je možné najprv nahradiť a potom reštartovať, ale aj tak bude obdobie, počas ktorého nebudú požiadavky na aplikáciu spracované, keďže aplikácii zvyčajne treba nejaký čas na prvotné načítanie. A čo ak sa spustí, ale nebude funkčná? Takúto úlohu poďme vyriešiť minimálnymi prostriedkami a maximálne elegantne.

DISCLAIMER: Väčšina článku je prezentovaná v experimentálnom formáte — vo forme záznamu konzolovej relácie. Dúfam, že to nebude príliš ťažké na pochopenie a tento kód sám seba dokumentuje v dostatočnej miere. Pre atmosféru si predstavte, že to nie sú len kódové útržky, ale papier z 'železného' teletajpu.

Blue-Green Deployment na minimálkach

Zaujímavé techniky, ktoré sa ťažko hľadajú len čítaním kódu, sú popísané na začiatku každého oddielu. Ak niečo nebude jasné — hľadajte a kontrolujte v explainshell (našťastie, opäť funguje, po odblokovaní Telegramu). Ak sa niečo nedá nájsť — pýtajte sa v komentároch. S radosťou doplním príslušný oddiel 'Zaujímavé techniky'.

Poďme na to.

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

Služba

Vytvoríme pokusnú službu a umiestnime ju do kontajnera.

Zaujímavé techniky

  • cat < file-name (Here Document + I/O Redirection) — spôsob, ako vytvoriť viacriadkový súbor jednou príkazom. Všetko, čo bash prečíta z /dev/stdin po tejto riadke a až do riadku EOF bude zapísané do file-name.
  • wget -qO- URL (explainshell) — vypísať získaný dokument cez HTTP do /dev/stdout (rovnako ako curl URL).

Tlač

Úmyselne prerušujem snippety, aby som zahrnul zvýraznenie pre Python. Na konci bude ešte jedna takáto časť. Považujte to za úpravy papiera na odovzdanie do oddelenia zvýrazňovania (kde kód ručne zafarbovali zvýrazňovačmi), a potom tieto úseky znovu prilepili.

$ cat < uptimer.py
from http.server import BaseHTTPRequestHandler, HTTPServer
from 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} beží už {t:3.1f} sekúnd.</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} (načítava sa za {loading_seconds} sek.) spustené.')
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 (nahráva sa za 10 sekúnd) spustený.

$ wget -qSO- http:\/\/localhost:8080
  HTTP\/1.0 503 Služba nedostupná
  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 beží 15,4 sekundy.</h2>

$ docker rm --force uptimer
uptimer

Reverzný proxy server

Aby naše aplikácie mali možnosť bez problémov sa zmeniť, je potrebné, aby pred nimi bola nejaká entita, ktorá skryje ich výmenu. Môže to byť webový server. nginx v v režime reverzného proxy servera. Reverzný proxy server je umiestnený medzi klientom a aplikáciou. Prijíma požiadavky od klientov a presmerováva ich do aplikácie, a odpovede aplikácie posiela klientom.

Aplikáciu a reverzný proxy server môžeme spojiť v prostredí Docker pomocou docker network. Takže kontajneru s aplikáciou ani nemusíte otvorene priraďovať port na hostiteľskom systéme, čo umožňuje maximálne izolovať aplikáciu od externých hrozieb.

Ak bude reverzný proxy server bežať na inom hostiteľovi, budete musieť vzdať docker network a prepojiť aplikáciu s reverzným proxy serverom prostredníctvom siete hostiteľa, priradením portu aplikácie parametrom --publish, ako pri prvom spustení a ako pri reverznom proxy serveri.

Reverzný proxy server spustíme na porte 80, pretože to je tá entita, ktorá by mala počúvať na externé požiadavky. Ak je port 80 na vašom testovacom hostiteľovi obsadený, zmeňte parameter --publish 80:80 na --publish ANY_FREE_PORT:80.

Zaujímavé techniky

Tlač

$ 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 beží 11,5 sekundy.</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.…"   pred 27 sekundami       Beží 25 sekúnd       0.0.0.0:80-&gt;80\/tcp   reverse-proxy
a1105f1b583d        uptimer             "python -u .\/app.py"     pred asi minútou   Beží asi minútu   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: So, 22 Aug 2020 19:56:24 GMT
  Content-Type: text/html
  Transfer-Encoding: chunked
  Connection: keep-alive
<h2>Uptimer v1.0 beží 104,1 sekundy.</h2>

Bezproblémové nasadenie

Nasadíme novú verziu aplikácie (s dvojnásobným zlepšením štartovacej výkonnosti) a pokúsime sa ju bezproblémovo nasadiť.

Zaujímavé techniky

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' — Zapísať text my text do súboru /my-file.txt v rámci kontajnera my-container.
  • cat > /my-file.txt — Zapísať do súboru obsah štandardného vstupu /dev/stdin.

Tlač

$ 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 (naštartuje za 5 sekúnd) začal.

$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 beží už 23,9 sekundy.</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 beží už 63,4 sekundy.</h2>

$ docker rm -f uptimer
uptimer

$ wget -qO- http://localhost
<h2>Uptimer v2.0 beží už 84,8 sekundy.</h2>

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                NAMES
96932d4ca97a        uptimer             "python -u .\/app.py"     Pred minútou        Beží približne minútu   8080/tcp             uptimer_BLUE
80695a822c19        nginx:alpine        "\/docker-entrypoint.…"   Pred 8 minútami      Beží 8 minút        0.0.0.0:80-&gt;80/tcp   reverse-proxy

V tejto fáze sa obraz vybuduje priamo na serveri, čo si vyžaduje, aby tam boli zdrojové kódy aplikácie a tiež zaťažuje server zbytočnou prácou. Nasledujúcim krokom bude vyčlenenie procesu vytvárania obrazu na samostatný stroj (napríklad do CI systému) s následným odovzdaním na server.

Prenos obrazov

Žiaľ, prenášať obrazy z localhost na localhost nemá zmysel, takže túto časť môžete preskúmať len s dvoma hostiteľmi s Dockerom. Pri minimálnych nastaveniach to vyzerá asi takto:

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

$ docker image save uptimer | ssh production-server 'docker image load'
Načítaný obraz: uptimer:latest

$ ssh production-server docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
uptimer             latest              1d56897841ec        pred 5 minútami     78.9MB

Tím docker save ukladá dáta obrazu do archívu .tar, čo znamená, že váži približne 1.5-krát viac, ako by mohol vážiť v komprimovanej podobe. Tak to teda skomprimujme v mene úspory času a dátového toku:

$ docker image save uptimer | gzip | ssh production-server 'zcat | docker image load'
Načítaný obraz: uptimer:latest

A ešte, môžete sledovať proces prenášania (pravda, pre to potrebujete externú utilitu):

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

Rada: Ak potrebujete pre pripojenie k serveru cez SSH množstvo parametrov, možno nepoužívate súbor ~/.ssh/config.

Prenos obrazu cez docker image save/load — to je najminimalistickejšia metóda, ale nie jediná. Existujú aj ďalšie:

  1. Container Registry (štandard v odbore).
  2. Pripojiť sa k Docker daemonu servera z iného hostiteľa:
    1. Premenná prostredia DOCKER_HOST.
    2. Parametr príkazového riadku -H alebo --host nástroja docker-compose.
    3. docker context

Druhý spôsob (s tromi variantami jeho realizácie) je dobre popísaný v článku How to deploy on remote Docker hosts with docker-compose.

deploy.sh

Teraz všetko, čo sme robili ručne, zbalíme do jedného skriptu. Začneme s funkciou na najvyššej úrovni a potom sa pozrieme na ostatné, ktoré ju používajú.

Zaujímavé techniky

  • ${parameter?err_msg} — jedno z kúziel bash-magic (aka parameter substitution). Ak parameter nie je zadaný, vypíše err_msg a výjde s kódom 1.
  • docker --log-driver journald — predvolene je ovládač logovania dockera textový súbor bez akejkoľvek rotácie. S takým prístupom rýchlo zaplnia logy celý disk, takže pre produkčné prostredie je potrebné zmeniť ovládač na inteligentnejší.

Skript nasadenia

deploy() {
    local usage_msg="Použitie: ${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 "Nasadzujem '$NEW' miesto '$OLD'..."
    docker run 
        --detach 
        --restart always 
        --log-driver journald 
        --name $NEW 
        --network web-gateway 
        $image_name || return 3
    echo "Kontajner spustený. Kontrolujem zdravie..."
    for i in {1..20}
    do
        sleep 1
        if get-service-status $image_name $new_slot
        then
            echo "Nová '$NEW' služba vyzerá byť v poriadku. Prepínam hlavy..."
            sleep 2  # Zabezpečte, aby bola služba pripravená
            set-active-slot $image_name $new_slot || return 4
            echo "'$NEW' služba je aktívna!"
            sleep 2  # Zabezpečte, aby všetky požiadavky boli spracované
            echo "Zabíjam '$OLD'..."
            docker rm -f $OLD
            docker image prune -f
            echo "Nasadenie úspešné!"
            return 0
        fi
        echo "Nová '$NEW' služba ešte nie je pripravená. Čakanie ($i)..."
    done
    echo "Nová '$NEW' služba sa nespustila, zabíjam ju. Nasadenie zlyhalo T_T"
    docker rm -f $NEW
    return 5
}

Použité funkcie:

  • ensure-reverse-proxy — Zabezpečuje, že reverzní proxy funguje (užitočné pre prvé nasadenie)
  • get-active-slot service_name — Určuje, ktorý slot je momentálne aktívny pre zadanú službu (BLUE alebo GREEN)
  • get-service-status service_name deployment_slot — Určuje, či je služba pripravená na spracovanie prichádzajúcich požiadaviek
  • set-active-slot service_name deployment_slot — Zmení konfiguráciu nginx v kontajneri reverznej proxy

Po poriadku:

ensure-reverse-proxy() {
    is-container-up reverse-proxy && return 0
    echo "Nasazujem 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?"Použitie: ${FUNCNAME[0]} meno_kontejnera"}

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

get-active-slot() {
    local service=${1?"Použitie: ${FUNCNAME[0]} meno_služby"}

    if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
        echo "Zistená kolízia! Zastavujem ${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="Použitie: ${FUNCNAME[0]} meno_služby deployment_slot"
    local service=${1?usage_msg}
    local slot=${2?$usage_msg}

    case $service in
        # Pridajte špecifické cesty pre healthcheck pre vaše služby tu
        *) local health_check_port_path=":8080/" ;;
    esac
    local health_check_address="http://${service}_${slot}${health_check_port_path}"
    echo "Žiadam '$health_check_address' v rámci 'web-gateway' docker siete:"
    docker run --rm --network web-gateway alpine 
        wget --timeout=1 --quiet --server-response $health_check_address
    return $?
}

set-active-slot() {
    local usage_msg="Použitie: ${FUNCNAME[0]} meno_služby 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
}

Funkcia get-active-slot vyžaduje malé vysvetlenie:

Prečo vracia číslo a nie vypisuje reťazec?

Aj tak v volajúcej funkcii kontrolujeme výsledok jej práce, a overovať exit kód prostriedkami bash je oveľa jednoduchšie ako reťazec. Navyše, získať z nej reťazec je veľmi jednoduché:
get-active-slot service && echo BLUE || echo GREEN.

Stačí na rozlíšenie všetkých stavov tri podmienky?

Blue-Green Deployment na minimálkach

Dve stačia, posledná tu je len pre úplnosť, aby sa nemuselo písať else.

Zostala neurčená len funkcia, ktorá vracia konfigurácie nginx: get-nginx-config service_name deployment_slot. Podobne ako s healthcheckom, tu je možné nastaviť akúkoľvek konfiguráciu pre akúkoľvek službu. Zaujímavé je len, cat <<- EOF, čo umožňuje odstrániť všetky taby na začiatku. Pravda, cena za dobrou formátovanie — zmiešané taby s medzerami, čo sa dnes považuje za veľmi zlý tón. Ale bash núti taby, a v konfigurácii nginx by tiež nebolo zlé mať normálne formátovanie. Skrátka, tu sa zmiešanie tabov s medzerami zdá byť naozaj najlepším riešením spomedzi najhorších. Avšak v nižšie uvedenom snippete to neuvidíte, pretože habr „robí dobre“, mení všetky taby na 4 medzery a robí neplatný EOF. Ale tu je to znateľné.

Aby som sa dvakrát nezastavoval, hneď poviem o cat << 'EOF', ktorý sa ešte objaví neskôr. Ak napíšete jednoducho cat << EOF, tak v rámci heredocu prebieha interpolácia reťazca (rozpracúvajú sa premenné ($foo), volania príkazov ($(bar)) atď.), a ak uzatvoríte znak konca dokumentu do jednoduchých úvodzoviek, interpolácia sa vypne a symbol $ sa vypíše tak, ako je. To, čo potrebujete na vloženie skriptu do iného skriptu.

get-nginx-config() {
    local usage_msg="Použitie: ${FUNCNAME[0]} názov_služby nasadenie_slot"
    local service=${1?$usage_msg}
    local slot=${2?$usage_msg}
    [ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1

    local container_name=${service}_${slot}
    case $service in
        # Pridajte konkrétne nginx konfigurácie pre vaše služby tu
        *) nginx-config-simple-service $container_name:8080 ;;
    esac
}

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

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

To je celý skript. A toto gist s týmto skriptom na stiahnutie cez wget alebo curl.

Vykonávanie parametrových skriptov na vzdialenom serveri

Prišiel čas zaklopať na cieľový server. Tentoraz localhost sa úplne hodí:

$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
himura@localhost's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'localhost'"
and check to make sure that only the key(s) you wanted were added.

Napísali sme skript na nasadenie, ktorý prenáša predom zostavený obraz na cieľový server a bezproblémovo nahrádza kontajner služby, ale ako ho vykonať na vzdialenom stroji? Skript má argumenty, pretože je univerzálny a môže nasadzovať viacero služieb pre jeden reverzný proxy (v konfiguráciách Nginx môžeme vyriešiť, na akú url aká služba bude). Skript nemožno uchovávať na serveri, pretože v takom prípade ho nebudeme môcť automaticky aktualizovať (s cieľom opravy chýb a pridávania nových služieb) a celkovo, stav = zlo.

Riešenie 1: Takže uchovávať skript na serveri, ale každýkrát ho kopírovať cez scp. Potom sa pripojiť cez ssh a vykonať skript s potrebnými argumentmi.

Nevýhody:

  • Dve akcie namiesto jednej
  • Miesta, kam kopírujete, nemusí existovať, alebo nemusí byť možné k nemu získať prístup, alebo skript sa môže vykonávať v čase nahradenia.
  • Odporúča sa po sebe upratať (vymazať skript).
  • Už tri akcie.

Riešenie 2:

  • V skripte mať len definície funkcií a vôbec nič nespúšťať,
  • S pomocou sed dodať na koniec volanie funkcie.
  • Poslať to všetko priamo do shh cez pipe (|)

Výhody:

  • Skutočne bezstavový
  • Žiadne boilerplate entity
  • Cítim sa skvele

Ale bez Ansible. Áno, už je to vymyslené. Áno, bicykel. Pozrite sa, aký jednoduchý, elegantný a minimalistický bicykel je:

$ 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
Pripojenie na localhost...
Hurá! Funkcia 'deploy' sa vykonáva na 'hut' s argumentom 'magic-porridge-pot'

Avšak nemôžeme si byť istí, že na vzdialenom hoste je adekvátny bash, takže na začiatok pridáme malú overovaciu kontrolu (to je na mieste shellbang):

if [ "$SHELL" != "/bin/bash" ]
then
    echo "Shell '$SHELL' nie je podporovaný 'deploy.sh'. Nastavte shell '/bin/bash' pre '$USER@$HOSTNAME'."
    exit 1
fi

A teraz všetko naozaj:

$ 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
Odosielanie komprimovaného obrazu 'uptimer' na 'localhost' prostredníctvom ssh...
Načítaný obraz: uptimer:latest
Pripojenie k 'localhost' prostredníctvom ssh na bezproblémové nasadenie 'uptimer'...
Nasadenie 'uptimer_GREEN' namiesto 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Kontajner bol spustený. Kontrola zdravia...
Žiadosť o 'http://uptimer_GREEN:8080/' v rámci docker siete 'web-gateway':
  HTTP/1.0 503 Služba nedostupná
wget: server vrátil chybu: HTTP/1.0 503 Služba nedostupná
Nová služba 'uptimer_GREEN' ešte nie je pripravená. Čakanie (1)...
Žiadosť o 'http://uptimer_GREEN:8080/' v rámci docker siete 'web-gateway':
  HTTP/1.0 503 Služba nedostupná
wget: server vrátil chybu: HTTP/1.0 503 Služba nedostupná
Nová služba 'uptimer_GREEN' ešte nie je pripravená. Čakanie (2)...
Žiadosť o 'http://uptimer_GREEN:8080/' v rámci docker siete 'web-gateway':
  HTTP/1.0 200 OK
  Server: BaseHTTP/0.6 Python/3.8.3
  Dátum: So, 22. augusta 2020 20:15:50 GMT
  Typ obsahu: text/html

Nová služba 'uptimer_GREEN' sa javí byť OK. Prechádzam na hlavy...
nginx: syntax konfigurácie súboru /etc/nginx/nginx.conf je v poriadku
nginx: test konfigurácie súboru /etc/nginx/nginx.conf bol úspešný
2020/08/22 20:15:54 [oznámenie] 97#97: proces signálu bol spustený
Služba 'uptimer_GREEN' je živá!
Zabíjam 'uptimer_BLUE'...
uptimer_BLUE
Celkový uvoľnený priestor: 0B
Nasadenie úspešné!

Teraz môžete otvoriť http://localhost/ v prehliadači, spustiť nasadenie ešte raz a uistiť sa, že prebieha bezproblémovo aktualizovaním stránky klávesom F5 počas nasadenia.

Nezabúdajme upratať po práci :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

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster