ืคืจื™ืกื” ื›ื—ื•ืœ-ื™ืจื•ืง ื‘ืฉื›ืจ ืžื™ื ื™ืžื•ื

ื‘ืžืืžืจ ื–ื” ืื ื• ืžืฉืชืžืฉื™ื ืœื—ื‘ื•ื˜, ssh, ืกึทื•ึธืจ ะธ nginx ื ืืจื’ืŸ ืคืจื™ืกื” ื—ืœืงื” ืฉืœ ืืคืœื™ืงืฆื™ื™ืช ื”ืื™ื ื˜ืจื ื˜. ืคืจื™ืกื” ื›ื—ื•ืœ-ื™ืจื•ืง ื”ื™ื ื˜ื›ื ื™ืงื” ื”ืžืืคืฉืจืช ืœืš ืœืขื“ื›ืŸ ืืคืœื™ืงืฆื™ื” ื‘ืื•ืคืŸ ืžื™ื™ื“ื™ ืžื‘ืœื™ ืœื“ื—ื•ืช ื‘ืงืฉื” ืื—ืช. ื–ื•ื”ื™ ืื—ืช ืžืืกื˜ืจื˜ื’ื™ื•ืช ื”ืคืจื™ืกื” ืฉืœ ืืคืก ื–ืžืŸ ื”ืฉื‘ืชื” ื•ื”ื™ื ืžืชืื™ืžื” ื‘ื™ื•ืชืจ ืœื™ื™ืฉื•ืžื™ื ืขื ืžื•ืคืข ืื—ื“, ืืš ื”ื™ื›ื•ืœืช ืœื˜ืขื•ืŸ ืžื•ืคืข ืฉื ื™, ืžื•ื›ืŸ ืœื”ืคืขืœื” ื‘ืงืจื‘ืช ืžืงื•ื.

ื ื ื™ื— ืฉื™ืฉ ืœืš ื™ื™ืฉื•ื ืื™ื ื˜ืจื ื˜ ืฉืื™ืชื• ืœืงื•ื—ื•ืช ืจื‘ื™ื ืขื•ื‘ื“ื™ื ื‘ืื•ืคืŸ ืคืขื™ืœ, ื•ืื™ืŸ ืฉื•ื ื“ืจืš ืฉื”ื•ื ื™ืฉื›ื‘ ืœื›ืžื” ืฉื ื™ื•ืช. ื•ืืชื” ื‘ืืžืช ืฆืจื™ืš ืœื”ื•ืฆื™ื ืขื“ื›ื•ืŸ ืกืคืจื™ื™ื”, ืชื™ืงื•ืŸ ื‘ืื’ื™ื ืื• ืชื›ื•ื ื” ืžื’ื ื™ื‘ื” ื—ื“ืฉื”. ื‘ืžืฆื‘ ืจื’ื™ืœ, ื™ื”ื™ื” ืขืœื™ืš ืœืขืฆื•ืจ ืืช ื”ื™ื™ืฉื•ื, ืœื”ื—ืœื™ืฃ ืื•ืชื• ื•ืœื”ืคืขื™ืœ ืื•ืชื• ืฉื•ื‘. ื‘ืžืงืจื” ืฉืœ docker, ืืชื” ื™ื›ื•ืœ ืชื—ื™ืœื” ืœื”ื—ืœื™ืฃ ืื•ืชื•, ื•ืื– ืœื”ืคืขื™ืœ ืื•ืชื• ืžื—ื“ืฉ, ืืš ืขื“ื™ื™ืŸ ืชื”ื™ื” ืชืงื•ืคื” ืฉื‘ื” ื‘ืงืฉื•ืช ืœืืคืœื™ืงืฆื™ื” ืœื ื™ืขื•ื‘ื“ื•, ื›ื™ ื‘ื“ืจืš ื›ืœืœ ืœืืคืœื™ืงืฆื™ื” ืœื•ืงื— ืงืฆืช ื–ืžืŸ ืœื˜ืขื•ืŸ ืชื—ื™ืœื”. ืžื” ืื ื–ื” ื™ืชื—ื™ืœ, ืื‘ืœ ื™ืชื‘ืจืจ ืฉื”ื•ื ืœื ื ื™ืชืŸ ืœื”ืคืขืœื”? ื–ื• ื”ื‘ืขื™ื”, ื‘ื•ืื• ื ืคืชื•ืจ ืื•ืชื” ื‘ืืžืฆืขื™ื ืžื™ื ื™ืžืœื™ื™ื ื•ื‘ืืœื’ื ื˜ื™ื•ืช ื›ื›ืœ ื”ืืคืฉืจ.

DISCLAIMER: ืจื•ื‘ ื”ืžืืžืจ ืžื•ืฆื’ ื‘ืคื•ืจืžื˜ ื ื™ืกื™ื•ื ื™ - ื‘ืฆื•ืจื” ืฉืœ ื”ืงืœื˜ื” ืฉืœ โ€‹โ€‹ืกืฉืŸ ืงื•ื ืกื•ืœื”. ืื ื™ ืžืงื•ื•ื” ืฉื–ื” ืœื ื™ื”ื™ื” ืงืฉื” ืžื“ื™ ืœื”ื‘ื ื” ื•ื”ืงื•ื“ ื™ืชืขื“ ืืช ืขืฆืžื• ืžืกืคื™ืง. ืœืื•ื•ื™ืจื”, ื“ืžื™ื™ื ื• ืฉืืœื• ืœื ืจืง ืงื˜ืขื™ ืงื•ื“, ืืœื ื ื™ื™ืจ ืžื˜ืœื˜ื™ื™ืค "ื‘ืจื–ืœ".

ืคืจื™ืกื” ื›ื—ื•ืœ-ื™ืจื•ืง ื‘ืฉื›ืจ ืžื™ื ื™ืžื•ื

ื˜ื›ื ื™ืงื•ืช ืžืขื ื™ื™ื ื•ืช ืฉืงืฉื” ืœื’ื•ื’ืœ ืจืง ืขืœ ื™ื“ื™ ืงืจื™ืืช ื”ืงื•ื“ ืžืชื•ืืจื•ืช ื‘ืชื—ื™ืœืช ื›ืœ ืกืขื™ืฃ. ืื ืขื•ื“ ืžืฉื”ื• ืœื ื‘ืจื•ืจ, ื—ืคืฉ ื‘ื’ื•ื’ืœ ื•ืชื‘ื“ื•ืง. ืžืกื‘ื™ืจ ืžืขื˜ืคืช (ืœืžืจื‘ื” ื”ืžื–ืœ, ื–ื” ืขื•ื‘ื“ ืฉื•ื‘, ืขืงื‘ ื‘ื™ื˜ื•ืœ ื”ื—ืกื™ืžื” ืฉืœ ื”ืžื‘ืจืง). ืื ืืชื” ืœื ื™ื›ื•ืœ ืœืขืฉื•ืช ืฉื•ื ื“ื‘ืจ ื‘ื’ื•ื’ืœ, ืฉืืœ ื‘ืชื’ื•ื‘ื•ืช. ืืฉืžื— ืœื”ื•ืกื™ืฃ ืœืกืขื™ืฃ ื”ืžืงื‘ื™ืœ "ื˜ื›ื ื™ืงื•ืช ืžืขื ื™ื™ื ื•ืช".

ื‘ื•ืื• ื ืชื—ื™ืœ.

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

ืฉื™ืจื•ืช

ื‘ื•ืื• ื ืขืฉื” ืฉื™ืจื•ืช ื ื™ืกื™ื•ื ื™ ื•ื ื ื™ื— ืื•ืชื• ื‘ืžื™ื›ืœ.

ื˜ื›ื ื™ืงื•ืช ืžืขื ื™ื™ื ื•ืช

  • cat << EOF > file-name (ื›ืืŸ ืžืกืžืš + ื ื™ืชื•ื‘ I/O) ื”ื™ื ื“ืจืš ืœื™ืฆื•ืจ ืงื•ื‘ืฅ ืจื‘ ืฉื•ืจื•ืช ื‘ืคืงื•ื“ื” ืื—ืช. ื›ืœ ืžื” ืฉื‘ืืฉ ืงื•ืจื ืžืžื ื• /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

ืคืจื•ืงืกื™ ื”ืคื•ืš

ื›ื“ื™ ืฉื”ืืคืœื™ืงืฆื™ื” ืฉืœื ื• ืชื•ื›ืœ ืœื”ืฉืชื ื•ืช ื‘ืœื™ ืœืฉื™ื ืœื‘, ื™ืฉ ืฆื•ืจืš ืฉืชื”ื™ื” ื™ืฉื•ืช ืื—ืจืช ืžื•ืœื” ืฉืชืกืชื™ืจ ืืช ื”ื—ืœืคืชื”. ื–ื” ื™ื›ื•ืœ ืœื”ื™ื•ืช ืฉืจืช ืื™ื ื˜ืจื ื˜ nginx ะฒ ืžืฆื‘ ืคืจื•ืงืกื™ ื”ืคื•ืš. ื ื•ืฆืจ ืคืจื•ืงืกื™ ื”ืคื•ืš ื‘ื™ืŸ ื”ืœืงื•ื— ืœืืคืœื™ืงืฆื™ื”. ื”ื•ื ืžืงื‘ืœ ื‘ืงืฉื•ืช ืžืœืงื•ื—ื•ืช ื•ืžืขื‘ื™ืจ ืื•ืชืŸ ืœืืคืœื™ืงืฆื™ื” ื•ืžืขื‘ื™ืจ ืืช ืชืฉื•ื‘ื•ืช ื”ืืคืœื™ืงืฆื™ื” ืœืœืงื•ื—ื•ืช.

ื ื™ืชืŸ ืœืงืฉืจ ืืช ื”ืืคืœื™ืงืฆื™ื” ื•ื”ืคืจื•ืงืกื™ ื”ืคื•ื›ื” ื‘ืชื•ืš docker ื‘ืืžืฆืขื•ืช ืจืฉืช docker. ืœืคื™ื›ืš, ื”ืงื•ื ื˜ื™ื™ื ืจ ืขื ื”ืืคืœื™ืงืฆื™ื” ืืคื™ืœื• ืœื ืฆืจื™ืš ืœื”ืขื‘ื™ืจ ืคื•ืจื˜ ื‘ืžืขืจื›ืช ื”ืžืืจื—ืช; ื–ื” ืžืืคืฉืจ ืœืืคืœื™ืงืฆื™ื” ืœื”ื™ื•ืช ืžื‘ื•ื“ื“ืช ืžืงืกื™ืžืœื™ืช ืžืื™ื•ืžื™ื ื—ื™ืฆื•ื ื™ื™ื.

ืื ื”-proxy ื”ื”ืคื•ืš ื—ื™ ืขืœ ืžืืจื— ืื—ืจ, ืชืฆื˜ืจืš ืœื ื˜ื•ืฉ ืืช ืจืฉืช ื”ื“ื•ืงืจ ื•ืœื—ื‘ืจ ืืช ื”ื™ื™ืฉื•ื ืœ-proxy ื”ื”ืคื•ืš ื“ืจืš ื”ืจืฉืช ื”ืžืืจื—ืช, ื•ืœื”ืขื‘ื™ืจ ืืช ื”ื™ืฆื™ืื” ืืคืœื™ืงืฆื™ื•ืช ืคึผึธืจึธืžึถื˜ึถืจ --publish, ื›ืžื• ื‘ื”ืชื—ืœื” ื”ืจืืฉื•ื ื” ื•ื›ืžื• ื‘ืคืจื•ืงืกื™ ื”ื”ืคื•ืš.

ื ืจื™ืฅ ืืช ื”-proxy ื”ื”ืคื•ืš ืขืœ ื™ืฆื™ืื” 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. ื”ืชื—ื‘ืจ ืœืฉืจืช Docker daemon ืžืžืืจื— โ€‹โ€‹ืื—ืจ:
    1. ืžืฉืชื ื” ื”ืกื‘ื™ื‘ื” DOCKER_HOST.
    2. ืืคืฉืจื•ืช ืฉื•ืจืช ื”ืคืงื•ื“ื” -H ืื• --host ื›ืœื™ docker-compose.
    3. docker context

ื”ืฉื™ื˜ื” ื”ืฉื ื™ื™ื” (ืขื ืฉืœื•ืฉ ืืคืฉืจื•ื™ื•ืช ืœื™ื™ืฉื•ื ืฉืœื”) ืžืชื•ืืจืช ื”ื™ื˜ื‘ ื‘ืžืืžืจ ื›ื™ืฆื“ ืœืคืจื•ืก ื‘ืžืืจื—ื™ Docker ืžืจื•ื—ืงื™ื ืขื docker-compose.

deploy.sh

ืขื›ืฉื™ื• ื‘ื•ืื• ื ืืกื•ืฃ ืืช ื›ืœ ืžื” ืฉืขืฉื™ื ื• ื‘ืื•ืคืŸ ื™ื“ื ื™ ืœืกืงืจื™ืคื˜ ืื—ื“. ื ืชื—ื™ืœ ืขื ื”ืคื•ื ืงืฆื™ื” ื‘ืจืžื” ื”ืขืœื™ื•ื ื”, ื•ืœืื—ืจ ืžื›ืŸ ื ืกืชื›ืœ ืขืœ ื”ืื—ืจื™ื ื”ืžืฉืžืฉื™ื ื‘ื”.

ื˜ื›ื ื™ืงื•ืช ืžืขื ื™ื™ื ื•ืช

  • ${parameter?err_msg} - ืื—ื“ ืžื›ืฉืคื™ ื”ืงืกื ืฉืœ ื”ื‘ืฉ (aka ื”ื—ืœืคืช ืคืจืžื˜ืจื™ื). ืื 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 - ืžื•ื•ื“ื ืฉื”-proxy ื”ื”ืคื•ืš ืคื•ืขืœ (ืฉื™ืžื•ืฉื™ ืœืคืจื™ืกื” ื”ืจืืฉื•ื ื”)
  • get-active-slot service_name - ืงื•ื‘ืข ืื™ื–ื• ืžืฉื‘ืฆืช ืคืขื™ืœื” ื›ืขืช ืขื‘ื•ืจ ืฉื™ืจื•ืช ื ืชื•ืŸ (BLUE ืื• GREEN)
  • get-service-status service_name deployment_slot โ€” ืงื•ื‘ืข ืื ื”ืฉื™ืจื•ืช ืžื•ื›ืŸ ืœืขื‘ื“ ื‘ืงืฉื•ืช ื ื›ื ืกื•ืช
  • set-active-slot service_name deployment_slot - ืžืฉื ื” ืืช ืชืฆื•ืจืช ื”-nginx ื‘ืžื™ื›ืœ ื”-proxy ื”ื”ืคื•ืš

ื›ื“ื™:

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.

ื”ืื ื‘ืืžืช ืžืกืคื™ืงื™ื ืฉืœื•ืฉื” ืชื ืื™ื ื›ื“ื™ ืœื”ื‘ื—ื™ืŸ ื‘ื™ืŸ ื›ืœ ื”ืžื“ื™ื ื•ืช?

ืคืจื™ืกื” ื›ื—ื•ืœ-ื™ืจื•ืง ื‘ืฉื›ืจ ืžื™ื ื™ืžื•ื

