❌

Vue lecture

Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.
🔲 ☆

Printr - Météo, sudoku et autres petits mots sur des tickets de caisse

Printr , c'est un petit projet que j'ai trouvé vraiment sympa et original qui permet de transformer une imprimante à tickets de caisse Epson TM-T88V en gadget utile pour toute la famille !!

Vous la branchez sur un Raspberry Pi, et depuis votre smartphone, vous composez un ticket au design de votre choix grâce à une quarantaine de blocs (météo, agenda, sudoku, énigmes, horoscope, photos...) disponibles. Ensuite, vous lancez l'impression, et ça sort sur le rouleau. Ou alors vous pouvez faire ça à demande, par exemple à 7 h 30 tous les matins de la semaine si vous le planifiez comme ça.

Le ticket du matin tel que le rend l'émulateur de la TM-T88V, de la météo jusqu'aux cases à cocher

Le ticket du matin, par exemple, vous donne la date et le saint du jour, la météo de votre ville (via Open-Meteo), la phase de la lune, une citation et votre liste de choses à faire dans la journée avec des cases à cocher.

Vous pouvez aussi y ajouter vos rendez-vous à partir d'un lien ICS (calendrier quoi), et même un bloc "poubelles" qui imprime "Ce soir, on sort le bac jaune" la veille du ramassage.

C'est vraiment sympa non comme façon de se faire des petits pense bêtes.

Et pour occuper les enfants (ou les grands au petit-déj), il y a également toute une série de jeux comme le sudoku, des mots mêlés en français, logimage, petit bac, calcul mental, mandala à colorier et 125 énigmes dont la réponse est imprimée à l'envers, histoire d'obliger à retourner le ticket pour la lire.

Côté horoscope, c'est Claude Haiku qui s'y colle, avec quatre tons au choix : Sérieux, farfelu, vachard ou insultant. Et le dernier ne fait pas semblant visiblement. Ce bloc et le mot du jour sont d'ailleurs les deux seuls à appeler l'API d'Anthropic (donc c'est payant) et leur texte restera en cache pour la journée. Après, si ça vous saoule de payer l'API d'Anthropic, le bloc Barnum vous calculera un horoscope gratuitement, un peu moins fun et hors ligne.

Tout ça se pilote depuis une appli web conçue pour le smartphone et chaque membre de votre famille peut avoir son compte et composer ses tickets avec un aperçu en direct, puis les enregistrer, les partager et les planifier à l'heure voulue.

L'éditeur sur ordinateur, avec à droite l'aperçu du ticket et la réponse de l'énigme imprimée à l'envers

Il y a aussi la possibilité de laisser un petit mot à distance sur l'imprimante de la maison, avec une photo prise avec le téléphone, un cœur dessiné, quelques mots...etc. Sympa quand on est loin de chez soi pour envoyer des petits mots d'amour à sa famille.

De son côté, la liste de courses est partagée entre tout le monde et se vide toute seule une fois imprimée. Et sur iOS, l'app Raccourcis peut même envoyer vos Rappels au Pi pour qu'il les imprime. Par contre, pour envoyer un mot depuis l'extérieur de chez vous, il faudra que le téléphone puisse joindre le Pi, donc soit un VPN type Tailscale, soit l'appli ouverte sur Internet derrière un reverse proxy HTTPS.

Maintenant, côté matériel, Printr, comme je vous le disais en début d'article, vise une seule imprimante bien précise, la TM-T88V, dont la largeur imprimable de 512 points est fixée en dur dans le code. Pour le reste, il vous faudra une machine Linux avec systemd, idéalement un Raspberry Pi sous Raspberry Pi OS Lite 64 bits, et l'imprimante en USB ou sur le réseau.

Et il faudra ensuite compiler tout ça, puisque le script de déploiement compile le programme pour l'architecture précise de la machine avant de l'envoyer en SSH et d'installer le service :

scripts/deploy.sh pi@printer.local

Si vous voulez voir ce que ça donne avant d'aller acheter une TM-T88V avec vos petits sousous, le dépôt fournit aussi un émulateur pour l'imprimante à lancer via la commande scripts/demo.sh qui imprimera le ticket complet dedans et en sortira une image PNG.

Merci à XNinety9 et bravo pour le boulot !!

🔲 ☆

RaspRover – Le kit robot 4×4 pour Raspberry Pi 4 et 5

Vous rêvez de vous initier au monde de la robotique et de l’intelligence artificielle ?

Ca tombe bien puisque ce kit RaspRover va vous permettre de réaliser ce doux rêve ! Ce petit robot 4×4 hyper versatile est le compagnon idéal pour partir à l’aventure car il embarque un Raspberry Pi dernière génération (modèles 4 ou 5) ce qui va vous permettre de laisser libre cours à votre créativité en le programmant vous-même.

Et pas besoin d’être un crack en informatique pour dompter ce robot puisque tout est pensé pour faciliter sa prise en main. En effet, sous le capot, vous trouverez un double contrôleur avec d’un côté le Raspberry Pi qui gère les fonctions IA de haut niveau pendant que de l’autre, un microcontrôleur se charge des opérations basiques à haute fréquence. Comme ça, chaque action que vous lui demandez est précise et fluide.

Côté perception, le RaspRover a du flair grâce à sa caméra mobile grand angle qui lui offre une vision périphérique à 160°. Couplée à l’IA de reconnaissance d’images de OpenCV, cette caméra détecte et piste les visages ou les objets. Bref, de quoi lui permettre d’interagir avec son environnement sans souci.

Et ce n’est pas tout puisqu’il dispose d’une panoplie de capteurs (IMU 9 axes, télémètre laser…) qui décuplent ses talents : cartographie, évitement d’obstacles, localisation précise… et son architecture modulaire et ses nombreuses interfaces (GPIO, USB, I2C, UART…) lui permettent d’accueillir une foule d’accessoires : bras robotique, capteurs environnementaux, module 4G/5G… De quoi l’adapter à une infinité de scénarios comme de l’agriculture de précision, l’inspection de sites industriels, de la télé-surveillance, pour l’éducation, la recherche…

Et bien sûr, il carbure à l’open source ce qui veut dire que vous pouvez personnaliser son code et ajouter vos propres fonctionnalités. De plus, grâce au protocole ESP-NOW, plusieurs RaspRover peuvent même communiquer et collaborer entre eux ! Imaginez l’escouade de robots que vous allez pouvoir programmer avec ce truc.

Si ça vous dit, y’a un super Wiki qui vous explique comment monter et programmer tout ça.

🔲 ☆

Déploiement Kubernetes sur Raspberry Pi

Aujourd’hui, nous procéderons au déploiement d’un cluster Kubernetes sur Raspberry PI.

On ne présente plus Kubernetes, solution de conteneurisation open-source, simplifiant le déploiement, la mise à l’échelle, et la gestion en général, d’applications conteneurisées. Ni Raspberry-Pi, ordinateur ARM, de la taille d’une carte de crédit, idéal pour vos projets domotique, IoT, rétro-gaming, …

Notre cluster sera composé d’une douzaine de noeuds, et d’un LoadBalancer. Nous le déploierons à l’aide de Kubespray, outil de déploiement faisant partie de l’écosystème Kubernetes, modulaire, fiable, et relativement exhaustif.

Préparatifs Raspbian

Commençons par télécharger Raspbian.

Pour les noeuds master, il est impératif d’utiliser une image 64b, sans quoi Kubespray ne sait pas installer etcd. Nous retrouverons une image beta du projet Raspbian, fournissant une version arm64. Le problème étant que les mainteneurs du projet etcd ne publient pas de version 32b. Notons qu’il reste possible d’installer une version 32b d’etcd, fournie par Debian, quoi que Kubespray ne supporte pas cette combinaison.

