Blau-grien ynset by minimumleanen

Yn dit artikel brûke wy bash, ssh, docker и nginx Wy sille in naadleaze yndieling fan 'e webapplikaasje organisearje. Blau-griene ynset is in technyk wêrmei jo in applikaasje daliks bywurkje kinne sûnder ien inkeld fersyk te wegerjen. It is ien fan 'e nul-downtime-ynsetstrategyen en is it bêste geskikt foar applikaasjes mei ien eksimplaar, mar de mooglikheid om in twadde, klear te rinnen eksimplaar tichtby te laden.

Litte wy sizze dat jo in webapplikaasje hawwe wêrmei in protte kliïnten aktyf wurkje, en d'r is perfoarst gjin manier om it in pear sekonden te lizzen. En jo moatte wirklik in biblioteekfernijing, in bugfix of in nije koele funksje útrolje. Yn in normale situaasje moatte jo de applikaasje stopje, ferfange en opnij begjinne. Yn it gefal fan docker kinne jo it earst ferfange, dan opnij starte, mar d'r sil noch in perioade wêze wêryn fersiken nei de applikaasje net wurde ferwurke, om't normaal de applikaasje wat tiid nimt om earst te laden. Wat as it begjint, mar blykt net te wurkjen? Dit is it probleem, litte wy it oplosse mei minimale middels en sa elegant mooglik.

DISCLAIMER: It grutste part fan it artikel wurdt presintearre yn in eksperiminteel formaat - yn 'e foarm fan in opname fan in konsole sesje. Hooplik sil dit net te dreech wêze om te begripen en sil de koade himsels genôch dokumintearje. Foar sfear, yntinke dat dit net allinnich koade snippets, mar papier fan in "izeren" teletype.

Blau-grien ynset by minimumleanen

Nijsgjirrige techniken dy't lestich binne foar Google troch gewoan de koade te lêzen wurde beskreaun oan it begjin fan elke seksje. As der noch wat ûndúdlik is, google it dan en kontrolearje it. útlisshell (gelokkich wurket it wer, troch it ûntsluten fan it telegram). As jo ​​neat kinne Google, freegje dan yn 'e kommentaren. Ik sil graach tafoegje oan de oerienkommende seksje "Ynteressante techniken".

Litte wy begjinne.

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

tsjinst

Litte wy in eksperimintele tsjinst meitsje en it yn in kontener pleatse.

Nijsgjirrige techniken

  • cat << EOF > file-name (Hjir dokumint + I/O Omlieding) is in manier om in multi-line triem te meitsjen mei ien kommando. Alles bash lêst fan /dev/stdin nei dizze line en foar de line EOF sil wurde opnommen yn file-name.
  • wget -qO- URL (útlisshell) - útfier in dokumint ûntfongen fia HTTP nei /dev/stdout (analooch curl URL).

Ofdruk

Ik brekke spesifyk it snippet om markearring foar Python yn te skeakeljen. Oan 'e ein komt der noch in stik as dit. Tink derom dat op dizze plakken it papier knipt waard om te stjoeren nei de markearringôfdieling (dêr't de koade mei de hân kleure wie mei markearders), en dan waarden dizze stikken werom lijm.

$ 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

Reverse proxy

Om ús applikaasje ûngemurken te kinne feroarje, is it needsaaklik dat d'r in oare entiteit foar is dy't har ferfanger sil ferbergje. It kin in webserver wêze nginx в reverse proxy modus. In reverse proxy wurdt oprjochte tusken de kliïnt en de applikaasje. It akseptearret oanfragen fan kliïnten en stjoert se troch nei de applikaasje en stjoert de antwurden fan 'e applikaasje troch nei de kliïnten.

De applikaasje en reverse proxy kinne wurde keppele binnen docker mei help fan docker netwurk. Sa hoecht de kontener mei de applikaasje net iens in poarte op it hostsysteem troch te stjoeren; dit lit de applikaasje maksimaal isolearje fan eksterne bedrigingen.

As de reverse proxy op in oare host libbet, moatte jo it docker-netwurk ferlitte en de applikaasje ferbine mei de reverse proxy fia it hostnetwurk, troch de poarte troch te stjoeren apps parameter --publish, lykas by de earste start en lykas by de reverse proxy.

Wy sille de omkearde proxy útfiere op poarte 80, om't dit krekt de entiteit is dy't nei it eksterne netwurk harkje moat. As poarte 80 drok is op jo testhost, feroarje de parameter --publish 80:80 op --publish ANY_FREE_PORT:80.

Nijsgjirrige techniken

Ofdruk

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

Naadleaze ynset

Litte wy in nije ferzje fan 'e applikaasje útrolje (mei in ympuls foar opstartprestaasjes fan twa kear) en besykje it naadloos yn te setten.

Nijsgjirrige techniken

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' - Skriuw tekst my text opslaan /my-file.txt binnen de kontener my-container.
  • cat > /my-file.txt - Skriuw de ynhâld fan standert ynfier nei in bestân /dev/stdin.

Ofdruk

$ 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

Op dit poadium wurdt de ôfbylding direkt op 'e tsjinner boud, wat fereasket dat de applikaasjeboarnen dêr binne, en ek de tsjinner mei ûnnedich wurk laden. De folgjende stap is om de ôfbyldingsassemblage oan in aparte masine te allocearjen (bygelyks nei in CI-systeem) en it dan oer te bringen nei de tsjinner.

Ofbyldings oerdrage

Spitigernôch hat it gjin sin om ôfbyldings fan localhost nei localhost oer te bringen, dus dizze seksje kin allinich ferkend wurde as jo twa hosts hawwe mei Docker by de hân. Op syn minst sjocht it der sa út:

$ 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 bewarret de ôfbylding gegevens yn in .tar argyf, wat betsjut dat it waacht oer 1.5 kear mear as it soe weagje yn komprimearre foarm. Dat litte wy it skodzje yn 'e namme fan it besparjen fan tiid en ferkear:

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

Jo kinne ek it ynlaadproses kontrolearje (hoewol dit in hulpprogramma fan tredden fereasket):

$ 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: As jo ​​​​in boskje parameters nedich binne om te ferbinen mei in server fia SSH, kinne jo it bestân net brûke ~/.ssh/config.

Oerdracht fan de ôfbylding fia docker image save/load - Dit is de meast minimalistyske metoade, mar net de ienige. Der binne oaren:

  1. Container Registry (yndustry standert).
  2. Ferbine mei docker daemon-tsjinner fan in oare host:
    1. omjouwing fariabele DOCKER_HOST.
    2. Kommandorigel opsje -H of --host tool docker-compose.
    3. docker context

De twadde metoade (mei trije opsjes foar syn útfiering) wurdt goed beskreaun yn it artikel Hoe kinne jo ynsette op Docker-hosts op ôfstân mei docker-compose.

deploy.sh

Litte wy no alles wat wy mei de hân diene sammelje yn ien skript. Litte wy begjinne mei de funksje op boppeste nivo, en sjoch dan nei de oaren dy't dêryn brûkt wurde.

Nijsgjirrige techniken

  • ${parameter?err_msg} - ien fan 'e bash magyske spreuken (aka parameter ferfanging). As parameter net oantsjutte, útfier err_msg en gean út mei koade 1.
  • docker --log-driver journald - standert is de docker-logging-bestjoerder in tekstbestân sûnder rotaasje. Mei dizze oanpak folje de logs fluch de hiele skiif op, dus foar in produksjeomjouwing is it nedich om de bestjoerder te feroarjen nei in slimmer.

Ynset skript

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
}

Funksjes brûkt:

  • ensure-reverse-proxy - Soargje derfoar dat de omkearde proxy wurket (nuttich foar de earste ynset)
  • get-active-slot service_name - Bepaalt hokker slot op it stuit aktyf is foar in opjûne tsjinst (BLUE of GREEN)
  • get-service-status service_name deployment_slot - Bepaalt oft de tsjinst ree is om ynkommende oanfragen te ferwurkjen
  • set-active-slot service_name deployment_slot - Feroaret de nginx-konfiguraasje yn 'e omkearde proxy-kontainer

Yn opdracht:

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
}

function get-active-slot freget in lytse útlis:

Wêrom jout it in getal werom en gjin tekenrige út?

Hoe dan ek, yn 'e opropfunksje kontrolearje wy it resultaat fan har wurk, en it kontrolearjen fan útgongskoade mei bash is folle makliker dan it kontrolearjen fan in tekenrige. Derneist is it heul ienfâldich om in tekenrige te krijen:
get-active-slot service && echo BLUE || echo GREEN.

Binne trije betingsten echt genôch om alle steaten te ûnderskieden?

