Blue-Green Deployment na minimumu

U ovom članku ćemo uz pomoć bash, ssh, docker i nginx organizirati nevidljivo implementiranje web aplikacije. Blue-green deployment je tehnika koja omogućava trenutno ažuriranje aplikacije bez odbijanja nijednog zahteva. To je jedna od strategija zero downtime deployment i najbolje se uklapa u aplikacije sa jednim instancom, ali mogućnošću učitavanja pored njega drugog, spremnog za rad.

Recimo da imate web aplikaciju s kojom aktivno radi mnogo klijenata i ne može jednostavno stati na nekoliko sekundi. A potrebno vam je da implementirate ažuriranje biblioteke, ispravku greške ili novu sjajnu funkcionalnost. U normalnim okolnostima, trebalo bi da zaustavite aplikaciju, zamenite je i ponovo pokrenete. U slučaju dockera, možete prvo zameniti, a zatim ponovo pokrenuti, ali i dalje će postojati period tokom kojeg zahtevi za aplikaciju neće biti obrađeni, jer obično aplikaciji treba neko vreme za prvobitno učitavanje. A šta ako se pokrene, ali ispostavi se da nije funkcionalno? Ova situacija, hajde da je rešimo minimalnim sredstvima i maksimalno elegantno.

DISCLAIMER: Veći deo članka predstavljen je u eksperimentalnom formatu — u obliku snimka konzolne sesije. Nadam se da to neće biti previše teško za razumevanje i da će ovaj kod sam sebe dokumentovati u dovoljnoj meri. Za atmosferu, zamislite da ovo nisu samo kod snimci, već papir iz 'čeličnog' teletajpa.

Blue-Green Deployment na minimumu

Zanimljive tehnike koje je teško pronaći jednostavnim čitanjem koda opisane su na početku svake sekcije. Ako nešto još bude nejasno — guglajte i proverite u explainshell (srećom, ponovo funkcioniše, zbog ponovnog otvaranja Telegrama). Ono što se ne može guglati — pitajte u komentarima. Rado ću dopuniti odgovarajući deo 'Zanimljive tehnike'.

Krenimo.

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

Servis

Napravimo eksperimentalni servis i stavimo ga u kontejner.

Zanimljive tehnike

  • cat < file-name (Here Document + I/O Redirection) — način da se stvori višelinijski fajl jednim komandama. Sve što bash pročita od /dev/stdin posle ove linije i do linije EOF biće zapisano u file-name.
  • wget -qO- URL (explainshell) — da se izvede dobijeni HTTP dokument u /dev/stdout (analog curl URL).

Štampanje

Namjerno prekinem snipet kako bih uključio naglašavanje za Python. Na kraju će biti još jedan takav dio. Smatrajte da su na tim mjestima papir razrezali za prijenos u odjel za naglašavanje (gdje su kod ručno bojili markerima), a zatim su te komade ponovno zalijepili.

$ cat < uptimer.py
iz http.server import BaseHTTPRequestHandler, HTTPServer
iz vremena 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} radi već {t:3.1f} sekundi.</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} (učitava se u {loading_seconds} sek.) pokrenut.')
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 radi već 15.4 sekundi.</h2>

$ docker rm --force uptimer
uptimer

Obrnuti proxy

Da bi naša aplikacija mogla neprimjetno promijeniti, potrebno je da ispred nje bude još neka entitet koji će sakriti njezinu promjenu. To može biti web server. nginx u u režimu obrnuti proxy. Obrnuti proxy postavlja se između klijenta i aplikacije. Prima zahtjeve od klijenata i preusmjerava ih na aplikaciju, a odgovore aplikacije šalje klijentima.

Aplikaciju i obrnuti proxy možemo povezati unutar dockera pomoću docker network. Na taj način, kontejneru s aplikacijom čak nije potrebno proslijediti port u host sustavu, što omogućuje maksimalnu izolaciju aplikacije od vanjskih prijetnji.

Ako obrnuti proxy bude na drugom hostu, morat ćemo odustati od docker network i povezati aplikaciju s obrnuti proxy preko host mreže, proslijedivši port. aplikacije parametrom --publish, kao pri prvom pokretanju i kao kod obrnuti proxy.

Obrnuti proxy ćemo pokrenuti na portu 80, jer je to zapravo ta entitet koja bi trebala slušati vanjske zahtjeve. Ako je 80. port zauzet na vašem testnom hostu, promijenite parametar. --publish 80:80 na --publish ANY_FREE_PORT:80.

Zanimljive tehnike

Štampanje