Pour nos autres noeuds, nous pourrons utiliser la dernière image officiel Raspbian.

Une fois ces archives téléchargées et extraites, nous pourrons flasher les cartes micro-sd de nos Raspberry Pi :

$ dd if=./2020-08-20-raspios-buster-arm64-lite.img | pv | dd of=/dev/mmcblk0

Cluster-rapsi

Démarrer le Raspberry, avec écran et clavier, afin d’y configurer un mot de passe root :

$ sudo -i
# passwd

Une addresse IP statique :

# cat <<EOF >/etc/network/interfaces
auto eth0
iface eth0 inet static
    address x.y.z.a
    netmask 255.255.255.0
    gateway x.y.z.b
EOF

Serveur DNS :

# cat <<EOF >/etc/resolv.conf
nameserver 10.255.255.255
domain friends.intra.example.com
search friends.intra.example.com
EOF

Désactiver le fichier d’échange swap :

# sed -i 's|CONF_SWAPSIZE=.*|CONF_SWAPSIZE=0|' /etc/dphys-swapfile
# systemctl disable dphys-swapfile

Configurer le serveur SSH :

# sed -i -e 's|#PermitRootLog.*|PermitRootLogin yes|' -e 's|#PasswordAuth|PasswordAuthentication yes|' /etc/ssh/sshd_config
# systemctl enable ssh

Désactiver les services réseaux que nous n’utiliserons pas :

# systemctl disable dhcpcd
# systemctl disable wpa_supplicant
# systemctl disable bluetooth

Configurer le nom de la machine :

# cat <<EOF >/etc/hostname
EOF
# cat <<EOF >/etc/hosts
x.y.z.a <fqdn> <hostname>
127.0.0.1 <fqdn> <hostname>
127.0.0.1 localhost
::1 localhost6 localhost6.localdomain
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
EOF

Activer les cgroups mémoire :

# sed -i 's|rootwait$|rootwait cgroup_enable=cpuset cgroup_enable=memory|' /boot/cmdline.txt
# cat /boot/cmdline.txt
console=serial0,115200 console=tty1 root=PARTUUID=dcca89ee-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait cgroup_enable=cpuset cgroup_enable=memory

Redémarrer, et vérifier les mots de passe root, IP, gateway, groups mémoire, … puis mettre le système à jour :

# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
...
memory 9 186 1
....
# hostname -s
# hostname -f
# ip a
# ip r
# apt-get update --allow-releaseinfo-change
# apt-get install python-apt cgroupfs-mount ceph-common rbd-nbd
# apt-get upgrade
# apt-get dist-upgrade

LoadBalancer Kubernetes

Raspberry 3 et 4 conviendront parfaitement au déploiement de Kubernetes. En revanche, les modèles plus anciens seront incapables de lancer certaines composantes Kubernetes, faute d’images adaptées à leur ARM v6.

Profitons d’un Raspberry 2b, pour y déployer le LoadBalancer, qui se trouvera devant le service API des masters Kubernetes :

# apt-get install haproxy hatop
# cat <<EOF >/etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    ca-base /etc/ssl/certs
    crt-base /etc/ssl/private
    ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
    ssl-default-bind-options no-sslv3

defaults
    log global
    option dontlognull
    timeout connect 5000
    timeout client 50000
    timeout server 50000
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http

listen kubernetes-apiserver-https
    bind 0.0.0.0:6443
    mode tcp
    option log-health-checks
    timeout client 3h
    timeout server 3h
    server master1 10.42.253.40:6443 check check-ssl verify none inter 10s
    server master2 10.42.253.41:6443 check check-ssl verify none inter 10s
    server master3 10.42.253.42:6443 check check-ssl verify none inter 10s
    balance roundrobin
EOF
# cat <<EOF >/etc/profile.d/haproxy.sh
alias hatop='hatop -s /run/haproxy/admin.sock'
EOF
# systemctl start haproxy
# systemctl enable haproxy
# . /etc/profile.d/haproxy.sh
# hatop

Préparatifs Ansible

Il nous faudra ensuite préparer le noeud Ansible.

Nous pourrions déployer depuis un laptop, ou n’importe quel poste. L’utilisation d’une machine dédié simplifiera les interventions futures sur le cluster, s’assurant que les mêmes versions de kubespray, python, ansible, … soient utilisées, si l’on veut, par exemple, rajouter un noeud.

Commençons par installer les playbooks Kubespray, et Ansible :

$ sudo apt-get install python-pip git ca-certificates
$ git clone https://github.com/kubernetes-sigs/kubespray
$ cd kubespray
$ sudo pip install -r requirements.txt

Nous devrons alors préparer l’inventaire, listant les machines composant notre cluster :

$ mkdir -p inventory/rpi/group_vars/all inventory/rpi/group_vars/all/k8s-cluster
$ cat <<EOF >inventory/rpi/hosts.yaml
all:
  hosts:
    pandore.friends.intra.example.com:
      etcd_member_name: pandore
      node_labels:
        my.topology/rack: rpi-rack1
        my.topology/row: rpi-row2
    hellenes.friends.intra.example.com:
      etcd_member_name: hellenes
      node_labels:
        my.topology/rack: rpi-rack2
        my.topology/row: rpi-row2
    epimethee.friends.intra.example.com:
      etcd_member_name: epimethee
      node_labels:
        my.topology/rack: rpi-rack3
        my.topology/row: rpi-row2
    pyrrha.friends.intra.example.com:
      node_labels:
        my.topology/rack: rpi-rack1
        my.topology/row: rpi-row3
        node-role.kubernetes.io/infra: "true"
    calliope.friends.intra.example.com:
      node_labels:
        my.topology/rack: rpi-rack2
        my.topology/row: rpi-row3
        node-role.kubernetes.io/infra: "true"
    clio.friends.intra.example.com:
      node_labels:
        my.topology/rack: rpi-rack3
        my.topology/row: rpi-row3
        node-role.kubernetes.io/infra: "true"
    erato.friends.intra.example.com:
      node_labels:
        my.topology/rack: rpi-rack1
        my.topology/row: rpi-row4
        node-role.kubernetes.io/worker: "true"
    euterpe.friends.intra.example.com:
      node_labels:
        my.topology/rack: rpi-rack2
        my.topology/row: rpi-row4
        node-role.kubernetes.io/worker: "true"
    melpomene.friends.intra.example.com:
      node_labels:
        my.topology/rack: rpi-rack3
        my.topology/row: rpi-row4
        node-role.kubernetes.io/worker: "true"
    polyhymnia.friends.intra.example.com:
      node_labels:
        my.topology/rack: rpi-rack3
        my.topology/row: rpi-row1
        node-role.kubernetes.io/worker: "true"
    terpsichore.friends.intra.example.com:
      node_labels:
        my.topology/rack: rpi-rack2
        my.topology/row: rpi-row1
        node-role.kubernetes.io/worker: "true"
    thalia.friends.intra.example.com:
      node_labels:
        my.topology/rack: rpi-rack1
        my.topology/row: rpi-row1
        node-role.kubernetes.io/worker: "true"
  children:
    calico-rr:
      hosts: {}
    etcd:
      hosts:
        pandore.friends.intra.example.com:
        hellenes.friends.intra.example.com:
        epimethee.friends.intra.example.com:
    kube-infra:
      hosts:
        pyrrha.friends.intra.example.com:
        calliope.friends.intra.example.com:
        clio.friends.intra.example.com:
    kube-master:
      hosts:
        pandore.friends.intra.example.com:
        hellenes.friends.intra.example.com:
        epimethee.friends.intra.example.com:
    kube-workers:
      hosts:
        erato.friends.intra.example.com:
        euterpe.friends.intra.example.com:
        melpomene.friends.intra.example.com:
        polyhymnia.friends.intra.example.com:
        terpsichore.friends.intra.example.com:
        thalia.friends.intra.example.com:
    kube-node:
      children:
        kube-master:
        kube-infra:
        kube-workers:
    k8s-cluster:
      children:
        calico-rr:
        kube-node:
EOF

Cet inventaire décrit un cluster composé de 3 noeuds masters+etcd, 3 noeuds “infra”, et 6 noeuds “workers”.

Ensuite, nous voudrons créer un premier fichier de variables globales :

$ cat <<EOF >inventory/rpi/group_vars/all/all.yaml
ansible_user: root
etcd_data_dir: /var/lib/etcd
etcd_kubeadm_enabled: false
bin_dir: /usr/local/bin
apiserver_loadbalancer_domain_name: api-k8s-arm.intra.example.com
loadbalancer_apiserver:
address: 10.42.253.52
port: 6443
loadbalancer_apiserver_localhost: false
loadbalancer_apiserver_port: 6443
upstream_dns_servers:
- 10.255.255.255
no_proxy_exclude_workers: false
cert_management: script
download_container: true
deploy_container_engine: true
EOF

Nous désignons ici le ou les serveurs DNS existants que Kubernetes devra interroger pour la résolution de noms hors clusters. Ainsi que la VIP d’un LoadBalancer, et le nom DNS correspondant, ceux-ci pointant sur l’HAProxy que nous venons de déployer.

Il faudra ensuite créer un fichier de configuration avec les variables relatives au cluster etcd :

$ cat <<EOF >inventory/rpi/group_vars/etcd.yaml
etcd_compaction_retention: 8
etcd_metrics: basic
etcd_memory_limit: 2GB
etcd_quota_backend_bytes: "2147483648"
etcd_peer_client_auth: true
etcd_deployment_type: host
EOF

On rajoute une limite mémoire et un quota de 2GB, pour etcd, qui doit rentrer sur nos masters, disposant de 4GB de mémoire. Si l’on ne veut pas utiliser docker, comme container runtime, alors on devra changer le type déploiement etcd. Par défaut conteneurisé, son déploiement dépend de commandes docker. Le déploiement de type host permettant l’utilisation d’un runtime crio, ou containerd.

Nous devrons alors créer un nouveau de fichier de variables :

cat <<EOF >inventory/rpi/group_vars/k8s-cluster/k8s-cluster.yaml
kube_config_dir: /etc/kubernetes
kube_script_dir: "{{ bin_dir }}/kubernetes-scripts"
kube_manifest_dir: "{{ kube_config_dir }}/manifests"
kube_cert_dir: "{{ kube_config_dir }}/ssl"
kube_token_dir: "{{ kube_config_dir }}/tokens"
kube_api_anonymous_auth: true
kube_version: v1.19.5
local_release_dir: /tmp/releases
retry_stagger: 5
kube_cert_group: kube-cert
kube_log_level: 2
credentials_dir: "{{ inventory_dir }}/credentials"
kube_oidc_auth: false
kube_token_auth: true
kube_network_plugin: flannel
kube_network_plugin_multus: false
kube_service_addresses: 10.233.128.0/18
kube_pods_subnet: 10.233.192.0/18
kube_network_node_prefix: 24
kube_apiserver_ip: "{{ kube_service_addresses|ipaddr('net')|ipaddr(1)|ipaddr('address') }}"
kube_apiserver_port: 6443
kube_apiserver_insecure_port: 0
kube_proxy_mode: ipvs
authorization_modes: ['Node', 'RBAC']
kube_proxy_strict_arp: false
kube_encrypt_secret_data: false
cluster_name: cluster.local
ndots: 2
dns_mode: coredns
enable_nodelocaldns: true
nodelocaldns_ip: 169.254.25.10
nodelocaldns_health_port: 9254
enable_coredns_k8s_external: false
enable_coredns_k8s_endpoint_pod_names: false
resolvconf_mode: none
deploy_netchecker: false
skydns_server: "{{ kube_service_addresses|ipaddr('net')|ipaddr(3)|ipaddr('address') }}"
skydns_server_secondary: "{{ kube_service_addresses|ipaddr('net')|ipaddr(4)|ipaddr('address') }}"
dns_domain: "{{ cluster_name }}"
container_manager: containerd
kata_containers_enabled: false
kubeadm_certificate_key: "{{ lookup('password', credentials_dir + '/kubeadm_certificate_key.creds length=64 chars=hexdigits') | lower }}"
k8s_image_pull_policy: IfNotPresent
kubernetes_audit: false
dynamic_kubelet_configuration: false
default_kubelet_config_dir: "{{ kube_config_dir }}/dynamic_kubelet_dir"
dynamic_kubelet_configuration_dir: "{{ kubelet_config_dir | default(default_kubelet_config_dir) }}"
podsecuritypolicy_enabled: true
kubeconfig_localhost: true
kubectl_localhost: false
system_reserved: true
system_memory_reserved: 128Mi
system_cpu_reserved: 250m
system_master_memory_reserved: 256Mi
system_master_cpu_reserved: 250m
volume_cross_zone_attachment: false
persistent_volumes_enabled: false
event_ttl_duration: 1h0m0s
force_certificate_regeneration: false
minimal_node_memory_mb: 896
kube_proxy_nodeport_addresses: >-

Beaucoup de variables ci-dessus reprennent des défaults. Entre autre changements, notons le plugin réseau, “flannel“, et la définition des “kube_service_addresses” et “kube_pods_subnet” ou le “resolvconf_mode“. Le runtime conteneur : “containerd“. L’activation des PodSecurityPolicy. Les “system_*” vont réserver un peu de CPU et mémoire pour l’OS des noeuds. Le “minimal_node_memory” sera indispensable, lorsque certains noeuds ont moins d’1Gi de mémoire – les RPI 3b+ remontent 924Mi.

Il sera possible de configurer le runtime conteneur du cluster, en créant un fichier tel que le suivant :

$ cat <<EOF >inventory/rpi/group_vars/all/containerd.yaml
containerd_config:
  grpc:
    max_recv_message_size: 16777216
    max_send_message_size: 16777216
  debug:
    level: ""
  registries:
    docker.io: "https://registry-1.docker.io"
    katello: "https://katello.vms.intra.example.com:5000"
    "registry.registry.svc.cluster.local:5000": "http://registry.registry.svc.cluster.local:5000"
  max_container_log_line_size: -1
  metrics:
    address: ""
    grpc_histogram: false
EOF

Kubespray permet le déploiement de diverses applications optionnelles :

$ cat <<EOF >inventory/rpi/group_vars/k8s-cluster/addons.yaml
helm_enabled: false
registry_enabled: false
metrics_server_enabled: true
metrics_server_kubelet_insecure_tls: true
metrics_server_metric_resolution: 60s
metrics_server_kubelet_preferred_address_types: "InternalIP"
local_path_provisioner_enabled: false
local_volume_provisioner_enabled: false
cephfs_provisioner_enabled: false
rbd_provisioner_enabled: false
ingress_nginx_enabled: true
ingress_ambassador_enabled: false
ingress_alb_enabled: false
cert_manager_enabled: false
metallb_enabled: false
EOF

Dans notre cas, nous activerons le metrics server, et l’Ingress Controller Nginx.

Enfin, générer une clé SSH sur le noeud Ansible, et l’installer sur tous les noeuds composant le cluster :

$ ssh-keygen -t rsa -b 4096 -N '' -f ~/.ssh/id_rsa
$ for i in pandore hellenes .... terpsichore thalia
do ssh-copy-id -i ~/.ssh/id_rsa.pub root@$i.friends.intra.example.com; done
...
$ ansible -m ping -i inventory/rpi/hosts.yaml all

Déploiement Kubernetes

Nous pourrons enfin procéder au déploiement :

$ ansible-playbook -i inventory/rpi/hosts.yaml cluster.yml 2>&&1 | tee -a deploy.$(date +%s).log

Si le déploiement échoue, nous pourrons corriger notre inventaire et relancer cette même commande, à moins que l’erreur ne se soit produite lorsqu’ansible lance l’initialisation du cluster Kubernetes (kubeadm init), auquel cas nous devrions d’abord lancer le playbook de reset, pour réinitialiser les noeuds :

$ ansible-playbook -i inventory/rpi/hosts.yaml reset.yml

Si tout se passe bien, le déploiement ne prendra pas plus d’une trentaine de minutes.

...
PLAY RECAP *******************************************
calliope.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1
clio.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1
epimethee.friends.intra.example.com : ok=440 changed=67 unreachable=0 failed=0 skipped=864 rescued=0 ignored=1
erato.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1
euterpe.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1
hellenes.friends.intra.example.com : ok=442 changed=68 unreachable=0 failed=0 skipped=862 rescued=0 ignored=1
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
melpomene.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1
pandore.friends.intra.example.com : ok=497 changed=86 unreachable=0 failed=0 skipped=924 rescued=0 ignored=1
pyrrha.friends.intra.example.com : ok=308 changed=30 unreachable=0 failed=0 skipped=604 rescued=0 ignored=1
polyhymnia.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1
terpsichore.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1
thalia.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1


$ kubectl get nodes
NAME                                    STATUS   ROLES    AGE     VERSION
calliope.friends.intra.example.com      Ready    infra    7m14s   v1.19.5
clio.friends.intra.example.com          Ready    infra    7m14s   v1.19.5
epimethee.friends.intra.example.com     Ready    master   9m26s   v1.19.5
erato.friends.intra.example.com         Ready    worker   7m14s   v1.19.5
euterpe.friends.intra.example.com       Ready    worker   7m1s    v1.19.5
hellenes.friends.intra.example.com      Ready    master   9m24s   v1.19.5
melpomene.friends.intra.example.com     Ready    worker   7m1s    v1.19.5
pandore.friends.intra.example.com       Ready    master   9m59s   v1.19.5
pyrrha.friends.intra.example.com        Ready    infra    7m14s   v1.19.5
polyhimnia.friends.intra.example.com    Ready    worker   7m1s    v1.19.5
terpsichore.friends.intra.example.com   Ready    worker   7m13s   v1.19.5
thalia.friends.intra.example.com        Ready    worker   7m      v1.19.5

A ce stade, nous pourrions intégrer le cluster avec une solution de stockage externe – au plus simple, un serveur NFS. Notons en revanche que l’on reste limité par les modules kernels fournis avec Raspbian : pour s’interfacer avec avec un cluster Ceph, il faudra recompiler votre kernel, ou s’intéresser à rbd-ndb. Nous reviendrons sur ce point dans un article ultérieur.

Conclusion

Cluster-raspi2

Le support ARM par Kubespray est relativement récent, mais fonctionne bien, contrairement à ce que l’on peut lire sur quelques articles de blogs, plus anciens. Il faudra en revanche s’assurer qu’au moins vos noeuds disposent de processeurs armv7, et qu’au moins vos master puissent lancer des binaires 64b.

En règle générale, il faudra s’assurer que les applications que l’on compte déployer dans notre cluster offrent bien des images ARM, ce qui n’est pas toujours le cas. Ceci dit, libre à vous d’assembler vous-mêmes vos images.

Contrairement à OpenShift 4, un cluster Kubernetes demande moins de ressources, offre une plus grande portabilitée, tandis que Kubespray n’a rien à envier aux outils de déploiements d’OpenShift que sont openshift-ansible, ou openshift-install.

❌