Os Immutable: Flatcar Linux
FlatCar Linux est un fork de CoreOS Container Linux développé par la société Kinvolk ↗. Ce fork a été initié pour assurer la pérennité de CoreOS à la suite de l’annonce de Redhat d’acquérir CoreOS.
Concepts de FlatCar Linux
FlatCar Linux ne contient que ce qui est nécessaires pour déployer des applications dans des conteneurs, ce qui permet de limiter sa surface d’attaque.
Il est considéré comme immuable, car sa partition principale (/usr) est montée en lecture seule et il n’intègre pas de gestionnaire de packages. Pour les mises à jour, FlatCar reprend le système de mise à jour de ChromeOS. Il existe en fait deux partitions où est installé des versions différentes du système d’exploitation, mais une seule est active. Lors de la publication d’une nouvelle version, la nouvelle image est chargée sur la partition inactive. Au prochain reboot, c’est cette nouvelle version qui deviendra active. En cas de problème de démarrage, le système revient automatiquement à l’ancienne version.
La configuration des instances est fait comme pour CoreOS via un fichier de configuration au format ignition (proche de cloud-init). Via ce fichier, il est possible de :
- Ajouter des utilisateurs et groupes
- Créer et gérer des devices, système de fichiers, des fichiers et définir la swap.
- De customiser les mises à jour et reboots automatiques
- De créer des configurations réseaux
- De définir les services à démarrer
Installation de FlatCar Linux
FlatCar Linux peut être installé sur toutes les plateformes cloud suivantes :
- AWS EC2
- Equinix Metal
- Microsoft Azure
- VMware
- Google Compute Engine
- DigitalOcean
- Hetzner
Mais il peut être aussi installé sur :
- Libvirt
- Qemu
- VirtualBox
On peut le tester avec Vagrant via les boxes mise à disposition. C’est ce que nous allons voir, dans un premier temps et nous verrons par la suite comment l’installer sur du libvirt avec Terraform.
Test de FlatCar Linux avec Vagrant et VirtualBox
Il faut installer au préalable VirtualBox et Vagrant :
sudo apt install virtualbox# Pour un debian/ubuntucurl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"sudo apt update && sudo apt install vagrant
Maintenant on peut passer au test :
wget https://alpha.release.flatcar-linux.net/amd64-usr/current/flatcar_production_vagrant.boxvagrant box add flatcar-alpha flatcar_production_vagrant.boxvagrant init flatcar-alphavagrant upvagrant ssh
On vérifie que /usr est bien en read only :
mount | grep usr/dev/mapper/usr on /usr type ext4 (ro,relatime,seclabel)/dev/vda6 on /usr/share/oem type btrfs (rw,nodev,relatime,seclabel,space_cache=v2,subvolid=5,subvol=/)
Installation de FlatCar avec Terraform
En m’appuyant sur la documentation du site ↗ (qui est excellente), je vais monter une VM dans ma VM de développement avec du Terraform.
Ce qui est sympathique avec Terraform c’est que la transpilation du
fichier iginition est assuré par le provider posseidon/ct
. Il suffit
donc de créer un fichier de configuration via le système de template
de
Terraform.
Si vous voulez instancier sur d’autres plateformes le site du projet regorge d’exemples ↗
On commence par écrire le fichier main.tf
:
terraform { required_version = ">= 1.1.7" required_providers { libvirt = { source = "dmacvicar/libvirt" version = "0.6.14" } ct = { source = "poseidon/ct" version = "0.10.0" } template = { source = "hashicorp/template" version = "~> 2.2.0" } }}
provider "libvirt" { uri = "qemu:///system"}
resource "libvirt_pool" "volumetmp" { name = "${var.cluster_name}-pool" type = "dir" path = "/var/tmp/${var.cluster_name}-pool"}
resource "libvirt_volume" "base" { name = "flatcar-base" source = var.base_image pool = libvirt_pool.volumetmp.name format = "qcow2"}
resource "libvirt_volume" "vm-disk" { for_each = toset(var.machines) # workaround: depend on libvirt_ignition.ignition[each.key], otherwise the VM will use the old disk when the user-data changes name = "${var.cluster_name}-${each.key}-${md5(libvirt_ignition.ignition[each.key].id)}.qcow2" base_volume_id = libvirt_volume.base.id pool = libvirt_pool.volumetmp.name format = "qcow2"}
resource "libvirt_ignition" "ignition" { for_each = toset(var.machines) name = "${var.cluster_name}-${each.key}-ignition" pool = libvirt_pool.volumetmp.name content = data.ct_config.vm-ignitions[each.key].rendered}
resource "libvirt_domain" "machine" { for_each = toset(var.machines) name = "${var.cluster_name}-${each.key}" vcpu = var.virtual_cpus memory = var.virtual_memory
fw_cfg_name = "opt/org.flatcar-linux/config" coreos_ignition = libvirt_ignition.ignition[each.key].id
disk { volume_id = libvirt_volume.vm-disk[each.key].id }
graphics { listen_type = "address" }
# dynamic IP assignment on the bridge, NAT for Internet access network_interface { network_name = "default" wait_for_lease = true }}
data "ct_config" "vm-ignitions" { for_each = toset(var.machines) content = data.template_file.vm-configs[each.key].rendered}
data "template_file" "vm-configs" { for_each = toset(var.machines) template = file("${path.module}/machine-${each.key}.yaml.tmpl")
vars = { ssh_keys = jsonencode(var.ssh_keys) name = each.key }}
output "ip-addresses" { value = { for key in var.machines : "${var.cluster_name}-${key}" => libvirt_domain.machine[key].network_interface.0.addresses.* }}
Le fichier de variables.tf
:
variable "machines" { type = list(string) description : "Machine names, corresponding to machine-NAME.yaml.tmpl files"}
variable "cluster_name" { type = string description : "Cluster name used as prefix for the machine names"}
variable "ssh_keys" { type = list(string) description : "SSH public keys for user 'core'"}
variable "base_image" { type = string description : "Path to unpacked Flatcar Container Linux image flatcar_production_qemu_image.img (probably after a qemu-img resize IMG +5G)"}
variable "virtual_memory" { type = number default = 2048 description : "Virtual RAM in MB"}
variable "virtual_cpus" { type = number default = 1 description : "Number of virtual CPUs"}
Un fichier terraform.tfvars
(remplacer la clé ssh avec la vôtre) :
base_image = file("${path.module}/flatcar_production_qemu_image-libvirt-import.img")cluster_name = "mycluster"machines = ["mynode"]virtual_memory = 1024ssh_keys = ["ssh-ed25519 AA... me@mail.net"]
Nous devons récupérer l’image de base, celle définie par la variable base_image
:
cd ~/Downloadswget https://stable.release.flatcar-linux.net/amd64-usr/current/flatcar_production_qemu_image.img.bz2bunzip2 flatcar_production_qemu_image.img.bz2# optional, increase the image by 5 GB:qemu-img resize flatcar_production_qemu_image.img +5G
Et pour finir, nous allons créer le fichier de template pour la configuration Ignition :
---passwd: users: - name: core ssh_authorized_keys: ${ssh_keys}storage: files: - path: /home/core/works filesystem: root mode: 0755 contents: inline: | #!/bin/bash set -euo pipefail hostname="$(hostname)" echo My name is ${name} and the hostname is $${hostname}
On applique :
terraform initterraform apply --auto-approve
Outputs:
ip-addresses = { "mycluster-mynode" = tolist([ "192.168.122.45", ])}
Et on lance le script créé par la configuration ignition :
ssh core@192.168.122.45core@mycluster ~ $ ./worksMy name is mynode and the hostname is mycluster
Docker est opérationnel ?
core@mycluster ~ $ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Mais comment ajouter des packages à FlatCar Linux ?
Customisation de FlatCar Linux
Il est possible de modifier la configuration Ignition et tout est très bien documenté sur le site officiel ↗
Ajouter des packages ?
Il est possible de customiser FlatCar Linux
en lui ajoutant des packages par
exemple. Pour cela il faut ajouter des packages Gentoo via le SDK de
FlatCar Linux. Tiré de la documentation du
site ↗
Pourquoi pas lui ajouter zsh (pour faire simple) ?
Il faut dans un premier temps récupérer le SDK de FlatCar Linux :
git clone --recurse-submodules https://github.com/flatcar-linux/scripts.gitcd scripts
Nous sommes sur la branche main du projet et donc la dernière version nigthly. Pour changer de version, il suffit de regarder dans les branches et de basculer sur celle désirée :
git tag -l | grep -E 'stable-[0-9.]+$'...stable-3033.2.0stable-3033.2.1stable-3033.2.2stable-3033.2.3stable-3033.2.4stable-3139.2.0...
git checkout stable-3139.2.0
Pour lancer le SDK, qui tourne dans container, on a à disposition un script. Ce script se charge de récupérer l’image du sdk correspondant à la branche dans laquelle vous avez basculé :
./run_sdk_container -t###### Writing versionfile 'sdk_container/.repo/manifests/version.txt' to SDK '3200.0.0', OS '3200.0.0+nightly-20220408-0155'. ######
###### Creating a new container 'flatcar-sdk-all-3200.0.0_os-alpha-3200.0.0-nightly-20220408-0155' ######3200.0.0: Pulling from flatcar-linux/flatcar-sdk-all7d387896cfc0: Extracting [==================> ] 1.315GB/3.547GB49afac011620: Download complete7a0d64bb993d: Download complete64465755da82: Download complete9b0499ad5de5: Download completeEmaint: fix world 100% [============================================>]INFO setup_board: Elapsed time (setup_board): 0m5sINFO setup_board: The SYSROOT is: /build/arm64-usr
Il faut ensuite récupérer le projet Gentoo :
git clone --depth 5 https://github.com/gentoo/gentoo.git
Avant tout ajout, il faut builder tous les packages (c’est assez long !) :
./build_packages
On recherche zsh dans le projet Gentoo :
sdk@flatcar-sdk-all-3139_0_0_os-stable-3139_2_0 ~/trunk/src/scripts $ find gentoo -name zsh
Il faut copier ce répertoire dans celui de FlatCar Linux et lancer sa compilation :
mkdir -p ../third_party/portage-stable/app-shellscp -R gentoo/app-shells/zsh ../third_party/portage-stable/app-shellsemerge-amd64-usr --newuse app-shells/zsh
Il faudra répéter l’opération pour chaque dépendance manquante !
Il faut ensuite ajouter au fichier de description de FlatCar zsh :
vim ../third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1.ebuild
RDEPEND="${RDEPEND}... app-misc/jq app-misc/pax-utils app-shells/bash app-shells/bash coreos-base/afterburn coreos-base/coreos-cloudinit...
Ensuite on lance le build :
emerge-amd64-usr coreos-base/coreos./build_image./image_to_vm.sh --from=../build/images/amd64-usr/latest --format qemu
On sort de la session docker en cours, on copie la nouvelle image à la place de celle utilisée dans la configuration Terraform.
cp flatcar_production_qemu_image.img /home/vagrant/Projets/homelab/test-flatcar/flatcar_production_qemu_image-libvirt-import.imgterraform apply
Outputs:
ip-addresses = { "mycluster-mynode" = tolist([ "192.168.122.45", ])}
On vérifie que zsh
est disponible :
ssh core@192.168.122.45Last login: Fri Apr 8 06:59:28 UTC 2022 from 192.168.122.1 on pts/0Flatcar Container Linux by Kinvolk developer 3202.0.0+nightly-20220407-0155 for QEMUcore@mycluster ~ $ zshmycluster%
Voilà on a réussi, j’ai testé avec docker-compose
et ça fonctionne également,
mais il faut copier pas mal de dépendances de dev-python
. La liste :
pycparser, urllib3, six, idna, charset-normalizer, cffi, certifi, websocket-client, requests, pynacl, cryptography, bcrypt, pyrsistent, paramiko, docker, attrs, texttable, PyYAML, python-dotenv, jsonschema, docopt, dockerpty, distro, docker-compose
Dans un contexte de production, il y aura du travail pour créer un CI permettant de builder en automatique ce genre d’image et de les stocker.
Plus loin
C’est franchement une belle découverte. J’imagine déjà pas mal de cas d’utilisation. Vu que ssh et python fonctionne pourquoi pas utiliser Ansible pour installer des produits du type rundeck. Je pense que cette distribution a pas mal de potentiels, pas étonnant qu’on la retrouve chez tous les clouders.