CérénIT

Le blog tech de Nicolas Steinmetz (Time Series, IoT, Web, Ops, Data)

Web, Ops & Data - Janvier 2017

docker arm hypriot api rest raml python csp kubernetes spark kafka stream rancher json ansible devops elasticsearch postgres timezone pip virtualenv sql service worker react foundation

Nouvelle année, nouveau format - au programme une édition mensuelle mixant brèves et des choses plus construites/élaborées (j’espère le mois prochain)

En Bref

API

ARM / RPi

  • Setup Kubernetes on a Raspberry Pi Cluster easily the official way! : Kubernetes, la solution d’orchestration de conteneurs, devient de plus en plus utilisable sur un enrionnement ARM (Raspberry, etc). Il faut que je réessaie ça sur mon Picocluster ; les derniers essais n’étaient pas très probant mais je n’avais pas utilisé apparemment le bon driver réseau (ie flannel et non pas weave pour ARM comme indiqué dans le billet).
  • HypriotOS 1.2 avec Docker 1.13 est également disponible pour vos RPi.

Big Data

  • Databricks and Apache Spark 2016 Year in Review : Databricks, l’éditeur de Spark, fait sa revue de l’année 2016 et des apports significatifs réalisés sur Spark : Support SQL, Structured Streaming, Spark 2.x.
  • Introduction to Kafka Streams with a Real-Life Example : l’auteur montre les limites de la combinaison Kafka+Spark (j’en ai vécu une partie) et propose son retour d’expérience sur la migration vers Kafka Streams (et conforte l’opinion que j’avais). Reste la problématique du monitoring de Kafka Streams à améliorer même si des solutions adhoc sont listées.
  • Towards a realtime streaming architecture : dans la continuité du billet précédent, retour d’expérience d’une entreprise passant de Spark+Kafka à Kafka, Kafka Streams, Kafka Connect et Akka pour faire du vrai streaming (et pas du micro-batch). Intéressant de voir qu’ils jugent Flink trop complexe pour le moment au regard de leurs besoins. Globalement, l’article montre le problème récurrent dans une architecture big data de la maitrise de l’ensemble des composants pour bien les faire fonctionner. Confluent, en apportant Kafka Streams et Kafka Connect autour de Kafka, semble avoir trouver le bon créneau combinant (une relative) simplicité technologique et performance.

CLI

Container & Orchrestration

DevOps

  • 10 astuces Ansible : revue de 10 bonnes pratiques concernant l’outil d’automatisation Ansible. Il me manquait la personnalisation du logger et de ansible.cfg

Elasticsearch

Opinions

  • Tools & Teams : au-delà du “Utiliser le bon outil pour la bonne tâche”, c’est surtout d’utiliser les outils avec lesquelles une équipe est efficace à un instant donnée. La vision a long terme étant d’aller au-delà des outils vers les concepts afin d’avoir une compétence/expérience qui s’affranchit plus facilement des outils (qui ne sont pas éternels).

Postgres

  • Simple but handy postgresql features : Sympa le \watch ou jsonb_pretty pour respectivement surveiller le résultat d’une requête et affichrer proprement une donnée au format JSON.

Python

  • Records, SQL for Humans : comme tous les projets de Kenneth Reitz (requests, maya, etc), une API simple pour manipuler des données (ici des requêtes SQL)
  • pytz : World Timezone Definitions for Python - permet de faire des calculs sur les dates, la librairie gérerait également les heures d’été/d’hiver dans les calculs.
  • Announcing Pipenv! : Vous réviez d’un outil combinant pip et virtualenv et avec des options supplémentaires, Kenneth Reitz l’a fait durant un week-end…

Sécurité

  • Web Security 101 : présentation des principaux concepts, des cas d’exemples et des moyens de se prémunir.
  • Introducing support for Content Security Policy Level 2 : Microsoft Edge se dote du support de niveau 2 de Content Security Policy (CSP) afin de permettre au propriétaire d’un site de mieux protéger ses clients en déclarant les ressources autorisées ou pas.
  • Github’s Post CSP Journey : retour des équipes de Github sur l’implémentation de CSP et les points encore à adresser (spoiler : non, CSP n’est pas l’arme ultime). Ces points sont peut être des cas marginaux pour des sites classiques mais pas pour Github. Intéressant à lire.

Web

Web, Ops & Data - Semaine 51

docker kubernetes elasticsearch tick chronograf python date ansible redis hypriot arm

Plateform TICK

  • Beta 3 of Chronograf : Chronograf 1.1 continue son bonhomme de chemin avec la parution d’une bêta 3 apportant son lot d’améliorations et de correctifs.

Container & Orchestration

  • Kubernetes 1.5: Supporting Production Workloads : Kubernetes, dans cette version 1.5, apporte des améliorations notamment sur la gestion des applications statefull (passage d’un statut alpha à beta) et plein de choses en alpha : le support des containers windows, la fédération de cluster kubernetes, la haute disponibilité, etc.
  • containerd – a core container runtime project for the industry : Docker Inc continue de modulariser Docker (Engine) en publiant “containerd” et en prévoyant de le donner à une fondation en début d’année prochaine. Containerd est la partie centrale d’exécution du container. Il a été déployé silencieusement depuis Docker 1.11. L’idée de containerd serait de devenir le “format” universel pour faire tourner des containers sur lequel tout le monde s’appuierait… A suivre dans la guerre des containers et des initiatives de standardisation (ou pas).
  • An Early Look at Ansible Container v0.3.0 : Ansible 2.x permet déjà d’interagir avec les containers docker, ansible-container permet d’aller plus loin dans la gestion des containers avec ansible. Cette version apportera le support du format docker-compose v2 et le support de docker 1.12. Même si je suis parvenu à piloter des containers docker avec Ansible 2.2, j’avoue qu’il y a quelques bugs pénibles et j’ai pas forcément l’impression que ce soit la bonne façon de faire. Peut⁻être que ce module apportera des réponses ou qu’il faut repenser la chose différemment.
  • Making Elasticsearch in Docker Swarm Elastic : Un billet intéressant sur le déploiement d’Elaticsearch dans un contexte Docker Swarm. En effet, la partie pénible est de gérer la découverte par IP des noeuds et de rendre cela accessible de l’extérieur du cluster. Le billet présente des astuces pour le faire. J’aurais bien aimé l’avoir il y a de cela 6 mois à 1 an…

NoSQL

ARM

  • Hypriot OS 1.1.2 : vos raspberry pi vont être gatés avec les dernières versions de Docker, Docker-Compose et Docker-Machine. Je détaillerai en janvier la mise en place d’un cluster docker avec Hypriot OS avec 5 Raspberry et 2 Cubietruck qui permettent d’avoir un stockage distribué/résilient avec GlusterFS.

Python

Bonnes fêtes de fin d’année à tous !

Web, Ops & Data - Semaine 50

docker rancher mobile log grafana chronograf statistique packaging npm sécurité csp

Mobile

  • [Lecture] The 2016 U.S. Mobile App Report : Eric, sur la base des chiffres de 2016 rappelle que “[…] vouloir initier la diffusion de son produit/service par une app mobile, c’est partir avec un boulet au pied” et ce même si les statistiques de téléchargement d’applicaitons s’améliorent. Un site web adapté pour mobile sera donc suffisent à court terme (voire tout court), faudrait juste simplifier la création d’un raccourci sur la page d’accueil pour mettre son site mobile au même niveau qu’une application préférée…

Container & Orchestration

  • Rancher 1.2 Is Now Available! : En plus d’apporter la compatibilité avec les dernières versions de Docker (Swarm), Docker-Compose et Kubernes, cette version apporte un meilleur support des plugins réseaux et stockage de Kubernetes & Docker, ainsi qu’une amélioration de la haute disponibilité, de la gestion du cycle de vie de ses applications et une nouvelle politique de sortie de version avec un rythme mensuel.
  • Docker acquires Infinit: a new data layer for distributed applications : En faisant l’acquisition d’Infinit (société française !), Docker semble vouloir promettre un stockage distribué notamment pour les composants statefull (base de données, logs, etc) et ce de façon sécurisée (au sens sécurité ou résilience, cela n’est pas encore précisé).

Mode de travail

  • La revue de code bienveillante : l’article revient sur les bonnes habitudes à prendre dans le cadre d’une revue de code pour qu’elle soit d’une part efficace pour tous et avec la bonne façon de faire.
  • How we stay connected as a remote company : Petit retour pratique sur les habitudes prises au quotidien chez Gitlab pour gérer des équipes distantes.

Packaging

  • npm-based release workflow : Thomas décrit très clairement comment utiliser les fonctionnalités de npm pour gérer le cycle de release de son application (génération du changelog, gestion des numéros de versions, création des tags git, etc).

Statistiques, logs, monitoring (et vie privée)

Sécurité

  • Content Security Policy : la retranscription de la conférence donnée par Nicolas Hoffmann à Codeurs en Seine 2016 sur CSP, la couche sécurité coté navigateurs permettant d’indiquer quels ressources distantes votre site autorise ou pas.

Grav, Docker-Compose et Traefik

docker docker-compose traefik grav

Pour un projet en cours de finalisation, j’ai utilisé le CMS Grav et j’ai décidé tant pour mon développement en local que pour l’environnement de production de déployer cela sous la forme de container docker et d’utiliser Traefik comme reverse-proxy et m’appuyer notamment sur son support natif et dynamique des containers docker.

Premiers pas…

J’avais initialement publié une image nsteinmetz/grav basée sur l’image officielle PHP:apache mais elle ne me convenait pas totalement :

  • Utilisation d’apache2 alors que j’ai basculé depuis longtemps sur nginx
  • L’image PHP:apache est basée sur Debian (taille plus importante qu’une image basée sur Alpine)
  • Pas d’utilisation des volumes (je n’ai pas pris le temps de les déclarer)

Pour mémoire et à toutes fins utiles, le Dockerfile - nsteinmetz/docker-grav :

