Jouer avec Sōzu - 1ers pas

  • Objectif de ce blog post: mettre en place un environnement pour pouvoir “jouer” avec Sōzu
  • Niveau: débutant
  • ce que j’écris n’engage que moi bien sûr 😃
  • le réseau n’est vraiment pas mon domaine 😖
  • Nous verrons (rapidement)
    • comment installer Sōzu
    • comment l’utiliser pour répartir la charge sur plusieurs VMs
    • comment l’utiliser pour déployer une nouvelle version de site Et tout cela de manière transparante mais à la main ✋🤚 pour mieux comprendre

1) Sōzu?

Sōzu est un reverse proxy HTTP développé chez https://www.clever-cloud.com/ qui peut changer sa configuration au runtime, sans re-démarrer. Le proxy expose une chaîne de communication (communication channel in 🇬🇧) et du coup on peut envoyer des commandes à chaud à Sōzu.

Si vous voulez savoir ce qu’est un reverse proxy parce que vous n’y connaissez rien ou presque sur le sujet, je vous conseille cette vidéo Comprendre le Proxy et le Reverse Proxy en 5 minutes par “Cookie connecté”.

Si vous voulez en savoir plus sur Sōzu, je vous conseille la lecture de ce blog post (le comment, le pourquoi de Sōzu): Hot reloading configuration: why and how? par Geoffroy Couprie le papa de Sōzu (à qui je n’ai rien fait relire 😜)

Et le repository GitHub est par ici: https://github.com/sozu-proxy/sozu

✨✨✨ Remerciements pour leurs explications

2) Mise en place du terrain de jeux

  • Nous aurons besoin de plusieurs VMs (virtual machines), j’ai choisi d’utiliser:
  • Il faudra “builder” Sōzu
    • Nous devront donc installer Rust (car, je le rappelle Sōzu est codé en Rust).
    • ⚠️ Sōzu est encore en 🚧 donc certaines choses peuvent changer

Qu’allons nous faire?: héberger un même code source de site web en NodeJS dans plusieurs VMs

Tout d’abord créez une arborescence pour héberger nos expérimentations:

sandbox
   ├── hello-earth-v1   # ici le code source de mon site web
   ├── vms              # le projet Vagrant pour générer et provisionner nos VM
   ├── sozu-demo        # ici nous builderons le projet sozu

A) Le code source du site web

J’ai utilisé ExpressJS. vous allez avoir besoin uniquement de 2 fichiers:

  • index.js
  • package.js
sandbox
   ├── hello-earth-v1   
   │   ├── index.js
   │   └── package.json   
   ├── vms              
   ├── sozu-demo        

index.js

ℹ️ j’utilise la librairie 'project-name-generator' pour générer un nom de machine différent pour chacune des VMs.

const express = require("express");
const bodyParser = require("body-parser");
const generate = require('project-name-generator');

let port = process.env.PORT || 8080;

let app = express();
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: false}))

let machineName = generate({ words: 3, number: true }).dashed

app.disable('etag');

app.get('/', (req, res) => {
  res.send(`
    <!doctype html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <title>WebApp</title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <style>
        .container
        {
          min-height: 100vh;
          display: flex;
          justify-content: center;
          align-items: center;
          text-align: center;
        }
        .title
        {
          font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
          display: block;
          font-weight: 300;
          font-size: 40px;
          color: #35495e;
          letter-spacing: 1px;
        }
        .subtitle
        {
          font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
          font-weight: 300;
          font-size: 30px;
          color: #526488;
          word-spacing: 5px;
          padding-bottom: 15px;
        }
        </style>
      </head>
      <body>
        <section class="container">
          <div>
            <h1 class="title">
              👋 I am ${machineName}
            </h1>
            <h2 class="subtitle">
              Hello 🌍 v1
            </h2>
          </div>
        </section>
      </body>
    </html>
  `)
})

app.listen(port)
console.log(`🌍 ${machineName} is started - listening on `, port)

package.json

{
  "name": "hello-earth",
  "scripts": {
    "start": "node index.js"
  },
  "main": "index.js",
  "dependencies": {
    "body-parser": "^1.17.2",
    "express": "^4.15.3",
    "project-name-generator": "^2.1.3"
  }
}

B) Notre projet Vagrant

Dans le répertoire vms, allez créer un fichier Vagrantfile

sandbox
   ├── hello-earth-v1   
   │   ├── index.js
   │   └── package.json   
   ├── vms   
   │   └── Vagrantfile               
   ├── sozu-demo        

Vagrantfile

Que fait le script?

  • je crée 3 VMs à partie d’une image bento/ubuntu-17.04
  • chaque VM aura une ip fixe et se nomme webappN:
    • webapp1 : 192.168.1.21
    • webapp2 : 192.168.1.22
    • webapp3 : 192.168.1.23
  • j’expose le port 8080 (vous pourrez donc accéder à la webapp par http://192.168.1.2N:8080/)
  • je copie le code source de /hello-earth-v1 dans le répertoire /hello-earth de la vm
  • j’installe NodeJS
  • j’installe les packages nécessaires pour faire fonctionner la webapp (npm install)
BOX_IMAGE = "bento/ubuntu-17.04"
NODE_COUNT = 3

Vagrant.configure(2) do |config|
  config.vm.box = BOX_IMAGE

  (1..NODE_COUNT).each do |i|
    config.vm.define "webapp#{i}" do |node|

      node.vm.network :forwarded_port, guest: 8080, host: 9090 + i
      node.vm.network "public_network", ip: "192.168.1.2#{i}", bridge: "en0: Wi-Fi (AirPort)"

      node.vm.provider "virtualbox" do |node|
        node.memory = 256
        node.cpus = 1
      end
      
      node.vm.synced_folder '.', '/vagrant', disabled: true
      node.vm.provision "file", source: "../hello-earth-v1", destination: "hello-earth"
      
      node.vm.provision :shell, inline: <<-SHELL
        echo "👋 Installing NodeJS..."
        apt-get install curl python-software-properties -y
        curl -sL https://deb.nodesource.com/setup_7.x | sudo bash -
        apt-get install nodejs -y
        cd hello-earth
        npm install
        echo "😜 bye! 👋👋👋"
      SHELL

    end
  end
end

Nous pouvons dès maintenant générer les VMs et les démarrer ainsi que les webapps. ℹ️ Mais, vous n`êtes pas obligés de le faire tout de suite.

Générer (et démarrer les VMs) les VM

C’est tout simple:

cd vms
vagrant up
# ⏳ patientez un peu, il faut tout de même récupérer l'image de l'OS

Démarrer les webapps

Il faut donc démarrer les webapps au sein de chaque VM, comme cela:

vagrant ssh webapp1 -c "cd hello-earth; npm start"&
vagrant ssh webapp2 -c "cd hello-earth; npm start"&
vagrant ssh webapp3 -c "cd hello-earth; npm start"&

Vous devriez obtenir cette sortie:

🌍 delirious-flagrant-expert-3862 is started - listening on  8080
🌍 stimulating-jazzy-spoon-4201 is started - listening on  8080
🌍 knotty-responsible-muscle-3315 is started - listening on  8080

ℹ️ je génère un nom aléatoire pour chaque application pour mieux les reconnaître.

Vous pouvez déjà tester l’accès aux applications:

Maintenant passons aux choses sérieuses 😉

C) Installation de Sōzu

Suivre les étapes suivantes (cela peut être un peu long selon a configuration de votre machine):

# Installer Rust et le tooling associé
curl https://sh.rustup.rs -sSf | sh
# wait... 🤔

# cloner Sōzu
cd sozu-demo
git clone https://github.com/sozu-proxy/sozu

# compiler Sōzu
cd sozu
cd ctl && cargo build; cd ../bin && cargo build
# wait... 🤔

Vous devrier avoir cette arborescence:

sandbox
   ├── hello-earth-v1   
   │   ├── index.js
   │   └── package.json   
   ├── vms   
   │   └── Vagrantfile               
   ├── sozu-demo  
   │   └── sozu       

Dans le répertoire /sozu/demo nous allons créer:

  • un répertoire command_folder
  • un fichier vide state.json
  • un fichier 404.html
  • un fichier 503.html
  • un fichier demo.toml

404.html

HTTP/1.1 404 nope
Cache-Control: no-cache
Content-Type: text/html; charset=UTF-8
Connection: close
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta http-equiv="Pragma" content="no-cache">
    <style>
    .title
    {
      font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
      display: block;
      font-weight: 300;
      font-size: 60px;
      color: #35495e;
      letter-spacing: 1px;
    }
    </style>
  </head>
  <body>
    <h1 class="title">
      😡 Nope❗️
    </h1>
  </body>
</html>

503.html

HTTP/1.1 503 your application is in deployment
Cache-Control: no-cache
Content-Type: text/html; charset=UTF-8
Connection: close
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">

    <style>
    .title
    {
      font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
      display: block;
      font-weight: 300;
      font-size: 60px;
      color: #35495e;
      letter-spacing: 1px;
    }
    </style>
  </head>
  <body>
    <h1 class="title">
      😢 Ouch...
    </h1>
  </body>
</html>

demo.toml

C’est le fichier de configuration utilisé par Sōzu

# sozu proxy simple demo config file

command_socket = "./command_folder/sock"
saved_state    = "./state.json"
log_level      = "info"
log_target     = "stdout"
command_buffer_size = 16384
worker_count = 2
handle_process_affinity = false

# you need this, but currently it`s not used
[metrics]
address = "192.168.59.103"
port = 8125

[http]
address = "127.0.0.1"
max_connections = 10
port = 9090
buffer_size = 16384
answer_404 = "./404.html"
answer_503 = "./503.html"


[applications]

[applications.webapp]
hostname = "localhost"
frontends = [ "HTTP" ] # list of proxy tags
backends  = [ "192.168.1.21:8080", "192.168.1.22:8080", "192.168.1.22:8080" ] # list of IP/port 

Les 2 sections les plus importantes pour notre “POC” sont [http] et [applications.webapp]:

  • avec [http], vous pouvez déduire que vos allez accéder au reverse-proxy via l’ip 127.0.0.1 et le port http 9090
  • avec [applications.webapp], le nom de domaine pour accéder au proxy est "localhost" et vous serez redirigés vers une des machines comprises dans la liste des ip sur un port particulier
  • ℹ️ dans [applications.webapp] c’est vous qui donnez le nom webapp, vous l’appelez comme vous voulez

Maintenant vous devriez avoir cette arborescence:

sandbox
   ├── hello-earth-v1   
   │   ├── index.js
   │   └── package.json   
   ├── vms   
   │   └── Vagrantfile               
   ├── sozu-demo  
   │   ├── 404.html  
   │   ├── 503.html   
   │   ├── demo.toml   
   │   ├── state.json  
   │   ├── command_folder/       
   │   └── sozu/      

3) On joue … Une 1ère fois!

