chore: add helm chart and docker
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: gestion-backend
|
||||||
|
description: Go API backend — gestion-commande
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "1.0.0"
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# =========================================================
|
||||||
|
# Stage 1: Build Go
|
||||||
|
# =========================================================
|
||||||
|
FROM golang:1.24-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk add --no-cache git ca-certificates tzdata gcc musl-dev
|
||||||
|
|
||||||
|
COPY backend/gestion/go.mod backend/gestion/go.sum ./
|
||||||
|
|
||||||
|
ENV GOPROXY=https://proxy.golang.org,direct
|
||||||
|
ENV GOSUMDB=sum.golang.org
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY backend/gestion/ .
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||||
|
-ldflags="-w -s -X main.Version=1.0.0 -X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||||
|
-o /app/server .
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# Stage 2: Runtime
|
||||||
|
# =========================================================
|
||||||
|
FROM alpine:latest AS runtime
|
||||||
|
|
||||||
|
RUN apk --no-cache add ca-certificates tzdata wget
|
||||||
|
|
||||||
|
RUN addgroup -g 101 app && adduser -u 101 -S app -G app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/server .
|
||||||
|
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||||
|
|
||||||
|
COPY helm/backend/entrypoint.sh .
|
||||||
|
RUN chmod +x entrypoint.sh
|
||||||
|
|
||||||
|
RUN mkdir -p /app/uploads/images /app/uploads/videos && \
|
||||||
|
chown -R app:app /app
|
||||||
|
|
||||||
|
USER app
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Créer le dossier uploads s'il n'existe pas (le volume le crée en root)
|
||||||
|
# Puis créer les sous-dossiers
|
||||||
|
if [ ! -d "/app/uploads" ]; then
|
||||||
|
mkdir -p /app/uploads
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Créer les sous-répertoires
|
||||||
|
mkdir -p /app/uploads/images /app/uploads/videos 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "✅ Répertoires uploads prêts"
|
||||||
|
|
||||||
|
# Lancer l'application
|
||||||
|
exec ./server
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{{- define "backend.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "backend.fullname" -}}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "backend.labels" -}}
|
||||||
|
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
|
||||||
|
app.kubernetes.io/name: {{ include "backend.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "backend.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "backend.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "backend.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "backend.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
{{- if not .Values.autoscaling.enabled }}
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "backend.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "backend.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: {{ include "backend.fullname" . }}-secrets
|
||||||
|
env:
|
||||||
|
{{- range $k, $v := .Values.env }}
|
||||||
|
- name: {{ $k }}
|
||||||
|
value: {{ $v | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.persistence.uploads.enabled }}
|
||||||
|
volumeMounts:
|
||||||
|
- name: uploads
|
||||||
|
mountPath: /app/uploads
|
||||||
|
{{- end }}
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/v1/app-settings
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/v1/app-settings
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
{{- if .Values.persistence.uploads.enabled }}
|
||||||
|
volumes:
|
||||||
|
- name: uploads
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ include "backend.fullname" . }}-uploads
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{{- if .Values.autoscaling.enabled }}
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: {{ include "backend.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "backend.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: {{ include "backend.fullname" . }}
|
||||||
|
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||||
|
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||||
|
metrics:
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{{- if .Values.persistence.uploads.enabled }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "backend.fullname" . }}-uploads
|
||||||
|
labels:
|
||||||
|
{{- include "backend.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.uploads.accessMode }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.uploads.size }}
|
||||||
|
{{- if .Values.persistence.uploads.storageClass }}
|
||||||
|
storageClassName: {{ .Values.persistence.uploads.storageClass }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: {{ include "backend.fullname" . }}-secrets
|
||||||
|
labels:
|
||||||
|
{{- include "backend.labels" . | nindent 4 }}
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
{{- range $k, $v := .Values.secrets }}
|
||||||
|
{{ $k }}: {{ $v | quote }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "backend.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "backend.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
selector:
|
||||||
|
{{- include "backend.selectorLabels" . | nindent 4 }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: {{ .Values.service.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
replicaCount: 2
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: backend-mln
|
||||||
|
tag: helm
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 256Mi
|
||||||
|
|
||||||
|
autoscaling:
|
||||||
|
enabled: true
|
||||||
|
minReplicas: 2
|
||||||
|
maxReplicas: 5
|
||||||
|
targetCPUUtilizationPercentage: 70
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
uploads:
|
||||||
|
enabled: true
|
||||||
|
size: 5Gi
|
||||||
|
storageClass: ""
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
|
||||||
|
env:
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: "5432"
|
||||||
|
DB_USER: postgres
|
||||||
|
DB_NAME: gestion_db
|
||||||
|
DB_SSLMODE: disable
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: "6379"
|
||||||
|
API_PORT: "8080"
|
||||||
|
|
||||||
|
# Valeurs sensibles — à surcharger via --set-string ou un Secret Manager externe
|
||||||
|
secrets:
|
||||||
|
DB_PASSWORD: ""
|
||||||
|
SESSION_SECRET: ""
|
||||||
|
USER_JWT_SECRET: ""
|
||||||
|
USER_JWT_SECRET_OLD: ""
|
||||||
|
ADMIN_JWT_SECRET: ""
|
||||||
|
ADMIN_JWT_SECRET_OLD: ""
|
||||||
|
REDIS_PASSWORD: ""
|
||||||
|
TOMTOM_API_KEY: ""
|
||||||
|
TELEGRAM_WEBHOOK_URL: ""
|
||||||
|
TELEGRAM_WEBHOOK_SECRET: ""
|
||||||
|
NOWPAYMENTS_IPN_SECRET: ""
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
podSecurityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 101
|
||||||
|
runAsGroup: 101
|
||||||
|
fsGroup: 101
|
||||||
|
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
echo "▶ Build"
|
||||||
|
docker build -f "$SCRIPT_DIR/frontend/Dockerfile" -t gestion-frontend:latest "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
echo "▶ Export"
|
||||||
|
docker save gestion-frontend:latest -o /tmp/gestion-frontend.tar
|
||||||
|
|
||||||
|
for NODE in 192.168.1.43 192.168.1.44 192.168.1.45; do
|
||||||
|
echo "→ $NODE"
|
||||||
|
ssh xor@$NODE "sudo k3s ctr images rm docker.io/library/gestion-frontend:latest 2>/dev/null; true"
|
||||||
|
scp /tmp/gestion-frontend.tar xor@$NODE:/tmp/
|
||||||
|
ssh xor@$NODE "sudo k3s ctr images import /tmp/gestion-frontend.tar && rm /tmp/gestion-frontend.tar"
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -f /tmp/gestion-frontend.tar
|
||||||
|
echo "✓ Done"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
:8080 {
|
||||||
|
root * /srv
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
# SPA fallback — toutes les routes inconnues renvoient index.html
|
||||||
|
try_files {path} /index.html
|
||||||
|
|
||||||
|
file_server
|
||||||
|
|
||||||
|
header {
|
||||||
|
-Server
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: gestion-frontend
|
||||||
|
description: React/Vite frontend — gestion-commande
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "1.0.0"
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# =========================================================
|
||||||
|
# Stage 1: Build React/Vite
|
||||||
|
# =========================================================
|
||||||
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY frontend-prep/package.json frontend-prep/package-lock.json* ./
|
||||||
|
RUN npm ci --ignore-scripts
|
||||||
|
|
||||||
|
COPY frontend-prep/ .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# Stage 2: Caddy — SPA static server (TLS terminé par Traefik)
|
||||||
|
# =========================================================
|
||||||
|
FROM caddy:alpine AS runtime
|
||||||
|
|
||||||
|
# Retire la file capability CAP_NET_BIND_SERVICE — incompatible avec
|
||||||
|
# allowPrivilegeEscalation: false. On écoute sur 8080 à la place.
|
||||||
|
RUN apk add --no-cache libcap && setcap -r /usr/bin/caddy && apk del libcap
|
||||||
|
|
||||||
|
COPY helm/frontend/Caddyfile /etc/caddy/Caddyfile
|
||||||
|
COPY --from=builder /app/dist /srv
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{{- define "frontend.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "frontend.fullname" -}}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "frontend.labels" -}}
|
||||||
|
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
|
||||||
|
app.kubernetes.io/name: {{ include "frontend.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "frontend.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "frontend.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "frontend.fullname" . }}-config
|
||||||
|
labels:
|
||||||
|
{{- include "frontend.labels" . | nindent 4 }}
|
||||||
|
data:
|
||||||
|
config.js: |
|
||||||
|
window.__APP_CONFIG__ = {
|
||||||
|
apiUrl: {{ .Values.apiUrl | quote }}
|
||||||
|
};
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "frontend.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "frontend.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
{{- if not .Values.autoscaling.enabled }}
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "frontend.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "frontend.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: caddy-data
|
||||||
|
mountPath: /data
|
||||||
|
- name: app-config
|
||||||
|
mountPath: /srv/config.js
|
||||||
|
subPath: config.js
|
||||||
|
readOnly: true
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: caddy-data
|
||||||
|
emptyDir: {}
|
||||||
|
- name: app-config
|
||||||
|
configMap:
|
||||||
|
name: {{ include "frontend.fullname" . }}-config
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{{- if .Values.autoscaling.enabled }}
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: {{ include "frontend.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "frontend.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: {{ include "frontend.fullname" . }}
|
||||||
|
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||||
|
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||||
|
metrics:
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "frontend.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "frontend.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
selector:
|
||||||
|
{{- include "frontend.selectorLabels" . | nindent 4 }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: {{ .Values.service.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
replicaCount: 2
|
||||||
|
|
||||||
|
apiUrl: ""
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: frontend-mln
|
||||||
|
tag: helm
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 128Mi
|
||||||
|
|
||||||
|
autoscaling:
|
||||||
|
enabled: true
|
||||||
|
minReplicas: 2
|
||||||
|
maxReplicas: 4
|
||||||
|
targetCPUUtilizationPercentage: 70
|
||||||
|
|
||||||
|
podSecurityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 101
|
||||||
|
runAsGroup: 101
|
||||||
|
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: gestion-postgresql
|
||||||
|
description: PostgreSQL 16 — gestion-commande
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "16"
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{{- define "postgresql.fullname" -}}
|
||||||
|
{{- printf "%s-postgresql" .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "postgresql.labels" -}}
|
||||||
|
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
|
||||||
|
app.kubernetes.io/name: postgresql
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "postgresql.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: postgresql
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "postgresql.fullname" . }}-config
|
||||||
|
labels:
|
||||||
|
{{- include "postgresql.labels" . | nindent 4 }}
|
||||||
|
data:
|
||||||
|
postgresql.conf: |
|
||||||
|
max_connections = {{ .Values.config.maxConnections }}
|
||||||
|
shared_buffers = {{ .Values.config.sharedBuffers }}
|
||||||
|
effective_cache_size = {{ .Values.config.effectiveCacheSize }}
|
||||||
|
maintenance_work_mem = {{ .Values.config.maintenanceWorkMem }}
|
||||||
|
wal_buffers = {{ .Values.config.walBuffers }}
|
||||||
|
default_statistics_target = {{ .Values.config.defaultStatisticsTarget }}
|
||||||
|
random_page_cost = {{ .Values.config.randomPageCost }}
|
||||||
|
log_min_duration_statement = {{ .Values.config.logMinDurationStatement }}
|
||||||
|
log_timezone = 'UTC'
|
||||||
|
timezone = 'UTC'
|
||||||
|
# Connexions sécurisées uniquement depuis le réseau interne cluster
|
||||||
|
listen_addresses = '*'
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "postgresql.fullname" . }}-data
|
||||||
|
labels:
|
||||||
|
{{- include "postgresql.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.accessMode }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.size }}
|
||||||
|
{{- if .Values.persistence.storageClass }}
|
||||||
|
storageClassName: {{ .Values.persistence.storageClass }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: {{ include "postgresql.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "postgresql.labels" . | nindent 4 }}
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
POSTGRES_PASSWORD: {{ .Values.auth.password | quote }}
|
||||||
|
POSTGRES_USER: {{ .Values.auth.username | quote }}
|
||||||
|
POSTGRES_DB: {{ .Values.auth.database | quote }}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "postgresql.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "postgresql.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
selector:
|
||||||
|
{{- include "postgresql.selectorLabels" . | nindent 4 }}
|
||||||
|
ports:
|
||||||
|
- name: postgresql
|
||||||
|
port: {{ .Values.service.port }}
|
||||||
|
targetPort: postgresql
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
# Service headless pour le StatefulSet
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "postgresql.fullname" . }}-headless
|
||||||
|
labels:
|
||||||
|
{{- include "postgresql.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
clusterIP: None
|
||||||
|
selector:
|
||||||
|
{{- include "postgresql.selectorLabels" . | nindent 4 }}
|
||||||
|
ports:
|
||||||
|
- name: postgresql
|
||||||
|
port: {{ .Values.service.port }}
|
||||||
|
targetPort: postgresql
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: {{ include "postgresql.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "postgresql.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
serviceName: {{ include "postgresql.fullname" . }}-headless
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "postgresql.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "postgresql.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: postgresql
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
ports:
|
||||||
|
- name: postgresql
|
||||||
|
containerPort: 5432
|
||||||
|
protocol: TCP
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: {{ include "postgresql.fullname" . }}
|
||||||
|
env:
|
||||||
|
- name: PGDATA
|
||||||
|
value: /var/lib/postgresql/data/pgdata
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/postgresql/postgresql.conf
|
||||||
|
subPath: postgresql.conf
|
||||||
|
args:
|
||||||
|
- "-c"
|
||||||
|
- "config_file=/etc/postgresql/postgresql.conf"
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 6
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ include "postgresql.fullname" . }}-data
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: {{ include "postgresql.fullname" . }}-config
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
image:
|
||||||
|
repository: postgres
|
||||||
|
tag: "16-alpine"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
auth:
|
||||||
|
username: postgres
|
||||||
|
database: gestion_db
|
||||||
|
# À surcharger via --set auth.password=xxx
|
||||||
|
password: ""
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: ""
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 1Gi
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 5432
|
||||||
|
|
||||||
|
# Paramètres PostgreSQL (postgresql.conf)
|
||||||
|
config:
|
||||||
|
maxConnections: "200"
|
||||||
|
sharedBuffers: "256MB"
|
||||||
|
effectiveCacheSize: "768MB"
|
||||||
|
maintenanceWorkMem: "64MB"
|
||||||
|
walBuffers: "16MB"
|
||||||
|
defaultStatisticsTarget: "100"
|
||||||
|
randomPageCost: "1.1"
|
||||||
|
logMinDurationStatement: "1000"
|
||||||
|
|
||||||
|
podSecurityContext:
|
||||||
|
runAsUser: 999
|
||||||
|
runAsGroup: 999
|
||||||
|
fsGroup: 999
|
||||||
|
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: gestion-redis
|
||||||
|
description: Redis 7 — gestion-commande
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "7"
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{{- define "redis.fullname" -}}
|
||||||
|
{{- printf "%s-redis" .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "redis.labels" -}}
|
||||||
|
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
|
||||||
|
app.kubernetes.io/name: redis
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "redis.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: redis
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "redis.fullname" . }}-config
|
||||||
|
labels:
|
||||||
|
{{- include "redis.labels" . | nindent 4 }}
|
||||||
|
data:
|
||||||
|
redis.conf: |
|
||||||
|
maxmemory {{ .Values.config.maxmemory }}
|
||||||
|
maxmemory-policy {{ .Values.config.maxmemoryPolicy }}
|
||||||
|
appendonly {{ .Values.config.appendonly }}
|
||||||
|
appendfsync {{ .Values.config.appendfsync }}
|
||||||
|
{{- if .Values.config.save }}
|
||||||
|
save {{ .Values.config.save }}
|
||||||
|
{{- else }}
|
||||||
|
save ""
|
||||||
|
{{- end }}
|
||||||
|
protected-mode no
|
||||||
|
loglevel notice
|
||||||
|
# Le mot de passe est injecté au démarrage via --requirepass
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "redis.fullname" . }}-data
|
||||||
|
labels:
|
||||||
|
{{- include "redis.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.accessMode }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.size }}
|
||||||
|
{{- if .Values.persistence.storageClass }}
|
||||||
|
storageClassName: {{ .Values.persistence.storageClass }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: {{ include "redis.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "redis.labels" . | nindent 4 }}
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
REDIS_PASSWORD: {{ .Values.auth.password | quote }}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "redis.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "redis.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
selector:
|
||||||
|
{{- include "redis.selectorLabels" . | nindent 4 }}
|
||||||
|
ports:
|
||||||
|
- name: redis
|
||||||
|
port: {{ .Values.service.port }}
|
||||||
|
targetPort: redis
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "redis.fullname" . }}-headless
|
||||||
|
labels:
|
||||||
|
{{- include "redis.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
clusterIP: None
|
||||||
|
selector:
|
||||||
|
{{- include "redis.selectorLabels" . | nindent 4 }}
|
||||||
|
ports:
|
||||||
|
- name: redis
|
||||||
|
port: {{ .Values.service.port }}
|
||||||
|
targetPort: redis
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: {{ include "redis.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "redis.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
serviceName: {{ include "redis.fullname" . }}-headless
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "redis.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "redis.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: redis
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
ports:
|
||||||
|
- name: redis
|
||||||
|
containerPort: 6379
|
||||||
|
protocol: TCP
|
||||||
|
command:
|
||||||
|
- redis-server
|
||||||
|
- /etc/redis/redis.conf
|
||||||
|
- "--requirepass"
|
||||||
|
- "$(REDIS_PASSWORD)"
|
||||||
|
env:
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "redis.fullname" . }}
|
||||||
|
key: REDIS_PASSWORD
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/redis/redis.conf
|
||||||
|
subPath: redis.conf
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- redis-cli --no-auth-warning -a $REDIS_PASSWORD ping | grep PONG
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 6
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- redis-cli --no-auth-warning -a $REDIS_PASSWORD ping | grep PONG
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ include "redis.fullname" . }}-data
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: {{ include "redis.fullname" . }}-config
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
image:
|
||||||
|
repository: redis
|
||||||
|
tag: "7-alpine"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
auth:
|
||||||
|
# À surcharger via --set auth.password=xxx
|
||||||
|
password: ""
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
size: 2Gi
|
||||||
|
storageClass: ""
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 6379
|
||||||
|
|
||||||
|
config:
|
||||||
|
maxmemory: "256mb"
|
||||||
|
# allkeys-lru : expulse les clés les moins récemment utilisées quand la mémoire est pleine
|
||||||
|
maxmemoryPolicy: "allkeys-lru"
|
||||||
|
appendonly: "yes"
|
||||||
|
appendfsync: "everysec"
|
||||||
|
# Désactiver les snapshots RDB (AOF suffit)
|
||||||
|
save: ""
|
||||||
|
|
||||||
|
podSecurityContext:
|
||||||
|
runAsUser: 999
|
||||||
|
runAsGroup: 999
|
||||||
|
fsGroup: 999
|
||||||
|
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: gestion-traefik
|
||||||
|
description: Traefik ingress + Coraza WAF (OWASP CRS) — gestion-commande
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "3.1.0"
|
||||||
|
dependencies:
|
||||||
|
- name: traefik
|
||||||
|
version: ">=30.0.0"
|
||||||
|
repository: https://helm.traefik.io/traefik
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{{- define "gestion-traefik.labels" -}}
|
||||||
|
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
|
||||||
|
app.kubernetes.io/name: {{ .Chart.Name }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "gestion-traefik.certResolver" -}}
|
||||||
|
{{- if eq .Values.tls.mode "letsencrypt" -}}
|
||||||
|
letsencrypt
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
# HTTP → HTTPS redirect
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: gestion-http
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "gestion-traefik.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
routes:
|
||||||
|
- match: Host(`{{ .Values.domain }}`)
|
||||||
|
kind: Rule
|
||||||
|
middlewares:
|
||||||
|
- name: redirect-https
|
||||||
|
services:
|
||||||
|
- name: noop@internal
|
||||||
|
kind: TraefikService
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: redirect-https
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
spec:
|
||||||
|
redirectScheme:
|
||||||
|
scheme: https
|
||||||
|
permanent: true
|
||||||
|
---
|
||||||
|
# HTTPS — routes principales
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: gestion-https
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "gestion-traefik.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
# API backend — WAF + headers + rate limit
|
||||||
|
- match: Host(`{{ .Values.domain }}`) && (PathPrefix(`/api/`) || PathPrefix(`/webhook/`))
|
||||||
|
kind: Rule
|
||||||
|
priority: 20
|
||||||
|
middlewares:
|
||||||
|
- name: waf-chain
|
||||||
|
services:
|
||||||
|
- name: {{ .Values.backend.serviceName }}
|
||||||
|
namespace: {{ .Values.servicesNamespace }}
|
||||||
|
port: {{ .Values.backend.servicePort }}
|
||||||
|
|
||||||
|
# Fichiers uploadés — WAF désactivé dans les règles Coraza pour ce path
|
||||||
|
- match: Host(`{{ .Values.domain }}`) && PathPrefix(`/uploads/`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 15
|
||||||
|
middlewares:
|
||||||
|
- name: security-headers
|
||||||
|
services:
|
||||||
|
- name: {{ .Values.backend.serviceName }}
|
||||||
|
namespace: {{ .Values.servicesNamespace }}
|
||||||
|
port: {{ .Values.backend.servicePort }}
|
||||||
|
|
||||||
|
# Frontend SPA — WAF + headers + rate limit
|
||||||
|
- match: Host(`{{ .Values.domain }}`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 1
|
||||||
|
middlewares:
|
||||||
|
- name: waf-chain
|
||||||
|
services:
|
||||||
|
- name: {{ .Values.frontend.serviceName }}
|
||||||
|
namespace: {{ .Values.servicesNamespace }}
|
||||||
|
port: {{ .Values.frontend.servicePort }}
|
||||||
|
|
||||||
|
tls:
|
||||||
|
{{- if eq .Values.tls.mode "letsencrypt" }}
|
||||||
|
certResolver: letsencrypt
|
||||||
|
{{- else }}
|
||||||
|
secretName: {{ .Values.tls.secretName }}
|
||||||
|
{{- end }}
|
||||||
|
domains:
|
||||||
|
- main: {{ .Values.domain }}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: waf-chain
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "gestion-traefik.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
chain:
|
||||||
|
middlewares:
|
||||||
|
- name: rate-limit
|
||||||
|
- name: coraza-waf
|
||||||
|
- name: security-headers
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: security-headers
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "gestion-traefik.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
headers:
|
||||||
|
stsSeconds: 63072000
|
||||||
|
stsIncludeSubdomains: true
|
||||||
|
stsPreload: true
|
||||||
|
frameDeny: true
|
||||||
|
contentTypeNosniff: true
|
||||||
|
referrerPolicy: "strict-origin-when-cross-origin"
|
||||||
|
permissionsPolicy: "geolocation=(), microphone=(), camera=(), payment=()"
|
||||||
|
customResponseHeaders:
|
||||||
|
X-XSS-Protection: "0"
|
||||||
|
Content-Security-Policy: >-
|
||||||
|
default-src 'self';
|
||||||
|
script-src 'self' 'unsafe-inline';
|
||||||
|
style-src 'self' 'unsafe-inline';
|
||||||
|
img-src 'self' data: blob: https:;
|
||||||
|
font-src 'self' data:;
|
||||||
|
connect-src 'self' https:;
|
||||||
|
frame-ancestors 'self';
|
||||||
|
Server: ""
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: rate-limit
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "gestion-traefik.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
rateLimit:
|
||||||
|
average: {{ .Values.rateLimit.average }}
|
||||||
|
burst: {{ .Values.rateLimit.burst }}
|
||||||
|
period: 1m
|
||||||
|
sourceCriterion:
|
||||||
|
ipStrategy:
|
||||||
|
# depth=1 : utilise la première IP de X-Forwarded-For
|
||||||
|
# (Traefik pose lui-même ce header depuis la vraie IP TCP, non spoofable)
|
||||||
|
depth: 1
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: coraza-waf
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "gestion-traefik.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
plugin:
|
||||||
|
coraza-traefik:
|
||||||
|
directives:
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# Moteur et body inspection
|
||||||
|
# -------------------------------------------------------
|
||||||
|
- "SecRuleEngine On"
|
||||||
|
- "SecRequestBodyAccess On"
|
||||||
|
- "SecRequestBodyLimit 104857600"
|
||||||
|
- "SecRequestBodyInMemoryLimit 131072"
|
||||||
|
- "SecResponseBodyAccess Off"
|
||||||
|
- "SecDebugLogLevel 0"
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# OWASP CRS — inclus dans le plugin Coraza
|
||||||
|
# -------------------------------------------------------
|
||||||
|
- "Include @coraza.conf-recommended"
|
||||||
|
- "Include @crs-setup.conf.example"
|
||||||
|
- "Include @owasp_crs/*.conf"
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# Niveau de paranoia {{ .Values.waf.paranoia }}
|
||||||
|
# -------------------------------------------------------
|
||||||
|
- "SecAction \"id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level={{ .Values.waf.paranoia }}\""
|
||||||
|
- "SecAction \"id:900001,phase:1,nolog,pass,t:none,setvar:tx.inbound_anomaly_score_threshold={{ .Values.waf.anomalyInbound }}\""
|
||||||
|
- "SecAction \"id:900002,phase:1,nolog,pass,t:none,setvar:tx.outbound_anomaly_score_threshold={{ .Values.waf.anomalyOutbound }}\""
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# Suppressions CRS pour les routes légitimes
|
||||||
|
# -------------------------------------------------------
|
||||||
|
- "SecRuleRemoveById 932235"
|
||||||
|
- "SecRuleRemoveById 911100"
|
||||||
|
|
||||||
|
- "SecRule REQUEST_URI \"@streq /api/v2/admin/protected/products\" \"id:399001,phase:2,nolog,pass,ctl:ruleRemoveById=932235\""
|
||||||
|
- "SecRule REQUEST_URI \"@streq /api/v2/admin/protected/products\" \"id:399002,phase:2,nolog,pass,ctl:ruleRemoveById=920120,ctl:ruleRemoveById=920121\""
|
||||||
|
- "SecRule REQUEST_URI \"@streq /api/v1/panier/remove\" \"id:399010,phase:1,nolog,pass,ctl:ruleRemoveById=911100,ctl:ruleRemoveById=920350\""
|
||||||
|
- "SecRule REQUEST_URI \"@streq /api/v1/panier/clear\" \"id:399011,phase:1,nolog,pass,ctl:ruleRemoveById=911100,ctl:ruleRemoveById=920350\""
|
||||||
|
|
||||||
|
# Désactiver le WAF sur les uploads (fichiers statiques en lecture)
|
||||||
|
- "SecRule REQUEST_URI \"@beginsWith /uploads/\" \"id:1000,phase:1,pass,nolog,ctl:ruleEngine=Off\""
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# IP banning automatique sur attaques détectées
|
||||||
|
# Note : en-mémoire par pod — pour un ban distribué utiliser Redis + Coraza persistence
|
||||||
|
# -------------------------------------------------------
|
||||||
|
- "SecRule TX:SQL_INJECTION_SCORE \"@ge 5\" \"id:100001,phase:2,deny,status:403,log,msg:'SQL Injection detected',setvar:'ip.banned=1',expirevar:'ip.banned=172800'\""
|
||||||
|
- "SecRule TX:XSS_SCORE \"@ge 5\" \"id:100010,phase:2,deny,status:403,log,msg:'XSS detected',setvar:'ip.banned=1',expirevar:'ip.banned=172800'\""
|
||||||
|
- "SecRule TX:RCE_SCORE \"@ge 5\" \"id:100020,phase:2,deny,status:403,log,msg:'RCE detected',setvar:'ip.banned=1',expirevar:'ip.banned=259200'\""
|
||||||
|
- "SecRule TX:LFI_SCORE \"@ge 5\" \"id:100030,phase:2,deny,status:403,log,msg:'LFI detected',setvar:'ip.banned=1',expirevar:'ip.banned=172800'\""
|
||||||
|
- "SecRule TX:INBOUND_ANOMALY_SCORE \"@ge 20\" \"id:100060,phase:2,deny,status:403,log,msg:'Critical anomaly score',setvar:'ip.banned=1',expirevar:'ip.banned=172800'\""
|
||||||
|
|
||||||
|
- "SecRule IP:BANNED \"@eq 1\" \"id:100000,phase:1,deny,status:403,log,msg:'IP is banned'\""
|
||||||
|
- "SecRule IP:REPUTATION_SCORE \"@ge 100\" \"id:100099,phase:1,deny,status:403,log,msg:'Critical reputation score',setvar:'ip.blocked=1',expirevar:'ip.blocked=86400'\""
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: TLSStore
|
||||||
|
metadata:
|
||||||
|
name: default
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "gestion-traefik.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
{{- if eq .Values.tls.mode "secret" }}
|
||||||
|
defaultCertificate:
|
||||||
|
secretName: {{ .Values.tls.secretName }}
|
||||||
|
{{- end }}
|
||||||
|
defaultGeneratedCert:
|
||||||
|
resolver: {{ include "gestion-traefik.certResolver" . | default "letsencrypt" }}
|
||||||
|
domain:
|
||||||
|
main: {{ .Values.domain }}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# =============================================================
|
||||||
|
# Paramètres spécifiques à ce chart (IngressRoutes, Middlewares)
|
||||||
|
# =============================================================
|
||||||
|
|
||||||
|
# Namespace des services backend et frontend
|
||||||
|
servicesNamespace: gestion
|
||||||
|
|
||||||
|
domain: example.com
|
||||||
|
|
||||||
|
tls:
|
||||||
|
# "letsencrypt" | "secret"
|
||||||
|
mode: letsencrypt
|
||||||
|
email: admin@example.com
|
||||||
|
# Si mode=secret : nom du Secret TLS Kubernetes
|
||||||
|
secretName: gestion-tls
|
||||||
|
|
||||||
|
backend:
|
||||||
|
serviceName: gestion-backend
|
||||||
|
servicePort: 8080
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
serviceName: gestion-frontend
|
||||||
|
servicePort: 80
|
||||||
|
|
||||||
|
waf:
|
||||||
|
paranoia: 2
|
||||||
|
anomalyInbound: 5
|
||||||
|
anomalyOutbound: 4
|
||||||
|
|
||||||
|
rateLimit:
|
||||||
|
# requêtes moyennes par seconde par IP
|
||||||
|
average: 20
|
||||||
|
burst: 50
|
||||||
|
|
||||||
|
# =============================================================
|
||||||
|
# Valeurs passées au subchart officiel traefik
|
||||||
|
# https://github.com/traefik/traefik-helm-chart
|
||||||
|
# =============================================================
|
||||||
|
traefik:
|
||||||
|
# Expose les ports 80 et 443
|
||||||
|
ports:
|
||||||
|
web:
|
||||||
|
port: 8000
|
||||||
|
expose:
|
||||||
|
default: true
|
||||||
|
redirectTo:
|
||||||
|
port: websecure
|
||||||
|
websecure:
|
||||||
|
port: 8443
|
||||||
|
expose:
|
||||||
|
default: true
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs:
|
||||||
|
general:
|
||||||
|
level: INFO
|
||||||
|
access:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# Plugin Coraza WAF — Traefik télécharge le plugin au démarrage
|
||||||
|
# (le cluster doit avoir accès à internet ou utiliser un plugin local)
|
||||||
|
experimental:
|
||||||
|
plugins:
|
||||||
|
coraza-traefik:
|
||||||
|
moduleName: "github.com/corazawaf/coraza-traefik"
|
||||||
|
version: "v0.3.0"
|
||||||
|
|
||||||
|
# Cert resolver Let's Encrypt (ignoré si tls.mode=secret)
|
||||||
|
certResolvers:
|
||||||
|
letsencrypt:
|
||||||
|
email: admin@example.com
|
||||||
|
storage: /data/acme.json
|
||||||
|
httpChallenge:
|
||||||
|
entryPoint: web
|
||||||
|
|
||||||
|
# ACME storage persistant
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
size: 128Mi
|
||||||
|
|
||||||
|
# Traefik lit les IngressRoutes dans tous les namespaces
|
||||||
|
providers:
|
||||||
|
kubernetesCRD:
|
||||||
|
enabled: true
|
||||||
|
allowCrossNamespace: true
|
||||||
|
|
||||||
|
# IP réelle via X-Forwarded-For (profondeur 1 = proxy direct)
|
||||||
|
# Le rate-limiter et les logs utilisent cette IP
|
||||||
|
service:
|
||||||
|
spec:
|
||||||
|
externalTrafficPolicy: Local
|
||||||
|
|
||||||
|
# Métriques Prometheus
|
||||||
|
metrics:
|
||||||
|
prometheus:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# Ressources
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 256Mi
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:-omnex}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-omnex}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-omnex}
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-omnex} -d ${POSTGRES_DB:-omnex}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 10
|
||||||
|
# Pas de port exposé : accès interne uniquement (défense en profondeur).
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD:-omnexredis}", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
|
||||||
|
volumes:
|
||||||
|
- redisdata:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-omnexredis}", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: xor1234/omnex-web:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
api:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
|
||||||
|
api:
|
||||||
|
image: xor1234/omnex-api:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
OMNEX_ENV: ${OMNEX_ENV:-dev}
|
||||||
|
OMNEX_ADDR: ":8080"
|
||||||
|
OMNEX_JWT_SECRET: ${OMNEX_JWT_SECRET}
|
||||||
|
OMNEX_ALLOWED_ORIGINS: ${OMNEX_ALLOWED_ORIGINS:-http://localhost:3000,http://localhost:5173}
|
||||||
|
OMNEX_DATABASE_URL: "host=postgres user=${POSTGRES_USER:-omnex} password=${POSTGRES_PASSWORD:-omnex} dbname=${POSTGRES_DB:-omnex} port=5432 sslmode=disable"
|
||||||
|
OMNEX_REDIS_URL: "redis://:${REDIS_PASSWORD:-omnexredis}@redis:6379/0"
|
||||||
|
OMNEX_SEED_USERNAME: ${OMNEX_SEED_USERNAME:-admin}
|
||||||
|
OMNEX_SEED_PASSWORD: ${OMNEX_SEED_PASSWORD}
|
||||||
|
OMNEX_DEMO_DOMAIN: ${OMNEX_DEMO_DOMAIN:-demo.omnex.app}
|
||||||
|
FRONTEND_IMAGE_APP: ${FRONTEND_IMAGE_APP:-xor1234/frontend-mln:latest}
|
||||||
|
BACKEND_IMAGE_APP: ${BACKEND_IMAGE_APP:-xor1234/backend-mln:latest}
|
||||||
|
KUBECONFIG: /kubeconfig/config
|
||||||
|
volumes:
|
||||||
|
- ./deploy/chart-gestion:/charts:ro
|
||||||
|
- /home/xor_fakers/.kube/config:/kubeconfig/config:ro
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 10
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
|
redisdata:
|
||||||
Reference in New Issue
Block a user