From bdc5d18f9f796dc1bf5ac6b6e8b95728afd0b1ab Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Fri, 29 Jun 2018 01:26:59 -0700 Subject: [PATCH] Add blue green deployment example app --- blue-green-deploy/.gitignore | 4 + blue-green-deploy/app.yaml | 11 ++ blue-green-deploy/argocd.yaml | 3 + .../components/bg-deploy.jsonnet | 68 ++++++++++ blue-green-deploy/components/params.libsonnet | 18 +++ blue-green-deploy/environments/base.libsonnet | 4 + .../environments/default/globals.libsonnet | 2 + .../environments/default/main.jsonnet | 8 ++ .../environments/default/params.libsonnet | 17 +++ blue-green-deploy/workflows/bluegreen.yaml | 124 ++++++++++++++++++ 10 files changed, 259 insertions(+) create mode 100644 blue-green-deploy/.gitignore create mode 100644 blue-green-deploy/app.yaml create mode 100644 blue-green-deploy/argocd.yaml create mode 100644 blue-green-deploy/components/bg-deploy.jsonnet create mode 100644 blue-green-deploy/components/params.libsonnet create mode 100644 blue-green-deploy/environments/base.libsonnet create mode 100644 blue-green-deploy/environments/default/globals.libsonnet create mode 100644 blue-green-deploy/environments/default/main.jsonnet create mode 100644 blue-green-deploy/environments/default/params.libsonnet create mode 100644 blue-green-deploy/workflows/bluegreen.yaml diff --git a/blue-green-deploy/.gitignore b/blue-green-deploy/.gitignore new file mode 100644 index 0000000..f8714d3 --- /dev/null +++ b/blue-green-deploy/.gitignore @@ -0,0 +1,4 @@ +/lib +/.ksonnet/registries +/app.override.yaml +/.ks_environment diff --git a/blue-green-deploy/app.yaml b/blue-green-deploy/app.yaml new file mode 100644 index 0000000..5178e12 --- /dev/null +++ b/blue-green-deploy/app.yaml @@ -0,0 +1,11 @@ +apiVersion: 0.1.0 +environments: + default: + destination: + namespace: default + server: https://kubernetes.default.svc + k8sVersion: v1.10.0 + path: default +kind: ksonnet.io/app +name: blue-green-deploy +version: 0.0.1 diff --git a/blue-green-deploy/argocd.yaml b/blue-green-deploy/argocd.yaml new file mode 100644 index 0000000..4e7fdb1 --- /dev/null +++ b/blue-green-deploy/argocd.yaml @@ -0,0 +1,3 @@ +workflows: +- name: blue-green + path: workflows/bluegreen.yaml diff --git a/blue-green-deploy/components/bg-deploy.jsonnet b/blue-green-deploy/components/bg-deploy.jsonnet new file mode 100644 index 0000000..bc490cc --- /dev/null +++ b/blue-green-deploy/components/bg-deploy.jsonnet @@ -0,0 +1,68 @@ +local env = std.extVar("__ksonnet/environments"); +local params = std.extVar("__ksonnet/params").components["bg-deploy"]; +[ + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": params.name, + "annotations": { + "argocd.argoproj.io/workflow-sync": "blue-green", + "argocd.argoproj.io/workflow-param.blue-green.service-name": params.name, + "argocd.argoproj.io/workflow-param.blue-green.new-service-manifest": "{{manifest}}", + }, + }, + "spec": { + "ports": [ + { + "port": params.servicePort, + "targetPort": params.containerPort + } + ], + "selector": { + "app": params.name + }, + "type": params.type + } + }, + { + "apiVersion": "apps/v1beta2", + "kind": "Deployment", + "metadata": { + "name": params.name, + "annotations": { + "argocd.argoproj.io/workflow-sync": "blue-green", + "argocd.argoproj.io/workflow-param.blue-green.deployment-name": params.name, + "argocd.argoproj.io/workflow-param.blue-green.new-deployment-manifest": "{{manifest}}", + }, + }, + "spec": { + "replicas": params.replicas, + "selector": { + "matchLabels": { + "app": params.name + }, + }, + "template": { + "metadata": { + "labels": { + "app": params.name + } + }, + "spec": { + "containers": [ + { + "image": params.image, + "name": params.name, + "ports": [ + { + "containerPort": params.containerPort + } + ] + } + ] + } + } + } + } +] diff --git a/blue-green-deploy/components/params.libsonnet b/blue-green-deploy/components/params.libsonnet new file mode 100644 index 0000000..dc89dd7 --- /dev/null +++ b/blue-green-deploy/components/params.libsonnet @@ -0,0 +1,18 @@ +{ + global: { + // User-defined global parameters; accessible to all component and environments, Ex: + // replicas: 4, + }, + components: { + // Component-level parameters, defined initially from 'ks prototype use ...' + // Each object below should correspond to a component in the components/ directory + "bg-deploy": { + containerPort: 80, + image: "gcr.io/heptio-images/ks-guestbook-demo:0.1", + name: "bg-deploy", + replicas: 3, + servicePort: 80, + type: "LoadBalancer", + }, + }, +} diff --git a/blue-green-deploy/environments/base.libsonnet b/blue-green-deploy/environments/base.libsonnet new file mode 100644 index 0000000..a129aff --- /dev/null +++ b/blue-green-deploy/environments/base.libsonnet @@ -0,0 +1,4 @@ +local components = std.extVar("__ksonnet/components"); +components + { + // Insert user-specified overrides here. +} diff --git a/blue-green-deploy/environments/default/globals.libsonnet b/blue-green-deploy/environments/default/globals.libsonnet new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/blue-green-deploy/environments/default/globals.libsonnet @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/blue-green-deploy/environments/default/main.jsonnet b/blue-green-deploy/environments/default/main.jsonnet new file mode 100644 index 0000000..58695a8 --- /dev/null +++ b/blue-green-deploy/environments/default/main.jsonnet @@ -0,0 +1,8 @@ +local base = import "base.libsonnet"; +// uncomment if you reference ksonnet-lib +// local k = import "k.libsonnet"; + +base + { + // Insert user-specified overrides here. For example if a component is named \"nginx-deployment\", you might have something like:\n") + // "nginx-deployment"+: k.deployment.mixin.metadata.labels({foo: "bar"}) +} diff --git a/blue-green-deploy/environments/default/params.libsonnet b/blue-green-deploy/environments/default/params.libsonnet new file mode 100644 index 0000000..b6eb32d --- /dev/null +++ b/blue-green-deploy/environments/default/params.libsonnet @@ -0,0 +1,17 @@ +local params = std.extVar("__ksonnet/params"); +local globals = import "globals.libsonnet"; +local envParams = params + { + components +: { + // Insert component parameter overrides here. Ex: + // guestbook +: { + // name: "guestbook-dev", + // replicas: params.global.replicas, + // }, + }, +}; + +{ + components: { + [x]: envParams.components[x] + globals, for x in std.objectFields(envParams.components) + }, +} diff --git a/blue-green-deploy/workflows/bluegreen.yaml b/blue-green-deploy/workflows/bluegreen.yaml new file mode 100644 index 0000000..a1045c0 --- /dev/null +++ b/blue-green-deploy/workflows/bluegreen.yaml @@ -0,0 +1,124 @@ +# 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, transfering traffic +# back to the original (now upgraded) deployment. +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: k8s-bluegreen- +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. + # -blue is appended to the metadata.name, as well as the values in the spec.selector.matchLabels, + # and 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}} > /tmp/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)' + /tmp/original-deploy > /tmp/cloned-deploy && + cat /tmp/cloned-deploy && + kubectl apply -o yaml -f /tmp/cloned-deploy + + - name: apply-manifest + inputs: + parameters: + - name: manifest + resource: + action: apply + manifest: '{{inputs.parameters.manifest}}' + + - 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}}'] + + - 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 > /tmp/original-svc && + jq '.spec.selector = (.spec.selector | with_entries(.value+="-blue"))' /tmp/original-svc > /tmp/blue-svc && + kubectl apply -o yaml -f /tmp/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"]