DEVOPS
Avance

Deploiement d'applications avec Kubernetes : Guide Complet

Deploiement d'applications avec Kubernetes : Guide Complet

Guide complet pour deployer et gerer vos applications dans un cluster Kubernetes : Helm, Kustomize, HPA, securite, troubleshooting et best practices production.

Florian Courouge
30 min de lecture
5,847 mots
890 vues
kubernetes
devops
containers
deployment
helm
kustomize

Deploiement d'applications avec Kubernetes : Guide Complet

Kubernetes est devenu la plateforme de reference pour l'orchestration de conteneurs, utilisee par 96% des entreprises selon la CNCF. Dans ce guide complet, nous allons explorer les meilleures pratiques pour deployer vos applications en production.

Architecture Kubernetes

<svg viewBox="0 0 800 500" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <linearGradient id="masterGrad" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#326ce5"/>
      <stop offset="100%" style="stop-color:#2756b3"/>
    </linearGradient>
    <linearGradient id="workerGrad" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" style="stop-color:#22c55e"/>
      <stop offset="100%" style="stop-color:#16a34a"/>
    </linearGradient>
  </defs>

  <rect width="800" height="500" fill="#0f172a"/>

  <!-- Control Plane -->
  <rect x="50" y="30" width="700" height="140" rx="10" fill="#1e293b" stroke="#475569" stroke-width="2"/>
  <text x="400" y="55" text-anchor="middle" fill="#94a3b8" font-size="14" font-weight="bold">Control Plane (Masters)</text>

  <!-- Control Plane Components -->
  <g transform="translate(100, 90)">
    <rect x="-40" y="-30" width="80" height="60" rx="8" fill="url(#masterGrad)"/>
    <text y="5" text-anchor="middle" fill="white" font-size="11" font-weight="bold">API Server</text>
  </g>
  <g transform="translate(220, 90)">
    <rect x="-40" y="-30" width="80" height="60" rx="8" fill="url(#masterGrad)"/>
    <text y="5" text-anchor="middle" fill="white" font-size="11" font-weight="bold">etcd</text>
  </g>
  <g transform="translate(340, 90)">
    <rect x="-40" y="-30" width="80" height="60" rx="8" fill="url(#masterGrad)"/>
    <text y="-5" text-anchor="middle" fill="white" font-size="10" font-weight="bold">Controller</text>
    <text y="10" text-anchor="middle" fill="white" font-size="10" font-weight="bold">Manager</text>
  </g>
  <g transform="translate(460, 90)">
    <rect x="-40" y="-30" width="80" height="60" rx="8" fill="url(#masterGrad)"/>
    <text y="5" text-anchor="middle" fill="white" font-size="11" font-weight="bold">Scheduler</text>
  </g>
  <g transform="translate(580, 90)">
    <rect x="-40" y="-30" width="80" height="60" rx="8" fill="url(#masterGrad)"/>
    <text y="-5" text-anchor="middle" fill="white" font-size="10" font-weight="bold">Cloud</text>
    <text y="10" text-anchor="middle" fill="white" font-size="10" font-weight="bold">Controller</text>
  </g>

  <!-- Worker Nodes -->
  <g transform="translate(150, 300)">
    <rect x="-100" y="-80" width="200" height="160" rx="10" fill="#1e293b" stroke="#22c55e" stroke-width="2"/>
    <text y="-55" text-anchor="middle" fill="#22c55e" font-size="12" font-weight="bold">Worker Node 1</text>
    <rect x="-80" y="-40" width="70" height="40" rx="5" fill="url(#workerGrad)"/>
    <text x="-45" y="-15" text-anchor="middle" fill="white" font-size="9">kubelet</text>
    <rect x="10" y="-40" width="70" height="40" rx="5" fill="url(#workerGrad)"/>
    <text x="45" y="-15" text-anchor="middle" fill="white" font-size="9">kube-proxy</text>
    <rect x="-80" y="20" width="160" height="50" rx="5" fill="#374151"/>
    <text y="50" text-anchor="middle" fill="#9ca3af" font-size="10">Pods (Containers)</text>
  </g>

  <g transform="translate(400, 300)">
    <rect x="-100" y="-80" width="200" height="160" rx="10" fill="#1e293b" stroke="#22c55e" stroke-width="2"/>
    <text y="-55" text-anchor="middle" fill="#22c55e" font-size="12" font-weight="bold">Worker Node 2</text>
    <rect x="-80" y="-40" width="70" height="40" rx="5" fill="url(#workerGrad)"/>
    <text x="-45" y="-15" text-anchor="middle" fill="white" font-size="9">kubelet</text>
    <rect x="10" y="-40" width="70" height="40" rx="5" fill="url(#workerGrad)"/>
    <text x="45" y="-15" text-anchor="middle" fill="white" font-size="9">kube-proxy</text>
    <rect x="-80" y="20" width="160" height="50" rx="5" fill="#374151"/>
    <text y="50" text-anchor="middle" fill="#9ca3af" font-size="10">Pods (Containers)</text>
  </g>

  <g transform="translate(650, 300)">
    <rect x="-100" y="-80" width="200" height="160" rx="10" fill="#1e293b" stroke="#22c55e" stroke-width="2"/>
    <text y="-55" text-anchor="middle" fill="#22c55e" font-size="12" font-weight="bold">Worker Node 3</text>
    <rect x="-80" y="-40" width="70" height="40" rx="5" fill="url(#workerGrad)"/>
    <text x="-45" y="-15" text-anchor="middle" fill="white" font-size="9">kubelet</text>
    <rect x="10" y="-40" width="70" height="40" rx="5" fill="url(#workerGrad)"/>
    <text x="45" y="-15" text-anchor="middle" fill="white" font-size="9">kube-proxy</text>
    <rect x="-80" y="20" width="160" height="50" rx="5" fill="#374151"/>
    <text y="50" text-anchor="middle" fill="#9ca3af" font-size="10">Pods (Containers)</text>
  </g>

  <!-- Connections -->
  <line x1="400" y1="170" x2="150" y2="220" stroke="#475569" stroke-width="2" stroke-dasharray="5,5"/>
  <line x1="400" y1="170" x2="400" y2="220" stroke="#475569" stroke-width="2" stroke-dasharray="5,5"/>
  <line x1="400" y1="170" x2="650" y2="220" stroke="#475569" stroke-width="2" stroke-dasharray="5,5"/>

  <text x="400" y="480" text-anchor="middle" fill="#64748b" font-size="12">L'API Server orchestre la communication entre le Control Plane et les Worker Nodes</text>
