Vendosja blu-gjelbër me paga minimale

Në këtë artikull ne përdorim përplas, ssh, cungues и nginx Ne do të organizojmë një paraqitje pa probleme të aplikacionit në internet. Vendosja blu-jeshile është një teknikë që ju lejon të përditësoni në çast një aplikacion pa refuzuar një kërkesë të vetme. Është një nga strategjitë e vendosjes me kohë joproduktive dhe është më e përshtatshme për aplikacionet me një shembull, por aftësinë për të ngarkuar një shembull të dytë, të gatshëm për të ekzekutuar afër.

Le të themi se keni një aplikacion në internet me të cilin shumë klientë punojnë në mënyrë aktive dhe nuk ka absolutisht asnjë mënyrë që ai të shtrihet për disa sekonda. Dhe vërtet duhet të lëshoni një përditësim të bibliotekës, një rregullim të gabimeve ose një veçori të re të lezetshme. Në një situatë normale, do t'ju duhet të ndaloni aplikacionin, ta zëvendësoni dhe ta filloni përsëri. Në rastin e docker-it, fillimisht mund ta zëvendësoni, më pas ta rinisni, por do të ketë ende një periudhë në të cilën kërkesat për aplikacionin nuk do të përpunohen, sepse zakonisht aplikacioni kërkon pak kohë për t'u ngarkuar fillimisht. Po sikur të fillojë, por të rezultojë se nuk funksionon? Ky është problemi, le ta zgjidhim me mjete minimale dhe sa më elegante.

MOFIMI: Shumica e artikullit është paraqitur në një format eksperimental - në formën e një regjistrimi të një sesioni konsol. Shpresojmë se kjo nuk do të jetë shumë e vështirë për t'u kuptuar dhe kodi do të dokumentohet mjaftueshëm. Për atmosferën, imagjinoni që këto nuk janë vetëm copa kodi, por letra nga një teletip "hekuri".

Vendosja blu-gjelbër me paga minimale

Teknikat interesante që janë të vështira për t'u Google vetëm duke lexuar kodin përshkruhen në fillim të çdo seksioni. Nëse ndonjë gjë tjetër është e paqartë, kërkojeni në google dhe kontrollojeni. shpjegon shell (për fat funksionon sërish, për shkak të zhbllokimit të telegramit). Nëse nuk mund të kërkoni asgjë në Google, pyesni në komente. Do të jem i lumtur të shtoj në seksionin përkatës "Teknika interesante".

Le të fillojmë.

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

Shërbim

Le të bëjmë një shërbim eksperimental dhe ta vendosim në një enë.

Teknika interesante

  • cat << EOF > file-name (Këtu Dokumenti + Ridrejtimi I/O) është një mënyrë për të krijuar një skedar me shumë rreshta me një komandë. Gjithçka bash lexon nga /dev/stdin pas kësaj rreshti dhe para vijës EOF do të regjistrohet në file-name.
  • wget -qO- URL (shpjegon shell) — nxirrni një dokument të marrë nëpërmjet HTTP në /dev/stdout (analog curl URL).

Printim

Unë posaçërisht thyej fragmentin për të mundësuar theksimin për Python. Në fund do të ketë një tjetër pjesë si kjo. Konsideroni që në këto vende letra ishte prerë për t'u dërguar në departamentin e theksimit (ku kodi ishte i ngjyrosur me dorë me theksues), dhe më pas këto pjesë u ngjitën pas.

$ 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

Përfaqësues i kundërt

Në mënyrë që aplikacioni ynë të mund të ndryshojë pa u vënë re, është e nevojshme që të ketë një ent tjetër përpara tij që do të fshehë zëvendësimin e tij. Mund të jetë një server në internet nginx в modaliteti i përfaqësuesit të kundërt. Ndërmjet klientit dhe aplikacionit krijohet një përfaqësues i kundërt. Ai pranon kërkesat nga klientët dhe i përcjell ato në aplikacion dhe ua përcjell përgjigjet e aplikacionit klientëve.

Aplikacioni dhe përfaqësuesi i kundërt mund të lidhen brenda dokerit duke përdorur rrjeti doker. Kështu, kontejneri me aplikacionin nuk ka nevojë as të përcjellë një port në sistemin pritës; kjo lejon që aplikacioni të izolohet maksimalisht nga kërcënimet e jashtme.

Nëse përfaqësuesi i kundërt jeton në një host tjetër, do t'ju duhet të braktisni rrjetin docker dhe të lidhni aplikacionin me përfaqësuesin e kundërt përmes rrjetit pritës, duke përcjellë portin Apps parametri --publish, si në fillimin e parë dhe si me përfaqësuesin e kundërt.

