Jelajahi Sumber

Add blue green deployment example app

Jesse Suen 6 tahun lalu
induk
melakukan
b9eec94e60

+ 4 - 0
blue-green-deploy/.gitignore

@@ -0,0 +1,4 @@
+/lib
+/.ksonnet/registries
+/app.override.yaml
+/.ks_environment

+ 11 - 0
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

+ 64 - 0
blue-green-deploy/components/bg-guestbook.jsonnet

@@ -0,0 +1,64 @@
+local env = std.extVar("__ksonnet/environments");
+local params = std.extVar("__ksonnet/params").components["bg-guestbook"];
+[
+   {
+      "apiVersion": "v1",
+      "kind": "Service",
+      "metadata": {
+         "name": params.name,
+         "annotations": {
+             "argocd.argoproj.io/hook": "Skip",
+         },
+      },
+      "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/hook": "Skip",
+         },
+      },
+      "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
+                     }
+                     ]
+                  }
+               ]
+            }
+         }
+      }
+   }
+]

+ 20 - 0
blue-green-deploy/components/bg-workflow.jsonnet

@@ -0,0 +1,20 @@
+local bgGuestbook = std.extVar("__ksonnet/components")["bg-guestbook"];
+local bgGuestbookSvc = bgGuestbook[0];
+local bgGuestbookDeploy = bgGuestbook[1];
+local parseYaml = std.native("parseYaml");
+local bgWorkflow = parseYaml(importstr 'wf/bluegreen.yaml')[0];
+
+[
+  bgWorkflow + {
+    spec +: {
+      arguments +: {
+        parameters : [
+          {name: "deployment-name", value: bgGuestbookDeploy.metadata.name},
+          {name: "service-name", value: bgGuestbookSvc.metadata.name},
+          {name: "new-deployment-manifest", value: std.manifestJson(bgGuestbookDeploy)},
+          {name: "new-service-manifest", value: std.manifestJson(bgGuestbookSvc)},
+        ],
+      },
+    },
+  }
+]

+ 19 - 0
blue-green-deploy/components/params.libsonnet

@@ -0,0 +1,19 @@
+{
+  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-guestbook": {
+      containerPort: 80,
+      image: "gcr.io/heptio-images/ks-guestbook-demo:0.1",
+      name: "bg-guestbook",
+      replicas: 3,
+      servicePort: 80,
+      type: "LoadBalancer",
+    },
+    "bg-workflow": {},
+  },
+}

+ 150 - 0
blue-green-deploy/components/wf/bluegreen.yaml

@@ -0,0 +1,150 @@
+# 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"]

+ 4 - 0
blue-green-deploy/environments/base.libsonnet

@@ -0,0 +1,4 @@
+local components = std.extVar("__ksonnet/components");
+components + {
+  // Insert user-specified overrides here.
+}

+ 2 - 0
blue-green-deploy/environments/default/globals.libsonnet

@@ -0,0 +1,2 @@
+{
+}

+ 8 - 0
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"})
+}

+ 17 - 0
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)
+  },
+}