Website Logo. Upload to /source/logo.png ; disable in /source/_includes/logo.html

Zuzur’s wobleg

technology, Internet, and a bit of this and that (with some dyslexia inside)

Rancher: Déploiement Du Premier Service

| Comments

L’objectif de cet article est de vous montrer comment fabriquer un conteneur simple - un serveur HTTP pour des fichiers statiques, puis de le déployer sur le cluster rancher mis en place lors des précédents.

Fabrication du conteneur

On va partir d’un site octopress (un moteur de blog statique - celui avec lequel est fabriqué ce que vous lisez), et monter un conteneur avec les fichiers générés par Octopress.

J’ai créé un projet sur git.arzur.net que vous pouvez cloner. Pour démarrer, il vous faut un interpréteur ruby (à partir de la verson 1.9.4). J’utilise rbenv sur ma machine pour avoir plusieurs versions et environnements - brew install rbenv. Vous pouvez donc changer les fichiers .ruby-version et .ruby-gemset pour les adapter à votre environnements - utiliser rbenv et les gemset est une bonne idée, car pour supprimer toutes les gems installés, il vous suffit de lancer rbenv gemset delete 2.4.1 octopress-blog, c’est pratique une fois qu’on a fini d’expérimenter…

On commence par installer octopress

1
2
3
4
$ git clone https://git.arzur.net/erwan/rancher-blog
$ cd rancher-blog
$ gem install bundler
$ bundle install

puis on génère le site…

1
$ bundle exec rake generate

ça crée un répértoire public/ avec les articles de votre blog. J’en ai ajouté un en créant le projet, pour que le blog ne soit pas complètement vide.

On peut passer à la fabrication du conteneur. J’ai ajouté un Dockerfile, on ne peut plus simple:

1
2
3
4
FROM nginx:alpine
MAINTAINER Erwan Arzur <erwan@arzur.net>

COPY public/ /usr/share/nginx/html

On copie les fichiers de public/ dans /usr/share/nginx/html de l’image de base nginx:alpine, et c’est tout !

1
2
3
4
5
6
7
8
9
10
11
$ docker build -t rancher-blog .
Sending build context to Docker daemon 4.804 MB
Step 1/3 : FROM nginx:alpine
---> 7ebd6770d0d6
Step 2/3 : MAINTAINER Erwan Arzur <erwan@arzur.net>
---> Using cache
---> 5afa7592c1b8
Step 3/3 : ADD public/ /usr/share/nginx/html
---> 4462300117a1
Removing intermediate container 3aa86c32807f
Successfully built 4462300117a1```

Vous devriez avoir maintenant dans votre docker une image nommée rancher-blog:

1
2
3
4
$ docker images
REPOSITORY                                                                            TAG                 IMAGE ID            CREATED              SIZE
rancher-blog                                                                          latest              4462300117a1        About a minute ago   15.8 MB
...

vous pouvez lancer le conteneur sur votre machine locale:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ docker run -d --name rancher-blog -p 8080:80 rancher-blog
60b2e7817e9e97576315c7483febd34b1bdc4626021c120a5fab255f2ab5f417
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
60b2e7817e9e        rancher-blog        "nginx -g 'daemon ..."   14 seconds ago      Up 13 seconds       0.0.0.0:8080->80/tcp   rancher-blog
$ curl localhost:8080

<!DOCTYPE html>
<!--[if IEMobile 7 ]><html class="no-js iem7"><![endif]-->
<!--[if lt IE 9]><html class="no-js lte-ie8"><![endif]-->
<!--[if (gt IE 8)|(gt IEMobile 7)|!(IEMobile)|!(IE)]><!--><html class="no-js" lang="en"><!--<![endif]-->
<head>
  <meta charset="utf-8">
  <title>My Octopress Blog</title>
  <meta name="author" content="Your Name">


  <meta name="description" content="Welcome ! this is our first post !
">
...

Dans un navigateur, ça donne ça:

avec ça, le conteneur avec votre mini-blog est prêt sur le docker de votre poste de travail, mais il n’est pas encore accessible depuis votre serveur rancher, qui ne saurait tirer l’image que vous venez de créer depuis votre machine. Il va falloir la publier sur le Docker Hub

le docker hub

Ce service est un répertoire d’images publiques dans lequel chacun peut pousser ses images. Il est totalement gratuit pour les images publiques (que tout le monde peut importer) et payant quand on veut pousser des images privées.

j’ai poussé mon conteneur comme suit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: zuzur
Password:
Login Succeeded
$ docker tag rancher-blog zuzur/rancher-blog
$ docker push zuzur/rancher-blog
The push refers to a repository [docker.io/zuzur/rancher-blog]
719e34ff0905: Pushed
4ad6ee83f9e7: Mounted from library/nginx
e71a0b8e5627: Mounted from library/nginx
a5a93198a95b: Mounted from library/nginx
ff143e5bce0a: Mounted from library/nginx
latest: digest: sha256:589e88ad84a8bc8b6e00096d43944e6c257b46007ad0eeef87def7736b9f3a17 size: 1363
$

On a maintenant publié notre conteneur sur le Hub et notre serveur rancher va pouvoir le télécharger et l’executer.

Première stack rancher

on commence par s’assurer qu’on a bien les paramètres dans l’environnement pour se connecter à notre serveur rancher en sourçant le petit script qui contient l’adresse du serveur et les clés. On lance à nouveau rancher ps, pour être sûr.

dans la branche rancher-stack (git checkout rancher-stack) du projet, j’ai ajouté les fichiers docker-compose.yml et rancher-compose.yml suivants pour définir la stack rancher-blog (attention: rancher et rancher-compose prennent le nom du répertoire courant comme nom de la stack si vous ne le précisez pas avec l’option -p):

docker-compose.yml

1
2
3
4
5
6
7
8
9
version: '2'
services:
  blog:
    image: zuzur/rancher-blog:latest
    ports:
    - 80:80
    labels:
      io.rancher.container.pull_image: always
      role: blog
  • il va tirer l’image publique zuzur/rancher-blog (bien sûr, vous devez changer ce nom pour utiliser votre propre image !)
  • le conteneur expose le port 80 - si vous avez déjà un serveur HTTP en place, ça ne va pas fonctionner.
  • les labels permettent d’ajouter des méta-données pour le serveur rancher. Ici, par exemple, on demande au serveur de toujours rafraîchir (pull) l’image quand il démarre le conteneur. Le label role est juste informatif pour le moment, mais on verra plus tard à quoi il sert.

on peut d’hors et déjà lancer le service avec rancher:

1
2
3
4
5
$ rancher up -d blog
INFO[0000] [blog]: Creating
INFO[0000] [blog]: Created
INFO[0000] [blog]: Starting
INFO[0003] [blog]: Started

-d pour détacher le terminal après la sortie, sinon la commande affiche le log de tous les services dans la stack, et reste jusqu’à ce que vous l’arrêtiez.

si on envoie un navigateur sur l’adresse de notre serveur, c’est magique…

rancher-compose.yml

Pour un service aussi simple, on aurait pu complètement se passer de cette configuration, mais j’ai choisi d’en mettre un pour illustrer un truc important avec rancher, la notion de healthcheck

1
2
3
4
5
6
7
8
9
10
11
12
version: '2'
services:
  blog:
    health_check:
      healthy_threshold: 2
      response_timeout: 1000
      port: 80
      unhealthy_threshold: 3
      initialization_timeout: 1000
      interval: 10000
      strategy: recreate
      request_line: GET / HTTP/1.0

Avec cette configuration, le service healthcheck de rancher va essayer toutes les 10 secondes (interval) de se connecter au port 80 (port) du conteneur, et au bout de 3 essais infructueux (unhealthy_threshold), va supprimer le conteneur et en créer un à partir de l’image (strategy). Pour tester il tente la requête HTTP GET / HTTP/1.0 (request_line), laisse 1 seconde au conteneur pour démarrer (initialization_timeout) et le déclare en bonne santé après 2 essais fructueux (healthy_threshold)1.

Le problème, là, c’est que on voulait déployer un autre service, sur le port 80, on serait bien embêté parce qu’on ne pourrait pas le faire. Notre conteneur avec port 80 exposé par docker empêcherait un autre service d’écouter sur le même…

La solution est d’utiliser un conteneur, développé spécialement par rancher qui fournit un service de répartition de charge (load-balancer) pour les services déployés dans le cluster.

Load balancer

Dans la branche rancher-stack-haproxy du projet (git checkout rancher-stack-haproxy), j’ai poussé une nouvelle version des fichiers docker-compoose.yml et rancher-compose.yml avec le contenu suivant:

  • docker-compose.yml:
    • le port 80 du conteneur n’est plus exposé à l’extérieur, il ne sera accessible que par un service qui fonctionne dans le cluster rancher.
    • on déploie un nouveau service, basé sur l’image rancher/lb-service-haproxy:v0.7.5
    • ce service écoute sur le port 80
    • comme le service haproxy a besoin d’interagir avec l’API (pour lire les méta-données, retrouver les informations sur les healthchecks, etc…), il faut faire en sorte que l’agent pousse les clés afin de s’y connecter dans l’environnement au moment du démarrage du conteneur - c’est le but des étiquettes io.rancher.container.agent.role et io.rancher.container.create_agent 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '2'
services:
  blog:
    image: zuzur/rancher-blog:latest
    labels:
      io.rancher.container.pull_image: always
      role: blog
  load-balancer:
    image: rancher/lb-service-haproxy:v0.7.5
    ports:
    - 80:80/tcp
    labels:
      io.rancher.container.agent.role: environmentAdmin
      io.rancher.container.create_agent: 'true'
  • rancher-compose.yml
    • on a ajouté des règles pour le service load-balancer pour connecter notre service rancher-blog
    • on spécifie le port source et le port destination
    • hostname sert à router correctement - le load-balancer de rancher support l’hébergement de serveurs virtuels basé sur leur nom
    • et enfin, on branche cette règle au conteneur rancher-blog dans la même stack (service:) - on peut le brancher à un service dans une autre stack avec la notation <stack>/<service>, par exemple dans notre cas: rancher-blog/rancher-blog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: '2'
services:
  blog:
    health_check:
      healthy_threshold: 2
      response_timeout: 1000
      port: 80
      unhealthy_threshold: 3
      initialization_timeout: 1000
      interval: 10000
      strategy: recreate
      request_line: GET / HTTP/1.0
  load-balancer:
    start_on_create: true
    lb_config:
      port_rules:
      - source_port: 80
        target_port: 80
        hostname: arzur.net
        service: rancher-blog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ rancher-compose rm
INFO[0000] [0/2] [blog]: Deleting
INFO[0000] [0/2] [blog]: Deleted
$ rancher-compose up
INFO[0000] [0/2] [blog]: Creating
INFO[0000] Creating service blog
INFO[0000] [0/2] [blog]: Created
INFO[0000] [0/2] [load-balancer]: Creating
INFO[0000] Creating service load-balancer
INFO[0001] [0/2] [load-balancer]: Created
INFO[0001] [0/2] [blog]: Starting
INFO[0004] [1/2] [blog]: Started
INFO[0004] [1/2] [load-balancer]: Starting
INFO[0007] [2/2] [load-balancer]: Started
rancher-blog-blog-1 | 2017-06-18T16:14:29.681674463Z 10.42.174.5 - - [18/Jun/2017:16:14:29 +0000] "GET / HTTP/1.0" 200 0 "-" "-" "-"
rancher-blog-blog-1 | 2017-06-18T16:14:33.412793351Z 10.42.63.227 - - [18/Jun/2017:16:14:33 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 2017-06-18T16:14:39.684410119Z 10.42.174.5 - - [18/Jun/2017:16:14:39 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 2017-06-18T16:14:43.414156679Z 10.42.63.227 - - [18/Jun/2017:16:14:43 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 2017-06-18T16:14:49.687258855Z 10.42.174.5 - - [18/Jun/2017:16:14:49 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 2017-06-18T16:14:53.415826527Z 10.42.63.227 - - [18/Jun/2017:16:14:53 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
  • on commence par supprimer l’ancienne stack - on n’est pas obligé de le faire mais si on ne le fait pas, rancher-compose râle car les fichiers ne sont pas synchronisés. On peut alors lui demander plutôt de mettre à jour le service (option -u)3
  • après la création de notre stack, on voit que des accès HTTP sont régulièrement faits dans le conteneur. Exactement 2 espacés de 10s
    • le service healthcheck de rancher vérifie toutes les 10s que notre mini-blog répond. Sans réponse, il arrête le conteneur, le supprime et en redémarre un nouveau.
    • le conteneur load-balancer lui aussi vérifie la santé du backend. Si il ne répond pas, il cesse d’envoyer des requêtes au conteneur (comme on n’en a qu’un, on aurait dans ce cas des erreurs 502 - Bad Gateway renvoyées aux clients)

répartition de la charge

Si notre service a besoin de beaucoup de resources - on peut demander très facilement à rancher de lancer plusieurs conteneurs “blog” et le load-balancer va répartir automatiquement les requêtes entre les conteneurs.

1
2
3
$ rancher-compose scale blog=3
INFO[0000] Setting scale blog=3...
$

on a donc démarré 3 conteneurs pour notre blog. on peut le vérifier

1
2
3
4
5
$ rancher ps -c
1i1002    rancher-blog-blog-1                zuzur/rancher-blog:latest                                  healthy   1h2       10.42.27.235    19fc55ed4920
1i1003    rancher-blog-load-balancer-1       rancher/lb-service-haproxy:v0.7.5                          healthy   1h2       10.42.174.5     b83167c6e7c9
1i1004    rancher-blog-blog-2                zuzur/rancher-blog:latest                                  healthy   1h2       10.42.170.222   493755a6f20d
1i1005    rancher-blog-blog-3                zuzur/rancher-blog:latest                                  healthy   1h2       10.42.229.184   b287ea28f2b4

On voit bien nos 3 conteneurs en bonne santé (vérifiés par healthcheck). Si on vérifie les logs du service blog, on va voir tout d’un coup beaucoup plus d’accès:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ rancher logs -f rancher-blog/blog
rancher-blog-blog-1 | 10.42.174.5 - - [18/Jun/2017:16:30:14 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.63.227 - - [18/Jun/2017:16:30:21 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.174.5 - - [18/Jun/2017:16:30:24 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.63.227 - - [18/Jun/2017:16:30:31 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.174.5 - - [18/Jun/2017:16:30:34 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.63.227 - - [18/Jun/2017:16:30:41 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-2 | 10.42.63.227 - - [18/Jun/2017:16:30:42 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-3 | 10.42.63.227 - - [18/Jun/2017:16:30:43 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.174.5 - - [18/Jun/2017:16:30:44 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-3 | 10.42.174.5 - - [18/Jun/2017:16:30:47 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-2 | 10.42.174.5 - - [18/Jun/2017:16:30:50 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.63.227 - - [18/Jun/2017:16:30:51 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-2 | 10.42.63.227 - - [18/Jun/2017:16:30:52 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-3 | 10.42.63.227 - - [18/Jun/2017:16:30:53 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.174.5 - - [18/Jun/2017:16:30:54 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-3 | 10.42.174.5 - - [18/Jun/2017:16:30:57 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-2 | 10.42.174.5 - - [18/Jun/2017:16:31:00 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.63.227 - - [18/Jun/2017:16:31:01 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-2 | 10.42.63.227 - - [18/Jun/2017:16:31:02 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-3 | 10.42.63.227 - - [18/Jun/2017:16:31:03 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
rancher-blog-blog-1 | 10.42.174.5 - - [18/Jun/2017:16:31:04 +0000] "GET / HTTP/1.0" 200 3897 "-" "-" "-"
...

des accès plus fréquents, c’est normal puisque healthcheck et haproxy on plus de services à surveiller…

remarques

utilisation de l’interface graphique

Dans cet article, vous noterez qu’à aucun moment je n’ai mentionné l’interface d’administration du cluster rancher. On peut réaliser toutes ces opérations avec cette UI, évidemment, mais toutes ces actions ne sont alors pas répercutées dans les fichiers de configuration de la stack, et on ne peut pas en garder trace. Donc, j’ai plutôt tendance à éviter de l’utiliser, sauf pour des manipulations “one shot” que je sais répercuter facilement dans les fichiers correspondants. Le nombre de conteneurs pour un service, par exemple… Je vous invite à manipuler un peu, à voir les effets de vos commandes (upgrades, rollback, etc… dans l’interface)

système de publication

Une autre remarque d’ordre général. Pousser un nouvel article avec ce système est plutôt fastidieux.

Il faut:

  • écrire un nouvel article en markdown (bundle exec rake new_post['my new post'])
  • appeler bundler exec rake generate dans le répertoire du blog
  • construire la nouvelle version du conteneur (docker build -t rancher-blog .)
  • tagger l’image générée avec zuzur/rancher-blog:latest
  • la pousser sur le docker hub
  • mettre à jour le service rancher-blog/blog dans rancher pour qu’il utilise la nouvelle image

Quand je parlais de “canon à mouches”… C’est bien trop compliqué. Et automatiser tout cela sera l’objet d’un futur article…


  1. Si vous connaissez les healthcheck de haproxy, c’est exactement le même principe et c’est normal, puisque le service healthcheck, c’est haproxy, on peut le voir en s’y connectant: rancher exec -it healthcheck/healtcheck /bin/sh. On voit que c’est bien haproxy qui fonctionne (avec ps ax), et on peut retrouver le healthcheck en question dans /etc/haproxy/haproxy.cfg

  2. voir la documentation à ce sujet

  3. Dans ce cas, il va lancer un nouveau conteneur, avec la nouvelle configuration, et arrêter l’ancien. Si vous vous apercevez qu’il y a un soucis avec cette config, on peut faire un ‘rollback’ (option -r) et dans ce cas, il va arrêter et supprimer le nouveau conteneur, et redémarrer l’ancien qu’il avait simplement arrêté. C’est parfait pour faire des upgrades de service sans interruption. Il y a de nombreuses options documentées que je vous invite à retrouver dans la doc…

Comments