Dans cet article, nous allons utiliser , , et la mise en Ćuvre sans interruption d'une application Web. est une technique permettant de mettre Ă jour instantanĂ©ment une application sans rejeter de requĂȘtes. C'est l'une des stratĂ©gies de dĂ©ploiement sans temps d'arrĂȘt et elle convient le mieux aux applications avec une instance unique, mais avec la possibilitĂ© de charger une seconde instance prĂȘte Ă fonctionner Ă cĂŽtĂ©.
Supposons que vous ayez une application Web avec laquelle de nombreux clients interagissent activement, et elle ne peut pas trĂšs bien ĂȘtre arrĂȘtĂ©e pendant quelques secondes. Vous devez absolument dĂ©ployer une mise Ă jour de bibliothĂšque, corriger un bogue ou ajouter une nouvelle fonctionnalitĂ© intĂ©ressante. Dans une situation normale, il faudrait arrĂȘter l'application, la remplacer et la redĂ©marrer. Dans le cas de Docker, vous pouvez d'abord remplacer, puis redĂ©marrer, mais il y aura tout de mĂȘme une pĂ©riode au cours de laquelle les requĂȘtes Ă l'application ne seront pas traitĂ©es, car gĂ©nĂ©ralement, l'application nĂ©cessite un certain temps pour se charger initialement. Et si elle se lance, mais qu'elle s'avĂšre non fonctionnelle ? Voici un dĂ©fi, essayons de le rĂ©soudre avec un minimum de ressources et de maniĂšre aussi Ă©lĂ©gante que possible.
AVERTISSEMENT : La majeure partie de l'article est prĂ©sentĂ©e sous un format expĂ©rimental - sous forme d'enregistrement de session de console. J'espĂšre que cela ne sera pas trop difficile Ă comprendre, et ce code se documente suffisamment par lui-mĂȘme. Pour l'ambiance, imaginez que ce ne sont pas simplement des extraits de code, mais du papier provenant d'un tĂ©lĂ©type « mĂ©tallique ».
Les techniques intéressantes, qui sont difficiles à trouver simplement en lisant du code, sont décrites au début de chaque section. Si quelque chose n'est pas clair, cherchez et vérifiez sur (heureusement, il fonctionne à nouveau grùce au déblocage de Telegram). Ce qui ne se google pas, demandez dans les commentaires. Je compléterai avec plaisir la section correspondante « Techniques intéressantes ».
Commençons.
$ mkdir blue-green-deployment && cd $_Service
Créons un service d'essai et plaçons-le dans un conteneur.
Techniques intéressantes
cat < file-name( + ) est une maniĂšre de crĂ©er un fichier multi-lignes en une seule commande. Tout ce que bash lira Ă partir de/dev/stdinaprĂšs cette ligne et jusqu'Ă la ligneEOFsera Ă©crit dansfile-name.wget -qO- URL() â afficher le document reçu par HTTP dans/dev/stdout(similaire Ăcurl URL).
Impression
Je sĂ©pare volontairement le snippet pour activer la coloration syntaxique pour Python. Il y aura encore un morceau de ce type Ă la fin. ConsidĂ©rez que ces endroits ont Ă©tĂ© dĂ©coupĂ©s pour ĂȘtre envoyĂ©s au dĂ©partement de mise en surbrillance (oĂč le code Ă©tait colorĂ© Ă la main avec des surligneurs), puis ces morceaux ont Ă©tĂ© collĂ©s de nouveau.
$ 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} fonctionne depuis {t:3.1f} secondes.</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} (charges en {loading_seconds} sec.) démarré.')
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 .
Envoi du contexte de construction au démon Docker 39.42kB
Étape 1/4 : FROM python:alpine
---> 8ecf5a48c789
Étape 2/4 : EXPOSE 8080
---> Utilisation du cache
---> cf92d174c9d3
Étape 3/4 : COPY uptimer.py app.py
---> a7fbb33d6b7e
Étape 4/4 : CMD [ "python", "-u", ".\/app.py" ]
---> Exécution dans 1906b4bd9fdf
Suppression du conteneur intermédiaire 1906b4bd9fdf
---> c1655b996fe8
Construit avec succès c1655b996fe8
Tagué avec succès uptimer:latest
$ docker run --rm --detach --name uptimer --publish 8080:8080 uptimer
8f88c944b8bf78974a5727070a94c76aa0b9bb2b3ecf6324b784e782614b2fbf
$ docker ps
ID DU CONTENEUR IMAGE COMMANDE CRÉÉ ÉTAT PORTS NOMS
8f88c944b8bf uptimer "python -u .\/app.py" il y a 3 secondes En cours depuis 5 secondes 0.0.0.0:8080->8080\/tcp uptimer
$ docker logs uptimer
Uptimer v1.0 (chargement en 10 sec.) démarré.
$ wget -qSO- http:\/\/localhost:8080
HTTP\/1.0 503 Service Unavailable
Serveur: BaseHTTP\/0.6 Python\/3.8.3
Date: Sam, 22 Août 2020 19:52:40 GMT
Connexion: close
Content-Type: text\/html;charset=utf-8
Content-Length: 484
$ wget -qSO- http:\/\/localhost:8080
HTTP\/1.0 200 OK
Serveur: BaseHTTP\/0.6 Python\/3.8.3
Date: Sam, 22 Août 2020 19:52:45 GMT
Content-Type: text\/html
<h2>Uptimer v1.0 fonctionne depuis 15,4 secondes.</h2>
$ docker rm --force uptimer
uptimerProxy inverse
Pour que notre application puisse ĂȘtre remplacĂ©e discrĂštement, il est nĂ©cessaire qu'il y ait une autre entitĂ© devant elle, qui masquera son remplacement. Cela peut ĂȘtre un serveur web. dans . Un proxy inverse s'installe entre le client et l'application. Il reçoit les requĂȘtes des clients et les redirige vers l'application, tandis que les rĂ©ponses de l'application sont renvoyĂ©es aux clients.
L'application et le proxy inverse peuvent ĂȘtre liĂ©s Ă l'intĂ©rieur de Docker grĂące Ă . Ainsi, le conteneur avec l'application n'a mĂȘme pas besoin d'exposer de port sur le systĂšme hĂŽte, ce qui permet d'isoler au maximum l'application des menaces externes.
Si le proxy inverse se trouve sur un autre hÎte, il faudra renoncer à docker network et relier l'application au proxy inverse via le réseau de l'hÎte, en exposant un port. applications paramÚtre --publish, comme lors du premier lancement et comme pour le proxy inverse.
Nous allons lancer le proxy inverse sur le port 80, car c'est justement cette entité qui doit écouter l'extérieur. Si le port 80 est occupé sur votre hÎte de test, changez le paramÚtre. --publish 80:80 sur --publish ANY_FREE_PORT:80.
Techniques intéressantes
- «Dans les réseaux Docker créés par l'utilisateur, il est possible de se connecter aux conteneurs non seulement par l'adresse IP. Le nom du conteneur résout également en son adresse IP» (, point 5 du code Docker).
Impression
$ 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 tourne depuis 11,5 secondes.</h2>
$ docker run --detach --publish 80:80 --network web-gateway --name reverse-proxy nginx:alpine
80695a822c19051260c66bf60605dcb4ea66802c754037704968bc42527bf120
$ docker ps
IDENTIFIANT DU CONTENEUR IMAGE COMMAND CRÉÉ ÉTAT PORTS NOMS
80695a822c19 nginx:alpine "\/docker-entrypoint.…" il y a 27 secondes En cours depuis 25 secondes 0.0.0.0:80->80\/tcp reverse-proxy
a1105f1b583d uptimer "python -u .\/app.py" il y a environ une minute En cours depuis environ une 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 [avis] 31#31: signal de processus démarré
$ wget -qSO- http://localhost
HTTP\/1.1 200 OK
Serveur: nginx\/1.19.0
Date: Sam, 22 Août 2020 19:56:24 GMT
Type de contenu: text\/html
Transfert-encodage: chunked
Connexion: keep-alive
<h2>Uptimer v1.0 tourne depuis 104,1 secondes.</h2>Déploiement sans interruption
Nous allons déployer une nouvelle version de l'application (avec un double boost de performance au démarrage) et essayer de la déployer sans interruption.
Techniques intéressantes
echo 'my text' | docker exec -i my-container sh -c 'cat > /my-file.txt'â Ăcrire le textemy textdans le fichier/my-file.txtdans le conteneurmy-container.cat > /my-file.txtâ Ăcrire dans le fichier le contenu de l'entrĂ©e standard/dev/stdin.
Impression
$ sed -i "s/app_version = 1/app_version = 2/" uptimer.py
$ docker build --tag uptimer .
Envoi du contexte de construction au démon Docker 39.94kB
Étape 1/4 : FROM python:alpine
---> 8ecf5a48c789
Étape 2/4 : EXPOSE 8080
---> Utilisation du cache
---> cf92d174c9d3
Étape 3/4 : COPY uptimer.py app.py
---> 3eca6a51cb2d
Étape 4/4 : CMD [ "python", "-u", "./app.py" ]
---> Exécution dans 8f13c6d3d9e7
Suppression du conteneur intermédiaire 8f13c6d3d9e7
---> 1d56897841ec
Construit avec succès 1d56897841ec
Tagué avec succès : uptimer:latest
$ docker run --detach --rm --name uptimer_BLUE --network web-gateway uptimer
96932d4ca97a25b1b42d1b5f0ede993b43f95fac3c064262c5c527e16c119e02
$ docker logs uptimer_BLUE
Uptimer v2.0 (se charge en 5 sec.) démarré.
$ docker run --rm --network web-gateway alpine wget -qO- http://uptimer_BLUE:8080
<h2>Uptimer v2.0 fonctionne depuis 23.9 secondes.</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 [avis] 68#68 : processus du signal démarré
$ wget -qO- http://localhost
<h2>Uptimer v2.0 fonctionne depuis 63.4 secondes.</h2>
$ docker rm -f uptimer
uptimer
$ wget -qO- http://localhost
<h2>Uptimer v2.0 fonctionne depuis 84.8 secondes.</h2>
$ docker ps
ID DU CONTENEUR IMAGE COMMANDE CRÉÉ ÉTAT PORTS NOMS
96932d4ca97a uptimer "python -u ./app.py" Il y a environ une minute Actif Il y a environ une minute 8080/tcp uptimer_BLUE
80695a822c19 nginx:alpine "docker-entrypoint.…" Il y a 8 minutes Actif Il y a 8 minutes 0.0.0.0:80->80/tcp reverse-proxyĂ ce stade, l'image est construite directement sur le serveur, ce qui nĂ©cessite que les sources de l'application soient prĂ©sentes, et surcharge le serveur de travail inutile. L'Ă©tape suivante consistera Ă extraire la construction de l'image sur une machine distincte (par exemple, dans un systĂšme CI) avec un transfert ultĂ©rieur sur le serveur.
Transfert d'images
Malheureusement, il n'est pas judicieux de transfĂ©rer une image de localhost Ă localhost, donc cette section ne peut ĂȘtre explorĂ©e qu'avec deux hĂŽtes Docker Ă portĂ©e de main. Ă un niveau minimal, cela ressemble Ă ceci :
$ 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.9MBCommande docker save sauvegarde les données de l'image dans une archive .tar, c'est-à -dire qu'elle pÚse environ 1,5 fois plus que ce qu'elle pourrait peser en version compressée. Alors, compressons-la au nom de l'économie de temps et de bande passante :
$ docker image save uptimer | gzip | ssh production-server 'zcat | docker image load'
Loaded image: uptimer:latestDe plus, vous pouvez observer le processus de transfert (bien qu'une utilité tierce soit requise pour cela) :
$ docker image save uptimer | gzip | pv | ssh production-server 'zcat | docker image load'
25,7MiB 0:01:01 [ 425KiB/s] [ ]
Loaded image: uptimer:latestConseil : Si vous avez besoin de plusieurs paramĂštres pour vous connecter au serveur via SSH, il est possible que vous n'utilisiez pas le fichier
~/.ssh/config.
Transmission d'une image via docker image save/load â c'est la mĂ©thode la plus minimaliste, mais ce n'est pas la seule. Il en existe d'autres :
- Container Registry (standard de l'industrie).
- Se connecter au démon Docker du serveur depuis un autre hÎte :
- Variable d'environnement
DOCKER_HOST. - ParamĂštre de ligne de commande
-Hou--hostl'outildocker-compose. docker context
- Variable d'environnement
Une seconde mĂ©thode (avec trois variantes de mise en Ćuvre) est bien dĂ©crite dans l'article .
deploy.sh
Maintenant, rassemblons tout ce que nous avons fait manuellement dans un seul script. Commençons par la fonction principale, puis examinons les autres utilisées dans celle-ci.
Techniques intéressantes
${parameter?err_msg}â l'un des sorts de la magie bash (aka ). Siparametern'est pas spĂ©cifiĂ©, affichezerr_msget quittez avec le code 1.docker --log-driver journaldâ par dĂ©faut, le pilote de journalisation de Docker est un fichier texte sans rotation. Avec cette approche, les journaux remplissent rapidement tout le disque, donc pour un environnement de production, il est nĂ©cessaire de changer le pilote pour un plus intelligent.
Script de déploiement
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 "Déploiement de '$NEW' à la place de '$OLD'..."
docker run
--detach
--restart always
--log-driver journald
--name $NEW
--network web-gateway
$image_name || return 3
echo "Conteneur démarré. Vérification de l'état..."
for i in {1..20}
do
sleep 1
if get-service-status $image_name $new_slot
then
echo "Le nouveau service '$NEW' semble OK. Changement de tĂȘte..."
sleep 2 # Assurez-vous que le service est prĂȘt
set-active-slot $image_name $new_slot || return 4
echo "Le service '$NEW' est en ligne!"
sleep 2 # Assurez-vous que toutes les requĂȘtes ont Ă©tĂ© traitĂ©es
echo "ArrĂȘt de '$OLD'..."
docker rm -f $OLD
docker image prune -f
echo "Déploiement réussi!"
return 0
fi
echo "Le nouveau service '$NEW' n'est pas encore prĂȘt. Attente ($i)..."
done
echo "Le nouveau service '$NEW' n'a pas dĂ©marrĂ©, l'arrĂȘt de celui-ci. Ăchec du dĂ©ploiement T_T"
docker rm -f $NEW
return 5
}Fonctions utilisées :
ensure-reverse-proxyâ VĂ©rifie que le reverse proxy fonctionne (utile pour le premier dĂ©ploiement)get-active-slot service_nameâ DĂ©termine quel slot est actuellement actif pour le service donnĂ© (BLUEouGREEN)get-service-status service_name deployment_slotâ DĂ©termine si le service est prĂȘt Ă traiter les requĂȘtes entrantesset-active-slot service_name deployment_slotâ Change la configuration de nginx dans le conteneur reverse proxy
Dans l'ordre :
ensure-reverse-proxy() {
is-container-up reverse-proxy && return 0
echo "Déploiement du 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?"Utilisation : ${FUNCNAME[0]} container_name"}
[ -n "$(docker ps -f name=${container} -q)" ]
return $?
}
get-active-slot() {
local service=${1?"Utilisation : ${FUNCNAME[0]} service_name"}
if is-container-up ${service}_BLUE && is-container-up ${service}_GREEN; then
echo "Collision dĂ©tectĂ©e ! ArrĂȘt de ${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="Utilisation : ${FUNCNAME[0]} service_name deployment_slot"
local service=${1?usage_msg}
local slot=${2?$usage_msg}
case $service in
# Ajoutez ici des chemins de vérification de santé spécifiques pour vos services
*) local health_check_port_path=":8080/" ;;
esac
local health_check_address="http://${service}_${slot}${health_check_port_path}"
echo "Demande de '$health_check_address' dans le réseau docker '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="Utilisation : ${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
}Fonction get-active-slot nécessite quelques explications :
Pourquoi retourne-t-elle un nombre plutĂŽt que d'afficher une chaĂźne ?
Quoi qu'il en soit, dans la fonction appelante, nous vérifions le résultat de son opération, et vérifier le code de sortie avec bash est bien plus simple qu'une chaßne. De plus, obtenir une chaßne à partir de cela est trÚs simple :
get-active-slot service && echo BLUE || echo GREEN.
Les trois conditions suffisent-elles vraiment à distinguer tous les états ?
Deux suffisent mĂȘme, la derniĂšre est juste pour la complĂ©tude, afin de ne pas avoir Ă Ă©crire sinon.
La fonction retournant les configurations nginx reste indĂ©finie : get-nginx-config service_name deployment_slot. Par analogie avec le healthcheck, ici, il est possible de dĂ©finir n'importe quelle configuration pour n'importe quel service. Ce qui est intĂ©ressant â c'est seulement cat <<- EOF, ce qui permet de supprimer tous les onglets en dĂ©but de ligne. Cependant, le prix d'un formatage soignĂ© est le mĂ©lange des onglets avec des espaces, ce qui est aujourd'hui considĂ©rĂ© comme de trĂšs mauvais ton. Mais bash force les onglets, et il serait Ă©galement bon d'avoir un formatage correct dans la config de nginx. En rĂ©sumĂ©, le mĂ©lange des onglets avec des espaces semble vraiment ĂȘtre la meilleure solution parmi les pires. Cependant, dans l'extrait ci-dessous, vous ne le verrez pas, car Habr « fait bien », transformant tous les onglets en 4 espaces et rendant l'EOF invalide. .
Pour ne pas avoir à le répéter, je vais vous parler de
cat << 'EOF', qui apparaßtra plus tard. Si on écrit justecat << EOF, alors à l'intérieur du heredoc, l'interpolation de la chaßne est effectuée (les variables sont révélées ($foo), les appels de commandes ($(bar)) etc.), mais si l'on entoure le symbole de fin de document avec des guillemets simples, alors l'interpolation est désactivée et le symbole$est affiché tel quel. C'est exactement ce qu'il faut pour insérer un script à l'intérieur d'un autre script.
get-nginx-config() {
local usage_msg="Utilisation: ${FUNCNAME[0]} nom_du_service slot_de_deploiement"
local service=${1?$usage_msg}
local slot=${2?$usage_msg}
[ "$slot" == BLUE ] || [ "$slot" == GREEN ] || return 1
local container_name=${service}_${slot}
case $service in
# Ajoutez des configurations nginx spécifiques pour vos services ici
*) nginx-config-simple-service $container_name:8080 ;;
esac
}
nginx-config-simple-service() {
local usage_msg="Utilisation: ${FUNCNAME[0]} proxy_pass"
local proxy_pass=${1?$usage_msg}
cat << EOF
server {
listen 80;
location / {
proxy_pass http://$proxy_pass;
}
}
EOF
}C'est tout le script. Et voici pour le téléchargement via wget ou curl.
Exécution de scripts paramétrés sur un serveur distant
Il est temps d'accéder au serveur cible. Cette fois-ci localhost cela convient parfaitement :
$ ssh-copy-id localhost
/usr/bin/ssh-copy-id: INFO: tentative de connexion avec la ou les nouvelles clé(s), pour filtrer celles qui sont déjà installées
/usr/bin/ssh-copy-id: INFO: 1 clĂ©(s) reste(nt) Ă installer -- si vous ĂȘtes maintenant invitĂ©, c'est pour installer les nouvelles clĂ©s
mot de passe de himura@localhost :
Nombre de clé(s) ajoutée(s) : 1
Essayez maintenant de vous connecter Ă la machine avec : "ssh 'localhost'"
et vĂ©rifiez que seules les clĂ©(s) que vous vouliez ont Ă©tĂ© ajoutĂ©es.Nous avons Ă©crit un script de dĂ©ploiement qui transfĂšre une image prĂ©-construite vers le serveur cible et remplace sans couture le conteneur du service, mais comment l'exĂ©cuter sur une machine distante ? Le script a des arguments, car il est universel et peut dĂ©ployer plusieurs services sous un seul reverse proxy (les configurations nginx permettent de gĂ©rer quel service sera accessible sous quelle URL). Le script ne peut pas ĂȘtre stockĂ© sur le serveur, car dans ce cas, nous ne pourrions pas le mettre Ă jour automatiquement (pour corriger des bugs et ajouter de nouveaux services), et en gĂ©nĂ©ral, l'Ă©tat = mal.
Solution 1 : En fait, stocker le script sur le serveur, mais le copier à chaque fois via scp. Ensuite, se connecter en utilisant ssh et exécuter le script avec les arguments nécessaires.
Inconvénients :
- Deux actions au lieu d'une
- L'endroit oĂč vous copiez peut ne pas exister, ou vous n'y avez pas accĂšs, ou le script peut s'exĂ©cuter au moment du remplacement.
- Il est préférable de nettoyer aprÚs soi (supprimer le script).
- Déjà trois actions.
Solution 2 :
- Dans le script, garder seulement les définitions de fonctions et ne rien exécuter de maniÚre générale
- Avec
sedajouter Ă la fin l'appel de la fonction - Envoyer tout cela directement dans ssh via un pipe (
|)
Avantages :
- Vraiment sans état
- Pas d'entités de boilerplate
- Ăa fait cool
Mais évitons Ansible. Oui, tout a déjà été inventé. Oui, c'est un vélo. Regardez, quel vélo simple, élégant et minimaliste :
$ 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
Connexion Ă localhost...
Super ! La fonction 'deploy' s'exĂ©cute sur 'hut' avec l'argument 'magic-porridge-pot'Cependant, nous ne pouvons pas ĂȘtre certains qu'il y a un bash adĂ©quat sur l'hĂŽte distant, alors ajoutons une petite vĂ©rification au dĂ©but (c'est Ă la place de ):
if [ "$SHELL" != "/bin/bash" ]
then
echo "Le shell '$SHELL' n'est pas supporté par 'deploy.sh'. Définissez un shell '/bin/bash' pour '$USER@$HOSTNAME'."
exit 1
fiEt maintenant, tout est réel :
$ 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
Envoi de l'image gzippée 'uptimer' vers 'localhost' via ssh...
Image chargée : uptimer:latest
Connexion à 'localhost' via ssh pour déployer 'uptimer' de maniÚre transparente...
Déploiement de 'uptimer_VERT' à la place de 'uptimer_BLEU'...
06f5bc70e9c4f930e7b1f826ae2ca2f536023cc01e82c2b97b2c84d68048b18a
Conteneur démarré. Vérification de l'état...
Demande de 'http://uptimer_VERT:8080/' au sein du réseau docker 'web-gateway' :
HTTP/1.0 503 Service Unavailable
wget : le serveur a renvoyé une erreur : HTTP/1.0 503 Service Unavailable
Le nouveau service 'uptimer_VERT' n'est pas encore prĂȘt. Attente (1)...
Demande de 'http://uptimer_VERT:8080/' au sein du réseau docker 'web-gateway' :
HTTP/1.0 503 Service Unavailable
wget : le serveur a renvoyé une erreur : HTTP/1.0 503 Service Unavailable
Le nouveau service 'uptimer_VERT' n'est pas encore prĂȘt. Attente (2)...
Demande de 'http://uptimer_VERT:8080/' au sein du réseau docker 'web-gateway' :
HTTP/1.0 200 OK
Serveur : BaseHTTP/0.6 Python/3.8.3
Date : Sam, 22 Août 2020 20:15:50 GMT
Type de contenu : text/html
Le nouveau service 'uptimer_VERT' semble OK. Changement de tĂȘtes...
nginx : le fichier de configuration /etc/nginx/nginx.conf a une syntaxe correcte
nginx : le test du fichier de configuration /etc/nginx/nginx.conf est réussi
2020/08/22 20:15:54 [notice] 97#97 : signal process started
Le service 'uptimer_VERT' est en direct !
ArrĂȘt de 'uptimer_BLEU'...
uptimer_BLEU
Espace total récupéré : 0B
Déploiement réussi!Vous pouvez maintenant ouvrir dans le navigateur, relancer le déploiement et vous assurer qu'il se passe sans interruption en actualisant la page en continu pendant le déploiement.
N'oublions pas de ranger aprĂšs le travail :3
$ docker rm -f uptimer_VERT reverse-proxy
uptimer_VERT
reverse-proxy
$ docker network rm web-gateway
web-gateway
$ cd ..
$ rm -r blue-green-deploymentSource : habr.com
