diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c6ac208..4fa52a1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -32,5 +32,6 @@ jobs: kubectl apply -f https://raw.githubusercontent.com/opsgy/loki-rule-operator/main/chart/crds/logging.opsgy.com_lokirules.yaml kubectl apply -f https://raw.githubusercontent.com/istio/istio/master/manifests/charts/base/crds/crd-all.gen.yaml kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master/manifests/crds/rollout-crd.yaml + kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master/manifests/crds/analysis-template-crd.yaml - name: Install charts run: ct install --all diff --git a/charts/generic-service/README.md b/charts/generic-service/README.md index b088feb..89c15fc 100644 --- a/charts/generic-service/README.md +++ b/charts/generic-service/README.md @@ -68,7 +68,11 @@ app: | `rollout.autoPromotion` | `true` | Automatically promote rollouts (if `rollout.strategy` is `BlueGreen` and `rollout.flagger` if `false`) | | `rollout.flagger` | `false` | Use Flagger to control rollouts (`rollout.controller` must be `Deployment` or `StatefulSet`) | | `rollout.analysis` | req. for Canary or Flagger | Flagger or Argo Rollouts analysis for automatic `Canary` or `BlueGreen` promotion | -| `rollout.revisionHistoryLimit` | `null` | Number of old ReplicaSets to retain (`rollout.controller` must be `Deployment` or `ArgoRollout`) | +| `rollout.revisionHistoryLimit` | `600` | Number of old ReplicaSets to retain (`rollout.controller` must be `Deployment` or `ArgoRollout`) | +| `rollout.progressDeadlineSeconds` | `null` | The maximum time in seconds for a deployment to make progress before it is considered to be failed | +| `rollout.minReadySeconds` | `0` | Minimum number of seconds for which a new pod should be ready for it to be considered available | +| `rollout.progressDeadlineAbort` | `false` | Whether to abort the update when `rollout.progressDeadlineSeconds` is exceeded (`rollout.controller` must be `ArgoRollout` and `rollout.strategy` `Canary`) | +| `rollout.notifications` | `{}` | Option to subscribe to argo rollout events and send slack notifications | | `replicas` | `1` | The number of instances of the service to run (set at least `2` for Pod Disruption Budget) | | `autoscaling.enabled` | `false` | Enables automatic starting of additional instances | | `autoscaling.maxReplicas` | `3` | The maximum number of instances to run (must be larger than `replicas`) | diff --git a/charts/generic-service/ci/argo-bluegreen-values.yaml b/charts/generic-service/ci/argo-bluegreen-values.yaml new file mode 100644 index 0000000..20923cc --- /dev/null +++ b/charts/generic-service/ci/argo-bluegreen-values.yaml @@ -0,0 +1,20 @@ +# Argo Rollout controller BlueGreen test + +image: + repository: jwilder/whoami + tag: latest + +ingress: + enabled: true + port: 8000 + +rollout: + controller: ArgoRollout + strategy: BlueGreen + autoPromotion: true + analysis: + templates: + - templateName: success-rate + args: + - name: service-name + value: canary-demo diff --git a/charts/generic-service/ci/argo-canary-values.yaml b/charts/generic-service/ci/argo-canary-values.yaml new file mode 100644 index 0000000..c4813cf --- /dev/null +++ b/charts/generic-service/ci/argo-canary-values.yaml @@ -0,0 +1,48 @@ +# Argo Rollout controller Canary test + +image: + repository: jwilder/whoami + tag: latest + +ingress: + enabled: true + port: 8000 + +replicas: 2 + +rollout: + controller: ArgoRollout + strategy: Canary + analysis: + templates: + - templateName: success-rate + args: + - name: service-name + value: canary-demo + steps: + - setWeight: 20 + - analysis: + templates: + - templateName: success-rate + args: + - name: service-name + value: canary-demo + - pause: + duration: 1h + - setWeight: 40 + analysisTemplates: + - name: success-rate + args: + - name: service-name + metrics: + - name: success-rate + interval: 5m + count: 10 + successCondition: "result[0] >= 0.95" + query: | + sum(irate( + istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m] + )) / + sum(irate( + istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m] + )) diff --git a/charts/generic-service/templates/analysis-template.yaml b/charts/generic-service/templates/analysis-template.yaml new file mode 100644 index 0000000..9ce10c7 --- /dev/null +++ b/charts/generic-service/templates/analysis-template.yaml @@ -0,0 +1,55 @@ +{{- if eq .Values.rollout.controller "ArgoRollout" }} +{{- range .Values.rollout.analysisTemplates }} +--- +apiVersion: argoproj.io/v1alpha1 +kind: AnalysisTemplate +metadata: + name: {{ include "generic-service.fullname" $ }}-{{ .name }} +spec: + {{- if .args }} + args: + {{- toYaml .args | nindent 4 }} + {{- end }} + {{- if .dryRun }} + dryRun: + {{- toYaml .dryRun | nindent 4 }} + {{- end }} + {{- if .measurementRetention }} + measurementRetention: + {{- toYaml .measurementRetention | nindent 4 }} + {{- end }} + metrics: + {{- range .metrics }} + - name: {{ .name }} + {{- if .consecutiveErrorLimit }} + consecutiveErrorLimit: {{ .consecutiveErrorLimit }} + {{- end }} + {{- if .count }} + count: {{ .count }} + {{- end }} + {{- if .failureCondition }} + failureCondition: {{ .failureCondition }} + {{- end }} + {{- if .successCondition }} + successCondition: {{ .successCondition }} + {{- end }} + {{- if .failureLimit }} + failureLimit: {{ .failureLimit }} + {{- end }} + {{- if .inconclusiveLimit }} + inconclusiveLimit: {{ .inconclusiveLimit }} + {{- end }} + {{- if .initialDelay }} + initialDelay: {{ .initialDelay }} + {{- end }} + {{- if .interval }} + interval: {{ .interval }} + {{- end }} + provider: + prometheus: + address: {{ .prometheusAddress | default "http://prometheus-prometheus.monitoring:9090" }} + query: | + {{ .query | nindent 12 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/generic-service/templates/controller.yaml b/charts/generic-service/templates/controller.yaml index f00ee1d..1baf756 100644 --- a/charts/generic-service/templates/controller.yaml +++ b/charts/generic-service/templates/controller.yaml @@ -32,6 +32,13 @@ spec: selector: matchLabels: {{- include "generic-service.selector-labels" . | nindent 6 }} + progressDeadlineSeconds: {{ .Values.rollout.progressDeadlineSeconds| int }} + minReadySeconds: {{ .Values.rollout.minReadySeconds | int }} + + {{- if and .Values.rollout.progressDeadlineAbort (eq .Values.rollout.controller "ArgoRollout") }} + progressDeadlineAbort: {{ .Values.rollout.progressDeadlineAbort }} + {{- end }} + strategy: {{- if eq .Values.rollout.strategy "RollingUpdate" }} {{- if eq .Values.rollout.controller "ArgoRollout" }} @@ -51,14 +58,15 @@ spec: {{- end }} type: OnDelete {{- else if eq .Values.rollout.strategy "Canary" }} - {{- if not .Values.ingress.enabled }} - {{ fail "ingress.enabled must be true if rollout.strategy is Canary" }} - {{- end }} - {{- if not .Values.ingress.domains }} - {{ fail "ingress.domains must not be empty if rollout.strategy is Canary" }} - {{- end }} {{- if eq .Values.rollout.controller "ArgoRollout" }} canary: + {{- if .Values.rollout.analysis }} + analysis: {{- .Values.rollout.analysis | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.rollout.useTrafficRouting -}} + {{- if ne .Values.rollout.strategy "Canary" }} + {{ fail "Traffic routing is only available if rollout.strategy is Canary" }} + {{- end }} stableService: {{ include "generic-service.fullname" . }} stableMetadata: labels: @@ -67,25 +75,23 @@ spec: canaryMetadata: labels: role: preview - analysis: {{- .Values.rollout.analysis | required "rollout.analysis is required if rollout.strategy is Canary" | toYaml | nindent 8 }} trafficRouting: {{- if .Values.ingress.istio.enabled }} - istio: - virtualService: - name: {{ include "generic-service.fullname" . }} - {{- else if contains "nginx" .Values.ingress.class }} - nginx: - stableIngress: {{ include "generic-service.fullname" . }} + istio: + virtualService: + name: {{ include "generic-service.fullname" . }} + {{- else if eq .Values.ingress.class "nginx" }} + nginx: + stableIngress: {{ include "generic-service.fullname" . }} {{- else }} {{ fail "Ingress must use Istio or nginx if rollout.strategy is Canary" }} {{- end }} - {{- else if not .Values.rollout.flagger }} - {{ fail "rollout.flagger must be true or rollout.controller must be ArgoRollout if rollout.strategy is Canary" }} + {{- end }} + {{- with .Values.rollout.steps }} + steps: {{- . | toYaml | nindent 8 }} + {{- end }} {{- end }} {{- else if eq .Values.rollout.strategy "BlueGreen" }} - {{- if not .Values.ingress.enabled }} - {{ fail "ingress.enabled must be true if rollout.strategy is BlueGreen" }} - {{- end }} {{- if eq .Values.rollout.controller "ArgoRollout" }} blueGreen: activeService: {{ include "generic-service.fullname" . }} @@ -97,13 +103,11 @@ spec: labels: role: preview autoPromotionEnabled: {{ .Values.rollout.autoPromotion }} - {{- if .Values.rollout.analysis }} + {{- if and (.Values.rollout.analysis) (.Values.rollout.autoPromotion) }} prePromotionAnalysis: {{- .Values.rollout.analysis | toYaml | nindent 8 }} {{- end }} - {{- else if not .Values.rollout.flagger }} - {{ fail "rollout.flagger must be true or rollout.controller must be ArgoRollout if rollout.strategy is BlueGreen" }} {{- end }} - {{ else }} + {{- else }} {{ fail "Unknown rollout.strategy" }} {{- end }} @@ -463,4 +467,3 @@ spec: {{- end }} {{- end }} {{- end }} - diff --git a/charts/generic-service/templates/service.yaml b/charts/generic-service/templates/service.yaml index 9cc5dc4..1a22472 100644 --- a/charts/generic-service/templates/service.yaml +++ b/charts/generic-service/templates/service.yaml @@ -93,7 +93,7 @@ spec: {{- end }} {{- end }} -{{- if eq .Values.rollout.controller "ArgoRollout" }} +{{- if and (eq .Values.rollout.controller "ArgoRollout") (or (eq .Values.rollout.strategy "BlueGreen") (eq .Values.rollout.useTrafficRouting true)) }} --- apiVersion: v1 kind: Service diff --git a/charts/generic-service/values.schema.json b/charts/generic-service/values.schema.json index aa66591..96029fd 100644 --- a/charts/generic-service/values.schema.json +++ b/charts/generic-service/values.schema.json @@ -301,11 +301,38 @@ "description": "Use Flagger to control rollouts (rollout.controller must be Deployment or StatefulSet)" }, "analysis": { - "description": "Flagger or Argo Rollouts analysis for automatic Canary or BlueGreen promotion" + "type": "object", + "description": "Flagger or Argo Rollouts analysis for automatic Canary promotion" + }, + "steps": { + "type": "array", + "description": "Argo Rollouts list of steps to perform during a canary rollout" + }, + "analysisTemplates": { + "type": "array", + "description": "List of analysis templates to create (only applicable if rollout.controller is ArgoRollout)" + }, + "useTrafficRouting": { + "type": "boolean", + "default": false, + "description": "Whether to use Argo Rollouts traffig routing feature" }, "revisionHistoryLimit": { "type": ["integer", "null"], "description": "Number of old ReplicaSets to retain (rollout.controller must be Deployment or ArgoRollout)" + }, + "progressDeadlineSeconds": { + "type": ["integer"], + "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed" + }, + "minReadySeconds": { + "type": ["integer"], + "description": "Minimum number of seconds for which a new pod should be ready for it to be considered available" + }, + "progressDeadlineAbort": { + "type": ["boolean"], + "default": false, + "description": "Whether to abort the update when progressDeadlineSeconds is exceeded (rollout.controller must be ArgoRollout and rollout.strategy Canary)" } }, "additionalProperties": false diff --git a/charts/generic-service/values.yaml b/charts/generic-service/values.yaml index d621d86..2aba0b2 100644 --- a/charts/generic-service/values.yaml +++ b/charts/generic-service/values.yaml @@ -63,10 +63,16 @@ resources: rollout: controller: Deployment strategy: RollingUpdate + useTrafficRouting: false autoPromotion: true flagger: false - analysis: null + analysis: {} + steps: [] + analysisTemplates: [] revisionHistoryLimit: null + progressDeadlineSeconds: 600 + minReadySeconds: 0 + progressDeadlineAbort: false replicas: 1