Si vous ne l’avez pas déjà fait, générez vos VM et lancez les webapps

Dans un 1er templ, lancez Sōzu:

cd sozu-demo
./sozu/target/debug/sozu start -c ./demo.toml

A) 1er contact

Maintenant ouvrez 2 navigateurs différents (j’ai fait le test avec Chrome et FireFox) avec l’url http://localhost:9090/ et vous obtiendrez ceci:

Vous pouvez voir que Sōzu vous a redirigé sur des machines différentes (pour une seule et même url)

B) Tuons des VMs

Arrêtons 2 VMs:

# obtenir la liste des VMs
cd vms
vagrant status

# Vous allez obtenir ceci:
Current machine states:

webapp1                   running (virtualbox)
webapp2                   running (virtualbox)
webapp3                   running (virtualbox)

# arrêtons webapp2 et webapp3  ... et supprimons les
vagrant halt webapp2; vagrant destroy webapp2 -f
vagrant halt webapp3; vagrant destroy webapp3 -f

vagrant status
# nouveau status
webapp1                   running (virtualbox)
webapp2                   not created (virtualbox)
webapp3                   not created (virtualbox)

  • Raffraichissez vos navigateurs
  • Il va falloir attendre un peu: Sōzu dispose d’un circuit-breaker qui va tenter pendant un petit moment de joindre les 2 machines “disparues”
  • puis Sōzu va “se rabattre” sur la machine restante

Et vous devriez obtenir ceci (j’ai fait un test supplémentaire avec Safari):

Arrêtons la dernière VM

vagrant halt webapp1; vagrant destroy webapp1 -f
  • Raffraichissez vos navigateurs
  • Attendez un peu… (selon les navigateurs et leur cache, les comportements peuvent être variables)

Et vous devriez obtenir notre favuleuse page 503:

4) On relance les sites

Alors on recrée les VMs une nouvelle fois, on démarre les webapps:

cd vms
vagrant up

Et vous relancez la webapp ExpressJS de la 1ère VM:

vagrant ssh webapp1 -c "cd hello-earth; npm start"

Ensuite nous allons “expliquer” à Sozu que nous avons une nouvelle machine à nouveau disponible en lui passant la commande suivante:

cd sozu-demo
./sozu/target/debug/sozuctl --config ./demo.toml  backend add --id webapp --ip 192.168.1.21 --port 8080
  • Raffraichissez vos navigateurs
  • Attendez un peu…
  • Et 🥁🥁🥁

Et ceci sans redémarrer Sōzu 😍🤗

Relancez les autres webapps:

vagrant ssh webapp2 -c "cd hello-earth; npm start"&
vagrant ssh webapp3 -c "cd hello-earth; npm start"&

