Linux things 🐧

un blog sur les technologies des logiciels libres et autres digressions

auto-hébergement hybride

Thu, 25 May 2017 00:00:00 UTC
# docker   # traefik   # vpncloud   # syncthing  

Dans la sĂ©rie sur Docker, voici un nouvel article sous un titre un peu Ă©trange qui Ă©veillera sans doute votre curiositĂ© et c’est bien le but. Disclamer : cet article est tout frais la peinture n’est pas sĂšche, avec le temps son contenu sera modifiĂ©.

Historique

Pour les geeks sortant d’un caisson cryogĂ©nique, voici les 2 premiers lien expliquant ce qu’est l’auto-hĂ©bergement : auto-hebergement et sur wikipedia. En rĂ©sumĂ© cela consiste Ă  hĂ©berger ses donnĂ©es sur ses machines personnelles chez soi au lieu de les stocker chez Google, Facebook, Apple, etc.

Cela vient d’une dĂ©marche qui consiste Ă  dire que personne n’est mieux placĂ© que soit mĂȘme pour avoir une garantie sur la protection de ses donnĂ©es personnelles. Cela implique un minimum de compĂ©tences techniques et je ne vais malheureusement pas contedire cela ici.

Motivation

Je n’ai jamais Ă©tĂ© un fervent partisant de ce concept. Cela demandait trop de temps et de confiance dans son propre matĂ©riel afin de ne pas perdre mes donnĂ©es. Quid de celles-ci si le PC crame ou les disques dur ? Entre temps sont sorties les rĂ©vĂ©lations de Edward Snowden qui ont contre-balancĂ© Ă  mes yeux les inconvĂ©nients de l’AH.
Une solution est de louer des serveurs physiques ou vrituels chez un hĂ©bergeur, type OVH, afin de dĂ©ployer les logiciels libres permettant de gĂ©rer ses donnĂ©es. Cette solution qui semble plus rassurante que l’AH, puisque les serveurs sont dans un datacenter, la sĂ©curitĂ© physique Ă©tant garantie, mais il y a aussi une bande passante beaucoup plus importante que son ADSL ou mĂȘme sa fibre chez soi.

Les problÚmes de cette solution sont le coût du stockage et le coût CPU/RAM. Or un simple PC récent, type core I5, 8Go de RAM et 2 To de disque dur ne coute presque rien en comparaison du coût annuel chez un hébergeur à configuration identique.

J’avais ces donnĂ©es en tĂȘte, mais il me restait un dernier frein.
l’AH implique que les services hĂ©bergĂ© chez soi, comme un blog, les visiteurs arrivent sur l’IP publique fournie par son FAI… Or il est trĂšs facile de faire un DDOS vers un accĂšs Internet personnel ; dans ce cas je doute que l’usage d’Internet chez soi Ă  ce moment lĂ  soit trĂšs rĂ©actif …

Il restait aux hĂ©bergeurs de serveurs l’avantage d’offrir de base des protection anti DDOS ce qui est loin d’ĂȘtre superflu de nos jours.

Hybridation

L’idĂ©al serait de mixer ces 2 types d’environnement. Techniquement il suffit de monter un VPN entre une VM chez un hĂ©bergeur vers un serveur chez soi. Ensuite un serveur web configurĂ© en reverse proxy afin de relayer les requĂȘtes HTTP vers ses serveurs applicatifs. Le serveur web expose ainsi une IP de l’hĂ©bergeur, et non pas l’IP de son FAI et il bĂ©nĂ©ficie de l’anti DDOS.
Je prĂ©sume que je n’ai rien inventĂ© et que certains le font depuis longtemps mais jusqu’Ă  prĂ©sent, cette solution impliquait de modifier Ă  chaque ajout/suppression d’un service la configuration du reverse proxy. Idem si l’on souhaitait rĂ©partir la charge entre 2 serveurs physique chez soi ou placer un serveur supplĂ©mentaire chez un proche afin de rendre l’infrastructure plus rĂ©siliente.

La mise en place du VPN pouvait ĂȘtre Ă©galement laborieuse avec OpenVPN. Tout cela n’a rien d’impossible mais ma configuration devrait rendre tout cela bien plus simple et automatisĂ©.

Elle est constitué de docker swarm , traefik, vpncloud et syncthing

Description

Le serveur

pour ce projet j’ai recyclĂ© une tour de 2007 (aille la facture Ă©lec)

  • Intel® Core™2 Quad CPU Q6600 @ 2.40GHz
  • 8 Go de RAM (max de la carte mĂšre)
  • 2 HDD de 1To

Je ne dispose que d’une seule machine, or un docker swarm en nĂ©cessite au moins 3 pour avoir un intĂ©rĂȘt (1 leader, 2 workers). J’ai choisi de virtualiser 3 VMs centOS (qui seront des nodes worker docker) avec KVM (libvirtd/qemu) avec un bridge rĂ©seau sur le serveur physique afin de les voir comme des machines dans mon rĂ©seau local. Une configuration avec des VMs NATĂ© aurait fonctionnĂ© Ă©galement mais moins souple, par exemple pour scripter des tĂąches avec ansible directement vers l’IP des VMs.

Le serveur est rĂ©liĂ© au rĂ©seau en CPL, ce qui est pratique lorsqu’il est dans un placard de la cuisine et qu’on ne peut pas tirer un cable jusqu’Ă  la box.

A terme l’idĂ©al serait d’avoir 1 ou 2 barbones rĂ©cent, pour moins de consommation Ă©lectrique que cette tour ; ou pourquoi pas un cluster de raspberry pi et supprimer ainsi la couche de virtualisation. Attention dans ce cas avec docker il est nĂ©cessaire d’utiliser des conteneurs pour ARM comme ceux fournis par hypriot ce qui limite le choix logiciel. De plus il sera nĂ©cessaire d’acheter une carte d’extension SATA afin d’y connecter ses disques durs ; outre le surcoĂ»t non nĂ©gligeable, le bus interne de Pi sera toujours en USB … Quant Ă  utiliser le disque flash si le but est d’y stocker ses photos/vidĂ©os c’est sans doute une fausse bonne idĂ©e. Mais je serais ravi d’ĂȘtre contredis Ă  ce sujet.

Docker swarm

Mon objectif avec swarm est de pouvoir instancier rapidement n’importe quel logiciel libre dans mon infra sans me prĂ©occuper de la couche matĂ©riel ni du setup complet du service. Une simple ligne de commande doit me permettre d’utiliser un service et de l’exposer sur Internet quelques secondes/minutes aprĂšs. Il reste malgrĂ© tout une part de configuration mais elle reste bien plus souple qu’une installation/configuration Ă  l’ancienne. De plus le serveur hĂŽte n’est pas polluĂ© par les nombreuses blibliothĂšques nĂ©cessaires aux services.
La moins ancienne maniĂšre de faire serait d’installer une VM par service. Cependant une VM consomme Ă©normĂ©ment de ressource ce qui limite leur nombre, de plus on multiplie le problĂšme de la mise en place des services, et de la cohabitation de leurs bibliothĂšques (diffĂ©ntes versions de PHP/Ruby/Python/Java par exemple).
Avec swarm j’ai certes 3 VMs mais qui servent uniquement Ă  hĂ©berger des conteneurs, et je peux espĂ©rer en avoir une dizaine par VMs (2Go de RAM/VM ce qui en laisse 2go pour le serveur hĂŽte). A long terme j’espĂšre migrer sur 3 serveurs barmetal avec uniquement un dĂ©mon docker.

vpncloud.rs

Pour relier ma VM chez OVH et mes 3 VMs locales, je souhaitais utiliser un vpn simple Ă  mettre en oeuvre, sans serveur centralisĂ©, en rĂ©seau maillĂ©. J’avais utilisĂ© il y a quelques annĂ©es freelan qui correspondait Ă  ce cahier des charges. Cependant le projet ne propose de paquet rpm (j’utilise maintenant centos) et je ne me sentais pas me lancer dans une compilation laborieuse. Il n’est pas dit que je ne le reteste pas Ă  l’avenir.

J’ai testĂ© meshbird qui est trĂšs simple Ă  utiliser et Ă  compiler (merci Golang). J’ai eu malheureusement des bugs lors de mon test.

En fouillant github j’ai trouvĂ© vpncloud.rs. Lorsqu’on oublie pas d’installer la libtool (sick) il se compile tout seul, ce qui est pratique car le projet ne propose pas encore de rpm.
CodĂ© en langage Rust, le makefile installe les bibliothĂšque nĂ©cessaires grĂące Ă  cargo. PlutĂŽt long Ă  compiler par rapport Ă  Go, on obtient un binaire trĂšs performant . Il utilise la libsodium (bisous openssl) et le wiki propose 2 configurations en fonction de l’architecture souhaitĂ©e.

Le principe d’un rĂ©seau VPN sans serveur central, est que chaque noeud Ă©coute sur un port (UDP autant que possible) exposĂ© sur Internet. On indique le DNS de chaque noeud dans le fichier de config puis ils se mettent en relation une fois le chiffrement/dĂ©chiffrement rĂ©alisĂ© (dĂ©solĂ© pour l’explication vague).
Le problĂšme se pose pour son accĂšs Internet, puisqu’on ne dispose que d’une IP publique (cet article est basĂ© sur un environnement en IPv4 /o\ ) or je souhaite connecter 3 VMs interne vers une VM externe chez un hĂ©bergeur.

La solution est de configurer la box/routeur du FAI pour qu’elle forward le port UDP du VPN vers l’IP fixe du serveur physique interne. Seul ce dernier aura le client VPN et pourra se connecter vers la VM “dans le cloud”. Ensuite une simple rĂšgle de routage permettra aux VMs de se parler en passant par l’interface rĂ©seau du serveur hĂŽte.

L’explication en image avec cette page Dial in Tutorial.

dial_in_scenario.png

Le problĂšme de cette infra est que l’on ne dispose bien souvent pas d’une IP fixe chez soi. Ma box permet de se connecter vers des services de type dyndns, mais un simple script lancĂ© dans un cron pourra offrir le mĂȘme service. Voir la page Dynamic DNS et les conseils de Korben.
Ceci fait, dans la configuration du client vpncloud chez son hĂ©bergeur on indiquera l’adresse dns obtenue, (exemple : chezmoi.ddns.net). Ainsi lorsque le FAI change l’IP, le vpncloud “dans le cloud” pourra se reconnecter sans problĂšme Ă  la box.

syncthing

Le principe de docker est qu’un conteneur est jetable , un simple docker run permet de relancer le service, on peut ainsi en supprimer/instancier trĂšs rapidement en fonction des besoins. Cependant tout ce que contient le conteneur disparait lorsqu’il est supprimĂ©. Il faut donc crĂ©er des volumes, qui sont de simple rĂ©pertoire sur le serveur hĂŽte (ou un montage NFS/glusterfs) puis on les attache au conteneur Ă  son lancement. Ainsi le conteneur Ă©crit dans le volume persistant, si le conteneur est dĂ©tuit et relancĂ© il retrouvera ses donnĂ©es immĂ©diatement.
Dans un swarm le conteneur peut etre relancĂ© sur un autre noeud, qui correspond Ă  une autre machine (VM ou pas). En effet si le noeud oĂč Ă©tait le conteneur a plantĂ© ou Ă  Ă©tĂ© Ă©teint (VM plantĂ©, VM stoppĂ©, raspberry pi Ă©teint, …), le noeud leader du swarm dĂ©tecte le problĂšme et rĂ©instancie les conteneurs qui Ă©taient sur ce noeud vers un autre noeud en vie. Les volumes qui contiennent les donnĂ©es sont par contre toujours sur le noeud stoppĂ©, en relancant les conteneurs sur un autre, ils ne retrouveront pas leurs donnĂ©es… boom.

Il existe plusieurs solutions pour palier Ă  cela, par exemple utiliser un montage NFS sur chaque noeud vers un serveur NFS. Cela implique d’avoir un serveur NFS dĂ©diĂ© Ă  cela, mais qui sera un SPOF de l’infra. De plus si je souhaite avoir un noeud docker situĂ© Ă  des km de chez moi, les temps d’accĂšs NFS via le VPN risquent de tout plomber. ou pire si le lien VPN tombe.
Une autre solution est d’utiliser un systĂšme de fichier distribuĂ© type glusterFS. Chaque noeud (ici VM) fera un montage gluster grĂące Ă  un client glusterfs, mais cela implique que chaque VM soient aussi serveur glusterFS… CompliquĂ© Ă  mettre en oeuvre, sans parler des problĂšmes Ă  gĂ©rer avec ce logiciel (split brain).
Le plus simple pour une infra HA est d’utiliser un outil de synchronisation de fichiers comme l’explique cet article avec bittorrentsync, Distributed volumes with BitTorrent Sync. J’ai choisi la mĂȘme architecture mais avec syncthing qui a l’avantage d’ĂȘtre libre.

traefik

traefik est un serveur HTTP reverse proxy en Golang. C’est un superbe projet libre dĂ©veloppĂ© par un Lyonnais (cocorico), Emile Vauge propose d’ailleurs ses services via sa startup containous. L’intĂ©rĂȘt de cet outil est qu’il se connecte directement Ă  l’API de docker, lorsqu’on lance un conteneur traefik est immĂ©diatement informĂ© et expose automatiquement le service. De plus il peut demander de lui mĂȘme un certificat let’s encrypt. MĂȘme s’il ne pourra pas atteindre les performances d’un nginx codĂ© en C, il est trĂšs rapide. Pour les curieux un lien vers l’histoire de La naissance de Traefik.io.

Configuration

VPN

prérequis

Je prĂ©suppose que vous avez une VM chez un hĂ©bergeur. Je dĂ©conseille l’usage d’une VM chez scaleway tant que ce bug ne sera pas corrigĂ©, docker swarm ne fonctionne pas du tout avec leur kernel 4.10.8-docker. Pour ma part j’ai migrĂ© vers une VM Ă  3€ chez OVH qui suffit largement. Cette VM sera leader du swarm, c’est elle qui gĂ©rera les conteneurs dans la cuisine sur les noeuds workers. On peut amĂ©liorer la rĂ©silience de l’infra en ajoutant 2 VMs louĂ©es qui seront manager. Si la VM qui possĂšde le node leader tombe un des 2 autres managers sera Ă©lu leader du swarm (il faut 1 leader ou 3 mais pas 2). La VM leader sert uniquement Ă  instancier l’image traefik.

Pour le VPN si vous n’avez pas d’IP fixe il faut crĂ©er un compte sur un service de dns dynamique, voir l’article de Korben , pour ma part j’ai pris un compte gratuit noip. Ensuite si votre box le permet lui indiquer votre compte dyndns afin qu’elle le mettre Ă  jour avec votre nouvelle IP, soit lancer dans un cron un script qui fera ce travail. Enfin il faut configurer votre box pour qu’elle forward le port UDP du VPN (ici 3210) vers l’IP de votre serveur interne. Cela implique que le dhcp de votre box lui fournisse une IP fixe (associer l’adresse MAC de la carte de votre serveur Ă  une IP).

config

Sur le serveur physique on compile vpncloud.rs.

yum install rust libtool cargo git
git clone https://github.com/dswd/vpncloud.rs
cd vpncloud.rs
make build
cp target/release/vpncloud /usr/local/bin

On créé le fichier de configuration, bien remplacer achanger et achanger2 par ce que vous-voulez.

cat /etc/vpncloud/fredix.net

# This configuration file uses the YAML format.

# This configuration can be enabled/disabled and controlled by adding the
# network to `/etc/default/vpncloud` and starting/stopping it via
# `/etc/init.d/vpncloud start/stop` on non-systemd systems and via
# `systemctl enable/disable vpncloud@NAME` and
# `service vpncloud@NAME start/stop` on systemd systems.


# The port number on which to listen for data.
# Note: Every VPN needs a different port number.
port: 3210

# Address of a peer to connect to. The address should be in the form
# `addr:port`. If the node is not started, the connection will be retried
# periodically. This parameter can be repeated to connect to multiple peers.
# Note: Several entries can be separated by spaces.
#peers:
#  - node2.example.com:3210
#  - node3.example.com:3210

peers: []

# Peer timeout in seconds. The peers will exchange information periodically
# and drop peers that are silent for this period of time.
#peer_timeout: 1800

# Switch table entry timeout in seconds. This parameter is only used in switch
# mode. Addresses that have not been seen for the given period of time  will
# be forgot.
#dst_timeout: 300

# An optional token that identifies the network and helps to distinguish it
# from other networks.
magic: "achanger"

# An optional shared key to encrypt the VPN data. If this option is not set,
# the traffic will be sent unencrypted.
#shared_key: ""
shared_key: "achanger2"

# The encryption method to use ("aes256", or "chacha20"). Most current CPUs
# have special support for AES256 so this should be faster. For older
# computers lacking this support, only CHACHA20 is supported.
crypto: chacha20
#crypto: aes256

# Name of the virtual device. Any `%d` will be filled with a free number.
#device_name: "vpncloud%d"
device_name: "vpncloud%d"

# Set the type of network. There are two options: **tap** devices process
# Ethernet frames **tun** devices process IP packets. [default: `tap`]
#device_type: tap
device_type: tap

# The mode of the VPN. The VPN can like a router, a switch or a hub. A **hub**
# will send all data always to all peers. A **switch** will learn addresses
# from incoming data and only send data to all peers when the address is
# unknown. A **router** will send data according to known subnets of the
# peers and ignore them otherwise. The **normal** mode is switch for tap
# devices and router for tun devices. [default: `normal`]
#mode: normal
mode: normal

# The local subnets to use. This parameter should be in the form
# `address/prefixlen` where address is an IPv4 address, an IPv6 address, or a
# MAC address. The prefix length is the number of significant front bits that
# distinguish the subnet from other subnets. Example: `10.1.1.0/24`.
# Note: Several entries can be separated by spaces.
subnets:
#  - 10.1.1.0/24
  - 192.168.254.0/24

# A command to setup the network interface. The command will be run (as
# parameter to `sh -c`) when the device has been created to configure it.
# The name of the allocated device will be available via the environment
# variable `IFNAME`.
#ifup: ""
ifup: "ifconfig $IFNAME 192.168.254.254/24 mtu 1400; sysctl -w net.ipv4.ip_forward=1"

# A command to bring down the network interface. The command will be run (as
# parameter to `sh -c`) to remove any configuration from the device.
# The name of the allocated device will be available via the environment
# variable `IFNAME`.
#ifdown: ""
ifdown: "ifconfig $IFNAME down"

# Store the process id in this file when running in the background. If set,
# the given file will be created containing the process id of the new
# background process. This option is only used when running in background.
#pid_file: ""

# Change the user and/or group of the process once all the setup has been
# done and before spawning the background process. This option is only used
# when running in background.
#user: ""
#group: ""

si vous avez un vieux CPU comme moi il faut mettre définir la crypto en chacha20, sinon mettre aes256. Puis on créé le fichier init systemd /usr/lib/systemd/system/vpncloud@.service

cat /usr/lib/systemd/system/vpncloud\@.service

[Unit]
Description=VpnCloud network '%I'
Before=systemd-user-sessions.service docker.service

[Service]
Type=forking
ExecStart=/usr/local/bin/vpncloud --config /etc/vpncloud/%i.net --daemon --log-file /var/log/vpncloud-%i.log --pid-file /run/vpncloud-%i.run
WorkingDirectory=/etc/vpncloud
PIDFile=/run/vpncloud-%i.run

[Install]
WantedBy=multi-user.target

l’activer et le lancer

systemctl enable vpncloud@fredix.service
systemctl start vpncloud@fredix.service

On doit voir une interface vpncloud0

vpncloud0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1400
	inet 192.168.254.254  netmask 255.255.255.0  broadcast 192.168.254.255

Il faut copier le binaire vpncloud sur la VM de l’hĂ©bergeur, puis crĂ©er son fichier de config, remplacer chezmoi.ddns.net par le DNS fourni par le service choisi.

cat /etc/vpncloud/fredix.net

# This configuration file uses the YAML format.

# This configuration can be enabled/disabled and controlled by adding the
# network to `/etc/default/vpncloud` and starting/stopping it via
# `/etc/init.d/vpncloud start/stop` on non-systemd systems and via
# `systemctl enable/disable vpncloud@NAME` and
# `service vpncloud@NAME start/stop` on systemd systems.


# The port number on which to listen for data.
# Note: Every VPN needs a different port number.
port: 3210

# Address of a peer to connect to. The address should be in the form
# `addr:port`. If the node is not started, the connection will be retried
# periodically. This parameter can be repeated to connect to multiple peers.
# Note: Several entries can be separated by spaces.
peers:
#  - node2.example.com:3210
#  - node3.example.com:3210

  - chezmoi.ddns.net:3210

# Peer timeout in seconds. The peers will exchange information periodically
# and drop peers that are silent for this period of time.
#peer_timeout: 1800

# Switch table entry timeout in seconds. This parameter is only used in switch
# mode. Addresses that have not been seen for the given period of time  will
# be forgot.
#dst_timeout: 300

# An optional token that identifies the network and helps to distinguish it
# from other networks.
magic: "achanger"

# An optional shared key to encrypt the VPN data. If this option is not set,
# the traffic will be sent unencrypted.
#shared_key: ""
shared_key: "achanger2"

# The encryption method to use ("aes256", or "chacha20"). Most current CPUs
# have special support for AES256 so this should be faster. For older
# computers lacking this support, only CHACHA20 is supported.
crypto: chacha20
#crypto: aes256

# Name of the virtual device. Any `%d` will be filled with a free number.
#device_name: "vpncloud%d"
device_name: "vpncloud%d"

# Set the type of network. There are two options: **tap** devices process
# Ethernet frames **tun** devices process IP packets. [default: `tap`]
#device_type: tap
device_type: tap

# The mode of the VPN. The VPN can like a router, a switch or a hub. A **hub**
# will send all data always to all peers. A **switch** will learn addresses
# from incoming data and only send data to all peers when the address is
# unknown. A **router** will send data according to known subnets of the
# peers and ignore them otherwise. The **normal** mode is switch for tap
# devices and router for tun devices. [default: `normal`]
#mode: normal
mode: normal

# The local subnets to use. This parameter should be in the form
# `address/prefixlen` where address is an IPv4 address, an IPv6 address, or a
# MAC address. The prefix length is the number of significant front bits that
# distinguish the subnet from other subnets. Example: `10.1.1.0/24`.
# Note: Several entries can be separated by spaces.
subnets:
#  - 10.1.1.0/24
  - 192.168.254.1/32

# A command to setup the network interface. The command will be run (as
# parameter to `sh -c`) when the device has been created to configure it.
# The name of the allocated device will be available via the environment
# variable `IFNAME`.
#ifup: ""
ifup: "ifconfig $IFNAME 192.168.254.1/16 mtu 1400; route add 192.168.0.0/24 via $IFNAME"

# A command to bring down the network interface. The command will be run (as
# parameter to `sh -c`) to remove any configuration from the device.
# The name of the allocated device will be available via the environment
# variable `IFNAME`.
#ifdown: ""
ifdown: "ifconfig $IFNAME down"

# Store the process id in this file when running in the background. If set,
# the given file will be created containing the process id of the new
# background process. This option is only used when running in background.
#pid_file: ""

# Change the user and/or group of the process once all the setup has been
# done and before spawning the background process. This option is only used
# when running in background.
#user: ""
#group: ""

créer le fichier init systemd, enable et start comme au dessus. On doit voir maintenant une interface réseau vpncloud0

vpncloud0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1400
	inet 192.168.254.1  netmask 255.255.0.0  broadcast 192.168.255.255

Sur la VM louĂ©e on ajoute une route vers son rĂ©seau local en indiquant l’interface vpncloud0 Ă  utiliser et l’ip de son serveur local (Ă  changer avec la votre)

route add -net 192.168.0.0 netmask 255.255.0.0 gw 192.168.0.36 dev vpncloud0

pour rendre cette configuration persistente sur centOS il faut créer le fichier suivant

cat /etc/sysconfig/network-scripts/route-eth0

	192.168.0.0/24 via 192.168.0.36 dev vpncloud0

Si tout va bien on doit pouvoir pinger la passerelle du serveur local. Les VM chez soit ne sont pas encore joignable, sur chacune d’elle il suffit de leur ajouter cette route

route add -net 192.168.254.0 netmask 255.255.255.0 gw 192.168.0.36

on la rend persistente

cat /etc/sysconfig/network-scripts/route-eth0

192.168.254.0/24 via 192.168.0.36 dev eth0

Ainsi seul les VMs dans lesquelles on a ajouté cette rÚgle de routage seront joignable depuis la VM louée, ce qui est mieux pour la sécurité.

Docker

Sur une VM CentOS il faut suivre ce tutoriel pour installer le dĂ©pĂŽt docker. Faire de mĂȘme sur les VM ou serveur physique chez soi.

on initialise le swarm sur la VM louĂ©e en prĂ©cisant l’ip de l’interface vpncloud0

docker swarm init \  
    --listen-addr 192.168.254.1 \
    --advertise-addr 192.168.254.1

docker affiche la commande Ă  lancer sur les nodes workers pour qu’ils rejoignent le swarm, on peut l’obtenir Ă  tout moment avec cette commande

docker swarm join-token worker
# To add a worker to this swarm, run the following command:

docker swarm join \
   --token unetoken\
   192.168.254.1:2377

on peut vérifier que le swarm est bien relié

 docker node ls
ID                           HOSTNAME              STATUS  AVAILABILITY  MANAGER STATUS
gxy553ef1eudvfwidxh95dqa9    centos-2.localdomain  Ready   Active        
nd0edqf6txl1zry90bh4c7f6j    centos-1.localdomain  Ready   Active        
vyfozvy9gmu1bjf7txdr0qk2y *  vm.ovh.net     Ready   Active        Leader
zjstkea2kdqql5ujsgyeau3jl    centos-3.localdomain  Ready   Active        

on va taguer les noeuds worker, l’objectif et de pouvoir lancer des conteneurs uniquement dans nos VMs Ă  la maison. En effet si on loue une petite VM pas cher il n’est pas concevable que les conteneurs puissent se retrouver sur elle et la saturer. Par exemple imaginez que vous souhaitiez hĂ©berger une instance mastodon et que les 5 conteneurs nĂ©cessaire s’y retrouve dessus (rails, redis, postgresql…)

on execute cette commande sur le leader, pour les 3 VMs, le hostname dépend du nom de vos VMs chez vous

docker node update --label-add location=home centos-2.localdomain
docker node update --label-add location=home centos-2.localdomain
docker node update --label-add location=home centos-3.localdomain

le swarm est prĂȘt Ă  accueillir des conteneurs, voici un exemple avec la contrainte, mais il manque encore le reverse proxy http

docker service create --name test_bee --constraint 'node.labels.location == home' --network traefik-net --label traefik.frontend.rule=Host:test.fredix.xyz --label traefik.port=8080 fredix/test_bee

L’intĂ©rĂȘt du swarm est qu’en cas de dĂ©faillance d’un node, le leader instanciera automatiquement tous ses conteneurs vers un autre node. Cependant on peut souhaiter dĂ©sactiver un node manuellement pour mettre Ă  jour la VM par exemple, ou remplacer un disque. Rien de plus simple, quelques commandes avant

on liste les nodes :

docker node ls
ID                           HOSTNAME              STATUS  AVAILABILITY  MANAGER STATUS
gxy553ef1eudvfwidxh95dqa9    centos-2.localdomain  Ready   Active        
nd0edqf6txl1zry90bh4c7f6j    centos-1.localdomain  Ready   Active        
vyfozvy9gmu1bjf7txdr0qk2y *  vps410678.ovh.net     Ready   Active        Leader
zjstkea2kdqql5ujsgyeau3jl    centos-3.localdomain  Ready   Active        

on se connecte en ssh sur le noeud choisi pour vérifier

root@centos-2 ~]# docker ps
CONTAINER ID        IMAGE                                                                                         COMMAND                  CREATED             STATUS              PORTS               NAMES
2b666e08291d        gogs/gogs@sha256:1d3b11cd430cee3d526286876b2b4bd5173d623a4af4af7ad03cb9ab11362b68             "/app/gogs/docker/..."   12 hours ago        Up 12 hours         22/tcp, 3000/tcp    gogs.1.ko7abchgwnssgrojy5944jt8a
4c6b98dd728c        fredix/nodecast.net@sha256:5ea18a33a4fc89b510af96777b63208c6cbb9380b3a659605c177a22af4caa00   "/bin/sh -c /app/n..."   12 hours ago        Up 12 hours         8080/tcp            nodecast.1.ihutzgl5bcaeel8zkpbm30aso
2ddbf7039029        fredix/test_bee@sha256:0e12268edae9dfceddcc099506df8707866cf97ca39967708ffbc8ce942afff1       "/bin/sh -c /app/t..."   12 hours ago        Up 12 hours         8080/tcp            test_bee.1.3wsotxc6094la8ww34397i8y8

on le désactive depuis le leader

docker node update --availability drain centos-2.localdomain

le noeud est passé en mode drain

docker node ls
ID                           HOSTNAME              STATUS  AVAILABILITY  MANAGER STATUS
gxy553ef1eudvfwidxh95dqa9    centos-2.localdomain  Ready   Drain         
nd0edqf6txl1zry90bh4c7f6j    centos-1.localdomain  Ready   Active        
vyfozvy9gmu1bjf7txdr0qk2y *  vps410678.ovh.net     Ready   Active        Leader
zjstkea2kdqql5ujsgyeau3jl    centos-3.localdomain  Ready   Active        

docker ps sur le noeud, au bout de quelques secondes il est vide

[root@centos-2 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

si on liste les services ils sont toujours là, mais déplacés

[root@vps410678 ~]# docker service ls
ID            NAME                MODE        REPLICAS  IMAGE
67qdbifaytkk  nodecast            replicated  1/1       fredix/nodecast.net:latest
6xymi9ok4p6z  traefik             replicated  1/1       containous/traefik:latest
nrx0e32un5eg  gogs                replicated  1/1       gogs/gogs:latest
ozk0527bxa1k  test_bee            replicated  1/1       fredix/test_bee:latest
vazbqubeofuf  hugo                replicated  1/1       fredix/hugo:latest

une fois l’action de maintenance effectuĂ©e sur le noeud on rĂ©active le node

docker node update --availability active centos-2.localdomain

par contre les conteneurs déplacés restent à leur place, le node centos-2 reste vide à moins de lancer des conteneurs. On peut cependant forcer de les déplacer

docker service update --force nodecast

cela devrait forcer docker à déplacer le conteneur nodecast sur le node le moins chargé, donc centos-2

[root@centos-2 ~]# docker ps
CONTAINER ID        IMAGE                                                                                         COMMAND                  CREATED              STATUS              PORTS               NAMES
3e968837fa3c        fredix/nodecast.net@sha256:5ea18a33a4fc89b510af96777b63208c6cbb9380b3a659605c177a22af4caa00   "/bin/sh -c /app/n..."   About a minute ago   Up 54 seconds       8080/tcp            nodecast.1.s16q3biuiuafnrlv1885mun89

DerniĂšre commande est pas des moindres. On a un conteneur lancĂ©, mais entre temps l’image a Ă©tĂ© mise Ă  jour par l’auteur, ou soit mĂȘme. Rien de plus simple pour mettre Ă  jour l’instance

docker service update --image fredix/hugo hugo

Ici je met Ă  jour mon conteneur hugo qui utilise une image Ă  moi. Docker relance tout seul mon conteneur mis Ă  jour…

Une autre commande utile permet de passer un worker en leader

 docker node promote nom-du-node

ou l’inverse, passer un leader en worker

  docker node demote nom-du-node

Docker swarm possĂšdent de nombreuses commandes utiles dont je n’ai fais qu’un petit tour ici. Le grand concurrent de swarm est kubernetes. Il possĂšde une interface web d’administration trĂšs complĂšte, cependant je le trouve trop complexe et overkill pour un contexte en Auto-HĂ©bergement comme ici. Mais comme souvent il est possible que je change d’avis.

Traefik

Traefik va nous permettre de rendre nos conteneurs accessible depuis Internet dynamiquement, mais il va de plus gĂ©nĂ©rer un certificat let’s encrypt automatiquement. Traefik va uniquement tourner sur le leader, et c’est lui qui exposera l’ip de l’hĂ©bergeur.

On doit lui crĂ©er un rĂ©pertoire de travail et son fichier de configuration (j’utilise /traefik comme point de montage)

cat /traefik/etc/traefik.toml

	traefikLogsFile = "/log/traefik.log"
	logLevel = "WARNING"
	defaultEntryPoints = ["http", "https"]
	[entryPoints]
	  [entryPoints.http]
	  address = ":80"
	    [entryPoints.http.redirect]
	      entryPoint = "https"
	  [entryPoints.https]
	  address = ":443"
	    [entryPoints.https.tls]
	entryPoint = "https"
	[acme]
	email = "fredix@protonmail.com"
	#storageFile = "/certs/acme.json"
	storageFile = "/etc/traefik/acme/acme.json"
	entryPoint = "https"
	acmeLogging = true
	onDemand = false
	OnHostRule = true
	#[[acme.domains]]
	# main = "swarm.fredix.xyz"
	[[acme.domains]]
	  main = "fredix.xyz"
	  sans = ["www.fredix.xyz", "test.fredix.xyz", "gogs.fredix.xyz", "wallabag.fredix.xyz", "whoami0.fredix.xyz", "swarm.fredix.xyz", "pouet.fredix.xyz"]
	[[acme.domains]]
	  main = "nodecast.net"
	  sans = ["www.nodecast.net"]
	[web]
	address = ":8080"
	[docker]
	domain = "traefik"
	endpoint = "unix:///var/run/docker.sock"
	watch = true
	exposedbydefault = true

On voit ici la configuration de mes services, la doc est trĂšs complĂšte https://docs.traefik.io/toml/

Il faut lui créer un réseau dédié

docker network create --driver=overlay traefik-net

puis on le lance on indiquant les volumes qu’il doit utiliser afin de ne pas perdre sa configuration et les certificats

docker service create --name traefik --constraint=node.role==manager -p 443:443 -p 80:80 -p 8080:8080 --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock --mount type=bind,source=/traefik/etc/,target=/etc/traefik/ --mount type=bind,source=/traefik/log/,target=/log/ --mount type=bind,source=/traefik/etc/certs,target=/certs --network traefik-net  containous/traefik --docker --docker.swarmmode --docker.domain=traefik --docker.watch --web

il bind les ports 80,443 mais aussi le 8080. Ce port permet de consulter les diffĂ©rents conteneurs qu’il gĂšre comme on peut le voir chez moi http://fredix.xyz:8080 , c’est interface n’est qu’en lecture.

On peut maintenant lancer un conteneur dans le swarm

docker service create --name test_bee --constraint 'node.labels.location == home' --network traefik-net --label traefik.frontend.rule=Host:test.fredix.xyz --label traefik.port=8080 fredix/test_bee

Ce conteneur est basĂ© sur une image tout simple que j’ai crĂ©Ă© et disponible sur https://hub.docker.com/r/fredix/test_bee/. Il suffit de remplacer Host:test.fredix.xyz par votre sousdomaine.domain.tld pour tester. Ce conteneur lance un processus en Go qui Ă©coute sur le port 8080.

Le plus pĂ©nible est de devoir crĂ©er chez l’opĂ©rateur qui gĂšre votre DNS un sous domaine par service, or il suffit de mettre un jocker * en type A vers l’IP de votre hĂ©bergeur pour que tous les sous-domaines soient rĂ©solu.
Au final il suffira de lancer un service par docker pour qu’il soit installĂ© Ă  domicile puis exposĂ© automatiquement sur votre domaine.

Syncthing

cette partie est sans doute la plus pĂ©nible mais nĂ©cessaire pour rĂ©soudre le problĂšme de rĂ©plication de vos donnĂ©es. En effet si les conteneurs peuvent ĂȘtre migrĂ© d’un node Ă  l’autre, donc d’une machine (vm ou physique) Ă  une autre, les donnĂ©es dans les volumes ne bougeront pas. Dans l’exemple si dessus test_bee ce n’est pas gĂ©nant car aucune donnĂ©es n’est gĂ©nĂ©rĂ©, mais pour un conteneur qui gĂ©nĂšre des fichiers ou utilise une base de donnĂ©es c’est fatal.
Il existe des solutions comme NFS, mais cela oblige Ă  avoir un serveur NFS tout le temps disponible, de plus si l’on souhaite lancer un conteneur sur un node hors de chez soi, il devra accĂ©der Ă  ses donnĂ©es par le VPN, ce qui pourrait poser des problĂšmes de latence ou pire une indisponibilitĂ© si le lien VPN est coupĂ© entre les workers.

J’ai choisi d’utiliser syncthing pour synchroniser les rĂ©pertoires qui servent de volumes docker. Syncthing fonctionne en P2P, il peut se connecter entre chaque noeud via un serveur central qui les mets en relation, il peut aussi dĂ©couvrir d’autres noeuds locaux afin de pouvoir les connecter facilement. Nul besoin de cela ici puisque l’on connait l’ip de nos VMs, on va les indiquer en dur et dĂ©sactiver la dĂ©couverte.

Sur des centOS il existe un rpm sur copr, à rajouter comme dépot

cat /etc/yum.repos.d/_copr_decathorpe-syncthing.repo

	[decathorpe-syncthing]
	name=Copr repo for syncthing owned by decathorpe
	baseurl=https://copr-be.cloud.fedoraproject.org/results/decathorpe/syncthing/epel-7-$basearch/
	type=rpm-md
	skip_if_unavailable=True
	gpgcheck=1
	gpgkey=https://copr-be.cloud.fedoraproject.org/results/decathorpe/syncthing/pubkey.gpg
	repo_gpgcheck=0
	enabled=1
	enabled_metadata=1
yum update
yum install syncthing

systemctl enable syncthing@root.service

cat /etc/systemd/system/multi-user.target.wants/syncthing\@root.service

[Unit]
Description=Syncthing - Open Source Continuous File Synchronization for %I
Documentation=man:syncthing(1)
After=network.target
Wants=syncthing-inotify@.service

[Service]
User=%i
ExecStart=/usr/bin/syncthing -no-browser -no-restart -logflags=0
Restart=on-failure
SuccessExitStatus=3 4
RestartForceExitStatus=3 4

[Install]
WantedBy=multi-user.target

on start le service puis on le stop 2 secondes aprĂšs

systemctl start syncthing@root.service
sleep 2
systemctl stop syncthing@root.service

cela pour qu’il gĂ©nĂšre ses certificats et son fichier de configuration dans /root/.config/syncthing/

ls .config/syncthing/
cert.pem  config.xml  https-cert.pem  https-key.pem  index-v0.14.0.db  key.pem

Cette operation est à effecturer sur toutes les VMs locale. La VM louée ne stockera pas de volume. On édite ensuite le fichier config.xml
Dans ce fichier il faut indiquer les devices id des 3 VMs dans la section folder et aussi pour chaque device. Dans le champ adresse il faut indiquer l’ip de chaque VM et mettre Ă  false tous les champs Announce. J’ai de plus diminuĂ© le rescanIntervalS Ă  5 afin qu’une modification/ajout d’un fichier sur un noeud soit rĂ©percutĂ© rapidement sur les autres (syncthing-inotify ne semble pas ĂȘtre dispo sur centOS ce qui aurait Ă©vitĂ© un scan rĂ©gulier). Le descendre plus bas risque d’augmenter la charge CPU, mais il y a surement du tuning Ă  faire ici meilleur que le mien. Ici le chemin du rĂ©pertoire Ă  synchroniser est /sync. Il faudra donc lancer les conteneurs un indiquant que leurs volumes de donnĂ©es se trouvent sur /sync/nonduconteneur/
Remplacer VM1/2/3 par l’id unique gĂ©nĂ©rĂ© par syncthing.

	<configuration version="20">
	    <folder id="sync" label="sync" path="/sync/" type="readwrite" rescanIntervalS="5" ignorePerms="false" autoNormalize="true">
		<device id="VM1" introducedBy=""></device>
		<device id="VM2" introducedBy=""></device>
		<device id="VM3" introducedBy=""></device>
		<minDiskFree unit="%">1</minDiskFree>
		<versioning></versioning>
		<copiers>0</copiers>
		<pullers>0</pullers>
		<hashers>0</hashers>
		<order>random</order>
		<ignoreDelete>false</ignoreDelete>
		<scanProgressIntervalS>0</scanProgressIntervalS>
		<pullerSleepS>0</pullerSleepS>
		<pullerPauseS>0</pullerPauseS>
		<maxConflicts>-1</maxConflicts>
		<disableSparseFiles>false</disableSparseFiles>
		<disableTempIndexes>false</disableTempIndexes>
		<fsync>false</fsync>
		<paused>false</paused>
		<weakHashThresholdPct>25</weakHashThresholdPct>
		<minDiskFreePct>0</minDiskFreePct>
	    </folder>
	    <device id="VM1" name="centos-1" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
		<address>tcp://192.168.0.50:22000</address>
		<paused>false</paused>
	    </device>
	    <device id="VM2" name="centos-2" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
		<address>tcp://192.168.0.51:22000</address>
		<paused>false</paused>
	    </device>
	    <device id="VM3" name="centos-3" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
		<address>tcp://192.168.0.52:22000</address>
		<paused>false</paused>
	    </device>
	    <gui enabled="false" tls="false" debugging="false">
		<address>127.0.0.1:8384</address>
		<apikey>apikey</apikey>
		<theme>default</theme>
	    </gui>
	    <options>
		<listenAddress>tcp://192.168.0.50:22000</listenAddress>
		<globalAnnounceServer>default</globalAnnounceServer>
		<globalAnnounceEnabled>false</globalAnnounceEnabled>
		<localAnnounceEnabled>false</localAnnounceEnabled>
		<localAnnouncePort>21027</localAnnouncePort>
		<localAnnounceMCAddr>[ff12::8384]:21027</localAnnounceMCAddr>
		<maxSendKbps>0</maxSendKbps>
		<maxRecvKbps>0</maxRecvKbps>
		<reconnectionIntervalS>60</reconnectionIntervalS>
		<relaysEnabled>true</relaysEnabled>
		<relayReconnectIntervalM>10</relayReconnectIntervalM>
		<startBrowser>false</startBrowser>
		<natEnabled>false</natEnabled>
		<natLeaseMinutes>60</natLeaseMinutes>
		<natRenewalMinutes>30</natRenewalMinutes>
		<natTimeoutSeconds>10</natTimeoutSeconds>
		<urAccepted>0</urAccepted>
		<urUniqueID></urUniqueID>
		<urURL>https://data.syncthing.net/newdata</urURL>
		<urPostInsecurely>false</urPostInsecurely>
		<urInitialDelayS>1800</urInitialDelayS>
		<restartOnWakeup>true</restartOnWakeup>
		<autoUpgradeIntervalH>12</autoUpgradeIntervalH>
		<upgradeToPreReleases>false</upgradeToPreReleases>
		<keepTemporariesH>24</keepTemporariesH>
		<cacheIgnoredFiles>false</cacheIgnoredFiles>
		<progressUpdateIntervalS>5</progressUpdateIntervalS>
		<limitBandwidthInLan>false</limitBandwidthInLan>
		<minHomeDiskFree unit="%">1</minHomeDiskFree>
		<releasesURL>https://upgrades.syncthing.net/meta.json</releasesURL>
		<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
		<tempIndexMinBlocks>10</tempIndexMinBlocks>
		<trafficClass>0</trafficClass>
		<weakHashSelectionMethod>auto</weakHashSelectionMethod>
		<stunServer>default</stunServer>
		<stunKeepaliveSeconds>24</stunKeepaliveSeconds>
		<defaultKCPEnabled>false</defaultKCPEnabled>
		<kcpNoDelay>false</kcpNoDelay>
		<kcpUpdateIntervalMs>25</kcpUpdateIntervalMs>
		<kcpFastResend>false</kcpFastResend>
		<kcpCongestionControl>true</kcpCongestionControl>
		<kcpSendWindowSize>128</kcpSendWindowSize>
		<kcpReceiveWindowSize>128</kcpReceiveWindowSize>
		<minHomeDiskFreePct>0</minHomeDiskFreePct>
	    </options>
	</configuration>

on peut lancer ensuite le service et vĂ©rifier qu’il se connecte aux autres syncthing

systemctl start syncthing@root.service 
systemctl status syncthing@root.service -l

il suffit de vĂ©rifier en faisant un touch /sync/toto et qu’il se rĂ©percute bien dans les autres VMs.

Pour finir ce roman voici comment je lance un conteneur gogs dans mon swarm

docker service create --name gogs --constraint 'node.labels.location == home' --network traefik-net --label traefik.frontend.rule=Host:gogs.fredix.xyz --label traefik.port=3000 --label traefik.backend=gogs --mount type=bind,source=/sync/gogs/,target=/data/ gogs/gogs

La syntaxe de montage des volumes en swarm est diffĂ©rente du docker classique, on voit bien ici que j’indique comme source /sync/gogs (rĂ©pertoire que j’ai crĂ©Ă© auparavant et rĂ©pliquĂ© par syncthing). Gogs peut utiliser un fichier sqlite Ă  la place d’un sgbd, il stoke le fichier ici /sync/gogs/gogs/data/gogs.db

Quelque soit le node ou est lancĂ© gogs, syncthing va synchroniser le rĂ©pertoire de donnĂ©es /sync/gogs/ entre toutes les VMs, l’intĂ©rĂȘt est que si swarm dĂ©place le conteneur, ou que vous le relanciez Ă  la main (docker service rm gogs, docker service create …) il y a des chances qu’il soit lancĂ© sur un autre node. GrĂące Ă  syncthing il pourra retrouver ses donnĂ©es Ă  jour.

Pour amĂ©liorer la rĂ©silience de notre infra, qui dĂ©pend aprĂšs tout d’une ligne chez un FAI et d’une machine Ă  domicile, on peut imaginer dĂ©poser une autre machine chez un proche de confiance. En la reliant au VPN et au swarm elle pourra recevoir des conteneurs si le leader n’arrive plus Ă  joindre ses workers Ă  domicile. On obtient alors un datacenter auto-hĂ©bergĂ© rĂ©parti :)

Le prochain article parlera d’un service bien lourd et complexe Ă  mettre en oeuvre, mastodon un twitter opensource et dĂ©centralisĂ©. De part son architecture il se prĂȘte idĂ©alement Ă  une infrastructure auto-hĂ©bergĂ©e hybride.

quelques liens :

https://docs.traefik.io/user-guide/swarm-mode/
Traefik et Docker, le couple ultime !
Docker Swarm par l’exemple
http://blog.octo.com/tag/swarm/
http://jmkhael.io/traefik-as-a-dynamic-reverse-proxy-for-docker-swarm/
http://blog.wescale.fr/2017/01/04/tutoriel-infastructure-resiliente-et-scalable-avec-swarm-consul-et-traefik/