Blue-Green Deployment in een notendop

In dit artikel gaan we met behulp van bash, ssh, docker en met alles wat daarin al aanwezig is, en de inhoud van de map een naadloze uitrol van een webapplicatie organiseren. Blue-green deployment is een techniek waarmee je een applicatie onmiddellijk kunt updaten zonder dat een enkele aanvraag wordt afgewezen. Het is een van de strategieƫn voor zero downtime deployment en is het beste geschikt voor applicaties met ƩƩn instantie, maar met de mogelijkheid om daarnaast een tweede, werkende instantie te laden.

Stel, je hebt een webapplicatie die door veel klanten actief wordt gebruikt, en het is absoluut niet mogelijk om deze enkele seconden stil te leggen. Maar je hebt dringend een bibliotheekupdate, een bugfix of een nieuwe coole functie nodig. In een normale situatie zou je de applicatie moeten stoppen, vervangen en opnieuw opstarten. Bij Docker kun je deze eerst vervangen en dan opnieuw opstarten, maar er zal altijd een periode zijn waarin aanvragen naar de applicatie niet worden verwerkt, aangezien een applicatie meestal enige tijd nodig heeft voor de initiƫle opstart. En wat als deze opgestart wordt, maar niet werkt? Laten we deze taak oplossen met minimale middelen en maximaal elegant.

DISCLAIMER: Het grootste deel van het artikel is gepresenteerd in een experimenteel formaat - in de vorm van een console-sessie-opname. Ik hoop dat dit niet te moeilijk te volgen is en dat deze code zichzelf voldoende documenteert. Stel je voor dat dit geen eenvoudige codefragmenten zijn, maar papier van een 'ijzeren' telegraaf.

Blue-Green Deployment in een notendop

Interessante technieken die moeilijk te vinden zijn door alleen maar de code te lezen, worden aan het begin van elk hoofdstuk beschreven. Als er iets onduidelijk is, zoek dan op en controleer dit in explainshell (gelukkig werkt het weer, aangezien Telegram weer toegankelijk is). Wat niet te vinden is, vraag het in de reacties. Ik vul het betreffende gedeelte 'Interessante technieken' graag aan.

Laten we beginnen.

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

Dienst

Laten we een testservice maken en deze in een container plaatsen.

Interessante technieken

  • cat < file-name (Here Document + I/O Redirection) - is een manier om een meerregelig bestand met ƩƩn opdracht te maken. Alles wat bash leest van /dev/stdin na deze regel en tot de regel EOF zal worden opgeslagen in file-name.
  • wget -qO- URL (explainshell) - om het ontvangen document via HTTP weer te geven in /dev/stdout (vergelijkbaar met curl URL).

Afdrukken

Ik snijd de snippet speciaal door om de markup voor Python in te schakelen. Aan het einde komt er nog zo'n stuk. Beschouw het alsof op deze plekken het papier is gesneden om overgedragen te worden aan de highlight afdeling (waar de code met hand gemarkeerd werd met markeerstiften), en daarna zijn deze stukken weer teruggeplakt.

$ cat < uptimer.py
van http.server import BaseHTTPRequestHandler, HTTPServer
van tijd import monotonic

app_versie = 1
app_naam = f'Uptimer v{app_versie}.0'
laden_seconden = 15 - app_versie * 5

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '':
            try:
                t = monotonic() - server_start
                if t &lt; laden_seconden:
                    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_naam} draait al {t:3.1f} seconden.</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_naam} (laadt in {laden_seconden} sec.) gestart.')
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 .
Verzendt buildcontext naar de Docker-daemon  39.42kB
Stap 1/4 : FROM python:alpine
 ---&gt; 8ecf5a48c789
Stap 2/4 : EXPOSE 8080
 ---&gt; Gebruik cache
 ---&gt; cf92d174c9d3
Stap 3/4 : COPY uptimer.py app.py
 ---&gt; a7fbb33d6b7e
Stap 4/4 : CMD [ "python", "-u", ".\/app.py" ]
 ---&gt; Running in 1906b4bd9fdf
Verwijdert tussenliggende container 1906b4bd9fdf
 ---&gt; c1655b996fe8
Succesvol gebouwd c1655b996fe8
Succesvol getagd 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 seconden geleden       Op 5 seconden        0.0.0.0:8080-&gt;8080/tcp   uptimer

$ docker logs uptimer
Uptimer v1.0 (laadt in 10 sec.) gestart.

$ wget -qSO- http:\/\/localhost:8080
  HTTP\/1.0 503 Service Unavailable
  Server: BaseHTTP\/0.6 Python\/3.8.3
  Date: za, 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: za, 22 aug 2020 19:52:45 GMT
  Content-Type: text\/html
<h2>Uptimer v1.0 draait al 15,4 seconden.</h2>

