K8S suite: Migration CloudNative

K8S suite: Migration CloudNative
Photo by Todd Trapani / Unsplash

AAAaaaah, un nouvel article ! Bon alors pas mal occupé et la migration de mon serveur Bare Metal a pris son temps. Content de pouvoir réécrire donc car la migration est terminée ! J'aurais pu faire plein d'articles pour chaque étape, mais booon. Tunnel vision toussa.

Bon j'ai menti, le DHCP qui est géré par DHCPD sur mon vieux serveur n'a pas migré mais ce sera migré sur le DHCP de la box ou quelque chose comme ça bientôt. Ce n'est quand même pas le plus exaltant. Qui plus est le faire tourner sur un Kubernetes n'est à mon avis pas une bonne solution. Seul intérêt serait que ce serait l'occasion d'utiliser une Gateway API plutôt que les ingress que j'utilise encore pour l'instant. On a connu mieux comme prétexte au POC.

Alors au menu de cette migration et puis quel intérêt ? Problème de performance majoritairement, au bout d'un moment ton kernel même tweaker il fait pas de miracle quand il tourne sur un trop vieux CPU, et aussi côté consommation électrique. De ce point de vue pas évident que j'y gagne, ça consomme moins mais j'ai multiplié les serveurs avec 2 NUCs et 1 NAS, j'arrive au moins à égaliser la précédente consommation et j'ai dans l'espoir de la réduire ensuite en optimisant les apps et le cluster. J'avais deux-trois trucs qui tournaient sur ce vieux serveur Asus C60M1-I (2012...) quand même, long billet en vue:

Alors, j'ai tout n'a pas été migré ! Au rang des laisser pour compte:

  • TMNF: si j'ai une raison de remonter les serveurs, je le ferais ^^
  • Subsonic: remplacé par Jellyfin mais je recommande ce soft car il m'aura bien servi et très bien fait.
  • ShellInABox: no more need of SSH
  • CUPS: j'ai plus d'imprimante ni scanner problème résolu !
  • NFS: j'ai gardé Samba pour le côté windows fileshare ça suffira bien.
  • DHCP

Hardware et OS: Immutable OS pour Kube = Talos

Le cluster Kube aura bien évolué, on sera passé d'un control plane sur un NUC Intel tournant sur du CoreOS, et le vieux serveur Asus de 2012 sous Fedora en temps que worker à, toujours le NUC Intel et en plus un nœud TBao T8 plus, tous deux désormais sous Talos ! Et avec tout ça, un petit NAS perso tournant sous TrueNAS Scale. Cet édition Scale tournant sous Linux au lieu de BSD et embarque un k3s pour faire tourner des applications conteneurisées.

Alors qu'est ce que j'y gagne ? je ne n'occupe plus de l'OS, c'est devenu un composant de base au même titre que le kernel Linux. Le besoin de pouvoir tweak l'OS est trop peu intéressant pour ce que ça apporte face à un OS immutable pensé et optimisé pour Kubernetes. Je n'ai aucun besoin spécifique et toutes mes bidouilles autour du kernel étaient faites pour palier sa vieillesse... Au bout d'un moment faut débrancher. Alors, on pourra toujours composer l'OS avec Talos cela dit, c'est à dire choisir quels services systèmes on veut ajouter dedans, comme l'iSCSI par exemple, ou des drivers GPU. Au même titre que le kernel était très peu tweaker au final et ce plus dans un but de curiosité que de réels besoins.

Merci à ce serveur Asus de 2012 lui même successeur d'un serveur de 2006 dont j'ai oublié les références. On sera quand même passé d'une Fedora Core 6 à Fedora 35 en upgrade de paquets classique sans réinstalle. Qui a dit que Fedora n'était pas une rolling release !?

Bref ça marche tout seul, on suit la doc, on configure un YAML et on a un nœud avec une API Talos permettant d'y accéder et de le configurer par la suite avec la CLI talosctl. Elle vous permettra notamment de mettre à jour l'OS et/ou la version de Kubernetes aussi facilement qu'un apt-get upgrade, pacman -Suy ou dnf update.

Pourquoi avoir migré de CoreOS à Talos ? Simplement car CoreOS se retrouvait être une Fedora déguisée avec un gestionnaire de paquets à base de rpm-ostree et qu'au final, ça perdait un peu du sens de l'OS immutable. C'est dû notamment au rachat par RedHat de CoreOS qui a forké à l'époque et renommé flatcar. Bref, flatcar était un peu chiant à géré dans le sens où il fallait build son image avec encore des outils pour qui ne faisait pas grand chose à part du templating comme butane. Le support était pas ouf à l'époque, y'a 2 ans, et quand j'ai trouvé Talos, j'ai bien plus aimé l'approche. Le but étant de ne plus s'occuper de l'OS, il a gagné. Il peut venir en addition avec Sidero afin de provisionner le bare metal en automatique avec le classique PXE+TFTP. Je l'ai testé un peu, c'est très facilement utilisable et se base sur ClusterAPI pour provisionner Kube. Propre. Je n'ai pas l'utilité d'un tel logiciel cela dit, donc je provisionne mes nœuds à la main, et utilise la cli pour update. Cela me va bien.

Pour la mise à jour avec Talos, cela se passe par là: https://factory.talos.dev pour composer son image selon le matériel. Ensuite, on prend le sha de l'image et lance l'upgrade. Exemple avec une image x86_64, avec iSCSI et driver i915 (pour l’accélération matérielle de Jellyfin):

$ talosctl upgrade -i factory.talos.dev/installer/ebdfa27a8d6272acf806ac6a5c968c3c284a47ce880273cecb19442c11bf0474:v1.7.1 --preserve

Le flags --preserve étant là pour préserver les data si votre nœud est un control plane et qu'il est le seul du cluster, sinon, scoop, vous perdez votre cluster :D.

Sans faire un manuel de la cli et vu que ce n'est pas un post pour Talos, voici juste les commandes disponibles pour se donner une idée:

A CLI for out-of-band management of Kubernetes nodes created by Talos

Usage:
  talosctl [command]

Available Commands:
  apply-config        Apply a new configuration to a node
  bootstrap           Bootstrap the etcd cluster on the specified node.
  cluster             A collection of commands for managing local docker-based or QEMU-based clusters
  completion          Output shell completion code for the specified shell (bash, fish or zsh)
  config              Manage the client configuration file (talosconfig)
  conformance         Run conformance tests
  containers          List containers
  copy                Copy data out from the node
  dashboard           Cluster dashboard with node overview, logs and real-time metrics
  disks               Get the list of disks from /sys/block on the machine
  dmesg               Retrieve kernel logs
  edit                Edit a resource from the default editor.
  etcd                Manage etcd
  events              Stream runtime events
  gen                 Generate CAs, certificates, and private keys
  get                 Get a specific resource or list of resources (use 'talosctl get rd' to see all available resource types).
  health              Check cluster health
  help                Help about any command
  image               Manage CRI containter images
  inject              Inject Talos API resources into Kubernetes manifests
  inspect             Inspect internals of Talos
  kubeconfig          Download the admin kubeconfig from the node
  list                Retrieve a directory listing
  logs                Retrieve logs for a service
  machineconfig       Machine config related commands
  memory              Show memory usage
  meta                Write and delete keys in the META partition
  mounts              List mounts
  netstat             Show network connections and sockets
  patch               Update field(s) of a resource using a JSON patch.
  pcap                Capture the network packets from the node.
  processes           List running processes
  read                Read a file on the machine
  reboot              Reboot a node
  reset               Reset a node
  restart             Restart a process
  rollback            Rollback a node to the previous installation
  service             Retrieve the state of a service (or all services), control service state
  shutdown            Shutdown a node
  stats               Get container stats
  support             Dump debug information about the cluster
  time                Gets current server time
  upgrade             Upgrade Talos on the target node
  upgrade-k8s         Upgrade Kubernetes control plane in the Talos cluster.
  usage               Retrieve a disk usage
  validate            Validate config
  version             Prints the version

La suite, la stack, les choix, le parcours

Pourquoi cette migration puisque après tout ça marchait bien sur un vieux matos. Plutôt écolo donc. Et ce fût le plus "dur", se séparer d'un truc qui marche. Plus lentement, mais suffisant pour mes besoins. Le problème étant que ce matériel n'était absolument pas fait pour faire tourner un Kubernetes dessus. Vanilla je précise, un k3s aurait pu le faire mais je voulais du vanilla. Le but étant de se faire la main, autant utiliser le mainstream. Je vous renvoi à mon précédent billet sur le sujet de la transition à l'époque.

Alors depuis ces premiers pas, quelques choix ont évolué:

  • Interne:
    • CSI: SMB, NFS et Longhorn --> SMB et Democratic CSI. Rationalisation en supprimant le NFS qui était une plaie à gérer avec le mapping UID/GID, et parfait pour se faire la main mais n'est plus adapté. SMB est conservé par commodité de partage de fichier réseau plus "traditionnel". Le changement Longhorn vers Democratic CSI est uniquement lié au NAS TrueNAS Scale.
    • CNI: Flannel --> Cilium, ici simplement le plus évident avec le CNI le plus populaire en ce moment avec de très bonnes performances, fonctionnalités et support documentaire.
    • Ingress Controller: Traefik --> Cilium, question curiosité et rationalisation des briques logiciels à "maintenir".
    • Gateway API: Cilium
  • Middleware:
    • ArgoCD pour enfin faire du GitOps. Les manifests étaient déjà stockés dans un dépôt Git mais il manquait un outil pour les appliquer sur le cluster.
    • CoreDNS: une seconde instance à celle interne à Kube pour géré les noms sur le réseau local et puis ça fait facilement du DNS over TCP ou HTTPS.
    • LLDAP qui succède à l'ancien OpenLDAP. Ici je me suis basé sur le dépôt conseillé par l'upstream, adapté pour y inclure le script bootstrap.sh et importer mes quelques utilisateurs avec le job kube fourni. Pif paf c'est fait, n'ayant pas besoin de plus, j'ai reconfiguré mes instances pour pointer sur ce nouveau serveur et fait quelques arrangement pour remapper des anciens uuid avec les nouveaux pour ne pas perdre de profils utilisateurs (coucou Nextcloud). Je ferais sans doute un article rapide sur le sujet car ça peut servir.
    • Cert-Manager avec un cluster-issuer let's encrypt.
    • Kyverno pour configurer des trucs non prévu par les charts Helm
    • mysql-operator pour provisionner du MySQL
    • cloudnativepg pareil pour Postgres
    • node-feature-discovery pour l'application des labels sur les nodes selon leur fonctionnalité hardware.
    • inteldeviceplugins-system, gestion des drivers GPU pour Intel, ça peut proposer plus de choses mais je n'utilise que celle-ci.
  • Apps:
  • J'adore les bulletpoints vous saviez ?

Le DNS externe: CoreDNS

Passage de Bind9 à CoreDNS pour la gestion du DNS local. Ici, je ne me suis pas cassé la tête plus que nécessaire, j'ai porté mes fichiers bind9 sous CoreDNS en utilisant son plugin file. Alors pas très flexible cela dit, mais je n'ai pas d'évolution si dynamique de mon "parc". Je continue donc avec les fichiers pour le moment. Ce qui donne quelque chose comme ceci:

.:53 {
    file /etc/zones/claneys.home.db claneys.home
    errors
    health {
        lameduck 5s
    }
    ready
    log . {
        class error
    }
    prometheus :9153

    forward . 192.168.1.254 94.140.14.140
    cache 30
    loop
    reload
    loadbalance
}

#tls://. {
#  tls /opt/tls.crt /opt/tls.key /opt/ca.crt
#  forward . 127.0.0.1
#}
#
#https://. {
#  tls /opt/tls.crt /opt/tls.key /opt/ca.crt
#  forward . 127.0.0.1
#}

Corefile

$ORIGIN claneys.home.
$TTL 30M
@ IN SOA dns.claneys.home. root.claneys.home. (
      2012270652
      4H
      1H
      7D
      4H
    )
  IN NS dns.claneys.home.

dns            A X.X.X.X
old-nas        A X.X.X.X
intel-nuc      A X.X.X.X
ampli-hc       A X.X.X.X
datahouse      A X.X.X.X
old-kube       A X.X.X.X
t8-plus        A X.X.X.X
firetv         A X.X.X.X
roomba         A X.X.X.X
kube-ingress   A X.X.X.X
box            A X.X.X.X

shamash   CNAME   kube-ingress
home      CNAME   kube-ingress
grafana   CNAME   kube-ingress
zwave-js  CNAME   kube-ingress
psono     CNAME   kube-ingress
argocd    CNAME   kube-ingress

Zone

Il est prévu d'activer le DNS over HTTPs et TLS bientôt, c'était tout l'intérêt de CoreDNS. Pouvoir le faire, simplement et rapidement.

ArgoCD: enfin du GitOps, petit bonheur

Aaaah, bon Git c'est bien, le GitOps c'est mieux ! Désormais gestion du cluster depuis ArgoCD. Il nous faut pour être complet la gestion des secrets afin de pouvoir les provisionner dans le cluster. J'ai préféré Sops à Vault simplement par facilité pour la migration. D'abord on migre après, on verra pour mettre un Vault 😄. Du coup, on utilise un plugin kustomizations KSOPS, qui utilise SOPS derrière pour les capacités de chiffrement, avec une intégration à ArgoCD. Alors je trouve ça pas forcément très "pratique" ou "straigthforward" mais une fois en place, ça s'oublie et go.

J'utilise KSOPS avec Age, car je n'utilise pas de KMS externe et comme j'ai pas mis de Vault y'en aura pas non plus en interne. Peut-être plus tard, chaque chose en son temps. La kustomization finale utilise les manifestes de base d'argocd avec les patchs suivant:

argocd-repo-server-patch.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: argocd-repo-server
spec:
  template:
    spec:
      # 1. Define an emptyDir volume which will hold the custom binaries
      volumes:
        - name: custom-tools
          emptyDir: {}
      # 2. Use an init container to download/copy custom binaries into the emptyDir
      initContainers:
        - name: helm-secret
          image: alpine:latest
          imagePullPolicy: IfNotPresent
          env:
            - name: KUBECTL_VERSION
              value: "1.28.3"
            - name: SOPS_VERSION
              value: "3.8.1"
            - name: HELM_SECRETS_VERSION
              value: "4.5.1"
          command: [sh, -ec]
          args:
            - |
              mkdir -p /custom-tools/helm-plugins
              wget -qO- https://github.com/aslafy-z/helm-git/archive/refs/tags/v0.15.1.tar.gz | tar -C /custom-tools/helm-plugins -xzvf-;
              wget -qO- https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/helm-secrets.tar.gz | tar -C /custom-tools/helm-plugins -xzf-;
              wget -qO /custom-tools/sops https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux.amd64
              wget -qO /custom-tools/curl https://github.com/moparisthebest/static-curl/releases/latest/download/curl-amd64
              wget -qO /custom-tools/kubectl https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl
              cp /custom-tools/helm-plugins/helm-secrets/scripts/wrapper/helm.sh /custom-tools/helm
              mv /custom-tools/helm-plugins/helm-git-0.15.1 /custom-tools/helm-plugins/helm-git
              chmod +x /custom-tools/*
          volumeMounts:
            - mountPath: /custom-tools
              name: custom-tools
        - name: install-ksops
          image: viaductoss/ksops:v4.3.0
          command: ["/bin/sh", "-c"]
          args:
            - echo "Installing KSOPS...";
              mv ksops /custom-tools/;
              mv kustomize /custom-tools/;
              echo "Done.";
          volumeMounts:
            - mountPath: /custom-tools
              name: custom-tools
      # 3. Volume mount the custom binary to the bin directory (overriding the existing version)
      containers:
        - name: argocd-repo-server
          volumeMounts:
            - mountPath: /custom-tools
              name: custom-tools
            - mountPath: /usr/local/sbin/helm
              name: custom-tools
              subPath: helm
            - mountPath: /usr/local/bin/kustomize
              name: custom-tools
              subPath: kustomize
            - mountPath: /usr/local/bin/ksops
              name: custom-tools
              subPath: ksops
          env:
            - name: HELM_PLUGINS
              value: /custom-tools/helm-plugins/
            - name: HELM_SECRETS_CURL_PATH
              value: /custom-tools/curl
            - name: HELM_SECRETS_SOPS_PATH
              value: /custom-tools/sops
            - name: HELM_SECRETS_KUBECTL_PATH
              value: /custom-tools/kubectl
            - name: HELM_SECRETS_BACKEND
              value: sops
            - name: HELM_SECRETS_VALUES_ALLOW_SYMLINKS
              value: "false"
            - name: HELM_SECRETS_VALUES_ALLOW_ABSOLUTE_PATH
              value: "true"
            - name: HELM_SECRETS_VALUES_ALLOW_PATH_TRAVERSAL
              value: "false"
            - name: HELM_SECRETS_WRAPPER_ENABLED
              value: "true"
            - name: HELM_SECRETS_DECRYPT_SECRETS_IN_TMP_DIR
              value: "true"
            - name: HELM_SECRETS_HELM_PATH
              value: /usr/local/bin/helm
            - name: SOPS_AGE_KEY
              valueFrom:
                secretKeyRef:
                  name: argocd-age-credentials
                  key: age_secret_key

argocd-repo-server-patch.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  labels:
    app.kubernetes.io/name: argocd-cm
    app.kubernetes.io/part-of: argocd
data:
  kustomize.buildOptions: "--enable-alpha-plugins --enable-exec"
  helm.valuesFileSchemes: >-
    secrets+age-import, secrets+age-import-kubernetes,
    secrets, secrets+literal,
    https

argocd-cm-patch.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
  labels:
    app.kubernetes.io/name: argocd-cmd-params-cm
    app.kubernetes.io/part-of: argocd
data:
  server.insecure: "true"

argocd-cmd-params-cm-patch.yaml

Wala, wala, le argocd-repo-server-patch.yaml justifie le fait que ne soit pas fan de la soluce mais bon. J'ai choisi de mettre la clé SOPS dans un secret qui fait partie d'une autre kustomizations appliquer à la main dans un premier temps, œuf-poule oblige, puis en tant que partie de la post-install d'ArgoCD ensuite.

Voilà après ça, on a un ArgoCD qui fonctionne. Plus qu'à créer pleins d'applications ArgoCD à déployer ! Globalement j'ai suivi le pattern avec des Apps d'Apps pour bootstrapper le reste du cluster. Une grosse apps qui définit toutes les autres Apps.

Le statefulset, l'operator et la DB

Dans mon précédent billet, je relayais les problèmes de Kubernetes avec le stockage et surtout les problèmes hébergés une DB type SQL. En effet, une base de données SQL fonctionne en standalone de base et ne scale pas trop horizontalement "simplement". Elles ont généralement toute en revanche des mécanismes de réplication et HA ad hoc pour se mettre en cluster avec de la configuration et huile de coude. Souvent à base de journaux d'opérations SQL à rejouer et écriture plus intensives sur les disques etc. Cela existe depuis un sacré moment et les options sont pléthores, cela ne rends pas la chose très accessible pour un néophyte.

Du côté Kubernetes, il faudra attendre les statefulsets stable pour avoir un accès au stockage stable. Après le boulot de réplication n'étant pas son boulot, il nous manquera les operator Kube qui vont nous permettre de configurer la réplication d'instance SQL simplement sans être DBA ou manger de la docs sur X BDD. Du moins pour des besoins modestes de homelab, ça fait le taf facile !

Donc, j'avais mariaDB et MySQL, j'ai décidé de ne garder que MySQL à mon grand désarroi juste parce que la plateforme de blog que j'utilise, ghost, n'est plus compatible MariaDB... Et donc, il me reste 2 operators pour gérer cela:

Globalement, un cluster simple de MySQL ressemble à ça:

apiVersion: mysql.oracle.com/v2
kind: InnoDBCluster
metadata:
  name: nextcloud-db
  namespace: nextcloud
spec:
  secretName: mysql-pwds
  tlsUseSelfSigned: true
  instances: 2
  router:
    instances: 1

nextcloud-db.yaml

Hop, suffit de modifier le nombre d'instance et paf, ça réplique. 😎 Pas trop fan de cet operator, notamment à cause de la documentation vraiment limite. J'aurais bien aimé des capacités d'import un peu plus évolués pour cloner une base en fonctionnement.

Par contre l'operator cloudnativepg, lui le fait et est très bien documenté. Petit exemple:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: psono-db
  namespace: psono
spec:
  instances: 1

  bootstrap:
    initdb:
      database: psono
      owner: psono
      secret:
        name: psono-db-pwd
      import:
        type: microservice
        databases:
          - psono
        source:
          externalCluster: gilgamesh
  storage:
    size: 1Gi
  externalClusters:
    - name: gilgamesh
      connectionParameters:
        # Use the correct IP or host name for the source database
        host: gilgamesh.uruk.home
        user: postgres
        dbname: psono

cnpg.yaml

Configuration de Cilium

Pas grand chose à dire ici, j'avais quelques prérequis pour m'éviter de multiplier les middlewares, je voulais qu'il puisse être à la fois provider d'IP pour les services LoadBalancer, et m'éviter de déployer un MetalLB ou assimiler, Ingress controller pour remiser mon Traefik, supporter le Gateway API pour une future migration. Ce que fait Cilium !

Pour le côté LB, j'utilise la fonctionnalité de L2 announcements afin d'exposer les IPs sur le réseau local en dehors de Kube. Une alternative au setup BGP cilium, restons pragmatique j'ai pas trop envie de mettre du matos pour géré du BGP ou faire un setup pour ça.

Les values helm que j'utilise sont celles-ci:

rollOutCiliumPods: true
cgroup:
  autoMount:
    enabled: false
  hostRoot: /sys/fs/cgroup
cluster:
  id: 1
  name: uruk
hubble:
  enabled: true
  relay:
    enabled: true
ipam:
  mode: kubernetes
gatewayAPI:
  enabled: false
k8sServiceHost: localhost
k8sServicePort: 7445
kubeProxyReplacement: true
operator:
  replicas: 1
  rollOutPods: true
  securityContext:
    seccompProfile:
      type: "RuntimeDefault"
ingressController:
  enabled: true
  default: true
  defaultSecretNamespace: kube-system
  defaultSecretName: default-cert
  loadbalancerMode: shared
  service:
    annotations:
      io.cilium/lb-ipam-ips: X.X.X.X
l2announcements:
  enabled: true
  # -- If a lease is not renewed for X duration, the current leader is considered dead, a new leader is picked
  leaseDuration: 300s
  # -- The interval at which the leader will renew the lease
  leaseRenewDeadline: 60s
  # -- The timeout between retries if renewal fails
  leaseRetryPeriod: 10s
k8sClientRateLimit:
  qps: 100
  burst: 500
externalIPs:
  enabled: true
securityContext:
  capabilities:
    ciliumAgent:
    - CHOWN
    - KILL
    - NET_ADMIN
    - NET_RAW
    - IPC_LOCK
    - SYS_ADMIN
    - SYS_RESOURCE
    - DAC_OVERRIDE
    - FOWNER
    - SETGID
    - SETUID
    cleanCiliumState:
    - NET_ADMIN
    - SYS_ADMIN
    - SYS_RESOURCE
  seccompProfile:
    type: "RuntimeDefault"

cilium-values.yaml

On y voit notamment le port 7445 pour joindre l'api kube en local qui est une fonctionnalité apporté par Talos avec KubePrism, tldr ça permet de toujours pouvoir joindre l'API tant qu'ya un control plane dispo sans pour autant setup d'adresse IP de service.

Côté l2 announcement:

apiVersion: cilium.io/v2alpha1
kind: CiliumL2AnnouncementPolicy
metadata:
  name: l2announce
spec:
  externalIPs: true
  interfaces:
  - ^enp
  - ^eno
  loadBalancerIPs: true
  serviceSelector:
    matchLabels:
      announced: "true"

Et mon pool d'IPs pour l'IPAM:

apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
  name: main-pool
spec:
  cidrs:
  - start: "192.168.1.128"
    stop: "192.168.1.253"

Democratic CSI et chiffrement de valeurs de Chart Helm pour ArgoCD

Democratic CSI peut s'installer avec une Helm Chart. C'est encore le plus simple, un ensemble d'exemples ont été fourni par le CSI pour plusieurs type. Ici, ce sera l'exemple de freenas-iscsi que j'utiliserais pour utiliser à mon TrueNAS en tant que fournisseur de volumes.

Et en gros, voici les valeurs que j'utilise:

csiDriver:
    # should be globally unique for a given cluster
    name: org.democratic-csi.iscsi
node:
    hostPID: true
    driver:
        extraEnv:
            - name: ISCSIADM_HOST_STRATEGY
              value: nsenter
            - name: ISCSIADM_HOST_PATH
              value: /usr/local/sbin/iscsiadm
        iscsiDirHostPath: /usr/local/etc/iscsi
        iscsiDirHostPathType: ""
# add note here about volume expansion requirements
storageClasses:
    - name: freenas-iscsi-csi
      defaultClass: true
      reclaimPolicy: Retain
      volumeBindingMode: Immediate
      allowVolumeExpansion: true
      parameters:
        # for block-based storage can be ext3, ext4, xfs
        # for nfs should be nfs
        fsType: xfs
        # if true, volumes created from other snapshots will be
        # zfs send/received instead of zfs cloned
        # detachedVolumesFromSnapshots: "false"
        # if true, volumes created from other volumes will be
        # zfs send/received instead of zfs cloned
        # detachedVolumesFromVolumes: "false"
      mountOptions: []
      secrets:
        provisioner-secret: null
        controller-publish-secret: null
        node-stage-secret: null
        #      # any arbitrary iscsiadm entries can be add by creating keys starting with node-db.<entry.name>
        #      # if doing CHAP
        #      node-db.node.session.auth.authmethod: CHAP
        #      node-db.node.session.auth.username: foo
        #      node-db.node.session.auth.password: bar
        #
        #      # if doing mutual CHAP
        #      node-db.node.session.auth.username_in: baz
        #      node-db.node.session.auth.password_in: bar
        node-publish-secret: null
        controller-expand-secret: null
# if your cluster supports snapshots you may enable below
volumeSnapshotClasses: []
#- name: freenas-iscsi-csi
#  parameters:
#  # if true, snapshots will be created with zfs send/receive
#  # detachedSnapshots: "false"
#  secrets:
#    snapshotter-secret:
driver:
    config:
        # please see the most up-to-date example of the corresponding config here:
        # https://github.com/democratic-csi/democratic-csi/tree/master/examples
        # YOU MUST COPY THE DATA HERE INLINE!
        driver: freenas-api-iscsi
        instance_id: null
        httpConnection:
            protocol: http
            host: datahouse.uruk.home
            port: 80
            # use only 1 of apiKey or username/password
            # if both are present, apiKey is preferred
            # apiKey is only available starting in TrueNAS-12
            apiKey: ENC[AES256_GCM,data:PaaE4griA4NGcVMoCoIiS6fPAI5lnh+19fUaWudxIDN+5cRxoD1ACPlVA3X3GWQABlWRYRKWCcD3LD7OPw30Vl4E,iv:3XU9e7X1NdBYcpfPoHDvWjzH/WjMDE7yRHdXIOVcUaA=,tag:leowueiG9T5tqQjsbNkeDQ==,type:str]
            username: root
            allowInsecure: true
            # use apiVersion 2 for TrueNAS-12 and up (will work on 11.x in some scenarios as well)
            # leave unset for auto-detection
            #apiVersion: 2
        zfs:
            # can be used to override defaults if necessary
            # the example below is useful for TrueNAS 12
            #cli:
            #  sudoEnabled: true
            #
            #  leave paths unset for auto-detection
            #  paths:
            #    zfs: /usr/local/sbin/zfs
            #    zpool: /usr/local/sbin/zpool
            #    sudo: /usr/local/bin/sudo
            #    chroot: /usr/sbin/chroot
            # can be used to set arbitrary values on the dataset/zvol
            # can use handlebars templates with the parameters from the storage class/CO
            #datasetProperties:
            #  "org.freenas:description": "{{ parameters.[csi.storage.k8s.io/pvc/namespace] }}/{{ parameters.[csi.storage.k8s.io/pvc/name] }}"
            #  "org.freenas:test": "{{ parameters.foo }}"
            #  "org.freenas:test2": "some value"
            # total volume name (zvol/<datasetParentName>/<pvc name>) length cannot exceed 63 chars
            # https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
            # standard volume naming overhead is 46 chars
            # datasetParentName should therefore be 17 chars or less when using TrueNAS 12 or below
            datasetParentName: data_tank/k8s_share
            # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap
            # they may be siblings, but neither should be nested in the other 
            # do NOT comment this option out even if you don't plan to use snapshots, just leave it with dummy value
            detachedSnapshotsDatasetParentName: data_tank/k8s_snapshot
            # "" (inherit), lz4, gzip-9, etc
            zvolCompression: null
            # "" (inherit), on, off, verify
            zvolDedup: null
            zvolEnableReservation: false
            # 512, 1K, 2K, 4K, 8K, 16K, 64K, 128K default is 16K
            zvolBlocksize: null
        iscsi:
            targetPortal: datahouse.uruk.home:3260
            # for multipath
            targetPortals: []
            # leave empty to omit usage of -I with iscsiadm
            interface: null
            # MUST ensure uniqueness
            # full iqn limit is 223 bytes, plan accordingly
            # default is "{{ name }}"
            #nameTemplate: "{{ parameters.[csi.storage.k8s.io/pvc/namespace] }}-{{ parameters.[csi.storage.k8s.io/pvc/name] }}"
            namePrefix: csi-
            nameSuffix: -cluster
            # add as many as needed
            targetGroups:
                # get the correct ID from the "portal" section in the UI
                - targetGroupPortalGroup: 1
                  # get the correct ID from the "initiators" section in the UI
                  targetGroupInitiatorGroup: 1
                  # None, CHAP, or CHAP Mutual
                  targetGroupAuthType: None
                  # get the correct ID from the "Authorized Access" section of the UI
                  # only required if using Chap
                  targetGroupAuthGroup: null
            #extentCommentTemplate: "{{ parameters.[csi.storage.k8s.io/pvc/namespace] }}/{{ parameters.[csi.storage.k8s.io/pvc/name] }}"
            extentInsecureTpc: true
            extentXenCompat: false
            extentDisablePhysicalBlocksize: true
            # 512, 1024, 2048, or 4096,
            extentBlocksize: 4096
            # "" (let FreeNAS decide, currently defaults to SSD), Unknown, SSD, 5400, 7200, 10000, 15000
            extentRpm: "7200"
            # 0-100 (0 == ignore)
            extentAvailThreshold: 0
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age:
        - recipient: age1ne0yrjzklkpl2fwyt2r82hzruhcq8ny82mrsullyzd9ejylfja2qlrj9kg
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQWFpwTUdsV2dVWk5sUTlz
            L01VSTJpUFRpNktmQmgvalR4RVBjTHRPY0ZnCjF1SUpqNEVqd3haR3dGeGR5bDNu
            ZVhnNlp4N2gxTEE0WklURmpTTDZDZDQKLS0tIDJyRE1lbkVsQWZOTXdQOG1IRzRO
            TzVEVkVHVnA0TzBTdndKZjI5RnB6YUEKC3OI32/BQ0oCEPExgUr4e7Rxufww4k/q
            B9OfW1t4w9DWEZ4Y5ilUywNX15CTWKrnggW57YEuSejAfdqYnDXCoQ==
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2024-01-23T22:59:20Z"
    mac: ENC[AES256_GCM,data:nTulPFgnwMqewxpzfzAOOrIxIFmW1AVfpLoQSKbChXAN1odUfWCltqi8WupW5ghl2P3YsrwqlXLPQ4L9SbH87GcnjXjAcoqav/fvD/WjK9mrGzS2lZWYIH80HTYalxfwWelGc3my5u39BlnKeen66RCuCTGfelwE9m+VfdhwolY=,iv:XQT9sjNpyCWfF1NeRt8u5GDRnPvyq9GSJuPGf5yGMLA=,tag:U/2CNhN+qWLSzP3IRHRu+Q==,type:str]
    pgp: []
    encrypted_regex: apiKey
    version: 3.8.1

democratic-csi-freenas-iscsi-values.yaml

Et cela a son importance car j'ai pas mal galéré pour pouvoir avoir ArgoCD qui déchiffre le fichier de valeurs car autant dans une kustomization KSOPS fait le job pour les kustomizations mais autant pour un fichier de values Helm c'est pas prévu ! J'ai eu à modifier un chouille la syntaxe pour le faire fonctionner dans mon cas car il me fallait récup le secret depuis un endroit externe ce qui n'est pas supporté par le projet helm-secrets.

On ArgoCD 2.6.x, helm-secrets isn't supported in Multi-Source application, because the source reference, e.g.: $ref needs to be at the beginning of a string. This is in conflict with helm-secrets, since the string needs to begin with secrets://. On top, ArgoCD do not resolve references in URLs.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: democratic-csi
  namespace: argocd
spec:
  project: default
  sources:
  - repoURL: https://democratic-csi.github.io/charts/
    chart: democratic-csi
    targetRevision: 0.14.6
    helm:
      releaseName: democratic-csi
      valueFiles:
        - secrets://git+https://argocd:un-global-access-token-from-gitlab@gitlab.com/claneys/k8s.git@democratic-csi/democratic-csi-freenas-iscsi-values.yaml?ref=feat/migration
  - repoURL: git@gitlab.com:claneys/k8s.git
    targetRevision: feat/migration
    ref: k8s
  destination:
    server: "https://kubernetes.default.svc"
    namespace: democratic-csi
  syncPolicy:
    automated: {}

democratic-csi-application.yaml

On remarquera la syntaxe digne des meilleures regex pointant vers le fichier de values donc... Alors ça ne pointe sur $ref comme précisé car pas au début la ligne... Alors on contourne salement. On lui indique le fichier de valeurs est un secrets SOPS à déchiffrer, ensuite on indique qu'il est stocké dans une git accessible par https, puis et c'est la où le bât blesse, y'a un token gitlab qui traine dans l'url https vers le fichier. Bon le token n'a que des droits en lecture mais clairement pas fou ! C'est le problème. Enfin à la fin de la ligne on y met la branche qu'on veut.

Clairement si vous utilisez un KMS c'est mieux... Next step donc.

Bon bah on a fait le tour, je crois 😄.