Sobriété Technique: De WordPress vers Hugo

Texte alternatif

Migration de WordPress vers Hugo sur Kubernetes

Entre 470 millions et 590 millions de sites WordPress sont déployés dans le monde. Si l’on migrait tous ces sites vers Hugo les gains serait les suivants:

  1. Gain en Cycles CPU et RAM Infrastructure : La migration permettrait de supprimer la nécessité de millions d’instances de bases de données et de serveurs d’application. Un seul serveur standard pourrait héberger 10 à 50 fois plus de sites Hugo qu’il ne le fait pour WordPress, réduisant drastiquement le parc de serveurs physiques nécessaires.

  2. Mémoire vive : En économisant une moyenne basse de 256 Mo de RAM par site WordPress et 256 Mo de RAM par base MySQL, le gain global se chiffrerait en pétaoctets de RAM libérés à l’échelle des centres de données mondiaux.

  3. Réduction énergétique et CO2 Consommation par visite : Un site WordPress génère en moyenne 0,5 g de CO2 par vue de page. Un site statique optimisé descend souvent sous les 0,1 g.

Impact annuel : Sur la base du trafic cumulé de WordPress (plusieurs dizaines de milliards de vues par mois), une migration vers Hugo permettrait une réduction théorique de plusieurs millions de tonnes de CO2 par an. Cela équivaut à la suppression de l’empreinte carbone annuelle de petits pays ou de millions de véhicules thermiques. L’abandon de WordPress pour Hugo sur Kubernetes transforme une application monolithique dépendante d’une base de données en une architecture de contenu statique immuable. Cette approche élimine les vulnérabilités PHP/SQL, réduit la latence et optimise drastiquement la consommation de ressources du cluster.


Objectif

Mettre en place un pipeline CI/CD propre et sécurisé pour déployer un site Hugo sur Kubernetes, avec les contraintes suivantes :

  • aucun accès entrant vers le cluster
  • aucun kubeconfig stocké dans GitHub
  • aucun runner public GitHub
  • aucun token long terme dans les pods
  • séparation stricte des responsabilités

Le déploiement doit :

  • builder Hugo
  • construire une image Docker Nginx
  • pousser l’image
  • mettre à jour un Deployment Kubernetes existant

Architecture retenue

Le point clé est le sens des flux.

  • Kubernetes ne reçoit rien
  • le runner sort vers GitHub
  • GitHub ne connaît pas Kubernetes

GitHub Actions
↑
HTTPS sortant
↑
Runner GitHub (Pod Kubernetes)
↑
RBAC Kubernetes (interne)

Pour cela, on utilise Actions Runner Controller (ARC).


Pourquoi Actions Runner Controller (ARC)

summerwind/actions-runner n’est pas un runner autonome.

ARC apporte :

  • création dynamique de runners
  • tokens GitHub éphémères
  • pods jetables
  • aucun secret GitHub dans les runners
  • intégration native Kubernetes

Sans ARC, la sécurité est cassée.


Étape 1 — Installer Actions Runner Controller

Namespace système

kubectl create namespace actions-runner-system

Installation via Helm

helm repo add actions-runner-controller https://actions-runner-controller.github.io/actions-runner-controller
helm repo update

Token GitHub requis

Type :

  • Personal Access Token (classic)

Scope minimal :

repo

Ce token sert uniquement à ARC pour enregistrer des runners. Il n’est jamais utilisé par les jobs CI.

Injection du token

kubectl create secret generic controller-manager \
  -n actions-runner-system \
  --from-literal=github_token=GITHUB_TOKEN

Déploiement du contrôleur

helm install arc actions-runner-controller/actions-runner-controller \
  -n actions-runner-system \
  --set authSecret.name=controller-manager

Validation :

kubectl get pods -n actions-runner-system

Étape 2 — Namespace et identité des runners

Les runners ne doivent jamais tourner dans default.

apiVersion: v1
kind: Namespace
metadata:
  name: actions-runners

ServiceAccount explicitement utilisé par les runners :

apiVersion: v1
kind: ServiceAccount
metadata:
  name: runner-sa
  namespace: actions-runners

Étape 3 — RBAC minimal pour le déploiement Hugo

Objectif :

  • permettre uniquement la mise à jour du Deployment Hugo
  • aucun accès cluster-wide

Role (namespace applicatif)

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: runner-deployer
  namespace: default
rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "patch", "update"]

RoleBinding cross-namespace

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: runner-deployer-binding
  namespace: default
subjects:
- kind: ServiceAccount
  name: runner-sa
  namespace: actions-runners
roleRef:
  kind: Role
  name: runner-deployer
  apiGroup: rbac.authorization.k8s.io

Aucun kubeconfig. Aucun secret Kubernetes dans GitHub.


Étape 4 — Déclarer le runner GitHub

On ne crée pas de Deployment Kubernetes.

On déclare une intention via ARC.

apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
  name: runner-hugo
  namespace: actions-runners
spec:
  replicas: 1
  template:
    spec:
      repository: paracetamol32/hugo-vi
      labels:
        - hugo
      serviceAccountName: runner-sa
      ephemeral: true

Effet :

  • ARC crée un pod runner
  • le runner s’enregistre automatiquement
  • il disparaît après le job

Étape 5 — Dockerfile Hugo

Image simple, reproductible, sans logique CI dedans.

FROM nginx:alpine
COPY public /usr/share/nginx/html
EXPOSE 80

Hugo ne tourne pas en prod, il ne sert qu’au build.


Étape 6 — Workflow GitHub Actions (corrigé)

Différences majeures avec un workflow classique :

  • runner self-hosted
  • pas de ubuntu-latest
  • pas de kubeconfig
  • kubectl utilise l’auth Kubernetes native du pod
name: CI-CD-Hugo
on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: [self-hosted, hugo]
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: 'latest'
          extended: true

      - name: Build Static Site
        run: hugo --minify

      - name: Login to Docker Hub
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

      - name: Build and Push Docker Image
        run: |
          docker build -t ${{ secrets.DOCKER_USERNAME }}/hugo-site:latest .
          docker push ${{ secrets.DOCKER_USERNAME }}/hugo-site:latest

      - name: Install kubectl
        run: |
          curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
          chmod +x kubectl
          sudo mv kubectl /usr/local/bin/

      - name: Update K8S Deployment
        run: |

          kubectl set image deployment/hugo-site hugo-site=${{ secrets.DOCKER_USERNAME }}/hugo-site:latest -n hugo-vi
          # Force le redémarrage automatique après la mise à jour
          kubectl rollout restart deployment/hugo-site -n hugo-vi

Caractéristique,WordPress (Ancien),Hugo sur Kubernetes (Nouveau) Sécurité,Risques PHP/SQL/Plugins,“Fichiers statiques, lecture seule” Performance,Temps de rendu dynamique,Réponse Nginx instantanée Maintenance,Mises à jour manuelles fréquentes,Pipeline automatisé et immuable Infrastructure,Base de données + stockage persistant,Serveur web léger sans état (Stateless) Scalabilité,Complexe (sessions/DB),Horizontale et instantanée