Ne do të ekzekutojmë përfaqësuesin e kundërt në portin 80, sepse ky është pikërisht entiteti që duhet të dëgjojë rrjetin e jashtëm. Nëse porta 80 është e zënë në hostin tuaj të testimit, ndryshoni parametrin --publish 80:80 mbi --publish ANY_FREE_PORT:80.

Teknika interesante

Printim

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

Vendosja pa probleme

Le të nxjerrim një version të ri të aplikacionit (me një rritje të dyfishtë të performancës së fillimit) dhe të përpiqemi ta vendosim atë pa probleme.

Teknika interesante

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' - Shkruani tekst my text për të paraqitur /my-file.txt brenda enës my-container.
  • cat > /my-file.txt — Shkruani përmbajtjen e hyrjes standarde në një skedar /dev/stdin.

Printim

$ 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

Në këtë fazë, imazhi ndërtohet direkt në server, i cili kërkon që burimet e aplikacionit të jenë aty, dhe gjithashtu ngarkon serverin me punë të panevojshme. Hapi tjetër është ndarja e montimit të imazhit në një makinë të veçantë (për shembull, në një sistem CI) dhe më pas transferimi i tij në server.

Transferimi i imazheve

Fatkeqësisht, nuk ka kuptim të transferoni imazhe nga localhost në localhost, kështu që ky seksion mund të eksplorohet vetëm nëse keni dy hoste me Docker në dorë. Të paktën duket diçka si kjo:

$ 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

Ekip docker save ruan të dhënat e imazhit në një arkiv .tar, që do të thotë se peshon rreth 1.5 herë më shumë se sa do të peshonte në formë të ngjeshur. Pra, le ta tundim atë në emër të kursimit të kohës dhe trafikut:

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

Ju gjithashtu mund të monitoroni procesin e shkarkimit (megjithëse kjo kërkon një mjet të palës së tretë):

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

Këshillë: Nëse keni nevojë për një sërë parametrash për t'u lidhur me një server nëpërmjet SSH, mund të mos jeni duke përdorur skedarin ~/.ssh/config.

Transferimi i imazhit nëpërmjet docker image save/load - Kjo është metoda më minimaliste, por jo e vetmja. Ka të tjera:

  1. Regjistri i kontejnerëve (standardi i industrisë).
  2. Lidhu me serverin docker daemon nga një host tjetër:
    1. variabli i mjedisit DOCKER_HOST.
    2. Opsioni i linjës së komandës -H ose --host instrument docker-compose.
    3. docker context

Metoda e dytë (me tre opsione për zbatimin e saj) përshkruhet mirë në artikull Si të vendoset në hostet e largët të Docker me docker-compose.

deploy.sh

Tani le të mbledhim gjithçka që bëmë me dorë në një skenar. Le të fillojmë me funksionin e nivelit të lartë dhe më pas të shohim të tjerët që përdoren në të.

Teknika interesante

  • ${parameter?err_msg} - një nga magjitë magjike bash (aka zëvendësimi i parametrave) Nëse parameter nuk është specifikuar, prodhim err_msg dhe dilni me kodin 1.
  • docker --log-driver journald — si parazgjedhje, drejtuesi i regjistrimit të docker është një skedar teksti pa asnjë rrotullim. Me këtë qasje, regjistrat mbushin shpejt të gjithë diskun, kështu që për një mjedis prodhimi është e nevojshme të ndryshoni drejtuesin në një më të zgjuar.

Skripti i vendosjes

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
}

Karakteristikat e përdorura:

  • ensure-reverse-proxy — Sigurohet që përfaqësuesi i kundërt të funksionojë (i dobishëm për vendosjen e parë)
  • get-active-slot service_name — Përcakton se cila slot është aktualisht aktive për një shërbim të caktuar (BLUE ose GREEN)
  • get-service-status service_name deployment_slot — Përcakton nëse shërbimi është gati për të përpunuar kërkesat hyrëse
  • set-active-slot service_name deployment_slot — Ndryshon konfigurimin nginx në kontejnerin e përfaqësuesit të kundërt

Në mënyrë që:

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
}

Funksion get-active-slot kërkon pak shpjegim:

Pse kthen një numër dhe nuk nxjerr një varg?

Gjithsesi, në funksionin e thirrjes ne kontrollojmë rezultatin e punës së tij dhe kontrollimi i kodit të daljes duke përdorur bash është shumë më i lehtë sesa kontrollimi i një vargu. Për më tepër, marrja e një vargu prej tij është shumë e thjeshtë:
get-active-slot service && echo BLUE || echo GREEN.

A mjaftojnë vërtet tre kushte për të dalluar të gjitha shtetet?

Vendosja blu-gjelbër me paga minimale

Edhe dy do të mjaftojnë, ky i fundit është këtu vetëm për plotësi, që të mos shkruaj else.

Vetëm funksioni që kthen konfigurimet nginx mbetet i papërcaktuar: get-nginx-config service_name deployment_slot. Për analogji me kontrollin shëndetësor, këtu mund të vendosni çdo konfigurim për çdo shërbim. Nga gjërat interesante - vetëm cat <<- EOF, i cili ju lejon të hiqni të gjitha skedat në fillim. Vërtetë, çmimi i formatimit të mirë është skedat e përziera me hapësirat, të cilat sot konsiderohen si formë shumë e keqe. Por bash detyron skedat, dhe gjithashtu do të ishte mirë të kishim formatim normal në konfigurimin nginx. Me pak fjalë, përzierja e skedave me hapësira këtu duket vërtet si zgjidhja më e mirë nga më e keqja. Sidoqoftë, nuk do ta shihni këtë në fragmentin më poshtë, pasi Habr "e bën mirë" duke ndryshuar të gjitha skedat në 4 hapësira dhe duke e bërë EOF të pavlefshme. Dhe këtu bie në sy.

Për të mos u ngritur dy herë, do t'ju them menjëherë cat << 'EOF', të cilat do të ndeshen më vonë. Nëse shkruani thjesht cat << EOF, pastaj brenda heredoc vargu ndërpritet (variablat zgjerohen ($foo), thirrjet e komandave ($(bar)) etj.), dhe nëse mbyllni fundin e dokumentit në thonjëza të vetme, atëherë interpolimi çaktivizohet dhe simboli $ shfaqet siç është. Çfarë ju nevojitet për të futur një skript brenda një skripti tjetër.

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
}

Ky është i gjithë skenari. Dhe kështu thelbi me këtë skenar për shkarkim përmes wget ose curl.

Ekzekutimi i skripteve të parametrizuara në një server të largët

Është koha për të trokitur në serverin e synuar. Kësaj radhe localhost mjaft i përshtatshëm:

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

Ne kemi shkruar një skript vendosjeje që shkarkon një imazh të para-ndërtuar në serverin e synuar dhe zëvendëson pa probleme kontejnerin e shërbimit, por si mund ta ekzekutojmë atë në një makinë të largët? Skripti ka argumente, pasi është universal dhe mund të vendosë disa shërbime njëherësh nën një përfaqësues të kundërt (mund të përdorni konfigurimet nginx për të përcaktuar se cila url do të jetë cili shërbim). Skripti nuk mund të ruhet në server, pasi në këtë rast nuk do të jemi në gjendje ta përditësojmë automatikisht (me qëllim të korrigjimit të gabimeve dhe shtimit të shërbimeve të reja), dhe në përgjithësi, gjendje = e keqe.

Zgjidhja 1: Ende ruajeni skriptin në server, por kopjojeni atë çdo herë scp. Më pas lidheni nëpërmjet ssh dhe ekzekutoni skriptin me argumentet e nevojshme.

Cons:

  • Dy veprime në vend të një
  • Mund të mos ketë një vend ku ju kopjoni, ose mund të mos ketë akses në të, ose skripti mund të ekzekutohet në momentin e zëvendësimit.
  • Këshillohet që të pastroni veten (fshini skenarin).
  • Tashmë tre veprime.

Zgjidhja 2:

  • Mbani vetëm përkufizimet e funksioneve në skript dhe mos ekzekutoni asgjë
  • Me sed shtoni një thirrje funksioni në fund
  • Dërgojini të gjitha drejtpërdrejt në shh përmes tubit (|)

Pro:

  • Vërtet pa shtetësi
  • Nuk ka objekte të pllakës së bojlerit
  • Ndjenja e ftohtë

Le ta bëjmë atë pa Ansible. Po, gjithçka tashmë është shpikur. Po, një biçikletë. Shikoni sa e thjeshtë, elegante dhe minimaliste është biçikleta:

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

Megjithatë, nuk mund të jemi të sigurt që pritësi në distancë ka bash adekuat, kështu që do të shtojmë një kontroll të vogël në fillim (kjo është në vend të 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

Dhe tani është e vërtetë:

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

Tani mund të hapni http://localhost/ në shfletues, ekzekutoni përsëri vendosjen dhe sigurohuni që ai të funksionojë pa probleme duke përditësuar faqen sipas CD-së gjatë paraqitjes.

Mos harroni të pastroni pas punës :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

Burimi: www.habr.com