2 places Founding customer Scale — €1500/mo verrouillé à vie. Le 4e client paiera €1999.
FinOps
6 min de lecture

De GitLab self-hosted à Forgejo : 4.5 GiB de RAM en moins, un node de moins

Migration de GitLab Helm vers Forgejo sur mon cluster Kapsule perso. 14× moins de mémoire, 8× moins de CPU, et l'autoscaler qu'il a fallu débloquer pour matérialiser le gain FinOps.

Arthur Zinck
Arthur Zinck
Expert DevOps Kubernetes & Cloud

GitLab consommait 4.49 GiB de RAM en moyenne sur mon cluster perso. Forgejo en consomme 314 MiB. Même usage Git + CI, même cluster Kapsule, 14× moins de mémoire. Voilà ce que ça a donné, et la galère qui m’a coûté du temps au passage.

Pourquoi GitLab au départ, pourquoi plus

git.z3k.eu tournait sur GitLab Helm depuis un moment. La full-suite : Rails app, Sidekiq, Gitaly, Workhorse, registry, runners, le tout backé par CNPG PostgreSQL et Valkey, OIDC branché sur Authentik, SSH sur le 2222 via une IngressRouteTCP Traefik. C’était propre, ça marchait.

Le problème c’est que je suis tout seul derrière. Je pousse du code, je fais tourner de la CI, point. 90% des fonctionnalités GitLab dorment. Et même en désactivant proprement tout ce qui est désactivable côté chart (registry, KAS, mailroom, runner, prometheus…), le core reste lourd : Rails Webservice, Workhorse, Gitaly, Sidekiq, Postgres, Redis, gitlab-shell. Tous interdépendants, tu n’arrives pas à descendre en-dessous de plusieurs GiB.

Sur un cluster à quatre nodes Scaleway, ce poids commençait à se voir.

Les chiffres avant/après

J’ai laissé tourner les deux en parallèle quelques jours pour avoir une mesure honnête.

CPU par namespace, GitLab en orange et Forgejo en bleu

→ CPU moyen : GitLab 0.17 cores / Forgejo 0.02 cores (8× moins) → CPU max : GitLab 0.81 / Forgejo 0.12

Mémoire par namespace, GitLab en orange et Forgejo en bleu

→ Mémoire moyenne : GitLab 4.49 GiB / Forgejo 314 MiB (14× moins) → Mémoire max : GitLab 5.29 GiB / Forgejo 396 MiB

Sur un cluster Kapsule, libérer 4 GiB de mémoire et 0.15 core, c’est concrètement un node de moins à terme. Ce qui s’est passé après le ménage, j’y reviens.

La nouvelle stack

Forgejo via Helm sur git.z3k.eu, SSH 2222 via IngressRouteTCP Traefik (j’ai gardé exactement le même pattern d’URL pour pas casser mes remotes locaux). PostgreSQL via CNPG et OIDC vers Authentik : on ne change pas une équipe qui gagne.

La grosse réjouissance c’est Forgejo Actions. Le marketplace pointe sur code.forgejo.org, et la syntaxe des workflows est très proche de GitHub Actions. Attention : Forgejo ne promet pas la compatibilité GitHub Actions (la doc le précise explicitement), c’est juste que la grammaire et beaucoup d’actions tierces fonctionnent en pratique. Concrètement, mes .gitea/workflows/*.yaml ressemblent à du .github/workflows/*.yaml et j’ai pu réutiliser la plupart de mes actions sans réécrire — mais c’est de la convergence syntaxique, pas un contrat.

Côté runner : un deployment dédié avec code.forgejo.org/forgejo/runner:6 et un sidecar Docker-in-Docker en TCP plain sur localhost:2375. Pas de TLS, le sidecar tourne dans le même pod, l’isolation réseau est suffisante. J’expose deux labels : docker:docker://node:20-bookworm et buildah:docker://quay.io/buildah/stable.

L’autoscaler qui refuse de downscaler

Une fois GitLab supprimé, je m’attendais à voir le cluster passer de 4 à 3 nodes en quelques minutes. Trente minutes plus tard : toujours 4 nodes. La charge tenait pourtant largement sur 3.

J’ouvre les events du cluster autoscaler. Le downscale était bloqué pour deux raisons :

D’abord, 19 pods utilisaient un emptyDir sans annotation cluster-autoscaler.kubernetes.io/safe-to-evict: "true". Par défaut, l’autoscaler refuse d’évacuer un pod avec un emptyDir parce qu’il considère que les données dedans peuvent être importantes. Pour des composants stateless comme Traefik ou l’opérateur CNPG, ce ne l’est pas, mais l’autoscaler ne le sait pas.

Ensuite, les PDB des PostgreSQL primaires CNPG étaient à 0 allowed disruptions. Logique : tu ne veux pas évincer un primaire Postgres comme ça. Cette partie-là est correcte, je l’ai laissée telle quelle.

Le fix : j’ai annoté Traefik et l’opérateur CNPG en safe-to-evict: "true" (composants stateless, leur emptyDir est jetable), et laissé les PDB Postgres tranquilles. Quelques minutes après, l’autoscaler a fait son boulot. Passage à 3 nodes.

L’insight : virer un gros workload ne suffit pas à downscaler. Il y a toujours des pods qui traînent et qui bloquent l’éviction sans qu’on s’en rende compte. Dans 80% des cas c’est un emptyDir non annoté ou un PDB trop strict.

Petit rappel utile dans ce contexte : le kube-scheduler par défaut n’est pas en mode MostAllocated, il fonctionne en LeastAllocated (étale les pods sur tous les nodes pour équilibrer la charge instantanée). Et surtout, le scheduler ne re-balance jamais les pods déjà placés — une fois qu’un pod tourne quelque part, il y reste tant qu’il n’est pas évincé. Résultat : tu peux avoir un node qui se vide naturellement mais des pods qui restent sur les autres et empêchent le downscale. C’est exactement pour combler ce trou que Descheduler existe : un CronJob qui scanne périodiquement et évince les pods qui violent des policies (nodes sur/sous-utilisés, duplicates de ReplicaSet sur un même node), pour permettre au scheduler de les re-placer ailleurs. Sur un cluster où tu veux downscaler agressivement, c’est presque obligatoire.

Le bilan FinOps

Quatre nodes à trois nodes sur Kapsule, en POP2-4C-8G à 78 €/mois le node, ça fait quand même ~78 €/mois économisés. Mais le gain n’est pas que sur la facture.

Moins de pods, c’est aussi moins de PDB à suivre, moins de surfaces de panne, moins de bruit dans les alertes, des upgrades plus rapides. Quand tu hostes seul ton infra perso, ce bruit opérationnel compte autant que les euros.

GitLab full-suite n’a rien d’un mauvais produit. Pour une équipe qui utilise vraiment les boards, les MRs avec approbations multiples, le registry, le SAST intégré, les environnements de review, ça se justifie. Mais pour 90% des cas où tu fais hosting Git + CI, Forgejo (ou Gitea) couvre le besoin avec un dixième de la facture mémoire.

Ma position après ce switch : Forgejo est excellent en solo ou en petite équipe, là où tu veux juste du Git + de la CI sans payer la complexité GitLab. Dès que tu montes en charge — vraie équipe, exigences de gouvernance, besoin d’un registry costaud, SAST/DAST intégrés, environnements de review automatisés — GitLab reste mon choix préféré. Ce n’est pas un remplacement universel, c’est le bon outil pour le bon scope.

Ce que j’en retiens

Migrer un GitLab vers Forgejo, ça prend une demi-journée si tu connais les deux outils. Les gains de ressources sont réels et mesurables.

Et surtout : penser à débloquer l’autoscaler après avoir viré le gros workload. Sinon le cluster reste à sa taille d’avant et le gain FinOps ne se matérialise jamais sur la facture.

Prochaine étape quand j’aurai le temps : passer Forgejo en HA. D’après la doc Helm officielle, ça veut dire un stockage RWX partagé entre les replicas pour les repos, un indexer HA-ready (Elasticsearch ou Meilisearch), et un object storage S3-compatible pour les attachments et LFS. Pas critique aujourd’hui, mais c’est la suite logique.

Points clés à retenir

  • Forgejo consomme 314 MiB de RAM en moyenne contre 4.49 GiB pour GitLab full-suite, sur le même cluster, pour le même usage Git + CI
  • Forgejo Actions partage la syntaxe GitHub Actions sans promesse officielle de compatibilité : la plupart des actions tierces tournent telles quelles, mais c'est de la convergence, pas un contrat
  • Virer GitLab ne suffit pas à downscaler le cluster : il faut aussi annoter les pods avec emptyDir en safe-to-evict pour débloquer l'autoscaler
gitlab forgejo kubernetes self-hosted finops gitops ci-cd scaleway migration

Partager cet article

Twitter LinkedIn