In diesem Artikel zeigen wir, wie wir , , und eine nahtlose Bereitstellung einer Webanwendung organisieren. ist eine Technik, die es ermöglicht, Anwendungen sofort zu aktualisieren, ohne eine einzige Anfrage abzulehnen. Sie ist eine der Strategien für Zero Downtime Deployment und eignet sich am besten für Anwendungen mit einer Instanz, jedoch mit der Möglichkeit, daneben eine zweite, einsatzbereite Instanz zu laden.
Angenommen, Sie haben eine Webanwendung, die von vielen Kunden aktiv genutzt wird, und die darf unter keinen Umständen kurzzeitig ausfallen. Sie müssen jedoch ein Update der Bibliothek, einen Bugfix oder eine neue coole Funktion einspielen. In einer herkömmlichen Situation müssten Sie die Anwendung anhalten, sie ersetzen und erneut starten. Im Falle von Docker kann man zunächst ersetzen und danach neu starten, aber es gibt dennoch einen Zeitraum, in dem Anfragen an die Anwendung nicht bearbeitet werden, da die Anwendung in der Regel Zeit für den ersten Start benötigt. Und wenn sie startet, aber nicht funktioniert? Lassen Sie uns diese Herausforderung mit minimalen Mitteln und maximaler Eleganz lösen.
HAFTUNGSAUSSCHLUSS: Der Großteil des Artikels ist im experimentellen Format als Aufzeichnung einer Konsolensitzung dargestellt. Ich hoffe, das lässt sich recht einfach nachvollziehen, und dieser Code dokumentiert sich selbst in ausreichendem Maße. Um die Atmosphäre zu schaffen, stellen Sie sich vor, dass dies nicht einfach Code-Schnipsel sind, sondern Papier aus einem „eisernen“ Teletype.
Interessante Techniken, die man schwer finden kann, wenn man nur den Code liest, sind am Anfang jedes Abschnitts beschrieben. Wenn etwas unklar ist, googeln Sie bitte und überprüfen Sie in (zum Glück funktioniert es wieder, da Telegram entblockt wurde). Was nicht googelt, können Sie in den Kommentaren fragen. Ich ergänze gerne den entsprechenden Abschnitt "Interessante Techniken".
Legen wir los.
$ mkdir blue-green-deployment && cd $_Dienste
Lassen Sie uns einen Testdienst erstellen und diesen in einen Container packen.
Interessante Techniken
cat < file-name( + ) — eine Methode, um eine mehrzeilige Datei mit einem Befehl zu erstellen. Alles, was bash von/dev/stdinnach dieser Zeile und bis zur ZeileEOFwird infile-name.wget -qO- URL() — um das über HTTP erhaltene Dokument in/dev/stdout(entsprichtcurl URL).
Ausgabe
Ich schneide den Snippet absichtlich, um die Highlights für Python einzufügen. Am Ende wird es noch einen solchen Abschnitt geben. Stellen Sie sich vor, dass an diesen Stellen das Papier geschnitten wurde, um es an die Highlight-Abteilung weiterzugeben (wo der Code von Hand mit Markern hervorgehoben wurde), und diese Stücke wurden dann wieder eingeklebt.
$ cat < uptimer.pyvon http.server importieren BaseHTTPRequestHandler, HTTPServer
von time importieren 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} läuft seit {t:3.1f} Sekunden.</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} (lädt in {loading_seconds} Sek.) gestartet.')
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 (lädt in 10 Sek.) gestartet.
$ wget -qSO- http://localhost:8080
HTTP/1.0 503 Dienst nicht verfügbar
Server: BaseHTTP/0.6 Python/3.8.3
Date: Sa, 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: Sa, 22 Aug 2020 19:52:45 GMT
Content-Type: text/html
<h2>Uptimer v1.0 läuft seit 15,4 Sekunden.</h2>
$ docker rm --force uptimer
uptimerReverse-Proxy
Damit unsere Anwendung die Möglichkeit hat, nahtlos umzuschalten, muss etwas vor ihr stehen, das die Änderung verbirgt. Das könnte ein Webserver sein. in . Der Reverse-Proxy wird zwischen dem Client und der Anwendung eingerichtet. Er nimmt Anfragen von Clients entgegen und leitet sie an die Anwendung weiter, während die Antworten der Anwendung an die Clients geschickt werden.
Die Anwendung und der Reverse-Proxy können innerhalb von Docker über verbunden werden. Auf diese Weise muss dem Container mit der Anwendung nicht einmal ein Port im Hostsystem zugewiesen werden, was die Anwendung vor Bedrohungen von außen maximal isoliert.
Wenn der Reverse-Proxy auf einem anderen Host betrieben wird, müssen wir auf docker network verzichten und die Anwendung mit dem Reverse-Proxy über das Netzwerk des Hosts verbinden, indem wir den Port der Anwendung mit dem Parameter --publish, wie beim ersten Start und bei einem Reverse-Proxy.
Wir werden den Reverse-Proxy auf Port 80 starten, da dies die Entität ist, die nach außen hören sollte. Wenn der Port 80 auf Ihrem Testhost belegt ist, ändern Sie den Parameter --publish 80:80 findet man --publish ANY_FREE_PORT:80.
Interessante Techniken
- „In von Benutzern erstellten Docker-Netzwerken kann mit Containern nicht nur über die IP-Adresse, sondern auch über den Containernamen kommuniziert werden“ (, Punkt 5 des Docker-Codes).
Ausgabe
$ 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 läuft seit 11,5 Sekunden.</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.…" vor 27 Sekunden Hoch 25 Sekunden 0.0.0.0:80->80/tcp reverse-proxy
a1105f1b583d uptimer "python -u ./app.py" Vor etwa einer Minute Hoch Vor etwa einer 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: Sa, 22 Aug 2020 19:56:24 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
<h2>Uptimer v1.0 läuft seit 104,1 Sekunden.</h2>Nahtloses Deployment
Wir werden eine neue Version der Anwendung (mit doppelt so schneller Startleistung) ausrollen und versuchen, sie nahtlos zu deployen.
Interessante Techniken
echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt'— Text speichernmy textin die Datei/my-file.txtim Containermy-container.cat > /my-file.txt— Speichern Sie den Inhalt des Standard-Inputs in eine Datei/dev/stdin.
Ausgabe
$ sed -i "s/app_version = 1/app_version = 2/" uptimer.py
$ docker build --tag uptimer .
Der Build-Kontext wird an den Docker-Daemon gesendet 39,94kB
Schritt 1/4: FROM python:alpine
---> 8ecf5a48c789
Schritt 2/4: EXPOSE 8080
---> Cache wird verwendet
---> cf92d174c9d3
Schritt 3/4: COPY uptimer.py app.py
---> 3eca6a51cb2d
Schritt 4/4: CMD [ "python", "-u", ".\/app.py" ]
---> Wird ausgeführt in 8f13c6d3d9e7
Zwischencontainer 8f13c6d3d9e7 wird entfernt
---> 1d56897841ec
Erfolgreich gebaut 1d56897841ec
Erfolgreich getaggt uptimer:latest
$ docker run --detach --rm --name uptimer_BLUE --network web-gateway uptimer
96932d4ca97a25b1b42d1b5f0ede993b43f95fac3c064262c5c527e16c119e02
$ docker logs uptimer_BLUE
Uptimer v2.0 (lädt in 5 Sek.) gestartet.
$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 läuft seit 23,9 Sekunden.</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: Prozesssignal gestartet
$ wget -qO- http://localhost
<h2>Uptimer v2.0 läuft seit 63,4 Sekunden.</h2>
$ docker rm -f uptimer
uptimer
$ wget -qO- http://localhost
<h2>Uptimer v2.0 läuft seit 84,8 Sekunden.</h2>
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
96932d4ca97a uptimer "python -u .\/app.py" Vor etwa einer Minute Aktiv Vor etwa einer Minute 8080/tcp uptimer_BLUE
80695a822c19 nginx:alpine "\/docker-entrypoint.…" Vor 8 Minuten Aktiv 8 Minuten 0.0.0.0:80->80/tcp reverse-proxyIn diesem Schritt wird das Image direkt auf dem Server gebaut, was die Verfügbarkeit der Quellcodes der Anwendung erfordert und den Server mit zusätzlicher Arbeit belastet. Der nächste Schritt wird die Auslagerung des Bildbaus auf eine separate Maschine (z.B. in eine CI-System) und die anschließende Übertragung auf den Server sein.
Übertragung von Images
Leider macht es keinen Sinn, Images von localhost auf localhost zu übertragen. Daher können Sie diesen Abschnitt nur ausprobieren, wenn Sie zwei Hosts mit Docker zur Verfügung haben. Im Minimalfall sieht es etwa so aus:
$ ssh produktions-server docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
$ docker image save uptimer | ssh produktions-server 'docker image load'
Geladenes Image: uptimer:latest
$ ssh produktions-server docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
uptimer latest 1d56897841ec vor 5 Minuten 78,9MBDer Befehl docker save speichert die Daten des Images in ein .tar-Archiv, was bedeutet, dass es etwa 1,5-mal größer ist als im komprimierten Format. Lassen Sie uns also im Sinne der Zeit- und Datensparsamkeit komprimieren:
$ docker image save uptimer | gzip | ssh produktions-server 'zcat | docker image load'
Geladenes Image: uptimer:latestAußerdem können Sie den Übertragungsprozess überwachen (dafür benötigen Sie jedoch ein externes Tool):
$ docker image save uptimer | gzip | pv | ssh produktions-server 'zcat | docker image load'
25,7MiB 0:01:01 [ 425KiB/s] [ ]
Geladenes Image: uptimer:latestTipp: Wenn Sie viele Parameter für die Verbindung zum Server über SSH benötigen, verwenden Sie möglicherweise nicht die Datei
~/ .ssh / config.
Die Übertragung des Images über docker image save / load — ist die minimalistischste Methode, aber nicht die einzige. Es gibt auch andere:
- Container-Registry (Branchensstandard).
- Verbinden mit dem Docker-Daemon des Servers von einem anderen Host:
- Umgebungsvariable
DOCKER_HOST. - Kommandozeilenparameter
-Hoder--hostWerkzeugversion: '1' services: simplesample-sonar: image: sonarqube:lts ports: - 9001:9000 - 9092:9092 network_mode: bridge. docker-Kontext
- Umgebungsvariable
Eine zweite Methode (mit drei Implementierungsvarianten) ist gut in dem Artikel beschrieben .
deploy.sh
Jetzt fassen wir alles, was wir manuell gemacht haben, in ein Skript zusammen. Beginnen wir mit der Hauptfunktion und schauen wir uns dann die anderen an, die darin verwendet werden.
Interessante Techniken
${parameter?err_msg}— eines der Zauberformeln der Bash-Magie (aka ). Wennder Parameternicht gesetzt ist, gebeerr_msgaus und verlasse mit Code 1.docker --log-driver journald— standardmäßig verwendet der Docker-Logtreiber eine Textdatei ohne Rotation. Diese Herangehensweise führt dazu, dass die Logs schnell die gesamte Festplatte füllen, weshalb der Treiber für Produktionsumgebungen auf eine intelligentere Lösung geändert werden sollte.
Deployment-Skript
deploy() {
local usage_msg="Verwendung: ${FUNCNAME[0]} bild_name"
local image_name=${1?$usage_msg}
ensure-reverse-proxy || return 2
if get-active-slot $image_name
then
local OLD=${image_name}_BLAU
local new_slot=GRÜN
else
local OLD=${image_name}_GRÜN
local new_slot=BLAU
fi
local NEW=${image_name}_${new_slot}
echo "Bereitstellung von '$NEW' anstelle von '$OLD'..."
docker run
--detach
--restart always
--log-driver journald
--name $NEW
--network web-gateway
$image_name || return 3
echo "Container gestartet. Überprüfen der Gesundheit..."
for i in {1..20}
do
sleep 1
if get-service-status $image_name $new_slot
then
echo "Neuer '$NEW' Dienst scheint in Ordnung zu sein. Wechseln der Heads..."
sleep 2 # Sicherstellen, dass der Dienst bereit ist
set-active-slot $image_name $new_slot || return 4
echo "'$NEW' Dienst ist live!"
sleep 2 # Sicherstellen, dass alle Anfragen bearbeitet wurden
echo "'$OLD' wird beendet..."
docker rm -f $OLD
docker image prune -f
echo "Bereitstellung erfolgreich!"
return 0
fi
echo "Neuer '$NEW' Dienst ist noch nicht bereit. Warten ($i)..."
done
echo "Neuer '$NEW' Dienst wurde nicht hochgefahren, wird beendet. Bereitstellung fehlgeschlagen 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 aktuell aktiv ist für den angegebenen Dienst (BLAUoderGRÜN)get-service-status service_name deployment_slot— Bestimmt, ob der Dienst bereit ist, eingehende Anfragen zu verarbeitenset-active-slot service_name deployment_slot— Ändert die Nginx-Konfiguration im Reverse-Proxy-Container
In der richtigen Reihenfolge:
ensure-reverse-proxy() {
is-container-up reverse-proxy && return 0
echo "Starte den 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?"Benutzung: ${FUNCNAME[0]} container_name"}
[ -n "$(docker ps -f name=${container} -q)" ]
return $?
}
get-active-slot() {
local service=${1?"Benutzung: ${FUNCNAME[0]} service_name"}
if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
echo "Kollision festgestellt! Stoppe ${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="Benutzung: ${FUNCNAME[0]} service_name deployment_slot"
local service=${1?usage_msg}
local slot=${2?$usage_msg}
case $service in
# Fügen Sie hier spezifische Healthcheck-Pfade für Ihre Dienste hinzu
*) local health_check_port_path=":8080/" ;;
esac
local health_check_address="http://${service}_${slot}${health_check_port_path}"
echo "Anfrage '$health_check_address' im Docker-Netzwerk 'web-gateway':"
docker run --rm --network web-gateway alpine
wget --timeout=1 --quiet --server-response $health_check_address
return $?
}
set-active-slot() {
local usage_msg="Benutzung: ${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
}Die Funktion get-active-slot benötigt einige Erklärungen:
Warum gibt sie eine Zahl zurück und gibt keinen String aus?
Trotzdem prüfen wir im aufrufenden Code das Ergebnis ihrer Ausführung, und es ist viel einfacher, den Exit-Code mit Bash zu überprüfen als den String. Außerdem ist es ganz einfach, daraus einen String zu erstellen:
get-active-slot service && echo BLUE || echo GREEN.
Reichen drei Bedingungen aus, um alle Zustände zu unterscheiden?
Auch zwei reichen aus, die letzte ist nur der Vollständigkeit halber da, um nicht zu schreiben. else.
Nur die Funktion, die die Nginx-Konfigurationen zurückgibt, bleibt unbestimmt: get-nginx-config service_name deployment_slot. Analog zum Healthcheck kann hier jede Konfiguration für jeden Dienst festgelegt werden. Interessant ist nur cat <<- EOF, was alle Tabs am Anfang entfernt. Allerdings ist der Preis für anständige Formatierung die Mischung von Tabs mit Leerzeichen, was heute als sehr schlechter Stil gilt. Aber Bash erzwingt Tabs, und in der Nginx-Konfiguration wäre es auch schön, eine ordentliche Formatierung zu haben. Kurz gesagt, hier scheint die Mischung aus Tabs und Leerzeichen tatsächlich die beste Lösung aus den schlechtesten zu sein. Doch im folgenden Snippet werden Sie das nicht sehen, da Habr „es gut macht“ und alle Tabs in 4 Leerzeichen umwandelt, wodurch EOF ungültig wird. .
Um es einfacher zu machen, erzähle ich gleich von
cat << 'EOF', das später noch vorkommen wird. Wenn man einfach schreibtcat << EOF, wird in der heredoc die Interpolation der Zeichenfolge durchgeführt (Variablen ($foo), Befehlaufrufe ($(bar)), etc. werden aufgelöst), und wenn man das Ende des Dokuments in einfache Anführungszeichen setzt, wird die Interpolation deaktiviert und das Zeichen$wird unverändert ausgegeben. Das ist perfekt, um ein Skript in ein anderes Skript einzufügen.
get-nginx-config() {
local usage_msg="Verwendung: ${FUNCNAME[0]} dienst_name bereitstellungs_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
# Fügen Sie hier spezifische nginx-Konfigurationen für Ihre Dienste hinzu
*) nginx-config-simple-service $container_name:8080 ;;
esac
}
nginx-config-simple-service() {
local usage_msg="Verwendung: ${FUNCNAME[0]} proxy_pass"
local proxy_pass=${1?$usage_msg}
cat << EOF
server {
listen 80;
location / {
proxy_pass http://$proxy_pass;
}
}
EOF
}Das ist das gesamte Skript. Und hier ist zum Herunterladen über wget oder curl.
Ausführen von parametrierbaren Skripten auf einem entfernten Server
Es ist an der Zeit, sich mit dem Zielserver zu verbinden. Diesmal localhost passt es ganz gut:
$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: Versuch, sich mit dem neuen Schlüssel einzuloggen, um bestehende Schlüssel herauszufiltern
/usr/bin/ssh-copy-id: INFO: 1 Schlüssel ist noch zu installieren – falls Sie jetzt aufgefordert werden, ist es zur Installation der neuen Schlüssel
himura@localhost's Passwort:
Anzahl der hinzugefügten Schlüssel: 1
Versuchen Sie nun, sich mit folgendem Befehl auf dem Computer anzumelden: "ssh 'localhost'"
und überprüfen Sie, ob nur die gewünschten Schlüssel hinzugefügt wurden.Wir haben ein Bereitstellungsskript geschrieben, das ein vorab erstelltes Image auf den Zielserver überträgt und nahtlos den Dienstcontainer austauscht. Aber wie führen wir es auf einer entfernten Maschine aus? Das Skript hat Parameter, da es universell ist und mehrere Dienste gleichzeitig unter einem Reverse-Proxy bereitstellen kann (mit Nginx-Configs kann geregelt werden, unter welcher URL welcher Dienst läuft). Das Skript kann nicht auf dem Server gespeichert werden, da wir es in diesem Fall nicht automatisch aktualisieren können (zum Zweck von Bugfixes und zum Hinzufügen neuer Dienste) und überhaupt, Zustand = böse.
Lösung 1: Das Skript auf dem Server speichern, aber jedes Mal über scpkopieren. Dann sich per ssh verbinden und das Skript mit den erforderlichen Parametern ausführen.
Nachteile:
- Zwei Schritte anstelle von einem
- Der Ort, an den Sie kopieren, könnte nicht existieren, oder der Zugriff darauf könnte fehlen, oder das Skript könnte zum Zeitpunkt des Austauschs ausgeführt werden.
- Bitte den Bereich hinterlassen (Skript löschen).
- Bereits drei Aktionen.
Lösung 2:
- Im Skript nur Funktionsdefinitionen enthalten und nichts ausführen.
- Mit
sedFunktionaufruf am Ende hinzufügen. - Alles direkt über Pipe in shh senden (
|)
Vorteile:
- Wirklich zustandslos.
- Keine Boilerplate-Entitäten.
- Fühlt sich gut an.
Aber bitte ohne Ansible. Ja, alles wurde schon erfunden. Ja, das Fahrrad. Schaut mal, wie einfach, elegant und minimalistisch dieses Fahrrad ist:
$ cat < 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_SCRIPTEOF
$ chmod +x deploy.sh
$ ./deploy.sh localhost magic-porridge-pot
Verbindung zu localhost...
Juhu! Die 'deploy'-Funktion wird auf 'hut' mit dem Argument 'magic-porridge-pot' ausgeführt.Wir können jedoch nicht sicher sein, dass auf dem entfernten Host ein adäquates Bash vorhanden ist, also fügen wir zu Beginn einen kurzen Check hinzu (das ist anstelle von ):
if [ "$SHELL" != "/bin/bash" ]
then
echo "Die '$SHELL'-Shell wird von 'deploy.sh' nicht unterstützt. Setzen Sie eine '/bin/bash'-Shell für '$USER@$HOSTNAME' ein."
exit 1
fiUnd jetzt wird es ernst:
$ 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 kann man öffnen im Browser, das Deployment nochmals starten und sicherstellen, dass es nahtlos verläuft, indem man die Seite während der Bereitstellung per F5 aktualisiert.
Vergessen wir nicht aufzuräumen nach der Arbeit :3
$ docker rm -f uptimer_GREEN reverse-proxy
uptimer_GREEN
reverse-proxy
$ docker network rm web-gateway
web-gateway
$ cd ..
$ rm -r blue-green-deploymentQuelle: habr.com