Blau-grien ynset by minimumleanen

Sels twa sille genôch wêze, de lêste is hjir gewoan foar folsleinens, om net te skriuwen else.

Allinich de funksje dy't nginx-konfiguraasjes werombringt bliuwt ûndefinieare: get-nginx-config service_name deployment_slot. Nei analogy mei sûnenskontrôle kinne jo hjir elke konfiguraasje ynstelle foar elke tsjinst. Fan 'e nijsgjirrige dingen - allinich cat <<- EOF, wêrmei jo alle ljeppers oan it begjin kinne fuortsmite. Wier, de priis fan goede opmaak is mingde ljeppers mei spaasjes, dy't hjoeddedei wurdt beskôge as heul minne foarm. Mar bash twingt ljeppers, en it soe ek moai wêze om normale opmaak te hawwen yn 'e nginx-konfiguraasje. Koartsein, it mingjen fan ljeppers mei spaasjes hjir liket echt de bêste oplossing út it minste. Jo sille dit lykwols net sjen yn it snippet hjirûnder, om't Habr "it goed docht" troch alle ljeppers te feroarjen nei 4 spaasjes en EOF ûnjildich te meitsjen. En hjir is it te merken.

Om net twa kear oerein te kommen, fertel ik jo daliks oer cat << 'EOF', dy't letter tsjinkomme sil. As jo ​​gewoan skriuwe cat << EOF, dan binnen heredoc wurdt de tekenrige ynterpolearre (fariabelen wurde útwreide ($foo), opdrachtoproppen ($(bar)) ensfh.), en as jo it ein fan it dokumint ynslute yn inkele quotes, dan is ynterpolaasje útskeakele en it symboal $ wurdt werjûn lykas is. Wat jo nedich hawwe om in skript yn in oar skript yn te foegjen.

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 it hiele skript. Ensa essinsje mei dit skript foar ynladen fia wget of curl.

It útfieren fan parameterisearre skripts op in tsjinner op ôfstân

It is tiid om op 'e doeltsjinner te klopjen. Dizze kear localhost hiel geskikt:

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

Wy hawwe in ynsetskript skreaun dat in foarboude ôfbylding downloade nei de doeltsjinner en de tsjinstkontainer naadloos ferfangt, mar hoe kinne wy ​​it útfiere op in masine op ôfstân? It skript hat arguminten, om't it universeel is en ferskate tsjinsten tagelyk kin ynsette ûnder ien reverse proxy (jo kinne nginx-konfiguraasjes brûke om te bepalen hokker url hokker tsjinst sil wêze). It skript kin net opslein wurde op 'e tsjinner, om't wy yn dit gefal it net automatysk kinne bywurkje (foar it doel fan bug fixes en it tafoegjen fan nije tsjinsten), en yn it algemien, state = evil.

Oplossing 1: Bewarje it skript noch op 'e tsjinner, mar kopiearje it elke kear troch scp. Ferbine dan fia ssh en it skript útfiere mei de nedige arguminten.

Cons:

  • Twa aksjes ynstee fan ien
  • D'r kin gjin plak wêze wêr't jo kopiearje, of d'r is miskien gjin tagong ta it, of it skript kin wurde útfierd op it momint fan ferfanging.
  • It is oan te rieden om nei josels op te romjen (it skript wiskje).
  • Al trije aksjes.

Oplossing 2:

  • Hâld allinnich funksje definysjes yn it skript en útfiere neat
  • Mei help fan sed foegje in funksje oprop oan 'e ein
  • Stjoer it allegear direkt nei shh fia pipe (|)

Pros:

  • Echt steateleas
  • Gjin boilerplate entiteiten
  • Cool fiele

Litte wy it gewoan dwaan sûnder Ansible. Ja, alles is al útfûn. Ja, in fyts. Sjoch hoe ienfâldich, elegant en minimalistysk de fyts 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'

Wy kinne lykwols net wis wêze dat de host op ôfstân adekwate bash hat, dus sille wy in lyts kontrôle oan it begjin tafoegje (dit is ynstee fan 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 no is it 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!

No kinne jo iepenje http://localhost/ yn 'e browser, rinne de ynset nochris en soargje derfoar dat it soepel rint troch de side te aktualisearjen neffens de CD tidens de opmaak.

Ferjit net om nei it wurk op te romjen: 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

Boarne: www.habr.com