Distribuzione Blu-Verde con salari minimi

In questo articolo utilizziamo bash, SSH, scaricatore di porto и nginx Organizzeremo un layout senza soluzione di continuità dell'applicazione web. Schieramento blu-verde è una tecnica che ti consente di aggiornare istantaneamente un'applicazione senza rifiutare una singola richiesta. È una delle strategie di distribuzione con tempi di inattività pari a zero ed è più adatta per applicazioni con un'istanza, ma con la possibilità di caricare una seconda istanza pronta per l'esecuzione nelle vicinanze.

Supponiamo che tu abbia un'applicazione web con cui molti client lavorano attivamente e non c'è assolutamente modo di riposarsi per un paio di secondi. E hai davvero bisogno di implementare un aggiornamento della libreria, una correzione di bug o una nuova interessante funzionalità. In una situazione normale, sarà necessario interrompere l'applicazione, sostituirla e riavviarla. Nel caso del docker, è possibile prima sostituirlo, quindi riavviarlo, ma ci sarà comunque un periodo in cui le richieste all'applicazione non verranno elaborate, perché solitamente l'applicazione impiega del tempo per caricarsi inizialmente. Cosa succede se si avvia, ma risulta essere inutilizzabile? Questo è il problema, risolviamolo con mezzi minimi e nel modo più elegante possibile.

DISCLAIMER: La maggior parte dell'articolo è presentata in un formato sperimentale, sotto forma di registrazione di una sessione su console. Speriamo che questo non sia troppo difficile da capire e che il codice sia sufficientemente documentato. Per l'atmosfera, immagina che questi non siano solo frammenti di codice, ma carta di una telescrivente "di ferro".

Distribuzione Blu-Verde con salari minimi

All'inizio di ogni sezione sono descritte tecniche interessanti che risultano difficili da trovare su Google semplicemente leggendo il codice. Se qualcos'altro non è chiaro, cercalo su Google e controlla. spiegareshell (per fortuna funziona di nuovo, grazie allo sblocco del telegramma). Se non riesci a trovare nulla su Google, chiedi nei commenti. Sarò felice di aggiungere alla sezione corrispondente "Tecniche interessanti".

Cominciamo

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

Servizio

Creiamo un servizio sperimentale e inseriamolo in un contenitore.

Tecniche interessanti

  • cat << EOF > file-name (Qui Documento + Reindirizzamento I/O) è un modo per creare un file su più righe con un solo comando. Tutto ciò da cui bash legge /dev/stdin dopo questa riga e prima della riga EOF verrà registrato in file-name.
  • wget -qO- URL (spiegareshell) - invia un documento ricevuto tramite HTTP a /dev/stdout (analogico curl URL).

Stampare

Rompo specificamente lo snippet per abilitare l'evidenziazione per Python. Alla fine ci sarà un altro pezzo come questo. Considera che in questi punti la carta veniva tagliata per essere inviata al reparto evidenziazione (dove il codice veniva colorato a mano con gli evidenziatori), e poi questi pezzi venivano incollati nuovamente.

$ cat << EOF > 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 < loading_seconds:
                    self.send_error(503)
                else:
                    self.send_response(200)
                    self.send_header('Content-Type', 'text/html')
                    self.end_headers()
                    response = f'<h2>{app_name} is running for {t:3.1f} seconds.</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} (loads in {loading_seconds} sec.) started.')
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
 ---> 8ecf5a48c789
Step 2/4 : EXPOSE 8080
 ---> Using cache
 ---> cf92d174c9d3
Step 3/4 : COPY uptimer.py app.py
 ---> a7fbb33d6b7e
Step 4/4 : CMD [ "python", "-u", "./app.py" ]
 ---> Running in 1906b4bd9fdf
Removing intermediate container 1906b4bd9fdf
 ---> 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->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 is running for 15.4 seconds.</h2>

$ docker rm --force uptimer
uptimer

Procura inversa

Affinché la nostra applicazione possa cambiare inosservata, è necessario che davanti ad essa ci sia qualche altra entità che ne nasconda la sostituzione. Potrebbe essere un server web nginx в modalità proxy inverso. Viene stabilito un proxy inverso tra il client e l'applicazione. Accetta le richieste dai client e le inoltra all'applicazione e inoltra le risposte dell'applicazione ai client.

L'applicazione e il proxy inverso possono essere collegati all'interno della finestra mobile utilizzando rete docker. Pertanto il container con l'applicazione non ha nemmeno bisogno di inoltrare una porta sul sistema host; ciò consente all'applicazione di essere isolata al massimo dalle minacce esterne.

Se il proxy inverso risiede su un altro host, dovrai abbandonare la rete docker e connettere l'applicazione al proxy inverso attraverso la rete host, inoltrando la porta приложения parametro --publish, come al primo avvio e come con il proxy inverso.

Eseguiremo il proxy inverso sulla porta 80, perché questa è esattamente l'entità che dovrebbe ascoltare la rete esterna. Se la porta 80 è occupata sull'host di prova, modifica il parametro --publish 80:80 su --publish ANY_FREE_PORT:80.

Tecniche interessanti

Stampare

$ 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 is running for 11.5 seconds.</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 seconds ago       Up 25 seconds       0.0.0.0:80->80/tcp   reverse-proxy
a1105f1b583d        uptimer             "python -u ./app.py"     About a minute ago   Up About a 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: Sat, 22 Aug 2020 19:56:24 GMT
  Content-Type: text/html
  Transfer-Encoding: chunked
  Connection: keep-alive
<h2>Uptimer v1.0 is running for 104.1 seconds.</h2>

Distribuzione senza interruzioni

Lanciamo una nuova versione dell'applicazione (con un raddoppio delle prestazioni all'avvio) e proviamo a distribuirla senza problemi.

Tecniche interessanti

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' — Scrivi il testo my text archiviare /my-file.txt all'interno del contenitore my-container.
  • cat > /my-file.txt — Scrive il contenuto dello standard input in un file /dev/stdin.

Stampare

$ 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
 ---> 8ecf5a48c789
Step 2/4 : EXPOSE 8080
 ---> Using cache
 ---> cf92d174c9d3
Step 3/4 : COPY uptimer.py app.py
 ---> 3eca6a51cb2d
Step 4/4 : CMD [ "python", "-u", "./app.py" ]
 ---> Running in 8f13c6d3d9e7
Removing intermediate container 8f13c6d3d9e7
 ---> 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 is running for 23.9 seconds.</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
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 is running for 63.4 seconds.</h2>

$ docker rm -f uptimer
uptimer

$ wget -qO- http://localhost
<h2>Uptimer v2.0 is running for 84.8 seconds.</h2>

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                NAMES
96932d4ca97a        uptimer             "python -u ./app.py"     About a minute ago   Up About a minute   8080/tcp             uptimer_BLUE
80695a822c19        nginx:alpine        "/docker-entrypoint.…"   8 minutes ago        Up 8 minutes        0.0.0.0:80->80/tcp   reverse-proxy

In questa fase, l'immagine viene creata direttamente sul server, il che richiede la presenza delle origini dell'applicazione e carica anche il server con lavoro non necessario. Il passaggio successivo consiste nell'allocare l'assembly dell'immagine su una macchina separata (ad esempio, su un sistema CI) e quindi trasferirlo sul server.

Trasferimento di immagini

Sfortunatamente non ha senso trasferire immagini da localhost a localhost, quindi questa sezione può essere esplorata solo se si hanno due host con Docker a portata di mano. Come minimo assomiglia a questo:

$ 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

Squadra docker save salva i dati dell'immagine in un archivio .tar, il che significa che pesa circa 1.5 volte di più di quanto peserebbe in forma compressa. Quindi scuotiamolo in nome del risparmio di tempo e traffico:

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

Puoi anche monitorare il processo di download (sebbene ciò richieda un'utilità di terze parti):

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

Suggerimento: se hai bisogno di una serie di parametri per connetterti a un server tramite SSH, potresti non utilizzare il file ~/.ssh/config.

Trasferimento dell'immagine tramite docker image save/load - Questo è il metodo più minimalista, ma non l'unico. Ce ne sono altri:

  1. Registro dei contenitori (standard di settore).
  2. Connettiti al server daemon docker da un altro host:
    1. variabile d'ambiente DOCKER_HOST.
    2. Opzione della riga di comando -H o --host strumento docker-compose.
    3. docker context

Il secondo metodo (con tre opzioni per la sua implementazione) è ben descritto nell'articolo Come eseguire la distribuzione su host Docker remoti con docker-compose.

deploy.sh

Ora raccogliamo tutto ciò che abbiamo fatto manualmente in un unico script. Iniziamo con la funzione di livello superiore e poi osserviamo le altre utilizzate in essa.

Tecniche interessanti

  • ${parameter?err_msg} - uno degli incantesimi di bash magic (aka sostituzione dei parametri). Se parameter non specificato, output err_msg ed uscire con il codice 1.
  • docker --log-driver journald — per impostazione predefinita, il driver di registrazione della finestra mobile è un file di testo senza alcuna rotazione. Con questo approccio, i registri riempiono rapidamente l'intero disco, quindi per un ambiente di produzione è necessario cambiare il driver con uno più intelligente.

Script di distribuzione

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
}

Funzionalità utilizzate:

  • ensure-reverse-proxy — Assicura che il proxy inverso funzioni (utile per la prima distribuzione)
  • get-active-slot service_name — Determina quale slot è attualmente attivo per un determinato servizio (BLUE o GREEN)
  • get-service-status service_name deployment_slot — Determina se il servizio è pronto per elaborare le richieste in entrata
  • set-active-slot service_name deployment_slot — Modifica la configurazione di nginx nel contenitore del proxy inverso

In ordine:

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
}

Funzione get-active-slot richiede una piccola spiegazione:

Perché restituisce un numero e non restituisce una stringa?

Ad ogni modo, nella funzione chiamante controlliamo il risultato del suo lavoro e controllare il codice di uscita usando bash è molto più semplice che controllare una stringa. Inoltre, ottenere una stringa da esso è molto semplice:
get-active-slot service && echo BLUE || echo GREEN.

Sono davvero sufficienti tre condizioni per distinguere tutti gli Stati?

Distribuzione Blu-Verde con salari minimi

Ne basteranno anche due, l'ultimo è qui solo per completezza, per non scrivere else.

Solo la funzione che restituisce le configurazioni nginx rimane indefinita: get-nginx-config service_name deployment_slot. Per analogia con il controllo dello stato, qui puoi impostare qualsiasi configurazione per qualsiasi servizio. Delle cose interessanti - solo cat <<- EOF, che ti consente di rimuovere tutte le schede all'inizio. È vero, il prezzo di una buona formattazione è la combinazione di tabulazioni e spazi, che oggi è considerata una pessima forma. Ma bash forza le schede e sarebbe anche bello avere una formattazione normale nella configurazione di nginx. In breve, mescolare le schede con gli spazi qui sembra davvero la soluzione migliore per uscire dal peggio. Tuttavia, non lo vedrai nello snippet di seguito, poiché Habr “lo fa bene” modificando tutte le schede in 4 spazi e rendendo EOF non valido. E qui è evidente.

Per non alzarmi due volte, te ne parlerò subito cat << 'EOF', che incontreremo più avanti. Se scrivi semplicemente cat << EOF, quindi all'interno di heredoc la stringa viene interpolata (le variabili vengono espanse ($foo), chiamate di comando ($(bar)) ecc.), e se si racchiude la fine del documento tra virgolette singole, l'interpolazione è disabilitata e il simbolo $ viene visualizzato così com'è. Cosa serve per inserire uno script all'interno di un altro script.

get-nginx-config() {
    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

    local container_name=${service}_${slot}
    case $service in
        # Add specific nginx configs for your services here
        *) nginx-config-simple-service $container_name:8080 ;;
    esac
}

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

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

Questa è l'intera sceneggiatura. E così in sostanza con questo script per il download tramite wget o curl.

Esecuzione di script parametrizzati su un server remoto

È ora di bussare al server di destinazione. Questa volta localhost abbastanza adatto:

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

Abbiamo scritto uno script di distribuzione che scarica un'immagine precostruita sul server di destinazione e sostituisce perfettamente il contenitore del servizio, ma come possiamo eseguirlo su una macchina remota? Lo script ha argomenti, poiché è universale e può distribuire più servizi contemporaneamente sotto un proxy inverso (puoi utilizzare le configurazioni nginx per determinare quale URL sarà quale servizio). Lo script non può essere archiviato sul server, poiché in questo caso non saremo in grado di aggiornarlo automaticamente (ai fini della correzione di bug e dell'aggiunta di nuovi servizi) e in generale state = evil.

Soluzione 1: archivia comunque lo script sul server, ma copialo ogni volta scp. Quindi connettiti tramite ssh ed eseguire lo script con gli argomenti necessari.

contro:

  • Due azioni invece di una
  • Potrebbe non esserci un posto dove copiare, o potrebbe non esserci accesso, oppure lo script potrebbe essere eseguito al momento della sostituzione.
  • Si consiglia di ripulire da soli (eliminare lo script).
  • Già tre azioni.

Soluzione 2:

  • Mantieni solo le definizioni delle funzioni nello script e non esegui nulla
  • Con sed aggiungi una chiamata di funzione alla fine
  • Invia tutto direttamente a shh tramite pipe (|)

pro:

  • Veramente apolide
  • Nessuna entità standard
  • Sentendosi cool

Facciamolo senza Ansible. Sì, è già stato inventato tutto. Sì, una bicicletta. Guarda quanto è semplice, elegante e minimalista la bici:

$ cat << 'EOF' > 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
Connecting to localhost...
Yay! The 'deploy' function is executing on 'hut' with argument 'magic-porridge-pot'

Tuttavia, non possiamo essere sicuri che l'host remoto abbia una bash adeguata, quindi aggiungeremo un piccolo controllo all'inizio (questo è invece di shellbang):

if [ "$SHELL" != "/bin/bash" ]
then
    echo "The '$SHELL' shell is not supported by 'deploy.sh'. Set a '/bin/bash' shell for '$USER@$HOSTNAME'."
    exit 1
fi

E ora è reale:

$ 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
Sending gzipped image 'uptimer' to 'localhost' via ssh...
Loaded image: uptimer:latest
Connecting to 'localhost' via ssh to seamlessly deploy 'uptimer'...
Deploying 'uptimer_GREEN' in place 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!

Ora puoi aprire http://localhost/ nel browser, esegui nuovamente la distribuzione e assicurati che funzioni senza problemi aggiornando la pagina in base al CD durante il layout.

Non dimenticare di pulire dopo il lavoro: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

Fonte: habr.com