рдиреНрдпреВрдирддрдо рдкрд╛рд░рд┐рд╢реНрд░рдорд┐рдХрдорд╛ рдиреАрд▓реЛ-рд╣рд░рд┐рдпреЛ рддреИрдирд╛рддреА

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

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

рдЕрд╕реНрд╡реАрдХрд░рдг: рдЕрдзрд┐рдХрд╛рдВрд╢ рд▓реЗрдЦ рдкреНрд░рдпреЛрдЧрд╛рддреНрдордХ рдврд╛рдБрдЪрд╛рдорд╛ рдкреНрд░рд╕реНрддреБрдд рдЧрд░рд┐рдПрдХреЛ рдЫ - рдХрдиреНрд╕реЛрд▓ рд╕рддреНрд░рдХреЛ рд░реЗрдХрд░реНрдбрд┐рдЩрдХреЛ рд░реВрдкрдорд╛ред рдЖрд╢рд╛ рдЫ рдХрд┐ рдпреЛ рдмреБрдЭреНрди рдзреЗрд░реИ рдЧрд╛рд╣реНрд░реЛ рд╣реБрдиреЗрдЫреИрди рд░ рдХреЛрдб рдЖрдлреИрд▓рд╛рдИ рдкрд░реНрдпрд╛рдкреНрдд рдХрд╛рдЧрдЬрд╛рдд рд╣реБрдиреЗрдЫред рд╡рд╛рддрд╛рд╡рд░рдгрдХреЛ рд▓рд╛рдЧрд┐, рдХрд▓реНрдкрдирд╛ рдЧрд░реНрдиреБрд╣реЛрд╕реН рдХрд┐ рдпреА рдХреЗрд╡рд▓ рдХреЛрдб рд╕реНрдирд┐рдкреЗрдЯрд╣рд░реВ рд╣реЛрдЗрдирдиреН, рддрд░ "рдлрд▓рд╛рдо" рдЯреЗрд▓рд┐рдЯрд╛рдЗрдкрдмрд╛рдЯ рдХрд╛рдЧрдЬред

рдиреНрдпреВрдирддрдо рдкрд╛рд░рд┐рд╢реНрд░рдорд┐рдХрдорд╛ рдиреАрд▓реЛ-рд╣рд░рд┐рдпреЛ рддреИрдирд╛рддреА

рдХреЛрдб рдкрдвреЗрд░ рдорд╛рддреНрд░ рдЧреБрдЧрд▓рд▓рд╛рдИ рдЧрд╛рд╣реНрд░реЛ рд╣реБрдиреЗ рд░реЛрдЪрдХ рдкреНрд░рд╡рд┐рдзрд┐рд╣рд░реВ рдкреНрд░рддреНрдпреЗрдХ рдЦрдгреНрдбрдХреЛ рд╕реБрд░реБрдорд╛ рд╡рд░реНрдгрди рдЧрд░рд┐рдПрдХреЛ рдЫред рдпрджрд┐ рдХреЗрд╣рд┐ рдЕрд╕реНрдкрд╖реНрдЯ рдЫ рднрдиреЗ, рдпрд╕рд▓рд╛рдИ рдЧреБрдЧрд▓ рдЧрд░реНрдиреБрд╣реЛрд╕реН рд░ рдпрд╕рд▓рд╛рдИ рдЬрд╛рдБрдЪ рдЧрд░реНрдиреБрд╣реЛрд╕реНред рд╡реНрдпрд╛рдЦреНрдпрд╛ рдЧрд░реНрдиреБрд╣реЛрд╕реН (рднрд╛рдЧреНрдпрд╡рд╢, рдЯреЗрд▓рд┐рдЧреНрд░рд╛рдо рдЕрдирдмреНрд▓рдХрдХреЛ рдХрд╛рд░рдгрд▓реЗ, рдпрд╕рд▓реЗ рдлреЗрд░рд┐ рдХрд╛рдо рдЧрд░реНрджрдЫ)ред рдпрджрд┐ рддрдкрд╛рдИрдВ рдХреЗрд╣рд┐ рдЧреБрдЧрд▓ рдЧрд░реНрди рд╕рдХреНрдиреБрд╣реБрдиреНрди рднрдиреЗ, рдЯрд┐рдкреНрдкрдгреАрд╣рд░реВрдорд╛ рд╕реЛрдзреНрдиреБрд╣реЛрд╕реНред рдо рд╕рдореНрдмрдиреНрдзрд┐рдд рдЦрдгреНрдб "рд░реЛрдЪрдХ рдкреНрд░рд╡рд┐рдзрд┐рд╣рд░реВ" рдорд╛ рдердкреНрди рдЦреБрд╕реА рд╣реБрдиреЗрдЫреБред

рд╕реБрд░реБ рдЧрд░реМрдВред

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

рд╕реЗрд╡рд╛

