Blauw-Groene inzet tegen minimumlonen

In dit artikel gebruiken wij slaan, ssh, havenarbeider и nginx Wij zorgen voor een naadloze layout van de webapplicatie. Blauw-groene inzet is een techniek waarmee u een applicatie onmiddellijk kunt bijwerken zonder ook maar één verzoek af te wijzen. Het is een van de zero-downtime-implementatiestrategieën en is het meest geschikt voor applicaties met één instance, maar de mogelijkheid om een ​​tweede, kant-en-klare instance in de buurt te laden.

Stel dat u een webapplicatie heeft waarmee veel klanten actief werken, en deze kan absoluut niet een paar seconden stil blijven liggen. En je moet echt een bibliotheekupdate, een bugfix of een nieuwe coole functie uitrollen. In een normale situatie moet u de applicatie stoppen, vervangen en opnieuw starten. In het geval van docker kunt u deze eerst vervangen en vervolgens opnieuw opstarten, maar er zal nog steeds een periode zijn waarin verzoeken aan de applicatie niet worden verwerkt, omdat het meestal enige tijd duurt voordat de applicatie in eerste instantie is geladen. Wat als het begint, maar niet meer werkt? Dit is het probleem, laten we het met minimale middelen en zo elegant mogelijk oplossen.

DISCLAIMER: Het grootste deel van het artikel wordt gepresenteerd in een experimenteel formaat - in de vorm van een opname van een consolesessie. Hopelijk is dit niet al te moeilijk te begrijpen en zal de code zichzelf voldoende documenteren. Stel je voor de sfeer voor dat dit niet alleen codefragmenten zijn, maar papier van een ‘ijzeren’ teletype.

Blauw-Groene inzet tegen minimumlonen

Interessante technieken die moeilijk te Googlen zijn door alleen de code te lezen, worden aan het begin van elke sectie beschreven. Als er nog iets onduidelijk is, google het dan en bekijk het. leg uit (gelukkig werkt het weer, dankzij het deblokkeren van het telegram). Als je niets kunt Googlen, vraag het dan in de reacties. Ik zal graag iets toevoegen aan de overeenkomstige sectie "Interessante technieken".

Laten we beginnen.

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

Dienst

Laten we een experimentele dienst maken en deze in een container plaatsen.

Interessante technieken

  • cat << EOF > file-name (Hier documenteren + I/O-omleiding) is een manier om met één opdracht een bestand met meerdere regels te maken. Alles waar Bash van leest /dev/stdin na deze regel en vóór de regel EOF zal worden opgenomen file-name.
  • wget -qO- URL (leg uit) — voer een document uit dat is ontvangen via HTTP naar /dev/stdout (analoog curl URL).

Afdrukken

Ik breek het fragment specifiek om markering voor Python mogelijk te maken. Aan het einde zal er nog zo'n stuk zijn. Bedenk dat op deze plaatsen het papier werd gesneden om naar de markeerafdeling te worden gestuurd (waar de code met de hand werd ingekleurd met markeerstiften), en dat deze stukken vervolgens werden teruggelijmd.

$ 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

Omgekeerde proxy

Om ervoor te zorgen dat onze applicatie onopgemerkt kan veranderen, is het noodzakelijk dat er een andere entiteit voor staat die de vervanging ervan zal verbergen. Het zou een webserver kunnen zijn nginx в omgekeerde proxy-modus. Er wordt een reverse proxy tot stand gebracht tussen de client en de applicatie. Het accepteert verzoeken van clients en stuurt deze door naar de applicatie en stuurt de antwoorden van de applicatie door naar de clients.

De applicatie en reverse proxy kunnen binnen docker worden gekoppeld met behulp van docker-netwerk. De container met de applicatie hoeft dus niet eens een poort op het hostsysteem door te sturen; hierdoor kan de applicatie maximaal worden geïsoleerd van externe bedreigingen.

Als de reverse proxy op een andere host staat, moet u het docker-netwerk verlaten en de applicatie via het hostnetwerk met de reverse proxy verbinden, waarbij u de poort doorstuurt приложения parameter --publish, zoals bij de eerste start en zoals bij de omgekeerde proxy.

We zullen de reverse proxy op poort 80 draaien, omdat dit precies de entiteit is die naar het externe netwerk moet luisteren. Als poort 80 bezet is op uw testhost, wijzigt u de parameter --publish 80:80 op --publish ANY_FREE_PORT:80.

Interessante technieken

Afdrukken

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

Naadloze implementatie

Laten we een nieuwe versie van de applicatie uitrollen (met een tweevoudige prestatieverbetering bij het opstarten) en proberen deze naadloos te implementeren.

Interessante technieken

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' - Schrijf tekst my text indienen /my-file.txt in de container my-container.
  • cat > /my-file.txt — Schrijf de inhoud van standaardinvoer naar een bestand /dev/stdin.

Afdrukken

$ 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 dit stadium wordt de afbeelding rechtstreeks op de server gebouwd, waarvoor de applicatiebronnen aanwezig moeten zijn, en wordt de server ook met onnodig werk belast. De volgende stap is het toewijzen van de image-assembly aan een afzonderlijke machine (bijvoorbeeld aan een CI-systeem) en deze vervolgens overbrengen naar de server.

Afbeeldingen overbrengen

Helaas heeft het geen zin om afbeeldingen van localhost naar localhost over te zetten, dus deze sectie kan alleen worden verkend als je twee hosts met Docker bij de hand hebt. Het ziet er minimaal zo uit:

$ 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

Team docker save slaat de afbeeldingsgegevens op in een .tar-archief, wat betekent dat het ongeveer 1.5 keer meer weegt dan in gecomprimeerde vorm. Dus laten we het schudden in naam van het besparen van tijd en verkeer:

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

U kunt ook het downloadproces volgen (hoewel hiervoor een hulpprogramma van derden vereist):

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

Tip: Als je een aantal parameters nodig hebt om via SSH verbinding te maken met een server, gebruik je het bestand mogelijk niet ~/.ssh/config.

Het beeld overbrengen via docker image save/load - Dit is de meest minimalistische methode, maar niet de enige. Er zijn anderen:

  1. Containerregister (industriestandaard).
  2. Maak verbinding met de docker daemon-server vanaf een andere host:
    1. omgevingsvariabele DOCKER_HOST.
    2. Commandoregel optie -H of --host gereedschap docker-compose.
    3. docker context

De tweede methode (met drie opties voor de implementatie ervan) wordt goed beschreven in het artikel Hoe te implementeren op externe Docker-hosts met docker-compose.

deploy.sh

Laten we nu alles wat we handmatig hebben gedaan in één script verzamelen. Laten we beginnen met de functie op het hoogste niveau en vervolgens kijken naar de andere functies die daarin worden gebruikt.

Interessante technieken

  • ${parameter?err_msg} - een van de bash-magische spreuken (ook bekend als parametervervanging:). Als parameter niet gespecificeerd, uitvoer err_msg en sluit af met code 1.
  • docker --log-driver journald — standaard is het docker-logboekstuurprogramma een tekstbestand zonder enige rotatie. Met deze aanpak vullen de logs snel de hele schijf, dus voor een productieomgeving is het noodzakelijk om de driver te vervangen door een slimmer exemplaar.

Implementatiescript

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
}

Gebruikte functies:

  • ensure-reverse-proxy — Zorgt ervoor dat de reverse proxy werkt (handig voor de eerste implementatie)
  • get-active-slot service_name — Bepaalt welk slot momenteel actief is voor een bepaalde service (BLUE of GREEN)
  • get-service-status service_name deployment_slot — Bepaalt of de service gereed is om binnenkomende verzoeken te verwerken
  • set-active-slot service_name deployment_slot — Wijzigt de nginx-configuratie in de reverse proxy-container

In volgorde:

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
}

Functie get-active-slot vergt een kleine uitleg:

Waarom retourneert het een getal en geen string?

Hoe dan ook, in de aanroepende functie controleren we het resultaat van zijn werk, en het controleren van de exitcode met bash is veel eenvoudiger dan het controleren van een string. Bovendien is het heel eenvoudig om er een string uit te halen:
get-active-slot service && echo BLUE || echo GREEN.

