Aller au contenu

Os Immutable: Flatcar Linux

logo ansible

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 :

Terminal window
sudo apt install virtualbox
# Pour un debian/ubuntu
curl -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 :

Terminal window
wget https://alpha.release.flatcar-linux.net/amd64-usr/current/flatcar_production_vagrant.box
vagrant box add flatcar-alpha flatcar_production_vagrant.box
vagrant init flatcar-alpha
vagrant up
vagrant ssh

On vérifie que /usr est bien en read only :

Terminal window
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 = 1024
ssh_keys = ["ssh-ed25519 AA... me@mail.net"]

Nous devons récupérer l’image de base, celle définie par la variable base_image :

Terminal window
cd ~/Downloads
wget https://stable.release.flatcar-linux.net/amd64-usr/current/flatcar_production_qemu_image.img.bz2
bunzip2 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 :

Terminal window
terraform init
terraform apply --auto-approve
Outputs:
ip-addresses = {
"mycluster-mynode" = tolist([
"192.168.122.45",
])
}

Et on lance le script créé par la configuration ignition :

Terminal window
ssh core@192.168.122.45
core@mycluster ~ $ ./works
My name is mynode and the hostname is mycluster

Docker est opérationnel ?

Terminal window
core@mycluster ~ $ docker ps
CONTAINER 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 :

Terminal window
git clone --recurse-submodules https://github.com/flatcar-linux/scripts.git
cd 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 :

Terminal window
git tag -l | grep -E 'stable-[0-9.]+$'
...
stable-3033.2.0
stable-3033.2.1
stable-3033.2.2
stable-3033.2.3
stable-3033.2.4
stable-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é :

Terminal window
./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-all
7d387896cfc0: Extracting [==================> ] 1.315GB/3.547GB
49afac011620: Download complete
7a0d64bb993d: Download complete
64465755da82: Download complete
9b0499ad5de5: Download complete
Emaint: fix world 100% [============================================>]
INFO setup_board: Elapsed time (setup_board): 0m5s
INFO setup_board: The SYSROOT is: /build/arm64-usr

Il faut ensuite récupérer le projet Gentoo :

Terminal window
git clone --depth 5 https://github.com/gentoo/gentoo.git

Avant tout ajout, il faut builder tous les packages (c’est assez long !) :

Terminal window
./build_packages

On recherche zsh dans le projet Gentoo :

Terminal window
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 :

Terminal window
mkdir -p ../third_party/portage-stable/app-shells
cp -R gentoo/app-shells/zsh ../third_party/portage-stable/app-shells
emerge-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 :

Terminal window
vim ../third_party/coreos-overlay/coreos-base/coreos/coreos-0.0.1.ebuild
Terminal window
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 :

Terminal window
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.

Terminal window
cp flatcar_production_qemu_image.img /home/vagrant/Projets/homelab/test-flatcar/flatcar_production_qemu_image-libvirt-import.img
terraform apply
Outputs:
ip-addresses = {
"mycluster-mynode" = tolist([
"192.168.122.45",
])
}

On vérifie que zsh est disponible :

Terminal window
ssh core@192.168.122.45
Last login: Fri Apr 8 06:59:28 UTC 2022 from 192.168.122.1 on pts/0
Flatcar Container Linux by Kinvolk developer 3202.0.0+nightly-20220407-0155 for QEMU
core@mycluster ~ $ zsh
mycluster%

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.