Blue-Green Deployment sa pinakamababang sahod

Sa artikulong ito ginagamit namin malakas na palo, SSH, docker ΠΈ nginx Aayusin namin ang isang tuluy-tuloy na layout ng web application. Asul-berde na deployment ay isang pamamaraan na nagbibigay-daan sa iyo upang agad na i-update ang isang application nang hindi tinatanggihan ang isang kahilingan. Isa ito sa mga diskarte sa pag-deploy ng zero downtime at pinakaangkop para sa mga application na may isang instance, ngunit ang kakayahang mag-load ng pangalawang, ready-to-run na instance sa malapit.

Sabihin nating mayroon kang web application kung saan aktibong nagtatrabaho ang maraming kliyente, at talagang walang paraan para mahiga ito sa loob ng ilang segundo. At kailangan mo talagang maglunsad ng update sa library, pag-aayos ng bug, o bagong cool na feature. Sa isang normal na sitwasyon, kakailanganin mong ihinto ang application, palitan ito at simulan itong muli. Sa kaso ng docker, maaari mo munang palitan ito, pagkatapos ay i-restart ito, ngunit magkakaroon pa rin ng isang panahon kung saan ang mga kahilingan sa application ay hindi mapoproseso, dahil kadalasan ang application ay tumatagal ng ilang oras upang unang mag-load. Paano kung nagsimula ito, ngunit lumabas na hindi maoperahan? Ito ang problema, lutasin natin ito sa kaunting paraan at kasing elegante hangga't maaari.

DISCLAIMER: Karamihan sa artikulo ay ipinakita sa isang pang-eksperimentong format - sa anyo ng isang pag-record ng isang console session. Sana ay hindi ito masyadong mahirap maunawaan at ang code ay magdodokumento ng sarili nitong sapat. Para sa kapaligiran, isipin na ang mga ito ay hindi lamang mga snippet ng code, ngunit papel mula sa isang "bakal" na teletype.

Blue-Green Deployment sa pinakamababang sahod

Ang mga kagiliw-giliw na diskarte na mahirap sa Google sa pamamagitan lamang ng pagbabasa ng code ay inilarawan sa simula ng bawat seksyon. Kung mayroon pang hindi malinaw, i-google ito at tingnan ito. explainshell (sa kabutihang palad, ito ay gumagana muli, dahil sa pag-unblock ng telegrama). Kung wala kang magawa sa Google, magtanong sa mga komento. Ikalulugod kong idagdag sa kaukulang seksyon na "Mga kawili-wiling diskarte".

Magsimula tayo.

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

Tools

Gumawa tayo ng pang-eksperimentong serbisyo at ilagay ito sa isang lalagyan.

Mga kawili-wiling pamamaraan

  • cat << EOF > file-name (Narito ang Dokumento + I/O Redirection) ay isang paraan upang lumikha ng isang multi-line na file na may isang command. Lahat ng bash nagbabasa mula sa /dev/stdin pagkatapos ng linyang ito at bago ang linya EOF ay itatala sa file-name.
  • wget -qO- URL (explainshell) β€” maglabas ng dokumentong natanggap sa pamamagitan ng HTTP sa /dev/stdout (analogue curl URL).

Printout

Partikular kong sinira ang snippet upang paganahin ang pag-highlight para sa Python. Sa dulo magkakaroon ng isa pang piraso na tulad nito. Isaalang-alang na sa mga lugar na ito ang papel ay pinutol upang ipadala sa departamento ng pag-highlight (kung saan ang code ay kinulayan ng kamay na may mga highlighter), at pagkatapos ay ang mga piraso ay idinikit pabalik.

$ 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

Baliktarin ang proxy

Upang ang aming aplikasyon ay makapagbago nang hindi napapansin, kinakailangan na mayroong ibang entity sa harap nito na magtatago ng kapalit nito. Maaaring ito ay isang web server nginx Π² reverse proxy mode. Ang isang reverse proxy ay itinatag sa pagitan ng kliyente at ng application. Tumatanggap ito ng mga kahilingan mula sa mga kliyente at ipinapasa ang mga ito sa aplikasyon at ipinapasa ang mga tugon ng aplikasyon sa mga kliyente.

Ang application at reverse proxy ay maaaring maiugnay sa loob ng docker gamit network ng docker. Kaya, ang lalagyan na may application ay hindi na kailangang mag-forward ng isang port sa host system; ito ay nagpapahintulot sa application na lubos na ihiwalay mula sa mga panlabas na banta.

Kung ang reverse proxy ay nakatira sa isa pang host, kailangan mong iwanan ang docker network at ikonekta ang application sa reverse proxy sa pamamagitan ng host network, na ipapasa ang port apps parameter --publish, tulad ng sa unang pagsisimula at tulad ng sa reverse proxy.

Tatakbo kami ng reverse proxy sa port 80, dahil ito mismo ang entity na dapat makinig sa panlabas na network. Kung abala ang port 80 sa iyong test host, baguhin ang parameter --publish 80:80 sa --publish ANY_FREE_PORT:80.

Mga kawili-wiling pamamaraan

Printout

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

Walang putol na deployment

Maglunsad tayo ng bagong bersyon ng application (na may dalawang beses na pagpapalakas ng pagganap ng startup) at subukang i-deploy ito nang walang putol.

Mga kawili-wiling pamamaraan

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' β€” Sumulat ng teksto my text mag-file /my-file.txt sa loob ng lalagyan my-container.
  • cat > /my-file.txt β€” Isulat ang mga nilalaman ng karaniwang input sa isang file /dev/stdin.

Printout

$ 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

Sa yugtong ito, ang imahe ay binuo nang direkta sa server, na nangangailangan ng mga mapagkukunan ng application na naroroon, at naglo-load din sa server ng hindi kinakailangang trabaho. Ang susunod na hakbang ay ang paglalaan ng image assembly sa isang hiwalay na makina (halimbawa, sa isang CI system) at pagkatapos ay ilipat ito sa server.

Paglilipat ng mga larawan

Sa kasamaang palad, hindi makatuwirang ilipat ang mga larawan mula sa localhost patungo sa localhost, kaya't maaari lamang tuklasin ang seksyong ito kung mayroon kang dalawang host na may Docker sa kamay. Sa pinakamababa, ganito ang hitsura nito:

$ 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

Koponan docker save sine-save ang data ng imahe sa isang .tar archive, ibig sabihin, tumitimbang ito ng humigit-kumulang 1.5 beses na mas mataas kaysa sa tinitimbang nito sa naka-compress na anyo. Kaya't ipagpatuloy natin ito sa ngalan ng pagtitipid ng oras at trapiko:

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

Maaari mo ring subaybayan ang proseso ng pag-download (bagaman nangangailangan ito ng isang third-party na utility):

$ 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: Kung kailangan mo ng isang grupo ng mga parameter upang kumonekta sa isang server sa pamamagitan ng SSH, maaaring hindi mo ginagamit ang file ~/.ssh/config.

Paglilipat ng larawan sa pamamagitan ng docker image save/load - Ito ang pinaka minimalistic na paraan, ngunit hindi ang isa lamang. May iba pa:

  1. Container Registry (pamantayan sa industriya).
  2. Kumonekta sa docker daemon server mula sa isa pang host:
    1. variable ng kapaligiran DOCKER_HOST.
    2. Opsyon ng command line -H o --host tool docker-compose.
    3. docker context

Ang pangalawang paraan (na may tatlong mga pagpipilian para sa pagpapatupad nito) ay mahusay na inilarawan sa artikulo Paano mag-deploy sa mga malayuang host ng Docker na may docker-compose.

deploy.sh

Ngayon, kolektahin natin ang lahat ng ginawa natin nang manu-mano sa isang script. Magsimula tayo sa top-level na function, at pagkatapos ay tingnan ang iba pang ginamit dito.

Mga kawili-wiling pamamaraan

  • ${parameter?err_msg} - isa sa mga bash magic spells (aka pagpapalit ng parameter). Kung parameter hindi tinukoy, output err_msg at lumabas gamit ang code 1.
  • docker --log-driver journald β€” bilang default, ang driver ng pag-log ng docker ay isang text file na walang anumang pag-ikot. Sa diskarteng ito, mabilis na pinupuno ng mga log ang buong disk, kaya para sa isang kapaligiran ng produksyon kinakailangan na baguhin ang driver sa isang mas matalinong isa.

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
}

Mga tampok na ginamit:

  • ensure-reverse-proxy β€” Tinitiyak na gumagana ang reverse proxy (kapaki-pakinabang para sa unang pag-deploy)
  • get-active-slot service_name β€” Tinutukoy kung aling slot ang kasalukuyang aktibo para sa isang ibinigay na serbisyo (BLUE o GREEN)
  • get-service-status service_name deployment_slot β€” Tinutukoy kung handa na ang serbisyo na iproseso ang mga papasok na kahilingan
  • set-active-slot service_name deployment_slot β€” Binabago ang nginx config sa reverse proxy container

Upang:

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
}

Tungkulin get-active-slot nangangailangan ng kaunting paliwanag:

Bakit ito nagbabalik ng isang numero at hindi naglalabas ng isang string?

Anyway, sa calling function, sinusuri namin ang resulta ng trabaho nito, at ang pagsuri sa exit code gamit ang bash ay mas madali kaysa sa pagsuri ng string. Bilang karagdagan, ang pagkuha ng isang string mula dito ay napaka-simple:
get-active-slot service && echo BLUE || echo GREEN.

Sapat ba talaga ang tatlong kundisyon para makilala ang lahat ng estado?

Blue-Green Deployment sa pinakamababang sahod

Kahit dalawa ay sapat na, ang huli ay narito lamang para sa pagkakumpleto, upang hindi magsulat else.

Tanging ang function na nagbabalik ng nginx configs ay nananatiling hindi natukoy: get-nginx-config service_name deployment_slot. Sa pamamagitan ng pagkakatulad sa pagsusuri sa kalusugan, dito maaari kang magtakda ng anumang config para sa anumang serbisyo. Sa mga kawili-wiling bagay - lamang cat <<- EOF, na nagbibigay-daan sa iyong alisin ang lahat ng tab sa simula. Totoo, ang presyo ng mahusay na pag-format ay halo-halong mga tab na may mga puwang, na ngayon ay itinuturing na napakasamang anyo. Ngunit pinipilit ng bash ang mga tab, at maganda rin na magkaroon ng normal na pag-format sa nginx config. Sa madaling salita, ang paghahalo ng mga tab na may mga puwang dito ay tila ang pinakamahusay na solusyon sa pinakamasama. Gayunpaman, hindi mo ito makikita sa snippet sa ibaba, dahil "mahusay ang ginagawa" ni Habr sa pamamagitan ng pagpapalit ng lahat ng tab sa 4 na espasyo at paggawang hindi wasto ang EOF. At dito kapansin-pansin.

Para hindi makabangon ng dalawang beses, sasabihin ko kaagad sa iyo cat << 'EOF', na makakatagpo sa ibang pagkakataon. Kung magsusulat ka ng simple cat << EOF, pagkatapos ay sa loob ng heredoc ang string ay interpolated (mga variable ay pinalawak ($foo), command calls ($(bar)) atbp.), at kung isasama mo ang dulo ng dokumento sa mga solong panipi, hindi pinagana ang interpolation at ang simbolo $ ay ipinapakita bilang ay. Ano ang kailangan mong magpasok ng isang script sa loob ng isa pang 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
}

Ito ang buong script. At kaya buod sa script na ito para sa pag-download sa pamamagitan ng wget o curl.

Pagpapatupad ng mga parameterized na script sa isang malayuang server

Oras na para kumatok sa target na server. Sa pagkakataong ito localhost medyo angkop:

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

Nagsulat kami ng deployment script na nagda-download ng pre-built na imahe sa target na server at walang putol na pinapalitan ang service container, ngunit paano namin ito maipapatupad sa isang remote na makina? Ang script ay may mga argumento, dahil ito ay pangkalahatan at maaaring mag-deploy ng ilang mga serbisyo nang sabay-sabay sa ilalim ng isang reverse proxy (maaari kang gumamit ng nginx configs upang matukoy kung aling url ang magiging serbisyo). Ang script ay hindi maiimbak sa server, dahil sa kasong ito, hindi namin ito maa-update nang awtomatiko (para sa layunin ng pag-aayos ng bug at pagdaragdag ng mga bagong serbisyo), at sa pangkalahatan, state = evil.

Solusyon 1: Iimbak pa rin ang script sa server, ngunit kopyahin ito sa bawat oras scp. Pagkatapos ay kumonekta sa pamamagitan ng ssh at isagawa ang script gamit ang mga kinakailangang argumento.

Cons:

  • Dalawang aksyon sa halip na isa
  • Maaaring walang lugar kung saan mo kinokopya, o maaaring walang access dito, o maaaring isagawa ang script sa oras ng pagpapalit.
  • Maipapayo na maglinis pagkatapos ng iyong sarili (tanggalin ang script).
  • Tatlong aksyon na.

Solusyon 2:

  • Panatilihin lamang ang mga kahulugan ng function sa script at wala man lang pinapatakbo
  • May sed magdagdag ng isang function na tawag sa dulo
  • Direktang ipadala ang lahat sa shh sa pamamagitan ng pipe (|)

Pros:

  • Tunay na walang estado
  • Walang boilerplate entity
  • Feeling cool

Gawin na lang natin ng walang Ansible. Oo, lahat ay naimbento na. Oo, isang bisikleta. Tingnan kung gaano kasimple, elegante at minimalistic ang bike:

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

Gayunpaman, hindi namin matiyak na ang malayong host ay may sapat na bash, kaya magdadagdag kami ng maliit na tseke sa simula (ito ay sa halip na 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

At ngayon ito ay totoo:

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

Ngayon ay maaari mong buksan http://localhost/ sa browser, patakbuhin muli ang deployment at tiyaking tumatakbo ito nang walang putol sa pamamagitan ng pag-update ng page ayon sa CD sa panahon ng layout.

Huwag kalimutang maglinis pagkatapos ng trabaho :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

Pinagmulan: www.habr.com