Blau-Grüner Einsatz zum Mindestlohn

In diesem Artikel verwenden wir bash, ssh, Docker и Wir organisieren ein nahtloses Layout der Webanwendung. Blaugrüner Einsatz ist eine Technik, mit der Sie eine Anwendung sofort aktualisieren können, ohne eine einzige Anfrage abzulehnen. Dies ist eine der Bereitstellungsstrategien ohne Ausfallzeiten und eignet sich am besten für Anwendungen mit einer Instanz, aber der Möglichkeit, eine zweite, betriebsbereite Instanz in der Nähe zu laden.

Nehmen wir an, Sie haben eine Webanwendung, mit der viele Kunden aktiv arbeiten, und es gibt absolut keine Möglichkeit, dass sie für ein paar Sekunden stillsteht. Und Sie müssen wirklich ein Bibliotheksupdate, eine Fehlerbehebung oder eine neue coole Funktion einführen. Im Normalfall müssen Sie die Anwendung stoppen, ersetzen und erneut starten. Im Fall von Docker können Sie es zunächst ersetzen und dann neu starten, es wird jedoch immer noch eine Zeitspanne geben, in der Anfragen an die Anwendung nicht verarbeitet werden, da das erstmalige Laden der Anwendung normalerweise einige Zeit in Anspruch nimmt. Was ist, wenn es startet, sich aber als nicht funktionsfähig herausstellt? Das ist das Problem, lösen wir es mit minimalen Mitteln und so elegant wie möglich.

HAFTUNGSAUSSCHLUSS: Der Großteil des Artikels wird in einem experimentellen Format präsentiert – in Form einer Aufzeichnung einer Konsolensitzung. Hoffentlich ist das nicht zu schwer zu verstehen und der Code dokumentiert sich ausreichend. Stellen Sie sich für die Atmosphäre vor, dass es sich nicht nur um Codeschnipsel handelt, sondern um Papier aus einem „eisernen“ Fernschreiber.

Blau-Grüner Einsatz zum Mindestlohn

Zu Beginn jedes Abschnitts werden interessante Techniken beschrieben, die Google allein durch das Lesen des Codes nur schwer erkennen kann. Wenn noch etwas unklar ist, googeln Sie es und schauen Sie es sich an. erklärtHölle (Zum Glück funktioniert es wieder, da das Telegramm entsperrt wurde.) Wenn Sie nichts googeln können, fragen Sie in den Kommentaren nach. Gerne ergänze ich die entsprechende Rubrik „Interessante Techniken“.

Lass uns anfangen.

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

Service

Lassen Sie uns einen experimentellen Service erstellen und ihn in einen Container legen.

Interessante Techniken

  • cat << EOF > file-name (Hier Dokument + E/A-Umleitung) ist eine Möglichkeit, mit einem Befehl eine mehrzeilige Datei zu erstellen. Alles, wovon Bash liest /dev/stdin nach dieser Zeile und vor der Zeile EOF wird aufgezeichnet file-name.
  • wget -qO- URL (erklärtHölle) – ein über HTTP empfangenes Dokument ausgeben an /dev/stdout (analog curl URL).

Ausdrucken

Ich unterbreche das Snippet ausdrücklich, um die Hervorhebung für Python zu ermöglichen. Am Ende wird es noch ein Stück wie dieses geben. Bedenken Sie, dass an diesen Stellen das Papier zerschnitten wurde, um es an die Hervorhebungsabteilung zu schicken (wo der Code mit Textmarkern handkoloriert wurde), und dass diese Stücke dann wieder zusammengeklebt wurden.

$ 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

Reverse-Proxy

Damit sich unsere Anwendung unbemerkt ändern kann, muss sich vor ihr eine andere Entität befinden, die ihre Ersetzung verbirgt. Es könnte ein Webserver sein в Reverse-Proxy-Modus. Zwischen dem Client und der Anwendung wird ein Reverse-Proxy eingerichtet. Es nimmt Anfragen von Clients entgegen, leitet diese an die Anwendung weiter und leitet die Antworten der Anwendung an die Clients weiter.

Die Anwendung und der Reverse-Proxy können innerhalb von Docker verknüpft werden Docker-Netzwerk. Somit muss der Container mit der Anwendung nicht einmal einen Port auf dem Hostsystem weiterleiten, wodurch die Anwendung maximal von externen Bedrohungen isoliert werden kann.

Wenn sich der Reverse-Proxy auf einem anderen Host befindet, müssen Sie das Docker-Netzwerk verlassen und die Anwendung über das Host-Netzwerk mit dem Reverse-Proxy verbinden und den Port weiterleiten приложения Parameter --publish, wie beim ersten Start und wie beim Reverse-Proxy.