$ 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 radi 11.5 sekundi.</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 sekundi prije       Up 25 sekundi       0.0.0.0:80-&gt;80\/tcp   reverse-proxy
a1105f1b583d        uptimer             "python -u .\/app.py"     Prije otprilike minute   Up Prije otprilike minute   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: Sub, 22 Kol 2020 19:56:24 GMT
  Content-Type: text/html
  Transfer-Encoding: chunked
  Connection: keep-alive
<h2>Uptimer v1.0 radi 104.1 sekundi.</h2>

Besprijekorno uvođenje

Izbacit ćemo novu verziju aplikacije (s dvostrukim poboljšanjem startup performansi) i pokušati je besprijekorno implementirati.

Zanimljive tehnike

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

Štampanje

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

$ docker build --tag uptimer .
Slanje konteksta build-a Docker demon-u  39.94kB
Korak 1/4 : FROM python:alpine
 ---&gt; 8ecf5a48c789
Korak 2/4 : EXPOSE 8080
 ---&gt; Korištenje keša
 ---&gt; cf92d174c9d3
Korak 3/4 : COPY uptimer.py app.py
 ---&gt; 3eca6a51cb2d
Korak 4/4 : CMD [ "python", "-u", "../app.py" ]
 ---&gt; Izvodi se u 8f13c6d3d9e7
Uklanjanje međukontejnera 8f13c6d3d9e7
 ---&gt; 1d56897841ec
Uspješno je izgrađen 1d56897841ec
Uspješno označeno uptimer:latest

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

$ docker logs uptimer_BLUE
Uptimer v2.0 (uvođenje u 5 sekundi) je pokrenut.

$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 radi 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 [obavijest] 68#68: signal proces počeo

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

$ docker rm -f uptimer
uptimer

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

$ docker ps
ID KONTEJNERA        SLIKA               KOMANDA                  STVORENO              STATUS              PORTOVI                IMENA
96932d4ca97a        uptimer             "python -u ./app.py"     Prije oko minute       Up Prije oko 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 izravno na serveru, što zahtijeva prisutnost izvora aplikacije, a također opterećuje server dodatnim radom. Sljedeći korak bit će izdvajanje gradnje slike na zasebnom stroju (npr. u CI sustavu) s naknadnim prijenosom na server.

Preuzimanje slika

Nažalost, prijenos slika s localhosta na localhost nema smisla, stoga ovaj odjeljak možete istražiti samo s dva hosta sa Dockerom. U minimalnom obliku to izgleda otprilike ovako:

$ 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

Komanda docker save sprema podatke slike u .tar arhiv, tj. abit će otprilike 1.5 puta veći nego što bi mogao biti u komprimiranom formatu. Zbog toga ćemo ga komprimirati u ime uštede vremena i prometa:

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

Osim toga, možete pratiti proces prijenosa (iako za to treba treći alat):

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

Savjet: Ako vam za povezivanje sa serverom putem SSH treba mnogo parametara, možda ne koristite datoteku ~/.ssh/config.

Prijenos slike putem docker image save/load je najminimalističnija metoda, ali ne i jedina. Ima i drugih:

  1. Container Registry (standard u industriji).
  2. Povežite se na Docker daemon servera s drugog hosta:
    1. Varijabla okruženja DOCKER_HOST.
    2. Parametar naredbenog retka -H ili --host alat docker-compose.
    3. docker context

Drugi način (s tri mogućnosti njegove implementacije) dobro je opisan u članku How to deploy on remote Docker hosts with docker-compose.

deploy.sh

Sada ćemo sve što smo radili ručno okupiti u jedan skript. Počnimo s top-level funkcijom, a zatim ćemo pogledati ostale koje se koriste u njoj.

Zanimljive tehnike

  • ${parameter?err_msg} je jedan od čarobnjaka bash-magije (aka parameter substitution). Ako parameter nije postavljen, ispisati err_msg i izaći s kodom 1.
  • docker --log-driver journald je osnovni vozač za evidentiranje koji se koristi u Dockeru, a koji koristi tekstualnu datoteku bez rotacije. Takav pristup brzo popunjava cijeli disk s logovima, stoga je za produkcijsko okruženje potrebno promijeniti vozača na pametniji.

Skript za postavljanje

deploy() {
    local usage_msg="Korištenje: ${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 "Implementiram '$NEW' umjesto '$OLD'..."
    docker run 
        --detach 
        --restart always 
        --log-driver journald 
        --name $NEW 
        --network web-gateway 
        $image_name || return 3
    echo "Kontejner je započeo. Provjeravam stanje..."
    for i in {1..20}
    do
        sleep 1
        if get-service-status $image_name $new_slot
        then
            echo "Nova '$NEW' usluga izgleda u redu. Prebacujem glave..."
            sleep 2  # Osigurajte da je usluga spremna
            set-active-slot $image_name $new_slot || return 4
            echo "'$NEW' usluga je aktivna!"
            sleep 2  # Osigurajte da su svi zahtjevi obrađeni
            echo "Ubijam '$OLD'..."
            docker rm -f $OLD
            docker image prune -f
            echo "Implementacija uspješna!"
            return 0
        fi
        echo "Nova '$NEW' usluga još nije spremna. Čekam ($i)..."
    done
    echo "Nova '$NEW' usluga se nije pokrenula, ubijam je. Neuspješna implementacija T_T"
    docker rm -f $NEW
    return 5
}

Korištene funkcije:

  • ensure-reverse-proxy — Provjerava da li reverzni proxy radi (korisno za prvu implementaciju)
  • get-active-slot service_name — Određuje koji je trenutni slot aktivan za zadanu uslugu (BLUE ili GREEN)
  • get-service-status service_name deployment_slot — Utvrđuje je li usluga spremna za obradu ulaznih zahtjeva
  • set-active-slot service_name deployment_slot — Mijenja konfiguraciju nginx u kontejneru reverznog proxyja

Redom:

ensure-reverse-proxy() {
    is-container-up reverse-proxy && return 0
    echo "Deploying 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?"Usage: ${FUNCNAME[0]} container_name"}

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

get-active-slot() {
    local service=${1?"Usage: ${FUNCNAME[0]} service_name"}

    if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
        echo "Collision detected! Stopping ${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="Usage: ${FUNCNAME[0]} service_name deployment_slot"
    local service=${1?usage_msg}
    local slot=${2?$usage_msg}

    case $service in
        # Add specific healthcheck paths for your services here
        *) local health_check_port_path=":8080/" ;;
    esac
    local health_check_address="http://${service}_${slot}${health_check_port_path}"
    echo "Requesting '$health_check_address' within the 'web-gateway' docker network:"
    docker run --rm --network web-gateway alpine 
        wget --timeout=1 --quiet --server-response $health_check_address
    return $?
}

set-active-slot() {
    local usage_msg="Usage: ${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
}

Funkcija get-active-slot potrebno malo pojašnjenja:

Zašto vraća broj, a ne ispisuje string?

I dalje u pozivnoj funkciji provjeravamo rezultat njenog rada, a provjeravati exit code putem basha mnogo je jednostavnije nego string. Osim toga, dobiti string iz toga je vrlo lako:
get-active-slot service && echo BLUE || echo GREEN.

Da li tri uvjeta zaista dovoljno da razlikuju sve stanje?

Blue-Green Deployment na minimumu

Dovoljna su i dva, zadnje je ovde samo za potpunost, da ne pišem else.

Neodređena je samo funkcija koja vraća nginx konfiguracije: get-nginx-config service_name deployment_slot. Po analogiji sa healthcheckom, ovde se može postaviti bilo koja konfiguracija za bilo koju uslugu. Zanimljivo je samo cat <<- EOF, što omogućuje uklanjanje svih tabova na početku. Istina, cijena pristojnog oblikovanja su miješani tabovi s razmacima, što se danas smatra vrlo lošim stilom. No, bash forsira tabove, a u nginx konfiguraciji također bi bilo dobro imati normalno oblikovanje. Ukratko, ovdje se čini da je miješanje tabova s razmacima zapravo najbolja opcija među lošim. Međutim, u isječku dolje to nećete vidjeti, jer Habr „radi dobro“, mijenjajući sve tabove u 4 razmaka i čineći EOF nevažećim. A ovdje je to očigledno.

Da ne bismo dvaput govorili, odmah ću pričati o cat << 'EOF', koji će se opet susresti. Ako samo napišete cat << EOF, unutar heredoca se vrši interpolacija stringa (otvaraju se varijable ($foo), pozivi naredbi ($(bar)) itd.), a ako se znak kraja dokumenta stavi u jednostruke navodnike, interpolacija se onemogućava i znak $ se ispisuje onako kako jest. To je potrebno za umetanje skripte unutar druge skripte.

get-nginx-config() {
    local usage_msg="Upotreba: ${FUNCNAME[0]} naziv_usluge slot_deploymana"
    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 konfiguracije za vaše usluge ovdje
        *) nginx-config-simple-service $container_name:8080 ;;
    esac
}

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

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

To je cijela skripta. I evo gist s tom skriptom za preuzimanje putem wget ili curl.

Izvršavanje parametriziranih skripti na udaljenom poslužitelju

Došlo je vrijeme za povezivanje s ciljnim poslužiteljem. Ovaj put localhost savršeno odgovara:

$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: pokušaj prijave s novim ključem(ima), da filtrira sve koji su već instalirani
/usr/bin/ssh-copy-id: INFO: 1 ključ(ova) ostaje za instalaciju -- ako se sada zatraži, to je za instalaciju novih ključeva
himura@localhostova lozinka: 

Broj dodanih ključeva: 1

Sada pokušajte prijaviti se na računalo s:   "ssh 'localhost'"
i provjerite jesu li dodani samo ključevi koje ste željeli.

Napisali smo skriptu za implementaciju koja prebacuje prethodno sastavljenu sliku na ciljni server i neprimjetno zamjenjuje kontejner usluge, ali kako je izvršiti na udaljenoj mašini? Skripta ima argumente, budući da je univerzalna i može implementirati više usluga za jedan reverzni proxy (konfiguracije nginx-a mogu se preusmjeriti po kojoj URL-u će koja usluga biti). Skriptu nije moguće pohraniti na serveru jer tu ne bismo mogli automatski ažurirati (u svrhu ispravki grešaka i dodavanja novih usluga), a uostalom, stanje = zlo.

Rješenje 1: Čuvati skriptu na serveru, ali je svaki put kopirati putem scp. Zatim se priključiti putem ssh i izvršiti skriptu s potrebnim argumentima.

Mane:

  • Dva koraka umjesto jednog
  • Mjesta na koja kopirate možda nema, ili nemate pristup, ili se skripta može izvršavati tijekom zamjene.
  • Preporučljivo je ukloniti nakon upotrebe (izbrisati skriptu).
  • Sada imamo tri koraka.

Rješenje 2:

  • Skriptu treba držati samo s definicijama funkcija i uopće ne pokretati ništa
  • Pomoću sed dodati na kraj poziv funkcije
  • Sve ovo slati direktno u shh putem cijevi (|)

Prednosti:

  • Zaista bez stanja
  • Bez suvišnih entiteta
  • Super osjećaj

Samo da ne idemo na Ansible. Da, sve je već izmišljeno. Da, bicikl. Poglejte, 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
Spajanje na localhost...
Ura! Funkcija 'deploy' se izvršava na 'hut' s argumentom 'magic-porridge-pot'

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

if [ "$SHELL" != "/bin/bash" ]
then
    echo "Šell '$SHELL' nije podržan od strane 'deploy.sh'. Postavite ' /bin/bash' šell za '$USER@$HOSTNAME'."
    exit 1
fi

A sada sve pošteno:

$ 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
Slanje gzipane slike 'uptimer' na 'localhost' putem ssh...
Učitana slika: uptimer:latest
Povezivanje s 'localhost' putem ssh za neometano postavljanje 'uptimer'...
Postavljanje 'uptimer_GREEN' umjesto 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Kontejner pokrenut. Provjera zdravlja...
Zahtjev za 'http://uptimer_GREEN:8080/' unutar docker mreže 'web-gateway':
  HTTP/1.0 503 Usluga nedostupna
wget: server je vratio grešku: HTTP/1.0 503 Usluga nedostupna
Nova služba 'uptimer_GREEN' još nije spremna. Čekam (1)...
Zahtjev za 'http://uptimer_GREEN:8080/' unutar docker mreže 'web-gateway':
  HTTP/1.0 503 Usluga nedostupna
wget: server je vratio grešku: HTTP/1.0 503 Usluga nedostupna
Nova služba 'uptimer_GREEN' još nije spremna. Čekam (2)...
Zahtjev za 'http://uptimer_GREEN:8080/' unutar docker mreže 'web-gateway':
  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

Nova služba 'uptimer_GREEN' izgleda u redu. Prebacivanje glava...
nginx: konfiguracijska datoteka /etc/nginx/nginx.conf sintaksa je u redu
nginx: test konfiguracijske datoteke /etc/nginx/nginx.conf je uspješan
2020/08/22 20:15:54 [notice] 97#97: signal process started
Služba 'uptimer_GREEN' je aktivna!
Gasenje 'uptimer_BLUE'...
uptimer_BLUE
Ukupno oslobođen prostor: 0B
Implementacija uspješna!

Sada možete otvoriti http://localhost/ u pregledniku, ponovno pokrenuti implementaciju i osigurati da prolazi neometano tako da osvježite stranicu tijekom postavljanja.

Ne zaboravite očistiti nakon rada :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 pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster