Les deux premiers articles de cette série posent le vocabulaire d’OpenTelemetry et présentent OpenObserve comme backend « agent-friendly ».
Cet article provisionne le cluster Kubernetes Kapsule avec OpenTofu. Les deux articles suivants y installent OpenObserve, puis l’exposent en HTTPS avec Traefik.
Architecture
Le cluster Scaleway Kubernetes Kapsule en plan de contrôle mutualisé (type = "kapsule") est gratuit, sans SLA :
vous ne payez que les nœuds et les ressources associées, à l’heure.
Un Private Network, un pool de deux nœuds, une IP de load balancer réservée, puis Traefik et OpenObserve par Helm.
OpenObserve tourne en mode standalone :
- SQLite pour les métadonnées,
- disque pour les données.
Le mode HA demande un stockage objet, Postgres, NATS et plusieurs rôles de nœuds qui dépasse le cadre d’une démo.
Le schéma montre la cible à la fin des trois articles. Celui-ci en pose le cluster, le Private Network, le pool et les deux IP réservées.
Trois namespaces à plat : openobserve, otel, demo.
Le provider lit les variables d’environnement relatifs aux identifiants scaleway hors des fichiers .tf :
export SCW_ACCESS_KEY=<SCW_ACCESS_KEY>
export SCW_SECRET_KEY=<SCW_SECRET_KEY>
export SCW_DEFAULT_PROJECT_ID=<SCW_PROJECT_ID>
Gestion des variables d’env
Pour ma part, je ne transmets pas ces variables à l’exception du projet id.
J’utilise un outil de stockage des credentials qui transmet automatiquement ces credentials à OpenTofu.
Plusieurs solutions existent, telles que HashiCorp Vault, Pass, GoPass ou encore Teller. À titre personnel, j’utilise AutomicVault dans ce rôle.
Avec AutomicVault, il suffit de lancer la commande suivante pour injecter les secrets nécessaires :
av inject +SCW_ACCESS_KEY +SCW_SECRET_KEY scw infoDécrire le cluster en Terraform
Le provider et la zone sont fixés dans la configuration
Un volume Block Storage n’existe que dans une zone : un nœud d’une autre zone ne peut pas le monter.
Le pool doit donc être créé dans la zone du volume, et changer de zone recrée le pool.
# versions.tf
terraform {
required_providers {
scaleway = {
source = "scaleway/scaleway"
version = "~> 2.84"
}
}
}
provider "scaleway" {
zone = var.zone
region = "fr-par"
project_id = var.project_id
}
Les variables,
project_id vaut null par défaut. Le provider lit alors SCW_DEFAULT_PROJECT_ID.
# variables.tf
variable "project_id" {
type = string
default = null
}
variable "zone" {
type = string
default = "fr-par-1"
}
variable "kubernetes_version" {
type = string
default = "1.37.1"
}
variable "node_type" {
type = string
default = "DEV1-M"
}
variable "running" {
type = bool
default = true
}
variable "node_count" {
type = number
default = 2
}
1.37.1 vient de la doc du provider. Listez les versions proposées avant d’appliquer :
scw k8s version list
La ressource principale
# main.tf
resource "scaleway_vpc_private_network" "main" {
name = "observabilite"
}
resource "scaleway_k8s_cluster" "main" {
name = "observabilite"
type = "kapsule"
version = var.kubernetes_version
cni = "cilium"
private_network_id = scaleway_vpc_private_network.main.id
delete_additional_resources = false
}
resource "scaleway_k8s_pool" "main" {
count = var.running ? 1 : 0
cluster_id = scaleway_k8s_cluster.main.id
name = "main"
node_type = var.node_type
size = var.node_count
zone = var.zone
}
resource "scaleway_lb_ip" "traefik" {
zone = var.zone
}
resource "scaleway_lb_ip" "traefik_v6" {
zone = var.zone
is_ipv6 = true
}
Le Private Network est obligatoire sur un cluster Kapsule. delete_additional_resources = false permet de conserver les volumes à la destruction du cluster, mais orphelins : les rattacher demande des PV statiques.
Le cluster n’est donc jamais détruit dans cet article.
Les sorties
# outputs.tf
output "kubeconfig" {
value = scaleway_k8s_cluster.main.kubeconfig[0].config_file
sensitive = true
}
output "lb_ip_id" {
value = split("/", scaleway_lb_ip.traefik.id)[1]
}
output "lb_ip_address" {
value = scaleway_lb_ip.traefik.ip_address
}
output "lb_ipv6_id" {
value = split("/", scaleway_lb_ip.traefik_v6.id)[1]
}
output "lb_ipv6_address" {
value = scaleway_lb_ip.traefik_v6.ip_address
}
output "pod_cidr" {
value = scaleway_k8s_cluster.main.pod_cidr
}
output "private_network_cidr" {
value = scaleway_vpc_private_network.main.ipv4_subnet[0].subnet
}
L’identifiant d’une scaleway_lb_ip a la forme {zone}/{uuid}.
L’annotation du load balancer documente un <ip-id> nu, d’où le split.
La seconde IP, avec is_ipv6 = true, donne l’adresse du futur enregistrement AAAA.
Les commandes utilisent OpenTofu, dont les fichiers et les commandes sont ceux de Terraform.
Pour ne pas passer
-var project_id=...à chaque commande, placez l’identifiant du projet dans un fichierproject.auto.tfvars.OpenTofu charge automatiquement les fichiers
*.auto.tfvars, alors qu’un fichier nomméproject.tfvarsdemande-var-file.
# project.auto.tfvars
project_id = "<SCW_PROJECT_ID>"
Création du cluster
printf '%s\n' kubeconfig.yaml terraform.tfstate terraform.tfstate.backup .terraform/ '*.tfvars' >> .gitignore
tofu init
tofu apply
tofu output -raw kubeconfig > kubeconfig.yaml
chmod 600 kubeconfig.yaml
export KUBECONFIG=$PWD/kubeconfig.yaml
Appliquez, puis écrivez le kubeconfig dans un fichier local.
Il contient un token, et l’état Terraform le contient aussi : les deux restent hors de git.
Créez maintenant l’enregistrement DNS A de o2.example.com vers lb_ip_address, et l’enregistrement AAAA vers lb_ipv6_address.
Les IPs étant réservées, ils ne bougent plus entre deux sessions.
Dans cet article, j’utiliserai o2.obs.scw.clevertechware.fr au lieu o2.example.com.
Mesurer ce qui reste sur les nœuds
Scaleway ne publie pas ce que Kapsule réserve sur chaque nœud, donc mesurez-le :
kubectl get nodes
kubectl describe node | grep -A6 Allocatable
Deux DEV1-M offrent 2 × 4 Go de RAM.
C’est le plus petit choix qui passe : les types à moins de 4 Go (DEV1-S, PLAY2-PICO, STARDUST) sont refusés.
Pour cet article et le suivant, OpenObserve, Traefik et le Collector tiennent dans ce budget.
Dans un prochain article, il faudra ajouter Postgres et trois applications dont une JVM. On estime le besoin à 5 à 6 Go hors pods système : on passera alors en DEV1-L.
Changer
node_typerecrée le pool, mais les volumes restent.
Éteindre le cluster entre deux sessions
Supprimer le pool avec running=false
La variable running pilote le count de scaleway_k8s_pool : running=false supprime le pool, et le cluster reste.
tofu apply -var running=false
tofu apply -var running=trueUn cluster Kapsule au plan de contrôle mutualisé n’accepte pas un pool à zéro nœud : -var node_count=0 échoue avec a mutualized cluster cannot have less than 1 node. D’où la suppression du pool entier.
Sans pool, le cluster passe à l’état pool_required et le nom d’hôte de son API ne se résout plus : kubectl échoue jusqu’au rallumage. Les deux IP de load balancer restent réservées. Le plan de contrôle mutualisé ne coûte rien, et les nœuds ne sont facturés que tant que le pool existe.
