рдиреНрдпреВрдирддрдо рд╡реЗрддрди рдкрд░ рдмреНрд▓реВ-рдЧреНрд░реАрди рддреИрдирд╛рддреА

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

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

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

рдиреНрдпреВрдирддрдо рд╡реЗрддрди рдкрд░ рдмреНрд▓реВ-рдЧреНрд░реАрди рддреИрдирд╛рддреА

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

рдЪрд▓рд┐рдП рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ

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

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

рдЖрдЗрдП рдПрдХ рдкреНрд░рд╛рдпреЛрдЧрд┐рдХ рд╕реЗрд╡рд╛ рдмрдирд╛рдПрдВ рдФрд░ рдЗрд╕реЗ рдПрдХ рдХрдВрдЯреЗрдирд░ рдореЗрдВ рд░рдЦреЗрдВред

рджрд┐рд▓рдЪрд╕реНрдк рддрдХрдиреАрдХреЗрдВ

  • cat << EOF > file-name (рдпрд╣рд╛рдБ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ + I/O рдкреБрдирд░реНрдирд┐рд░реНрджреЗрд╢рди) рдПрдХ рдХрдорд╛рдВрдб рдХреЗ рд╕рд╛рде рдорд▓реНрдЯреА-рд▓рд╛рдЗрди рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдиреЗ рдХрд╛ рдПрдХ рддрд░реАрдХрд╛ рд╣реИред рд╕рдм рдХреБрдЫ рдмреИрд╢ рд╕реЗ рдкрдврд╝рддрд╛ рд╣реИ /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

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

рдЫрд╡рд┐рдпрд╛рдБ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдирд╛

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

$ 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, рдлрд┐рд░ рд╣реЗрд░реЗрдбреЛрдХ рдХреЗ рдЕрдВрджрд░ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдХреЛ рдкреНрд░рдХреНрд╖реЗрдкрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ (рдЪрд░ рдХрд╛ рд╡рд┐рд╕реНрддрд╛рд░ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ ($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.

рд╣рдордиреЗ рдПрдХ рдкрд░рд┐рдирд┐рдпреЛрдЬрди рд╕реНрдХреНрд░рд┐рдкреНрдЯ рд▓рд┐рдЦреА рд╣реИ рдЬреЛ рдПрдХ рдкреВрд░реНрд╡-рдирд┐рд░реНрдорд┐рдд рдЫрд╡рд┐ рдХреЛ рд▓рдХреНрд╖реНрдп рд╕рд░реНрд╡рд░ рдкрд░ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рддреА рд╣реИ рдФрд░ рд╕реЗрд╡рд╛ рдХрдВрдЯреЗрдирд░ рдХреЛ рдирд┐рд░реНрдмрд╛рдз рд░реВрдк рд╕реЗ рдмрджрд▓ рджреЗрддреА рд╣реИ, рд▓реЗрдХрд┐рди рд╣рдо рдЗрд╕реЗ рд░рд┐рдореЛрдЯ рдорд╢реАрди рдкрд░ рдХреИрд╕реЗ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ? рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдореЗрдВ рддрд░реНрдХ рд╣реИрдВ, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рд╕рд╛рд░реНрд╡рднреМрдорд┐рдХ рд╣реИ рдФрд░ рдПрдХ рд░рд┐рд╡рд░реНрд╕ рдкреНрд░реЙрдХреНрд╕реА рдХреЗ рддрд╣рдд рдПрдХ рд╕рд╛рде рдХрдИ рд╕реЗрд╡рд╛рдУрдВ рдХреЛ рддреИрдирд╛рдд рдХрд░ рд╕рдХрддрд╛ рд╣реИ (рдЖрдк рдпрд╣ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП nginx рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рдХреМрди рд╕рд╛ рдпреВрдЖрд░рдПрд▓ рдХреМрди рд╕реА рд╕реЗрд╡рд╛ рд╣реЛрдЧреА)ред рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЛ рд╕рд░реНрд╡рд░ рдкрд░ рд╕рдВрдЧреНрд░рд╣реАрдд рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ рд╣рдо рдЗрд╕реЗ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдЕрдкрдбреЗрдЯ рдирд╣реАрдВ рдХрд░ рдкрд╛рдПрдВрдЧреЗ (рдмрдЧ рдлрд┐рдХреНрд╕ рдФрд░ рдирдИ рд╕реЗрд╡рд╛рдУрдВ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рдЙрджреНрджреЗрд╢реНрдп рд╕реЗ), рдФрд░ рд╕рд╛рдорд╛рдиреНрдп рддреМрд░ рдкрд░, рд╕реНрдерд┐рддрд┐ = рдмреБрд░рд╛рдИред

рд╕рдорд╛рдзрд╛рди 1: рдЕрднреА рднреА рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЛ рд╕рд░реНрд╡рд░ рдкрд░ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░реЗрдВ, рд▓реЗрдХрд┐рди рдЗрд╕реЗ рд╣рд░ рдмрд╛рд░ рдХреЙрдкреА рдХрд░реЗрдВ scp. рдлрд┐рд░ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░реЗрдВ ssh рдФрд░ рдЖрд╡рд╢реНрдпрдХ рддрд░реНрдХреЛрдВ рдХреЗ рд╕рд╛рде рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░реЗрдВред

рд╡рд┐рдкрдХреНрд╖:

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

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

  • рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдореЗрдВ рдХреЗрд╡рд▓ рдлрд╝рдВрдХреНрд╢рди рдкрд░рд┐рднрд╛рд╖рд╛рдПрдБ рд░рдЦреЗрдВ рдФрд░ рдХреБрдЫ рднреА рди рдЪрд▓рд╛рдПрдБ
  • рд╕рд╛рде sed рдЕрдВрдд рдореЗрдВ рдПрдХ рдлрд╝рдВрдХреНрд╢рди рдХреЙрд▓ рдЬреЛрдбрд╝реЗрдВ
  • рдпрд╣ рд╕рдм рдкрд╛рдЗрдк рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рд╕реАрдзреЗ рд╢рд╛рд╣ рдХреЛ рднреЗрдЬреЗрдВ (|)

рдкреЗрд╢реЗрд╡рд░реЛрдВ:

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

рдЖрдЗрдП рдЗрд╕реЗ рдЕрдиреНрд╕рд┐рдмрд▓ рдХреЗ рдмрд┐рдирд╛ рд╣реА рдХрд░реЗрдВред рд╣рд╛рдБ, рд╣рд░ рдЪреАрдЬрд╝ рдХрд╛ рдЖрд╡рд┐рд╖реНрдХрд╛рд░ рдкрд╣рд▓реЗ рд╣реА рд╣реЛ рдЪреБрдХрд╛ рд╣реИред рд╣рд╛рдБ, рдПрдХ рд╕рд╛рдЗрдХрд┐рд▓. рджреЗрдЦрд┐рдП рдпрд╣ рдмрд╛рдЗрдХ рдХрд┐рддрдиреА рд╕рд░рд▓, рд╕реБрдВрджрд░ рдФрд░ рдиреНрдпреВрдирддрд░ рд╣реИ:

$ 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