Docker – le réseau

J’ai mis en oeuvre mon premier conteneur hier, une instance de Gitlab que j’ai exposé sur internet. La question de la configuration du réseau et la façon dont docker s’intègre se pose. L’objectif est de limiter les configurations et les compétences requises afin de montrer que c’est accessible.

Rappel sur un reseau domestique

La configuration du schéma précédent est commune dans les accès internet grand public. Comme évoqué dans le billet De la virtualisation à la conteneurisation ce reseau domestique peut peut-être utiliser pour accueillir des services autohébergés.

Avec la mutualisation comme les services sont sur la même IP, les conflits de port surviennent. Même si on pourrait definir plusieurs interfacex reseaux ou attribuer plusieurs IP à la même interface, cela complexifie la configuration de l’hôte et le routage au niveau de la box qui possède l’ip publique. Au final pour rester simple avec une seul IP sur l’hôte, on expose autant de ports qu’il y a de services.

Avec certains protocoles comme HTTP il est possible de virtualiser les serveurs basés sur le nom de domaine. Dans le DNS il suffit d’associer plusieurs noms de domaine à la même IP publique. On diminue ainsi le nombre de ports exposés sur l’IP publique.

Le paramétrage des services pour changer leur port d’écoute n’est pas le plus compliqué mais les conflits de librairies et de paramétrage des applications sur le même hôte fini par limiter l’agilité de la solution mutualisé. La virtualisation apporte des solutions.

La configuration de chaque service est autonome ainsi les virtualappliance permettent de dupliquer les services et la couche de virtualisation quemu/kvm crée automatiquement un réseau pour les VM. Ainsi on a une IP par service. Cette IP est attribuée par adresse MAC et même si elles ne sont pas visibles du réseau local, elles sont fixes et sont accessibles par l’hôte.

Reste le problème des ports de l’hôte. On peut là aussi mutualiser certains ports pour les services basés sur le nom de domaine. Dans le cas contraire il faut ouvrir un nouveau port sur la box. Ce port devra être redirigé vers l’IP de la VM. Par défaut cette IP est interne à l’hôte et non visible du reseau local (depuis le routeur de la box) donc il faudrait faire une redirection des paquets depuis l’hôte ou bien configurer la VM en mode bridge pour qu’elle ait une IP dans le réseau local et permettre de faire une redirection depuis le routeur vers l’IP de la VM.

Dans tous les cas il faudra ouvrir un nouveau port sur la box: un part instance de service. Avec le reroutage local à l’hôte la VM reste cachée derrière l’hôte et dans l’autre la VM sera visible du reseau local.

Avec les conteneurs docker

Le dernier schema vu plus haut reste vrai avec la configuration par défaut quand on utilise docker directement sur la machine hôte. On remplace les VM par des conteneurs.

On peut toujours mutualiser les ports 80 et 443 en déployant autant de services HTTP que voulu derrière le reverse proxy. On remarque déjà que les IP1 et IP2 associées à chacun des conteneurs ne sont pas utilisérs par le reverse proxy même si l’hôte les voit. Ces IP sont ephémères et changent d’un redémarrage à l’autre du service.

Docker expose chaque services sur un port de l’hôte.

On revient à l’organisation d’un serveur mutualisé… c’est d’ailleurs ce que l’on voulait en passant de la virtualisation à l’isolation autre terme pour parler de conteneurisation.

Conteneur virtualisé

On va avoir une concurrence entre les applications docker pour l’attribution des ports de l’hôte. Or la machine hôte est également une machine de développement. Nous sommes en informatique résidentielle. Il faudra séparer les réseaux en gérant soi-même 2 réseaux sur l’hôte avec 2 IP l’une pour le dev l’autre pour la production. Outre la compétence supplémentaire, la configuration de dev sera differente de celle de production. Ce qui n’est jamais bon et qu’on cherche à éviter. Cela est encore plus vrai si on veut avoir un environnent de préprod iso-prod. Une autre machine identique en guise d’hôte…. la virtualisation répond à ce besoin.

Faire tourner docker en machine virtuelle

L’utilisation de docker en VM est d’ailleurs un moyen de permettre la scalabilité. Au lieu d’augmenter la mémoire et le nombre de process sur même hôtes, on finit par trouver plus simple d’augmenter le nombre d’hôtes et distribuer les traitements à travers ceux-ci.

On voit que d’un point de vue de l’hôte (rectangle arrondi), les services conteneurisés dans une VM sont accessible par une IP spécifique car exposés par docker sur un port de l’hôte sur lequel il tourne, c’est-à-dire avec une IP fournie par la couche de virtualisation quem/kvm.

L’étape ultime sera pour moi d’avoir sur une VM tous les services du domaine bressure.net, ainsi j’aurais totalement séparé  par virtualisation  et conteneurisation les applications qui n’ont rien à faire sur un poste de travail. A partir de là, tous ces services pourront être hébergé ailleurs ou bénéficier d’une ré-allocation sur des ressources physiques plus grande sans être limité à l’hôte.

La suite au prochain billet.

 

Docker – sautons le pas

Docker est sans doute la plus populaire des solutions de conteneurisation du moment. Elle a l’avantage d’être assez mature puisque elle existe depuis 2013. Par ailleurs de nombreux outils et une documentation suffisante existe c’est un projet très actif pour l’outillage complémentaire contrairement à Lxc.

Si dans l’article précédent //blog.bressure.net/index.php/2019/05/24/de-la-virtualisation-kvm-a-la-conteneurisation-docker/ je présentais des idées générales, dans le présent article je vais illustrer par un cas pratique de mise en œuvre étant moi même le cobaye de cette expérience de migration de VM au conteneurs avec docker.

Pas convaincu

Pour l’histoire je viens de finir d’être convaincu que la conteneurisation est supérieur à la virtualisation en terme de prédictibilité d’exécution. Comme je suis débutant avec Docker, je vais sans doute faire des erreurs et un environnement de développement avec au minimum Git est nécessaire. Pour cela, je me suis tourné vers une Virtual Appliance turnkeylinux Gitlab. Je pensais comme cela avoir une solution sur étagère pour m’offrir le service de gestion de source moderne avec éditeurs etc etc.