Wir werden den Reverse-Proxy auf Port 80 ausführen, da dies genau die Entität ist, die auf das externe Netzwerk lauschen soll. Wenn Port 80 auf Ihrem Testhost belegt ist, ändern Sie den Parameter --publish 80:80 auf --publish ANY_FREE_PORT:80.

Interessante Techniken

Ausdrucken

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

Nahtlose Bereitstellung

Lassen Sie uns eine neue Version der Anwendung einführen (mit einer zweifachen Steigerung der Startleistung) und versuchen, sie nahtlos bereitzustellen.

Interessante Techniken

  • echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt' — Text schreiben my text einordnen /my-file.txt im Inneren des Behälters my-container.
  • cat > /my-file.txt — Schreiben Sie den Inhalt der Standardeingabe in eine Datei /dev/stdin.

Ausdrucken

$ 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

In dieser Phase wird das Image direkt auf dem Server erstellt, was erfordert, dass die Anwendungsquellen vorhanden sind, und außerdem den Server mit unnötiger Arbeit belastet. Der nächste Schritt besteht darin, die Image-Assembly einer separaten Maschine (z. B. einem CI-System) zuzuweisen und sie dann auf den Server zu übertragen.

Bilder übertragen

Leider macht es keinen Sinn, Bilder von localhost auf localhost zu übertragen, daher kann dieser Abschnitt nur erkundet werden, wenn Sie zwei Hosts mit Docker zur Hand haben. Zumindest sieht es ungefähr so ​​aus:

$ 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

Team docker save speichert die Bilddaten in einem .tar-Archiv, was bedeutet, dass sie etwa 1.5-mal mehr wiegen als in komprimierter Form. Lassen Sie uns also loslegen, um Zeit und Verkehr zu sparen:

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

Sie können den Downloadvorgang auch überwachen (hierfür ist jedoch ein Dienstprogramm eines Drittanbieters erforderlich):

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

Tipp: Wenn Sie eine Reihe von Parametern benötigen, um über SSH eine Verbindung zu einem Server herzustellen, verwenden Sie die Datei möglicherweise nicht ~/.ssh/config.

Übertragen des Bildes per docker image save/load - Dies ist die minimalistischste Methode, aber nicht die einzige. Da sind andere:

  1. Container Registry (Industriestandard).
  2. Stellen Sie von einem anderen Host aus eine Verbindung zum Docker-Daemon-Server her:
    1. Umgebungsvariable DOCKER_HOST.
    2. Befehlszeilenoption -H oder --host Werkzeug docker-compose.
    3. docker context

Die zweite Methode (mit drei Optionen für ihre Implementierung) wird im Artikel ausführlich beschrieben So stellen Sie es mit Docker-Compose auf Remote-Docker-Hosts bereit.

deploy.sh

Lassen Sie uns nun alles, was wir manuell gemacht haben, in einem Skript zusammenfassen. Beginnen wir mit der Funktion der obersten Ebene und schauen uns dann die anderen darin verwendeten Funktionen an.

Interessante Techniken

  • ${parameter?err_msg} - einer der Bash-Zaubersprüche (auch bekannt als Parametersubstitution). Wenn parameter nicht angegeben, Ausgabe err_msg und beenden Sie mit Code 1.
  • docker --log-driver journald — Standardmäßig ist der Docker-Protokollierungstreiber eine Textdatei ohne Rotation. Bei diesem Ansatz füllen die Protokolle schnell die gesamte Festplatte, sodass es für eine Produktionsumgebung erforderlich ist, den Treiber durch einen intelligenteren zu ersetzen.

Bereitstellungsskript

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
}

Verwendete Funktionen:

  • ensure-reverse-proxy — Stellt sicher, dass der Reverse-Proxy funktioniert (nützlich für die erste Bereitstellung)
  • get-active-slot service_name — Bestimmt, welcher Slot derzeit für einen bestimmten Dienst aktiv ist (BLUE oder GREEN)
  • get-service-status service_name deployment_slot – Bestimmt, ob der Dienst bereit ist, eingehende Anforderungen zu verarbeiten
  • set-active-slot service_name deployment_slot – Ändert die Nginx-Konfiguration im Reverse-Proxy-Container

Um:

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
}

Funktion get-active-slot bedarf einer kleinen Erklärung:

Warum wird eine Zahl zurückgegeben und keine Zeichenfolge ausgegeben?

Wie auch immer, in der aufrufenden Funktion überprüfen wir das Ergebnis ihrer Arbeit, und die Überprüfung des Exit-Codes mit Bash ist viel einfacher als die Überprüfung einer Zeichenfolge. Darüber hinaus ist es sehr einfach, daraus einen String zu erhalten:
get-active-slot service && echo BLUE || echo GREEN.

Reichen drei Bedingungen wirklich aus, um alle Staaten zu unterscheiden?

Blau-Grüner Einsatz zum Mindestlohn