Zijn drie voorwaarden werkelijk voldoende om alle toestanden te onderscheiden?

Blauw-Groene inzet tegen minimumlonen

Zelfs twee zijn voldoende, de laatste is hier alleen voor de volledigheid, om niet te schrijven else.

Alleen de functie die nginx-configuraties retourneert, blijft ongedefinieerd: get-nginx-config service_name deployment_slot. Naar analogie met de gezondheidscontrole kunt u hier elke configuratie voor elke service instellen. Van de interessante dingen - alleen cat <<- EOF, waarmee u in het begin alle tabbladen kunt verwijderen. Het is waar dat de prijs van goede opmaak bestaat uit gemengde tabbladen met spaties, wat tegenwoordig als een zeer slechte vorm wordt beschouwd. Maar bash dwingt tabbladen af, en het zou ook fijn zijn om normale opmaak te hebben in de nginx-configuratie. Kortom, het combineren van tabbladen met spaties lijkt hier echt de beste oplossing. Je zult dit echter niet zien in het onderstaande fragment, aangezien Habr “het goed doet” door alle tabbladen in 4 spaties te veranderen en EOF ongeldig te maken. En hier is het merkbaar.

Om niet twee keer op te staan, zal ik je er meteen over vertellen cat << 'EOF', die we later zullen tegenkomen. Als je eenvoudig schrijft cat << EOF, vervolgens wordt binnen heredoc de string geïnterpoleerd (variabelen worden uitgebreid ($foo), opdrachtaanroepen ($(bar)) enz.), en als u het einde van het document tussen enkele aanhalingstekens plaatst, wordt interpolatie uitgeschakeld en wordt het symbool $ wordt weergegeven zoals het is. Wat je nodig hebt om een ​​script in een ander script in te voegen.

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
}

Dit is het hele script. En dus met dit script om te downloaden via wget of curl.

Uitvoeren van geparametriseerde scripts op een externe server

Het is tijd om op de doelserver te kloppen. Deze keer localhost zeer geschikt:

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

We hebben een implementatiescript geschreven dat een vooraf gebouwde image naar de doelserver downloadt en de servicecontainer naadloos vervangt, maar hoe kunnen we dit uitvoeren op een externe machine? Het script heeft argumenten, omdat het universeel is en meerdere services tegelijk kan inzetten onder één omgekeerde proxy (je kunt nginx-configuraties gebruiken om te bepalen welke url welke service zal zijn). Het script kan niet op de server worden opgeslagen, omdat we het in dit geval niet automatisch kunnen bijwerken (met het oog op bugfixes en het toevoegen van nieuwe services), en in het algemeen geldt: state = evil.

Oplossing 1: Bewaar het script nog steeds op de server, maar kopieer het elke keer door scp. Maak dan verbinding via ssh en voer het script uit met de nodige argumenten.

Tegens:

  • Twee acties in plaats van één
  • Het kan zijn dat er geen plaats is waar u kopieert, of dat er geen toegang toe is, of dat het script wordt uitgevoerd op het moment van vervanging.
  • Het is raadzaam om zelf op te ruimen (verwijder het script).
  • Al drie acties.

Oplossing 2:

  • Bewaar alleen functiedefinities in het script en voer helemaal niets uit
  • Met sed voeg een functieaanroep toe aan het einde
  • Stuur het allemaal rechtstreeks naar shh via pipe (|)

Voors:

  • Echt staatloos
  • Geen standaardentiteiten
  • Je cool voelen

Laten we het gewoon doen zonder Ansible. Ja, alles is al uitgevonden. Ja, een fiets. Kijk hoe eenvoudig, elegant en minimalistisch de fiets is:

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

We kunnen er echter niet zeker van zijn dat de externe host voldoende bash heeft, dus zullen we aan het begin een kleine controle toevoegen (dit is in plaats van 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

En nu is het echt:

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

Nu kun je openen http://localhost/ Voer in de browser de implementatie opnieuw uit en zorg ervoor dat deze naadloos werkt door de pagina tijdens de lay-out bij te werken volgens de cd.

Vergeet niet op te ruimen na het werk :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

Bron: www.habr.com