In diesem Artikel werden wir , , und eine nahtlose Bereitstellung einer Webanwendung organisieren. ist eine Technik, die es ermöglicht, eine Anwendung sofort zu aktualisieren, ohne Anfragen abzulehnen. Es ist eine der Strategien für Zero Downtime Deployment und eignet sich am besten für Anwendungen mit einer Instanz, aber mit der Möglichkeit, eine zweite, betriebsbereite Instanz daneben zu laden.
Angenommen, Sie haben eine Webanwendung, die von vielen Kunden aktiv genutzt wird, und es darf absolut nicht für ein paar Sekunden ausfallen. Sie müssen dringend eine Bibliothek aktualisieren, einen Bugfix einspielen oder ein neues cooles Feature bereitstellen. In der Regel müsste die Anwendung gestoppt, ersetzt und wieder gestartet werden. Im Falle von Docker kann man zunächst ersetzen und dann neu starten, aber es gibt trotzdem einen Zeitraum, in dem keine Anfragen an die Anwendung bearbeitet werden, denn normalerweise benötigt die Anwendung einige Zeit zum initialen Laden. Und wenn sie gestartet wird, sich aber als nicht funktionsfähig herausstellt? Das ist das Problem, lassen Sie uns dies mit minimalen Mitteln und maximal elegant lösen.
HAFTUNGSAUSSCHLUSS: Der Großteil des Artikels liegt in einem experimentellen Format vor – in Form einer Aufzeichnung einer Konsolensitzung. Ich hoffe, es wird nicht zu kompliziert sein, dies zu verstehen, und dieser Code dokumentiert sich selbst in ausreichendem Maße. Für die Atmosphäre stellen Sie sich vor, dass dies nicht nur Code-Snippets sind, sondern Papier vom „Eisen“-Telegraphen.
Interessante Techniken, die man nicht einfach durch das Lesen des Codes googeln kann, werden am Anfang jedes Abschnitts beschrieben. Wenn etwas unklar ist, googeln Sie und überprüfen Sie in (glücklicherweise funktioniert es wieder, da Telegram freigeschaltet wurde). Was sich nicht googeln lässt – fragen Sie in den Kommentaren. Ich ergänze gerne den entsprechenden Abschnitt "Interessante Techniken".
Legen wir los.
$ mkdir blue-green-deployment && cd $_Dienst
Erstellen wir einen Testdienst und setzen ihn in einen Container.
Interessante Techniken
cat < file-name( + ) – eine Methode, um eine mehrzeilige Datei mit einem Befehl zu erstellen. Alles, was bash ab/dev/stdindieser Zeile und bis zur ZeileEOFgeschrieben wird, wird infile-name.wget -qO- URL() – die erhaltene HTTP-Dokumentation in/dev/stdout(analogcurl URL).
Druckausgabe
Ich breche absichtlich den Snippet auf, um die Hervorhebung für Python einzuschließen. Am Ende wird es noch einen ähnlichen Abschnitt geben. Stellen Sie sich vor, dass an diesen Stellen das Papier für den Highlighting-Abteilung (wo der Code manuell mit Textmarkern hervorgehoben wurde) geschnitten und dann diese Stücke wieder eingeklebt wurden.
$ cat < uptimer.pyfrom 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} 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
Datum: Sa, 22 Aug 2020 19:52:40 GMT
Verbindung: geschlossen
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
Datum: 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 sich unbemerkt ändern kann, muss davor eine Entität stehen, die ihre Modifikation verbirgt. Dies könnte ein Webserver sein. in . Der Reverse-Proxy wird zwischen dem Client und der Anwendung installiert. Er empfängt Anfragen von den Clients und leitet sie an die Anwendung weiter, während er die Antworten der Anwendung an die Clients zurücksendet.
Die Anwendung und der Reverse-Proxy können innerhalb von Docker mit verknüpft werden. So kann dem Container mit der Anwendung sogar der Port im Host-System erspart werden, was es ermöglicht, die Anwendung maximal vor externen Bedrohungen zu isolieren.
Wenn der Reverse-Proxy auf einem anderen Host lebt, muss man auf das docker network verzichten und die Anwendung mit dem Reverse-Proxy über das Host-Netzwerk verbinden, indem der Port weitergeleitet wird. der Anwendung Parameter --publish, wie bei der ersten Ausführung und wie beim Reverse-Proxy.
Den Reverse-Proxy werden wir auf Port 80 starten, da dies die Entität ist, die externe Anfragen annehmen sollte. Wenn Port 80 auf Ihrem Testhost belegt ist, ändern Sie den Parameter --publish 80:80 auf --publish ANY_FREE_PORT:80.
Interessante Techniken
- "In von Benutzern erstellten Docker-Netzwerken können Container nicht nur über die IP-Adresse angesprochen werden. Der Name des Containers wird auch in seine IP aufgelöst." (, Punkt 5 des Docker-Kodex).
Druckausgabe
$ 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 Läuft seit 25 Sekunden 0.0.0.0:80->80\/tcp reverse-proxy
a1105f1b583d uptimer "python -u .\/app.py" Vor etwa einer Minute Läuft seit 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: Sat, 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 einer zweifachen Steigerung der Startup-Leistung) veröffentlichen und versuchen, sie nahtlos zu deploien.
Interessante Techniken
echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt'— Text schreibenmy textin die Datei/my-file.txtim Containermy-container.cat > /my-file.txt— Den Inhalt des Standard-Input in die Datei schreiben/dev/stdin.
Druckausgabe
$ sed -i "s/app_version = 1/app_version = 2/" uptimer.py
$ docker build --tag uptimer .
Sende den Build-Kontext an den Docker-Daemon 39.94kB
Schritt 1/4 : VON python:alpine
---> 8ecf5a48c789
Schritt 2/4 : EXPONIEREN 8080
---> Aus dem Cache verwenden
---> cf92d174c9d3
Schritt 3/4 : KOPIEREN uptimer.py app.py
---> 3eca6a51cb2d
Schritt 4/4 : CMD [ "python", "-u", ". /app.py" ]
---> Ausgeführt in 8f13c6d3d9e7
Entferne Zwischencontainer 8f13c6d3d9e7
---> 1d56897841ec
Erfolgreich erstellt 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 [Hinweis] 68#68: Signalverarbeitung 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 KOMMANDO ERSTELLT STATUS PORTS NAMES
96932d4ca97a uptimer "python -u ./app.py" Vor etwa einer Minute Hoch Vor etwa einer Minute 8080/tcp uptimer_BLUE
80695a822c19 nginx:alpine " /docker-entrypoint.…" Vor 8 Minuten Hoch 8 Minuten 0.0.0.0:80->80/tcp reverse-proxyIn diesem Schritt wird das Image direkt auf dem Server gebaut, was die Anwesenheit der Anwendungsourcecodes dort erfordert und den Server zusätzlich belastet. Der nächste Schritt wird die Auslagerung des Builds auf eine separate Maschine (zum Beispiel in ein CI-System) mit anschließender Übertragung auf den Server sein.
Image-Übertragung
Leider macht es keinen Sinn, Images von localhost auf localhost zu übertragen, daher kann dieser Abschnitt nur getestet werden, wenn man zwei Hosts mit Docker zur Hand hat. Im Minimalfall 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'
Image geladen: uptimer:latest
$ ssh production-server docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
uptimer latest 1d56897841ec vor 5 Minuten 78.9MBTeam docker save speichert die Bilddaten in einem .tar-Archiv, das also ungefähr 1,5-mal so viel wiegen kann, wie es komprimiert wiegen würde. Also lasst uns das im Sinne der Zeit- und Datenreduzierung komprimieren:
$ docker image save uptimer | gzip | ssh production-server 'zcat | docker image load'
Image geladen: uptimer:latestAußerdem kann man den Übertragungsprozess beobachten (man benötigt allerdings ein externes Tool dazu):
$ docker image save uptimer | gzip | pv | ssh production-server 'zcat | docker image load'
25,7MiB 0:01:01 [ 425KiB/s] [ ]
Image geladen: uptimer:latestTipp: Wenn Sie für die Verbindung zum Server über SSH viele Parameter benötigen, verwenden Sie möglicherweise nicht die Datei
~/.ssh/config.
Die Übertragung des Images erfolgt über docker image save/load – das ist die minimalistischste Methode, aber nicht die einzige. Es gibt noch andere:
- Container Registry (Industrie-Standard).
- Verbinden Sie sich mit dem Docker-Daemon des Servers von einem anderen Host aus:
- Umgebungsvariable
DOCKER_HOST. - Befehlszeilenparameter
-Hoder--hosteinem Tooldocker-compose. docker context
- Umgebungsvariable
Die zweite Methode (mit drei Varianten ihrer Umsetzung) wird gut in dem Artikel beschrieben .
deploy.sh
Jetzt fassen wir alles, was wir manuell gemacht haben, in ein Script zusammen. Wir beginnen mit der Top-Level-Funktion und schauen uns dann die anderen an, die darin verwendet werden.
Interessante Techniken
${parameter?err_msg}– eines der Zauberwesen der Bash-Magie (aka ). Wennparameternicht gesetzt ist, zeigt eserr_msgan und beendet mit dem Code 1.docker --log-driver journald– standardmäßig ist der Docker-Logging-Treiber eine Textdatei ohne Rotation. Mit diesem Ansatz füllen die Logs schnell die gesamte Festplatte, daher muss der Treiber für Produktionsumgebungen auf einen intelligenteren gewechselt werden.
Deployment-Script
deploy() {
local usage_msg="Verwendung: ${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' im Austausch für '$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. Umschalten..."
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' töten..."
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 hat nicht hochgefahren, töten... Bereitstellung fehlgeschlagen T_T"
docker rm -f $NEW
return 5
}Verwendete Funktionen:
ensure-reverse-proxy— Stellt sicher, dass der Reverse-Proxy läuft (nützlich für die erste Bereitstellung)get-active-slot service_name— Bestimmt, welcher Slot derzeit aktiv ist für den angegebenen Dienst (BLUEoderGREEN)get-service-status service_name deployment_slot— Bestimmt, ob der Dienst bereit ist, eingehende Anfragen zu bearbeitenset-active-slot service_name deployment_slot— Ändert die Nginx-Konfiguration im Reverse-Proxy-Container
In der Reihenfolge:
ensure-reverse-proxy() {
is-container-up reverse-proxy && return 0
echo "Reverse-Proxy wird bereitgestellt..."
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?"Verwendung: ${FUNCNAME[0]} container_name"}
[ -n "$(docker ps -f name=${container} -q)" ]
return $?
}
get-active-slot() {
local service=${1?"Verwendung: ${FUNCNAME[0]} service_name"}
if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
echo "Kollision erkannt! 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="Verwendung: ${FUNCNAME[0]} service_name deployment_slot"
local service=${1?usage_msg}
local slot=${2?$usage_msg}
case $service in
# Fügen Sie hier spezifische Health-Check-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 "Fordere '$health_check_address' im Docker-Netzwerk 'web-gateway' an:"
docker run --rm --network web-gateway alpine
wget --timeout=1 --quiet --server-response $health_check_address
return $?
}
set-active-slot() {
local usage_msg="Verwendung: ${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 benötigt einige Erläuterungen:
Warum gibt sie eine Zahl zurück und nicht einen String?
Trotzdem überprüfen wir das Ergebnis in der aufrufenden Funktion, und den Exit-Code mit Bash zu überprüfen ist viel einfacher als einen String. Außerdem ist es ganz einfach, einen String daraus zu erhalten:
get-active-slot service && echo BLUE || echo GREEN.
Reichen die drei Bedingungen aus, um alle Zustände zu unterscheiden?
Zwei Bedingungen reichen sogar aus, die letzte ist hier nur der Vollständigkeit wegen. else.
Die Funktion, die die Nginx-Konfigurationen zurückgibt, ist noch unbestimmt: get-nginx-config service_name deployment_slot. Entsprechend dem Healthcheck kann hier jede Konfiguration für jeden Dienst festgelegt werden. Interessant ist lediglich, dass cat <<- EOF, was es ermöglicht, alle Tabs am Anfang zu entfernen. Leider ist der Preis für eine anständige Formatierung — gemischte Tabs mit Leerzeichen, was heute als sehr schlechter Stil gilt. Aber bash erzwingt Tabs, und auch in der nginx-Konfiguration wäre es schön, eine ordentliche Formatierung zu haben. Kurz gesagt, hier scheint das Mischen von Tabs und Leerzeichen wirklich die beste Lösung aus den schlechten Optionen zu sein. Allerdings werden Sie das im folgenden Snippet nicht sehen, da habr «gut macht», alle Tabs in 4 Leerzeichen umwandelt und EOF ungültig macht. .
Um es nicht zweimal zu wiederholen, sage ich gleich etwas über
cat << 'EOF', das später noch auftauchen wird. Wenn man einfach schreibtcat << EOF, erfolgt innerhalb des heredocs die Interpolation der Zeichenfolge (Variablen werden aufgelöst ($foo), Befehlaufrufe ($(bar)) usw.), 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 notwendig, um ein Skript in ein anderes Skript einzufügen.
get-nginx-config() {
local usage_msg="Verwendung: ${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
# 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 zum Herunterladen über wget oder curl.
Ausführung parametrisierten Skripte auf einem entfernten Server
Es ist an der Zeit, auf den Zielserver zuzugreifen. Diesmal localhost passt gut:
$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: versucht, sich mit den neuen Schlüssel(n) einzuloggen, um alle zu filtern, die bereits installiert sind
/usr/bin/ssh-copy-id: INFO: 1 Schlüssel(n) bleiben zu installieren -- wenn Sie jetzt aufgefordert werden, geschieht dies, um die neuen Schlüssel zu installieren
himura@localhost's Passwort:
Anzahl der hinzugefügten Schlüssel: 1
Versuchen Sie jetzt, sich mit der Maschine einzuloggen, mit: "ssh 'localhost'"
und überprüfen Sie, ob nur die Schlüssel hinzugefügt wurden, die Sie wollten.Wir haben ein Deployment-Skript geschrieben, das ein vorab zusammengestelltes Image auf den Zielserver überträgt und nahtlos den Service-Container austauscht. Aber wie führt man es auf einer entfernten Maschine aus? Das Skript hat Argumente, da es universell ist und mehrere Dienste unter einem Reverse-Proxy gleichzeitig bereitstellen kann (mit Nginx-Konfigurationen lässt sich regeln, welcher Dienst unter welcher URL läuft). Das Skript kann nicht auf dem Server gespeichert werden, da wir es nur im Falle von Bugfixes und der Hinzufügung neuer Dienste nicht automatisch aktualisieren können. Außerdem, der Zustand = das Böse.
Lösung 1: Das Skript auf dem Server speichern, es aber jedes Mal über scpzu kopieren. Dann per ssh eine Verbindung herstellen und das Skript mit den erforderlichen Argumenten ausführen.
Nachteile:
- Zwei Aktionen statt einer.
- Der Ort, an den Sie kopieren, könnte nicht existieren, oder es könnte keinen Zugang dazu geben, oder das Skript könnte während des Austauschs ausgeführt werden.
- Es wäre wünschenswert, es zu entfernen (das Skript zu löschen).
- Schon drei Aktionen.
Lösung 2:
- Im Skript nur Funktionsdefinitionen halten und überhaupt nichts ausführen.
- Mit Hilfe von
sedAm Ende einen Funktionsaufruf hinzufügen. - Alles direkt in shh über pipe senden (
|)
Vorteile:
- Wirklich zustandslos.
- Keine Boilerplate-Entitäten.
- Fühlt sich cool an.
Lass uns dabei nur ohne Ansible auskommen. Ja, alles wurde schon erdacht. Ja, das Fahrrad. Schaut euch dieses einfache, elegante und minimalistische Fahrrad an:
$ 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. Deshalb fügen wir zu Beginn eine kleine Überprüfung hinzu (das ist anstelle von ):
if [ "$SHELL" != "/bin/bash" ]
then
echo "Die '$SHELL'-Shell wird von 'deploy.sh' nicht unterstützt. Stellen Sie eine '/bin/bash'-Shell für '$USER@$HOSTNAME' ein."
exit 1
fiUnd jetzt geht es wirklich los:
$ 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
Sende das komprimierte Image 'uptimer' an 'localhost' per ssh...
Geladenes Image: uptimer:latest
Verbindung zu 'localhost' via ssh, um 'uptimer' nahtlos bereitzustellen...
Bereitstellung von 'uptimer_GREEN' anstelle von 'uptimer_BLUE'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Container gestartet. Überprüfe die Gesundheit...
Anfrage an 'http://uptimer_GREEN:8080/' im Docker-Netzwerk 'web-gateway':
HTTP/1.0 503 Dienst nicht verfügbar
wget: Server hat Fehler zurückgegeben: HTTP/1.0 503 Dienst nicht verfügbar
Der neue Dienst 'uptimer_GREEN' ist noch nicht bereit. Warte (1)...
Anfrage an 'http://uptimer_GREEN:8080/' im Docker-Netzwerk 'web-gateway':
HTTP/1.0 503 Dienst nicht verfügbar
wget: Server hat Fehler zurückgegeben: HTTP/1.0 503 Dienst nicht verfügbar
Der neue Dienst 'uptimer_GREEN' ist noch nicht bereit. Warte (2)...
Anfrage an 'http://uptimer_GREEN:8080/' im Docker-Netzwerk 'web-gateway':
HTTP/1.0 200 OK
Server: BaseHTTP/0.6 Python/3.8.3
Datum: Sa, 22. Aug 2020 20:15:50 GMT
Content-Type: text/html
Der neue Dienst 'uptimer_GREEN' scheint in Ordnung zu sein. Köpfe wechseln...
nginx: die Konfigurationsdatei /etc/nginx/nginx.conf hat die Syntax überprüfung bestanden
nginx: die Konfigurationsdatei /etc/nginx/nginx.conf hat den Test erfolgreich bestanden
2020/08/22 20:15:54 [Hinweis] 97#97: Signalprozess gestartet
Der Dienst 'uptimer_GREEN' ist online!
Killing 'uptimer_BLUE'...
uptimer_BLUE
Gesamt wiedergewonnenen Speicher: 0B
Bereitstellung erfolgreich!Jetzt kann man öffnen im Browser, das Deployment noch einmal starten und sicherstellen, dass es nahtlos verläuft, indem man die Seite während der Bereitstellung durch einen Klick aktualisiert.
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-deploymentQuelle: habr.com