Sogar zwei werden ausreichen, das letzte dient hier nur der Vollständigkeit, um es nicht zu schreiben else.

Nur die Funktion, die Nginx-Konfigurationen zurückgibt, bleibt undefiniert: get-nginx-config service_name deployment_slot. Analog zur Gesundheitsprüfung können Sie hier eine beliebige Konfiguration für jeden Dienst festlegen. Nur von den interessanten Dingen cat <<- EOF, wodurch Sie zu Beginn alle Tabs entfernen können. Der Preis für eine gute Formatierung ist zwar die Mischung von Tabulatoren und Leerzeichen, was heute als sehr schlechte Form gilt. Aber Bash erzwingt Tabulatoren, und es wäre auch schön, eine normale Formatierung in der Nginx-Konfiguration zu haben. Kurz gesagt, das Mischen von Tabulatoren und Leerzeichen scheint hier wirklich die beste von der schlechtesten Lösung zu sein. Allerdings werden Sie dies im Snippet unten nicht sehen, da Habr „es gut macht“, indem es alle Tabulatoren in 4 Leerzeichen ändert und EOF ungültig macht. Und hier ist es spürbar.

Um nicht zweimal aufzustehen, erzähle ich dir gleich davon cat << 'EOF', auf die wir später noch stoßen werden. Wenn Sie einfach schreiben cat << EOF, dann wird der String in Heredoc interpoliert (Variablen werden erweitert ($foo), Befehlsaufrufe ($(bar)) usw.), und wenn Sie das Ende des Dokuments in einfache Anführungszeichen setzen, ist die Interpolation deaktiviert und das Symbol $ wird so angezeigt, wie es ist. Was Sie benötigen, um ein Skript in ein anderes Skript einzufügen.

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
}

Dies ist das gesamte Skript. Und so Das Wesentliche mit diesem Skript zum Herunterladen über wget oder curl.

Parametrisierte Skripte auf einem Remote-Server ausführen

Es ist Zeit, auf dem Zielserver anzuklopfen. Dieses Mal localhost ganz passend:

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

Wir haben ein Bereitstellungsskript geschrieben, das ein vorgefertigtes Image auf den Zielserver herunterlädt und den Service-Container nahtlos ersetzt, aber wie können wir es auf einem Remote-Computer ausführen? Das Skript verfügt über Argumente, da es universell ist und mehrere Dienste gleichzeitig unter einem Reverse-Proxy bereitstellen kann (Sie können Nginx-Konfigurationen verwenden, um zu bestimmen, welche URL welcher Dienst sein soll). Das Skript kann nicht auf dem Server gespeichert werden, da wir es in diesem Fall nicht automatisch aktualisieren können (zur Fehlerbehebung und zum Hinzufügen neuer Dienste) und im Allgemeinen gilt: state = evil.

Lösung 1: Speichern Sie das Skript weiterhin auf dem Server, kopieren Sie es jedoch jedes Mal durch scp. Dann verbinden Sie sich über ssh und führen Sie das Skript mit den erforderlichen Argumenten aus.

Nachteile:

  • Zwei Aktionen statt einer
  • Möglicherweise gibt es keinen Ort, an dem Sie kopieren, oder es besteht kein Zugriff darauf, oder das Skript wird zum Zeitpunkt der Ersetzung möglicherweise ausgeführt.
  • Es ist ratsam, nach sich selbst aufzuräumen (das Skript zu löschen).
  • Schon drei Aktionen.

Lösung 2:

  • Behalten Sie nur Funktionsdefinitionen im Skript bei und führen Sie überhaupt nichts aus
  • Mit sed Fügen Sie am Ende einen Funktionsaufruf hinzu
  • Senden Sie alles per Pipe direkt an shh (|)

Profis:

  • Wirklich staatenlos
  • Keine vorgefertigten Entitäten
  • Kühl fühlen

Machen wir es einfach ohne Ansible. Ja, alles ist bereits erfunden. Ja, ein Fahrrad. Sehen Sie, wie schlicht, elegant und minimalistisch das Fahrrad ist:

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

Allerdings können wir nicht sicher sein, dass der Remote-Host über ausreichend Bash verfügt, daher fügen wir zu Beginn eine kleine Prüfung hinzu (dies ist statt Muschelknall):

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

Und jetzt ist es wahr:

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

Jetzt können Sie öffnen http://localhost/ Führen Sie im Browser die Bereitstellung erneut aus und stellen Sie sicher, dass sie reibungslos ausgeführt wird, indem Sie die Seite während des Layouts entsprechend der CD aktualisieren.

Vergessen Sie nicht, nach der Arbeit aufzuräumen :3

$ docker rm -f uptimer_GREEN reverse-proxy 
uptimer_GREEN
reverse-proxy

$ docker network rm web-gateway 
web-gateway

$ cd ..

$ rm -r blue-green-deployment

Source: habr.com