Blue-Green Deplwaman nan salè minimòm

Nan atik sa a nou itilize frape, sch, wokè и nginx Nou pral òganize yon layout san pwoblèm nan aplikasyon entènèt la. Deplwaman ble-vèt se yon teknik ki pèmèt ou imedyatman mete ajou yon aplikasyon san yo pa rejte yon sèl demann. Li se youn nan estrateji deplwaman zewo D 'epi li pi byen adapte pou aplikasyon ki gen yon sèl egzanp, men kapasite nan chaje yon dezyèm egzanp ki pare pou kouri tou pre.

Ann di ou gen yon aplikasyon entènèt ak anpil kliyan ap travay aktivman, epi pa gen absoliman okenn fason pou li kouche pou yon koup de segonn. Epi ou reyèlman bezwen woule yon aktyalizasyon bibliyotèk, yon ranje ensèk, oswa yon nouvo karakteristik fre. Nan yon sitiyasyon nòmal, w ap bezwen sispann aplikasyon an, ranplase li epi kòmanse li ankò. Nan ka a nan Docker, ou ka premye ranplase li, Lè sa a, rekòmanse li, men pral toujou gen yon peryòd nan ki demann nan aplikasyon an pa pral trete, paske anjeneral aplikasyon an pran kèk tan okòmansman chaje. E si li kòmanse, men li vire soti inoperable? Sa a se pwoblèm nan, se pou nou rezoud li ak mwayen minim ak kòm elegant ke posib.

DISCLAIMER: Pifò nan atik la prezante nan yon fòma eksperimantal - nan fòm lan nan yon anrejistreman nan yon sesyon konsole. Nou swete ke sa a pa pral twò difisil pou konprann epi kòd la pral dokimante tèt li ase. Pou atmosfè, imajine ke sa yo se pa sèlman fragman kòd, men papye ki soti nan yon teletype "fè".

Blue-Green Deplwaman nan salè minimòm

Teknik enteresan ki difisil pou Google jis lè w li kòd la dekri nan kòmansman chak seksyon. Si nenpòt lòt bagay pa klè, google li epi tcheke li. eksplike shell (Erezman, li travay ankò, akòz debloke telegram la). Si ou pa ka Google anyen, mande nan kòmantè yo. Mwen pral kontan ajoute nan seksyon ki koresponn lan "Teknik enteresan".

Ann kòmanse.

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

Lapòs

Ann fè yon sèvis eksperimantal epi mete l nan yon veso.

Teknik enteresan

  • cat << EOF > file-name (Isit la Dokiman + I/O Redireksyon) se yon fason yo kreye yon dosye milti-liy ak yon sèl lòd. Tout bagay bash li nan /dev/stdin apre liy sa a ak anvan liy lan EOF pral anrejistre nan file-name.
  • wget -qO- URL (eksplike shell) - pwodiksyon yon dokiman te resevwa via HTTP pou /dev/stdout (analòg curl URL).

Enprime

Mwen spesyalman kraze ti bout la pou pèmèt en pou Python. Nan fen a pral gen yon lòt moso tankou sa a. Konsidere ke nan kote sa yo te koupe papye a pou voye bay depatman en (kote kòd la te koulè men ak en), epi lè sa a pièces sa yo te kole tounen.

$ 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

Prokurasyon ranvèse

Pou aplikasyon nou an kapab chanje inapèsi, li nesesè ke gen kèk lòt antite devan li ki pral kache ranplasman li. Li ta ka yon sèvè entènèt nginx в mòd proxy ranvèse. Yon prokurasyon ranvèse etabli ant kliyan an ak aplikasyon an. Li aksepte demann kliyan yo epi voye yo nan aplikasyon an epi voye repons aplikasyon an bay kliyan yo.

Aplikasyon an ak prokurasyon ranvèse ka lye andedan docker lè l sèvi avèk rezo docker. Kidonk, veso ki gen aplikasyon an pa menm bezwen voye yon pò sou sistèm lame a; sa pèmèt aplikasyon an dwe izole maksimòm de menas ekstèn yo.

Si prokurasyon ranvèse a ap viv sou yon lòt lame, w ap oblije abandone rezo docker la epi konekte aplikasyon an nan proxy ranvèse atravè rezo lame a, voye pò a. apps paramèt --publish, tankou nan kòmansman an premye ak menm jan ak prokurasyon an ranvèse.

Nou pral kouri proxy ranvèse a sou pò 80, paske sa a se egzakteman antite ki ta dwe koute rezo ekstèn lan. Si pò 80 okipe sou lame tès ou a, chanje paramèt la --publish 80:80 sou --publish ANY_FREE_PORT:80.

Teknik enteresan

Enprime

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

Deplwaman san pwoblèm

Se pou yo woule yon nouvo vèsyon aplikasyon an (ak yon ogmantasyon pèfòmans demaraj de fwa) epi eseye deplwaye li san pwoblèm.

Teknik enteresan

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' — Ekri tèks my text pou ranpli /my-file.txt andedan veso a my-container.
  • cat > /my-file.txt — Ekri sa ki nan opinyon estanda nan yon dosye /dev/stdin.

Enprime

$ 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

Nan etap sa a, imaj la bati dirèkteman sou sèvè a, ki mande sous aplikasyon yo dwe la, epi tou li chaje sèvè a ak travay ki pa nesesè. Pwochen etap la se asiyen asanble imaj la nan yon machin separe (pa egzanp, nan yon sistèm CI) epi transfere li nan sèvè a.

Transfere imaj

Malerezman, li pa fè sans pou transfere imaj soti nan localhost nan localhost, kidonk seksyon sa a ka sèlman eksplore si ou gen de lame ak Docker nan men. Omwen li sanble yon bagay tankou sa a:

$ 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 sove done imaj yo nan yon achiv .tar, sa vle di li peze apeprè 1.5 fwa plis pase li ta peze nan fòm konprese. Se konsa, ann souke li nan non ekonomize tan ak trafik:

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

Ou kapab tou kontwole pwosesis telechaje a (byenke sa a mande pou yon sèvis piblik twazyèm pati):

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

Ide: Si ou bezwen yon pakèt paramèt pou konekte avèk yon sèvè via SSH, ou ka pa sèvi ak fichye a. ~/.ssh/config.

Transfere imaj la atravè docker image save/load - Sa a se metòd ki pi minimalist, men se pa youn nan sèlman. Gen lòt:

  1. Rejis Container (estanda endistri).
  2. Konekte ak sèvè Docker daemon soti nan yon lòt lame:
    1. anviwònman varyab DOCKER_HOST.
    2. Opsyon liy kòmand -H oswa --host enstriman docker-compose.
    3. docker context

Dezyèm metòd la (ak twa opsyon pou aplikasyon li) byen dekri nan atik la Ki jan yo deplwaye sou lame Docker aleka ak docker-compose.

deploy.sh

Koulye a, an n kolekte tout sa nou te fè manyèlman nan yon sèl script. Ann kòmanse ak fonksyon an tèt nivo, ak Lè sa a, gade nan lòt moun yo itilize nan li.

Teknik enteresan

  • ${parameter?err_msg} - youn nan òneman majik bash (aka sibstitisyon paramèt). Si parameter pa espesifye, pwodiksyon err_msg epi sòti ak kòd 1.
  • docker --log-driver journald — pa defo, chofè logging Docker se yon dosye tèks san okenn wotasyon. Avèk apwòch sa a, mòso bwa yo byen vit ranpli disk la tout antye, kidonk pou yon anviwònman pwodiksyon li nesesè chanje chofè a nan yon sèl pi entelijan.

Script deplwaman

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
}

Karakteristik yo itilize:

  • ensure-reverse-proxy — Asire w ke prokurasyon ranvèse a ap travay (itil pou premye deplwaman an)
  • get-active-slot service_name — Detèmine ki plas ki aktif kounye a pou yon sèvis bay (BLUE oswa GREEN)
  • get-service-status service_name deployment_slot — Detèmine si sèvis la pare pou trete demann k ap vini yo
  • set-active-slot service_name deployment_slot — Chanje konfigirasyon nginx nan veso prokurasyon ranvèse a

Nan lòd:

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
}

Fonksyon get-active-slot mande yon ti eksplikasyon:

Poukisa li retounen yon nimewo epi li pa soti yon fisèl?

De tout fason, nan fonksyon apèl la nou tcheke rezilta travay li yo, epi tcheke kòd sòti lè l sèvi avèk bash se pi fasil pase tcheke yon fisèl. Anplis de sa, jwenn yon fisèl nan li trè senp:
get-active-slot service && echo BLUE || echo GREEN.

