Ce qu’on va faire

Nous allons installer Nginx Gateway Fabric pour exposer nos services Kubernetes sur internet, ainsi que Cert Manager pour gérer les certificats TLS.

Architecture cible

Je possède un nom de domaine chez Cloudflare, je vais donc utiliser Cloudflare comme DNS provider pour gérer les enregistrements DNS de mon domaine. Nginx Gateway Fabric sera configuré pour utiliser un LoadBalancer de type External, ce qui me permettra d’obtenir une adresse IP publique pour exposer mes services.

Voici un schéma pour une meilleur compréhension :

  graph TD
    A[Internet] --> B[Cloudflare DNS + Proxy]
    B --> C[Enregistrement DNS pointant vers l'IP du LoadBalancer GCP]
    C --> D[LoadBalancer GCP]
    D --> E[Nginx Gateway Fabric sur Kubernetes]
    E --> F[HTTPRoute -> Services Kubernetes]
    F --> G[Cert Manager pour TLS]

Étapes

1. Installation de la CRD gateway API

Les ingress controller ne sont plus supportés, il faut désormais ce tourner vers les gateway API. Il existe plusieurs implémentations, j’ai choisi d’installer Nginx Gateway Fabric qui est une solution robuste et bien supportée.

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml

ou via ArgoCD (la sync-wave à 0 permet de s’assurer que les CRDs sont installées avant les autres ressources) :

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: gateway-api-crds
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
  annotations:
    argocd.argoproj.io/sync-wave: "0"
spec:
  project: default
  source:
    repoURL: https://github.com/nginx/nginx-gateway-fabric.git
    targetRevision: v2.6.3
    path: config/crd/gateway-api/standard
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - ServerSideApply=true

2. Installation de Nginx Gateway Fabric

Je vais aller assez vite sur l’installation de Nginx Gateway Fabric, si vous avez suivi les articles précédents cela ne devrait pas poser de problème.

On ajoute dans notre repo de CD la configuration suivante :

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx-gateway-fabric
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
  annotations:
    argocd.argoproj.io/sync-wave: "1"
spec:
  project: default
  source:
    repoURL: ghcr.io/nginx/charts
    chart: nginx-gateway-fabric
    targetRevision: 2.6.3
    helm:
      releaseName: nginx-gateway-fabric
  destination:
    server: https://kubernetes.default.svc
    namespace: nginx-gateway
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Après quelques secondes les ressources sont créées et nous avons notre gateway qui tourne.

kubectl get pods -n nginx-gateway
NAME                                    READY   STATUS    RESTARTS   AGE
nginx-gateway-fabric-77c559bb4f-c7zlb   1/1     Running   0          93s

3. Installation de Cert Manager

Le principe est le même, voici la configuration à appliquer :

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cert-manager
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
  annotations:
    argocd.argoproj.io/sync-wave: "0"
spec:
  project: default
  source:
    repoURL: https://charts.jetstack.io
    chart: cert-manager
    targetRevision: v1.20.2
    helm:
      releaseName: cert-manager
      values: |
        crds:
          enabled: true
          keep: true
  destination:
    server: https://kubernetes.default.svc
    namespace: cert-manager
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true

Après quelques secondes les ressources sont créées et nous avons notre cert manager qui tourne.

kubectl get pods -n cert-manager
NAME                                       READY   STATUS              RESTARTS   AGE
cert-manager-65dd558d9c-qzjw9              1/1     Running             0          5m6s
cert-manager-cainjector-77bd875bf4-6bcpn   1/1     Running             0          5m5s
cert-manager-startupapicheck-fhsng         0/1     ContainerCreating   0          2m29s
cert-manager-webhook-69666f4d4c-7wccg      1/1     Running             0          5m6s

4. Configuration Cloudflare

Récupération de l’IP du LoadBalancer GCP

Maintenant que ma gateway est en place, je vais configurer Cloudflare pour pointer mon domaine vers l’IP publique de mon LoadBalancer GCP. Je vais créer un enregistrement A dans Cloudflare qui pointe vers l’IP du LoadBalancer, et je vais activer le proxy de Cloudflare pour bénéficier de la protection DDoS et du CDN.

Pour cela il faut récupérer l’IP du LoadBalancer GCP qui a été créé pour exposer Nginx Gateway Fabric. Vous pouvez le faire avec la commande suivante :

kubectl get svc -n nginx-gateway
# Noter l'EXTERNAL-IP → à mettre dans Cloudflare DNS (enregistrement A)

Création d’un token API Cloudflare

Pour automatiser la configuration de Cloudflare, je vais créer un token API avec les permissions nécessaires pour gérer les enregistrements DNS. Dans Cloudflare → My Profile → API Tokens → Create Token :

Template : Edit zone DNS Permissions : Zone / DNS / Edit Zone : mon domaine (ici valerian-pyckaert.dev)

kubectl create secret generic cloudflare-api-token \
  --namespace cert-manager \
  --from-literal=api-token=<TOKEN_CF>

5. Configuration de Cert Manager pour Cloudflare

Il s’agit maintenant d’ajouter une ressource ClusterIssuer pour utiliser le token API Cloudflare et gérer les certificats TLS automatiquement.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    email: ton@email.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-key
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token

Une fois cette configuration appliquée, Cert Manager pourra automatiquement obtenir et renouveler les certificats TLS pour les domaines gérés via Cloudflare. Pour vérifier le bon fonctionnement :

kubectl get clusterissuer -A
NAME               READY   AGE
letsencrypt-prod   True    17h
kubectl get certificate -A
default     wildcard-cert   True    wildcard-cert   54m

6. Configuration d’une HTTPRoute pour exposer un service

Maintenant que tout est en place, nous allons créer une ressource HTTPRoute pour exposer un service Kubernetes via Nginx Gateway Fabric et positionner le certificat généré par Cert Manager.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: main-gateway
  namespace: default
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  gatewayClassName: nginx
  listeners:
  - name: https
    hostname: "*.mondomaine.com"
    port: 443
    protocol: HTTPS
    tls:
      mode: Terminate
      certificateRefs:
      - name: wildcard-cert
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app
spec:
  parentRefs:
  - name: main-gateway
  hostnames:
  - "app.mondomaine.com"
  rules:
  - backendRefs:
    - name: my-service
      port: 80
---

Résultat

Si vous avez suivi chaque étapes vous devriez avoir un résultat similaire à celui-ci :

❯ kubectl get gateway -A
NAMESPACE   NAME           CLASS   ADDRESS         PROGRAMMED   AGE
default     main-gateway   nginx   34.156.85.195   True         29m
❯ kubectl get httproute -A
NAMESPACE   NAME        HOSTNAMES                         AGE
default     nginx-app   ["nginx.valerian-pyckaert.dev"]   29m
❯ kubectl get certificate -A
NAMESPACE   NAME            READY   SECRET          AGE
default     wildcard-cert   True    wildcard-cert   21m

Votre service devrait être accessible via l’URL https://nginx.mondomaine.com et le certificat TLS devrait être valide, en l’occurence pour moi c’était https://nginx.valerian-pyckaert.dev avec un certificat pour valerian-pyckaert.dev généré par Let’s Encrypt.

Welcome nginx

Pour ce faire j’ai enregistré un enregistrement A dans Cloudflare qui pointe vers l’IP du LoadBalancer GCP : Cloudflare record


Obstacles rencontrés

La gateway ne peut pas accéder au namespace demo et au service nginx

J’avais installé ma ressource HTTPRoute dans le namespace default alors que mon service nginx était dans le namespace demo. Par défaut, la gateway n’a pas accès aux ressources d’autres namespaces, il faut donc lui accorder les permissions nécessaires.

Soit via un ReferenceGrant :

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-gateway-to-demo
  namespace: demo
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    namespace: default
  to:
  - group: ""
    kind: Service

Soit installé la ressource HTTPRoute dans le même namespace que le service nginx (ici demo).

Pods en pending

Par péché de rapidité j’ai volontairement omis de spécifier les ressources CPU et memory de mes workloads, ce qui a pour conséquence que les pods sont restés en pending car le scheduler ne pouvait pas les placer sur un nœud. Il faut donc veiller à toujours spécifier les ressources de vos workloads pour éviter ce genre de problème.


Pour la suite

Dans le prochain et dernier article de cette série The Forge je vous parlerai de SOPS et de comment gérer vos secrets de manière sécurisée dans Kubernetes. Nous verrons comment chiffrer vos secrets avec SOPS et les stocker dans Git pour une gestion centralisée et sécurisée de vos secrets.