Expliquons à Sōzu qu’il y a nouveau des machines supplémentaires:

./sozu/target/debug/sozuctl --config ./demo.toml  backend add --id webapp --ip 192.168.1.22 --port 8080

puis

./sozu/target/debug/sozuctl --config ./demo.toml  backend add --id webapp --ip 192.168.1.23 --port 8080
  • Raffraichissez vos navigateurs
  • Et voilà:

5) On fait une mise à jour de site

J’ai créé une nouvelle version du site dans un répertoire /hello-earth-v2 avec une évolution majeure, j’ai modifié une partie du code html:

<body>
  <section class="container">
    <div>
      <h1 class="title">
        👋 I am ${machineName} I ❤️ 🐼
      </h1>
      <h2 class="subtitle">
        Hello 🌍 v2
      </h2>
    </div>
  </section>
</body>

A) Nouveau projet Vagrant

Je vais donc créer de nouvelles VMs dans un répéertoire /vms-new

sandbox
   ├── hello-earth-v1   
   │   ├── index.js
   │   └── package.json   
   ├── hello-earth-v2  
   │   ├── index.js
   │   └── package.json      
   ├── vms   
   │   └── Vagrantfile   
   ├── vms-new   
   │   └── Vagrantfile                 
   ├── sozu-demo       
   │   └── sozu/      

… Avec un nouveau Vagrantfile

  • chaque VM aura une nouvelle ip fixe et se nomme webapp_newN:
    • webapp1 : 192.168.1.31
    • webapp2 : 192.168.1.32
    • webapp3 : 192.168.1.33
  • je copie le code source de /hello-earth-v2 dans le répertoire /hello-earth de la vm
BOX_IMAGE = "bento/ubuntu-17.04"
NODE_COUNT = 3

Vagrant.configure(2) do |config|
  config.vm.box = BOX_IMAGE

  (1..NODE_COUNT).each do |i|
    config.vm.define "webapp_new#{i}" do |node|

      node.vm.network :forwarded_port, guest: 8080, host: 9100 + i
      node.vm.network "public_network", ip: "192.168.1.3#{i}", bridge: "en0: Wi-Fi (AirPort)"

      node.vm.provider "virtualbox" do |node|
        node.memory = 256
        node.cpus = 1
      end
      
      node.vm.synced_folder '.', '/vagrant', disabled: true
      node.vm.provision "file", source: "../hello-earth-v2", destination: "hello-earth"
      
      node.vm.provision :shell, inline: <<-SHELL
        echo "👋 Installing NodeJS..."
        apt-get install curl python-software-properties -y
        curl -sL https://deb.nodesource.com/setup_7.x | sudo bash -
        apt-get install nodejs -y
        cd hello-earth
        npm install
        echo "😜 bye! 👋👋👋"
      SHELL

    end
  end
end

B) Création, provisionning et lancement

On provisionne donc les nouvelles machines:

vagrant up
# puis
vagrant ssh webapp_new1 -c "cd hello-earth; npm start"
vagrant ssh webapp_new2 -c "cd hello-earth; npm start"
vagrant ssh webapp_new3 -c "cd hello-earth; npm start"

On “👋 notifie” Sōzu de l’arrivée de nouvelles machines

./sozu/target/debug/sozuctl --config ./demo.toml  backend add --id webapp --ip 192.168.1.31 --port 8080
./sozu/target/debug/sozuctl --config ./demo.toml  backend add --id webapp --ip 192.168.1.32 --port 8080
./sozu/target/debug/sozuctl --config ./demo.toml  backend add --id webapp --ip 192.168.1.33 --port 8080

Ensuite on “👋 notifie” Sōzu du départ des anciennes machines (cette fois çi avec le mot-clé remove)

./sozu/target/debug/sozuctl --config ./demo.toml  backend remove --id webapp --ip 192.168.1.21 --port 8080
./sozu/target/debug/sozuctl --config ./demo.toml  backend remove --id webapp --ip 192.168.1.22 --port 8080
./sozu/target/debug/sozuctl --config ./demo.toml  backend remove --id webapp --ip 192.168.1.23 --port 8080
  • Raffraichissez vos navigateurs
  • Attendez un peu…
  • Et ✨🐿 le nouveau site a été déploté sans aucun arrêt relance 🕺

Voilà, c’est tout pour aujourd’hui. Sōzu évolue et de nouvelles fonctionnalités sont à venir. Et de mon côté je pense déjà au prochain tuto Sōzu avec des microservices Vert.x.

blog comments powered by Disqus

Related posts