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
| Valeur | Defaut | Effet |
|---|---|---|
replicaCount | 1 | nombre 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.allowStaticToken | false | allow_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.pullPolicy | craftogether/craft-file-gate, la version du chart, IfNotPresent | l’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.type | 2222, ClusterIP | le Service <release>, SFTP seul |
service.admin.port, service.admin.type | 8080, ClusterIP | le Service <release>-api : API de fichiers, explorateur ; absent avec un config.inline sans [admin] |
service.control.port | 8081 | control_listen : /admin, /metrics, sondes ; "" pour une seule porte ; ignore avec un config.inline sans [admin] |
service.control.type | ClusterIP | le 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.expose | false | ce port aussi sur le Service <release>-control : sans lui, sondes et collecte passent par l’IP du pod |
networkPolicy.enabled | true | une NetworkPolicy d’entree sur le pod |
networkPolicy.publicFrom | [] : toute source | sources admises sur SFTP et le port admin |
networkPolicy.controlFrom | - namespaceSelector: {} : tout pod du cluster | sources admises sur le port de controle et celui des sondes ; au moins une |
ban.backend | file | configmap pour partager les bans entre pods |
ban.configmapName | "" : <release>-bans | la ConfigMap des bans, creee par le chart et donnee au serveur par CRAFT_FILE_GATE_{SFTP,API,ADMIN}_BAN_CONFIGMAP_NAME |
stateDir.enabled, stateDir.path | true, /var/lib/craft-file-gate | un emptyDir pour le fichier de bans |
logFiles.enabled, logFiles.dir | false, /var/log/craft-file-gate | fichiers de log, dans un emptyDir ou logFiles.existingClaim |
passwordHashing.workers | "" : requests.cpu | fils de hachage ; voir Verification des mots de passe |
passwordHashing.queue | "" : 1024 | file de hachage |
resources | requests 100m, 64Mi ; limits 500m, 256Mi | voir Memoire |
tls.enabled | false | sondes 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 s | le 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 ; 8083 | le canal entre pods : Plusieurs instances |
cluster.minPeers, cluster.unreadyWhen | "" : floor(replicaCount / 2) ; "" : alerter seulement | les 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
ipBlockanetworkPolicy.controlFrom. Pour restreindre Prometheus et l’ingress, listez leurs namespaces dansnetworkPolicy.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.
| Reglage | Valeur |
|---|---|
| utilisateur | runAsNonRoot: true, runAsUser, runAsGroup, fsGroup : 65532 |
| privileges | allowPrivilegeEscalation: false, privileged: false, capabilities.drop: [ALL] |
| seccomp | RuntimeDefault |
| racine | readOnlyRootFilesystem: true : le serveur n’ecrit que dans un volume |
| jeton de service account | monte avec ban.backend: configmap, le Secret partage (clusterSecret) ou la ConfigMap d’acces (adminRevocations, adminGrants) |
| configuration, cles d’hote | montees 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 partage | Comment | Voir |
|---|---|---|
| les pods se trouvent | des 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 partage | Plusieurs instances |
| cle de session de la console | le Secret partage (clusterSecret), entree admin-session.key | Le Secret partage des pods |
| revocations de la console, acces temporaires | la ConfigMap <release>-access (access.configmapName) | Partager les revocations |
| bans | ConfigMap (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 eventuelle | Bans |
| sessions, metriques, limites de debit | rien : 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.