diff --git a/.github/workflows/ops-ci.yml b/.github/workflows/ops-ci.yml index 0b959bd..f3d3dfe 100644 --- a/.github/workflows/ops-ci.yml +++ b/.github/workflows/ops-ci.yml @@ -40,7 +40,13 @@ jobs: - name: Terraform validate run: terraform -chdir=terraform validate - # À activer quand les manifests seront réels (pas des placeholders) : - # - name: Validate manifests (kubeconform) - # run: | - # find k8s -name '*.yaml' | xargs kubeconform -strict -ignore-missing-schemas -summary + - name: Validate Kubernetes manifests + run: | + docker run --rm \ + -v "$PWD:/workspace" \ + -w /workspace \ + ghcr.io/yannh/kubeconform:v0.7.0 \ + -strict \ + -ignore-missing-schemas \ + -summary \ + $(find k8s -name '*.yaml' | sort) diff --git a/Makefile b/Makefile index 2c2df5b..2e4701a 100644 --- a/Makefile +++ b/Makefile @@ -40,10 +40,12 @@ LB := k8s/traefik/traefik.ingressclass.yaml k8s/traefik/traefik.rbac.yam k8s/traefik/traefik.deployment.yaml k8s/traefik/traefik.service.yaml # Couche app (placeholders) — décommenter au fil des images publiées : # APP := k8s/server/ k8s/client-web/ k8s/investigation/ k8s/worker/ +READY_MANIFESTS := $(MONITORING) $(DATA) $(LB) +K8S_MANIFESTS := $(shell find k8s -name '*.yaml' | sort) .DEFAULT_GOAL := help .PHONY: help all infra kubeconfig deploy db-check hosts smoke status destroy \ - fmt fmt-check validate tf-lint \ + fmt fmt-check validate dry-run tf-lint \ metrics hpa pdb load harden soft-affinity hard-affinity \ minikube minikube-up minikube-deploy minikube-hosts minikube-smoke minikube-down @@ -108,9 +110,19 @@ fmt-check: ## Vérifie le formatage sans rien modifier (miroir CI) npx --yes prettier --check "**/*.{yaml,yml,md,json}" terraform -chdir=$(TF_DIR) fmt -check -validate: ## Valide les manifests k8s (kubeconform) + terraform - terraform -chdir=$(TF_DIR) validate || true - find k8s -name '*.yaml' -print0 | xargs -0 -I{} sh -c 'kubeconform -strict -summary "{}" || true' +validate: ## Valide les manifests k8s hors-cluster (kubeconform) + terraform + terraform -chdir=$(TF_DIR) init -backend=false -input=false + terraform -chdir=$(TF_DIR) validate + kubeconform -strict -ignore-missing-schemas -summary $(K8S_MANIFESTS) + +dry-run: ## Simule l'application réelle contre un cluster vivant (ignore placeholders) + @echo ">> Server-side dry-run de la couche prête (observability + data + traefik)" + @timeout 8s kubectl --request-timeout=3s cluster-info >/dev/null 2>/dev/null || { \ + echo ">> Cluster Kubernetes inaccessible via KUBECONFIG=$(KUBECONFIG)."; \ + echo ">> Lance minikube ou pointe KUBECONFIG vers DOKS, puis relance make dry-run."; \ + exit 1; \ + } + kubectl --request-timeout=10s apply --dry-run=server $(addprefix -f ,$(READY_MANIFESTS)) tf-lint: ## Lint terraform (tflint) cd $(TF_DIR) && tflint || true diff --git a/flake.nix b/flake.nix index 58a63c9..2816968 100644 --- a/flake.nix +++ b/flake.nix @@ -20,10 +20,13 @@ devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ kubectl + kubeconform terraform + tflint k9s kubernetes-helm minikube + nodejs_22 ]; shellHook = '' diff --git a/k8s/traefik/traefik.deployment.yaml b/k8s/traefik/traefik.deployment.yaml index 4a317b8..7ca8c42 100644 --- a/k8s/traefik/traefik.deployment.yaml +++ b/k8s/traefik/traefik.deployment.yaml @@ -23,28 +23,49 @@ spec: serviceAccountName: traefik-ingress-controller affinity: podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - traefik - topologyKey: "kubernetes.io/hostname" + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - traefik + topologyKey: kubernetes.io/hostname containers: - name: traefik - # Traefik v3 — the CLI flags below (api.insecure, kubernetesingress, - # entrypoints) are v2/v3-stable; re-validate on a live cluster when the - # app-service manifests stop being placeholders. image: traefik:v3.7 args: - - --api.insecure=true - --providers.kubernetesingress=true + - --providers.kubernetesingress.ingressclass=traefik - --entrypoints.web.address=:80 - - --entrypoints.traefik.address=:8080 + - --ping=true + - --ping.entrypoint=web ports: - name: web containerPort: 80 - - name: dashboard - containerPort: 8080 + readinessProbe: + httpGet: + path: /ping + port: web + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 2 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /ping + port: web + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 2 + failureThreshold: 3 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 500m + memory: 256Mi restartPolicy: Always diff --git a/k8s/traefik/traefik.pdb.yaml b/k8s/traefik/traefik.pdb.yaml index c73f830..2eeb2ce 100644 --- a/k8s/traefik/traefik.pdb.yaml +++ b/k8s/traefik/traefik.pdb.yaml @@ -1,7 +1,8 @@ # PodDisruptionBudget — garde >=1 replica Traefik en service pendant les # perturbations volontaires (drain de nœud, rolling update, éviction). Traefik # tourne en 2 replicas avec anti-affinity required ; ce PDB protège l'entrée -# publique du cluster. +# publique du cluster. L'anti-affinity du Deployment est volontairement +# "preferred" pour que les labs single-node restent déployables. apiVersion: policy/v1 kind: PodDisruptionBudget metadata: diff --git a/k8s/traefik/traefik.rbac.yaml b/k8s/traefik/traefik.rbac.yaml index 36a75d4..daab99e 100644 --- a/k8s/traefik/traefik.rbac.yaml +++ b/k8s/traefik/traefik.rbac.yaml @@ -20,7 +20,6 @@ rules: - list - watch - apiGroups: - - extensions - networking.k8s.io resources: - ingresses @@ -30,7 +29,6 @@ rules: - list - watch - apiGroups: - - extensions - networking.k8s.io resources: - ingresses/status diff --git a/k8s/traefik/traefik.service.yaml b/k8s/traefik/traefik.service.yaml index 9c38f8b..22ff65b 100644 --- a/k8s/traefik/traefik.service.yaml +++ b/k8s/traefik/traefik.service.yaml @@ -14,7 +14,3 @@ spec: port: 80 targetPort: 80 nodePort: 30021 - - name: dashboard - port: 8080 - targetPort: 8080 - nodePort: 30042 diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..ef0d111 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,47 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/digitalocean/digitalocean" { + version = "2.92.0" + constraints = "~> 2.0" + hashes = [ + "h1:oSdvE2SKcZTnsfxwruWNT2RrKzSdjtqJl0tyozcPnIM=", + "zh:13cefc6a94b74445713abeacfdf6422d1aecf820ec08fe69bae63c3ea6fbe24e", + "zh:20fc749afda0dfd10ec6815db78efb0bdf399033db536738580816ca341cd2c6", + "zh:2fac398f97fbec5d9c16ce3c58a9925ca0474c4931ead3352af56161fd7d6f1e", + "zh:3e0542d5200c1efb3113bd2ad3a5cc1ba32b9d1fe7017044ceeb0b7729a7a7f6", + "zh:583ddc43350dfb84a9a5689fe11964df9afe1ae03d099ab96c8f0fb7bc7a4cad", + "zh:6025ea83b0602b6ff01b3c5bbe025e73e8b47a217aae6c4270725feac01ebb2b", + "zh:6be3c78cb90752ce9357c33792f869382ff9dbd01333d985127116478bdcec21", + "zh:75c4c76c24bdc7e9c8626603d1c082d0894c798096ccfe8e2ceba68ad4570638", + "zh:7abc9714982dee251e6b9ce6d4910cd413a46cb92f76a4ed3a92a56e7cc1b4e7", + "zh:7c4808dd90886f33c5bd861b7b6be9b942ae2b32a188793f6f4e07be4e146b47", + "zh:7d13d3bec74e08444334e6b5c1c5f5380d40dd0bbb80d2d387d9084aaecbd3cf", + "zh:8a11b04c46865bdcd49f15622398e6e4911aa5be5d0b12d0b708cdda5c8ff734", + "zh:910cad53707e4743f6c277fb0007f6937a64be5b3a8ded3af1273628b9c141fe", + "zh:a67d98e6aceb5837064c6e811a557dbaaa61791b99a8b8d87b278aecb871910e", + "zh:bed15d16d4be506123fba16c3fa6db7cafa7d2ed53f07ff370cc2228e5f6d9ba", + "zh:f794ef952a8b2b5702ecfecf9bfe372dac392789b0762e5598764d10f24a8210", + ] +} + +provider "registry.terraform.io/hashicorp/local" { + version = "2.9.0" + constraints = "~> 2.0" + hashes = [ + "h1:9rBZCMNpxKwMlRbWH2QpwD3kqUCAejdOZQ/aiiDObXQ=", + "zh:0baa4566cf77f1ff52f4293d1c8536202dd23edc197c3196413a28343c3ac3a0", + "zh:16b5559c3c07088ddad11a9bb9e9c0799999363c2958e9a5be2bcbbf2cd9ca64", + "zh:197c79015a10d1cce904a8ea722cbc750c42aeae2da53f44a6a0751d9fd1aa90", + "zh:29d0b03e5343a80677ebfeb2e2c31cbe4b1f65e736e53417454a4277fec2544c", + "zh:4896bfa6cf1d2fd562b47ef2e87f47862ae92a04f8ad5d764380f0c6653473b8", + "zh:531f8529cbca49f681883e57761a05a8398afaef6d1ab0d205d26bf12f4428e8", + "zh:6aaf5011d83161c86d2bfb80c0923ec934e578288758da2f37acb7aec129004b", + "zh:7430275253d3d3c40aa6179e0ec0d63212874dbbc06c5a51b9d07ec590f9756c", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:be17dc611e95e26cdf6cad79dfccf1064f0e32032a2efeb939a9bbe7fb1cbfe9", + "zh:f0e3b0aa644202e1d79d2000dca91f6019425da71e9800fa23f27e51c034f195", + "zh:f62bae4519e4ead49182ddc8afe8cf61e2a4c3ba3973b0fbba967736a2696aa3", + "zh:fcafa360a5b0b96244f26f4e3a6d642b716a376557142c2442ff2fb12d11da18", + ] +}