Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kubernetes

Le parcours de Docker sous le chart : le stockage local sur un volume, les comptes dans un Secret, les roles dans config.toml.

kubectl create secret generic sftp-host-keys --from-file=host_ed25519
kubectl create secret generic sftp-users --from-file=users.toml
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: sftp-data }
spec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 10Gi } } }
EOF
helm install sftp deploy/helm/craft-file-gate -f sftp-values.yaml \
  --set-file config.inline=config.toml
# sftp-values.yaml
hostKeys:
  existingSecret: sftp-host-keys                         # /keys/host_ed25519
extraVolumes:
  - name: data
    persistentVolumeClaim: { claimName: sftp-data }
  - name: users
    secret: { secretName: sftp-users, defaultMode: 0440 }
extraVolumeMounts:
  - { name: data, mountPath: /data }                     # root = "/data" du backend
  - { name: users, mountPath: /secrets/users, readOnly: true }

config.toml est celui de Docker suivi du contenu de son roles.toml (le chart ne monte que config.toml), avec host_keys = ["/keys/host_ed25519"] sous [sftp] et users_file = "/secrets/users/users.toml" sous [auth]. Les ecoutes restent sur 0.0.0.0 (les sondes joignent le pod par son IP) et [[admin.roles]] ouvre la console, puisque le chart ecarte le jeton statique. Le volume est au groupe du pod (fsGroup 65532) : create_home y cree alice/. Au-dela d’un pod, le volume est ReadWriteMany.

Le chart

ValeurDefautEffet
replicaCount1nombre de pods ; au-dela de 1, voir Plusieurs pods
config.inline""le contenu de config.toml, rendu dans le ConfigMap <release>-config ; un changement fait rouler les pods
config.existingConfigMap""ou un ConfigMap a vous, cle config.toml ; exactement une des deux sources
config.allowStaticTokenfalseallow_static_token, ecrit sous [admin] dans config.inline sauf si la configuration le pose ; avec existingConfigMap, a ecrire vous-meme. Voir S’authentifier
extraVolumes, extraVolumeMounts, extraEnv, extraEnvFrom[]ajoutes tels quels au pod et a son conteneur, apres ceux du chart : un volume pour la racine d’un backend local, un Secret
image.repository, image.tag, image.pullPolicycraftogether/craft-file-gate, la version du chart, IfNotPresentl’image
hostKeys.existingSecret""le Secret des cles d’hote, monte dans hostKeys.mountPath (/keys), fichiers en 0440 avec le fsGroup du pod ; voir Secrets
service.sftp.port, service.sftp.type2222, ClusterIPle Service <release>, SFTP seul
service.admin.port, service.admin.type8080, ClusterIPle Service <release>-api : API de fichiers, explorateur ; absent avec un config.inline sans [admin]
service.control.port8081control_listen : /admin, /metrics, sondes ; "" pour une seule porte ; ignore avec un config.inline sans [admin]
service.control.typeClusterIPle type du Service <release>-control : port de controle et port des sondes expose
service.probes.port""probes_listen : /livez, /readyz, /health et /metrics sur leur port, en HTTP ; les sondes y vont
service.probes.exposefalsece port aussi sur le Service <release>-control : sans lui, sondes et collecte passent par l’IP du pod
networkPolicy.enabledtrueune NetworkPolicy d’entree sur le pod
networkPolicy.publicFrom[] : toute sourcesources admises sur SFTP et le port admin
networkPolicy.controlFrom- namespaceSelector: {} : tout pod du clustersources admises sur le port de controle et celui des sondes ; au moins une
ban.backendfileconfigmap pour partager les bans entre pods
ban.configmapName"" : <release>-bansla ConfigMap des bans, creee par le chart et donnee au serveur par CRAFT_FILE_GATE_{SFTP,API,ADMIN}_BAN_CONFIGMAP_NAME
stateDir.enabled, stateDir.pathtrue, /var/lib/craft-file-gateun emptyDir pour le fichier de bans
logFiles.enabled, logFiles.dirfalse, /var/log/craft-file-gatefichiers de log, dans un emptyDir ou logFiles.existingClaim
passwordHashing.workers"" : requests.cpufils de hachage ; voir Verification des mots de passe
passwordHashing.queue"" : 1024file de hachage
resourcesrequests 100m, 64Mi ; limits 500m, 256Mivoir Memoire
tls.enabledfalsesondes en HTTPS, sauf sur service.probes.port ; [admin.tls] se regle dans config.toml
terminationGracePeriodSeconds"" : le shutdown_grace_period_secs de config.inline plus 60 s, sinon 90 sle delai d’arret du pod, au-dessus du pire cas d’un arret ; a poser avec un existingConfigMap qui allonge le delai
clusterSecret.*, adminRevocations.*, adminGrants.*, access.*la cle de session et les revocations partagees entre pods : Secrets
cluster.enabled, cluster.port"" : des que replicaCount depasse 1 ; 8083le canal entre pods : Plusieurs instances
cluster.minPeers, cluster.unreadyWhen"" : floor(replicaCount / 2) ; "" : alerter seulementles detecteurs ; unreadyWhen : une liste, ou auto (storage_alone, isolated_and_storage_down)
  • Un Service par exposition : publier SFTP par un LoadBalancer (service.sftp.type) ne publie ni la console, ni /metrics, ni les sondes.
  • Les sondes du kubelet viennent du noeud du pod, que la plupart des CNI laissent passer malgre la NetworkPolicy ; sinon, ajoutez le CIDR des noeuds en ipBlock a networkPolicy.controlFrom. Pour restreindre Prometheus et l’ingress, listez leurs namespaces dans networkPolicy.controlFrom.

Les sondes

/livez et /readyz (Sondes et sante) sont sondes sur le port des sondes quand il est donne, sinon sur le port de controle, sinon sur le port admin.

Un port pour les sondes

service.probes.port: 9090 pose [server] probes_listen : les sondes et /metrics ont un port a eux, en HTTP simple, meme sous [admin.tls] (Une porte ou deux). Le kubelet et Prometheus (par pod : PodMonitor ou annotations) n’ont plus besoin du certificat ni du port de la console ; le port n’est sur le Service <release>-control qu’avec service.probes.expose: true. Un pod SFTP sans [admin] a ainsi ses sondes, /readyz pret des que la porte SFTP ecoute.

Durcissement du pod

Le chart applique le profil restricted des Pod Security Standards. Chaque champ se surcharge dans podSecurityContext et securityContext ; null retire un champ ou un bloc.

ReglageValeur
utilisateurrunAsNonRoot: true, runAsUser, runAsGroup, fsGroup : 65532
privilegesallowPrivilegeEscalation: false, privileged: false, capabilities.drop: [ALL]
seccompRuntimeDefault
racinereadOnlyRootFilesystem: true : le serveur n’ecrit que dans un volume
jeton de service accountmonte avec ban.backend: configmap, le Secret partage (clusterSecret) ou la ConfigMap d’acces (adminRevocations, adminGrants)
configuration, cles d’hotemontees readOnly: true

La racine d’un backend local est un volume de extraVolumes. Cles d’hote et paire TLS viennent d’un Secret (cert-manager pour le TLS), jamais d’un emptyDir : auto_generate de [admin.tls] ne sert pas sous ce chart.

Plusieurs pods

Ce qui se partageCommentVoir
les pods se trouventdes que replicaCount depasse 1 : Service headless <release>-cluster, port cluster.port reserve aux pods de la release (app.kubernetes.io/instance), certificat dans le Secret partagePlusieurs instances
cle de session de la consolele Secret partage (clusterSecret), entree admin-session.keyLe Secret partage des pods
revocations de la console, acces temporairesla ConfigMap <release>-access (access.configmapName)Partager les revocations
bansConfigMap (ban.backend: configmap), suivie par un watch : un peu plus de 100 ms ; ou fichier sur un volume ReadWriteMany (persist_file), relu toutes les 5 s, convergence eventuelleBans
sessions, metriques, limites de debitrien : propres a chaque pod ; GET /admin/sessions liste celles du pod qui repond, chacune avec son pod_name (HOSTNAME) ; scope=cluster (la console) celles de tous

Chaque Role porte sur un seul nom, sans create : get, update sur le Secret ; get, update, patch sur chaque ConfigMap, watch sur leur collection pour les bans (helm template ... --show-only templates/rbac.yaml).

Le partage par ConfigMap

Feature k8s (dans les images publiees). Le pod lit la ConfigMap avant de servir ; chaque publication est conditionnee a la resourceVersion lue, et un data.bans efface par kubectl edit est republie a la suivante. Sans watch, la propagation tombe a une relecture toutes les 30 s ; un deploiement qui ne cree pas la ConfigMap ajoute create sur la collection.

[sftp.ban]           # de meme sous [api.ban] et [admin.ban] ; le chart en donne le nom
backend = "configmap"

inotify sur un noeud partage

fs.inotify.max_user_instances (128 par defaut) se compte par UID, sur tout le noeud. Le serveur n’en prend qu’une. Quand d’autres pods l’ont epuise et que vous ne pouvez pas regler le noeud :

[reload]
watch = "poll"   # aucune instance inotify ; une mise a jour de Secret est vue en 5 s

Voir Rechargement.