# Blue / Green deployment
Label the default namespace with istio-injection=enabled:
kubectl label namespace default istio-injection=enabled
Output:
namespace/default labeled
Prepare the application manifest and save it to Git repository:
cat << EOF > tmp/k8s-flux-repository/workloads/podinfo.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: podinfo
namespace: default
labels:
app: podinfo
annotations:
flux.weave.works/automated: "true"
spec:
minReadySeconds: 5
revisionHistoryLimit: 5
progressDeadlineSeconds: 60
strategy:
rollingUpdate:
maxUnavailable: 0
type: RollingUpdate
selector:
matchLabels:
app: podinfo
template:
metadata:
annotations:
prometheus.io/scrape: "true"
labels:
app: podinfo
spec:
imagePullSecrets:
- name: docker-config
containers:
- name: podinfod
image: pruzickak8smyexampledev.azurecr.io/library/podinfo:2.1.3
ports:
- containerPort: 9898
name: http
protocol: TCP
command:
- ./podinfo
- --port=9898
- --level=info
- --random-delay=false
- --random-error=false
env:
- name: PODINFO_UI_COLOR
value: green
livenessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/healthz
initialDelaySeconds: 5
timeoutSeconds: 5
readinessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/readyz
initialDelaySeconds: 5
timeoutSeconds: 5
resources:
limits:
cpu: 2000m
memory: 512Mi
requests:
cpu: 100m
memory: 64Mi
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: podinfo-gateway
namespace: default
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http-podinfo
protocol: HTTP
hosts:
- podinfo.${MY_DOMAIN}
- port:
number: 443
name: https-podinfo
protocol: HTTPS
hosts:
- podinfo.${MY_DOMAIN}
tls:
credentialName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
mode: SIMPLE
privateKey: sds
serverCertificate: sds
---
apiVersion: flagger.app/v1alpha3
kind: Canary
metadata:
name: podinfo
namespace: default
spec:
# service mesh provider (default istio)
# can be: kubernetes, istio, appmesh, smi, nginx, gloo, supergloo
# use the kubernetes provider for Blue/Green style deployments
provider: istio
# deployment reference
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
# the maximum time in seconds for the canary deployment
# to make progress before it is rollback (default 600s)
progressDeadlineSeconds: 60
service:
# container port
port: 9898
# port name can be http or grpc (default http)
portName: http
# add all the other container ports
# when generating ClusterIP services (default false)
portDiscovery: false
# Istio gateways (optional)
gateways:
- podinfo-gateway
# remove the mesh gateway if the public host is
# shared across multiple virtual services
# - mesh
# Istio virtual service host names (optional)
hosts:
- podinfo.${MY_DOMAIN}
# define the canary analysis timing and KPIs
canaryAnalysis:
# schedule interval (default 60s)
interval: 10s
# max number of failed metric checks before rollback
threshold: 10
# max traffic percentage routed to canary
# percentage (0-100)
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 5
# Prometheus checks
metrics:
- name: request-success-rate
# minimum req success rate (non 5xx responses)
# percentage (0-100)
threshold: 99
interval: 1m
- name: request-duration
# maximum req duration P99
# milliseconds
threshold: 500
interval: 30s
EOF
Push the manifest file into the git repository:
git -C tmp/k8s-flux-repository add --verbose .
git -C tmp/k8s-flux-repository commit -m "Add podinfo application"
git -C tmp/k8s-flux-repository push -q
fluxctl sync
Output:
add 'workloads/podinfo.yaml'
[master 22f3a9c] Add podinfo application
1 file changed, 98 insertions(+)
create mode 100644 workloads/podinfo.yaml
Synchronizing with git@github.com:ruzickap/k8s-flux-repository
Revision of master to apply is 22f3a9c
Waiting for 22f3a9c to be applied ...
Done.
If you open the http://podinfo.myexample.dev (opens new window) you should see the following application:

if [ -x /usr/bin/falkon ]; then falkon http://podinfo.${MY_DOMAIN} & fi
The application should be ready. Verify the canary deployment details:
kubectl describe canaries.flagger.app podinfo
Output:
Name: podinfo
Namespace: default
Labels: fluxcd.io/sync-gc-mark=sha256.suFcgXeCzNfy8IJ1JhXT7MbBGtOihk5WMiPqxL3zuOY
Annotations: fluxcd.io/sync-checksum: 82d7e7352c7d652cb326d2510831c2d82d1c48f6
kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"flagger.app/v1alpha3","kind":"Canary","metadata":{"annotations":{"fluxcd.io/sync-checksum":"82d7e7352c7d652cb326d2510831c2d...
API Version: flagger.app/v1alpha3
Kind: Canary
Metadata:
Creation Timestamp: 2019-09-19T06:32:30Z
Generation: 1
Resource Version: 7504
Self Link: /apis/flagger.app/v1alpha3/namespaces/default/canaries/podinfo
UID: 41ae5dd2-daa7-11e9-b8e0-e21b22ce44b3
Spec:
Canary Analysis:
Interval: 10s
Max Weight: 50
Metrics:
Interval: 1m
Name: request-success-rate
Threshold: 99
Interval: 30s
Name: request-duration
Threshold: 500
Step Weight: 5
Threshold: 10
Progress Deadline Seconds: 60
Provider: istio
Service:
Gateways:
podinfo-gateway
Hosts:
podinfo.myexample.dev
Port: 9898
Port Discovery: false
Port Name: http
Target Ref:
API Version: apps/v1
Kind: Deployment
Name: podinfo
Status:
Canary Weight: 0
Conditions:
Last Transition Time: 2019-09-19T06:33:07Z
Last Update Time: 2019-09-19T06:33:07Z
Message: Deployment initialization completed.
Reason: Initialized
Status: True
Type: Promoted
Failed Checks: 0
Iterations: 0
Last Applied Spec: 3370313630240737
Last Transition Time: 2019-09-19T06:33:07Z
Phase: Initialized
Tracked Configs:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Synced 40s (x3 over 59s) flagger Halt advancement podinfo-primary.default waiting for rollout to finish: 0 of 1 updated replicas are available
Normal Synced 30s flagger Initialization done! podinfo.default
The original deployment podinfo doesn't have any pods now and new deployment
podinfo-primary was created by Flagger which takes care about the traffic:
kubectl get deployment
Output:
NAME READY UP-TO-DATE AVAILABLE AGE
podinfo 0/0 0 0 7m3s
podinfo-primary 1/1 1 1 117s
There are three new services created
by Flagger - podinfo-canary and podinfo-primary:
kubectl get services
Output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 43m
podinfo ClusterIP 10.0.4.160 <none> 9898/TCP 3m38s
podinfo-canary ClusterIP 10.0.179.151 <none> 9898/TCP 3m38s
podinfo-primary ClusterIP 10.0.29.128 <none> 9898/TCP 3m38s
There is also new VirtualService:
kubectl describe virtualservices.networking.istio.io podinfo
Output:
Name: podinfo
Namespace: default
Labels: <none>
Annotations: <none>
API Version: networking.istio.io/v1alpha3
Kind: VirtualService
Metadata:
Creation Timestamp: 2019-09-19T06:32:38Z
Generation: 1
Owner References:
API Version: flagger.app/v1alpha3
Block Owner Deletion: true
Controller: true
Kind: Canary
Name: podinfo
UID: 41ae5dd2-daa7-11e9-b8e0-e21b22ce44b3
Resource Version: 7403
Self Link: /apis/networking.istio.io/v1alpha3/namespaces/default/virtualservices/podinfo
UID: 4621815f-daa7-11e9-b8e0-e21b22ce44b3
Spec:
Gateways:
podinfo-gateway
Hosts:
podinfo.myexample.dev
podinfo
Http:
Route:
Destination:
Host: podinfo-primary
Weight: 100
Destination:
Host: podinfo-canary
Weight: 0
Events: <none>
You can also see two new DestinationRules:
kubectl describe destinationrules.networking.istio.io
Output:
Name: podinfo-canary
Namespace: default
Labels: <none>
Annotations: <none>
API Version: networking.istio.io/v1alpha3
Kind: DestinationRule
Metadata:
Creation Timestamp: 2019-09-19T06:32:37Z
Generation: 1
Owner References:
API Version: flagger.app/v1alpha3
Block Owner Deletion: true
Controller: true
Kind: Canary
Name: podinfo
UID: 41ae5dd2-daa7-11e9-b8e0-e21b22ce44b3
Resource Version: 7400
Self Link: /apis/networking.istio.io/v1alpha3/namespaces/default/destinationrules/podinfo-canary
UID: 45ed89ac-daa7-11e9-b8e0-e21b22ce44b3
Spec:
Host: podinfo-canary
Events: <none>
Name: podinfo-primary
Namespace: default
Labels: <none>
Annotations: <none>
API Version: networking.istio.io/v1alpha3
Kind: DestinationRule
Metadata:
Creation Timestamp: 2019-09-19T06:32:38Z
Generation: 1
Owner References:
API Version: flagger.app/v1alpha3
Block Owner Deletion: true
Controller: true
Kind: Canary
Name: podinfo
UID: 41ae5dd2-daa7-11e9-b8e0-e21b22ce44b3
Resource Version: 7401
Self Link: /apis/networking.istio.io/v1alpha3/namespaces/default/destinationrules/podinfo-primary
UID: 460d1352-daa7-11e9-b8e0-e21b22ce44b3
Spec:
Host: podinfo-primary
Events: <none>
Configure Tekton pipelines to build new version of podinfo application:
sed -i "s/2.1.3/3.0.0/" tmp/k8s-flux-repository/workloads/tekton-pipelineresource.yaml
git -C tmp/k8s-flux-repository diff
git -C tmp/k8s-flux-repository add --verbose .
git -C tmp/k8s-flux-repository commit -m "Increase podinfo application version and build container image"
git -C tmp/k8s-flux-repository push -q
fluxctl sync
Start building new image:
sed -i "s/name: podinfo-build-docker-image-from-git-pipelinerun/name: podinfo-build-docker-image-from-git-pipelinerun-2/" tmp/k8s-flux-repository/workloads/tekton-pipelinerun.yaml
git -C tmp/k8s-flux-repository diff
git -C tmp/k8s-flux-repository add --verbose .
git -C tmp/k8s-flux-repository commit -m "Build new image and upload it to ACR"
git -C tmp/k8s-flux-repository push -q
fluxctl sync
You can see the details about the pipeline here: https://tekton-dashboard.myexample.dev/#/pipelineruns (opens new window)

if [ -x /usr/bin/falkon ]; then falkon http://tekton-dashboard.${MY_DOMAIN} & fi
When the build completes the new version of the podinfo container image will
appear in ACR:

# Canary deployment
In few minutes the new image will be stored in ACR and Flux will automatically
deploy the new image (version 3.0.0) into Kubernetes Deployment. The change
made by Flux into the "deployment" will be registered by Flagger and then the
canary deployment will start.
Open pages in browser:
if [ -x /usr/bin/falkon ]; then
falkon "http://flagger-grafana.${MY_DOMAIN}/d/flagger-istio/istio-canary?orgId=1&refresh=5s&var-namespace=default&var-primary=podinfo-primary&var-canary=podinfo" &
falkon "http://grafana.${MY_DOMAIN}/d/UbsSZTDik/istio-workload-dashboard?orgId=1&refresh=5s&var-namespace=default&var-workload=podinfo-primary&var-srcns=All&var-srcwl=All&var-dstsvc=All" &
fi
Run few commands in tmux:
tmux new-session \; \
send-keys "\
while true ; do
fluxctl list-images --workload default:deployment/podinfo ;
sleep 5 ;
done
" C-m \; \
split-window -h -p 27 \; \
send-keys "while true; do curl -s http://podinfo.${MY_DOMAIN} | jq .message; sleep 3; done" C-m \; \
split-window -v -p 50 \; \
send-keys "while true; do kubectl get canary/podinfo -o json | jq .status; sleep 2; done" C-m \; \
select-pane -t 0 \; \
split-window -v -p 50 \; \
send-keys "kubectl -n istio-system logs deployment/flagger -f | jq .msg" C-m \; \
split-window -h -p 39 \; \
send-keys "while true ; do kubectl get canaries; sleep 3; done" C-m \; \
set-option status off
Flux discovered new version of the container image in ACR and changed the deployment to use it. You should see the following on the console:

Canary deployment in progress:

Grafana showing the progress of canary deployment:

Canary deployment successfully finished:

Grafana graphs when canary deployment finished:

# Failed Canary deployment
fluxctl deautomate --workload default:deployment/podinfo
fluxctl release --workload=default:deployment/podinfo --update-image=pruzickak8smyexampledev.azurecr.io/library/podinfo:2.1.3
Run the tmux commands again:
tmux new-session \; \
send-keys "\
while true ; do
fluxctl list-images --workload default:deployment/podinfo ;
sleep 5 ;
done
" C-m \; \
split-window -h -p 27 \; \
send-keys "while true; do curl -s http://podinfo.${MY_DOMAIN} | jq .message; sleep 3; done" C-m \; \
split-window -v -p 50 \; \
send-keys "while true; do kubectl get canary/podinfo -o json | jq .status; sleep 2; done" C-m \; \
select-pane -t 0 \; \
split-window -v -p 50 \; \
send-keys "kubectl -n istio-system logs deployment/flagger -f | jq .msg" C-m \; \
split-window -h -p 39 \; \
send-keys "while true ; do kubectl get canaries; sleep 3; done" C-m \; \
set-option status off
Generate few HTTP 500 errors during the podinfo migration to new version.
The migration to new version should be stopped.
watch curl -s http://podinfo.${MY_DOMAIN}/status/500
You can see that the Flagger registered the errors (generated by curl above):

Failing canary deployment on Grafana dashboard:

Canary deployment failed:

Grafana dashboard after canary deployment failure:
