Déploiement Blue-Green simplifié

Dans cet article, nous allons utiliser bash, ssh, docker et nginx la mise en Ɠuvre sans interruption d'une application Web. Le dĂ©ploiement blue-green 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 ».

Déploiement Blue-Green simplifié

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 explainshell (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 (Document ici + Redirection I/O) est une maniĂšre de crĂ©er un fichier multi-lignes en une seule commande. Tout ce que bash lira Ă  partir de /dev/stdin aprĂšs cette ligne et jusqu'Ă  la ligne EOF sera Ă©crit dans file-name.
  • wget -qO- URL (explainshell) — 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.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 &lt; loading_seconds:
                    self.send_error(503)
                else:
                    self.send_response(200)
                    self.send_header(&#039;Content-Type&#039;, &#039;text\/html&#039;)
                    self.end_headers()
                    response = f&#039;<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
 ---&gt; 8ecf5a48c789
Étape 2/4 : EXPOSE 8080
 ---&gt; Utilisation du cache
 ---&gt; cf92d174c9d3
Étape 3/4 : COPY uptimer.py app.py
 ---&gt; a7fbb33d6b7e
Étape 4/4 : CMD [ "python", "-u", ".\/app.py" ]
 ---&gt; Exécution dans 1906b4bd9fdf
Suppression du conteneur intermédiaire 1906b4bd9fdf
 ---&gt; 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-&gt;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
uptimer

Proxy 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. nginx dans en mode proxy inverse. 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 Ă  docker network. 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

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-&gt;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 texte my text dans le fichier /my-file.txt dans le conteneur my-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
 ---&gt; 8ecf5a48c789
Étape 2/4 : EXPOSE 8080
 ---&gt; Utilisation du cache
 ---&gt; cf92d174c9d3
Étape 3/4 : COPY uptimer.py app.py
 ---&gt; 3eca6a51cb2d
Étape 4/4 : CMD [ "python", "-u", "./app.py" ]
 ---&gt; Exécution dans 8f13c6d3d9e7
Suppression du conteneur intermédiaire 8f13c6d3d9e7
 ---&gt; 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 &gt; /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-&gt;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.9MB

Commande 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:latest

De 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:latest

Conseil : 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 :

  1. Container Registry (standard de l'industrie).
  2. Se connecter au démon Docker du serveur depuis un autre hÎte :
    1. Variable d'environnement DOCKER_HOST.
    2. ParamĂštre de ligne de commande -H ou --host l'outil docker-compose.
    3. docker context

Une seconde mĂ©thode (avec trois variantes de mise en Ɠuvre) est bien dĂ©crite dans l'article How to deploy on remote Docker hosts with docker-compose.

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 parameter substitution). Si parameter n'est pas spĂ©cifiĂ©, affichez err_msg et 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Ă© (BLUE ou GREEN)
  • get-service-status service_name deployment_slot — DĂ©termine si le service est prĂȘt Ă  traiter les requĂȘtes entrantes
  • set-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 ?

Déploiement Blue-Green simplifié

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. Ici, c'est Ă©vident.

Pour ne pas avoir à le répéter, je vais vous parler de cat << 'EOF', qui apparaßtra plus tard. Si on écrit juste cat << 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 le gist avec ce script 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 sed ajouter Ă  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_SCRIPT
EOF

$ 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 shellbang):

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
fi

Et 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 http://localhost/ 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-deployment

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster