From ab865631fe153101fa3bcebf4d85c02b34a66cc3 Mon Sep 17 00:00:00 2001 From: dahoud <41957584+DahoudG@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:22:00 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20chart=20Helm=20parent=20lions-app=20v1.?= =?UTF-8?q?0.0=20=E2=80=94=20templates=20hardened=20(secretKeyRef,=20readO?= =?UTF-8?q?nlyRootFS,=20NetworkPolicy,=20ExternalSecret,=20PDB,=20SM,=20HP?= =?UTF-8?q?A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .helmignore | 15 ++ Chart.yaml | 24 +++ README.md | 178 ++++++++++++++++++++ templates/NOTES.txt | 36 ++++ templates/_helpers.tpl | 98 +++++++++++ templates/configmap.yaml | 13 ++ templates/deployment.yaml | 132 +++++++++++++++ templates/externalsecret.yaml | 28 ++++ templates/hpa.yaml | 46 ++++++ templates/ingress.yaml | 49 ++++++ templates/networkpolicy.yaml | 79 +++++++++ templates/pdb.yaml | 18 ++ templates/service.yaml | 20 +++ templates/serviceaccount.yaml | 14 ++ templates/servicemonitor.yaml | 25 +++ values.yaml | 299 ++++++++++++++++++++++++++++++++++ 16 files changed, 1074 insertions(+) create mode 100644 .helmignore create mode 100644 Chart.yaml create mode 100644 README.md create mode 100644 templates/NOTES.txt create mode 100644 templates/_helpers.tpl create mode 100644 templates/configmap.yaml create mode 100644 templates/deployment.yaml create mode 100644 templates/externalsecret.yaml create mode 100644 templates/hpa.yaml create mode 100644 templates/ingress.yaml create mode 100644 templates/networkpolicy.yaml create mode 100644 templates/pdb.yaml create mode 100644 templates/service.yaml create mode 100644 templates/serviceaccount.yaml create mode 100644 templates/servicemonitor.yaml create mode 100644 values.yaml diff --git a/.helmignore b/.helmignore new file mode 100644 index 0000000..707114d --- /dev/null +++ b/.helmignore @@ -0,0 +1,15 @@ +# Patterns to ignore when building packages. +# Operator and tooling files, documentation +.DS_Store +.git/ +.gitignore +.vscode/ +.idea/ +*.tmproj +*.swp +*.swo +*.bak +*.tgz +README.md.bak +examples/ +tests/ diff --git a/Chart.yaml b/Chart.yaml new file mode 100644 index 0000000..6c967f8 --- /dev/null +++ b/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: lions-app +description: | + Chart Helm standard pour toutes les applications Lions Dev. + Fournit : Deployment hardened, Service, Ingress avec cert-manager + rate-limit, + ConfigMap, ExternalSecret (Vault → K8s), NetworkPolicy, PDB, ServiceMonitor, HPA. +type: application +version: 1.0.0 +appVersion: "1.0.0" +kubeVersion: ">=1.28.0-0" +maintainers: + - name: Lions Infrastructure Team + email: infrastructure@lions.dev +home: https://git.lions.dev/lionsdev/helm-chart-lions-app +sources: + - https://git.lions.dev/lionsdev/helm-chart-lions-app +keywords: + - lions + - quarkus + - java + - microservice +annotations: + artifacthub.io/changes: | + - Chart initial (v1.0.0) : Deployment/Service/Ingress/ConfigMap/ExternalSecret/NetworkPolicy/PDB/SM/HPA diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d6a205 --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +# helm-chart-lions-app + +Chart Helm standard pour toutes les applications Lions Dev. + +## Ce que ce chart déploie + +- **Deployment** hardened (securityContext non-root, readOnlyRootFS, drop capabilities) +- **Service** ClusterIP +- **Ingress** nginx avec cert-manager + rate-limit + CORS optionnels +- **ConfigMap** (env non-sensibles) +- **ExternalSecret** (Vault → K8s Secret via External Secrets Operator) +- **NetworkPolicy** default-deny + allow list paramétrable +- **ServiceAccount** dédié par app +- **PodDisruptionBudget** (si replicas > 1) +- **ServiceMonitor** Prometheus (optionnel) +- **HorizontalPodAutoscaler** (optionnel) + +## Installation / consommation + +### Via dependency Helm (recommandé) + +Chaque app crée un repo deploy minimaliste qui dépend de ce chart : + +```yaml +# Chart.yaml de l'app consommatrice +apiVersion: v2 +name: unionflow-server-impl-quarkus +version: 1.0.0 +dependencies: + - name: lions-app + version: "1.0.0" + repository: "https://git.lions.dev/api/packages/lionsdev/helm" +``` + +```bash +helm dependency update . +helm upgrade --install unionflow-server-impl-quarkus . \ + --namespace applications --create-namespace \ + --values values.yaml +``` + +### Via package OCI local (dev) + +```bash +helm package . +helm upgrade --install myapp lions-app-1.0.0.tgz \ + --namespace applications \ + --set image.name=myapp --set image.tag=1.0.5 +``` + +## Overrides usuels (values.yaml de l'app) + +```yaml +lions-app: + # Image + image: + name: unionflow-server-impl-quarkus + tag: "1.0.5-20260418-081420" + + # Replicas + HPA + replicaCount: 1 + hpa: + enabled: false + + # Resources + resources: + requests: { cpu: 200m, memory: 512Mi } + limits: { cpu: "1", memory: 1Gi } + + # Env non-sensibles + configMap: + data: + QUARKUS_PROFILE: prod + APP_ENV: production + QUARKUS_HIBERNATE_ORM_DATABASE_GENERATION: validate + KAFKA_BOOTSTRAP_SERVERS: kafka-service.kafka.svc.cluster.local:9092 + APP_BASE_URL: https://unionflow.lions.dev + + # Secrets depuis Vault + externalSecret: + enabled: true + data: + - secretKey: QUARKUS_DATASOURCE_USERNAME + remoteRef: + key: lions/applications/unionflow-server/db + property: username + - secretKey: QUARKUS_DATASOURCE_PASSWORD + remoteRef: + key: lions/applications/unionflow-server/db + property: password + - secretKey: KEYCLOAK_CLIENT_SECRET + remoteRef: + key: lions/applications/unionflow-server/oidc + property: client-secret + + # Ingress + ingress: + host: api.lions.dev + pathPrefix: + enabled: true + strip: /unionflow # /unionflow/(.*) → /(.*) backend + rateLimit: + enabled: true + rpm: 3000 + cors: + enabled: true + origins: "https://unionflow.lions.dev" + + # Egress vers services externes + networkPolicy: + enabled: true + allowEgressTo: + - namespaceSelector: + kubernetes.io/metadata.name: postgresql + ports: + - port: 5432 + protocol: TCP + - namespaceSelector: + kubernetes.io/metadata.name: kafka + ports: + - port: 9092 + protocol: TCP + - namespaceSelector: + kubernetes.io/metadata.name: keycloak + ports: + - port: 8080 + protocol: TCP + + # Probes Quarkus + probes: + liveness: + httpGet: + path: /q/health/live + port: 8080 + readiness: + httpGet: + path: /q/health/ready + port: 8080 +``` + +## Conventions Lions + +1. **Release name = app name** (`{{ .Release.Name }}` partout) +2. **Namespace = environnement** (`applications` pour prod, `applications-dev`, etc.) +3. **Image** : `registry.lions.dev/lionsdev/:` +4. **Secrets** : toujours via ExternalSecret → Vault. Jamais de plaintext dans le values.yaml. +5. **TLS** : Let's Encrypt via cert-manager (`letsencrypt-prod`) +6. **NetworkPolicy** : activée par défaut (zero-trust) +7. **SecurityContext** : non-root, readOnlyRootFilesystem, capabilities drop all + +## Publication du chart (pour les mainteneurs) + +```bash +# Incrémenter version dans Chart.yaml +helm package . +# Upload vers Gitea Helm registry (avec token lionsctl-bot) +curl -u lionsctl-bot:$LIONS_GIT_ACCESS_TOKEN \ + --upload-file lions-app-1.0.0.tgz \ + https://git.lions.dev/api/packages/lionsdev/helm/api/charts +``` + +## Validation locale + +```bash +helm lint . +helm template test-release . --namespace applications \ + --set image.name=unionflow-server-impl-quarkus \ + --set image.tag=1.0.5 \ + --set ingress.host=api.lions.dev +``` + +## Changelog + +### 1.0.0 (2026-04-22) +- Chart initial +- Templates : Deployment, Service, Ingress, ConfigMap, ExternalSecret, + NetworkPolicy, ServiceAccount, PDB, ServiceMonitor, HPA +- Compatible Kubernetes ≥ 1.28 diff --git a/templates/NOTES.txt b/templates/NOTES.txt new file mode 100644 index 0000000..0e0de39 --- /dev/null +++ b/templates/NOTES.txt @@ -0,0 +1,36 @@ +Application {{ include "lions-app.name" . }} déployée sur Lions ({{ .Release.Namespace }}). + +1. Vérifier le rollout : + kubectl rollout status deployment/{{ include "lions-app.name" . }} -n {{ .Release.Namespace }} + +2. Vérifier les pods : + kubectl get pods -n {{ .Release.Namespace }} -l app={{ include "lions-app.name" . }} + +3. Health check : +{{- if .Values.ingress.enabled }} + curl -sk https://{{ .Values.ingress.host }}{{ .Values.probes.readiness.httpGet.path | default "/q/health/ready" }} +{{- else }} + kubectl port-forward -n {{ .Release.Namespace }} svc/{{ include "lions-app.name" . }} 8080:{{ .Values.service.port }} + curl -s http://localhost:8080{{ .Values.probes.readiness.httpGet.path | default "/q/health/ready" }} +{{- end }} + +4. Logs : + kubectl logs -n {{ .Release.Namespace }} -l app={{ include "lions-app.name" . }} --tail=100 -f + +{{- if .Values.externalSecret.enabled }} + +5. Vérifier que les secrets Vault sont synchronisés : + kubectl get externalsecret -n {{ .Release.Namespace }} {{ include "lions-app.name" . }} + kubectl get secret -n {{ .Release.Namespace }} {{ include "lions-app.secretName" . }} +{{- end }} + +{{- if .Values.hpa.enabled }} + +6. Vérifier l'HPA : + kubectl get hpa -n {{ .Release.Namespace }} {{ include "lions-app.name" . }} +{{- end }} + +Image déployée : {{ include "lions-app.image" . }} +{{- if .Values.ingress.enabled }} +URL d'accès : https://{{ .Values.ingress.host }}{{ .Values.ingress.path }} +{{- end }} diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl new file mode 100644 index 0000000..d82a9a6 --- /dev/null +++ b/templates/_helpers.tpl @@ -0,0 +1,98 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Nom de l'app = nom du release Helm. +Pattern DGBF : tout est nommé pareil (Deployment, Service, Ingress, ConfigMap…). +*/}} +{{- define "lions-app.name" -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Chart fullname = name + chart version (pour helm.sh/chart label). +*/}} +{{- define "lions-app.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels — appliqués sur TOUTES les ressources. +Conformes aux recommendations Kubernetes (app.kubernetes.io/*). +*/}} +{{- define "lions-app.labels" -}} +app.kubernetes.io/name: {{ include "lions-app.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: lions-infrastructure +helm.sh/chart: {{ include "lions-app.chart" . }} +project: lions-infrastructure-2025 +{{- with .Values.commonLabels }} +{{ toYaml . }} +{{- end }} +{{- end -}} + +{{/* +Selector labels — stables (ne changent jamais), utilisés par Service et Deployment. +"app" est gardé pour compatibilité avec les legacy deployments Lions. +*/}} +{{- define "lions-app.selectorLabels" -}} +app: {{ include "lions-app.name" . }} +app.kubernetes.io/name: {{ include "lions-app.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Image reference complète : registry/repo/name:tag +*/}} +{{- define "lions-app.image" -}} +{{- $registry := .Values.image.registry | default "registry.lions.dev" -}} +{{- $repo := .Values.image.repository | default "lionsdev" -}} +{{- $name := .Values.image.name | default (include "lions-app.name" .) -}} +{{- $tag := .Values.image.tag | default .Chart.AppVersion -}} +{{- printf "%s/%s/%s:%s" $registry $repo $name $tag -}} +{{- end -}} + +{{/* +Nom du ServiceAccount. +*/}} +{{- define "lions-app.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} +{{- .Values.serviceAccount.name | default (include "lions-app.name" .) -}} +{{- else -}} +{{- .Values.serviceAccount.name | default "default" -}} +{{- end -}} +{{- end -}} + +{{/* +Nom du K8s Secret cible de l'ExternalSecret. +*/}} +{{- define "lions-app.secretName" -}} +{{- include "lions-app.name" . -}}-secrets +{{- end -}} + +{{/* +Nom du TLS secret de l'Ingress. +*/}} +{{- define "lions-app.tlsSecretName" -}} +{{- include "lions-app.name" . -}}-tls +{{- end -}} + +{{/* +Path de l'Ingress selon le mode (simple ou prefix-strip). +*/}} +{{- define "lions-app.ingressPath" -}} +{{- if .Values.ingress.pathPrefix.enabled -}} +{{- printf "%s(/|$)(.*)" .Values.ingress.pathPrefix.strip -}} +{{- else -}} +{{- .Values.ingress.path | default "/" -}} +{{- end -}} +{{- end -}} + +{{- define "lions-app.ingressPathType" -}} +{{- if .Values.ingress.pathPrefix.enabled -}} +ImplementationSpecific +{{- else -}} +{{- .Values.ingress.pathType | default "Prefix" -}} +{{- end -}} +{{- end -}} diff --git a/templates/configmap.yaml b/templates/configmap.yaml new file mode 100644 index 0000000..edf3eec --- /dev/null +++ b/templates/configmap.yaml @@ -0,0 +1,13 @@ +{{- if and .Values.configMap.enabled (or .Values.configMap.data (gt (len (keys .Values.configMap.data)) 0)) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "lions-app.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} +data: + {{- range $key, $val := .Values.configMap.data }} + {{ $key }}: {{ $val | quote }} + {{- end }} +{{- end }} diff --git a/templates/deployment.yaml b/templates/deployment.yaml new file mode 100644 index 0000000..ed50c7c --- /dev/null +++ b/templates/deployment.yaml @@ -0,0 +1,132 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "lions-app.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} + {{- with .Values.commonAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + {{- toYaml .Values.strategy | nindent 4 }} + selector: + matchLabels: + {{- include "lions-app.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "lions-app.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + {{- if .Values.configMap.enabled }} + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.externalSecret.enabled }} + checksum/externalsecret: {{ include (print $.Template.BasePath "/externalsecret.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "lions-app.serviceAccountName" . }} + automountServiceAccountToken: false + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- range . }} + - name: {{ . }} + {{- end }} + {{- end }} + containers: + - name: {{ include "lions-app.name" . }} + image: {{ include "lions-app.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + ports: + - name: http + containerPort: {{ .Values.container.port }} + protocol: TCP + {{- with .Values.container.extraArgs }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if or .Values.env .Values.container.extraEnv }} + env: + {{- with .Values.env }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.container.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} + {{- if or (and .Values.configMap.enabled .Values.configMap.envFrom (gt (len (keys .Values.configMap.data)) 0)) .Values.externalSecret.enabled }} + envFrom: + {{- if and .Values.configMap.enabled .Values.configMap.envFrom (gt (len (keys .Values.configMap.data)) 0) }} + - configMapRef: + name: {{ include "lions-app.name" . }} + {{- end }} + {{- if .Values.externalSecret.enabled }} + - secretRef: + name: {{ include "lions-app.secretName" . }} + {{- end }} + {{- end }} + {{- if .Values.probes.startup.enabled }} + startupProbe: + {{- omit .Values.probes.startup "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.probes.liveness.enabled }} + livenessProbe: + {{- omit .Values.probes.liveness "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.probes.readiness.enabled }} + readinessProbe: + {{- omit .Values.probes.readiness "enabled" | toYaml | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + {{- if .Values.volumes.tmp.enabled }} + - name: tmp + mountPath: /tmp + {{- end }} + {{- if .Values.volumes.logs.enabled }} + - name: logs + mountPath: {{ .Values.volumes.logs.mountPath | default "/app/logs" }} + {{- end }} + {{- with .Values.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + volumes: + {{- if .Values.volumes.tmp.enabled }} + - name: tmp + emptyDir: + sizeLimit: {{ .Values.volumes.tmp.sizeLimit | default "100Mi" }} + {{- end }} + {{- if .Values.volumes.logs.enabled }} + - name: logs + emptyDir: + sizeLimit: {{ .Values.volumes.logs.sizeLimit | default "500Mi" }} + {{- end }} + {{- with .Values.volumes.extra }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/templates/externalsecret.yaml b/templates/externalsecret.yaml new file mode 100644 index 0000000..e8ee805 --- /dev/null +++ b/templates/externalsecret.yaml @@ -0,0 +1,28 @@ +{{- if .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: {{ include "lions-app.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} +spec: + refreshInterval: {{ .Values.externalSecret.refreshInterval | default "1h" }} + secretStoreRef: + kind: {{ .Values.externalSecret.secretStoreRef.kind }} + name: {{ .Values.externalSecret.secretStoreRef.name }} + target: + name: {{ include "lions-app.secretName" . }} + creationPolicy: {{ .Values.externalSecret.target.creationPolicy | default "Owner" }} + deletionPolicy: {{ .Values.externalSecret.target.deletionPolicy | default "Retain" }} + data: + {{- range .Values.externalSecret.data }} + - secretKey: {{ .secretKey }} + remoteRef: + key: {{ .remoteRef.key }} + property: {{ .remoteRef.property }} + {{- if .remoteRef.conversionStrategy }} + conversionStrategy: {{ .remoteRef.conversionStrategy }} + {{- end }} + {{- end }} +{{- end }} diff --git a/templates/hpa.yaml b/templates/hpa.yaml new file mode 100644 index 0000000..efb7fd9 --- /dev/null +++ b/templates/hpa.yaml @@ -0,0 +1,46 @@ +{{- if .Values.hpa.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "lions-app.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "lions-app.name" . }} + minReplicas: {{ .Values.hpa.minReplicas | default 1 }} + maxReplicas: {{ .Values.hpa.maxReplicas | default 3 }} + metrics: + {{- if .Values.hpa.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.hpa.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.hpa.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.hpa.targetMemoryUtilizationPercentage }} + {{- end }} + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 10 + periodSeconds: 60 + scaleUp: + stabilizationWindowSeconds: 30 + policies: + - type: Percent + value: 50 + periodSeconds: 30 +{{- end }} diff --git a/templates/ingress.yaml b/templates/ingress.yaml new file mode 100644 index 0000000..82c55de --- /dev/null +++ b/templates/ingress.yaml @@ -0,0 +1,49 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "lions-app.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} + annotations: + # cert-manager + cert-manager.io/cluster-issuer: {{ .Values.ingress.clusterIssuer | quote }} + {{- if .Values.ingress.pathPrefix.enabled }} + # Mode prefix-strip : le path /prefix(/|$)(.*) est rewrité en /$2 + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/rewrite-target: /$2 + {{- end }} + {{- if .Values.ingress.rateLimit.enabled }} + nginx.ingress.kubernetes.io/limit-rpm: {{ .Values.ingress.rateLimit.rpm | default 1000 | quote }} + nginx.ingress.kubernetes.io/limit-connections: {{ .Values.ingress.rateLimit.connections | default 100 | quote }} + {{- end }} + {{- if .Values.ingress.cors.enabled }} + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/cors-allow-origin: {{ .Values.ingress.cors.origins | quote }} + nginx.ingress.kubernetes.io/cors-allow-methods: {{ .Values.ingress.cors.methods | quote }} + nginx.ingress.kubernetes.io/cors-allow-headers: {{ .Values.ingress.cors.headers | quote }} + {{- end }} + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className | default "nginx" }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.host | quote }} + secretName: {{ .Values.ingress.tls.secretName | default (include "lions-app.tlsSecretName" .) }} + {{- end }} + rules: + - host: {{ .Values.ingress.host | quote }} + http: + paths: + - path: {{ include "lions-app.ingressPath" . }} + pathType: {{ include "lions-app.ingressPathType" . }} + backend: + service: + name: {{ include "lions-app.name" . }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/templates/networkpolicy.yaml b/templates/networkpolicy.yaml new file mode 100644 index 0000000..7d53509 --- /dev/null +++ b/templates/networkpolicy.yaml @@ -0,0 +1,79 @@ +{{- if .Values.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "lions-app.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "lions-app.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + # Ingress depuis les namespaces autorisés + {{- range .Values.networkPolicy.allowIngressFrom }} + - from: + - namespaceSelector: + matchLabels: + {{- toYaml .namespaceSelector | nindent 14 }} + {{- with .ports }} + ports: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + # Ingress depuis pods du même namespace (pour communication intra-ns) + - from: + - podSelector: {} + egress: + # DNS (CoreDNS dans kube-system) + {{- if .Values.networkPolicy.allowEgressDNS }} + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + {{- end }} + # K8s API (nécessaire si l'app utilise l'API via ServiceAccount) + {{- if .Values.networkPolicy.allowEgressKubeAPI }} + - to: + - ipBlock: + cidr: 10.96.0.0/12 # service CIDR + ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP + {{- end }} + # Egress spécifique de l'app (Postgres, Keycloak, Kafka, etc.) + {{- range .Values.networkPolicy.allowEgressTo }} + - to: + - namespaceSelector: + matchLabels: + {{- toYaml .namespaceSelector | nindent 14 }} + {{- with .ports }} + ports: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + # Sortie HTTPS vers Internet (Let's Encrypt ACME, external APIs) + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + ports: + - port: 443 + protocol: TCP + - port: 80 + protocol: TCP +{{- end }} diff --git a/templates/pdb.yaml b/templates/pdb.yaml new file mode 100644 index 0000000..05ce3c1 --- /dev/null +++ b/templates/pdb.yaml @@ -0,0 +1,18 @@ +{{- if and .Values.pdb.enabled (gt (int .Values.replicaCount) 1) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "lions-app.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} +spec: + {{- if .Values.pdb.minAvailable }} + minAvailable: {{ .Values.pdb.minAvailable }} + {{- else if .Values.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.pdb.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "lions-app.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/templates/service.yaml b/templates/service.yaml new file mode 100644 index 0000000..e0bfe64 --- /dev/null +++ b/templates/service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "lions-app.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: {{ .Values.service.protocol }} + selector: + {{- include "lions-app.selectorLabels" . | nindent 4 }} diff --git a/templates/serviceaccount.yaml b/templates/serviceaccount.yaml new file mode 100644 index 0000000..0e57e15 --- /dev/null +++ b/templates/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "lions-app.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: false +{{- end }} diff --git a/templates/servicemonitor.yaml b/templates/servicemonitor.yaml new file mode 100644 index 0000000..ca2d3a4 --- /dev/null +++ b/templates/servicemonitor.yaml @@ -0,0 +1,25 @@ +{{- if .Values.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "lions-app.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "lions-app.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: http + path: {{ .Values.serviceMonitor.path | default "/q/metrics" }} + interval: {{ .Values.serviceMonitor.interval | default "30s" }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout | default "10s" }} + scheme: http + selector: + matchLabels: + {{- include "lions-app.selectorLabels" . | nindent 6 }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} +{{- end }} diff --git a/values.yaml b/values.yaml new file mode 100644 index 0000000..3bb78cb --- /dev/null +++ b/values.yaml @@ -0,0 +1,299 @@ +# ============================================================ +# helm-chart-lions-app — Defaults +# ============================================================ +# Ces valeurs sont les defaults raisonnables pour une app Quarkus +# sur le cluster Lions. Chaque app override ce qui lui est propre. + +# ------------------------------------------------------------ +# Image +# ------------------------------------------------------------ +image: + registry: registry.lions.dev + repository: lionsdev + # name: — par défaut = .Release.Name (= nom de l'app) + # name: override-name # si nom d'image != nom release + tag: latest + pullPolicy: IfNotPresent + pullSecrets: + - lionsregistry-secret + +# ------------------------------------------------------------ +# Replicas +# ------------------------------------------------------------ +replicaCount: 1 + +# ------------------------------------------------------------ +# Stratégie de déploiement +# ------------------------------------------------------------ +strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + +# ------------------------------------------------------------ +# Resources — conservateur pour un node partagé +# ------------------------------------------------------------ +resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + +# ------------------------------------------------------------ +# Securité pod & container (OWASP Kubernetes top 10) +# ------------------------------------------------------------ +podSecurityContext: + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + seccompProfile: + type: RuntimeDefault + +containerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 1001 + capabilities: + drop: ["ALL"] + +# ------------------------------------------------------------ +# Service +# ------------------------------------------------------------ +service: + type: ClusterIP + port: 80 + targetPort: 8080 + protocol: TCP + annotations: {} + +# ------------------------------------------------------------ +# Conteneur +# ------------------------------------------------------------ +container: + port: 8080 + # extraArgs: [] # args passés à l'entrypoint + # extraEnv: [] # env additionnels (objets {name, value|valueFrom}) + +# ------------------------------------------------------------ +# Probes (valeurs Quarkus SmallRye Health par défaut) +# ------------------------------------------------------------ +probes: + liveness: + enabled: true + httpGet: + path: /q/health/live + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readiness: + enabled: true + httpGet: + path: /q/health/ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + startup: + enabled: false + httpGet: + path: /q/health/started + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 30 + +# ------------------------------------------------------------ +# Environment variables +# ------------------------------------------------------------ +# env: liste d'env vars en clair (non-sensibles uniquement) +env: [] +# - name: QUARKUS_PROFILE +# value: prod +# - name: JAVA_OPTS +# value: "-Xms256m -Xmx512m" + +# ------------------------------------------------------------ +# ConfigMap (env vars non-sensibles, partagées) +# ------------------------------------------------------------ +configMap: + enabled: true + # envFrom: true → toutes les clés sont injectées comme env + envFrom: true + data: {} + # QUARKUS_PROFILE: prod + # APP_ENV: production + # KAFKA_BOOTSTRAP_SERVERS: kafka-service.kafka.svc.cluster.local:9092 + +# ------------------------------------------------------------ +# ExternalSecret (Vault → K8s Secret via ESO) +# ------------------------------------------------------------ +externalSecret: + enabled: false + # Référence au ClusterSecretStore (Vault côté cluster) + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + refreshInterval: 1h + target: + creationPolicy: Owner + deletionPolicy: Retain + # Mapping : clé du K8s Secret → chemin Vault + propriété + data: [] + # - secretKey: QUARKUS_DATASOURCE_USERNAME + # remoteRef: + # key: lions/applications/unionflow-server/db + # property: username + # - secretKey: QUARKUS_DATASOURCE_PASSWORD + # remoteRef: + # key: lions/applications/unionflow-server/db + # property: password + +# ------------------------------------------------------------ +# Ingress +# ------------------------------------------------------------ +ingress: + enabled: true + className: nginx + clusterIssuer: letsencrypt-prod + host: lions.dev + # Deux modes de routing : + # 1. simple : path "/", pathType Prefix, pas de rewrite + # 2. prefix : path "/monchemin(/|$)(.*)", pathType ImplementationSpecific, rewrite-target /$2 + path: / + pathType: Prefix + pathPrefix: + enabled: false + # strip: "/myapp" # prefix à stripper (ex: /unionflow) + tls: + enabled: true + # secretName: = "-tls" + rateLimit: + enabled: true + rpm: 1000 # nginx.ingress.kubernetes.io/limit-rpm + connections: 100 # connections simultanées max par IP + cors: + enabled: false + origins: "*" + methods: "GET, POST, PUT, DELETE, OPTIONS, PATCH" + headers: "DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" + # Annotations nginx additionnelles + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + nginx.ingress.kubernetes.io/proxy-send-timeout: "300" + nginx.ingress.kubernetes.io/proxy-buffer-size: "16k" + nginx.ingress.kubernetes.io/proxy-buffers-number: "4" + nginx.ingress.kubernetes.io/proxy-buffering: "on" + nginx.ingress.kubernetes.io/enable-compression: "true" + nginx.ingress.kubernetes.io/compression-types: "text/plain,text/css,application/json,application/javascript,text/xml,application/xml" + +# ------------------------------------------------------------ +# NetworkPolicy (zero-trust, default-deny + allow list) +# ------------------------------------------------------------ +networkPolicy: + enabled: true + # Ingress autorisés (toujours depuis ingress-nginx) + allowIngressFrom: + - namespaceSelector: + kubernetes.io/metadata.name: ingress-nginx + - namespaceSelector: + kubernetes.io/metadata.name: monitoring # Prometheus scrape + # Egress : DNS + K8s API toujours autorisés + allowEgressDNS: true + allowEgressKubeAPI: true + # Egress spécifique (à override par app) + allowEgressTo: [] + # - namespaceSelector: + # kubernetes.io/metadata.name: postgresql + # ports: + # - port: 5432 + # protocol: TCP + # - namespaceSelector: + # kubernetes.io/metadata.name: keycloak + # ports: + # - port: 8080 + # protocol: TCP + +# ------------------------------------------------------------ +# PodDisruptionBudget +# ------------------------------------------------------------ +pdb: + enabled: false + minAvailable: 1 + +# ------------------------------------------------------------ +# ServiceAccount +# ------------------------------------------------------------ +serviceAccount: + create: true + annotations: {} + name: "" + +# ------------------------------------------------------------ +# ServiceMonitor (Prometheus Operator, si déployé) +# ------------------------------------------------------------ +serviceMonitor: + enabled: false + path: /q/metrics + interval: 30s + scrapeTimeout: 10s + labels: {} + +# ------------------------------------------------------------ +# Horizontal Pod Autoscaler +# ------------------------------------------------------------ +hpa: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 80 + +# ------------------------------------------------------------ +# Volumes — nécessaire avec readOnlyRootFilesystem=true +# ------------------------------------------------------------ +volumes: + tmp: + enabled: true + sizeLimit: 100Mi + logs: + enabled: true + sizeLimit: 500Mi + mountPath: /app/logs + # extra : volumes user-defined (PVC, configMap mounts, etc.) + extra: [] + +volumeMounts: [] + +# ------------------------------------------------------------ +# Scheduling +# ------------------------------------------------------------ +nodeSelector: + kubernetes.io/os: linux + +tolerations: + # Autorise scheduling sur control-plane (single-node cluster) + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule + +affinity: {} + +# ------------------------------------------------------------ +# Pod annotations/labels additionnels +# ------------------------------------------------------------ +podAnnotations: {} +podLabels: {} + +# Annotations/labels sur le chart lui-même +commonAnnotations: {} +commonLabels: {}