Blue-Green izvietošana pamatiem

Šajā rakstā mēs ar bash, ssh, docker un nginx organizēsim bezšuvju tīmekļa lietojumprogrammas izvietošanu. Zilā zaļā izvietošana ir tehnika, kas ļauj nekavējoties atjaunināt lietojumprogrammu, nepārtraucot nevienu pieprasījumu. Tā ir viena no zero downtime deployment stratēģijām un vislabāk piemērota lietojumprogrammām ar vienu instanci, taču ar iespēju blakus augšupielādēt otro, gatavu instanci.

Pieņemsim, ka jums ir tīmekļa lietojumprogramma, ar kuru aktīvi strādā daudzi klienti, un tai nekādā gadījumā nevar atļauties, lai uz dažām sekundēm tas apstājas. Un jums ļoti nepieciešams izlaist bibliotēkas atjauninājumu, labot kļūdu vai pievienot jaunu foršu funkciju. Parastā situācijā būs nepieciešams apturēt lietojumprogrammu, aizvietot to un pēc tam atkārtoti palaist. Ja izmantojat Docker, varat vispirms aizstāt, pēc tam restartēt, taču tomēr būs periods, kad pieprasījumi uz lietojumprogrammu netiks apstrādāti, jo parasti lietojumprogrammai ir nepieciešams laiks, lai sākotnēji ielādētu. Un, ja tā tiek palaista, bet izrādās, ka tā nedarbojas? Šāda uzdevuma risināšana ir jāveic minimāliem līdzekļiem un maksimāli eleganti.

ATKLĀJUMS: Lielākā daļa raksta ir ietverta eksperimentālā formātā - kā konsoles sesijas ieraksts. Ceru, ka to būs viegli uztvert, un šis kods pietiekami dokumentē sevi. Lai radītu gaisotni, iedomājieties, ka tas nav tikai kods, bet papīrs no "dzelzs" telegrāfa.

Blue-Green izvietošana pamatiem

Interesantas tehnikas, kuras ir grūti atrast, vienkārši lasot kodu, ir aprakstītas katra nodaļas sākumā. Ja kaut kas nav skaidrs - meklējiet un pārbaudiet explainshell (laimīgā kārtā tas atkal darbojas sakarā ar Telegrama atbloķēšanu). Ko nevar atrast - uzdodiet jautājumus komentāros. Ar prieku papildināšu attiecīgo sadaļu "Interesantas tehnikas".

Sāksim.

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

Serviss

Izveidosim eksperimentālo servisu un ievietosim to konteinerā.

Interesantas tehnikas

  • cat < file-name (Here Document + I/O Redirection) - veids, kā izveidot daudzrindu failu ar vienu komandu. Viss, ko bash izlasīs no /dev/stdin pēc šīs rindiņas un līdz rindai EOF tiks ierakstīts file-name.
  • wget -qO- URL (explainshell) - izvadīt saņemto HTTP dokumentu uz /dev/stdout (analogs curl URL).

Izvadīt

Es īpaši pārtraucu snippetu, lai iekļautu izcelšanu Python. Beigās būs vēl viens šāds fragments. Uztveriet, ka šajās vietās papīrs tika sagriezts, lai to nodotu krāsotāju nodaļai (kur kods tika krāsots ar roku ar augstspiediena marķieriem), un pēc tam šie gabali tika atkārtoti ielīmēti.

$ cat < uptimer.py
importēt http.server  kā BaseHTTPRequestHandler, HTTPServer
importēt laiku kā monotonic

lietojumprogrammas_versija = 1
lietojumprogrammas_nosaukums = f'Uptimer v{lietojumprogrammas_versija}.0'
lādēšanās_sekundes = 15 - lietojumprogrammas_versija * 5

klase Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        ja self.path == '\/':
            mēģināt:
                t = monotonic() - server_start
                ja t &lt; lādēšanās_sekundes:
                    self.send_error(503)
                cita:
                    self.send_response(200)
                    self.send_header(&#039;Content-Type&#039;, &#039;text\/html&#039;)
                    self.end_headers()
                    atbilde = f&#039;<h2>{lietojumprogrammas_nosaukums} darbojas {t:3.1f} sekundes.</h2>n'
                    self.wfile.write(atbilde.encode('utf-8'))
            except Exception:
                self.send_error(500)
        cita:
            self.send_error(404)

httpd = HTTPServer(('', 8080), Handler)
server_start = monotonic()
print(f'{lietojumprogrammas_nosaukums} (ielādējas {lādēšanās_sekundes} sek.) uzsākta.')
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
 ---&gt; 8ecf5a48c789
Step 2\/4 : EXPOSE 8080
 ---&gt; Using cache
 ---&gt; cf92d174c9d3
Step 3\/4 : COPY uptimer.py app.py
 ---&gt; a7fbb33d6b7e
Step 4\/4 : CMD [ "python", "-u", ".\/app.py" ]
 ---&gt; Running in 1906b4bd9fdf
Removing intermediate container 1906b4bd9fdf
 ---&gt; 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-&gt;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 darbojas jau 15.4 sekundes.</h2>

$ docker rm --force uptimer
uptimer

Tiesas proxy

Lai mūsu lietotne varētu nemanāmi mainīties, ir nepieciešams, lai priekšā tai būtu vēl kāda entītija, kas slēpj tās nomaiņu. Tas var būt tīmekļa serveris nginx par reversā proxy režīmā. Reversā proxy tiek novietots starp klientu un lietotni. Tas pieņem pieprasījumus no klientiem un pārsūta tos uz lietotni, savukārt lietotnes atbildes nosūta klientiem.

Lietotni un reverso proxy var savienot iekš Docker, izmantojot docker network. Tādējādi lietotnes konteineram pat nav jāpārsūta ports viesējsistēmā, tas ļauj maksimāli izolēt lietotni no ārējiem draudiem.

Ja reversais proxy dzivos citā viesējsistēmā, būs jāatsakās no docker tīkla un jāsaista lietotni ar reverso proxy caur viesējsistēmas tīklu, pārsūtot portu lietojumprogrammas parametru --publish, kā pirmajā palaišanā un kā luksoforam.

Reverso proxy darbosies uz 80. porta, jo tā ir tā entītija, kurai jāuzklausa ārpuse. Ja 80. ports jūsu testēšanas viesējsistēmā ir aizņemts, mainiet parametru --publish 80:80 uz --publish ANY_FREE_PORT:80.

Interesantas tehnikas

Izvadīt

$ 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 darbojas jau 11.5 sekundes.</h2>

$ docker run --detach --publish 80:80 --network web-gateway --name reverse-proxy nginx:alpine
80695a822c19051260c66bf60605dcb4ea66802c754037704968bc42527bf120

$ docker ps
KONTEINERA ID        ATTĒLS               KOMANDA                  RADĪTS              STATUSS              PORTI                NOSAUKUMI
80695a822c19        nginx:alpine        "\/docker-entrypoint.…"   27 sekundes atpakaļ       Darbojās 25 sekundes       0.0.0.0:80-&gt;80\/tcp   reverse-proxy
a1105f1b583d        uptimer             "python -u .\/app.py"     Apmēram pirms minūtes   Darbojās Apmēram minūti   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 darbojas jau 104.1 sekundes.</h2>

Bezsavienojuma izvietošana

Mēs izlaidīsim jauno lietotnes versiju (ar divkāršu palielinājumu starta veiktspējā) un mēģināsim to bezsasaistē izvietot.

Interesantas tehnikas

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' — Ierakstīt tekstu my text failā /my-file.txt konteinera iekšienē my-container.
  • cat > /my-file.txt — Ierakstīt failā standartā ievadi /dev/stdin.

Izvadīt

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

$ docker build --tag uptimer .
Sūtot būvniecības kontekstu Docker demonam  39.94kB
Solījums 1/4 : NO python:alpine
 ---&gt; 8ecf5a48c789
Solījums 2/4 : EXPOSE 8080
 ---&gt; Izmantojot starpkontu
 ---&gt; cf92d174c9d3
Solījums 3/4 : COPY uptimer.py app.py
 ---&gt; 3eca6a51cb2d
Solījums 4/4 : CMD [ "python", "-u", ".\/app.py" ]
 ---&gt; Darbojas 8f13c6d3d9e7
Noņemot starpkonteineru 8f13c6d3d9e7
 ---&gt; 1d56897841ec
Veiksmīgi izveidots 1d56897841ec
Veiksmīgi apzīmēts uptimer:latest

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

$ docker logs uptimer_BLUE
Uptimer v2.0 (ielāde 5 sek.) uzsāka.

$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 darbojas 23.9 sekundes.</h2>

$ sed s/uptimer/uptimer_BLUE/ uptimer.conf | docker exec --interactive reverse-proxy sh -c 'cat &gt; /etc/nginx/conf.d/default.conf'

$ docker exec reverse-proxy cat /etc/nginx/conf.d/default.conf
server {
    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: signālu process uzsākts

$ wget -qO- http://localhost
<h2>Uptimer v2.0 darbojas 63.4 sekundes.</h2>

$ docker rm -f uptimer
uptimer

$ wget -qO- http://localhost
<h2>Uptimer v2.0 darbojas 84.8 sekundes.</h2>

$ docker ps
KONTEINERA ID        ATTĒLS               KOMANDA                  IZVEIDOTS              STATUSS              PORTI                NOSAUMI
96932d4ca97a        uptimer             "python -u .\/app.py"     Pirms aptuveni minūtes   Aktīvs aptuveni minūti   8080/tcp             uptimer_BLUE
80695a822c19        nginx:alpine        "\/docker-entrypoint.…"   8 minūtes ago        Aktīvs 8 minūtes        0.0.0.0:80-&gt;80/tcp   reverse-proxy

Šajā posmā attēls tiek veidots tieši serverī, kas prasa lietotnes avotu klātbūtni tur, kā arī papildus noslogo serveri ar lieku darbu. Nākamais solis būs izveidot attēla veidošanu uz atsevišķas mašīnas (piemēram, CI sistēmā) ar turpmāku tā nodošanu serverim.

Attēlu pārsūtīšana

Diemžēl nav jēgas pārsūtīt attēlus no localhost uz localhost, tādēļ šo sadaļu var izmēģināt tikai, ja jums ir divi hosti ar docker. Minimālajā variantā tas izskatās apmēram šādi:

$ 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

Komanda docker save saglabā attēla datus .tar arhīvā, tas nozīmē, ka tam ir aptuveni 1,5 reizes lielāks apjoms nekā tam būtu kompresētā veidā. Tātad piespiežam to saspiest laika un datu ietaupīšanas labad:

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

Turklāt var novērot pārsūtīšanas procesu (gan tam ir nepieciešama trešā puse utilīta):

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

Padoms: Ja jums SSH savienojumam ar serveri nepieciešami daudz parametru, iespējams, jūs neizmantojat failu ~/.ssh/config.

Attēla pārsūtīšana caur docker image save/load ir visminimalistiskākais veids, bet ne vienīgais. Ir arī citi:

  1. Container Registry (nozares standarts).
  2. Pieslēgties docker daemon serverim no cita hosta:
    1. Videss maiņu DOCKER_HOST.
    2. Komandrindas parametrs -H vai --host instrumenta docker-compose.
    3. docker context

Otrais veids (ar trim tā īstenošanas variantiem) ir labi aprakstīts rakstā How to deploy on remote Docker hosts with docker-compose.

deploy.sh

Tagad savāksim visu, ko mēs darījām manuāli, vienā skriptā. Sāksim ar augstāko līmeņa funkciju, tad aplūkosim pārējās, kas tajā tiek izmantotas.

Interesantas tehnikas

  • ${parameter?err_msg} ir viens no bash burvju burtiem (aka parameter substitution). Ja parameter nav norādīts, jāizvada err_msg un jāiznāk ar 1. kodu.
  • docker --log-driver journald pēc noklusējuma docker žurnālu draiveris ir tekstu fails bez jebkādas rotācijas. Šādā veidā žurnāli ātri aizpilda visu disku, tāpēc produkcijas vidē ir jānomaina draiveris uz gudrāku.

Dēplojuma skripts

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
}

Izmantotās funkcijas:

  • ensure-reverse-proxy — Pārliecinās, ka apgrieztā proxy darbojas (noderīgi pirmajā izvietošanā)
  • get-active-slot service_name — Nosaka, kurš slots šobrīd ir aktīvs norādītajam servisam (BLUE vai GREEN)
  • get-service-status service_name deployment_slot — Nosaka, vai serviss ir gatavs apstrādāt ienākošos pieprasījumus
  • set-active-slot service_name deployment_slot — Maina nginx konfigurāciju apgrieztā proxy konteinerā

Secībā:

ensure-reverse-proxy() {
    is-container-up reverse-proxy && return 0
    echo "Izvietojam 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?"Izmantošana: ${FUNCNAME[0]} container_name"}

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

get-active-slot() {
    local service=${1?"Izmantošana: ${FUNCNAME[0]} service_name"}

    if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
        echo "Kollizija atklāta! Apstādinām ${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="Izmantošana: ${FUNCNAME[0]} service_name deployment_slot"
    local service=${1?usage_msg}
    local slot=${2?$usage_msg}

    case $service in
        # Pievienojiet konkrētas veselības pārbaudes ceļus jūsu pakalpojumiem šeit
        *) local health_check_port_path=":8080/" ;;
    esac
    local health_check_address="http://${service}_${slot}${health_check_port_path}"
    echo "Pieprasām '$health_check_address' 'web-gateway' docker tīklā:"
    docker run --rm --network web-gateway alpine 
        wget --timeout=1 --quiet --server-response $health_check_address
    return $?
}

set-active-slot() {
    local usage_msg="Izmantošana: ${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
}

Funkcija get-active-slot prasa nelielus paskaidrojumus:

Kāpēc tā atgriež skaitli, nevis izvada virkni?

Tomēr izsaukšanas funkcijā mēs pārbaudām tās izpildes rezultātu, un pārbaudīt exit kodu ar bash līdzekļiem ir daudz vienkāršāk nekā virkni. Turklāt, iegūt no tās virkni ir ļoti vienkārši:
get-active-slot service && echo BLUE || echo GREEN.

Vai trīs nosacījumu nepietiek, lai atšķirtu visus stāvokļus?

Blue-Green izvietošana pamatiem

Pat divu pietiek, pēdējais šeit ir vienkārši pilnīgam, lai neuzrakstītu else.

Joprojām ir nenoteikta tikai funkcija, kas atgriež nginx konfigurācijas: get-nginx-config service_name deployment_slot. Līdzīgi kā ar veselības pārbaudi, šeit var noteikt jebkuru konfigurāciju jebkuram pakalpojumam. No interesantā — tikai cat <<- EOF, kas ļauj noņemt visus tabulācijas simbolus sākumā. Patiesība ir tāda, ka kārtīga formatējuma cena ir sajauktie tabuli un atstarpes, kas šodien tiek uzskatīta par ļoti sliktu toni. Bet bash piespiež tabulācijas simbolus, un arī nginx konfigurācijā nebūtu slikti iegūt normālu formatējumu. Tātad sajaukums starp tabulācijām un atstarpēm šeit šķiet patiešām labākais risinājums no sliktākajiem. Tomēr zemāk esošajā snippētā jūs to neredzēsiet, jo habr šie ir manāmi.

Lai divas reizes neizsēdētos, uzreiz pastāstīšu par cat << 'EOF', kas vēlāk parādīsies. Ja rakstīt vienkārši cat << EOF, tad iekšā heredoc notiek virknes interpolācija (tiek atvērtas mainīgās ($foo), komandu izsaukumi ($(bar)) utt.), bet, ja beigu dokumenta zīmi ieliek vienkāršajos pēdiņās, interpolācija tiek izslēgta, un simbols $ tiek izvadīts tāds, kāds tas ir. Tas ir tieši tas, kas nepieciešams, lai ievietotu skriptu cita skripta iekšpusē.

get-nginx-config() {
    local usage_msg="Izmantošana: ${FUNCNAME[0]} pakalpojuma_nosaukums dislokācijas_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
        # Šeit pievienojiet specifiskas nginx konfigurācijas jūsu pakalpojumiem
        *) nginx-config-simple-service $container_name:8080 ;;
    esac
}

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

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

Un tas ir viss skripts. Un šeit gists ar šo skriptu lejupielādei caur wget vai curl.

Parametrizētu skriptu izpilde attālinātajā serverī

Ir pienācis laiks piekļūt mērķa serverim. Šoreiz localhost piemēroti būs:

$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: cenšas pierakstīties ar jaunajiem atslēgas(-ēm), lai filtrētu ārā tās, kas jau ir instalētas
/usr/bin/ssh-copy-id: INFO: 1 atslēga(s) paliek instalēšanai -- ja jums tagad tiek prasīts, tad tas ir jauno atslēgu instalēšanai
himura@localhost's parole: 

Pievienoto atslēgu skaits: 1

Tagad mēģiniet pierakstīties mašīnā, ar: "ssh 'localhost'"
 un pārbaudiet, vai tika pievienotas tikai tās atslēgas, kuras jums bija jāpieprasa.

Mēs uzrakstījām izvietošanas skriptu, kas pārkopē iepriekš sagatavoto attēlu uz mērķa serveri un nevainojami aizstāj pakalpojuma konteineru, bet kā to izpildīt attālinātajā mašīnā? Skriptam ir argumenti, jo tas ir universāls un var izvietot vairākus pakalpojumus zem viena reversā prokšija (ar nginx konfigurācijām var sakārtot, kāds pakalpojums būs pieejams, izmantojot kādu URL). Skriptu nevajadzētu glabāt serverī, jo šādā gadījumā mēs nevarēsim to automātiski atjaunināt (lai novērstu kļūdas un pievienotu jaunus pakalpojumus), un vispār — saglabāt stāvokli = slikti.

