Минималдуу эмгек акы боюнча Blue-Green Deployment

Бул макалада биз колдонобуз Баш, SSH, ютуб и жөргөмүш Биз веб-тиркеменин кемчиликсиз макетін уюштурабыз. Көк-жашыл жайылтуу бир эле өтүнүчтү четке какпастан, тиркемени заматта жаңыртууга мүмкүндүк берген техника. Бул нөлдүк токтоп калуу стратегияларынын бири жана бир нускасы бар тиркемелер үчүн эң ылайыктуу, бирок жакын жерде экинчи, иштетүүгө даяр инстанцияны жүктөө мүмкүнчүлүгү.

Сизде көптөгөн кардарлар жигердүү иштеп жаткан веб-тиркеме бар дейли, жана анын бир-эки секунд жатып калышына таптакыр жол жок. Сиз чындап эле китепкана жаңыртуу, мүчүлүштүктөрдү оңдоо же жаңы сонун функцияны чыгарышыңыз керек. Кадимки кырдаалда, сиз колдонмону токтотуп, аны алмаштырып, кайра башташыңыз керек болот. Докердин шартында, сиз адегенде аны алмаштырып, анан кайра иштетсеңиз болот, бирок тиркемеге болгон суроо-талаптар иштелбей турган мезгил болот, анткени, эреже катары, тиркемени жүктөө үчүн бир аз убакыт талап кылынат. Ал башталса, бирок иштебей калсачы? Бул көйгөй, келгиле, аны минималдуу каражаттар менен жана мүмкүн болушунча жарашыктуу чечели.

ЭСКЕРТҮҮ: Макаланын көбү эксперименталдык форматта - консолдук сессиянын жазуусу түрүндө берилген. Муну түшүнүү өтө кыйын болбойт жана код өзүн жетиштүү түрдө документтештирет деп үмүттөнөбүз. Атмосфера үчүн бул жөн гана код үзүндүлөрү эмес, “темир” телетайптын кагазы деп элестетиңиз.

Минималдуу эмгек акы боюнча Blue-Green Deployment

Кодду окуп эле Google үчүн кыйын болгон кызыктуу ыкмалар ар бир бөлүмдүн башында баяндалат. Эгер дагы бир нерсе түшүнүксүз болсо, аны гуглдан таап, текшериңиз. түшүндүрүү (Бактыга жараша, ал телеграмманын бөгөттөн чыгарылгандыгына байланыштуу кайра иштейт). Эгер Google эч нерсе кыла албасаңыз, комментарийлерде сураңыз. Тиешелүү бөлүмгө "Кызыктуу техникалар" кошууга кубанычта болом.

Улантуу.

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

кызмат

Эксперименталдык кызмат жасап, аны контейнерге салалы.

Кызыктуу техникалар

  • cat << EOF > file-name (Бул жерде документ + I/O кайра багыттоо) бир команда менен көп саптуу файлды түзүү ыкмасы. Бардык bash окуйт /dev/stdin бул саптан кийин жана саптын алдында EOF жылы жазылат file-name.
  • wget -qO- URL (түшүндүрүү) — HTTP аркылуу алынган документти чыгаруу /dev/stdout (аналог curl URL).

Басып чыгаруу

Мен өзгөчө Python үчүн бөлүп көрсөтүүнү иштетүү үчүн үзүндүнү сындырдым. Аягында ушул сыяктуу дагы бир чыгарма болот. Бул жерлерде кагазды бөлүп көрсөтүү бөлүмүнө жөнөтүү үчүн кесилгенин (ал жерде кодду кол менен боёгучтар менен боёп коюшкан), андан кийин бул бөлүктөр кайра чапталганын карап көрөлү.

$ 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

Тескери прокси

Биздин тиркеме байкалбастан өзгөрө алышы үчүн, анын алдында анын алмаштырууну жашыра турган башка объект болушу керек. Бул веб-сервер болушу мүмкүн жөргөмүш в тескери прокси режими. Кардар менен тиркеменин ортосунда тескери прокси орнотулат. Ал кардарлардын суроо-талаптарын кабыл алат жана аларды колдонмого жөнөтөт жана өтүнмөнүн жоопторун кардарларга жөнөтөт.

Тиркеме жана тескери прокси аркылуу докердин ичинде байланыштырса болот докер тармагы. Ошентип, колдонмосу бар контейнерге хост тутумундагы портту башка жакка жөнөтүүнүн кереги жок; бул колдонмону тышкы коркунучтардан максималдуу түрдө обочолонтууга мүмкүндүк берет.

Эгерде тескери прокси башка хостто жашаса, сиз докер тармагынан баш тартып, портту багыттоо менен тиркемени хост тармагы аркылуу тескери проксиге туташтырыңыз. колдонмолор параметр --publish, биринчи башталгычтагыдай жана тескери проксидегидей.

Биз 80-портто тескери проксиди иштетебиз, анткени дал ушул нерсе тышкы тармакты угушу керек. Эгерде 80 порту сыноо хостуңузда бош эмес болсо, параметрди өзгөртүңүз --publish 80:80 боюнча --publish ANY_FREE_PORT:80.

Кызыктуу техникалар

Басып чыгаруу

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

Бир калыпта жайгаштыруу

Келгиле, тиркеменин жаңы версиясын чыгаралы (эки эсе баштоонун натыйжалуулугун жогорулатуу менен) жана аны бир калыпта жайгаштырууга аракет кылалы.

Кызыктуу техникалар

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' — Текст жаз my text файлга /my-file.txt контейнердин ичинде my-container.
  • cat > /my-file.txt — Стандарттык киргизүүнүн мазмунун файлга жазыңыз /dev/stdin.

Басып чыгаруу

$ 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

Бул этапта, сүрөт түздөн-түз серверде курулат, ал тиркеме булактарынын ошол жерде болушун талап кылат, ошондой эле серверди керексиз жумуш менен жүктөйт. Кийинки кадам сүрөт жыйындысын өзүнчө машинага (мисалы, CI системасына) бөлүп, андан кийин серверге өткөрүп берүү.

Сүрөттөрдү өткөрүү

Тилекке каршы, сүрөттөрдү localhost'тан localhostко которуунун мааниси жок, андыктан бул бөлүмдү Docker менен эки хостуңуз болгондо гана изилдөөгө болот. Жок дегенде, ал төмөнкүдөй көрүнөт:

$ 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

команда docker save сүрөттөлүштүн берилиштерин .tar архивинде сактайт, башкача айтканда, ал кысылган түрдө таразага караганда болжол менен 1.5 эсеге көп. Келгиле, убакытты жана трафикти үнөмдөө максатында аны чайкап көрөлү:

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

Сиз ошондой эле жүктөө процессин көзөмөлдөй аласыз (бирок бул үчүнчү тараптын утилитасын талап кылат):

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

Кеңеш: SSH аркылуу серверге туташуу үчүн бир катар параметрлер керек болсо, сиз файлды колдонбой жаткан болушуңуз мүмкүн ~/.ssh/config.

Сүрөттү өткөрүү аркылуу docker image save/load - Бул эң минималисттик ыкма, бирок жалгыз эмес. Башкалар бар:

  1. Контейнер реестри (тармактык стандарт).
  2. Башка хосттон докер демон серверине туташуу:
    1. курчап турган чөйрөнү өзгөрмө DOCKER_HOST.
    2. Буйрук сабы опциясы -H же --host аспап docker-compose.
    3. docker context

Экинчи ыкма (аны ишке ашыруунун үч варианты менен) макалада жакшы сүрөттөлгөн Docker-compose менен алыскы Docker хостторунда кантип жайгаштыруу керек.

deploy.sh

Эми кол менен жасаган нерселердин баарын бир сценарийге чогулталы. Келгиле, жогорку деңгээлдеги функциядан баштайлы, андан кийин анда колдонулган башка функцияларды карап көрөлү.

Кызыктуу техникалар

  • ${parameter?err_msg} - баш сыйкырлардын бири (ака параметр алмаштыруу). Эгерде parameter көрсөтүлгөн эмес, чыгаруу err_msg жана коду 1 менен чыгуу.
  • docker --log-driver journald — демейки боюнча, докер журналынын драйвери эч кандай айлануусуз текст файлы. Бул ыкма менен журналдар бүт дискти тез толтурат, андыктан өндүрүш чөйрөсү үчүн драйверди акылдууга алмаштыруу керек.

Жайгаштыруу скрипти

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
}

Колдонулган өзгөчөлүктөр:

  • ensure-reverse-proxy — Тескери прокси иштеп жатканын текшерет (биринчи жайылтуу үчүн пайдалуу)
  • get-active-slot service_name — Берилген кызмат үчүн учурда кайсы слот активдүү экенин аныктайт (BLUE же GREEN)
  • get-service-status service_name deployment_slot — Кызмат түшкөн суроо-талаптарды иштетүүгө даярбы же жокпу аныктайт
  • set-active-slot service_name deployment_slot — Тескери прокси контейнериндеги nginx конфигурациясын өзгөртөт

Ирээти менен:

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
}

милдети get-active-slot бир аз түшүндүрмө талап кылат:

Эмне үчүн ал санды кайтарып, сапты чыгарбайт?

Баары бир, чакыруу функциясында биз анын ишинин жыйынтыгын текшеребиз, ал эми bash аркылуу чыгуу кодун текшерүү сапты текшерүүгө караганда алда канча жеңил. Мындан тышкары, андан сап алуу абдан жөнөкөй:
get-active-slot service && echo BLUE || echo GREEN.

Чынында эле бардык мамлекеттерди айырмалоо үчүн үч шарт жетиштүүбү?

Минималдуу эмгек акы боюнча Blue-Green Deployment

Жада калса экөө жетиштүү болот, акыркысы толуктук үчүн бул жерде, жазбаш үчүн else.

Nginx конфигурациясын кайтарган функция гана аныкталбаган бойдон калат: get-nginx-config service_name deployment_slot. Ден соолук текшерүүсүнө окшошуп, бул жерде сиз каалаган кызмат үчүн каалаган конфигурацияны орното аласыз. Кызыктуу нерселерден - бир гана cat <<- EOF, бул сиз башында бардык өтмөктөрдү алып салууга мүмкүндүк берет. Ырас, жакшы форматтоо баасы бүгүнкү күндө абдан жаман түрү болуп эсептелет боштуктар менен аралаш өтмөктөр болуп саналат. Бирок bash өтмөктөрдү күчтөндүрөт жана nginx конфигурациясында кадимки форматтоо болсо жакшы болмок. Кыскача айтканда, бул жерде боштуктар менен өтмөктөрдү аралаштыруу, чынында эле, эң начар чечим катары көрүнөт. Бирок, сиз муну төмөндөгү фрагментте көрбөйсүз, анткени Хабр бардык өтмөктөрдү 4 боштукка өзгөртүп, EOF жараксыз кылып "жакшы кылат". Жана бул жерде байкалып турат.

Эки жолу туруп калбаш үчүн, мен сага дароо айтып берем cat << 'EOF', бул кийинчерээк кездешет. Жөнөкөй жазсаңыз cat << EOF, анда heredoc ичинде сап интерполяцияланат (өзгөрмөлөр кеңейтилет ($foo), командалык чакыруулар ($(bar)) ж.б.) жана эгерде сиз документтин соңун жалгыз тырмакчага кошсоңуз, анда интерполяция өчүрүлөт жана символ $ болуп көрсөтүлөт. Башка скрипттин ичине скрипт киргизүү үчүн эмне керек.

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
}

Бул бүт сценарий. Жана ошентип бул скрипт менен wget же curl аркылуу жүктөө үчүн.

Алыскы серверде параметрленген скрипттерди аткаруу

Бул максаттуу серверди тыкылдатууга убакыт келди. Бул убакыт localhost абдан ылайыктуу:

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

Биз алдын ала түзүлгөн сүрөттү максаттуу серверге жүктөөчү жана тейлөө контейнерин кемчиликсиз алмаштыра турган жайылтуу сценарийин жаздык, бирок аны алыскы машинада кантип аткара алабыз? Скрипттин аргументтери бар, анткени ал универсалдуу жана бир эле тескери прокси астында бир нече кызматтарды орното алат (кайсы url кайсы кызмат болорун аныктоо үчүн nginx конфигурацияларын колдонсоңуз болот). Скриптти серверде сактоо мүмкүн эмес, анткени бул учурда биз аны автоматтык түрдө жаңырта албайбыз (каталарды оңдоо жана жаңы кызматтарды кошуу үчүн) жана жалпысынан абал = жамандык.

Чечим 1: Скриптти дагы эле серверде сактаңыз, бирок аны ар дайым көчүрүңүз scp. Андан кийин аркылуу байланыш ssh жана сценарийди керектүү аргументтер менен аткарыңыз.

жактары:

  • Бир эмес, эки аракет
  • Сиз көчүргөн жер жок болушу мүмкүн, же ага жетүү мүмкүн эмес, же скрипт алмаштыруу учурунда аткарылышы мүмкүн.
  • Өзүңүздөн кийин тазалоо сунушталат (скриптти жок кылуу).
  • Буга чейин үч иш-аракет.

Чечим 2:

  • Скриптте функциянын аныктамаларын гана сактап, эч нерсени иштетпеңиз
  • Жардамы менен sed аягына чейин функция чалуу кошуу
  • Баарын труба аркылуу түз эле шшга жөнөтүңүз (|)

артыкчылыктары:

  • Чынында жарандыгы жок
  • Эч кандай объектилер жок
  • Сезим салкын

Келгиле, Ansible жок эле кылалы. Ооба, баары мурунтан эле ойлоп табылган. Ооба, велосипед. Велосипед канчалык жөнөкөй, жарашыктуу жана минималисттик экенин караңыз:

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

Бирок, биз алыскы хосттун адекваттуу башына ээ экенине ишене албайбыз, андыктан башында кичинекей чекти кошобуз (бул анын ордуна 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

Ал эми азыр реалдуу:

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

Эми сиз ача аласыз http://localhost/ браузерде жайгаштырууну кайра иштетиңиз жана макет учурунда CDге ылайык баракчаны жаңыртуу менен анын үзгүлтүксүз иштешине ынаныңыз.

Жумуштан кийин тазалоону унутпаңыз :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

Source: www.habr.com