</svg>

Concepts fondamentaux

Pods

Un Pod est la plus petite unite deployable dans Kubernetes. Il peut contenir un ou plusieurs conteneurs qui partagent le meme reseau et stockage.

apiVersion: v1
kind: Pod
metadata:
  name: my-app
  labels:
    app: my-app
    version: v1
spec:
  containers:
  - name: app
    image: nginx:1.25
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "64Mi"
        cpu: "100m"
      limits:
        memory: "128Mi"
        cpu: "200m"
    livenessProbe:
      httpGet:
        path: /health
        port: 80
      initialDelaySeconds: 10
      periodSeconds: 5
    readinessProbe:
      httpGet:
        path: /ready
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 3

Deployments

Les Deployments gerent les ReplicaSets et fournissent des fonctionnalites comme les mises a jour progressives et les rollbacks.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  revisionHistoryLimit: 5  # Garder 5 versions pour rollback
  selector:
    matchLabels:
      app: my-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    metadata:
      labels:
        app: my-app
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
    spec:
      # Eviter la colocation sur le meme noeud
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: my-app
              topologyKey: kubernetes.io/hostname

      containers:
      - name: app
        image: my-registry/my-app:v1.2.3
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http
        - containerPort: 9090
          name: metrics

        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: url

        envFrom:
        - configMapRef:
            name: app-config

        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

        livenessProbe:
          httpGet:
            path: /health/live
            port: http
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3

        readinessProbe:
          httpGet:
            path: /health/ready
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3

        startupProbe:
          httpGet:
            path: /health/startup
            port: http
          initialDelaySeconds: 10
          periodSeconds: 5
          failureThreshold: 30  # 30 * 5 = 150s max startup

        volumeMounts:
        - name: config-volume
          mountPath: /app/config
          readOnly: true
        - name: cache-volume
          mountPath: /app/cache

        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          runAsUser: 1000
          capabilities:
            drop:
            - ALL

      volumes:
      - name: config-volume
        configMap:
          name: app-config
      - name: cache-volume
        emptyDir: {}

      securityContext:
        fsGroup: 2000
        seccompProfile:
          type: RuntimeDefault

      terminationGracePeriodSeconds: 30

      serviceAccountName: my-app-sa

      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app: my-app

Services et exposition

Types de Services

<svg viewBox="0 0 800 300" xmlns="http://www.w3.org/2000/svg">
  <rect width="800" height="300" fill="#0f172a"/>

  <!-- ClusterIP -->
  <g transform="translate(130, 150)">
    <rect x="-100" y="-60" width="200" height="120" rx="10" fill="#1e293b" stroke="#3b82f6" stroke-width="2"/>
    <text y="-35" text-anchor="middle" fill="#3b82f6" font-size="14" font-weight="bold">ClusterIP</text>
    <text y="-10" text-anchor="middle" fill="#94a3b8" font-size="11">IP interne au cluster</text>
    <text y="10" text-anchor="middle" fill="#64748b" font-size="10">Pod-to-Pod uniquement</text>
    <text y="40" text-anchor="middle" fill="#22c55e" font-size="10">Par defaut</text>
  </g>

  <!-- NodePort -->
  <g transform="translate(350, 150)">
    <rect x="-100" y="-60" width="200" height="120" rx="10" fill="#1e293b" stroke="#f97316" stroke-width="2"/>
    <text y="-35" text-anchor="middle" fill="#f97316" font-size="14" font-weight="bold">NodePort</text>
    <text y="-10" text-anchor="middle" fill="#94a3b8" font-size="11">Port sur chaque noeud</text>
    <text y="10" text-anchor="middle" fill="#64748b" font-size="10">30000-32767</text>
    <text y="40" text-anchor="middle" fill="#f59e0b" font-size="10">Dev/Test</text>
  </g>

  <!-- LoadBalancer -->
  <g transform="translate(570, 150)">
    <rect x="-100" y="-60" width="200" height="120" rx="10" fill="#1e293b" stroke="#22c55e" stroke-width="2"/>
    <text y="-35" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="bold">LoadBalancer</text>
    <text y="-10" text-anchor="middle" fill="#94a3b8" font-size="11">IP externe (Cloud)</text>
    <text y="10" text-anchor="middle" fill="#64748b" font-size="10">AWS ELB, GCP LB, Azure LB</text>
    <text y="40" text-anchor="middle" fill="#22c55e" font-size="10">Production</text>
  </g>

  <!-- Arrows -->
  <path d="M235 150 L245 150" stroke="#475569" stroke-width="2" marker-end="url(#arrowService)"/>
  <path d="M455 150 L465 150" stroke="#475569" stroke-width="2" marker-end="url(#arrowService)"/>

  <defs>
    <marker id="arrowService" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="#475569"/>
    </marker>
  </defs>

  <text x="400" y="280" text-anchor="middle" fill="#64748b" font-size="12">Niveau d'exposition croissant</text>
</svg>

Service ClusterIP (Communication interne)

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  labels:
    app: my-app
spec:
  type: ClusterIP
  selector:
    app: my-app
  ports:
  - name: http
    port: 80
    targetPort: 8080
    protocol: TCP
  - name: grpc
    port: 9000
    targetPort: 9000
    protocol: TCP
  sessionAffinity: None  # ou ClientIP pour sticky sessions

Service avec Headless (StatefulSets)

apiVersion: v1
kind: Service
metadata:
  name: my-db-headless
spec:
  clusterIP: None  # Headless service
  selector:
    app: my-db
  ports:
  - port: 5432
    targetPort: 5432

Ingress (Exposition HTTP/HTTPS)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    # Nginx Ingress Controller
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"

    # Rate limiting
    nginx.ingress.kubernetes.io/limit-rps: "100"
    nginx.ingress.kubernetes.io/limit-connections: "50"

    # CORS
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://example.com"

    # cert-manager pour TLS automatique
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app.example.com
    - api.example.com
    secretName: app-tls-secret
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
  - host: api.example.com
    http:
      paths:
      - path: /v1
        pathType: Prefix
        backend:
          service:
            name: api-v1-service
            port:
              number: 80
      - path: /v2
        pathType: Prefix
        backend:
          service:
            name: api-v2-service
            port:
              number: 80

Helm : Gestionnaire de packages

Helm simplifie le deploiement d'applications complexes via des charts reutilisables.

Structure d'un Chart Helm

my-app-chart/
├── Chart.yaml           # Metadata du chart
├── values.yaml          # Valeurs par defaut
├── values-prod.yaml     # Override production
├── templates/
│   ├── _helpers.tpl     # Fonctions template
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── hpa.yaml
│   ├── pdb.yaml
│   └── serviceaccount.yaml
└── charts/              # Dependances

Chart.yaml

apiVersion: v2
name: my-app
description: My Application Helm Chart
type: application
version: 1.0.0
appVersion: "2.1.0"
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
  - name: redis
    version: "17.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled

values.yaml

# Application
replicaCount: 3
image:
  repository: my-registry/my-app
  tag: "latest"
  pullPolicy: IfNotPresent

# Resources
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

# Autoscaling
autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
  targetMemoryUtilizationPercentage: 80

# Service
service:
  type: ClusterIP
  port: 80
  targetPort: 8080

# Ingress
ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: app-tls
      hosts:
        - app.example.com

# Probes
probes:
  liveness:
    path: /health/live
    initialDelaySeconds: 30
    periodSeconds: 10
  readiness:
    path: /health/ready
    initialDelaySeconds: 5
    periodSeconds: 5

# Pod Disruption Budget
pdb:
  enabled: true
  minAvailable: 1

# Dependencies
postgresql:
  enabled: true
  auth:
    database: myapp
    existingSecret: db-credentials

redis:
  enabled: false

templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-app.fullname" . }}
  labels:
    {{- include "my-app.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "my-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
      labels:
        {{- include "my-app.selectorLabels" . | nindent 8 }}
    spec:
      serviceAccountName: {{ include "my-app.serviceAccountName" . }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.service.targetPort }}
              protocol: TCP
          livenessProbe:
            httpGet:
              path: {{ .Values.probes.liveness.path }}
              port: http
            initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
            periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
          readinessProbe:
            httpGet:
              path: {{ .Values.probes.readiness.path }}
              port: http
            initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
            periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          envFrom:
            - configMapRef:
                name: {{ include "my-app.fullname" . }}-config
            - secretRef:
                name: {{ include "my-app.fullname" . }}-secrets

Commandes Helm essentielles

# Installation
helm install my-app ./my-app-chart -n production --create-namespace -f values-prod.yaml

# Mise a jour
helm upgrade my-app ./my-app-chart -n production -f values-prod.yaml

# Installation/Mise a jour en une commande
helm upgrade --install my-app ./my-app-chart -n production -f values-prod.yaml

# Voir les valeurs utilisees
helm get values my-app -n production

# Historique des releases
helm history my-app -n production

# Rollback
helm rollback my-app 2 -n production  # Revenir a la revision 2

# Desinstallation
helm uninstall my-app -n production

# Template local (debug)
helm template my-app ./my-app-chart -f values-prod.yaml > manifest.yaml

Kustomize : Configuration declarative

Kustomize permet de personnaliser des manifestes Kubernetes sans templates, en utilisant des overlays.

Structure Kustomize

my-app/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   └── configmap.yaml
└── overlays/
    ├── development/
    │   ├── kustomization.yaml
    │   ├── namespace.yaml
    │   └── patches/
    │       └── replicas.yaml
    ├── staging/
    │   ├── kustomization.yaml
    │   └── patches/
    │       └── resources.yaml
    └── production/
        ├── kustomization.yaml
        ├── namespace.yaml
        └── patches/
            ├── replicas.yaml
            ├── resources.yaml
            └── hpa.yaml

base/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

commonLabels:
  app.kubernetes.io/name: my-app
  app.kubernetes.io/managed-by: kustomize

resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml

overlays/production/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: production

namePrefix: prod-
commonLabels:
  environment: production

resources:
  - ../../base
  - namespace.yaml

patches:
  - path: patches/replicas.yaml
  - path: patches/resources.yaml
  - path: patches/hpa.yaml

configMapGenerator:
  - name: app-config
    behavior: merge
    literals:
      - LOG_LEVEL=warn
      - ENVIRONMENT=production

secretGenerator:
  - name: app-secrets
    type: Opaque
    files:
      - secrets/database-url.txt
    options:
      disableNameSuffixHash: true

images:
  - name: my-app
    newName: my-registry.com/my-app
    newTag: v2.1.0

replicas:
  - name: my-app
    count: 5

overlays/production/patches/resources.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"

Commandes Kustomize

# Preview des manifestes
kubectl kustomize overlays/production

# Appliquer
kubectl apply -k overlays/production

# Diff avant application
kubectl diff -k overlays/production

# Build et redirection
kustomize build overlays/production > production-manifests.yaml

Autoscaling

Horizontal Pod Autoscaler (HPA)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 20
  metrics:
  # CPU
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  # Memory
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  # Metriques custom (Prometheus Adapter)
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "1000"
  # Metriques externes
  - type: External
    external:
      metric:
        name: queue_messages_ready
        selector:
          matchLabels:
            queue: my-queue
      target:
        type: AverageValue
        averageValue: "100"
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300  # 5 min avant scale down
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0  # Scale up immediat
      policies:
      - type: Percent
        value: 100
        periodSeconds: 15
      - type: Pods
        value: 4
        periodSeconds: 15
      selectPolicy: Max

Vertical Pod Autoscaler (VPA)

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: my-app-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  updatePolicy:
    updateMode: "Auto"  # Off, Initial, Recreate, Auto
  resourcePolicy:
    containerPolicies:
    - containerName: app
      minAllowed:
        cpu: 100m
        memory: 128Mi
      maxAllowed:
        cpu: 2000m
        memory: 2Gi
      controlledResources: ["cpu", "memory"]

Pod Disruption Budget (PDB)

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: my-app-pdb
spec:
  # Au moins 2 pods disponibles
  minAvailable: 2
  # OU maximum 1 pod indisponible
  # maxUnavailable: 1
  selector:
    matchLabels:
      app: my-app

Securite

RBAC (Role-Based Access Control)

# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app-sa
  namespace: production
---
# Role (namespace-scoped)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: my-app-role
  namespace: production
rules:
- apiGroups: [""]
  resources: ["configmaps", "secrets"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: my-app-rolebinding
  namespace: production
subjects:
- kind: ServiceAccount
  name: my-app-sa
  namespace: production
roleRef:
  kind: Role
  name: my-app-role
  apiGroup: rbac.authorization.k8s.io

Network Policies

# Deny all par defaut
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
# Autoriser le trafic specifique
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-app-network-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: my-app
  policyTypes:
  - Ingress
  - Egress
  ingress:
  # Depuis l'Ingress Controller
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8080
  # Depuis d'autres pods du meme namespace
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  # Vers la base de donnees
  - to:
    - podSelector:
        matchLabels:
          app: postgresql
    ports:
    - protocol: TCP
      port: 5432
  # DNS
  - to:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
  # HTTPS externe
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
    ports:
    - protocol: TCP
      port: 443

Pod Security Standards

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    # Enforce restricted security
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    # Warn pour baseline
    pod-security.kubernetes.io/warn: baseline
    pod-security.kubernetes.io/warn-version: latest

Strategies de deploiement

Rolling Update (par defaut)

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%  # ou nombre absolu
      maxSurge: 25%        # ou nombre absolu

Blue-Green avec Argo Rollouts

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  replicas: 5
  strategy:
    blueGreen:
      activeService: my-app-active
      previewService: my-app-preview
      autoPromotionEnabled: false  # Promotion manuelle
      scaleDownDelaySeconds: 30
      prePromotionAnalysis:
        templates:
        - templateName: success-rate
        args:
        - name: service-name
          value: my-app-preview
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: my-app:v2

Canary avec Argo Rollouts

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  replicas: 10
  strategy:
    canary:
      canaryService: my-app-canary
      stableService: my-app-stable
      trafficRouting:
        nginx:
          stableIngress: my-app-ingress
      steps:
      - setWeight: 5
      - pause: {duration: 5m}
      - setWeight: 20
      - pause: {duration: 10m}
      - setWeight: 50
      - pause: {duration: 10m}
      - setWeight: 80
      - pause: {duration: 5m}
      analysis:
        templates:
        - templateName: success-rate
        startingStep: 2  # Commencer l'analyse au step 2
        args:
        - name: service-name
          value: my-app-canary

AnalysisTemplate pour Rollouts

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  args:
  - name: service-name
  metrics:
  - name: success-rate
    interval: 1m
    successCondition: result[0] >= 0.95
    failureLimit: 3
    provider:
      prometheus:
        address: http://prometheus:9090
        query: |
          sum(rate(http_requests_total{status=~"2..",service="{{args.service-name}}"}[5m]))
          /
          sum(rate(http_requests_total{service="{{args.service-name}}"}[5m]))

Troubleshooting

Commandes de diagnostic

# Etat general du cluster
kubectl cluster-info
kubectl get nodes -o wide
kubectl top nodes

# Pods
kubectl get pods -n production -o wide
kubectl describe pod <pod-name> -n production
kubectl logs <pod-name> -n production --tail=100 -f
kubectl logs <pod-name> -n production -c <container-name>  # Multi-container
kubectl logs <pod-name> -n production --previous  # Container precedent

# Events
kubectl get events -n production --sort-by='.lastTimestamp'
kubectl get events -n production --field-selector type=Warning

# Debugging interactif
kubectl exec -it <pod-name> -n production -- /bin/sh
kubectl run debug --rm -it --image=busybox -- /bin/sh

# Port-forward pour debug
kubectl port-forward pod/<pod-name> 8080:8080 -n production
kubectl port-forward svc/<service-name> 8080:80 -n production

# Copier des fichiers
kubectl cp <pod-name>:/path/to/file ./local-file -n production

Problemes courants et solutions

1. Pod en CrashLoopBackOff

# Diagnostic
kubectl describe pod <pod-name>
kubectl logs <pod-name> --previous

# Causes courantes:
# - Application crash (voir logs)
# - Probe qui echoue (augmenter initialDelaySeconds)
# - Ressources insuffisantes (augmenter limits)
# - Configuration invalide (verifier ConfigMaps/Secrets)

2. Pod en Pending

# Diagnostic
kubectl describe pod <pod-name>

# Causes courantes:
# - Ressources insuffisantes sur les noeuds
kubectl describe nodes | grep -A 5 "Allocated resources"

# - PVC non bound
kubectl get pvc -n production

# - Affinity/Taint non satisfait
kubectl get nodes --show-labels

3. ImagePullBackOff

# Verifier le secret de registry
kubectl get secrets -n production
kubectl get pod <pod-name> -o jsonpath='{.spec.imagePullSecrets}'

# Creer le secret si manquant
kubectl create secret docker-registry regcred \
  --docker-server=my-registry.com \
  --docker-username=user \
  --docker-password=pass \
  -n production

4. Service inaccessible

# Verifier le service
kubectl get svc <service-name> -n production -o wide
kubectl get endpoints <service-name> -n production

# Tester la connectivite
kubectl run test --rm -it --image=busybox -- wget -qO- http://<service-name>.<namespace>.svc.cluster.local

# Verifier les labels
kubectl get pods -l app=my-app -n production

Script de diagnostic complet

#!/bin/bash
# k8s-diag.sh - Diagnostic complet Kubernetes

NAMESPACE=${1:-default}
APP=${2:-""}

echo "=== Kubernetes Diagnostic ==="
echo "Namespace: $NAMESPACE"
echo "App: $APP"
echo ""

echo "--- Nodes Status ---"
kubectl get nodes -o wide

echo -e "\n--- Pods Status ---"
if [ -n "$APP" ]; then
  kubectl get pods -n $NAMESPACE -l app=$APP -o wide
else
  kubectl get pods -n $NAMESPACE -o wide
fi

echo -e "\n--- Events (last 20) ---"
kubectl get events -n $NAMESPACE --sort-by='.lastTimestamp' | tail -20

echo -e "\n--- Services ---"
kubectl get svc -n $NAMESPACE

echo -e "\n--- Ingress ---"
kubectl get ingress -n $NAMESPACE

echo -e "\n--- Resource Usage ---"
kubectl top pods -n $NAMESPACE 2>/dev/null || echo "Metrics server not available"

echo -e "\n--- HPA Status ---"
kubectl get hpa -n $NAMESPACE

echo -e "\n--- PDB Status ---"
kubectl get pdb -n $NAMESPACE

if [ -n "$APP" ]; then
  echo -e "\n--- Deployment Status ---"
  kubectl describe deployment $APP -n $NAMESPACE | head -50

  echo -e "\n--- Recent Logs ---"
  kubectl logs -l app=$APP -n $NAMESPACE --tail=50
fi

echo -e "\n=== End Diagnostic ==="

Best Practices Production

Checklist Pre-deploiement

## Pre-Deployment Checklist

### Images
- [ ] Image tag specifique (pas :latest)
- [ ] Image scannee pour vulnerabilites
- [ ] Image signee (Sigstore/Cosign)

### Resources
- [ ] Requests et Limits definis
- [ ] Resource Quotas sur le namespace
- [ ] LimitRange configure

### Probes
- [ ] Liveness probe configuree
- [ ] Readiness probe configuree
- [ ] Startup probe si necessaire

### Securite
- [ ] ServiceAccount dedie
- [ ] RBAC minimal
- [ ] SecurityContext restrictif
- [ ] Network Policies
- [ ] Pod Security Standards

### Haute Disponibilite
- [ ] replicas >= 2
- [ ] PodDisruptionBudget
- [ ] Anti-affinity (spread across nodes/zones)
- [ ] TopologySpreadConstraints

### Configuration
- [ ] ConfigMaps pour config non sensible
- [ ] Secrets pour donnees sensibles
- [ ] External Secrets si applicable

### Monitoring
- [ ] Metriques exposees (/metrics)
- [ ] ServiceMonitor pour Prometheus
- [ ] Dashboards Grafana
- [ ] Alertes configurees

Labels recommandes

metadata:
  labels:
    # Kubernetes standard labels
    app.kubernetes.io/name: my-app
    app.kubernetes.io/instance: my-app-production
    app.kubernetes.io/version: "2.1.0"
    app.kubernetes.io/component: backend
    app.kubernetes.io/part-of: my-platform
    app.kubernetes.io/managed-by: helm

    # Custom labels
    environment: production
    team: platform
    cost-center: engineering

Conclusion

Le deploiement sur Kubernetes necessite une bonne comprehension des concepts fondamentaux et l'application rigoureuse des meilleures pratiques. Points cles a retenir :

  1. Commencez simple : Pod -> Deployment -> Service -> Ingress
  2. Securisez des le depart : RBAC, Network Policies, Pod Security
  3. Automatisez : Helm ou Kustomize pour la gestion des configurations
  4. Monitorez : Probes, metriques, logs centralises
  5. Preparez la HA : Replicas, PDB, anti-affinity

Ressources


Besoin d'aide pour deployer vos applications Kubernetes en production ? Decouvrez mes services de consulting et formations.

F

Florian Courouge

Expert DevOps & Kafka | Consultant freelance specialise dans les architectures distribuees et le streaming de donnees.

Articles similaires