Déployer indicat

Une vraie application web sur le cluster : un namespace, ses secrets, PostgreSQL (opérateur ou StatefulSet), le Deployment avec sondes et limites, les migrations, un Service, un Ingress avec TLS sur ton domaine, et un autoscaler.

intermédiaire~50 min de manipulation
#kubernetes#k3s#postgresql#cloudnativepg#ingress#hpa

Pas encore validée de bout en bout — sois le premier.Signaler un problème

Brouillon — pas encore exécuté de bout en bout. Cette page est écrite mais son auteur ne l'a pas encore déroulée sur une vraie machine. Des commandes peuvent être fausses : lis avant de lancer, et dis-nous ce qui casse.

En un coup d'œilindicat sur le cluster
Cluster (kube-system et opérateurs)
Ton namespace
HTTPSIngressDATABASE_URLpull
Utilisateurshttps://
Registre
TraefikIngress · TLS
indicat pods · :
PostgreSQLindicat-db-rw:5432

Traefik termine le TLS pour ${APP_DOMAIN} et route vers le Service indicat, qui répartit les requêtes sur les pods. Les pods lisent leur configuration dans un Secret et écrivent dans PostgreSQL via le Service de la base. L'image vient du registre à chaque démarrage de pod.

indicat est une application web conteneurisée : une image, , qui écoute sur , a besoin d'un DATABASE_URL et d'une SECRET_KEY dans son environnement, et répond sur /healthz quand elle va bien. Cette page lui donne un namespace, ses secrets, un PostgreSQL, réplicas avec sondes et limites, des migrations qui tournent avant chaque rollout, un Ingress avec TLS sur , et un autoscaler.

Avant de commencer

$openssl rand -hex 32 # APP_SECRET
$openssl rand -hex 16 # DB_PASSWORD

Namespace et secrets

$kubectl create namespace --dry-run=client -o yaml | kubectl apply -f -
$kubectl -n create secret generic indicat-db-app --type=kubernetes.io/basic-auth \
$ --from-literal=username=indicat --from-literal=password="" \
$ --dry-run=client -o yaml | kubectl apply -f -
$kubectl -n create secret generic indicat-secrets \
$ --from-literal=DATABASE_URL="postgresql://indicat:@indicat-db-rw:5432/indicat" \
$ --from-literal=SECRET_KEY="" \
$ --dry-run=client -o yaml | kubectl apply -f -

PostgreSQL

$helm repo add cnpg https://cloudnative-pg.github.io/charts --force-update
$helm upgrade --install cnpg cnpg/cloudnative-pg --namespace cnpg-system --create-namespace --wait
deploy/postgres.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: indicat-db
spec:
instances: 1
imageName: ghcr.io/cloudnative-pg/postgresql:16
storage:
size: 10Gi
storageClass: local-path
bootstrap:
initdb:
database: indicat
owner: indicat
secret:
name: indicat-db-app
$kubectl -n apply -f deploy/postgres.yaml
$kubectl -n get cluster indicat-db -w
Vérification
$kubectl -n  get cluster indicat-db -o jsonpath='{.status.phase}'
Retour attendu
Cluster in healthy state

Pull secret

$kubectl -n create secret docker-registry regcred \
$ --docker-server=$(echo | cut -d/ -f1) \
$ --docker-username= --docker-password="" \
$ --dry-run=client -o yaml | kubectl apply -f -
$kubectl -n patch serviceaccount default -p '{"imagePullSecrets":[{"name":"regcred"}]}'
Vérification
$kubectl -n  get serviceaccount default -o jsonpath='{.imagePullSecrets[0].name}'
Retour attendu
regcred

Migrations

deploy/migrate.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: indicat-migrate
labels: { app: indicat-migrate }
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 600
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image:
command: ["npm", "run", "migrate"]
envFrom:
- secretRef: { name: indicat-secrets }
$kubectl -n delete job indicat-migrate --ignore-not-found
$kubectl -n apply -f deploy/migrate.yaml
$kubectl -n wait --for=condition=complete job/indicat-migrate --timeout=300s
Vérification
$kubectl -n  wait --for=condition=complete job/indicat-migrate --timeout=10s
Retour attendu
job.batch/indicat-migrate condition met

L'application

deploy/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: indicat
spec:
replicas:
selector:
matchLabels: { app: indicat }
strategy:
type: RollingUpdate
rollingUpdate: { maxUnavailable: 0, maxSurge: 1 }
template:
metadata:
labels: { app: indicat }
spec:
containers:
- name: indicat
image:
ports:
- containerPort:
envFrom:
- secretRef: { name: indicat-secrets }
readinessProbe:
httpGet: { path: /healthz, port: }
periodSeconds: 5
livenessProbe:
httpGet: { path: /healthz, port: }
initialDelaySeconds: 15
periodSeconds: 10
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 256Mi }
$kubectl -n apply -f deploy/deployment.yaml -f deploy/service.yaml -f deploy/ingress.yaml
$kubectl -n rollout status deploy/indicat
Vérification
$kubectl -n  get pods -l app=indicat --field-selector=status.phase=Running --no-headers | wc -l
Retour attendu
Vérification
$curl -s -o /dev/null -w '%{http_code}' https:///healthz
Retour attendu
200
Si les pods ne passent jamais Ready

Commence par les événements et les logs :

$kubectl -n describe pod -l app=indicat | tail -20
$kubectl -n logs -l app=indicat --tail=50

Par ordre de probabilité :

  • ImagePullBackOff : mauvais nom ou tag d'image, ou pull secret manquant ou expiré.
  • Readiness probe failed: connection refused : le processus n'écoute pas sur , ou pas sur toutes les interfaces (0.0.0.0).
  • Les logs montrent une erreur de base : DATABASE_URL a le mauvais hôte ou mot de passe. Décode le Secret pour voir ce que voit le pod : kubectl get secret indicat-secrets -o jsonpath='{.data.DATABASE_URL}' | base64 -d.
  • OOMKilled dans le statut du pod : monte limits.memory.

Autoscaling

deploy/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: indicat
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: indicat
minReplicas:
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
$kubectl -n apply -f deploy/hpa.yaml
Vérification
$kubectl -n  top pods -l app=indicat --no-headers | wc -l
Retour attendu

Au quotidien : rollout, rollback, logs, exec

$kubectl -n rollout status deploy/indicat
$kubectl -n rollout history deploy/indicat
$kubectl -n rollout undo deploy/indicat
$kubectl -n logs -l app=indicat --tail=100 -f
$kubectl -n exec -it deploy/indicat -- sh

Plus d'un réplica

Avec pods, trois choses qui étaient gratuites sur un serveur demandent une décision : les sessions, les migrations, et ce qui se passe pendant la maintenance d'un nœud.

Terminé

indicat répond sur depuis pods, avec sa base, ses secrets, des migrations qui tournent avant chaque rollout, et un autoscaler. Chaque objet est un fichier dans deploy/, exactement ce dont la page suivante a besoin : un pipeline qui construit l'image à chaque push, la tague avec le commit, et applique ces fichiers avec un ServiceAccount qui ne peut toucher qu'à .

Tout a fonctionné ?

Si tu as suivi cette page jusqu'au bout sur une vraie machine, dis-le. Ta validation est datée et enregistre ta stack : le prochain lecteur sur le même chemin sait que ça marche toujours.

Cette copie est en lecture seule. Pour dire que ça marche, ou que ça ne marche pas, ouvre une issue

Seuls tes choix de stack sont enregistrés, jamais tes valeurs. Le pseudo reste sur ce navigateur.