Blog

  • docker – liens entre container

    Une chose qui m’a posé problème dans l’article sur le load balacing est que en fonction de l’ordre de démarrage des conteneurs, il fallait modifier le fichier de configuration haproxy.cfg pour avoir les bonnes ips. Je me suis donc intéressé aux liens entre conteneurs et j’ai un peu fait évoluer les choses. Tout d’abord, j’ai commencé par donné des noms à mes conteneurs applicatifs:

    docker run --name appA session-webapp/1.0-SNAPSHOT &
    
    docker run --name appB session-webapp/1.0-SNAPSHOT &
    
    

    Je vais donc avoir un conteneur 1 avec un nom appA et un conteneur 2 avec un nom appB (les conteneurs étant les mêmes). J’ai ensuite construit une nouvelle image haproxy en repartant de zéro pour  apporter quelques modifications au script de démarrage. J’ai d’abord redéfini le Dockerfile:

    FROM dockerfile/ubuntu
    
    RUN apt-get update
    RUN apt-get install -y haproxy
    RUN sed -i 's|/dev/log|127.0.0.1|g' /etc/haproxy/haproxy.cfg
    RUN sed -i 's/daemon/#daemon/g' /etc/haproxy/haproxy.cfg
    
    ADD start_haproxy.sh /usr/local/bin/start_haproxy.sh
    
    
    CMD [ "bash" , "/usr/local/bin/start_haproxy.sh"]
    
    EXPOSE 80

    J’installe le paquet haproxy et je modifie quelques éléments de la configuration. Notamment, je désactive le mode daemon. J’ajoute un script sh qui servira de base au lancement de l’image et j’expose le port 80. Le fichier script va construire la configuration au démarrage en fonction des conteneurs qui seront linkés. En regardant la documentation sur les links, on voit que pour chaque lien déclarer, docker va pousser des variables d’environnement dans le conteneur parent.

    Exemple avec la commande

    docker run -i -t --link appA:app1 bcharret/haproxy /bin/bash

    j’obtiens les valeurs suivantes

    [ root@c47b85aaf7e5:~ ]$ env | grep APP1 | sort
    APP1_ENV_CATALINA_HOME=/opt/tomcat
    APP1_ENV_TOMCAT_VERSION=6.0.39
    APP1_NAME=/stupefied_euclid/app1
    APP1_PORT=tcp://172.17.0.4:8080
    APP1_PORT_8080_TCP=tcp://172.17.0.4:8080
    APP1_PORT_8080_TCP_ADDR=172.17.0.4
    APP1_PORT_8080_TCP_PORT=8080
    APP1_PORT_8080_TCP_PROTO=tcp
    APP1_PORT_8081_TCP=tcp://172.17.0.4:8081
    APP1_PORT_8081_TCP_ADDR=172.17.0.4
    APP1_PORT_8081_TCP_PORT=8081
    APP1_PORT_8081_TCP_PROTO=tcp
    
    

    Je peux donc me servir de ces valeurs pour construire ma configuration à la volée. La seule valeur supplémentaire dont j’ai besoin est de connaître le nombre de liens que je vais ajouter. Je vais donc introduire la variable UPSTREAMS

    #!/bin/sh
    
    PIDFILE="/var/run/haproxy.pid"
    UP_COUNT=${UPSTREAMS:-1}
    
    ADD_CONFIG="listen session-webapp :80"$'\n'
    
    HAPROXY="/etc/haproxy"
    CONFIG="haproxy.cfg"
    
    cd "$HAPROXY"
    
    index=0
    while [ $index -lt $UP_COUNT ]; do
    #for index in {1..$UP_COUNT}; do
    let $((index++))
    IP_ADDR=`eval echo '$'APP${index}_PORT_8080_TCP_ADDR`
    ADD_CONFIG="${ADD_CONFIG}"$'\n'"server tomcat${index} ${IP_ADDR}:8080 check"
    done
    
    ADD_CONFIG="${ADD_CONFIG}"$'\n'"stats enable"$'\n'"stats uri /stats"
    
    echo "${ADD_CONFIG}" >> "$CONFIG"
    
    haproxy -f /etc/haproxy/haproxy.cfg -p "$PIDFILE"

    on voit bien que je vais ajouter autant de ligne server… que de $UPSTREAMS en me basant sur les valeurs successives de $APP?_PORT_8080_TCP_ADDR Cela donne avec deux liens:

    listen session-webapp :80
    
    server tomcat1 172.17.0.4:8080 check
    server tomcat2 172.17.0.3:8080 check
    stats enable
    stats uri /stats

    Pour lancer un conteneur avec des liens, on utilise la syntaxe suivante (dans un docker run):

    --link container_name:link_name

    container_name étant le nom défini à la suite de la commande
    –name link_name étant le nom qui sera utilisé dans le conteneur parent

    Ce qui donne par exemple :

    docker run  --name haproxy --link appA:app1 --link appB:app2 --env "UPSTREAMS=2" bcharret/haproxy &

    Il est maintenant plus facile de rajouter des serveurs applicatifs. Il faut lancer un conteneur supplementaire un un nom et le linker avec le haproxy:

    docker run  --name appC session-webapp/1.0-SNAPSHOT &
    
    docker run  --name haproxy --link appA:app1 --link appB:app2 --link appC:app3 --env "UPSTREAMS=3" bcharret/haproxy &

     

    Il ne me reste plus qu’a travailler sur le lien entre les serveur applicatifs et la base redis.

    Comme d’habitude les sources sont ici: https://github.com/BenoitCharret/session-webapp. J’ai ajouté un script start_all.sh et stop_all.sh pour simplifier la mise en route.

  • Partage de session avec redis, tomcat et docker

    Maintenant que nous avons 2 serveurs tomcat, ça serait bien de pouvoir les arrêter un par un (pour effectuer une livraison par exemples) sans que cela n’ait d’impact pour l’utilisateur. Je pense notamment à la gestion de la session utilisateur. Je vais donc essayer de stocker (sérialiser) la session dans une base nosql. Vu qu’il faut juste stocker un couple clé/valeur, c’est beaucoup moins lourd qu’une base de données.

    Préparation du code

    Pour commencer, je vais reprendre le travail de James Coleman sur ce sujet https://github.com/jcoleman/tomcat-redis-session-manager. Tout d’abord comme je ne suis pas très à l’aise avec gradle (pour le moment), j’ai commencé par créer un pom.xml pour gérer les dépendances et plus tard créer une image docker. Comme expliqué dans le préambule du repository, la détection de la modification de la session pose des soucis si on modifie directement l’objet après avoir fait un getAttribute()

    Produit p=(Produit)session.getAttribute("product");
    p.setCount(p.getCount()+1);

    J’ai donc décidé (pour l’exemple) de forcer l’enregistrement de la session à la fin de chaque requête. Il faudra vérifier avec un test de charge que ça ne pose pas de soucis pour une mise en production. Pour utiliser cette libraire, il faut la builder et l’ajouter au répertoire lib de tomcat (ça ne fonctionne pas si la librairie est embarqué dans le projet. Pour faciliter un peu les choses, maven va créer une librairie qui contiendra ses propres dépendances:

    <plugin>
    	<artifactId>maven-assembly-plugin</artifactId>
    	<executions>
    	    <execution>
    		<phase>package</phase>
    		<goals>
    		    <goal>single</goal>
    		</goals>
    	    </execution>
    	</executions>
    	<configuration>
    	    <descriptorRefs>
    		<descriptorRef>jar-with-dependencies</descriptorRef>
    	    </descriptorRefs>
    	</configuration>
    </plugin>

    Ensuite, il suffit juste au niveau du projet de modifier le fichier META-INF/context.xml pour ajouter une valve et un manager:

     <Valve className="com.radiadesign.catalina.session.RedisSessionHandlerValve" />
        <Manager className="com.radiadesign.catalina.session.RedisSessionManager"
                 host="172.17.0.2"
        port="6379"
        database="0"
        maxInactiveInterval="180" />
    

    On peut personnaliser la machine et le port de redis ainsi que la durée d’une session (ici j’ai réglé à 3 min). Maintenant que tout est configuré, voyons comment intégré cela dans docker.

    Préparation des images

    J’ai créé un fichier Dockerfile dans le projet tomcat-redis-session-manager pour créer une nouvelle image se basant sur jolokia/tomcat-6.0 et ajoutant notre librairie dans le répertoire lib.

    FROM jolokia/tomcat-6.0
    
    ENV TOMCAT_VERSION 6.0.39
    
    ADD target/tomcat-redis-session-manager-jar-with-dependencies.jar /opt/apache-tomcat-${TOMCAT_VERSION}/lib/
    

    D’ailleurs, il semble que docker est des soucis avec des répertoires symboliques target. Si je remplace la dernière ligne par

    ADD target/tomcat-redis-session-manager-jar-with-dependencies.jar /opt/tomcat/lib/

    j’obtiens l’erreur suivante (/opt/tomcat étant un lien symbolique vers opt/apache-tomcat-${TOMCAT_VERSION})

    &nbsp;2014/05/25 17:18:25 mkdir /var/lib/docker/aufs/mnt/309ed27293f7af78691fbea762bfb364a3f6dd86fcec8c4a302d16cc91329708/opt/tomcat: file exists

    J’ai ensuite modifier le fichier Dockerfile du projet session-webapp pour se baser sur l’image qu’on vient de créer:

    FROM benoitcharret/tomcat6-session
    

    Il ne reste plus qu’à lancer toutes les images pour voir le résultat

    Lancement des images

    Je commence par lancer redis:

    docker run -d --name redis -p 6379:6379 -v /tmp/data:/data dockerfile/redis

    avec la même commande que dans le dernier article, je récupère son ip (à mettre dans le context.xml) Je lance ensuite un redis-cli pour tester la connectivité

    docker run -d --name redis -p 6379:6379 -v /tmp/data:/data dockerfile/redis
    redis-cli -h 172.17.0.2
    172.17.0.2:6379> ping
    PONG
    
    

    Mon serveur redis est donc opérationnel. Je lance maintenant 2 serveurs tomcat et haproxy. Je me connecte maintenant sur le site, je me loggue et fais des refresh sur la page index.jsp. Cela nous donne dans logs:

    INFO: Server startup in 404 ms
    917E8054D3BC7A4D9E4217136C415248
    917E8054D3BC7A4D9E4217136C415248
    value of product 1
    917E8054D3BC7A4D9E4217136C415248
    value of product 2
    917E8054D3BC7A4D9E4217136C415248
    value of product 3
    917E8054D3BC7A4D9E4217136C415248
    value of product 4
    917E8054D3BC7A4D9E4217136C415248
    value of product 5
    917E8054D3BC7A4D9E4217136C415248
    value of product 6
    917E8054D3BC7A4D9E4217136C415248
    value of product 7
    ^CMay 25, 2014 3:26:33 PM org.apache.coyote.http11.Http11Protocol pause
    INFO: Pausing Coyote HTTP/1.1 on http-8080
    May 25, 2014 3:26:34 PM org.apache.catalina.core.StandardService stop
    INFO: Stopping service Catalina

    Je suis bien resté sur le même serveur à chaque fois et j’ai bien récupéré ma donnée product. Si ne n’avais pas fait la modification dans RedisSessionManager, la valeur de product serait toujours de 1. J’ai ensuite arrêté le serveur tomcat et fais des refresh sur index.jsp. Cela nous donne:

    INFO: Server startup in 392 ms
    917E8054D3BC7A4D9E4217136C415248
    value of product 8
    917E8054D3BC7A4D9E4217136C415248
    value of product 9
    917E8054D3BC7A4D9E4217136C415248
    value of product 10
    917E8054D3BC7A4D9E4217136C415248
    value of product 11
    917E8054D3BC7A4D9E4217136C415248
    value of product 12

    J’ai donc bien récupérer ma session et la valeur de product. Pour voir le contenu des clés de redis, on peut utiliser la commande (a ne pas faire en production):

    KEYS *

    Voila comment tester facilement la persistance de session avec redis et docker.

    Les sources sont ici:

  • load balancing haproxy sous docker

    Après avoir créé une première image contenant une application basique sous tomcat, je vais tester une montée en charge. Pour cela, nous allons lancer 2 containers tomcat et mettre en place un HA proxy (il s’agit d’un load balancer logiciel qui va répartir la charge entre les 2 containers tomcat.

    Je vais utiliser l’image suivante : https://index.docker.io/u/dockerfile/haproxy.

    Il y a juste à surcharger la configuration pour l’adapter à nos besoins:

    global
      log 127.0.0.1 local0
      log 127.0.0.1 local1 notice
      chroot /var/lib/haproxy
      user haproxy
      group haproxy
      # daemon
    
    defaults
      log global
      mode http
      option httplog
      option dontlognull
      contimeout 5000
      clitimeout 50000
      srvtimeout 50000
      errorfile 400 /etc/haproxy/errors/400.http
      errorfile 403 /etc/haproxy/errors/403.http
      errorfile 408 /etc/haproxy/errors/408.http
      errorfile 500 /etc/haproxy/errors/500.http
      errorfile 502 /etc/haproxy/errors/502.http
      errorfile 503 /etc/haproxy/errors/503.http
      errorfile 504 /etc/haproxy/errors/504.http
    
    listen session-webapp :80
        balance roundrobin
        server tomcat1 172.17.0.3:8080 check
        server tomcat2 172.17.0.4:8080 check
        stats enable
        stats uri /stats

    j’ai repris le fichier de l’image et j’ai ajouté la partie en bas. On va donc faire du round robin  entre nos 2 serveurs tomcat qui sont ensuite définis. J’ai gardé la partie stats pour vérifier que tout se passe bien.

    En démarrant un container, on obtient le résultat suivant:

    docker run -i -t  -v /tmp/session-webapp/src/main/haproxy:/haproxy-override dockerfile/haproxy

     

    [WARNING] 143/194932 (12) : Server session-webapp/tomcat1 is DOWN, reason: Layer4 timeout, check duration: 2002ms. 1 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
    [WARNING] 143/194933 (12) : Server session-webapp/tomcat2 is DOWN, reason: Layer4 timeout, check duration: 2002ms. 0 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
    [ALERT] 143/194933 (12) : proxy 'session-webapp' has no server available!
    

    Le serveur ne trouve aucun serveur « backend ». C’est ce que l’on constate aussi en regardant les stats:

    stats1

    En démarrant un container tomcat, on peut voir que les choses s’améliorent:

    docker run -i -t session-webapp/1.0-SNAPSHOT

     

    [WARNING] 143/195256 (12) : Server session-webapp/tomcat1 is UP, reason: Layer4 check passed, check duration: 0ms. 1 active and 0 backup servers online. 0 sessions requeued, 0 total in queue.
    

    stats2

    On a maintenant un premier backend. Et effectivement, en allant sur le site, j’obtiens bien la page voulue

    hello1

    Je lance maintenant le deuxième:

    [WARNING] 143/195615 (12) : Server session-webapp/tomcat2 is UP, reason: Layer4 check passed, check duration: 0ms. 2 active and 0 backup servers online. 0 sessions requeued, 0 total in queue.
    

    stats3

    Nous avons maintenant une infrastructure avec 2 serveur tomcat et un load balancer devant.

    En lancant une commande de type :

    while [ 1 -eq 1 ]; do wget "http://172.17.0.2/session-webapp/"; done

    On voit bien l’alternance des serveurs dans les logs et dans les stats.

    stats4

    Les sources de l’exemple sont disponible ici: git.

  • Contruire une image docker à la suite d’un build maven

    Pour mes premiers pas avec docker, je souhaitais faire une appli web java très simple qui s’intègre dans une image docker au moment du build.

    J’ai trouvé quelques exemples de plugin maven pour construire facilement une image  mais la plupart utilise une api REST en http qui ne fonctionne plus depuis la version 0.56 de docker. Il faut maintenant utiliser une socket Unix.

    J’ai donc utiliser un plugin maven pour lancer la construction de l’image via une ligne de commande (il y a sûrement mieux, mais c’était aussi pour aller vite):

    <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>1.3</version>
    <executions>
    <execution>
    
    <phase>install</phase>
    <goals>
    <goal>exec
    </goal>
    </goals>
    <configuration>
        <executable>docker</executable>
        <arguments>
        <argument>build</argument>
        <argument>-t</argument>
        <argument>${project.artifactId}/${project.version}</argument>
        <argument>.</argument>
    </arguments>
    </configuration>
    </execution>
    </executions>
    </plugin>

    Cette commande va construire l’image à partir d’un fichier DockerFile et va poser un tag composé du nom de l’artefact et de la version. Ce sera plus facile pour retrouver l’image par la suite.

    Le fichier Dockerfile est construit comme ceci:

    FROM jolokia/tomcat-6.0
    
    ADD target/session-webapp.war /maven/
    
    EXPOSE 8080

    On indique à partir de quelle image on se base. Ici c’est un tomcat.

    Puis on ajoute le war dans un répertoire particulier pour que tomcat le charge au démarrage.

    Et enfin, on expose le port 8080 pour pouvoir accéder au tomcat depuis l’extérieur du conteneur.

    Pour ce faire, il faut lancer le container avec la commande:

     docker run "session-webapp/1.0-SNAPSHOT"

    Puis la commande :

     docker ps

    qui va lister les containers en cours d’exécution et indiquer le container id de chacun.

    Et enfin la commande suivante pour récupérer l’adresse du container.

    docker inspect container_id | grep IPAddress

    On peut maintenant vérifier que l’application fonctionne bien dans un navigateur:

    hello world

    Voila pour cette première rencontre avec docker.

    Les sources sont ici : https://github.com/BenoitCharret/session-webapp

     

  • installation de docker sur ubuntu

    L’installation sur une distribution ubuntu 14.04 est assez facile (http://docs.docker.io/installation/ubuntulinux/)

    $ sudo apt-get update
    $ sudo apt-get install docker.io
    $ sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker

    Pour tester l’installation on peut lancer la commande suivante qui va telecharger une image ubuntu et executer la commande /bin/bash

    sudo docker run -i -t ubuntu /bin/bash

     

  • Publier sur un repository distant avec git et une clé ssh

    Voici deux articles pour mettre en place une clé ssh avec git.

    Pour générer sa clé et la tester:

    https://help.github.com/articles/generating-ssh-keys

    Pour que la clé soit prise en compte en ligne de commande:

    http://matharvard.ca/posts/2011/aug/11/git-push-with-specific-ssh-key/

    A bientot

  • Envoyer des mails facilement avec ubuntu

    Un bon tutoriel pour configurer un serveur smtp qui fait du relaying vers un autre serveur smtp (google pour l’exemple).

    https://rtcamp.com/tutorials/linux/ubuntu-postfix-gmail-smtp/

    Très pratique si on ne veut pas s’embeter avec la configuration d’un postfix. On peut ainsi recevoir facilement des mails depuis la ligne de commande vers une boite gmail.

    A bientot

  • Ouverture

    Bienvenue sur ce blog.

    Je vais essayer de présenter au fil des posts les technologies/découvertes que j’ai pu faire durant ma veille technologique autour de Java.

    Pour faire un peu de teasing, voici les sujets (pas forcément par ordre d’importance) que j’ai envie d’aborder sous peu:

    • docker
    • angular JS
    • les futures de java8
    • mongodb

    Je publierais aussi des articles qui me serviront de pense-bête. 🙂

    A bientot