ืืคื™ืœื• ืฉื ื™ื™ื ื™ืกืคื™ืงื•, ื”ืื—ืจื•ืŸ ื›ืืŸ ืจืง ืœืฉื ื”ืฉืœืžื•ืช, ื›ื“ื™ ืœื ืœื›ืชื•ื‘ 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.

ื›ืชื‘ื ื• ืกืงืจื™ืคื˜ ืคืจื™ืกื” ืฉืžื•ืจื™ื“ ืชืžื•ื ื” ื‘ื ื•ื™ื” ืžืจืืฉ ืœืฉืจืช ื”ื™ืขื“ ื•ืžื—ืœื™ืฃ ื‘ืฆื•ืจื” ื—ืœืงื” ืืช ืžื™ื›ืœ ื”ืฉื™ืจื•ืช, ืื‘ืœ ืื™ืš ื ื•ื›ืœ ืœื”ืคืขื™ืœ ืื•ืชื• ื‘ืžื—ืฉื‘ ืžืจื•ื—ืง? ืœืกืงืจื™ืคื˜ ื™ืฉ ืืจื’ื•ืžื ื˜ื™ื, ืžื›ื™ื•ื•ืŸ ืฉื”ื•ื ืื•ื ื™ื‘ืจืกืœื™ ื•ื™ื›ื•ืœ ืœืคืจื•ืก ืžืกืคืจ ืฉื™ืจื•ืชื™ื ื‘ื•-ื–ืžื ื™ืช ืชื—ืช ืคืจื•ืงืกื™ ื”ืคื•ืš ืื—ื“ (ืชื•ื›ืœ ืœื”ืฉืชืžืฉ ื‘ืชืฆื•ืจื•ืช nginx ื›ื“ื™ ืœืงื‘ื•ืข ืื™ื–ื• url ื™ื”ื™ื” ืื™ื–ื” ืฉื™ืจื•ืช). ืœื ื ื™ืชืŸ ืœืื—ืกืŸ ืืช ื”ืกืงืจื™ืคื˜ ื‘ืฉืจืช, ืฉื›ืŸ ื‘ืžืงืจื” ื–ื” ืœื ื ื•ื›ืœ ืœืขื“ื›ืŸ ืื•ืชื• ืื•ื˜ื•ืžื˜ื™ืช (ืœืฆื•ืจืš ืชื™ืงื•ื ื™ ื‘ืื’ื™ื ื•ื”ื•ืกืคืช ืฉื™ืจื•ืชื™ื ื—ื“ืฉื™ื), ื•ื‘ื›ืœืœ ืžืฆื‘ = evil.

ืคืชืจื•ืŸ 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'

ืขื ื–ืืช, ืื ื—ื ื• ืœื ื™ื›ื•ืœื™ื ืœื”ื™ื•ืช ื‘ื˜ื•ื—ื™ื ืฉืœืžืืจื— ื”ืžืจื•ื—ืง ื™ืฉ bash ืžืกืคื™ืง, ืื– ื ื•ืกื™ืฃ ืกื™ืžื•ืŸ ืงื˜ืŸ ื‘ื”ืชื—ืœื” (ื–ื” ื‘ืžืงื•ื 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/ ื‘ื“ืคื“ืคืŸ, ื”ืคืขืœ ืฉื•ื‘ ืืช ื”ืคืจื™ืกื” ื•ื•ื“ื ืฉื”ื™ื ืคื•ืขืœืช ื‘ืฆื•ืจื” ื—ืœืงื” ืขืœ ื™ื“ื™ ืขื“ื›ื•ืŸ ื”ื“ืฃ ื‘ื”ืชืื ืœืชืงืœื™ื˜ื•ืจ ื‘ืžื”ืœืš ื”ืคืจื™ืกื”.

ืืœ ืชืฉื›ื— ืœื ืงื•ืช ืื—ืจื™ ื”ืขื‘ื•ื“ื” :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

ืžืงื•ืจ: www.habr.com