Étiquette : docker

  • docker sous intellij

    Intellij supporte docker maintenant:

    On peut lancer les containers depuis intellij. On peut aussi exec des commandes directement sur le container (docker exec)

    Capture d’écran 2016-02-25 à 21.21.05

    Il y a aussi un debut de completion dans le Dockerfile.

    Capture d’écran 2016-02-25 à 21.23.11

    Cela va me faciliter la tache pour la mise au point de dockerfile.

  • Netstat sous debian

    Petit pense-bete personnel car cela fait plusieurs fois que je cherche l’info.

    Sous debian, netstat est dans le package net-tools:

    apt-get install net-tools

    Ca peut être utile pour mettre au point des images docker.

     

  • Gestion de session avec un cluster redis

    Voyons comment mettre en pratique les deux articles précédents (ici et ici).

    Les choses n’étant pas encore très évolués du cote de spring-data-redis pour le mode cluster, je suis revenu sur la version de session-webapp utilisant une valve tomcat.

    Les seules modifications apportées sont la suppression de l’image redis (je vais utiliser la version cluster) et l’utilisation d’une nouvelle image docker de base pour les serveurs Tomcat.

    En effet, j’ai modifié la valve pour utiliser la nouvelle classe JedisCluster à la place de Jedis.
    Mais pour que cela fonctionne, j’ai du utilisé la version 3.0.0-SNAPSHOT de jedis. La version releasée actuelle n’implémente pas les commandes binary par la lecture/ecriture.

    J’ai du également modifié l’image docker pour le cluster redis car par default, le cluster est configuré en 127.0.0.1. Mais quand le client java essaye de découvrir la topologie du cluster, il obtient ces ips, et ne peux pas se connecter aux autres noeuds. La modification permet d’établir la connexion sur l’ip du conteneur.

    Il ne reste plus qu’à lancer tout ce petit monde:

    redis:

    /docker-redis-cluster$ make-clean && make-build && make-run

    tomcat & haproxy

    /session-webapp$ ./stop_all.sh ;  mvn clean install && ./start_all.sh

     

    Lorsque je crée une session, je la vois bien sur un noeud redis et sur un autre noeud, je vois bien le message de redirection:

    http://172.17.0.29:8080/session-webapp/login.html?userName=toto

     

    docker-redis-cluster$ redis-cli -p 7000
    127.0.0.1:7000> get 9C58CB03A3E0898CEA70420FA681B626
    "\xac\xed\x00\x05w\b\x00\x00\x01M\xcf\x82%\xc6sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01M\xcf\x82%\xc6sq\x00~\x00\x00\x00\x00\x01M\xcf\x84\xd43sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexq\x00~\x00\x01\x00\x02\xbf sr\x00\x11java.lang.Boolean\xcd r\x80\xd5\x9c\xfa\xee\x02\x00\x01Z\x00\x05valuexp\x00sq\x00~\x00\x06\x01sq\x00~\x00\x00\x00\x00\x01M\xcf\x84\xd43t\x00 9C58CB03A3E0898CEA70420FA681B626sq\x00~\x00\x04\x00\x00\x00\x02t\x00\buserNamet\x00\x06benoitt\x00\aproductsr\x00$com.axioconsulting.test.bean.ProduitW\x02ts6\x14\xa0\xee\x02\x00\x02J\x00\x05countL\x00\x02idt\x00\x12Ljava/lang/String;xp\x00\x00\x00\x00\x00\x00\x00\x0et\x00\x05pates"
    127.0.0.1:7000> 
    docker-redis-cluster$ redis-cli -p 7001
    127.0.0.1:7001> get 9C58CB03A3E0898CEA

     

     

    Les sources sont ici:
    tomcat-session-manager
    webapp
    docker-redis-cluster

     

  • Redis Cluster en java

    En commençant à regarder le mode cluster de redis à travers une image docker déjà construire https://github.com/Grokzen/docker-redis-cluster, je me suis aperçu que ce n’est pas si transparent. En effaçant, si on essaye d’ajouter une valeur mais que la clé ne correspond au bon noeud, redis ne renvoi pas OK mais plutôt l’indication de ou insérer la donnée:

    $ redis-cli -p 7000
    127.0.0.1:7000> set toto foo
    (error) MOVED 9738 127.0.0.1:7001

    Je me suis demandé comment gérer cela dans un programme Java. Est-ce le driver qui va faire le travail ou il faut le gérer à la main.

    Si on utilise le driver standard de Jedis, voici ce qu’on obtiens:

    Jedis jedis=new Jedis("localhost",7000);
    jedis.connect();
    jedis.set("toto","bar");
    String value=jedis.get("toto");
    System.out.println("res - "+value);
    Exception in thread "main" redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 9738 127.0.0.1:7001
    at redis.clients.jedis.Protocol.processError(Protocol.java:108)
    at redis.clients.jedis.Protocol.process(Protocol.java:142)
    at redis.clients.jedis.Protocol.read(Protocol.java:196)
    at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:288)
    at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:187)
    at redis.clients.jedis.Jedis.set(Jedis.java:66)
    at com.axioconsulting.redis.TestCluster.main(TestCluster.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

    Heureusement, Jedis propose une classe pour résoudre ce problème (Je n’ai rien trouvé coté JRedis).

    Set jedisClusterNodes=new HashSet(); jedisClusterNodes.add(new HostAndPort("localhost",7000)); 
    jc.set("toto","bar");
    String value=jc.get("toto");
    System.out.println("res - "+value);
    res - bar

     

    Le driver est même capable de récupérer les autres noeuds. Dans l’exemple j’ai indiqué un seul noeud, mais avec le code suivant :

    for (Map.Entry<string,jedispool> entry:jc.getClusterNodes().entrySet()){
       System.out.println(entry.getKey());
    }

    on voit bien que le driver a pu récupérer les autres noeuds:

    localhost:7000
    127.0.0.1:7004
    127.0.0.1:7005
    127.0.0.1:7002
    127.0.0.1:7003
    127.0.0.1:7000
    127.0.0.1:7001
    
    

    les sources sont ici

    Quelques liens utiles:
    – commandes redis cluster

    – À la découverte de Redis Cluster 3.0

     

  • Docker compose

    En assistant à une présentation à Devoxx France, j’ai pû voir docker-compose en action.
    J’étais passé à côté mais docker-compose permet de faire ce que j’ai fait de façon artisanale dans les scripts startAll.sh et stopAll.sh de mon application demo (session-webapp).

    J’ai donc mis à jour les sources pour tester cette fonctionnalité.

    Il faut dans un premier temps définir un fichier docker-compose.yml qui va décrire nos services.

    haproxy:
      build: src/main/haproxy
      links:
       - app1
       - app2
      environment:
        - UPSTREAMS=2
      ports:
        - "2080:80"
    
    app2:
     build: .
     links:
        - db
    
    app1:
     build: .
     links:
      - db
    
    
    db:
      image: dockerfile/redis
      volumes:
      - /tmp/data:/data

    Pour faire simple, je définis une image haproxy, 2 webapps et 1 backend. Et j’indique les liens entre container (links).

    Pour lancer, tous les containers, il faut juste taper:

    docker-compose up

    Cette commande va builder et lancer les containers qui ne sont pas déjà lancé.

    Pour arrêter, il faut taper:

    docker-compose stop

    Le service va démarrer ou arrêter d’un seul coup tous les services décrit dans le fichier.yml. Cela peut être pratique dans une architecture fortement distribuée.
    Il va aussi exposer automatiquement les ports entre containers qui sont dépendant les uns des autres.

    Une option intéressante de docker-compose est la fonction scale qui permet de provisionner facilement une application. Je reviendrais sur cette fonctionnalité sans un prochain article.

    Les sources de l’exemple sont ici.

  • Envoi de mail en developpement

    Au détour d’un article sur linux pratique (sur l’installation d’un forum discourse), je suis tombé sur un container docker permettant de tester localement l’envoi de mail. Cela peut être utile en développement pour que les mails ne partent pas aux clients finaux ou parce qu’il y a des restrictions sur les serveurs smtps.

    L’utilisation est plutôt simple. Il faut démarrer le container et exporter les 8080 et 25 du container.
    Le port 25 est pour l’envoi smtp et le 8080 est l’interface web de consultation des emails.

    docker run --name mailc -p 8080:80 -p 25:25 -d simpledrupalcloud/mailcatcher:dev

    Il faut récupérer l’ip du container:

    $ docker inspect mailc| grep IPAddress
    "IPAddress": "172.17.0.2",

    On peut déjà essayer l’envoi de mail via un telnet:

    telnet 172.17.0.2 25
    Trying 172.17.0.2...
    Connected to 172.17.0.2.
    Escape character is '^]'.
    220 EventMachine SMTP Server
    HELO
    250 Ok EventMachine SMTP Server
    MAIL FROM: test@from.com
    250 Ok
    RCPT TO:test@to.com
    250 Ok
    DATA
    354 Send it
    this is data
    
    
    Send it
    
    .
    250 Message accepted

    On peut ensuite accéder à l’interface web pour vérifier si le mail est bien reçu (http://172.17.0.2 dans mon cas):

    mailcatcher

    Et voila, il est maintenant possible de tester l’envoi de mail dans ses programmes de façon autonome.

  • Partage de session avec spring-session

    Un collègue m’a fait découvrir spring-session lors d’un échange sur la façon de stocker les sessions dans un tiers.

    J’ai donc décidé de reprendre mon poc pour l’adapter à cette librairie.

    La mise en place est assez simple. Il faut juste créer 2 classes.

    1 pour la configuration (Config) et 1 pour que spring initialise mon application (Initializer). Dans une application existante la dernière classe n’est pas obligatoire. Il suffira de déclarer le bean config dans le fichier de context XML spring.

    • configuration:
    @Configuration
    @EnableRedisHttpSession
    public class Config {
    
          @Bean
          public JedisConnectionFactory connectionFactory() {
                JedisConnectionFactory factory = new JedisConnectionFactory();
                factory.setHostName("db");
                return factory;
          }
    }

    Il suffit de placer l’annotation EnableRedisHttpSession et de déclarer un bean pour la connectionFactory. Par defaut JedisConnectionFactory se connecte sur la base redis en localhost. Dans mon cas, j’ai surchargé pour aller sur le host db (container redis)

    Il faut supprimer aussi le fichier context.xml qui n’est plus nécessaire.
    Il faut passer en servlet 3.0 pour que l’initialisation fonctionne.

    et voilà, c’est tout.

    On peut tester le tout avec les commandes:

    mvn clean install
    ./start_all.sh

     

    • Changements:

    Par rapport au précédent article, le poc a subi quelques modifications pour fonctionner:

    – passage à tomcat 8
    – inclusion de spring 4
    – inclusion de spring web
    – inclusion de spring-data
    – servlet 3.0

    Les sources sont disponibles ici

    Merci Raphaël pour l’info.

     

  • docker – liens entre container suite

    Maintenant que nous avons la bonne version de docker, nous pouvons modifier le fichier context.xml de l’application pour pointer sur le hostname de redis :

    className="com.radiadesign.catalina.session.RedisSessionManager"
    host="db"
    port="6379"

    On va aussi modifier le démarrage du serveur d’applications pour créer un lien vers redis:

    docker run -i -t --link redis:db session-webapp:1.0-SNAPSHOT

    tomcat et redis sont maintenant connectée sans avoir besoin de connaître l’ip de chacun.

    Et voici le script de démarrage complet :

    docker run --rm --name redis -p 6379:6379 -v /tmp/data:/data dockerfile/redis &
    
    sleep 5
    
    docker run --rm --name appA --link redis:db session-webapp:1.0-SNAPSHOT &
    
    docker run --rm --name appB --link redis:db session-webapp:1.0-SNAPSHOT &
    
    sleep 10
    
    docker run --rm --name haproxy --link appA:app1 --link appB:app2 --env "UPSTREAMS=2" bcharret/haproxy &

    Nous avons donc réussi à déployer des applications qui sont connectées entre elles, sans connaître les ips de chacune à l’avance. En termes de scalabilité, il devient aisé de rajouter un serveur d’application. Il faut juste démarrer un nouveau conteneur et le linker au haproxy.

    Dans un prochain article, nous essayerons de voir comment faire fonctionner un cluster de redis avec docker.

    Les sources sont à jour ici: https://github.com/BenoitCharret/session-webapp/tree/article_3

  • Mettre à jour docker sous ubuntu

    Pour faire suite au précédent article, je voulais utiliser la fonctionnalité du link qui modifie le /etc/hosts du conteneur parent pour ajouter le nom et l’ip du conteneur fils. Mais cette fonctionnalité est arrivée avec docker 0.11.

    Si vous êtes comme moi sous ubuntu 14.04, mon premier réflmexe a été comme indiqué dans la documentation, d’installer docker à partir d’un des dépôts d’ubuntu:

    $ sudo apt-get update
    $ sudo apt-get install docker.io 
    $ sudo apt-cache madison docker.io
     docker.io | 0.9.1~dfsg1-2 | http://fr.archive.ubuntu.com/ubuntu/ trusty/universe amd64 Packages
     docker.io | 0.9.1~dfsg1-2 | http://fr.archive.ubuntu.com/ubuntu/ trusty/universe Sources

    Or dans ce dépôt la version ne suit pas les différentes releases du projet.

    Si vous voulez mettre à jour docker dans une version plus récente (1.0.0 aujourd’hui), voila ce qu’il faut faire:

    sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
    sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\ > /etc/apt/sources.list.d/docker.list"
    $ sudo apt-get update
    $ sudo apt-get install lxc-docker

    Ce qui nous donne une version :

    $ sudo apt-cache madison lxc-docker
    lxc-docker | 1.0.0 | http://get.docker.io/ubuntu/ docker/main amd64 Packages

    On peut donc maintenant vérifier que la fonctionnalité est bien présente:

    pc:~/session-webapp$ ./run_redis-server.sh 
    85c5a0a51fe3df1dcb678b6bccbee7c9bc8db54232c100be03e1b4aa22e49050
    pc:~/session-webapp$ docker run -i -t --link redis:redis dockerfile/ubuntu /bin/bash
    [ root@c9cee8eddad7:~ ]$ more /etc/hosts
    172.17.0.3 c9cee8eddad7
    127.0.0.1 localhost
    ::1 localhost ip6-localhost ip6-loopback
    fe00::0 ip6-localnet
    ff00::0 ip6-mcastprefix
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    172.17.0.2 redis

    J’ai lancé le serveur redis et l’ai linké à un conteneur de test. On voit bien que dans le /etc/hosts, l’ip du conteneur redis est ajouté. Dans le prochain article, nous allons donc pouvoir lié la datasource et les serveurs applicatifs sans avoir a connaitre à l’avance l’ip de la datasource.

     

  • 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.