FROM php:7-apache
ADD https://github.com/getgrav/grav/releases/download/1.1.8/grav-admin-v1.1.8.zip /tmp/grav-admin-v1.1.8.zip
RUN apt update && \
    apt upgrade -y && \
    apt install -y \
        unzip \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libmcrypt-dev \
        libpng12-dev \
        pkg-config && \
    unzip /tmp/grav-admin-v1.1.8.zip -d /tmp/ && \
    mv /tmp/grav-admin/* /var/www/html/ && \
    mv /tmp/grav-admin/.htaccess /var/www/html/ && \
    chown www-data:www-data -R /var/www/html && \
    docker-php-ext-install -j$(nproc) mcrypt && \
    docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
    docker-php-ext-install -j$(nproc) gd &&\
    docker-php-ext-install -j$(nproc) zip &&\
    a2enmod rewrite && \
    rm -rf /var/lib/apt/lists/*
COPY php.conf /etc/apache2/conf-enabled/ 
COPY server-signature.conf /etc/apache2/conf-enabled/

Avec php.conf :

php_admin_flag display_errors off
php_admin_flag expose_php off

php_admin_value post_max_size "40M"
php_admin_value upload_max_filesize "40M"

et server-signature.conf :

ServerSignature Off
ServerTokens Prod

Améliorations

Les améliorations apportées :

  • Passage à docker-compose pour avoir plus de flexibilité au moment de démarrer les containers en fonction des projets utilisant Grav,
  • Passage à des images basées sur alpine (plus légères)
  • Passage à une architecture PHP-FPM + Nginx

Cela donne :

├── app
│   ├── Dockerfile
│   ├── security.conf
│   └── uploads.conf
└── web
│   ├── Dockerfile
│   └── php-fpm.conf
├── docker-compose.yml

Petites précisions à ce niveau :

  • app correspond au container php-fpm
    • security.conf contient des options de sécurité PHP (ne pas exposer la version de PHP, ne pas afficher les erreurs, etc)
    • uploads.conf contient des options liés aux uploads PHP (taille max de fichiers, etc)
  • web est le container nginx
    • php-fpm.conf est la configuration de mon virtualhost nginx

Revue du container “app” (php-fpm)

On a app/Dockerfile :

FROM php:7-fpm-alpine
ADD https://github.com/getgrav/grav/releases/download/1.1.8/grav-admin-v1.1.8.zip /tmp/grav-admin-v1.1.8.zip
RUN apk update &&\
    apk upgrade &&\
    unzip /tmp/grav-admin-v1.1.8.zip -d /tmp/ && \
    mv /tmp/grav-admin/* /var/www/html/ && \
    mv /tmp/grav-admin/.htaccess /var/www/html/ && \
    chown www-data:www-data -R /var/www/html &&\
    apk add libjpeg libjpeg-turbo libjpeg-turbo-dev libpng libpng-dev freetype freetype-dev &&\
    docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
    docker-php-ext-install mcrypt zip gd &&\
    rm -rf /var/cache/apk/* &&\
    rm -rf /tmp/grav-admin-v1.1.8.zip &&\
    cd /var/www/html && ./bin/gpm update --no-interaction
COPY uploads.conf /usr/local/etc/php-fpm.d/uploads.conf
COPY security.conf /usr/local/etc/php-fpm.d/security.conf
VOLUME ["/var/www/html", "/var/www/html/assets", "/var/www/html/backup", "/var/www/html/cache", "/var/www/html/images", "/var/www/html/logs", "/var/www/html/tmp"]

A noter, même s’il n’y a rien d’exceptionnel :

  • Initialisation du container en récupérerant la version de Grav, gestion des dépendances et des permissions
  • La dernière ligne de RUN va mettre à jour les plugins inclus nativement dans Grav
  • Les volumes déclarés permettent de sortir du container toutes les zones non stateless : logs, cache, contenus, etc sur la base de la documentation officielle de Grav ; j’ai ajouté néanmoins les répertoire backups et tmp.

Ensuite, app/security.conf:

[www]

php_admin_flag[display_errors] = off
php_admin_flag[expose_php] = off

et app/uploads.conf :

[www]

php_admin_value[post_max_size] = "40M"
php_admin_value[upload_max_filesize] = "40M"

J’ai fait le choix de surcharger l’instance php-fpm par défaut (ie: www) et j’utilise php_admin_* afin d’interdire toute surcharge de ces variables par l’application.

Revue du container “web” (nginx)

web/Dockerfile :

FROM nginx:stable-alpine
RUN rm /etc/nginx/conf.d/default.conf
ADD php-fpm.conf /etc/nginx/conf.d/php-fpm.conf

Je supprime le virtualhost fourni par défaut et je fournis le mien en lieu et place.

web/php-fpm.conf :

server {
    listen 80;
    server_name _;
    charset utf-8;
    
    root /var/www/html/;
    index index.html index.php;

    # Uploads to 100M
    client_max_body_size 100m;

    location / {
        try_files $uri $uri/ /index.php?_url=$uri;
    }

    ## Begin - Security

    # don't send the nginx version number in error pages and Server header
    server_tokens off;

    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Frame-Options SAMEORIGIN;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data: ; style-src 'self' 'unsafe-inline'; font-src 'self'; child-src; object-src 'none'";

    # deny all direct access for these folders
    location ~* /(.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
    # deny running scripts inside core system folders
    location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
    # deny running scripts inside user folder
    location ~* /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
    # deny access to specific files in the root folder
    location ~ /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess) { return 403; }
    ## End - Security

    ## Begin - PHP
    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }
    ## End - PHP
    
    location ~* ^.+\.(ico|js|gif|jpg|jpeg|png|bmp)$ {
        expires 30d;
    }
}

A noter :

  • Le fichier est inspiré de la configuration officielle nginx donnée pour Grav
  • Ajout de directives de sécurité (X-Content-Type-Options, X-XSS-Protection, X-Frame-Options et Content-Security-Policy)
  • Interdiction d’accès à des répertoires
  • Déclaration de php-fpm où le point d’attention est juste d’indiquer app:9000, car app est le nom de mon service.
  • Mise en cache des images

Et un docker-compose.yml pour enrober le tout

version: '2'
services:
    web:
      build: ./web/
      depends_on:
        - app
      volumes_from:
        - app
      ports:
        - "80:80"
    app:
      build: ./app/
      volumes:
        - ./user:/var/www/html/user
        - ./assets:/var/www/html/assets
        - ./backup:/var/www/html/backup
        - ./cache:/var/www/html/cache
        - ./images:/var/www/html/images
        - ./logs:/var/www/html/logs
        - ./tmp:/var/www/html/tmp

Pour builder puis lancer vos containers en mode daemon :

docker-compse up -d --build

Traefik en reverse-proxy

Traefik est un reverse-proxy moderne et il a le bon goût de s’interfacer notamment avec l’API de Docker. On peut alors déclarer dynamiquement nos containers à traefik et celui-ci les prend en compte dynamiquement.

Démarrer Traefik

Pour mon poste en local, j’ai décidé de lancer Traefik de la façon suivante :

docker run -d -v /dev/null:/traefik.toml -v /var/run/docker.sock:/var/run/docker.sock -p 80:80 -p 443:443 -p 8080:8080 --name traefik traefik:camembert --web --docker --docker.domain=docker.localhost --logLevel=DEBUG

Pour les détails :

  • -v /dev/null:/traefik.toml permet de démarrer Traefik sans fichier de configuration
  • -v /var/run/docker.sock:/var/run/docker.sock permert au container Traefik de communiquer avec Docker et son API
  • J’expose les ports 80, 443 et 8080. Le dernier permettra d’accéder au dashboard fourni nativement par Traefik
  • --web : lance l’interface web (dashboard)
  • --docker --docker.domain=docker.localhost : active le support de docker et fourni un domaine par défaut à tous les containers docker.
  • --logLevel=DEBUG : permet d’avoir des logs verbeux si problème via docker logs -f traefik

Rendre son application “traefik-aware”

Il “suffit” d’indiquer des labels à vos services :

version: '2'
services:
    web:
      build: ./web/
      depends_on:
        - app
      volumes_from:
        - app
      labels:
        - "traefik.backend=grav-project"
        - "traefik.frontend.rule=Host:project.grav"
        - "traefik.port=80"
        - "traefik.protocol=http"
        - "traefik.frontend.entryPoints=http"
        - "traefik.docker.network=nom_du_reseau"
    app:
      build: ./app/
      volumes:
        - ./user:/var/www/html/user
        - ./assets:/var/www/html/assets
        - ./backup:/var/www/html/backup
        - ./cache:/var/www/html/cache
        - ./images:/var/www/html/images
        - ./logs:/var/www/html/logs
        - ./tmp:/var/www/html/tmp
      labels:
        - "traefik.enable=false" 

Explications pour le service web :

  • je lui indique un nom de backend (peu importe le nom) qui sert juste à Traefik pour “nommer” votre container
  • l’url (de frontend) sur laquelle Traefik devra réagir pour vous interfacer avec votre container (ici en tapant http://project.grav/ dans mon navigateur, je dois arriver sur mon container)
  • le protocol et le port indiquent que je me connecte sur le port 80 de mon container avec le protocole http
  • l’entryPoint indique que je vais me connecter à Traefik sur le port 80 pour me connecter ensuite à mon conteneur.
  • le nom du réseau que vous a créé docker lors de l’initialisation du projet. Pour le récupérer, faire un docker network ls.

En gros on a :

Vous <=> Votre navigateur <=> Traefik Frontend (Host:project.grav (frontend.rule) + protocole http (EntryPoint)) <=> Traefik Backend (grav-project + port 80 + protocole http) <=> Container Web

Explications pour le service app :

  • Je ne souhaite pas exposer ce service dans Traefik, je désactive alors ce service (par défault, il y a une auto-découverte de tous les services).

Pour finir

Il ne me reste plus qu’à :

  • Faire une entrée DNS ou éditer /etc/hosts pour project.grav
  • Ouvrir votre navigateur sur http://project.grav/

Sauf que cela ne marche pas à ce stade (si vous utilisez traefik dans un container docker). En effet, lors du docker-compose up -d, docker a créé un réseau pour votre application. Or par défaut, Traefik n’y a pas accès. Il vous faut faire:

docker network connect <nom_du_reseau> <container-traefik>

Et ce coup-ci, votre site propulsé par Grav s’affiche.

Vous pouvez retrouver les fichiers et remonter vos commentaires sur cerenit/docker-grav

Une fois prochaine; je vous parlerais d’Ansible, Docker et Traefik pour déployer vos projets aisément.

Web, Ops & Data - Semaine 48

traefik docker kubernetes grafana null aws alerte monitoring dashboard reverse-proxy

Container & orchrestration

  • Kompose: a tool to go from Docker-compose to Kubernetes : en cours d’incubation chez Kubernetes, ce projet permet de faciliter la transition de Docker à Kubernetes en transformant les fichier docker-compose.yml en fichiers Manifests Kubernetes. Un pivot intéressant si on considère que les développeurs vont utiliser Docker (et au mieux Swarm) pour leurs environnements de développement et en production éventuellement Swarm et ensuite vouloir migrer vers Kubernetes pour ses fonctionnalités plus évoluées.
  • Introducing Distributed Cheese: Traefik 1.1 Camembert Is Out! : Traefik, le reverse proxy moderne qui sait s’interfacer notamment avec Docker, Consul, Kubernetes et plein d’autres est sorti en version 1.1.x. Cette version apporte notamment le support de l’API Docker Swarm (Docker 1.12+), une meilleure gestion des contraintes, le support de Mesos, une meilleure gestion des affinités de sessions, un mode cluster pour Traefik (expérimental) et une image officielle docker basée sur alpine. J’ai profité de l’installation d’un nouveau serveur pour déployer Traefik et c’est vraiment agréable de pouvoir déclarer dynamiquement ses containers docker. Par ailleurs, je rappelle que je maintiens des images docker de Traefik pour sa version ARM
  • Docker for AWS Public Beta : l’offre Docker pour AWS passe d’une version alpha à une verion beta. On notera essentiellement qu’il s’agit d’une version plus intégrée avec l’offre AWS que si l’on déployait un docker soi-même. A suivre mais attention à vérifier qu’il n’y a pas un lock-in qui se crée à la fin, que l’intégration apporte un plus et ne nuit pas à la portabilité des containers.

Dashboard, Monitoring

  • What’s new in Grafana v4.0 : la fonctionnalité phare de cette version est la capacité de définir des alertes au niveau de chaque élément d’un dashboard. Pour se faire il faut définir les règles à appliquer et ensuite s’appuyer sur la partie notification. Pour ceux qui étaient tentés d’installer Chronograf 1.1 (béta) pour avoir cet alerting en plus des dashboards adhoc de Grafana, ils pourraient bien finalement rester dans Grafana (dont la maturité n’est plus à prouver) plutôt que d’attendre que Chronograf se stabilise… A moins que Chonograf n’apporte une valeur ajoutée de part son intégration native avec Telegraf.

Pépites

  • Si vous pensiez que toutes les villes commencent par une lettre, maintenant vous savez que c’est faux avec la ville de ’s_Herenelderen. Vous pouvez mettre vos regexp à jour !
  • These unlucky poeple have names that break computers : même si l’article concède que ce problème est de moins en moins vrai au fur et à mesure des progrès réalisés et de la prise de conscience par les développeurs, mais s’appeller “Null” ou avoir un nom de famille très long (36 caractères dans l’exemple donné ou même 8 au Japon quand l’habitude est de 4…), cela pose des tas de problèmes dans la vie du quotidien.
22 23 24 25 26