$ docker rm --force uptimer
uptimer

Reverse proxy

Om ons applicatie eindelijk onopgemerkt te kunnen veranderen, moet er een andere entiteit voor staan die de vervangingen verbergt. Dit kan een webserver zijn. met alles wat daarin al aanwezig is, en de inhoud van de map in in reverse proxy modus. De reverse proxy staat tussen de client en de applicatie. Het accepteert verzoeken van clients en stuurt deze door naar de applicatie, terwijl het de antwoorden van de applicatie weer naar de clients stuurt.

De applicatie en de reverse proxy kunnen binnen Docker worden verbonden met behulp van docker network. Op deze manier hoeft de container met de applicatie geen poort in het host-systeem door te geven, wat het mogelijk maakt om de applicatie maximaal te isoleren van bedreigingen van buitenaf.

Als de reverse proxy op een andere host draait, moet je docker network opgeven en de applicatie met de reverse proxy verbinden via het hostnetwerk door de poort door te geven. applicatie parameter --publish, net als bij de eerste opstart en zoals bij de reverse proxy.

We zullen de reverse proxy op poort 80 draaien, omdat dit precies de entiteit is die naar de buitenwereld moet luisteren. Als poort 80 op jouw testhost al bezet is, wijzig dan de parameter. --publish 80:80 en een werkende opdracht krijgen. --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 draait al 11,5 seconden.</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 seconden geleden       Up 25 seconden       0.0.0.0:80-&gt;80\/tcp   reverse-proxy
a1105f1b583d        uptimer             "python -u .\/app.py"     Ongeveer een minuut geleden   Up Ongeveer een minuut   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: Za, 22 Aug 2020 19:56:24 GMT
  Content-Type: text\/html
  Transfer-Encoding: chunked
  Connection: keep-alive
<h2>Uptimer v1.0 draait al 104,1 seconden.</h2>

Naadloze implementatie

We zullen een nieuwe versie van de applicatie uitrollen (met een tweevoudige boost in startup-prestaties) 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 naar bestand /my-file.txt binnen de container my-container.
  • cat > /my-file.txt — Schrijf de inhoud van de standaardinvoer naar een bestand /dev/stdin.

Afdrukken

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

$ docker build --tag uptimer .
Verzending bouwcontext naar Docker daemon  39.94kB
Stap 1/4 : VAN python:alpine
 ---&gt; 8ecf5a48c789
Stap 2/4 : EXPOSE 8080
 ---&gt; Hierbij wordt cache gebruikt
 ---&gt; cf92d174c9d3
Stap 3/4 : KOPIEER uptimer.py app.py
 ---&gt; 3eca6a51cb2d
Stap 4/4 : CMD [ "python", "-u", ".\/app.py" ]
 ---&gt; Uitvoering in 8f13c6d3d9e7
Verwijdering van tussencontainer 8f13c6d3d9e7
 ---&gt; 1d56897841ec
Met succes gebouwd 1d56897841ec
Met succes getagd uptimer:latest

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

$ docker logs uptimer_BLUE
Uptimer v2.0 (laadt in 5 sec.) gestart.

$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 draait nu al 23,9 seconden.</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 {
    luister 80;
    locatie / {
        proxy_pass http://uptimer_BLUE:8080;
    }
}

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

$ wget -qO- http://localhost
<h2>Uptimer v2.0 draait nu al 63,4 seconden.</h2>

$ docker rm -f uptimer
uptimer

$ wget -qO- http://localhost
<h2>Uptimer v2.0 draait nu al 84,8 seconden.</h2>

$ docker ps
CONTAINER-ID        IMAGE               COMMAND                  AANGEMAAKT          STATUS              POORTEN                NAAM
96932d4ca97a        uptimer             "python -u .\/app.py"     Ongeveer een minuut geleden   Actief Ongeveer een minuut   8080/tcp             uptimer_BLUE
80695a822c19        nginx:alpine        "\/docker-entrypoint.…"   8 minuten geleden        Actief 8 minuten        0.0.0.0:80-&gt;80/tcp   reverse-proxy

In deze fase wordt het image direct op de server gebouwd, wat vereist dat de bronschriften daar beschikbaar zijn, en het belast de server met onnodig werk. De volgende stap zal zijn om de constructie van het image naar een aparte machine (bijvoorbeeld in een CI-systeem) te verplaatsen en het daarna naar de server te verzenden.

Images overzetten

Helaas heeft het weinig zin om afbeeldingen van localhost naar localhost over te zetten, dus deze sectie kan alleen worden verkend als je twee hosts met Docker tot je beschikking hebt. In de minimalistische opzet ziet het er ongeveer 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

Opdracht docker save slaat de gegevens van de afbeelding op in een .tar-archief, wat betekent dat het ongeveer 1,5 keer zoveel weegt als het in gecomprimeerde vorm zou wegen. Laten we het daarom comprimeren om tijd en bandbreedte te besparen:

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

Daarnaast kun je het overdrachtsproces volgen (hiervoor heb je echter een externe tool nodig):

$ 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 hoop parameters nodig hebt voor de SSH-verbinding met de server, gebruik je mogelijk het bestand niet ~/.ssh/config.

Overdracht van afbeeldingen via docker image save/load is de meest minimalistische methode, maar niet de enige. Er zijn ook andere:

  1. Container Registry (de industrie standaard).
  2. Verbind met de Docker-daemon van de server vanaf een andere host:
    1. Omgevingsvariabele DOCKER_HOST.
    2. Opdrachtregelparameter -H of --host tool docker-compose.
    3. docker context

De tweede methode (met drie implementatievarianten) is goed beschreven in het artikel How to deploy on remote Docker hosts with docker-compose.

deploy.sh

Laten we alles wat we handmatig hebben gedaan samenvoegen in ƩƩn script. We beginnen met de top-level functie en kijken dan naar de andere die daarin worden gebruikt.

Interessante technieken

  • ${parameter?err_msg} is een van de spreuken van bash-magic (aka parameter substitution). Als parameter niet is opgegeven, geef dan err_msg weer en verlaat met code 1.
  • docker --log-driver journald is standaard de logging driver van Docker en dat is een tekstbestand zonder enige rotatie. Met deze aanpak vullen de logs snel de schijf, dus voor een productieomgeving is het noodzakelijk om de driver te vervangen door een slimmere.

Deployment script

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 deployment)
  • get-active-slot service_name — Bepaalt welke slot momenteel actief is voor de opgegeven service (BLAUW of GROEN)
  • get-service-status service_name deployment_slot — Bepaalt of de service klaar is om inkomende aanvragen te verwerken
  • set-active-slot service_name deployment_slot — Wijzigt de nginx-configuratie in de reverse proxy container

Volgorde:

ensure-reverse-proxy() {
    is-container-up reverse-proxy && return 0
    echo "Reverse-proxy aan het implementeren..."
    docker network create web-gateway
    docker run 
        --detach 
        --restart altijd 
        --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?"Gebruik: ${FUNCNAME[0]} container_naam"}

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

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

    if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
        echo "Botsing gedetecteerd! ${service}_GREEN stoppen..."
        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="Gebruik: ${FUNCNAME[0]} service_naam deployment_slot"
    local service=${1?usage_msg}
    local slot=${2?$usage_msg}

    case $service in
        # Voeg specifieke healthcheck-paden voor uw services hier toe
        *) local health_check_port_path=":8080/" ;;
    esac
    local health_check_address="http://${service}_${slot}${health_check_port_path}"
    echo "Verzoek om '$health_check_address' binnen het 'web-gateway' docker netwerk:"
    docker run --rm --network web-gateway alpine 
        wget --timeout=1 --quiet --server-response $health_check_address
    return $?
}

set-active-slot() {
    local usage_msg="Gebruik: ${FUNCNAME[0]} service_naam 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 vereist enige toelichting:

Waarom retourneert het een nummer en geeft het geen string weer?

Toch controleren we het resultaat in de aanroepende functie; het is veel eenvoudiger om de exitcode in bash te controleren dan een string. Bovendien is het heel eenvoudig om een string eruit te krijgen:
get-active-slot service && echo BLUE || echo GREEN.

Hebben we echt drie voorwaarden nodig om alle toestanden te onderscheiden?

Blue-Green Deployment in een notendop

Twee zijn zelfs voldoende; de laatste is hier gewoon voor de volledigheid, zodat we niet hoeven te schrijven. else.

De functie die de nginx-configuraties retourneert is nog onbepaald: get-nginx-config service_naam deployment_slot. Net als bij de healthcheck kan hier elke configuratie voor elke service worden ingesteld. Wat interessant is — alleen cat <<- EOF, waardoor alle tabs aan het begin kunnen worden verwijderd. De prijs van nette opmaak is echter gemengde tabs met spaties, wat tegenwoordig als zeer onfatsoenlijk wordt beschouwd. Maar bash dwingt tabs af, en het zou ook prettig zijn om normale opmaak in de nginx-configuratie te hebben. Kortom, hier lijkt de mix van tabs en spaties echt de beste oplossing van de slechtste opties te zijn. Echter, in de onderstaande snippet zult u dit niet zien, omdat habr 'het goed doet', door alle tabs te vervangen door 4 spaties en EOF ongeldig te maken. En hier is goed zichtbaar.

Om twee keer te voorkomen dat ik dezelfde informatie geef, zal ik direct vertellen over cat << 'EOF', dat verderop ook nog zal voorkomen. Als je gewoon schrijft cat << EOF, dan vindt er binnen de heredoc interpolatie van de string plaats (variabelen worden onthuld ($foo), opdrachtoproepen ($(bar)) enzovoort), en als je het eindteken van het document tussen enkele aanhalingstekens plaatst, dan wordt de interpolatie uitgeschakeld en het teken $ wordt letterlijk weergegeven. Dat is wat je nodig hebt om een script in een ander script in te voegen.

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

    local container_name=${service}_${slot}
    case $service in
        # Voeg specifieke nginx-configs voor uw diensten hier toe
        *) nginx-config-simple-service $container_name:8080 ;;
    esac
}

