# This workflow performs a "blue-green" deployment, while preserving the original deployment object # and name. It accomplishes this by temporarily redirecting traffic to a *clone* of the original # deployment. Then after upgrading the original deployment to a later version, redirects traffic # back to the original (now upgraded) deployment. apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: k8s-bluegreen- annotations: argocd.argoproj.io/hook: Sync spec: entrypoint: k8s-bluegreen arguments: parameters: - name: deployment-name - name: service-name - name: new-deployment-manifest - name: new-service-manifest templates: - name: k8s-bluegreen steps: # 1. Create a parallel Kubernetes deployment with tweaks to name and selectors - - name: create-blue-deployment template: clone-deployment # 2. Wait for parallel deployment to become ready - - name: wait-for-blue-deployment template: wait-deployment-ready arguments: parameters: - name: deployment-name value: '{{workflow.parameters.deployment-name}}-blue' # 3. Patch the named service to point to the parallel deployment app - - name: switch-service-to-blue-deployment template: patch-service # 4. Update the original deployment (receiving no traffic) with a new version - - name: apply-green-deployment template: apply-manifest arguments: parameters: - name: manifest value: '{{workflow.parameters.new-deployment-manifest}}' # 5. Wait for the original deployment, now updated, to become ready - - name: wait-for-green-deployment template: wait-deployment-ready arguments: parameters: - name: deployment-name value: '{{workflow.parameters.deployment-name}}' # dummy approval step for demo purposes. Sleeps for 30 seconds - - name: approve template: approve # 6. Patch the named service to point to the original, now updated app - - name: switch-service-to-green-deployment template: apply-manifest arguments: parameters: - name: manifest value: '{{workflow.parameters.new-service-manifest}}' # 7. Remove the cloned deployment (no longer receiving traffic) - - name: delete-cloned-deployment template: delete-deployment # end of steps # clone-deployment creates a "blue" clone of an existing deployment. The string -blue is appended to: # - metadata.name # - spec.selector.matchLabels # - spec.template.metadata.labels - name: clone-deployment container: image: argoproj/argoexec:v2.1.1 command: [sh, -c, -x] args: - kubectl get --export -o json deployment.apps/{{workflow.parameters.deployment-name}} > /original-deploy && jq -r '.metadata.name+="-blue" | .spec.template.metadata.labels += (.spec.template.metadata.labels | to_entries | map(select(.key != "applications.argoproj.io/app-name")) | map(.value+="-blue") | from_entries) | .spec.selector.matchLabels += (.spec.selector.matchLabels | to_entries | map(select(.key != "applications.argoproj.io/app-name")) | map(.value+="-blue") | from_entries)' /original-deploy > /cloned-deploy && cat /cloned-deploy && kubectl apply -o yaml -f /cloned-deploy # apply-manifest takes a kubernetes manifest and carrys over the app-name label (if present) # before running `kubectl apply`. The label is used by ArgoCD for monitoring. - name: apply-manifest inputs: parameters: - name: manifest artifacts: - name: manifest-file path: /manifest raw: data: '{{inputs.parameters.manifest}}' container: image: argoproj/argoexec:v2.1.1 command: [sh, -c, -x] args: - cp /manifest /manifest-new && APP_NAME=$(kubectl get -n default -f /manifest-new -o json | jq -r '.metadata.labels."applications.argoproj.io/app-name"') && if [ "$APP_NAME" != "null" ]; then jq -r --arg APP_NAME "$APP_NAME" '.metadata.labels."applications.argoproj.io/app-name" = $APP_NAME' /manifest-new > /manifest-new.tmp && mv /manifest-new.tmp /manifest-new && if [ "$(jq -r .spec.template.metadata.labels /manifest-new)" != "null" ]; then jq -r --arg APP_NAME "$APP_NAME" '.spec.template.metadata.labels."applications.argoproj.io/app-name" = $APP_NAME' /manifest-new > /manifest-new.tmp && mv /manifest-new.tmp /manifest-new ; fi ; fi && cat /manifest-new && kubectl apply -f /manifest-new # wait-deployment-ready waits for a deployment to become fully deployed and ready, using the # `kubectl rollout` command - name: wait-deployment-ready inputs: parameters: - name: deployment-name container: image: argoproj/argoexec:v2.1.1 command: [kubectl, rollout, status, --watch=true, 'deployments/{{inputs.parameters.deployment-name}}'] # patch-service updates the service selector labels to point to the "blue" deployment - name: patch-service container: image: argoproj/argoexec:v2.1.1 command: [sh, -c, -x] args: - kubectl get -n default service {{workflow.parameters.service-name}} --export -o json > /original-svc && jq '.spec.selector = (.spec.selector | with_entries(.value+="-blue"))' /original-svc > /blue-svc && kubectl apply -o yaml -f /blue-svc - name: delete-deployment resource: action: delete manifest: | apiVersion: apps/v1 kind: Deployment metadata: name: {{workflow.parameters.deployment-name}}-blue - name: approve container: image: argoproj/argoexec:v2.1.1 command: [sleep, "30"]