[{"data":1,"prerenderedAt":1096},["ShallowReactive",2],{"blog-\u002Fblog\u002Fgitops-flux-kubernetes":3,"blog-related-\u002Fblog\u002Fgitops-flux-kubernetes":1077},{"id":4,"title":5,"body":6,"category":1060,"date":1061,"description":1062,"draft":1063,"extension":1064,"meta":1065,"navigation":1066,"path":1067,"readingTime":178,"seo":1068,"sitemap":1069,"stem":1070,"tags":1071,"__hash__":1076},"blog\u002Fblog\u002Fgitops-flux-kubernetes.md","GitOps avec Flux CD : piloter Kubernetes depuis Git, sans kubectl en production",{"type":7,"value":8,"toc":1049},"minimark",[9,18,23,26,59,62,66,69,79,104,108,111,243,250,410,420,423,688,695,699,702,720,723,768,772,775,800,803,807,817,927,934,938,982,989,993,996,1032,1036,1045],[10,11,12,13,17],"p",{},"Qui a modifié ce déploiement ? Pourquoi le cluster de production diffère-t-il de celui de recette ? Ces questions reviennent dès que des changements sont appliqués à la main avec ",[14,15,16],"code",{},"kubectl apply",". GitOps y répond en déplaçant la vérité du cluster vers Git : l'état souhaité est décrit dans un dépôt, et un contrôleur s'assure en continu que le cluster lui correspond. Flux CD est l'une des implémentations les plus matures de cette approche.",[19,20,22],"h2",{"id":21},"le-principe-git-comme-source-de-vérité","Le principe : Git comme source de vérité",[10,24,25],{},"Un déploiement GitOps repose sur quatre idées :",[27,28,29,38,45,52],"ol",{},[30,31,32,33,37],"li",{},"L'état désiré du système est ",[34,35,36],"strong",{},"déclaratif"," (manifestes YAML, charts Helm, overlays Kustomize).",[30,39,40,41,44],{},"Il est ",[34,42,43],{},"versionné"," dans Git, avec revue de code et historique.",[30,46,47,48,51],{},"Un agent, dans le cluster, ",[34,49,50],{},"récupère"," cet état (modèle pull, pas push).",[30,53,54,55,58],{},"Cet agent ",[34,56,57],{},"réconcilie"," en boucle : si quelqu'un modifie une ressource à la main, Flux la remet dans l'état décrit par Git.",[10,60,61],{},"Le modèle pull a un avantage de sécurité considérable : la CI n'a plus besoin d'identifiants d'accès au cluster. Seul Flux, à l'intérieur, détient les droits nécessaires.",[19,63,65],{"id":64},"structure-de-dépôt-recommandée","Structure de dépôt recommandée",[10,67,68],{},"Nous utilisons une organisation simple, qui passe à l'échelle :",[70,71,77],"pre",{"className":72,"code":74,"language":75,"meta":76},[73],"language-text",".\n├── clusters\u002F\n│   ├── staging\u002F\n│   │   ├── flux-system\u002F\n│   │   ├── infrastructure.yaml\n│   │   └── apps.yaml\n│   └── production\u002F\n│       ├── flux-system\u002F\n│       ├── infrastructure.yaml\n│       └── apps.yaml\n├── infrastructure\u002F\n│   ├── base\u002F\n│   │   ├── ingress-nginx\u002F\n│   │   └── cert-manager\u002F\n│   ├── staging\u002F\n│   └── production\u002F\n└── apps\u002F\n    ├── base\u002F\n    │   └── podinfo\u002F\n    ├── staging\u002F\n    └── production\u002F\n","text","",[14,78,74],{"__ignoreMap":76},[80,81,82,88,94],"ul",{},[30,83,84,87],{},[14,85,86],{},"clusters\u002F"," contient le point d'entrée de chaque cluster : ce que Flux doit synchroniser.",[30,89,90,93],{},[14,91,92],{},"infrastructure\u002F"," regroupe les composants transverses (ingress, cert-manager, monitoring).",[30,95,96,99,100,103],{},[14,97,98],{},"apps\u002F"," décrit les applications métier, en ",[14,101,102],{},"base"," commune avec des surcharges par environnement.",[19,105,107],{"id":106},"source-kustomization-et-helmrelease","Source, Kustomization et HelmRelease",[10,109,110],{},"Flux s'appuie sur des ressources personnalisées. La première déclare où lire :",[70,112,116],{"className":113,"code":114,"language":115,"meta":76,"style":76},"language-yaml shiki shiki-themes material-theme-lighter github-light github-dark","apiVersion: source.toolkit.fluxcd.io\u002Fv1\nkind: GitRepository\nmetadata:\n  name: flux-system\n  namespace: flux-system\nspec:\n  interval: 1m\n  url: ssh:\u002F\u002Fgit@gitlab.example.org\u002Fplatform\u002Ffleet.git\n  ref:\n    branch: main\n  secretRef:\n    name: flux-system\n","yaml",[14,117,118,135,146,155,166,176,184,195,206,214,225,233],{"__ignoreMap":76},[119,120,123,127,131],"span",{"class":121,"line":122},"line",1,[119,124,126],{"class":125},"sQzsp","apiVersion",[119,128,130],{"class":129},"sP7_E",":",[119,132,134],{"class":133},"s_sjI"," source.toolkit.fluxcd.io\u002Fv1\n",[119,136,138,141,143],{"class":121,"line":137},2,[119,139,140],{"class":125},"kind",[119,142,130],{"class":129},[119,144,145],{"class":133}," GitRepository\n",[119,147,149,152],{"class":121,"line":148},3,[119,150,151],{"class":125},"metadata",[119,153,154],{"class":129},":\n",[119,156,158,161,163],{"class":121,"line":157},4,[119,159,160],{"class":125},"  name",[119,162,130],{"class":129},[119,164,165],{"class":133}," flux-system\n",[119,167,169,172,174],{"class":121,"line":168},5,[119,170,171],{"class":125},"  namespace",[119,173,130],{"class":129},[119,175,165],{"class":133},[119,177,179,182],{"class":121,"line":178},6,[119,180,181],{"class":125},"spec",[119,183,154],{"class":129},[119,185,187,190,192],{"class":121,"line":186},7,[119,188,189],{"class":125},"  interval",[119,191,130],{"class":129},[119,193,194],{"class":133}," 1m\n",[119,196,198,201,203],{"class":121,"line":197},8,[119,199,200],{"class":125},"  url",[119,202,130],{"class":129},[119,204,205],{"class":133}," ssh:\u002F\u002Fgit@gitlab.example.org\u002Fplatform\u002Ffleet.git\n",[119,207,209,212],{"class":121,"line":208},9,[119,210,211],{"class":125},"  ref",[119,213,154],{"class":129},[119,215,217,220,222],{"class":121,"line":216},10,[119,218,219],{"class":125},"    branch",[119,221,130],{"class":129},[119,223,224],{"class":133}," main\n",[119,226,228,231],{"class":121,"line":227},11,[119,229,230],{"class":125},"  secretRef",[119,232,154],{"class":129},[119,234,236,239,241],{"class":121,"line":235},12,[119,237,238],{"class":125},"    name",[119,240,130],{"class":129},[119,242,165],{"class":133},[10,244,245,246,249],{},"La ",[14,247,248],{},"Kustomization"," indique ensuite quel chemin appliquer, et dans quel ordre :",[70,251,253],{"className":113,"code":252,"language":115,"meta":76,"style":76},"apiVersion: kustomize.toolkit.fluxcd.io\u002Fv1\nkind: Kustomization\nmetadata:\n  name: apps\n  namespace: flux-system\nspec:\n  interval: 10m\n  retryInterval: 2m\n  timeout: 5m\n  dependsOn:\n    - name: infrastructure\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  path: .\u002Fapps\u002Fproduction\n  prune: true\n  wait: true\n",[14,254,255,264,273,279,288,296,302,311,321,331,338,351,358,368,377,388,400],{"__ignoreMap":76},[119,256,257,259,261],{"class":121,"line":122},[119,258,126],{"class":125},[119,260,130],{"class":129},[119,262,263],{"class":133}," kustomize.toolkit.fluxcd.io\u002Fv1\n",[119,265,266,268,270],{"class":121,"line":137},[119,267,140],{"class":125},[119,269,130],{"class":129},[119,271,272],{"class":133}," Kustomization\n",[119,274,275,277],{"class":121,"line":148},[119,276,151],{"class":125},[119,278,154],{"class":129},[119,280,281,283,285],{"class":121,"line":157},[119,282,160],{"class":125},[119,284,130],{"class":129},[119,286,287],{"class":133}," apps\n",[119,289,290,292,294],{"class":121,"line":168},[119,291,171],{"class":125},[119,293,130],{"class":129},[119,295,165],{"class":133},[119,297,298,300],{"class":121,"line":178},[119,299,181],{"class":125},[119,301,154],{"class":129},[119,303,304,306,308],{"class":121,"line":186},[119,305,189],{"class":125},[119,307,130],{"class":129},[119,309,310],{"class":133}," 10m\n",[119,312,313,316,318],{"class":121,"line":197},[119,314,315],{"class":125},"  retryInterval",[119,317,130],{"class":129},[119,319,320],{"class":133}," 2m\n",[119,322,323,326,328],{"class":121,"line":208},[119,324,325],{"class":125},"  timeout",[119,327,130],{"class":129},[119,329,330],{"class":133}," 5m\n",[119,332,333,336],{"class":121,"line":216},[119,334,335],{"class":125},"  dependsOn",[119,337,154],{"class":129},[119,339,340,343,346,348],{"class":121,"line":227},[119,341,342],{"class":129},"    -",[119,344,345],{"class":125}," name",[119,347,130],{"class":129},[119,349,350],{"class":133}," infrastructure\n",[119,352,353,356],{"class":121,"line":235},[119,354,355],{"class":125},"  sourceRef",[119,357,154],{"class":129},[119,359,361,364,366],{"class":121,"line":360},13,[119,362,363],{"class":125},"    kind",[119,365,130],{"class":129},[119,367,145],{"class":133},[119,369,371,373,375],{"class":121,"line":370},14,[119,372,238],{"class":125},[119,374,130],{"class":129},[119,376,165],{"class":133},[119,378,380,383,385],{"class":121,"line":379},15,[119,381,382],{"class":125},"  path",[119,384,130],{"class":129},[119,386,387],{"class":133}," .\u002Fapps\u002Fproduction\n",[119,389,391,394,396],{"class":121,"line":390},16,[119,392,393],{"class":125},"  prune",[119,395,130],{"class":129},[119,397,399],{"class":398},"syTEX"," true\n",[119,401,403,406,408],{"class":121,"line":402},17,[119,404,405],{"class":125},"  wait",[119,407,130],{"class":129},[119,409,399],{"class":398},[10,411,412,415,416,419],{},[14,413,414],{},"prune: true"," supprime du cluster ce qui a disparu de Git, et ",[14,417,418],{},"dependsOn"," garantit que l'infrastructure (CRD, ingress) est prête avant les applications.",[10,421,422],{},"Pour les charts Helm, on déclare le dépôt puis la release :",[70,424,426],{"className":113,"code":425,"language":115,"meta":76,"style":76},"apiVersion: source.toolkit.fluxcd.io\u002Fv1\nkind: HelmRepository\nmetadata:\n  name: podinfo\n  namespace: apps\nspec:\n  interval: 1h\n  url: https:\u002F\u002Fstefanprodan.github.io\u002Fpodinfo\n---\napiVersion: helm.toolkit.fluxcd.io\u002Fv2\nkind: HelmRelease\nmetadata:\n  name: podinfo\n  namespace: apps\nspec:\n  interval: 15m\n  chart:\n    spec:\n      chart: podinfo\n      version: \"6.x\"\n      sourceRef:\n        kind: HelmRepository\n        name: podinfo\n  values:\n    replicaCount: 3\n    ingress:\n      enabled: true\n  upgrade:\n    remediation:\n      retries: 3\n",[14,427,428,436,445,451,460,468,474,483,492,498,507,516,522,530,538,544,553,560,568,578,596,604,614,624,632,644,652,662,670,678],{"__ignoreMap":76},[119,429,430,432,434],{"class":121,"line":122},[119,431,126],{"class":125},[119,433,130],{"class":129},[119,435,134],{"class":133},[119,437,438,440,442],{"class":121,"line":137},[119,439,140],{"class":125},[119,441,130],{"class":129},[119,443,444],{"class":133}," HelmRepository\n",[119,446,447,449],{"class":121,"line":148},[119,448,151],{"class":125},[119,450,154],{"class":129},[119,452,453,455,457],{"class":121,"line":157},[119,454,160],{"class":125},[119,456,130],{"class":129},[119,458,459],{"class":133}," podinfo\n",[119,461,462,464,466],{"class":121,"line":168},[119,463,171],{"class":125},[119,465,130],{"class":129},[119,467,287],{"class":133},[119,469,470,472],{"class":121,"line":178},[119,471,181],{"class":125},[119,473,154],{"class":129},[119,475,476,478,480],{"class":121,"line":186},[119,477,189],{"class":125},[119,479,130],{"class":129},[119,481,482],{"class":133}," 1h\n",[119,484,485,487,489],{"class":121,"line":197},[119,486,200],{"class":125},[119,488,130],{"class":129},[119,490,491],{"class":133}," https:\u002F\u002Fstefanprodan.github.io\u002Fpodinfo\n",[119,493,494],{"class":121,"line":208},[119,495,497],{"class":496},"sbgvK","---\n",[119,499,500,502,504],{"class":121,"line":216},[119,501,126],{"class":125},[119,503,130],{"class":129},[119,505,506],{"class":133}," helm.toolkit.fluxcd.io\u002Fv2\n",[119,508,509,511,513],{"class":121,"line":227},[119,510,140],{"class":125},[119,512,130],{"class":129},[119,514,515],{"class":133}," HelmRelease\n",[119,517,518,520],{"class":121,"line":235},[119,519,151],{"class":125},[119,521,154],{"class":129},[119,523,524,526,528],{"class":121,"line":360},[119,525,160],{"class":125},[119,527,130],{"class":129},[119,529,459],{"class":133},[119,531,532,534,536],{"class":121,"line":370},[119,533,171],{"class":125},[119,535,130],{"class":129},[119,537,287],{"class":133},[119,539,540,542],{"class":121,"line":379},[119,541,181],{"class":125},[119,543,154],{"class":129},[119,545,546,548,550],{"class":121,"line":390},[119,547,189],{"class":125},[119,549,130],{"class":129},[119,551,552],{"class":133}," 15m\n",[119,554,555,558],{"class":121,"line":402},[119,556,557],{"class":125},"  chart",[119,559,154],{"class":129},[119,561,563,566],{"class":121,"line":562},18,[119,564,565],{"class":125},"    spec",[119,567,154],{"class":129},[119,569,571,574,576],{"class":121,"line":570},19,[119,572,573],{"class":125},"      chart",[119,575,130],{"class":129},[119,577,459],{"class":133},[119,579,581,584,586,590,593],{"class":121,"line":580},20,[119,582,583],{"class":125},"      version",[119,585,130],{"class":129},[119,587,589],{"class":588},"sjJ54"," \"",[119,591,592],{"class":133},"6.x",[119,594,595],{"class":588},"\"\n",[119,597,599,602],{"class":121,"line":598},21,[119,600,601],{"class":125},"      sourceRef",[119,603,154],{"class":129},[119,605,607,610,612],{"class":121,"line":606},22,[119,608,609],{"class":125},"        kind",[119,611,130],{"class":129},[119,613,444],{"class":133},[119,615,617,620,622],{"class":121,"line":616},23,[119,618,619],{"class":125},"        name",[119,621,130],{"class":129},[119,623,459],{"class":133},[119,625,627,630],{"class":121,"line":626},24,[119,628,629],{"class":125},"  values",[119,631,154],{"class":129},[119,633,635,638,640],{"class":121,"line":634},25,[119,636,637],{"class":125},"    replicaCount",[119,639,130],{"class":129},[119,641,643],{"class":642},"srdBf"," 3\n",[119,645,647,650],{"class":121,"line":646},26,[119,648,649],{"class":125},"    ingress",[119,651,154],{"class":129},[119,653,655,658,660],{"class":121,"line":654},27,[119,656,657],{"class":125},"      enabled",[119,659,130],{"class":129},[119,661,399],{"class":398},[119,663,665,668],{"class":121,"line":664},28,[119,666,667],{"class":125},"  upgrade",[119,669,154],{"class":129},[119,671,673,676],{"class":121,"line":672},29,[119,674,675],{"class":125},"    remediation",[119,677,154],{"class":129},[119,679,681,684,686],{"class":121,"line":680},30,[119,682,683],{"class":125},"      retries",[119,685,130],{"class":129},[119,687,643],{"class":642},[10,689,690,691,694],{},"Les options de ",[14,692,693],{},"remediation"," permettent un retour arrière automatique si une mise à jour Helm échoue.",[19,696,698],{"id":697},"gestion-des-secrets","Gestion des secrets",[10,700,701],{},"Ne jamais committer un secret en clair. Deux approches fonctionnent bien avec Flux :",[80,703,704,714],{},[30,705,706,709,710,713],{},[34,707,708],{},"SOPS"," : les fichiers ",[14,711,712],{},"Secret"," sont chiffrés dans Git (avec age, ou un KMS cloud) et Flux les déchiffre à l'application. C'est simple et auditable, la clé de déchiffrement restant uniquement dans le cluster.",[30,715,716,719],{},[34,717,718],{},"External Secrets Operator"," : Git ne contient qu'une référence, et l'opérateur récupère la valeur dans Vault, AWS Secrets Manager ou GCP Secret Manager. Idéal quand un gestionnaire de secrets existe déjà.",[10,721,722],{},"Avec SOPS, il suffit d'ajouter le déchiffrement à la Kustomization :",[70,724,726],{"className":113,"code":725,"language":115,"meta":76,"style":76},"spec:\n  decryption:\n    provider: sops\n    secretRef:\n      name: sops-age\n",[14,727,728,734,741,751,758],{"__ignoreMap":76},[119,729,730,732],{"class":121,"line":122},[119,731,181],{"class":125},[119,733,154],{"class":129},[119,735,736,739],{"class":121,"line":137},[119,737,738],{"class":125},"  decryption",[119,740,154],{"class":129},[119,742,743,746,748],{"class":121,"line":148},[119,744,745],{"class":125},"    provider",[119,747,130],{"class":129},[119,749,750],{"class":133}," sops\n",[119,752,753,756],{"class":121,"line":157},[119,754,755],{"class":125},"    secretRef",[119,757,154],{"class":129},[119,759,760,763,765],{"class":121,"line":168},[119,761,762],{"class":125},"      name",[119,764,130],{"class":129},[119,766,767],{"class":133}," sops-age\n",[19,769,771],{"id":770},"promotion-entre-environnements","Promotion entre environnements",[10,773,774],{},"La promotion devient une opération Git. Deux stratégies courantes :",[80,776,777,794],{},[30,778,779,782,783,786,787,790,791,793],{},[34,780,781],{},"Une branche, des dossiers"," : ",[14,784,785],{},"apps\u002Fstaging"," et ",[14,788,789],{},"apps\u002Fproduction"," partagent la même ",[14,792,102],{},". La promotion consiste à reporter la version d'image ou de chart de l'un vers l'autre, via une merge request.",[30,795,796,799],{},[34,797,798],{},"Automatisation des images"," : les contrôleurs d'automatisation d'images de Flux détectent un nouveau tag et ouvrent un commit en staging. La production reste validée manuellement.",[10,801,802],{},"Dans les deux cas, la production ne change jamais sans un commit relu et approuvé.",[19,804,806],{"id":805},"valider-en-ci-avant-de-fusionner","Valider en CI avant de fusionner",[10,808,809,810,786,813,816],{},"Une erreur de YAML ne doit jamais atteindre le cluster. Nous validons chaque merge request avec ",[14,811,812],{},"kustomize build",[14,814,815],{},"kubeconform"," :",[70,818,820],{"className":113,"code":819,"language":115,"meta":76,"style":76},"validate-manifests:\n  stage: test\n  image: alpine:3.21\n  before_script:\n    - apk add --no-cache kustomize kubeconform\n  script:\n    - |\n      for env in staging production; do\n        kustomize build \".\u002Fapps\u002F$env\" \\\n          | kubeconform -strict -summary -ignore-missing-schemas \\\n              -schema-location default \\\n              -schema-location \"https:\u002F\u002Fraw.githubusercontent.com\u002Fdatreeio\u002FCRDs-catalog\u002Fmain\u002F{{.Group}}\u002F{{.ResourceKind}}_{{.ResourceAPIVersion}}.json\"\n      done\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n",[14,821,822,829,839,849,856,863,870,878,883,888,893,898,903,908,915],{"__ignoreMap":76},[119,823,824,827],{"class":121,"line":122},[119,825,826],{"class":125},"validate-manifests",[119,828,154],{"class":129},[119,830,831,834,836],{"class":121,"line":137},[119,832,833],{"class":125},"  stage",[119,835,130],{"class":129},[119,837,838],{"class":133}," test\n",[119,840,841,844,846],{"class":121,"line":148},[119,842,843],{"class":125},"  image",[119,845,130],{"class":129},[119,847,848],{"class":133}," alpine:3.21\n",[119,850,851,854],{"class":121,"line":157},[119,852,853],{"class":125},"  before_script",[119,855,154],{"class":129},[119,857,858,860],{"class":121,"line":168},[119,859,342],{"class":129},[119,861,862],{"class":133}," apk add --no-cache kustomize kubeconform\n",[119,864,865,868],{"class":121,"line":178},[119,866,867],{"class":125},"  script",[119,869,154],{"class":129},[119,871,872,874],{"class":121,"line":186},[119,873,342],{"class":129},[119,875,877],{"class":876},"sVHd0"," |\n",[119,879,880],{"class":121,"line":197},[119,881,882],{"class":133},"      for env in staging production; do\n",[119,884,885],{"class":121,"line":208},[119,886,887],{"class":133},"        kustomize build \".\u002Fapps\u002F$env\" \\\n",[119,889,890],{"class":121,"line":216},[119,891,892],{"class":133},"          | kubeconform -strict -summary -ignore-missing-schemas \\\n",[119,894,895],{"class":121,"line":227},[119,896,897],{"class":133},"              -schema-location default \\\n",[119,899,900],{"class":121,"line":235},[119,901,902],{"class":133},"              -schema-location \"https:\u002F\u002Fraw.githubusercontent.com\u002Fdatreeio\u002FCRDs-catalog\u002Fmain\u002F{{.Group}}\u002F{{.ResourceKind}}_{{.ResourceAPIVersion}}.json\"\n",[119,904,905],{"class":121,"line":360},[119,906,907],{"class":133},"      done\n",[119,909,910,913],{"class":121,"line":370},[119,911,912],{"class":125},"  rules",[119,914,154],{"class":129},[119,916,917,919,922,924],{"class":121,"line":379},[119,918,342],{"class":129},[119,920,921],{"class":125}," if",[119,923,130],{"class":129},[119,925,926],{"class":133}," $CI_PIPELINE_SOURCE == \"merge_request_event\"\n",[10,928,929,930,933],{},"Le catalogue de CRD permet de valider aussi les ressources Flux. Vous pouvez compléter avec ",[14,931,932],{},"flux build kustomization"," pour tester le rendu exact tel que Flux le verra.",[19,935,937],{"id":936},"ce-que-gitops-change-au-quotidien","Ce que GitOps change au quotidien",[80,939,940,946,956,966,976],{},[30,941,942,945],{},[34,943,944],{},"Audit"," : chaque modification est un commit signé, relu, daté et attribué.",[30,947,948,951,952,955],{},[34,949,950],{},"Rollback"," : un ",[14,953,954],{},"git revert"," suffit, Flux réconcilie le cluster vers l'état précédent.",[30,957,958,961,962,965],{},[34,959,960],{},"Sécurité"," : plus d'accès ",[14,963,964],{},"kubectl"," en écriture en production, les droits se gèrent dans Git et par RBAC.",[30,967,968,971,972,975],{},[34,969,970],{},"Reproductibilité"," : recréer un cluster, c'est lancer ",[14,973,974],{},"flux bootstrap"," sur le même dépôt.",[30,977,978,981],{},[34,979,980],{},"Détection de dérive"," : toute modification manuelle est corrigée ou signalée.",[10,983,984,985,988],{},"Pour que l'adoption se passe bien, commencez par un cluster non critique, migrez une application à la fois et gardez un accès d'urgence documenté, en mettant la réconciliation en pause (",[14,986,987],{},"flux suspend",") le temps d'une intervention.",[19,990,992],{"id":991},"les-pièges-à-éviter","Les pièges à éviter",[10,994,995],{},"Quelques erreurs reviennent souvent lors d'une première adoption :",[80,997,998,1004,1010,1022],{},[30,999,1000,1003],{},[34,1001,1002],{},"Mélanger GitOps et modifications manuelles."," Une ressource éditée à la main sera écrasée à la prochaine réconciliation, ce qui surprend les équipes. Communiquez la règle dès le départ.",[30,1005,1006,1009],{},[34,1007,1008],{},"Un dépôt unique pour tout, sans droits différenciés."," Séparez les droits d'écriture entre l'infrastructure et les applications, et protégez la branche principale par des revues obligatoires.",[30,1011,1012,1015,1016,786,1018,1021],{},[34,1013,1014],{},"Oublier la supervision de Flux lui-même."," Exposez les métriques des contrôleurs et branchez des alertes sur les ",[14,1017,248],{},[14,1019,1020],{},"HelmRelease"," en échec : une réconciliation bloquée en silence est pire qu'un déploiement manuel.",[30,1023,1024,1027,1028,1031],{},[34,1025,1026],{},"Ne pas épingler les versions."," Un chart suivi en ",[14,1029,1030],{},"latest"," ou une plage trop large rend les mises à jour imprévisibles. Préférez des versions explicites, mises à jour par des merge requests automatisées.",[19,1033,1035],{"id":1034},"besoin-dun-coup-de-main","Besoin d'un coup de main ?",[10,1037,1038,1039,1044],{},"Structurer un dépôt GitOps, migrer des clusters existants vers Flux ou mettre en place une gestion des secrets robuste sont des chantiers que nous menons régulièrement. Pour en discuter, vous pouvez ",[1040,1041,1043],"a",{"href":1042},"\u002F#contact","nous contacter",".",[1046,1047,1048],"style",{},"html pre.shiki code .sQzsp, html code.shiki .sQzsp{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}",{"title":76,"searchDepth":137,"depth":137,"links":1050},[1051,1052,1053,1054,1055,1056,1057,1058,1059],{"id":21,"depth":137,"text":22},{"id":64,"depth":137,"text":65},{"id":106,"depth":137,"text":107},{"id":697,"depth":137,"text":698},{"id":770,"depth":137,"text":771},{"id":805,"depth":137,"text":806},{"id":936,"depth":137,"text":937},{"id":991,"depth":137,"text":992},{"id":1034,"depth":137,"text":1035},"Infrastructure","2026-08-28","GitOps avec Flux CD sur Kubernetes : structure de dépôt, Kustomization, HelmRelease, secrets SOPS, promotion entre environnements et validation en CI.",false,"md",{},true,"\u002Fblog\u002Fgitops-flux-kubernetes",{"title":5,"description":1062},{"loc":1067,"lastmod":1061},"blog\u002Fgitops-flux-kubernetes",[1072,1073,1074,1075],"gitops","flux","kubernetes","infrastructure","z6yx1i4PrSsno0yaIvx7Y4OtmSeCNrQ95N7-53olyL4",[1078,1084,1090],{"path":1079,"title":1080,"description":1081,"date":1082,"category":1083,"readingTime":186},"\u002Fblog\u002Fapplication-mobile-pwa-native-tauri","Application mobile : native, Flutter, PWA ou Tauri 2, comment choisir ?","Native, cross-platform, PWA ou Tauri 2 : comparatif concret selon l'accès matériel, le hors-ligne, la maintenance et la publication sur les stores.","2026-10-06","Mobile",{"path":1085,"title":1086,"description":1087,"date":1088,"category":1089,"readingTime":178},"\u002Fblog\u002Fsecuriser-pipeline-gitlab-ci-devsecops","Sécuriser une pipeline GitLab CI\u002FCD sans ralentir vos livraisons","SAST, scan de dépendances et d'images, secrets, SBOM, signature cosign : comment intégrer la sécurité dans GitLab CI\u002FCD sans freiner vos mises en production.","2026-09-22","DevSecOps",{"path":1091,"title":1092,"description":1093,"date":1094,"category":1095,"readingTime":178},"\u002Fblog\u002Fseo-technique-nuxt","SEO technique avec Nuxt : le guide pour un site rapide et bien référencé","Rendu SSR ou pré-rendu, useSeoMeta, canoniques, sitemap, JSON-LD, Core Web Vitals : les réglages SEO techniques à maîtriser sur un site Nuxt 4.","2026-09-10","SEO",1792329028556]