De quoi va-t-on parler aujourd'hui

Mise en contexte

Car oui, il faut du contexte !

Je gère depuis maintenant un an une instance utilisant Peertube pour héberger des vidéos dédiées au monde vidéoludique francophone.

A l'origine mis en place pour aider au projet et tester la version beta sur un petit serveur seul dans son coin, les limites se sont assez vite faite sentir quand les gens intéressés par le projet ont commencés à devenir plus nombreux (et c'est cool !) Le stockage notamment est une grosse problématique pour les vidéos, et c'est ce point qui a vite présenté un goulot d'étranglement. Pour proposer un service de qualité, je voulais utiliser un stockage réparti ou à minima sécurisé, car les sauvegardes d'une telle quantité de données ne sont pas sans poser également quelques problèmes. Techniquement, il me fallait donc du RAID en mono-serveur, ou un stockage de type distribué (ceph, glusterfs, moosefs, ...)

Malheureusement en cherchant de meilleurs solution de stockage, il est assez vite apparu que les solutions de type Amazon S3 étaient les plus répandues, extensibles, garanties, etc.. Et surtout, totalement peu sûre et chez des GAFAM, chose que je voulais éviter absolument pour un projet dont l'optique est de proposer une alternative à Youtube !

De même, les stockages distribués plus standards que l'on peut trouver chez Scaleway, OVH ou Hetzner coutent une fortune dès que l'on veut de la quantité (au dessus de 500Go, préparez l'héritage pour le payer).

Associé au fait que j'aime bien faire moi-même les choses, j'ai donc décidé de partir sur une infrastructure maison, en place maintenant depuis 6 mois, et c'est celle-ci que je vais maintenant vous proposer :-)

Mise en technique

Mes besoins techniques étaient donc les suivants :

  • Kubernetes pour sa capacité à héberger en cluster des images dockers (et donc les différents outils nécessaire à Peertube)
  • Ceph pour le stockage, bien connu, plutôt robuste avec de très bonnes performances et supporté par Kubernetes
  • Un réseau dédié au stockage et au trafic interne de Kubernetes, donc un réseau privé (dans l'idéal chiffré) avec un bon débit
  • Des serveurs (3 pour commencer) pour faire tourner tout ça, disposant d'un SSD pour le système et d'un disque dur que l'on dédiera au stockage Ceph
  • Des serveurs disposant au minimum de 16Go de RAM et d'un CPU i7 pour faire tourner Peertube tout en tenant la charge supplémentaire de Kubernetes et Ceph

Ceux qui sont un peu familier avec les offres de serveur privés existantes savent que pour les serveurs notamment, c'est une configuration très difficile à trouver. Pour des questions de commodité les hébergeurs proposent en effet des serveurs proposant soit peu de disque en SSD, soit de gros volumes de stockage en HDD, mais pas de mix des deux.

C'est pourquoi je vous propose ici un compromis, avec des hypothèses simplificatrices qui présentent des risques mais que j'ai décidé d'accepter, on en parlera à la fin.

  • 3 serveurs chez Hetzner (qui propose des serveurs peu cher avec un trafic mensuel très correct)
  • 2 disques HDD de 3To par serveur, core i7, 16Go de RAM
  • Un réseau VPN privé monté entre les serveurs pour gérer le trafic Kubernetes et Ceph
  • Un des disques sera entièrement dédié à Ceph. L'autre permettra sur une partition de 100Go d'installer le système, le reste devant à terme être intégré à ceph

Nous voila donc avec la base, le matériel pour lancer notre infrastructure. Y'a plus qu'à !

Installation des serveurs

L'idée de l'installation va être de garder un maximum de place pour le cluster ceph. Les serveurs doivent être jetables et remplaçables facilement, on a donc pas besoin de RAID ou de sécuriser particulièrement le système.

Pour cela on va se baser sur une partition de 100Go pour le système sur le premier disque dur, le deuxième est laissé entièrement vierge. Normalement votre hébergeur doit vous laisser la main sur ce formatage pour des serveurs physiques, chez Hetzner ça passe par un fichier texte à remplir en mode rescue. Debian 9 est une bonne base pour notre infrastructure. J'aurai bien voulu en profiter pour le chiffrer, mais on sort du cadre de cet article et de ma motivation ici :-D

Openvpn

A moins que vous ayez le budget en conséquence pour vous payer un réseau privé sécurisé entre vos serveurs, on va ici se baser sur Openvpn pour monter un réseau chiffré qui servira à la fois pour ceph et pour kubernetes.

L'idée étant de ne pas avoir de point faible dans l'infrastructure, on va contourner la faiblesse client/serveur d'Openvpn en utilisant une technique de Ring : Chaque client VPN est également serveur VPN, et peut donc se connecter aux autre ou recevoir leurs connexions. De cette manière on passe d'une topologie en étoile à une topologie en cercle, plus robuste !

Ring VPN

On va commencer par configurer un serveur VPN en étoile classique et ses clients, de la même manière que dans mon article sur installer openvpn sur debian 9.

Une fois le serveur VPN fonctionnel et vos 2 clients connectés dessus (les 2 autres serveurs physiques), on va le transformer en structure en cercle.

Pour cela c'est très simple, il suffit de copier le fichier de configuration du serveur et ses clefs sur les différents serveurs physiques, exactement comme pour le premier serveur VPN. Une fois ces serveur démarrés, vous disposez de 3 serveurs VPN identiques.

Attention, vous aurez remarqué que le serveur VPN original n'a pas de client, c'est normal car il ne se connectera à personne pour éviter les boucles réseaux. Notez le sens des flèches sur le schéma ci-dessus.

Mais vos clients ne se connectent toujours qu'au premier serveur, et c'est ici qu'arrive la subtilité du cercle. On va modifier les fichiers de configuration clients pour leur dire qu'il y a plusieurs serveurs disponibles :

remote XXX.XXX.XXX.XXX 1994
remote YYY.YYY.YYY.YYY 1994
keepalive 30 120

Ici on dit à notre client VPN d'essayer de se connecter à XXX en premier. S'il n'est pas disponible, il va essayer de se connecter à YYY. Au cas où un serveur est indisponible, keepalive signifie qu'il va essayer toutes les 30 secondes ses différents remote, avec un timeout de 120 secondes.

Ainsi en cas de panne d'un serveur, au bout de 120 secondes max les clients essaieront de se connecter au serveur suivant et reconstruiront le réseau.

Il s'agit ici d'un compromis, car en cas de panne du serveur principal, votre cluster sera dans le noir pendant plusieurs minutes le temps que tout se reconstruise, mais une fois les VPN reconnectés ceph et kubernetes retrouveront leurs petits. Attendez vous également à des performances moindres qu'un véritable réseau dédié mais on fait avec le budget ;-)

En outre cette technique peut tout à fait être utilisée entre différents hébergeurs ou datacenters pour plus de résilience avec des performances très correctes.

Ceph

Une fois le serveur installé, on va commencer par installer ceph, qui sera ainsi prêt à recevoir kubernetes et ses provisionner (les outils kubernetes qui parlent au système de stockage).

Comme je suis un grand utilisateur de Ansible, j'ai décidé d'utiliser le projet *ceph-ansible pour déployer ceph. L'avantage est d'avoir un ensemble de playbook pour gérer le cluster et de pouvoir ainsi rajouter des serveurs de manière très simple. Là encore l'idée est de pouvoir jeter, remplacer et ajouter des serveurs le plus facilement possible.

Ici va se poser la question du type de stockage que vous souhaitez utiliser avec ceph. Les plus connus sont Rados Block Device (RBD) et Ceph FileSystem (cephfs).

Chacun ont des avantages et des inconvénients, mais dans kubernetes la principale différence est que RBD ne peut marcher qu'en ReadWriteOnce, c'est à dire qu'un stockage ne peut être monté que sur une seule node à la fois. D'expérience ça pose quelques problèmes avec le fonctionnement de k8s, notamment en cas de soucis sur une node (crash, perte de réseau, ...) si elle n'a pas pu annoncer qu'elle relachait le RBD, votre pod qui l'utilise ne pourra jamais redémarrer ailleurs ûisque pour k8s le stockage sera toujours utilisé...

Cephfs de son côté présente l'avantage de permettre du ReadWriteMany et en outre comme tout filesystem on lui définit un quota plutôt quu'ne taille fixe, ce qui est pratique quand on a besoin d'un stockage extensible comme pour Peertube. J'ai donc choisi Cephfs comme système de stockage via ceph.

Cela impose quelques subtilités lors du déploiement, notamment la création de pools ceph dédié à cephfs car il a besoin de stocker ses data et ses metadata.

Pour ce faire, on va ajouter dans les variables de déploiement de ceph-ansible les valeurs suivantes : (on parle bien ici de variables ansible)

cephfs: cephfs # nom du filesystem
cephfs_data: cephfs_data # nom du pool de données du cephfs
cephfs_metadata: cephfs_metadata # nom du pool pour les metadata du cephfs

Il vous faudra également créer les pools correspondants:

cephfs_pools:
  - { name: "{{ cephfs_data }}", pgs: "{{ hostvars[groups[mon_group_name][0]]['osd_pool_default_pg_num'] }}", pg_num: "{{ hostvars[groups[mon_group_name][0]]['osd_pool_default_pg_num'] }}", pgp_num: "{{ hostvars[groups[mon_group_name][0]]['osd_pool_default_pg_num'] }}" }
  - { name: "{{ cephfs_metadata }}", pgs: "{{ hostvars[groups[mon_group_name][0]]['osd_pool_default_pg_num'] }}", pg_num: "{{ hostvars[groups[mon_group_name][0]]['osd_pool_default_pg_num'] }}", pgp_num: "{{ hostvars[groups[mon_group_name][0]]['osd_pool_default_pg_num'] }}" }

Pour ce qui est du reste des variables, elles sont communes à un déploiement ceph, je vous renvoie vers la documentation de ceph-ansible

Le nombre de pg à définir notamment va grandement dépendre de la taille de votre cluster. Ceph propose un outil pour le calculer selon vos besoins. A titre indicatif, pour le cluster avec 6 disques de 3To, j'utilise 64 pg par pool.

Kubernetes

Concernant kubernetes, j'ai décidé d'utiliser l'excellent kubespray qui permet de configurer et maintenir un cluster kubernetes avec Ansible.

La configuration par défaut est fonctionnelle, pensez à spécifier votre bootstrap_os pour coller à vos serveur (debian dans mon cas).

Concernant les variables à modifier, quelques bonnes pratiques de sécurités consistent à forcer tout le TLS possible, et à désactiver tout ce qui est authentification anonyme, que ce soit sur l'API du cluster ou sur l'API interne des nodes :

authorization_modes: ['Node', 'RBAC']
kubelet_authentication_token_webhook: false
kubelet_authorization_mode_webhook: false

Pensez également à activer le provisionneur cephfs directement, puisque kubespray le supporte par le biais d'un addons

# CephFS provisioner deployment
cephfs_provisioner_enabled: true
cephfs_provisioner_namespace: "cephfs-provisioner"
cephfs_provisioner_cluster: ceph
cephfs_provisioner_monitors: "10.8.0.1:6789,10.8.0.2:6789,10.8.0.3:6789"
cephfs_provisioner_admin_id: admin
cephfs_provisioner_secret: YourSecret
cephfs_provisioner_storage_class: cephfs
cephfs_provisioner_reclaim_policy: Delete
cephfs_provisioner_claim_root: /volumes
cephfs_provisioner_deterministic_names: true

Remplacez bien sur les IP des monitors avec vos propres IP, celles utilisées dans le réseau OpenVPN ;-)

Le secret ceph se retrouvera une fois le cluster ceph installé, il est généré par ceph lors de la création des pools pour l'utilisateur admin :

# ceph auth get client.admin

A titre personnel, j'utilise également helm et le cert-manager proposé en tant qu'addons :

# Helm deployment
helm_enabled: true
# Cert manager deployment
cert_manager_enabled: true
cert_manager_namespace: "cert-manager"

Ansible et déploiement

Concernant l'inventaire ansible, il reste assez simple dans l'optique où les noeuds kubernetes seront les mêmes que les noeuds ceph.

Pour ma part, il ressemble à ça :

[k8s]
node1.local osd_crush_location="{ 'root': 'default', 'rack': 'cephfs_data', 'pod': 'pod1', 'host': 'node1.local' }"
node2.local osd_crush_location="{ 'root': 'default', 'rack': 'cephfs_data', 'pod': 'pod1', 'host': 'node2.local' }"
node3.local osd_crush_location="{ 'root': 'default', 'rack': 'cephfs_data', 'pod': 'pod1', 'host': 'node3.local' }"

[kube-master:children]
k8s

[etcd:children]
k8s

[kube-node:children]
k8s

[k8s-cluster:children]
kube-master
kube-node

[mons:children]
k8s

[osds:children]
k8s

[clients:children]
k8s

[mdss:children]
k8s

[mgrs:children]
k8s

Au final vous devriez avoir une arborescence de ce type pour l'inventaire ansible :

.
├── ansible.cfg
├── group_vars
   ├── all
      ├── ceph_pool.yml
      ├── ceph.yml
      ├── k8s-addons.yml
      ├── k8s-cluster.yml
      └── kubespray.yml
   ├── clients.yml
   ├── mons.yml
   └── osds.yml
├── hosts
├── host_vars
   ├── node1.local
   ├── node2.local
   └── node3.local

Il ne vous reste alors plus qu'à déployer !

Comme kubernetes a besoin de ceph pour fonctionner, je vous conseille de commencer par ce dernier. Attention à utiliser ansible 2.5 pour ces déploiements (au 30/12/18, ceph-ansible ne supporte pas plus)

[ceph-ansible] ± cp site.yml.sample site.yml
[ceph-ansible] ± ansible-playbook -i ../your_inventory/hosts site.yml

[ceph-ansible] ± cd ~/kubespray
[kubespray] master ± ansible-playbook -i ../your_inventory/hosts cluster.yml

Les déploiements peuvent prendre un certain temps selon votre bande passante et le nombre de noeuds, parce qu'il y a quand même pas mal de chose à faire.

Si vous rencontrez des erreurs, les documentations sont en général suffisantes pour les corriger facilement, et les deux playbook sont idempotents (notamment kubespray) donc pas trop de problème pour les repasser en cas de crash.

Si vous rencontrez de gros soucis durant le déploiement de ceph, n'hésitez pas à purger le cluster pour recommencer, après tout vous n'avez pas de données dessus pour l'instant !

Note : il s'agit ici d'utiliser ansible pour aider au déploiement. Vous pouvez tout à fait utiliser un autre outil ou déployer à la main si vous le préférez. Ne mettez pas en production un outil que vous ne maitrisez pas !

Au final, vous disposez d'un cluster kubernetes fonctionnel avec une base ceph pour le stockage persistent ! Il va donc être temps de s'amuser avec ;-)

Sécurité

D'un point de vue sécurité, il est très important que votre cluster ne soit accessible que sur les IP du VPN. Bloquez les accès à l'API kubernetes sur le port 6443 car elle a tendance à écouter un peu n'importe où, utilisez exclusivement des certificats pour les utilisateurs !

Kubespray de base est plutôt sécurisé, ce qui est assez plaisant. Notamment il déploie des certificats pour sécuriser toutes les connexions (etc, api, kubelet, ...) mais cela n'empêche pas de limiter les accès pour éviter les angles d'attaques.

Concernant ceph, si vous lui avez fourni les bonnes IP VPN pour son déploiement, il ne devra jamais se retrouver exposé à l'extérieur.

Selon ce que vous souhaitez héberger dans votre cluster, les seuls ports à ouvrir devraient être ceux des services hébergés.

Par exemple dans mon cas pour pair2jeux, seuls les ports 80 et 443 sont exposés pour joindre Peertube et les outils web associés.

Conclusion

Les clusters (ceph et kubernetes) proposés ici sont maintenables via les projets kubespray et ceph-ansible, que ce soit pour ajouter des nodes, en détruire ou pour mise à jour de l'infrastructure.

Il vous faudra choisir comment configurer vos accès aux services (j'utilise pour ma part haproxy-ingress.

Cette infrastructure me permet de faire tourner l'instance peertube de pair2jeux avec une assez confortable marge de stockage et d'y associer d'autres outils (wiki, ...) de manière fun.

Plusieurs améliorations peuvent être mises en place sur cette infrastructure, comme par exemple

  • Un système de log centralisé (très utile pour les pods kubernetes)
  • Un monitoring des services kubernetes et des pods
  • Des sauvegardes des données ceph et de l'etcd
  • Un système d'IP virtuelle pour utiliser des entrées DNS qui tapent toujours dans le cluster fonctionnel en cas de panne
  • Intégrer l'espace disque restant au cluster ceph
  • Séparer les master kubernetes des nodes (quand le budget sera plus étendu)
  • Trouver des serveurs avec SSD pour faire tourner le système

J'essaierai de traiter certains de ces points dans de futurs articles, en attendant j'espère que celui vous aura plu !

Remerciements

Pour leur relecture :


Victor Avatar Victor est le rédacteur principal du blog.
Comments

Si vous avez des questions, si quelque chose n'est pas clair, n'hésitez pas à commenter !

  • Avatar
    Permalink

    zwindler

    Posted on

    Merci pour l'article, bien documenté avec les playbooks et les explications :)

    Plutôt qu'utiliser OpenVPN (justement à cause le la limitation client/serveur), as tu envisagé une solution avec un VPN multipoint (type tinc ou zerotier) ?

    Et même si je suis un grand fan d'ansible moi aussi, j'ai essayé récemment Rook qui "simplifie" l'instanciation du cluster Kubernetes en ajoutant des CRDs qui gèrent tout à ta place. A voir comment ça se comporte dans le temps mais ça simplifie pas mal la gestion quotidienne je trouve.


    • Avatar
      Permalink

      Victor

      Posted on

      Hello !

      Content que l'article t'ai plu :-)

      Concernant le VPN, j'ai utilisé OpenVPN principalement parce que je le connais bien, mais je me suis depuis intéressé à Tinc et wireguard, qui pourraient être très intéressants dans ce contexte en effet.
      Ça éliminerait la notion de "boucle" pour faire du point à point, par contre ils ne permettent pas le routage pour joindre un serveur derrière le VPN facilement (d'après ce que j'en ai lu, je n'ai pas eu l'occasion de les tester). Mais je pense (dès que j'ai du temps 😅) tester un poc avec l'un deux pour voir ce que ça donne.

      A propos de rook, honnêtement, j'ai déjà joué avec avec quelques collègues, et c'était, disons, vraiment pas stable. Ça pétait au reboot, on perdait les communications avec les noeuds ceph, c'est moyennement résilient quand on rajoute des noeuds, bref on l'a abandonné après avoir échoué à remonter un cluster pour la Xieme fois...

      Le projet a probablement évolué depuis (je l'espère en tout cas) mais je préconiserai quand même de très lourds tests dessus (péter des nodes, renouveler les certificats, tester le changement d'IP, etc) avant de passer en production :-)

      J'ai vu sur ton blog que tu étais déjà bien lancé sur le sujet de rook, je suis intéressé par les retours si jamais tu rencontres des problèmes en tout cas ! Ou même si tout va bien, ça m'intéresse aussi ^^

  • Avatar
    Permalink

    Guilhem Bonnefille

    Posted on

    Salut,

    Article vraiment intéressant, merci.

    As-tu envisagé d'utiliser un K8S managé comme celui proposé par OVH ? Je ne me rend pas compte de la différence de prix. Mais ça me semble largement simplifier l'affaire.


    • Avatar
      Permalink

      Victor

      Posted on

      Hello,

      Merci pour ton retour !

      À l'époque où j'ai monté l'infrastructure, OVH n'avait pas sorti son offre, les seuls sur le créneau étaient google, azure et amazon, et ça n'est pas trop mon délire je l'avoue. D'après mon expérience pro, poser un service chez eux simplifie la partie Ops mais fait exploser les coûts et c'est assez difficile d'en sortir...

      Chez OVH je pense que ça serait probablement pareil, d'autant que Peertube demande quand mêem pas mal de ressoures en général couteuse, notamment du CPU massivement (quand il encode des vidéos) et surtout du stockage (près de 2To de vidéos aujourd'hui et ça augmente). Du coup je pense que le tarif pourrait assez vite exploser, mais ça serait intéressant de comparer quand même, j'essaierai de regarder leurs offres à l'occasion :-)

      Cela dit, il faut aussi avouer que je suis admin système, j'aime jouer avec les outils moi-même, avec les inconvénients que ça peut comporter ^^

Ajouter un commentaire / Add a Comment

Vous pouvez utiliser la syntaxe Markdown pour formatter votre commentaire.

You can use the Markdown syntax to format your comment.

Flux Atom pour commentaire / Comment Atom Feed

Published

Category

Système

Tags

Restez en contact