Add blue green deployment example app

master bg-guestbook-v0.1
Jesse Suen 2018-06-29 01:26:59 -07:00
parent 852d97d96e
commit b9eec94e60
No known key found for this signature in database
GPG Key ID: 90C911E8A6106562
10 changed files with 299 additions and 0 deletions

4
blue-green-deploy/.gitignore vendored Normal file
View File

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

View File

@ -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

View File

@ -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
}
]
}
]
}
}
}
}
]

View File

@ -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)},
],
},
},
}
]

View File

@ -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": {},
},
}

View File

@ -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"]

View File

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

View File

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

View File

@ -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"})
}

View File

@ -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)
},
}