Risinājums 1: Tomēr glabāt skriptu serverī, bet katru reizi to kopēt caur scp. Pēc tam pievienoties caur ssh un izpildīt skriptu ar nepieciešamajiem argumentiem.

Mīnusi:

  • Divas darbības, nevis viena.
  • Vietas, uz kurām jūs kopējat, var nebūt, vai arī nepieejamība var būt, vai skripts var izpildīties aizvietošanas brīdī.
  • Vēlams, lai viss būtu sakārtots (izdzēst skriptu).
  • Jau trīs darbības.

Risinājums 2:

  • Skriptā glabāt tikai funkciju definīcijas un vispār neko neizpildīt.
  • Izmantojot sed pievienot funkcijas izsaukumu beigās.
  • Nosūtīt visu tieši uz shh caur cauruļvadu (|)

Priekšrocības:

  • Patiesi bezstāvoklis.
  • Bez boilerplate entītēm.
  • Jūtos forši.

Labi, bet tikai bez Ansible. Jā, viss jau ir izgudrots. Jā, velosipēds. Skatieties, kāds vienkāršs, elegants un minimālistisks velosipēds:

$ cat < deploy.sh
#!/bin/bash

usage_msg="Usage: $0 ssh_address local_image_tag"
ssh_address=${1?$usage_msg}
image_name=${2?$usage_msg}

echo "Connecting to '$ssh_address' via ssh to seamlessly deploy '$image_name'..."
( sed "$a deploy $image_name" | ssh -T $ssh_address ) << 'END_OF_SCRIPT'
deploy() {
    echo "Yay! The '${FUNCNAME[0]}' function is executing on '$(hostname)' with argument '$1'"
}
END_OF_SCRIPT
EOF

$ chmod +x deploy.sh

$ ./deploy.sh localhost magic-porridge-pot
Izveidoju savienojumu ar localhost...
Jā! 'deploy' funkcija izpildās uz 'hut' ar argumentu 'magic-porridge-pot'.

Tomēr mēs nevaram būt droši, ka attālinātajā hostā ir adekvāts bash, tādēļ pievienosim nelielu pārbaudi sākumā (tas vietā shellbang.):

if [ "$SHELL" != "/bin/bash" ]
then
    echo "'$SHELL' shells nav atbalstīts ar 'deploy.sh'. Iestatiet 'bash' shell '$USER@$HOSTNAME'."
    exit 1
fi

Un tagad viss patiesi:

$ 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
Sūtot gzipēto attēlu 'uptimer' uz 'localhost' pa ssh...
Ielādētais attēls: uptimer:latest
Savienojamies ar 'localhost' pa ssh, lai bezšuvju uzstādītu 'uptimer'...
Uzstādam 'uptimer_GREEN' vietā 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Konteiners uzsākts. Pārbaudām veselību...
Pieprasām 'http://uptimer_GREEN:8080/' iekš 'web-gateway' docker tīkla:
  HTTP/1.0 503 Pakalpojums nav pieejams
