feat: add Argo Rollouts with canary strategy for prod
All checks were successful
AI Review / AI Code Review (pull_request) Successful in 1s
PR Checks / Validate & Security Scan (pull_request) Successful in 10s

- Install Argo Rollouts via ArgoCD (Helm chart 2.39.1)
- Add Rollout template with nginx traffic routing
- Add canary Service for traffic splitting
- Enable canary for prod arch-docs (20% → 60s → 50% → 60s → 100%)
- Dev/staging remain standard Deployment (1 replica, canary not useful)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root 2026-02-22 19:36:11 +01:00
parent e159bcac20
commit 465a9859b7
6 changed files with 148 additions and 0 deletions

View File

@ -0,0 +1,36 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argo-rollouts
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
chart: argo-rollouts
repoURL: https://argoproj.github.io/argo-helm
targetRevision: "2.39.1"
helm:
values: |
controller:
replicas: 1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
dashboard:
enabled: false
destination:
server: https://kubernetes.default.svc
namespace: argo-rollouts
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true

View File

@ -1,3 +1,4 @@
{{- if not .Values.rollout.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
@ -63,3 +64,4 @@ spec:
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,77 @@
{{- if .Values.rollout.enabled }}
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: {{ include "web-app.fullname" . }}
labels:
{{- include "web-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
revisionHistoryLimit: 3
selector:
matchLabels:
{{- include "web-app.selectorLabels" . | nindent 6 }}
strategy:
canary:
canaryService: {{ include "web-app.fullname" . }}-canary
stableService: {{ include "web-app.fullname" . }}
trafficRouting:
nginx:
stableIngress: {{ include "web-app.fullname" . }}
steps:
{{- toYaml .Values.rollout.steps | nindent 8 }}
template:
metadata:
labels:
{{- include "web-app.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: {{ include "web-app.fullname" . }}
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.containerPort }}
protocol: TCP
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: true
{{- if .Values.env }}
env:
{{- toYaml .Values.env | nindent 12 }}
{{- end }}
volumeMounts:
- name: tmp
mountPath: /tmp
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: tmp
emptyDir: {}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,17 @@
{{- if .Values.rollout.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "web-app.fullname" . }}-canary
labels:
{{- include "web-app.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "web-app.selectorLabels" . | nindent 4 }}
{{- end }}

View File

@ -50,6 +50,14 @@ env: []
extraVolumeMounts: []
extraVolumes: []
rollout:
enabled: false
steps:
- setWeight: 20
- pause: { duration: 60s }
- setWeight: 50
- pause: { duration: 60s }
autoscaling:
enabled: false
minReplicas: 1

View File

@ -32,6 +32,14 @@ ingress:
enabled: true
clusterIssuer: letsencrypt-prod
rollout:
enabled: true
steps:
- setWeight: 20
- pause: { duration: 60s }
- setWeight: 50
- pause: { duration: 60s }
service:
type: ClusterIP
port: 80