Malheureusement, avec une VM de 2 Go de mémoire adressable, mon Gitlab plante sur erreur 500. En passant à 3 Go même erreur. Comme je n’ai pas le temps d’investiguer, alors je me suis installé une gitlab dans docker. Et là ça a marché du premier coup !!!

Docker pour peu que les images prises soient récentes honore bien le principe de prédictibilité de l’exécution.

Revenons à mon cas

L’objectif est de migrer une quinzaine de VM constituant mes serveurs tous hébergés sur mon gros PC Linux/Debian qui fait également office de PC de bureau. Certes je m’en sers très peu, c’est donc plus un serveur mais par période il m’arrive de l’utiliser et….

Utilisation mémoire de mon serveur

… l’image ci-dessus montre de l’overcommit de mémoire et du swap malgré mon passage de 16 à 32 Mo de RAM. Entre-temps, le nombre de VM avait bien augmenté également.

VM et mémoire hôte uilisée

Récemment j’ai installé ElasticSearch et Owncloud, la mémoire de mon PC est mise à rude épreuve. Et finalement le seuil d’overcommit que je me suis fixé a été atteint aujourd’hui.

Overcommit et swap, mon serveur est à la peine

Feuille de route

La migration a pour objectif de:

  • Réduire le nombre de VM (moins d’administration etc)
  • Réduire l’empreinte mémoire des services (blog divers, owncloud etc)
  • Répondre au besoin de sauvegarde comme à l’heure actuel
  • Répondre au besoin de monitoring comme à l’heure actuel
  • Ajouter la possibilité d’avoir une préproduction complète

En option:

  • Permettre la scalabilité des services
  • L’exécution des services dans le cloud

Préliminaires

Après avoir parcouru la documentation de docker, voici les étapes à faire:

  1. Installer les dépôt docker
  2. puis docker
  3. enfin docker-compose

Toutes ces actions sont décrites dans la documentation et ce n’est pas mon propos de la dupliquer ici. Je précise en revanche leur intérêts:

Le dépôt docker plutôt que ceux de Débian afin d’avoir la dernière version de docker. Docker lui-même permettra d’exécuter des images chacune offrant un service tandis que docker-compose permettra d’agréger des services entre eux afin de constituer une application.

VOCABULAIRE

On voit que j’utilise les VM comme réceptacle pour accueillir un service ou une application : une application wordpress pour offrir un service de blog par exemple. Turnkeylinux est ainsi fait: une VM qui contient toute l’application.

Avec Docker, il faut oublier la notion de VM et surtout la relation 1-1 VM-application

Avec docker, les services sont des éléments unitaires logiciels qui s’exécutent dans un conteneur. La granularité est importante car la bonne pratique dans docker est de conserver au service la granularité la plus fine possible. Par exemple un service sera dans mon cas la couche web wordpress. Je vais donc avoir besoin d’un service de base de données en plus. Ces 2 services devront être agrégés pour constituer mon application wordpress fonctionnelle.

Heureusement que les services de bases sont déjà disponibles et c’est ce qui permet à tout un chacun de créer des applications en agrégeant des services pris sur l’étagère. Ces services sont distribués sous forme d’image docker.

Premier conteneur GItlab

Comme je l’indiquais plus haut le premier conteneur qui sera installé sur mon serveur debian, sera Gitlab pour avoir l’outillage de développement.

Faire un premier test en lançant en tant que root dans un terminal

# docker run gitlab/gitlab-ce

La première fois c’est long car il faut que docker télécharge l’image qui fait plus de 600 Mo. Quand c’est finit gitlab va envoyer une sortie de ce genre

GItlab en fonctionnement

On vient de lancer le conteneur avec aucune option. C’est juste pour la premier essai afin de prendre confiance dans en docker. On va se connecter à l’application par l’adresse ip du conteneur même. Prendre un autre terminal lancer la commande suivantes afin de voir le conteneur gitlab:

# docker container ps

Ensuite déterminer l’identifiant du réseau bridge qui est celui auquel est connecté le conteneur lancé sans aucune option.

# docker network ls

Puis regarder le réseau bridge

# docker network inspect 4f1346b59d99

Le conteneur possède l’ip 172.17.0.2 et quand on ouvre un navigateur sur cette ip depuis la machine qui a lancé la commande docker run gitlab/gitlab:ce alors on arrive sur notre instance gitlab.

GITLAB PERSISTENT

Le conteneur gitlab que l’on a lancé précédemment ne conserve pas les informations en cas d’arrêt/redémarrage. Une notion importante à garder à l’esprit, un conteneur n’est pas une VM c’est seulement l’exécution d’un service (d’un programme) dans un espace isolé. Tant que le conteneur s’exécute il écrit sur le disque dans on espace mais quand il s’éteint, tout disparaît. C’est ce que docker appelle l’éphémérité.

La plupart des services doivent être persistent. Heureusement permet de relier des répertoire du système de fichier du conteneur à un point sur la machine hôte.

Commençons par arrêter le conteneurs éphémère

# docker container stop 1b166f7989b1

Préparer un fichier de définition du service gitlab docker-compose.yml

version: "3.7"
services:

 web:
   image: 'gitlab/gitlab-ce:latest'
   restart: always
   hostname: 'gitlab.bressure.net'
   environment:
     GITLAB_OMNIBUS_CONFIG: |
       # external_url '//gitlab.bressure.net'
       # Add any other gitlab.rb configuration here, each on its own line
   ports:
      - '8080:80'
 #    - '443:443'
      - '2222:22'
   volumes:
     - '/srv/gitlab/config:/etc/gitlab'
     - '/srv/gitlab/logs:/var/log/gitlab'
     - '/srv/gitlab/data:/var/opt/gitlab'

Comme l’ip du serveur gitlab change au grès des redémarrages du conteneur, il faut passer par l’ip de l’hôte qui est bien plus stable… Je vais par la suite utiliser une redirection depuis mon frontal apache et j’aurais alors besoin d’un ip fixe vers laquelle rediriger les flux. Comme sur l’ip de l’hôte le port 80 es déjà pris par mon apache, j’indique le port 8080 comme celui sur lequel sera exposé le conteneur gitlab sur l’hôte. De même pour le port ssh pour les commit git, le port 22 étant déjà occupé sur l’hôte, le port 2222 sera utilisé pour accéder à mon serveur git dans gitlab.

En tant que root il suffit de lancer

# docker-compose up -d

La suite

Je viens de monter mon premier service docker. La prochaine étape va consister à mettre en place la redirection web et la couche TLS dans le frontal apache ainsi que la redirection pour SSH afin de pouvoir utiliser mon serveur gitlab depuis internet avant de replonger dans le conteneurisation de mes VM.

De la virtualisation kvm à la conteneurisation docker

Le cloud est à la mode et son adoptions par les géants du web semble prouver qu’il est bien plus qu’une passade. Au sens strict une plateforme ou un service dans le cloud se definissent comme des ressources dont la localisation est secondaire. La conteneurisation prend ici tout son intérêt et pas seulement pour le GAFA. Le particulier qui héberge lui-même ses services trouve aussi un avantage à passer aux conteneurs.

Historique

Internet n’était pas à l’origine un concept centralisé. Loin s’en faut. Tout noeud du réseau, toute machine peut-être un serveur, tout devrait être au même niveau, tout devrait être en pair-à-pair. Grâce au logiciel libre, cela est possible depuis toujours.

Limite de l’infrastructure personnelle

Un particulier qui héberge son propre serveur arrivera néanmoins assez immédiatement à la limite de ressource physique: l’espace qui va limiter le nombre de machines physiques. Le coût d’acquisition ainsi que la mise en place de l’infrastructure réseau sont également des facteurs limitants.

Mutualisation

Le particulier va donc mutualiser des fonctions sur une même machine qui fera office de serveur web, de serveur de messagerie ou encore de serveur ftp à la fois par exemple. Mais:

La mutalisation engendre des conflits de version de librairies

Les logiciels s’appuyant sur des bibliothèques du système, partagent ces ressources de l’OS et des incompatibilités peuvent empêcher les services d’évoluer indépendamment les uns des autres.

La virtualisation

Heureusement que la virtualisation permet de s’affranchir des contraintes physiques et la prise en charge de la virtualisation par le processeur et l’OS (kvm, intel VT-x) permet d’avoir des performances proches d’une exécution non virtualisée.

La virtualisation au niveau hardware permet d’avoir des environnements d’exécution cloisonnés

Ainsi chaque application peut êtres dans la version de son choix avec la version d’OS de son choix et même avec le type de ressources de son choix. La virtualisation telle que kvm par exemple le permet, est une abstraction de la machine physique.

Le prix à payer est celui d’un nombre d’installations d’OS qui croit avec le nombre d’application. Dans ce modèle une application c’est une VM au moins. Une VM c’est un OS qu’il faut installer et administrer.

Limites de la virtualisation

Outres la charge d’administration système qui ne change pas par rapport une plateforme physique, la virtualisation est coûteuse en ressources physique de la machine hôte.

En effet une VM c’est une allocation d’office d’un espace disque et mémoire sur l’hôte. Ainsi une VM qui est taillée avec 1 Go de mémoire consomme 1 Go de mémoire sur l’hôte même si dans la VM son OS ne prend que 600 Mo. La technique de balloon que j’ai testé ne permet que de réduire la mémoire de l’invité à chaud. L’augmentation de la mémoire ne se fait pas à chaud et n’est pas automatisée. Une intervention humaine est nécessaire.

Il en est de même pour l’espace disque. La virtualisation définit des disques virtuels. Grâce à LVM on peut augmenter la tailles des partitions dans l’invité à chaud mais c’est une intervention volontaire comme on ne ferait sur une machine physique. De plus quand on alloue un disque de 10 Go à une VM, on crée un fichier de 10 Go sur l’hôte même si dans l’invité on utilise que 2 Go.

La virtualisation c’est un découpage d’une machine physique. Sur chaque partie, on installe un OS et les applications.

En terme d’isolation la virtualisation est championne

Simplifier la virtualisation

L’administration de multiples VM est fastidieuse. Il faut mettre à jours autant d’OS que de VM.

La création d’une VM fonctionnelle l’est tout autant. Il faut d’abord installer l’OS puis l’application et son parametrage. Heureusement grâce aux virtual appliance comme celles fournies par turnkeylinux, l’ajout d’une nouvelle application virtualisée, qui se materialise par une VM, est rapide.

Vers la conteneurisation

On a évoqué le manque de souplesse dans l’allocation des ressources. Ce problème ne se rencontrerait pas si les applications étaient toutes installées sur l’hôte en partageant les bibliothèques du système et les ressources de l’OS que sont la mémoire et le disque.

Cette idée qu’un service comme un moteur de blog par exemple devrait être ce qu’il a toujours été depuis l’origine: une application et non pas une VM, est fondamentale à mon sens pout appréhender la conteneurisation avec la bonne grille de lecture. Comme une application ne doit pas être associé à un OS dédié depuis l’avènement du multitâche…les applications doivent se partager less ressources de la machine qui les heberge via un l’OS.

Pour autant on ne veut pas retomber dans les écueils de la mutualisation et garder un cloisonnement des applications afin que chacune puisse évoluer avec des versions de librairies différentes.

C’est là que les concepts d’isolations via des techniques qui ont évoluées du chroot (1982), en passant pas lxc (2008) puis docker (2013) au sein même de l’OS rentrent en jeu. Ainsi les applications sont isolées tout en étant à un niveau de performances quasi identiques que si elles non conteneurisées. En effet tous les appels systèmes se font directement sur l’OS hôtes sans passer par un OS invité qui va ensuite appeler via la couche de virtualisation l’OS hôte. C’est plus long même si et le noyaux linux avec kvm et les cpu (ex: Intek VT-x) offrent un support de la virtualisation.

Un conteneur n’est pas une VM

Je souligne encore cette difference car il me semble assez commun de faire l’erreur. Voici les differences à bien avoir en tête.

Un conteneur execute une application qui utilise l’OS de l’hôte ou plus exactement les appels systèmes (noyaux linux) de l’hôte. De la on comprend qu’il ne sera pas possible d’executer une applications compilée pour un CPU différent ou un OS différent. On pourra exécuter une distribution différente (simple arborescence de fichiers spécifique à la distribution) mais il n’est pas possible d’executer une application Windows sur un hôte linux.

Un conteneur c’est une application. On ne définit plus des ressources physiques dans ce paradigme. L’application va utiliser à la demande la mémoire et le disque disponible sur l’hôte.

Quand passer à Docker ?

Hier.

Dans le cas où toutes VM sont des linux que l’on souhaite garder à jour c’est-à-dire dans la plus récente version disponible alors la conteneurisation est la solution pour mieux utiliser les ressources en quantité et en performance.

La paradigme des VM mono-application n’est pas optimum et represente une solution surdimensionnée au besoin d’isolations réel: a-t-on vraiment besoin d’un OS par application ?

A suivre sur ce blog, mes prochaines aventures dans le monde de docker avec la conteneurisations de mes VM.

Shell distant pour connexion instable

Pour administrer mes serveurs en déplacement rien de plus facile avec SSH et les réseaux mobiles. J’utilisais ainsi SSH via l’application ConnectBot sur Android. Si cette solution permet de dépanner elle présente quelques défauts.

Limites du SSH

Tout d’abord elle est très sensible à la qualité de la connexion. En effet le terminal est rapidement bloqué si le retour (echo) du serveur tarde à venir. Cela peut même provoquer un plantage de ConnectBot.

Ensuite quand la commande est longue comme par exemple un download ou une commande de compilation ou bien une commande de transcodage vidéo ou même une installation de paquet, une interruption de la connexion lui est fatal. On peut y remédier par l’utilisation d’un nohup mais ce dernier fait perdre la main sur la commande. De plus la commande qui part en nohup n’est plus accrochée à la connexion ssh quand le shell se termine.

La perte de connexion réseau est un réel problème pour les longues sessions de shell distant. C’est encore plus vrai même pour les courtes sessions quane la connectivité est mauvaise ou par intermittence comme dans le métro parisien !

Mosh le shell déconnecté

J’ai alors découvert Mosh. C’est un shell qui au lieu d’être connecté à un serveur ssh en TCP, envoie et reçoit ses paquets en UDP. La phase de connexion initiale se fait en SSH classique pour démarrer ensuite une session en UDP . Cette session en UDP survit aux interruptions réseaux !

Pour l’utilisateur client, mosh se presente comme un shell ssh. Quand il y a une perte de connexion, mosh indique par un message que la connexion réseaux est perdue, le terminal est alors bloqué puis quand le réseau revient, le terminal retrouve sont fonctionnement normal. Les commandes lancées sur le serveur continuent pendant la perte de connexion.

Installation

Mosh est un programme client/serveur. Il faut donc l’installer sur le serveur que l’on veut pouvoir atteindre vie un réseau intermittant. En root sur ma Debian

# apt-get install mosh

Termux le client Android le plus simple

Sur le client, il faut également installer mosh. Avec Android le plus simple est d’installer Termux. Cet emulateur de terminal fournit le paquet mosh.

pkg install mosh

L’utilisation de mosh est comme ssh

mosh user@server.net

On pourra avantageusement passer unr authentification par clé via une commande du type

mosh –ssh=”ssh -i /data/data/com.termux/files/usr/etc/ssh/ssh_host_ed25519_key” user@server.net

UserLand pour un client Linux sur Android

Si on veut rester dans un environnement familier en l’occurence Debian pour moi, le logiciel UserLand pour Android permet d’avoir une distribution Linux complète. A partir de là on installe mosh de manière classique. Pour Debian

sudo apt-get install mosh

UserLand permettra également de partager l’installation de mosh avec une session UserLand graphique. Mais le grand intérêt pour moi est de bénéficier de l’émulateur de terminal de UserLand que je trouve plus abouti que celui de Termux.

Paramétrage avec UserLand

Néanmoins les sessions Debian de UserLand sont un peu brutes. Il faut les personnaliser un peu. En effet mosh nécessite une locales UTF-8.

sudo apt-get install locale

Choisir alors une locales UTF-8, pour moi fr_FR.UTF-8

sudo dpkg-reconfigure locales

Mettre à la fin de .bashrc

LANG=”fr_FR.UTF-8″
export LANG

Sourcer le fichier

source .bashrc

Mosh est désormais utilisable.

Application automatique des mises à jour de sécurité

Une mésaventure m’est arrivée ce matin sur ma VM hebergeant un site LAMP avec une installation TURNKEY. J’avais activé les mises à jour automatiques de sécurité en voulant m’éviter d’aller manuellement faire des “apt-get dist-upgrade” quand apticron m’alerte par mail.

Ce matin Jetpack me previent que mon site est indisponible. Rien d’affolant jusque là car je n’ai jamais eu le temps de régler l’installation pour la charge attendue etde temps à autre, linux tue mes processus mysql. C’est vrai que je suis plus famillier des applications Java que Php.

Le message qui s’affichait était un habituel “Erreur de connexion à la base de données”. Dans ce cas je fait un tour sur la VM à la recherche du status du service mysql. Et la c’est le drame ! Pas de service mysql enregistré. Un tour dans /var/log/message pour constater que le paquet mysql avait été désinstallé! Par qui ? par quoi ? un hacker ?

La trace indique que c’est cron-apt qui a lancé la suppression et en allant voir l’historique des commande dans /var/log/apt/history.log je vois:

Start-Date: 2018-11-20 04:54:11
Commandline: /usr/bin/apt-get -o quiet=1 dist-upgrade -q -y -o APT
:Get::Show-Upgraded=true -o Dir::Etc::sourcelist=/etc/apt/sources.
ist.d/security.sources.list -o Dir::Etc::sourceparts=nonexistent –
DPkg::Options::=–force-confdef -o DPkg::Options::=–force-confol
Upgrade: mariadb-common:amd64 (10.1.26-0+deb9u1, 10.1.37-0+deb9u1)
mariadb-server-core-10.1:amd64 (10.1.26-0+deb9u1, 10.1.37-0+deb9u
), mariadb-client-core-10.1:amd64 (10.1.26-0+deb9u1, 10.1.37-0+deb
u1)
Remove: mysql-server:amd64 (5.5.9999+default), mariadb-server-10.1
amd64 (10.1.26-0+deb9u1), default-mysql-server:amd64 (1.0.2), mari
db-client-10.1:amd64 (10.1.26-0+deb9u1)
End-Date: 2018-11-20 04:56:13

Je ne suis pas absolument sûr que c’est la mise à jour automatique de sécurité qui est la seule en cause ou bien si c’est une mauvaise configuration entre turnkey et les dépôt de debian mais en tout depuis cette mise à jour de la version 10.1.26 vers la 10.137 je n’ai plus de BDD.

Encore quelques heures à passer dans l’administration du système…

update1: une mise installation manuelle par un apt-get install mariadb-server remet tout en ordre

update2: un mail de la part de turnkey indique la marche officielle pour correction. //www.turnkeylinux.org/blog/debian-secupdate-breaks-lamp-server

Avec le libre les problèmes sont pris en compte rapidement.

Tubes nommés et redirections en parallèle

La ligne de commande est bien faite. Cette phrase est souvent entendue dans la bouche des informaticiens. J’en ai encore vérifié la véracité ses jours derniers.

Ma problèmatique était de constituer des statistiques sur des accès HTTP en consultant un fichier de log. La volumétrie des log étant de l’ordre de plusieurs gigaoctets et je ne disposais que de ma ligne de commande en bash.

Je me suis alors dirigé vers une serie de grep avec les bons motifs de recherche couplé à un bon vieux wc:

grep motif fichier.log | wc -l

Pour compliquer il y avait plusieurs motifs differents à compter et il fallait produire un fichier par jour avec le comptage de chaques motifs.

ACCES_COUNT=`grep -e motif_date -e motif_acces fichier.log | wc -l`

Autant de ligne de ce genre que de motif. L’inconvenient est que l’on parcourt la log autant de fois qu’il y a de motif d’acces pour une date donnée. Dans mon cas j’avais 15 motifs soit 1h30 pour obtenir les statistiques pour un jour donné.

Or, une fois que l’on est sur la bonne date dans le fichier de log c’est dommage de le reparcourir depuis le debut pour le motif d’accès suivant. D’où l’idée d’externaliser le parcours du fichier à la recherche de la bonne date puis de dispatcher la ligne aux grep qui compte les motifs d’accès.

tee

On a donc envie de faire un premier grep dont on va diriger la sortie sur d’autres grep en arborscence. La première pièce du puzzle est la commande tee. Voici l’idée :

grep -e motif_date fichier.log | tee -a entree_proc_grep1 entree_proc_grep2 … > /dev/null

Il manque la seconde piece qui est celle qui permet de definir un tube entre le premier grep et les autres qui doivent s’exécuter en parallèle. C’est là que les tube nommés interviennent.

named pipe

La commande mkfifo crée un tube nommé que l’on donne en entrée au grep de motif d’accès. Ces grep seront alors bloqués en attente de lire des donnés dans leurs tubes respectifs.

mkfifo entree_proc_grep1

grep -e motif_acces < entree_proc_grep1 | wc -l > fichier_temp1` &

PID_LIST+=” $!”

Autant de lignes de ce genre qu’il y a de motif d’accès à compter. Remarquons qu’à ce stade on à seulement créé des processus pour faire les grep de comptage et qu’ils sont tous bloqués en attentes sur leur tube. On alimente ensuite les tubes. Voici l’ordre

  1. créer les tube
  2. lancer en arrière plan les grep prenant les tubes en entrée
  3. lancer le grep sur la date pipé avec le tee vers les tubes

Subtilites

On ne peut plus récupérer la valeur des wc car c’est un sous shell qui l’exécute. On passe par un fichier temporaire.

On stocke la liste des pid des grep de comptage car le shell courant qui est leur parent doit attendre qu’ils soient tous finis avant d’aller lire les resultats dans les fichiers temporaires.

wait $PID_LIST

cat fichier_temp1

cat fichier_temp2

….

Dans mon cas particulier je suis passé de 1h30 de traitement pour avoir les statistiques d’un jour donné à seulement 3 minutes !

Finitions

Pour être propre il faut terminer en supprimant les fichiers temporaires (de comptage et les tubes).

Pour pouvoir lancer plusieurs instance du script sur des dates differentes en parallèle, ii faut ajouter le pid du script dans les noms des fichiers temporaires ( tubes nommés et comptage).

Dans mon cas particulier il me fallait les statistiques de plusieurs jours: chaque jour ayant sa propre stat. On peut lancer le script manuellement pour chaque jour ou bien intérer sur les jours, ce qui donne a peu près ceci:

for jour in 1 2 3 4

do

mkfifo entree_proc_grep1

grep -e motif_acces < entree_proc_grep1 | wc -l > fichier_temp1` &

PID_LIST+=” $!”

…..

grep -e motif_date fichier.log | tee -a entre_proc_grep1 entree_proc_grep2 … > /dev/null

wait $PID_LIST

cat fichier_temp1 >> fichier_stat_$jour

echo “” >> fichier_stat_$jour

…..

rm entree_proc_grep1

….

done

On remarque encore que pour chaque jour en parcourt l’ensemble de la log d’accès. Or dès le premier passage on a deja passé sur toutes les dates possibles. Une seule passe doit suffire ! L’idée est que le grep sur le motif de la date ne prenne plus le fichier de log en entrée mais un tube nommé…

for jour in 1 2 3 4

do

mkfifo entree_proc_grep1_$jour

grep -e motif_acces < entree_proc_grep1_$jour | wc -l > fichier_temp1_$jour` &

PID_LIST+=” $!”

…..

mkfifo entree_proc_$jour

PROC_JOUR_LIST+=” entree_proc_$jour”

grep -e motif_date entree_proc_$jour| tee -a entre_proc_grep1_$jour entree_proc_grep2_$jour … > /dev/null &

PID_LIST+=” $!”

done

cat fichier_log | tee -a $PROC_JOUR_LIST > /dev/null

wait $PID_LIST

for jour in 1 2 3 4

do

cat fichier_temp1_$jour >> fichier_stat_$jour

echo “” >> fichier_stat_$jour

…..

rm entree_proc_grep1_$jour

….

rm entree_proc_$jour

done

C’est un peu plus compliqué car pour distinguer tous ces tubes nommés on doit ajouter le numero de jour à leur nom.

Eviter ainsi de reparcourir la log depuis le début pour chaque jour permet d’aller plus vite. Dans mon cas particulier pour les statistiques de 3 jours je suis passé de 2mn50 à 0mn45s et pour les statistiques de 14 journées je suis passé de 22mn à 5mn !

Moralité de l’histoire, l’algorithmie alliée à la ligne de commande et la puissance du shell ça fait des étincelles.

Out of memory dans le kernel Linux

Sous ce titre se cache les soucis de disponibilité de mes sites web virtualisés. Depuis quelques mois ils tombent de temps en temps, surtout quand il y a un peu de charge c’est-à-dire quand j’y publie un article un peu racoleur  et que le nombre d’accès journalier dépasse l’extraordinaire nombre de quelques dizaines d’utlisateurs….

Quand cela arrive c’est souvent le SGBD qui a “crashé” et c’est signalé par un jolie “impossible d’obtenir une connexion à la base de donnée”. Le SGBD est effectivement arrêté et le redémarrer permet de remettre en fonctionnement le site. Le plus intéressant est de trouver la cause du crash.

Dans la log de démarrage on peut y voir des erreur /var/log/mysql/error.log:

181014  6:30:23 [Warning] Using unique option prefix myisam-recover instead of myisam-recover-options is deprecated and will be removed in a future release. Please use the full name instead.
181014  6:30:23 [Note] Plugin 'FEDERATED' is disabled.
181014  6:30:27 InnoDB: The InnoDB memory heap is disabled
181014  6:30:27 InnoDB: Mutexes and rw_locks use GCC atomic builtins
181014  6:30:27 InnoDB: Compressed tables use zlib 1.2.8
181014  6:30:27 InnoDB: Using Linux native AIO
181014  6:30:29 InnoDB: Initializing buffer pool, size = 128.0M
181014  6:30:31 InnoDB: Completed initialization of buffer pool
181014  6:30:35 InnoDB: highest supported file format is Barracuda.
InnoDB: Log scan progressed past the checkpoint lsn 15552477668
181014  6:30:35  InnoDB: Database was not shut down normally!
InnoDB: Starting crash recovery.
InnoDB: Reading tablespace information from the .ibd files...
InnoDB: Restoring possible half-written data pages from the doublewrite
InnoDB: buffer...


En poussant l’investigation plus loin par un grep de “mysql” dans /var/log/syslog

Oct 14 01:14:54 wordpress kernel: [1971121.772280] INFO: task mysqld:13245 blocked for more than 120 seconds.
Oct 14 01:14:54 wordpress kernel: [1971121.772288] mysqld D ffff880019674468 0 13245 611 0x00000000
Oct 14 01:20:56 wordpress kernel: [1971481.772349] INFO: task mysqld:14574 blocked for more than 120 seconds.
Oct 14 01:20:56 wordpress kernel: [1971481.772358] mysqld D ffff880079cf8f38 0 14574 611 0x00000000
Oct 14 01:32:55 wordpress kernel: [1972201.772091] INFO: task mysqld:13240 blocked for more than 120 seconds.
Oct 14 01:32:55 wordpress kernel: [1972201.772094] mysqld D ffff8800197a4828 0 13240 611 0x00000000
Oct 14 01:34:53 wordpress kernel: [1972321.772343] INFO: task mysqld:13240 blocked for more than 120 seconds.
Oct 14 01:34:53 wordpress kernel: [1972321.772351] mysqld D ffff8800197a4828 0 13240 611 0x00000000
Oct 14 01:38:54 wordpress kernel: [1972561.772076] INFO: task mysqld:13240 blocked for more than 120 seconds.
Oct 14 01:38:54 wordpress kernel: [1972561.772078] mysqld D ffff8800197a4828 0 13240 611 0x00000000
Oct 14 01:55:40 wordpress kernel: [1973565.634464] [ 611] 0 611 1085 1 7 39 0 mysqld_safe
Oct 14 01:55:40 wordpress kernel: [1973565.634471] [ 1188] 104 1188 226779 4831 131 25556 0 mysqld
Oct 14 01:55:40 wordpress kernel: [1973565.634837] Out of memory: Kill process 1188 (mysqld) score 39 or sacrifice child
Oct 14 01:55:40 wordpress kernel: [1973565.634847] Killed process 1188 (mysqld) total-vm:907116kB, anon-rss:19324kB, file-rss:0kB
Oct 14 01:55:40 wordpress kernel: [1973568.287694] [ 611] 0 611 1085 1 7 39 0 mysqld_safe
Oct 14 01:55:40 wordpress kernel: [1973568.287706] [17643] 104 1188 226779 5726 131 24720 0 mysqld
Oct 14 01:55:40 wordpress kernel: [1973568.288313] Out of memory: Kill process 17643 (mysqld) score 39 or sacrifice child
Oct 14 01:55:40 wordpress kernel: [1973568.288317] Killed process 17643 (mysqld) total-vm:907116kB, anon-rss:22904kB, file-rss:0kB
Oct 14 03:54:02 wordpress kernel: [1980665.296368] [ 611] 0 611 1085 4 7 40 0 mysqld_safe
Oct 14 03:54:02 wordpress kernel: [1980665.296430] [17670] 104 17670 228505 5694 97 11154 0 mysqld
Oct 14 03:54:03 wordpress kernel: [1980665.296509] Out of memory: Kill process 17670 (mysqld) score 21 or sacrifice child
Oct 14 03:54:03 wordpress kernel: [1980665.296515] Killed process 17670 (mysqld) total-vm:914020kB, anon-rss:22776kB, file-rss:0kB
Oct 14 03:54:03 wordpress kernel: [1980668.526394] [ 611] 0 611 1085 3 7 40 0 mysqld_safe
Oct 14 03:54:03 wordpress kernel: [1980668.526456] [20409] 104 17670 228505 6736 97 10220 0 mysqld
Oct 14 03:54:03 wordpress kernel: [1980668.526536] Out of memory: Kill process 21984 (mysqld) score 21 or sacrifice child
Oct 14 03:54:03 wordpress kernel: [1980668.526537] Killed process 20409 (mysqld) total-vm:914020kB, anon-rss:26920kB, file-rss:24kB
Oct 14 03:55:21 wordpress kernel: [1980749.353383] [ 611] 0 611 1085 1 7 40 0 mysqld_safe
Oct 14 03:55:21 wordpress kernel: [1980749.357504] [ 611] 0 611 1085 1 7 40 0 mysqld_safe

Linux décide donc de tuer des processus quand il est à court de mémoire. Il lui arrive même de tuer des processus apache2. En faisant un grep de “apache2” dans /var/log/syslog

Oct 14 03:55:21 wordpress kernel: [1980749.357771] Out of memory: Kill process 13173 (apache2) score 13 or sacrifice child
Oct 14 03:55:21 wordpress kernel: [1980749.357772] Killed process 13173 (apache2) total-vm:457020kB, anon-rss:1708kB, file-rss:0kB

Il ne me reste plus qu’a allouer un peu plus de mémoire à mes VM que le petit gigaoctet par défaut. Trêve de radinerie.

 

 

 

 

RSYNC depuis AIX vers Linux

Ce billet aurait pu s’intituler “mes aventures dans le monde AIX” ou bien “comment faire du nohup sur AIX” mais se serait être malhonnête car laisserait croire que tous mes problèmes venaient de cet OS propriétaire. Pour être exact le contexte était celui d’un transfert de fichiers depuis une machine AIX 5.3 vers un Linux Cent OS.

Je ne pensais pas que j’allais être confronté à tant de subtilités. Si certaines sont imputables à la limite de mes connaissances en scripting, d’autres ont pour cause les différences entre le monde merveilleux de Linux et celui de AIX.

En premier lieu voici la liste des contraintes imposées par des besoins fonctionnelles, des contraintes de sécurités et des raisons pratiques.

  • c’est AIX qui pilote le transfert: la sécurité impose ce sens de flux
  • les fichiers les plus récents  doivent être copiés en premier: besoin fonctionnel
  • seul les fichiers de moins d’un certain âges seront copiés: contrainte fonctionnelle.
  • la commande de transfert doit être en nohup: raison pratique car le transfert va être long.

Le chemin fut semé d’embûches avant d’arriver à mes fins dont la plupart vient des spécificités du système AIX.

La première difficulté est venue du tri pour obtenir les fichiers les plus récents en premier. Sous Linux on fait un find avec un printf suivi d’un sort

find . -type for -ctime -1460 -printf "T@ %p\n" | sort -r | awk '{print $(NF)}'

pas de printf dans la fin de AIX

la clé NF pour le awk non plus ou conséquence du point precendent

J’avais envie d’utiliser l’option exec du find en lui passant un stat comme ceci

find . -type for -ctime -1460 -exec stat -c '%Y %n' {} \; | sort -r | awk '{print $(2)}'

Pas de stat sous AIX

Malheureusement la commande stat n’existe pas sous AIX et la commande qui s’en rapproche n’a pas la même fonctionnalité. La solution est de passer par une commande perle dans le exec du find

find . -type for -ctime -1460 -exec perl -e '@tmp=stat ($ARGV [0]); print "$tmp [10]\t $ARGV [0]\n";' | sort -r | awk '{print $(2)}'

Mon dieu que c’est compliqué ! Et je n’étais pas au bout de mes peines.

Nohup et pipe

Le lecture ajoutera de lui même un pipe supplémentaire pour faire le rsync. Là n’est pas la question…. quoique on verra plus tard. Il s’agit surtout de mettre toutes ces commandes en nohup. Comme

nohup cmd1 | cmd2

ne porte le nohup que sur cmd1 l’idée est de passer par un seul sous she’ll qui sera vu comme une seule commande.

nohup $SHELL<<EOF &
find . -type for -ctime -1460 -exec perl -e '@tmp=stat ($ARGV [0]); print "$tmp [10]\t $ARGV [0]\n";' | sort -r | awk '{print $(2)}'
EOF

Ceci fonctionne bien sous Linux et il suffit de remplacer les commandes compliquées par un sleep mais sous AIX ?

Pas de sous shell avec le nohup AIX

La documentation IBM AIX disponible sur Internet est formelle. Si le nohup AIX prend en argument une et une seule commande. La seule moyen est de passer un script qui sera vu comme une commande pour le nohup.

nohup sh mon_script.sh

Rsync

Apres les difficultés liées à AIX j’avais également oublié de faire un échange de clé car en nohup on ne pourra pas saisir de mot de passe. Cela n’a rien à voir avec l’AIX, j’étais seulement absorbé sur les spécificités de l’AIX que j’en ai oublié cette subtilité.

Juste pour le plaisir… CC-BY-SA

Ransomware Linux

Il est loin le temps où avoir une machine  sous Linux protégeait des attaques des Virus. Quand j’ai commencé à héberger mon site web, au début des années 2000 j’avais utilisé un serveur Web dédié  (Savant Web serveur) sur Windows et j’avais été très étonné de constater le nombre de tentative  d’accès à des répertoires système de L’OS.  Ces tentatives échouaient car le serveur Web n’était pas IIS….

Depuis j’ai passé toutes mon informatique personnelle en GNU/Linux et je n’ai jamais été confronté à des attaques…. jusqu’à la semaine dernière.

Les pirates ciblent désormais également les machines Linux. Ainsi mes fichiers de disques de VM se sont mis à être renommés avec des suffixes .enc ! Ce que je pensais être un problème d’espace dans ma VM était bel et bien une attaque d’un virus de type ransomware. Tous mes fichiers personnels ainsi que la configurations apache et celle du serveur DNS étaient cryptées.

Heureusement que j’ai des sauvegardes… mais le désagrément causé est certain: rupture de service, éventuelle perte de données (de quand date la dernière sauvegarde?). On ne pense jamais assez aux sauvegardes ! La première chose à faire façe à une infection que l’on ne peut pas éradiquer (manque de littérature et désintérêt des éditeurs) est de limiter la casse. Arrêter le système car le virus à réussi à obtenir les droits root ou du groupe root puisqu’il chiffre des fichiers en écriture à root. Donc arrêter le système puis débrancher le câble d’alimentation ! Ensuite démarrer depuis une clé usb de type live-cd pour récupérer ce qui peut l’être. Enfin réinstaller le système complètement.

Après l’incident il faut également cherchez à s’en prémunir. Comment une telle infection à pu survenir ? Il y a 2 ou 3 semaines mon  serveur n’a pas redémarré sur échec de montage d’un Volume logique. Suite à cet étrange incident je n’avais trouvé comme solution que de démarrer sur un kernel précédent: coïncidence ou la première étape de l’attaque ? M’obliger à utiliser un kernel ancien contenant des failles ?

Ne jamais utiliser de kernel non à jour !

Leçon à retenir: Linux n’est plus sous les radars. Il faut donc s’équiper d’antivirus et d’antispam. Les solutions existent:clamav et spamassasin.

Juste pour le plaisir… CC-BY-SA

Compression de log Tomcat 6 avec log4j 1.2

Le sujet est largement débattu mais il n’est pas trivial de trouver la solution de bout en bout. Diversité des cas, obsolescence de Tomcat 6 ou de log4j 1.2 ? En tout cas voici comment on peut mettre en place la compression de log de Tomcat 6 à l’aide de log4j 1.2.15 !

D’abord pourquoi mettre en place de la compression de log ? La log de Tomcat (ex catalina.out) ne devrait contenir que peu de messages et seulement de rare erreurs, non ? La diversité des programmeurs peut amener ces log à grossir et les framework qui génèrent des URL mal formées existent (caractère illégal). La rotation des fichiers de log si elle limite la taille ne permet pas de garder un historique c’est pourquoi je lui préfère la compression.

La documentation de Tomcat 6 permet de mettre en place rapidement log4j 1.2.17 et de disposer de la log de  Tomcat configurée par un fichier log4j.properties. L’activation de la compression des log se fait par l’utilisation de l’appender RollingFileAppender mais il ne s’agit pas de celui du paquet log4j mais plutôt du sous-paquet rolling (en gras dans l’exemple suivant). Ensuite il faut choisir la politique de rotation basée sur le temp TimeBasedRollingPolicy et enfin en spécifiant un motif de nom de fichier avec extension gz ou zip, le fichier sera compressé. Pour tester immédiatement la compression des log catalina.out, vous pouvez utiliser le fichier de configuration suivant où le niveau de log est passé à DEBUG et génère un fichier compressé par seconde.

log4j.rootLogger=DEBUG, CATALINA

# Define all the appenders
log4j.appender.CATALINA=org.apache.log4j.rolling.RollingFileAppender
log4j.appender.CATALINA.File=${catalina.base}/logs/catalina.
log4j.appender.CATALINA.RollingPolicy=org.apache.log4j.rolling.TimeBasedRollingPolicy
log4j.appender.CATALINA.RollingPolicy.ActiveFileName =${catalina.base}/logs/catalina.log
log4j.appender.CATALINA.RollingPolicy.FileNamePattern=${catalina.base}/logs/catalina.%d{yyyyMMdd.HHmmss}.gz
log4j.appender.CATALINA.Append=true
log4j.appender.CATALINA.Encoding=UTF-8
log4j.appender.CATALINA.layout = org.apache.log4j.PatternLayout
log4j.appender.CATALINA.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.LOCALHOST=org.apache.log4j.DailyRollingFileAppender
log4j.appender.LOCALHOST.File=${catalina.base}/logs/localhost.
log4j.appender.LOCALHOST.Append=true
log4j.appender.LOCALHOST.Encoding=UTF-8
log4j.appender.LOCALHOST.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.LOCALHOST.layout = org.apache.log4j.PatternLayout
log4j.appender.LOCALHOST.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.MANAGER=org.apache.log4j.DailyRollingFileAppender
log4j.appender.MANAGER.File=${catalina.base}/logs/manager.
log4j.appender.MANAGER.Append=true
log4j.appender.MANAGER.Encoding=UTF-8
log4j.appender.MANAGER.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.MANAGER.layout = org.apache.log4j.PatternLayout
log4j.appender.MANAGER.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.HOST-MANAGER=org.apache.log4j.DailyRollingFileAppender
log4j.appender.HOST-MANAGER.File=${catalina.base}/logs/host-manager.
log4j.appender.HOST-MANAGER.Append=true
log4j.appender.HOST-MANAGER.Encoding=UTF-8
log4j.appender.HOST-MANAGER.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.HOST-MANAGER.layout = org.apache.log4j.PatternLayout
log4j.appender.HOST-MANAGER.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Encoding=UTF-8
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

# Configure which loggers log to which appenders
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost]=INFO, LOCALHOST
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager]=\
  INFO, MANAGER
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager]=\
  INFO, HOST-MANAGER

Cette configuration fonctionne avec log4j 1.2.17 mais pas seulement. En remplaçant le jar log4j.jar par celui de la version 1.2.16, cela fonctionne aussi. En rétrogradant en version 1.2.15 il faudra utiliser un fichier de configuration au format XML (car le support du fichier dde conf en propriété a été introduit dans la version suivante 1.2.16). Voici un exemple de fifichier de configuration au format XML:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="//jakarta.apache.org/log4j/">
  <appender name="console"> 
      <param name="file" value="${catalina.base}/logs/catalina."/>        
       <rollingPolicy>
      <param name="FileNamePattern" value="${catalina.base}/logs/catalina.%d{yyyyMMdd.HHmmss}.gz"/>
      <param name="ActiveFileName" value="${catalina.base}/logs/catalina.log" />
    </rollingPolicy>
    <layout> 
      <param name="ConversionPattern" value="%-5p %c{1} - %m%n"/> 
    </layout> 
  </appender> 

  <root> 
    <priority value ="debug" /> 
    <appender-ref ref="console" /> 
  </root>
  
</log4j:configuration>