рдПрдХ рдкреНрд░рдпреЛрдЧрд╛рддреНрдордХ рд╕реЗрд╡рд╛ рдмрдирд╛рдЙрдиреБрд╣реЛрд╕реН рд░ рдпрд╕рд▓рд╛рдИ рдХрдиреНрдЯреЗрдирд░рдорд╛ рд░рд╛рдЦреНрдиреБрд╣реЛрд╕реНред

рд░реЛрдЪрдХ рдкреНрд░рд╡рд┐рдзрд┐рд╣рд░реВ

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

рдкрд╛рдЗрдердирдХрд╛ рд▓рд╛рдЧрд┐ рд╣рд╛рдЗрд▓рд╛рдЗрдЯрд┐рдЩ рд╕рдХреНрд╖рдо рдЧрд░реНрди рдореИрд▓реЗ рд╡рд┐рд╢реЗрд╖ рд░реВрдкрдорд╛ рд╕реНрдирд┐рдкреЗрдЯ рддреЛрдбреЗрдХреЛ рдЫреБред рдЕрдиреНрддрдорд╛ рдпрд╕реНрддреИ рдЕрд░реНрдХреЛ рдЯреБрдХреНрд░рд╛ рд╣реБрдиреЗрдЫред рд╡рд┐рдЪрд╛рд░ рдЧрд░реНрдиреБрд╣реЛрд╕реН рдХрд┐ рдпреА рдард╛рдЙрдБрд╣рд░реВрдорд╛ рдХрд╛рдЧрдЬ рд╣рд╛рдЗрд▓рд╛рдЗрдЯрд┐рдЩ рд╡рд┐рднрд╛рдЧрдорд╛ рдкрдард╛рдЙрдирдХреЛ рд▓рд╛рдЧрд┐ рдХрд╛рдЯрд┐рдПрдХреЛ рдерд┐рдпреЛ (рдЬрд╣рд╛рдБ рдХреЛрдб рд╣рд╛рдЗрд▓рд╛рдЗрдЯрд░рд╣рд░реВрд╕рдБрдЧ рд╣рд╛рддрд▓реЗ рд░рдВрдЧрд┐рдПрдХреЛ рдерд┐рдпреЛ), рд░ рддреНрдпрд╕рдкрдЫрд┐ рдпреА рдЯреБрдХреНрд░рд╛рд╣рд░реВ рдЯрд╛рдБрд╕рд┐рдПрдХрд╛ рдерд┐рдПред

$ 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, рдЬрд╕рд▓реЗ рддрдкрд╛рдИрдВрд▓рд╛рдИ рд╕реБрд░реБрдорд╛ рд╕рдмреИ рдЯреНрдпрд╛рдмрд╣рд░реВ рд╣рдЯрд╛рдЙрди рдЕрдиреБрдорддрд┐ рджрд┐рдиреНрдЫред рд╕рд╛рдБрдЪреЛ, рд░рд╛рдореНрд░реЛ рдврд╛рдБрдЪрд╛рдХреЛ рдореВрд▓реНрдп рдЦрд╛рд▓реА рдард╛рдЙрдБрд╣рд░реВрд╕рдБрдЧ рдорд┐рд╢реНрд░рд┐рдд рдЯреНрдпрд╛рдмрд╣рд░реВ рд╣реЛ, рдЬреБрди рдЖрдЬ рдзреЗрд░реИ рдЦрд░рд╛рдм рд░реВрдк рдорд╛рдирд┐рдиреНрдЫред рддрд░ bash рдмрд▓ рдЯреНрдпрд╛рдмрд╣рд░реВ, рд░ рдпреЛ рдкрдирд┐ рд░рд╛рдореНрд░реЛ рд╣реБрдиреЗрдЫ 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 рд╡рд╛ рдХрд░реНрд▓ рдорд╛рд░реНрдлрдд рдбрд╛рдЙрдирд▓реЛрдб рдЧрд░реНрдирдХрд╛ рд▓рд╛рдЧрд┐ред

рд░рд┐рдореЛрдЯ рд╕рд░реНрднрд░рдорд╛ рдкреНрдпрд╛рд░рд╛рдорд┐рдЯрд░рд╛рдЗрдЬреНрдб рд╕реНрдХреНрд░рд┐рдкреНрдЯрд╣рд░реВ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЧрд░реНрджреИ

рдпреЛ рд▓рдХреНрд╖реНрдп рд╕рд░реНрднрд░рдорд╛ рджрд╕реНрддрдХ рдЧрд░реНрдиреЗ рд╕рдордп рд╣реЛред рдпреЛ рд╕рдордп 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 рд░ рдЖрд╡рд╢реНрдпрдХ рддрд░реНрдХ рд╕рдВрдЧ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдЧрд░реНрдиреБрд╣реЛрд╕реНред