nginx-config-simple-service() {
    local usage_msg="Gebruik: ${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 volledige script. En hier is de gist met dit script om te downloaden via wget of curl.

Het uitvoeren van geparameteriseerde scripts op een externe server

Het is tijd om in te loggen op de doelserver. Dit keer localhost is totaal geschikt:

$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: probeert in te loggen met de nieuwe sleutel(els), om alle reeds geĆÆnstalleerde te filteren
/usr/bin/ssh-copy-id: INFO: 1 sleutel(s) blijven om geĆÆnstalleerd te worden -- als u nu wordt gevraagd, is het om de nieuwe sleutels te installeren
himura@localhost's wachtwoord: 

Aantal toegevoegde sleutel(s): 1

Probeer nu in te loggen op de machine met:   "ssh 'localhost'"
en controleer of alleen de sleutel(s) die je wilde, zijn toegevoegd.

We wrote a deployment script that transfers a pre-compiled image to the target server and seamlessly replaces the service container, but how do we execute it on a remote machine? The script has arguments since it is versatile and can deploy multiple services under one reverse proxy (you can manage which service connects to which URL using nginx configurations). The script cannot be stored on the server because we won't be able to automatically update it (for bug fixes and adding new services), and generally, state = evil.

Solution 1: Store the script on the server but copy it each time via scp. Then connect via ssh and execute the script with the necessary arguments.

Nadelen:

  • Two actions instead of one.
  • The destination where you copy may not exist, or there may not be access, or the script may execute during the replacement.
  • It's advisable to clean up afterwards (delete the script).
  • Now three actions.

Solution 2:

  • Keep only function definitions in the script and don't execute anything.
  • Met sed Append a function call at the end.
  • Send all of this directly to shh through a pipe (|)

Voordelen:

  • Truly stateless.
  • No boilerplate entities.
  • Feeling cool.

Let's just avoid Ansible. Yes, everything has already been invented. Yes, the bicycle. Look at this simple, elegant, and minimalist bicycle:

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

However, we cannot be certain that there is an adequate bash on the remote host, so let's add a small check at the beginning (this instead of 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

And now everything is real:

$ 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
Gezipte afbeelding 'uptimer' naar 'localhost' verzenden via ssh...
Afbeelding geladen: uptimer:latest
Verbinden met 'localhost' via ssh om 'uptimer' naadloos te implementeren...
'Uptimer_GREEN' implementeren ter vervanging van 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Container gestart. Gezondheid controleren...
'http://uptimer_GREEN:8080/' aanvragen binnen het 'web-gateway' docker netwerk:
  HTTP/1.0 503 Service Unavailable
wget: server gaf fout terug: HTTP/1.0 503 Service Unavailable
Nieuwe 'uptimer_GREEN' service is nog niet klaar. Wachten (1)...
'http://uptimer_GREEN:8080/' aanvragen binnen het 'web-gateway' docker netwerk:
  HTTP/1.0 503 Service Unavailable
wget: server gaf fout terug: HTTP/1.0 503 Service Unavailable
Nieuwe 'uptimer_GREEN' service is nog niet klaar. Wachten (2)...
'http://uptimer_GREEN:8080/' aanvragen binnen het 'web-gateway' docker netwerk:
  HTTP/1.0 200 OK
  Server: BaseHTTP/0.6 Python/3.8.3
  Datum: za, 22 aug 2020 20:15:50 GMT
  Content-Type: text/html

Nieuwe 'uptimer_GREEN' service lijkt OK. Overstappen...
nginx: de configuratiebestanden /etc/nginx/nginx.conf syntaxis is ok
nginx: configuratiebestand /etc/nginx/nginx.conf test is succesvol
2020/08/22 20:15:54 [notice] 97#97: signaalproces gestart
De 'uptimer_GREEN' service is live!
'Uptimer_BLUE' beƫindigen...
uptimer_BLUE
Totaal teruggewonnen ruimte: 0B
Implementatie succesvol!

Nu kan je openen http://localhost/ in de browser, de implementatie nogmaals uitvoeren en controleren of deze naadloos verloopt door de pagina te verversen terwijl je aan het uitrollen bent.

Vergeet niet om 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: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers šŸ”„ Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster