рдХрд┐рдорд╛рди рд╡реЗрддрдирд╛рд╡рд░ рдирд┐рд│рд╛-рд╣рд┐рд░рд╡рд╛ рддреИрдирд╛рдд

рдпрд╛ рд▓реЗрдЦрд╛рдд рдЖрдореНрд╣реА рд╡рд╛рдкрд░рддреЛ рдмрд╛рд╢, рдПрд╕рдПрд╕рдПрдЪ, рдбреЙрдХрд░ ╨╕ nginx рдЖрдореНрд╣реА рд╡реЗрдм рдНрдкреНрд▓рд┐рдХреЗрд╢рдирдЪрд╛ рдЕрдЦрдВрдб рд▓реЗрдЖрдЙрдЯ рдЖрдпреЛрдЬрд┐рдд рдХрд░реВ. рдирд┐рд│рд╛-рд╣рд┐рд░рд╡рд╛ рдЙрдкрдпреЛрдЬрди рд╣реЗ рдПрдХ рддрдВрддреНрд░ рдЖрд╣реЗ рдЬреЗ рддреБрдореНрд╣рд╛рд▓рд╛ рдПрдХрд╣реА рд╡рд┐рдирдВрддреА рдирд╛рдХрд╛рд░рд▓реНрдпрд╛рд╢рд┐рд╡рд╛рдп рдЕрдиреБрдкреНрд░рдпреЛрдЧ рддреНрд╡рд░рд┐рдд рдЕрджреНрдпрддрдирд┐рдд рдХрд░рдгреНрдпрд╛рдЪреА рдкрд░рд╡рд╛рдирдЧреА рджреЗрддреЗ. рд╣реА рд╢реВрдиреНрдп рдбрд╛рдЙрдирдЯрд╛рдЗрдо рдЙрдкрдпреЛрдЬрди рдзреЛрд░рдгрд╛рдВрдкреИрдХреА рдПрдХ рдЖрд╣реЗ рдЖрдгрд┐ рдПрдХрд╛ рдЙрджрд╛рд╣рд░рдгрд╛рд╕рд╣ рдЕрдиреБрдкреНрд░рдпреЛрдЧрд╛рдВрд╕рд╛рдареА рд╕рд░реНрд╡реЛрддреНрддрдо рдЕрдиреБрдХреВрд▓ рдЖрд╣реЗ, рдкрд░рдВрддреБ рдЬрд╡рд│рдЪ рджреБрд╕рд░рд╛, рддрдпрд╛рд░-рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА-рдЪрд╛рд▓рд╡рдгрд╛рд░рд╛ рдкреНрд░рд╕рдВрдЧ рд▓реЛрдб рдХрд░рдгреНрдпрд╛рдЪреА рдХреНрд╖рдорддрд╛ рдЖрд╣реЗ.

рд╕рдордЬрд╛ рддреБрдордЪреНрдпрд╛рдХрдбреЗ рдПрдХ рд╡реЗрдм рдНрдкреНрд▓рд┐рдХреЗрд╢рди рдЖрд╣реЗ рдЬреНрдпрд╛рд╕рд╣ рдмрд░реЗрдЪ рдХреНрд▓рд╛рдпрдВрдЯ рд╕рдХреНрд░рд┐рдпрдкрдгреЗ рдХрд╛рдо рдХрд░рдд рдЖрд╣реЗрдд рдЖрдгрд┐ рдХрд╛рд╣реА рд╕реЗрдХрдВрджрд╛рдВрд╕рд╛рдареА рдЭреЛрдкрдгреНрдпрд╛рдЪрд╛ рдХреЛрдгрддрд╛рд╣реА рдорд╛рд░реНрдЧ рдирд╛рд╣реА. рдЖрдгрд┐ рддреБрдореНрд╣рд╛рд▓рд╛ рдЦрд░реЛрдЦрд░ рд▓рд╛рдпрдмреНрд░рд░реА рдЕрдкрдбреЗрдЯ, рдмрдЧ рдлрд┐рдХреНрд╕ рдХрд┐рдВрд╡рд╛ рдирд╡реАрди рдЫрд╛рди рд╡реИрд╢рд┐рд╖реНрдЯреНрдп рд░реЛрд▓ рдЖрдЙрдЯ рдХрд░рдгреНрдпрд╛рдЪреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдЖрд╣реЗ. рд╕рд╛рдорд╛рдиреНрдп рдкрд░рд┐рд╕реНрдерд┐рддреАрдд, рддреБрдореНрд╣рд╛рд▓рд╛ рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдерд╛рдВрдмрд╡рд╛рд╡рд╛ рд▓рд╛рдЧреЗрд▓, рддреЛ рдкреБрдирд░реНрд╕реНрдерд┐рдд рдХрд░рд╛ рдЖрдгрд┐ рддреЛ рдкреБрдиреНрд╣рд╛ рд╕реБрд░реВ рдХрд░рд╛. рдбреЙрдХрд░рдЪреНрдпрд╛ рдмрд╛рдмрддреАрдд, рддреБрдореНрд╣реА рдкреНрд░рдердо рддреЗ рдкреБрдирд░реНрд╕реНрдерд┐рдд рдХрд░реВ рд╢рдХрддрд╛, рдирдВрддрд░ рддреЗ рд░реАрд╕реНрдЯрд╛рд░реНрдЯ рдХрд░реВ рд╢рдХрддрд╛, рдкрд░рдВрддреБ рддрд░реАрд╣реА рдПрдХ рдХрд╛рд▓рд╛рд╡рдзреА рдЕрд╕реЗрд▓ рдЬреНрдпрд╛рдордзреНрдпреЗ рдЕрдиреБрдкреНрд░рдпреЛрдЧрд╛рдЪреНрдпрд╛ рд╡рд┐рдирдВрддреНрдпрд╛рдВрд╡рд░ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХреЗрд▓реА рдЬрд╛рдгрд╛рд░ рдирд╛рд╣реА, рдХрд╛рд░рдг рд╕рд╣рд╕рд╛ рдЕрдиреБрдкреНрд░рдпреЛрдЧрд╛рд╕ рдкреНрд░рд╛рд░рдВрднреА рд▓реЛрдб рд╣реЛрдгреНрдпрд╛рд╕ рдереЛрдбрд╛ рд╡реЗрд│ рд▓рд╛рдЧрддреЛ. рдЬрд░ рддреЗ рд╕реБрд░реВ рдЭрд╛рд▓реЗ, рдкрд░рдВрддреБ рдЕрдХреНрд╖рдо рдЭрд╛рд▓реЗ рддрд░? рд╣реА рд╕рдорд╕реНрдпрд╛ рдЖрд╣реЗ, рддреА рдХрдореАрддрдХрдореА рдорд╛рдзреНрдпрдорд╛рдВрдиреА рдЖрдгрд┐ рд╢рдХреНрдп рддрд┐рддрдХреНрдпрд╛ рд╕реБрдВрджрд░рдкрдгреЗ рд╕реЛрдбрд╡реВрдпрд╛.

рдЕрд╕реНрд╡реАрдХрд░рдг: рдмрд╣реБрддреЗрдХ рд▓реЗрдЦ рдкреНрд░рд╛рдпреЛрдЧрд┐рдХ рд╕реНрд╡рд░реВрдкрд╛рдд рд╕рд╛рджрд░ рдХреЗрд▓реЗ рдЬрд╛рддрд╛рдд - рдХрдиреНрд╕реЛрд▓ рд╕рддреНрд░рд╛рдЪреНрдпрд╛ рд░реЗрдХреЙрд░реНрдбрд┐рдВрдЧрдЪреНрдпрд╛ рд╕реНрд╡рд░реВрдкрд╛рдд. рдЖрд╢рд╛ рдЖрд╣реЗ рдХреА рд╣реЗ рд╕рдордЬрдгреЗ рдлрд╛рд░ рдХрдареАрдг рд╣реЛрдгрд╛рд░ рдирд╛рд╣реА рдЖрдгрд┐ рдХреЛрдб рд╕реНрд╡рддрдГрдЪ рдкреБрд░реЗрд╕реЗ рджрд╕реНрддрдРрд╡рдЬреАрдХрд░рдг рдХрд░реЗрд▓. рд╡рд╛рддрд╛рд╡рд░рдгрд╛рд╕рд╛рдареА, рдХрд▓реНрдкрдирд╛ рдХрд░рд╛ рдХреА рд╣реЗ рдлрдХреНрдд рдХреЛрдб рд╕реНрдирд┐рдкреЗрдЯ рдирд╛рд╣реАрдд рддрд░ тАЬрд▓реЛрд╣тАЭ рдЯреЗрд▓рд┐рдЯрд╛рдЗрдкрдордзреАрд▓ рдХрд╛рдЧрдж рдЖрд╣реЗрдд.

рдХрд┐рдорд╛рди рд╡реЗрддрдирд╛рд╡рд░ рдирд┐рд│рд╛-рд╣рд┐рд░рд╡рд╛ рддреИрдирд╛рдд

рдлрдХреНрдд рдХреЛрдб рд╡рд╛рдЪреВрди 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).

рдкреНрд░рд┐рдВрдЯрдЖрдЙрдЯ

рдкрд╛рдпрдердирд╕рд╛рдареА рд╣рд╛рдпрд▓рд╛рдЗрдЯрд┐рдВрдЧ рд╕рдХреНрд╖рдо рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдореА рд╡рд┐рд╢реЗрд╖рддрдГ рд╕реНрдирд┐рдкреЗрдЯ рдЦрдВрдбрд┐рдд рдХрд░рддреЛ. рд╢реЗрд╡рдЯреА рдЕрд╕рд╛ рджреБрд╕рд░рд╛ рддреБрдХрдбрд╛ рдЕрд╕реЗрд▓. рд▓рдХреНрд╖рд╛рдд рдШреНрдпрд╛ рдХреА рдпрд╛ рдард┐рдХрд╛рдгреА рдХрд╛рдЧрдж рд╣рд╛рдпрд▓рд╛рдЗрдЯрд┐рдВрдЧ рд╡рд┐рднрд╛рдЧрд╛рдХрдбреЗ рдкрд╛рдард╡рдгреНрдпрд╛рд╕рд╛рдареА рдХрд╛рдкрд▓рд╛ рдЧреЗрд▓рд╛ рд╣реЛрддрд╛ (рдЬреЗрдереЗ рдХреЛрдб рд╣рд╛рдпрд▓рд╛рдЗрдЯрд░рд╕рд╣ рд╣рд╛рддрд╛рдиреЗ рд░рдВрдЧрд╡рд▓реЗрд▓рд╛ рд╣реЛрддрд╛), рдЖрдгрд┐ рдирдВрддрд░ рд╣реЗ рддреБрдХрдбреЗ рдкрд░рдд рдЪрд┐рдХрдЯрд╡рд▓реЗ рдЧреЗрд▓реЗ.

$ 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 ╨▓ рд░рд┐рд╡реНрд╣рд░реНрд╕ рдкреНрд░реЙрдХреНрд╕реА рдореЛрдб. рдХреНрд▓рд╛рдпрдВрдЯ рдЖрдгрд┐ рдНрдкреНрд▓рд┐рдХреЗрд╢рди рджрд░рдореНрдпрд╛рди рд░рд┐рд╡реНрд╣рд░реНрд╕ рдкреНрд░реЙрдХреНрд╕реА рд╕реНрдерд╛рдкрд┐рдд рдХреЗрд▓реА рдЬрд╛рддреЗ. рд╣реЗ рдХреНрд▓рд╛рдпрдВрдЯрдХрдбреВрди рдЖрд▓реЗрд▓реНрдпрд╛ рд╡рд┐рдирдВрддреНрдпрд╛ рд╕реНрд╡реАрдХрд╛рд░рддреЗ рдЖрдгрд┐ рддреНрдпрд╛рдВрдирд╛ рдЕреЕрдкреНрд▓рд┐рдХреЗрд╢рдирдХрдбреЗ рдкрд╛рдард╡рддреЗ рдЖрдгрд┐ рдЕреЕрдкреНрд▓рд┐рдХреЗрд╢рдирдЪреЗ рдкреНрд░рддрд┐рд╕рд╛рдж рдХреНрд▓рд╛рдпрдВрдЯрд▓рд╛ рдлреЙрд░рд╡рд░реНрдб рдХрд░рддреЗ.

рдЕреЕрдкреНрд▓рд┐рдХреЗрд╢рди рдЖрдгрд┐ рд░рд┐рд╡реНрд╣рд░реНрд╕ рдкреНрд░реЙрдХреНрд╕реА рдбреЙрдХрд░рдЪреНрдпрд╛ рдЖрдд рдЬреЛрдбрд▓реЗ рдЬрд╛рдК рд╢рдХрддрд╛рдд рдбреЙрдХрд░ рдиреЗрдЯрд╡рд░реНрдХ. рдЕрд╢рд╛ рдкреНрд░рдХрд╛рд░реЗ, рдНрдкреНрд▓рд┐рдХреЗрд╢рдирд╕рд╣ рдХрдВрдЯреЗрдирд░рд▓рд╛ рд╣реЛрд╕реНрдЯ рд╕рд┐рд╕реНрдЯрдорд╡рд░ рдкреЛрд░реНрдЯ рдлреЙрд░рд╡рд░реНрдб рдХрд░рдгреНрдпрд╛рдЪреА рджреЗрдЦреАрд▓ рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╛рд╣реА; рдпрд╛рдореБрд│реЗ рдНрдкреНрд▓рд┐рдХреЗрд╢рдирд▓рд╛ рдмрд╛рд╣реНрдп рдзреЛрдХреНрдпрд╛рдВрдкрд╛рд╕реВрди рдЬрд╛рд╕реНрддреАрдд рдЬрд╛рд╕реНрдд рд╡реЗрдЧрд│реЗ рдХреЗрд▓реЗ рдЬрд╛рдК рд╢рдХрддреЗ.

рд░рд┐рд╡реНрд╣рд░реНрд╕ рдкреНрд░реЙрдХреНрд╕реА рджреБрд╕рд░реНтАНрдпрд╛ рд╣реЛрд╕реНрдЯрд╡рд░ рд░рд╛рд╣рдд рдЕрд╕рд▓реНрдпрд╛рд╕, рддреБрдореНрд╣рд╛рд▓рд╛ рдбреЙрдХрд░ рдиреЗрдЯрд╡рд░реНрдХ рд╕реЛрдбреВрди рджреНрдпрд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓ рдЖрдгрд┐ рдкреЛрд░реНрдЯ рдлреЙрд░рд╡рд░реНрдб рдХрд░реВрди рд╣реЛрд╕реНрдЯ рдиреЗрдЯрд╡рд░реНрдХрджреНрд╡рд╛рд░реЗ рд░рд┐рд╡реНрд╣рд░реНрд╕ рдкреНрд░реЙрдХреНрд╕реАрд╢реА рдНрдкреНрд▓рд┐рдХреЗрд╢рди рдХрдиреЗрдХреНрдЯ рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧреЗрд▓. рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдкреЕрд░рд╛рдореАрдЯрд░ --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 рдкреНрд░рдгрд╛рд▓реАрд╡рд░) рд╡рд╛рдЯрдк рдХрд░рдгреЗ рдЖрдгрд┐ рдирдВрддрд░ рддреЗ рд╕рд░реНрд╡реНрд╣рд░рд╡рд░ рд╣рд╕реНрддрд╛рдВрддрд░рд┐рдд рдХрд░рдгреЗ.

рдкреНрд░рддрд┐рдорд╛ рд╣рд╕реНрддрд╛рдВрддрд░рд┐рдд рдХрд░рдд рдЖрд╣реЗ

рджреБрд░реНрджреИрд╡рд╛рдиреЗ, рд▓реЛрдХрд▓рд╣реЛрд╕реНрдЯрд╡рд░реВрди рд▓реЛрдХрд▓рд╣реЛрд╕реНрдЯрдордзреНрдпреЗ рдкреНрд░рддрд┐рдорд╛ рд╣рд╕реНрддрд╛рдВрддрд░рд┐рдд рдХрд░рдгреНрдпрд╛рдд рдХрд╛рд╣реА рдЕрд░реНрде рдирд╛рд╣реА, рдореНрд╣рдгреВрди рдЬрд░ рддреБрдордЪреНрдпрд╛рдХрдбреЗ рдбреЙрдХрд░рд╕рд╣ рджреЛрди рд╣реЛрд╕реНрдЯ рдЕрд╕рддреАрд▓ рддрд░рдЪ рд╣рд╛ рд╡рд┐рднрд╛рдЧ рдПрдХреНрд╕рдкреНрд▓реЛрд░ рдХреЗрд▓рд╛ рдЬрд╛рдК рд╢рдХрддреЛ. рдХрд┐рдорд╛рди рддреЗ рдЕрд╕реЗ рдХрд╛рд╣реАрддрд░реА рджрд┐рд╕рддреЗ:

$ 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

рджреБрд╕рд░реА рдкрджреНрдзрдд (рддреНрдпрд╛рдЪреНрдпрд╛ рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреАрд╕рд╛рдареА рддреАрди рдкрд░реНрдпрд╛рдпрд╛рдВрд╕рд╣) рд▓реЗрдЦрд╛рдд рдЪрд╛рдВрдЧрд▓реЗ рд╡рд░реНрдгрди рдХреЗрд▓реЗ рдЖрд╣реЗ рдбреЙрдХрд░-рдХрдВрдкреЛрдЬрд╕рд╣ рд░рд┐рдореЛрдЯ рдбреЙрдХрд░ рд╣реЛрд╕реНрдЯрд╡рд░ рдХрд╕реЗ рддреИрдирд╛рдд рдХрд░рд╛рд╡реЗ.

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 рдереЛрдбреЗ рд╕реНрдкрд╖реНрдЯреАрдХрд░рдг рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ:

рддреЛ рдирдВрдмрд░ рдХрд╛ рджреЗрддреЛ рдЖрдгрд┐ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдЖрдЙрдЯрдкреБрдЯ рдХрд╛ рдХрд░рдд рдирд╛рд╣реА?

рддрд░реАрд╣реА, рдХреЙрд▓рд┐рдВрдЧ рдлрдВрдХреНрд╢рдирдордзреНрдпреЗ рдЖрдореНрд╣реА рддреНрдпрд╛рдЪреНрдпрд╛ рдХрд╛рдорд╛рдЪрд╛ рдкрд░рд┐рдгрд╛рдо рддрдкрд╛рд╕рддреЛ рдЖрдгрд┐ рдмреЕрд╢ рд╡рд╛рдкрд░реВрди рдПрдХреНрдЭрд┐рдЯ рдХреЛрдб рддрдкрд╛рд╕рдгреЗ рд╕реНрдЯреНрд░рд┐рдВрдЧ рддрдкрд╛рд╕рдгреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рдЦреВрдк рд╕реЛрдкреЗ рдЖрд╣реЗ. рдпрд╛рд╡реНрдпрддрд┐рд░рд┐рдХреНрдд, рддреНрдпрд╛рддреВрди рд╕реНрдЯреНрд░рд┐рдВрдЧ рдорд┐рд│рд╡рдгреЗ рдЦреВрдк рд╕реЛрдкреЗ рдЖрд╣реЗ:
get-active-slot service && echo BLUE || echo GREEN.

рд╕рд░реНрд╡ рд░рд╛рдЬреНрдпрд╛рдВрдордзреНрдпреЗ рдлрд░рдХ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреАрди рдЕрдЯреА рдЦрд░реЛрдЦрд░рдЪ рдкреБрд░реЗрд╢рд╛ рдЖрд╣реЗрдд рдХрд╛?