Cons:

  • рдПрдХрдХреЛ рд╕рдЯреНрдЯрд╛ рджреБрдИ рдХрд╛рд░реНрдпрд╣рд░реВ
  • рддреНрдпрд╣рд╛рдБ рддрдкрд╛рдИрдВрд▓реЗ рдкреНрд░рддрд┐рд▓рд┐рдкрд┐ рдЧрд░реНрдиреЗ рдард╛рдЙрдБ рдирд╣реБрди рд╕рдХреНрдЫ, рд╡рд╛ рддреНрдпрд╣рд╛рдБ рдкрд╣реБрдБрдЪ рдирд╣реБрди рд╕рдХреНрдЫ, рд╡рд╛ рд▓рд┐рдкрд┐ рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрдирдХреЛ рд╕рдордпрдорд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╣реБрди рд╕рдХреНрдЫред
  • рдпреЛ рдЖрдлреИрдВ рдкрдЫрд┐ рд╕рдлрд╛ рдЧрд░реНрди рд╕рд▓реНрд▓рд╛рд╣ рджрд┐рдЗрдиреНрдЫ (рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдореЗрдЯрд╛рдЙрдиреБрд╣реЛрд╕реН)ред
  • рдкрд╣рд┐рд▓реЗ рдиреИ рддреАрди рдХрд╛рд░реНрдпрд╣рд░реВред

рд╕рдорд╛рдзрд╛рди реи:

  • рд╕реНрдХреНрд░рд┐рдкреНрдЯрдорд╛ рдХреЗрд╡рд▓ рдкреНрд░рдХрд╛рд░реНрдп рдкрд░рд┐рднрд╛рд╖рд╛рд╣рд░реВ рд░рд╛рдЦреНрдиреБрд╣реЛрд╕реН рд░ рдХреЗрд╣рд┐ рдкрдирд┐ рдЪрд▓рд╛рдЙрдиреБрд╣реЛрд╕реН
  • рд╕рд╣рдпреЛрдЧрдХреЛ рд╕рд╛рде sed рдЕрдиреНрддреНрдпрдорд╛ рдлрдВрдХреНрд╢рди рдХрд▓ рдердкреНрдиреБрд╣реЛрд╕реН
  • рдпреЛ рд╕рдмреИ рд╕рд┐рдзреИ рдкрд╛рдЗрдк рдорд╛рд░реНрдлрдд shh рдорд╛ рдкрдард╛рдЙрдиреБрд╣реЛрд╕реН (|)

рдкреНрд░реЛ:

  • рд╕рд╛рдБрдЪреНрдЪреИ рд░рд╛рдЬреНрдпрд╡рд┐рд╣реАрди
  • рдХреБрдиреИ рдмреЙрдпрд▓рд░рдкреНрд▓реЗрдЯ рдирд┐рдХрд╛рдпрд╣рд░реВ рдЫреИрдирдиреН
  • рдЪрд┐рд╕реЛ рдорд╣рд╕реБрд╕ рдЧрд░реНрджреИ

рдЖрдЙрдиреБрд╣реЛрд╕реН рдЬрд╡рд╛рдл рдмрд┐рдирд╛ рдпреЛ рдЧрд░реМрдВред рд╣реЛ, рд╕рдмреИ рдХреБрд░рд╛ рдкрд╣рд┐рд▓реЗ рдиреИ рдЖрд╡рд┐рд╖реНрдХрд╛рд░ рдЧрд░рд┐рдПрдХреЛ рдЫред рд╣реЛ, рд╕рд╛рдЗрдХрд▓ред рд╣реЗрд░реНрдиреБрд╣реЛрд╕реН рдмрд╛рдЗрдХ рдХрддрд┐ рд╕рд░рд▓, рд╕реБрд░реБрдЪрд┐рдкреВрд░реНрдг рд░ рдиреНрдпреВрдирддрдо рдЫ:

$ 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/ рдмреНрд░рд╛рдЙрдЬрд░рдорд╛, рдбрд┐рдкреНрд▓реЛрдпрдореЗрдиреНрдЯрд▓рд╛рдИ рдлреЗрд░рд┐ рдЪрд▓рд╛рдЙрдиреБрд╣реЛрд╕реН рд░ рд▓реЗрдЖрдЙрдЯрдХреЛ рд╕рдордпрдорд╛ рд╕реАрдбреА рдЕрдиреБрд╕рд╛рд░ рдкреГрд╖реНрда рдЕрджреНрдпрд╛рд╡рдзрд┐рдХ рдЧрд░реЗрд░ рдпреЛ рдирд┐рд░реНрдмрд╛рдз рд░реВрдкрдорд╛ рдЪрд▓реНрдЫ рднрдиреЗрд░ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдЧрд░реНрдиреБрд╣реЛрд╕реНред

рдХрд╛рдо рдкрдЫрд┐ рд╕рдлрд╛ рдЧрд░реНрди рдирдмрд┐рд░реНрд╕рдиреБрд╣реЛрд╕реН: 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