Èske twa kondisyon vrèman ase pou distenge tout eta yo?

Blue-Green Deplwaman nan salè minimòm

Menm de pral ase, dènye a se isit la jis pou konplè, se konsa yo pa ekri else.

Se sèlman fonksyon an ki retounen konfigirasyon nginx ki rete endefini: get-nginx-config service_name deployment_slot. Pa analoji ak chèk sante, isit la ou ka mete nenpòt konfigirasyon pou nenpòt sèvis. Nan bagay sa yo enteresan - sèlman cat <<- EOF, ki pèmèt ou retire tout onglè nan kòmansman an. Se vre, pri a nan bon fòma se onglè melanje ak espas, ki jodi a konsidere kòm fòm trè move. Men, bash fòs onglè, epi li ta tou bon pou gen fòma nòmal nan konfigirasyon nginx la. Nan ti bout tan, melanje onglè ak espas isit la reyèlman sanble tankou solisyon ki pi bon soti nan pi move a. Sepandan, ou pa pral wè sa a nan snippet ki anba a, paske Habr "fè li byen" lè w chanje tout onglè yo nan 4 espas epi fè EOF envalid. Ak isit la li se aparan.

Pou pa leve de fwa, mwen pral di ou touswit sou cat << 'EOF', ki pral rankontre pita. Si ou ekri tou senpleman cat << EOF, Lè sa a, andedan heredoc fisèl la entèpolò (variab yo elaji ($foo), apèl kòmand ($(bar)) elatriye), epi si ou mete fen dokiman an nan yon sèl guillemets, lè sa a entèpolasyon enfim epi senbòl la. $ parèt jan li ye. Ki sa ou bezwen mete yon script andedan yon lòt script.

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

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

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

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

Sa a se tout script la. Epi esansyèl ak script sa a pou telechaje atravè wget oswa curl.

Egzekite scripts paramèt sou yon sèvè aleka

Li lè yo frape sou sèvè a sib. Fwa sa a localhost byen apwopriye:

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

Nou te ekri yon script deplwaman ki telechaje yon imaj pre-bati nan sèvè sib la ak san pwoblèm ranplase veso sèvis la, men ki jan nou ka egzekite li sou yon machin aleka? Script la gen agiman, paske li se inivèsèl epi li ka deplwaye plizyè sèvis nan yon fwa anba yon prokurasyon ranvèse (ou ka itilize konfigirasyon nginx pou detèmine ki adrès ki pral ki sèvis). Script la pa ka estoke sou sèvè a, paske nan ka sa a nou pa pral kapab mete ajou li otomatikman (nan bi pou yo fikse ensèk ak ajoute nouvo sèvis), ak an jeneral, eta = sa ki mal.

Solisyon 1: Toujou sere script la sou sèvè a, men kopye li chak fwa scp. Lè sa a konekte via ssh epi egzekite script la ak agiman ki nesesè yo.

Kont:

  • De aksyon olye de yon sèl
  • Gen dwa pa gen yon kote ou kopye, oswa pa gen aksè a li, oswa script la ka egzekite nan moman sibstitisyon an.
  • Li rekòmande pou netwaye apre tèt ou (efase script la).
  • Deja twa aksyon.

Solisyon 2:

  • Kenbe sèlman definisyon fonksyon nan script la epi kouri pa gen anyen ditou
  • Avèk sed ajoute yon apèl fonksyon nan fen an
  • Voye li tout dirèkteman nan shh atravè tiyo (|)

Pou:

  • Vrèman apatrid
  • Pa gen antite boilerplate
  • Santi fre

Ann jis fè li san Ansible. Wi, tout bagay te deja envante. Wi, yon bisiklèt. Gade jan bisiklèt la senp, elegant ak minimalist:

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

Sepandan, nou pa ka sèten ke lame a aleka gen bon jan bach, kidonk nou pral ajoute yon ti chèk nan kòmansman an (sa a se olye pou yo 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

Epi kounye a li reyèl:

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

Koulye a, ou ka louvri http://localhost/ nan navigatè a, kouri deplwaman an ankò epi asire w ke li kouri san pwoblèm pa mete ajou paj la dapre CD a pandan layout la.

Pa bliye netwaye apre travay :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

Sous: www.habr.com