рдХрд┐рдорд╛рди рд╡реЗрддрдирд╛рд╡рд░ рдирд┐рд│рд╛-рд╣рд┐рд░рд╡рд╛ рддреИрдирд╛рдд

рдЬрд░реА рджреЛрди рдкреБрд░реЗрд╕реЗ рдЕрд╕рддреАрд▓, рд╢реЗрд╡рдЯрдЪрд╛ рдпреЗрдереЗ рдлрдХреНрдд рдкреВрд░реНрдгрддреЗрд╕рд╛рдареА рдЖрд╣реЗ, рд▓рд┐рд╣реВ рдирдпреЗ рдореНрд╣рдгреВрди else.

рдХреЗрд╡рд│ nginx рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди рдкрд░рдд рдХрд░рдгрд╛рд░реЗ рдлрдВрдХреНрд╢рди рдЕрдкрд░рд┐рднрд╛рд╖рд┐рдд рд░рд╛рд╣рддреЗ: get-nginx-config service_name deployment_slot. рдЖрд░реЛрдЧреНрдп рддрдкрд╛рд╕рдгреАрд╢реА рд╕рд╛рдзрд░реНрдореНрдп рд╕рд╛рдзреВрди, рдпреЗрдереЗ рддреБрдореНрд╣реА рдХреЛрдгрддреНрдпрд╛рд╣реА рд╕реЗрд╡реЗрд╕рд╛рдареА рдХреЛрдгрддреЗрд╣реА рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди рд╕реЗрдЯ рдХрд░реВ рд╢рдХрддрд╛. рдордиреЛрд░рдВрдЬрдХ рдЧреЛрд╖реНрдЯреАрдВрдкреИрдХреА - рдлрдХреНрдд cat <<- EOF, рдЬреЗ рддреБрдореНрд╣рд╛рд▓рд╛ рд╕реБрд░реБрд╡рд╛рддреАрд▓рд╛ рд╕рд░реНрд╡ рдЯреЕрдм рдХрд╛рдврдгреНрдпрд╛рдЪреА рдкрд░рд╡рд╛рдирдЧреА рджреЗрддреЗ. рдЦрд░реЗ рдЖрд╣реЗ, рдЪрд╛рдВрдЧрд▓реНрдпрд╛ рд╕реНрд╡рд░реВрдкрдирд╛рдЪреА рдХрд┐рдВрдордд рд╕реНрдкреЗрд╕рд╕рд╣ рдорд┐рд╢реНрд░рд┐рдд рдЯреЕрдм рдЖрд╣реЗ, рдЬреА рдЖрдЬ рдЦреВрдк рд╡рд╛рдИрдЯ рдлреЙрд░реНрдо рдорд╛рдирд▓реА рдЬрд╛рддреЗ. рдкрдг рдмреЕрд╢ рдЯреЕрдмрд▓рд╛ рд╕рдХреНрддреА рдХрд░рддреЗ, рдЖрдгрд┐ nginx рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рдирдордзреНрдпреЗ рд╕рд╛рдорд╛рдиреНрдп рд╕реНрд╡рд░реВрдкрди рдХрд░рдгреЗ рджреЗрдЦреАрд▓ рдЪрд╛рдВрдЧрд▓реЗ рд╣реЛрдИрд▓. рдереЛрдбрдХреНрдпрд╛рдд, рдпреЗрдереЗ рд╕реНрдкреЗрд╕рд╕рд╣ рдЯреЕрдмрдЪреЗ рдорд┐рд╢реНрд░рдг рдХрд░рдгреЗ рдЦрд░реЛрдЦрд░рдЪ рд╕рд░реНрд╡рд╛рдд рд╡рд╛рдИрдЯ рд╕рдорд╛рдзрд╛рдирд╛рд╕рд╛рд░рдЦреЗ рджрд┐рд╕рддреЗ. рддрдерд╛рдкрд┐, рддреБрдореНрд╣рд╛рд▓рд╛ рд╣реЗ рдЦрд╛рд▓реАрд▓ рд╕реНрдирд┐рдкреЗрдЯрдордзреНрдпреЗ рджрд┐рд╕рдгрд╛рд░ рдирд╛рд╣реА, рдХрд╛рд░рдг Habr рд╕рд░реНрд╡ рдЯреЕрдм 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 рдЖрдгрд┐ рдЖрд╡рд╢реНрдпрдХ рдпреБрдХреНрддрд┐рд╡рд╛рджрд╛рдВрд╕рд╣ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рд┐рдд рдХрд░рд╛.

рдмрд╛рдзрдХ

  • рдПрдХрд╛ рдРрд╡рдЬреА рджреЛрди рдХреНрд░рд┐рдпрд╛
  • рддреБрдореНрд╣реА рдХреЙрдкреА рдХрд░рд╛рд▓ рдЕрд╢реА рдЬрд╛рдЧрд╛ рдирд╕реЗрд▓, рдХрд┐рдВрд╡рд╛ рддрд┐рдереЗ рдкреНрд░рд╡реЗрд╢ рдирд╕реЗрд▓, рдХрд┐рдВрд╡рд╛ рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрдирд╛рдЪреНрдпрд╛ рд╡реЗрд│реА рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЕрдВрдорд▓рд╛рдд рдЖрдгрд▓реА рдЬрд╛рдИрд▓.
  • рд╕реНрд╡рдд: рдирдВрддрд░ рд╕рд╛рдл рдХрд░рдгреНрдпрд╛рдЪрд╛ рд╕рд▓реНрд▓рд╛ рджрд┐рд▓рд╛ рдЬрд╛рддреЛ (рд╕реНрдХреНрд░рд┐рдкреНрдЯ рд╣рдЯрд╡рд╛).
  • рдЖрдзреАрдЪ рддреАрди рдХреНрд░рд┐рдпрд╛.

рдЙрдкрд╛рдп реи:

  • рд╕реНрдХреНрд░рд┐рдкреНрдЯрдордзреНрдпреЗ рдлрдХреНрдд рдлрдВрдХреНрд╢рди рд╡реНрдпрд╛рдЦреНрдпрд╛ рдареЗрд╡рд╛ рдЖрдгрд┐ рдХрд╛рд╣реАрд╣реА рдЪрд╛рд▓рд╡реВ рдирдХрд╛
  • рдЪреНрдпрд╛ рдорджрддреАрдиреЗ sed рд╢реЗрд╡рдЯреА рдлрдВрдХреНрд╢рди рдХреЙрд▓ рдЬреЛрдбрд╛
  • рд╣реЗ рд╕рд░реНрд╡ рдереЗрдЯ рдкрд╛рдИрдкрджреНрд╡рд╛рд░реЗ shh рд╡рд░ рдкрд╛рдард╡рд╛ (|)

рд╕рд╛рдзрдХ:

  • рдЦрд░рдЪ рд╕реНрдЯреЗрдЯрд▓реЗрд╕
  • рдмреЙрдпрд▓рд░рдкреНрд▓реЗрдЯ рдирд╛рд╣реА
  • рдорд╕реНрдд рд╡рд╛рдЯрддрдВрдп

рдлрдХреНрдд 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'

рддрдерд╛рдкрд┐, рдЖрдореНрд╣реА рдЦрд╛рддреНрд░реА рдмрд╛рд│рдЧреВ рд╢рдХрдд рдирд╛рд╣реА рдХреА рд░рд┐рдореЛрдЯ рд╣реЛрд╕реНрдЯрдХрдбреЗ рдкреБрд░реЗрд╕рд╛ рдмреЕрд╢ рдЖрд╣реЗ, рдореНрд╣рдгреВрди рдЖрдореНрд╣реА рд╕реБрд░реБрд╡рд╛рддреАрд▓рд╛ рдПрдХ рд▓рд╣рд╛рди рдЪреЗрдХ рдЬреЛрдбреВ (рд╣реЗ рддреНрдпрд╛рдРрд╡рдЬреА рдЖрд╣реЗ рд╢реЗрд▓рдмреЕрдВрдЧ):

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