wget: serveris atgrieza kļūdu: HTTP/1.0 503 Pakalpojums nav pieejams
Jaunais 'uptimer_GREEN' pakalpojums vēl nav gatavs. Gaidām (1)...
Pieprasām 'http://uptimer_GREEN:8080/' iekš 'web-gateway' docker tīkla:
  HTTP/1.0 503 Pakalpojums nav pieejams
wget: serveris atgrieza kļūdu: HTTP/1.0 503 Pakalpojums nav pieejams
Jaunais 'uptimer_GREEN' pakalpojums vēl nav gatavs. Gaidām (2)...
Pieprasām 'http://uptimer_GREEN:8080/' iekš 'web-gateway' docker tīkla:
  HTTP/1.0 200 OK
  Serveris: BaseHTTP/0.6 Python/3.8.3
  Datums: Sāt, 22 Aug 2020 20:15:50 GMT
  Saturs-Tips: text/html

Jaunais 'uptimer_GREEN' pakalpojums izskatās labi. Pārejam uz jauno versiju...
nginx: konfigurācijas fails /etc/nginx/nginx.conf sintakse ir pareiza
nginx: konfigurācijas fails /etc/nginx/nginx.conf tests ir veiksmīgs
2020/08/22 20:15:54 [paziņojums] 97#97: signāls procesam uzsākts
'uptimer_GREEN' pakalpojums ir aktīvs!
Nobeidzam 'uptimer_BLUE'...
uptimer_BLUE
Kopējā atbrīvotā telpa: 0B
Uzstādīšana veiksmīga!

Tagad varat atvērt http://localhost/ pārlūkprogrammā, vēlreiz palaist uzstādīšanu un pārliecināties, ka tā notiek bezšuvju veidā, atsvaidzinot lapu ar F5 laikā, kad notiek uzstādīšana.

Neaizmirstiet sakopt pēc darba :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

Avots: habr.com

Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem 🔥 Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem | ProHoster