diff --git a/404.html b/404.html index a196c00c..6c00ac34 100644 --- a/404.html +++ b/404.html @@ -7,17 +7,17 @@ - + - + -

404

There's nothing here.
Take me home.
- +

404

That's a Four-Oh-Four.
Take me home.
+ diff --git a/assets/js/10.bf6f38d4.js b/assets/js/10.bf6f38d4.js new file mode 100644 index 00000000..336f8d72 --- /dev/null +++ b/assets/js/10.bf6f38d4.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[10],{270:function(t,e,n){"use strict";n.r(e);var s=n(37),l=Object(s.a)({},function(){var t=this.$createElement;return(this._self._c||t)("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}})},[],!1,null,null,null);e.default=l.exports}}]); \ No newline at end of file diff --git a/assets/js/11.3af38cfe.js b/assets/js/11.3af38cfe.js new file mode 100644 index 00000000..d5503238 --- /dev/null +++ b/assets/js/11.3af38cfe.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[11],{271:function(a,e,s){"use strict";s.r(e);var t=s(37),r=Object(t.a)({},function(){var a=this,e=a.$createElement,s=a._self._c||e;return s("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[s("h1",{attrs:{id:"flagger-install-on-eks-app-mesh"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#flagger-install-on-eks-app-mesh","aria-hidden":"true"}},[a._v("#")]),a._v(" Flagger Install on EKS App Mesh")]),a._v(" "),s("p",[a._v("This guide walks you through setting up Flagger and AWS App Mesh on EKS.")]),a._v(" "),s("h2",{attrs:{id:"app-mesh"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#app-mesh","aria-hidden":"true"}},[a._v("#")]),a._v(" App Mesh")]),a._v(" "),s("p",[a._v("The App Mesh integration with EKS is made out of the following components:")]),a._v(" "),s("ul",[s("li",[a._v("Kubernetes custom resources\n"),s("ul",[s("li",[s("code",[a._v("mesh.appmesh.k8s.aws")]),a._v(" defines a logical boundary for network traffic between the services")]),a._v(" "),s("li",[s("code",[a._v("virtualnode.appmesh.k8s.aws")]),a._v(" defines a logical pointer to a Kubernetes workload")]),a._v(" "),s("li",[s("code",[a._v("virtualservice.appmesh.k8s.aws")]),a._v(" defines the routing rules for a workload inside the mesh")])])]),a._v(" "),s("li",[a._v("CRD controller - keeps the custom resources in sync with the App Mesh control plane")]),a._v(" "),s("li",[a._v("Admission controller - injects the Envoy sidecar and assigns Kubernetes pods to App Mesh virtual nodes")]),a._v(" "),s("li",[a._v("Telemetry service - Prometheus instance that collects and stores Envoy's metrics")])]),a._v(" "),s("h2",{attrs:{id:"create-a-kubernetes-cluster"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#create-a-kubernetes-cluster","aria-hidden":"true"}},[a._v("#")]),a._v(" Create a Kubernetes cluster")]),a._v(" "),s("p",[a._v("In order to create an EKS cluster you can use "),s("a",{attrs:{href:"https://eksctl.io",target:"_blank",rel:"noopener noreferrer"}},[a._v("eksctl"),s("OutboundLink")],1),a._v(". Eksctl is an open source command-line utility made by Weaveworks in collaboration with Amazon.")]),a._v(" "),s("p",[a._v("On MacOS you can install eksctl with Homebrew:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("brew tap weaveworks/tap\nbrew "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("install")]),a._v(" weaveworks/tap/eksctl\n")])])]),s("p",[a._v("Create an EKS cluster:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("eksctl create cluster --name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("appmesh "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--region"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("us-west-2 "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--nodes "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("3")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--node-volume-size"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("120")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--appmesh-access\n")])])]),s("p",[a._v("The above command will create a two nodes cluster with App Mesh "),s("a",{attrs:{href:"https://docs.aws.amazon.com/app-mesh/latest/userguide/MESH_IAM_user_policies.html",target:"_blank",rel:"noopener noreferrer"}},[a._v("IAM policy"),s("OutboundLink")],1),a._v(" attached to the EKS node instance role.")]),a._v(" "),s("p",[a._v("Verify the install with:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl get nodes\n")])])]),s("h2",{attrs:{id:"install-helm"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#install-helm","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Helm")]),a._v(" "),s("p",[a._v("Install the "),s("a",{attrs:{href:"https://docs.helm.sh/using_helm/#installing-helm",target:"_blank",rel:"noopener noreferrer"}},[a._v("Helm"),s("OutboundLink")],1),a._v(" command-line tool:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("brew install kubernetes-helm\n")])])]),s("p",[a._v("Create a service account and a cluster role binding for Tiller:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n kube-system create sa tiller\n\nkubectl create clusterrolebinding tiller-cluster-rule "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--clusterrole"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("cluster-admin "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--serviceaccount"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("kube-system:tiller\n")])])]),s("p",[a._v("Deploy Tiller in the "),s("code",[a._v("kube-system")]),a._v(" namespace:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm init --service-account tiller\n")])])]),s("p",[a._v("You should consider using SSL between Helm and Tiller, for more information on securing your Helm installation see "),s("a",{attrs:{href:"https://docs.helm.sh/using_helm/#securing-your-helm-installation",target:"_blank",rel:"noopener noreferrer"}},[a._v("docs.helm.sh"),s("OutboundLink")],1),a._v(".")]),a._v(" "),s("h2",{attrs:{id:"enable-horizontal-pod-auto-scaling"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#enable-horizontal-pod-auto-scaling","aria-hidden":"true"}},[a._v("#")]),a._v(" Enable horizontal pod auto-scaling")]),a._v(" "),s("p",[a._v("Install the Horizontal Pod Autoscaler (HPA) metrics provider:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i metrics-server stable/metrics-server "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace kube-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set args"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("[")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("]")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("--kubelet-preferred-address-types"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("InternalIP\n")])])]),s("p",[a._v("After a minute, the metrics API should report CPU and memory usage for pods. You can very the metrics API with:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n kube-system "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("top")]),a._v(" pods\n")])])]),s("h2",{attrs:{id:"install-the-app-mesh-components"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#install-the-app-mesh-components","aria-hidden":"true"}},[a._v("#")]),a._v(" Install the App Mesh components")]),a._v(" "),s("p",[a._v("Create the "),s("code",[a._v("appmesh-system")]),a._v(" namespace:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl create ns appmesh-system\n")])])]),s("p",[a._v("Apply the App Mesh CRDs:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl apply -k github.com/aws/eks-charts/stable/appmesh-controller//crds\n")])])]),s("p",[a._v("Add the EKS repository to Helm:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm repo "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" eks https://aws.github.io/eks-charts\n")])])]),s("p",[a._v("Install the App Mesh CRD controller:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i appmesh-controller eks/appmesh-controller "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--wait --namespace appmesh-system\n")])])]),s("p",[a._v("Install the App Mesh admission controller and create a mesh called "),s("code",[a._v("global")]),a._v(":")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i appmesh-inject eks/appmesh-inject "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--wait --namespace appmesh-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set mesh.create"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("true "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set mesh.name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("global\n")])])]),s("p",[a._v("Verify that the global mesh is active:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl describe mesh\n\nStatus:\n Mesh Condition:\n Status: True\n Type: MeshActive\n")])])]),s("p",[a._v("In order to collect the App Mesh metrics that Flagger needs to run the canary analysis, you'll need to setup a Prometheus instance to scrape the Envoy sidecars.")]),a._v(" "),s("p",[a._v("Install the App Mesh Prometheus:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i appmesh-prometheus eks/appmesh-prometheus "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--wait --namespace appmesh-system\n")])])]),s("h2",{attrs:{id:"install-flagger-and-grafana"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#install-flagger-and-grafana","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Flagger and Grafana")]),a._v(" "),s("p",[a._v("Add Flagger Helm repository:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm repo "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" flagger https://flagger.app\n")])])]),s("p",[a._v("Install Flagger's Canary CRD:")]),a._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[a._v("kubectl apply "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("f https"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//raw.githubusercontent.com/weaveworks/flagger/master/artifacts/flagger/crd.yaml\n")])])]),s("p",[a._v("Deploy Flagger in the "),s("em",[s("strong",[a._v("appmesh-system")])]),a._v(" namespace:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i flagger flagger/flagger "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("appmesh-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set crd.create"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("false "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshProvider")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("appmesh "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("metricsServer")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://appmesh-prometheus:9090\n")])])]),s("p",[a._v("You can enable Slack or MS Teams notifications with:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i flagger flagger/flagger "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--reuse-values "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("appmesh-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.url"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.channel"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("general "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.user"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("flagger\n")])])]),s("p",[a._v("Flagger comes with a Grafana dashboard made for monitoring the canary analysis. Deploy Grafana in the "),s("em",[s("strong",[a._v("appmesh-system")])]),a._v(" namespace:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i flagger-grafana flagger/grafana "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("appmesh-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("url")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://appmesh-prometheus:9090\n")])])]),s("p",[a._v("You can access Grafana using port forwarding:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n appmesh-system port-forward svc/flagger-grafana "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("3000")]),a._v(":80\n")])])]),s("p",[a._v("Now that you have Flagger running you can try the "),s("a",{attrs:{href:"https://docs.flagger.app/usage/appmesh-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[a._v("App Mesh canary deployments tutorial"),s("OutboundLink")],1),a._v(".")])])},[],!1,null,null,null);e.default=r.exports}}]); \ No newline at end of file diff --git a/assets/js/12.eca4b89c.js b/assets/js/12.eca4b89c.js new file mode 100644 index 00000000..b15a1dcf --- /dev/null +++ b/assets/js/12.eca4b89c.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[12],{272:function(a,t,s){"use strict";s.r(t);var e=s(37),n=Object(e.a)({},function(){var a=this,t=a.$createElement,s=a._self._c||t;return s("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[s("h1",{attrs:{id:"flagger-install-on-gke-istio"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#flagger-install-on-gke-istio","aria-hidden":"true"}},[a._v("#")]),a._v(" Flagger Install on GKE Istio")]),a._v(" "),s("p",[a._v("This guide walks you through setting up Flagger and Istio on Google Kubernetes Engine.")]),a._v(" "),s("p",[s("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-gke-istio.png",alt:"GKE Cluster Overview"}})]),a._v(" "),s("h2",{attrs:{id:"prerequisites"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[a._v("#")]),a._v(" Prerequisites")]),a._v(" "),s("p",[a._v("You will be creating a cluster on Google’s Kubernetes Engine (GKE), if you don’t have an account you can sign up "),s("a",{attrs:{href:"https://cloud.google.com/free/",target:"_blank",rel:"noopener noreferrer"}},[a._v("here"),s("OutboundLink")],1),a._v(" for free credits.")]),a._v(" "),s("p",[a._v("Login into Google Cloud, create a project and enable billing for it.")]),a._v(" "),s("p",[a._v("Install the "),s("a",{attrs:{href:"https://cloud.google.com/sdk/",target:"_blank",rel:"noopener noreferrer"}},[a._v("gcloud"),s("OutboundLink")],1),a._v(" command line utility and configure your project with "),s("code",[a._v("gcloud init")]),a._v(".")]),a._v(" "),s("p",[a._v("Set the default project (replace "),s("code",[a._v("PROJECT_ID")]),a._v(" with your own project):")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("gcloud config set project PROJECT_ID\n")])])]),s("p",[a._v("Set the default compute region and zone:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("gcloud config set compute/region us-central1\ngcloud config set compute/zone us-central1-a\n")])])]),s("p",[a._v("Enable the Kubernetes and Cloud DNS services for your project:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("gcloud services enable container.googleapis.com\ngcloud services enable dns.googleapis.com\n")])])]),s("p",[a._v("Install the kubectl command-line tool:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("gcloud components install kubectl\n")])])]),s("h2",{attrs:{id:"gke-cluster-setup"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#gke-cluster-setup","aria-hidden":"true"}},[a._v("#")]),a._v(" GKE cluster setup")]),a._v(" "),s("p",[a._v("Create a cluster with the Istio add-on:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("K8S_VERSION")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token variable"}},[s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("$(")]),a._v("gcloud container get-server-config --format"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("json "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("|")]),a._v(" jq -r "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v("'.validMasterVersions[0]'")]),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v(")")])]),a._v("\n\ngcloud beta container clusters create istio "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--cluster-version"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${K8S_VERSION}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--zone"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("us-central1-a "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--num-nodes"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("2")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--machine-type"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("n1-highcpu-4 "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--preemptible "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--no-enable-cloud-logging "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--no-enable-cloud-monitoring "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--disk-size"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("30")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--enable-autorepair "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--addons"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("HorizontalPodAutoscaling,Istio "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--istio-config"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("auth"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("MTLS_PERMISSIVE\n")])])]),s("p",[a._v("The above command will create a default node pool consisting of two "),s("code",[a._v("n1-highcpu-4")]),a._v(" (vCPU: 4, RAM 3.60GB, DISK: 30GB) preemptible VMs. Preemptible VMs are up to 80% cheaper than regular instances and are terminated and replaced after a maximum of 24 hours.")]),a._v(" "),s("p",[a._v("Set up credentials for "),s("code",[a._v("kubectl")]),a._v(":")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("gcloud container clusters get-credentials istio\n")])])]),s("p",[a._v("Create a cluster admin role binding:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl create clusterrolebinding "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"cluster-admin-'),s("span",{pre:!0,attrs:{class:"token variable"}},[s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("$(")]),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("whoami")]),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v(")")])]),a._v('"')]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--clusterrole"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("cluster-admin "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--user"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"'),s("span",{pre:!0,attrs:{class:"token variable"}},[s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("$(")]),a._v("gcloud config get-value core/account"),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v(")")])]),a._v('"')]),a._v("\n")])])]),s("p",[a._v("Validate your setup with:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n istio-system get svc\n")])])]),s("p",[a._v("In a couple of seconds GCP should allocate an external IP to the "),s("code",[a._v("istio-ingressgateway")]),a._v(" service.")]),a._v(" "),s("h2",{attrs:{id:"cloud-dns-setup"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#cloud-dns-setup","aria-hidden":"true"}},[a._v("#")]),a._v(" Cloud DNS setup")]),a._v(" "),s("p",[a._v("You will need an internet domain and access to the registrar to change the name servers to Google Cloud DNS.")]),a._v(" "),s("p",[a._v("Create a managed zone named "),s("code",[a._v("istio")]),a._v(" in Cloud DNS (replace "),s("code",[a._v("example.com")]),a._v(" with your domain):")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("gcloud dns managed-zones create "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--dns-name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"example.com."')]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--description"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"Istio zone"')]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"istio"')]),a._v("\n")])])]),s("p",[a._v("Look up your zone's name servers:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("gcloud dns managed-zones describe istio\n")])])]),s("p",[a._v("Update your registrar's name server records with the records returned by the above command.")]),a._v(" "),s("p",[a._v("Wait for the name servers to change (replace "),s("code",[a._v("example.com")]),a._v(" with your domain):")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("dig")]),a._v(" +short NS example.com\n")])])]),s("p",[a._v("Create a static IP address named "),s("code",[a._v("istio-gateway")]),a._v(" using the Istio ingress IP:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("export")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("GATEWAY_IP")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token variable"}},[s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("$(")]),a._v("kubectl -n istio-system get svc/istio-ingressgateway -ojson "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("|")]),a._v(" jq -r .status.loadBalancer.ingress"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("[")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("]")]),a._v(".ip"),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v(")")])]),a._v("\n\ngcloud compute addresses create istio-gateway --addresses "),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${GATEWAY_IP}")]),a._v(" --region us-central1\n")])])]),s("p",[a._v("Create the following DNS records (replace "),s("code",[a._v("example.com")]),a._v(" with your domain):")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("DOMAIN")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"example.com"')]),a._v("\n\ngcloud dns record-sets transaction start --zone"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio\n\ngcloud dns record-sets transaction "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" --zone"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"'),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${DOMAIN}")]),a._v('"')]),a._v(" --ttl"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("300")]),a._v(" --type"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("A "),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${GATEWAY_IP}")]),a._v("\n\ngcloud dns record-sets transaction "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" --zone"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"www.'),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${DOMAIN}")]),a._v('"')]),a._v(" --ttl"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("300")]),a._v(" --type"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("A "),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${GATEWAY_IP}")]),a._v("\n\ngcloud dns record-sets transaction "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" --zone"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"*.'),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${DOMAIN}")]),a._v('"')]),a._v(" --ttl"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("300")]),a._v(" --type"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("A "),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${GATEWAY_IP}")]),a._v("\n\ngcloud dns record-sets transaction execute --zone istio\n")])])]),s("p",[a._v("Verify that the wildcard DNS is working (replace "),s("code",[a._v("example.com")]),a._v(" with your domain):")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("host")]),a._v(" test.example.com\n")])])]),s("h2",{attrs:{id:"install-helm"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#install-helm","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Helm")]),a._v(" "),s("p",[a._v("Install the "),s("a",{attrs:{href:"https://docs.helm.sh/using_helm/#installing-helm",target:"_blank",rel:"noopener noreferrer"}},[a._v("Helm"),s("OutboundLink")],1),a._v(" command-line tool:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("brew install kubernetes-helm\n")])])]),s("p",[a._v("Create a service account and a cluster role binding for Tiller:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n kube-system create sa tiller\n\nkubectl create clusterrolebinding tiller-cluster-rule "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--clusterrole"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("cluster-admin "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--serviceaccount"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("kube-system:tiller\n")])])]),s("p",[a._v("Deploy Tiller in the "),s("code",[a._v("kube-system")]),a._v(" namespace:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm init --service-account tiller\n")])])]),s("p",[a._v("You should consider using SSL between Helm and Tiller, for more information on securing your Helm installation see "),s("a",{attrs:{href:"https://docs.helm.sh/using_helm/#securing-your-helm-installation",target:"_blank",rel:"noopener noreferrer"}},[a._v("docs.helm.sh"),s("OutboundLink")],1),a._v(".")]),a._v(" "),s("h2",{attrs:{id:"install-cert-manager"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#install-cert-manager","aria-hidden":"true"}},[a._v("#")]),a._v(" Install cert-manager")]),a._v(" "),s("p",[a._v("Jetstack's "),s("a",{attrs:{href:"https://github.com/jetstack/cert-manager",target:"_blank",rel:"noopener noreferrer"}},[a._v("cert-manager"),s("OutboundLink")],1),a._v(" is a Kubernetes operator that automatically creates and manages TLS certs issued by Let’s Encrypt.")]),a._v(" "),s("p",[a._v("You'll be using cert-manager to provision a wildcard certificate for the Istio ingress gateway.")]),a._v(" "),s("p",[a._v("Install cert-manager's CRDs:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("CERT_REPO")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://raw.githubusercontent.com/jetstack/cert-manager\n\nkubectl apply -f "),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${CERT_REPO}")]),a._v("/release-0.10/deploy/manifests/00-crds.yaml\n")])])]),s("p",[a._v("Create the cert-manager namespace and disable resource validation:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl create namespace cert-manager\n\nkubectl label namespace cert-manager certmanager.k8s.io/disable-validation"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("true\n")])])]),s("p",[a._v("Install cert-manager with Helm:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm repo "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" jetstack https://charts.jetstack.io "),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("&&")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\nhelm repo update "),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("&&")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\nhelm upgrade -i cert-manager "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace cert-manager "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--version v0.10.0 "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\njetstack/cert-manager\n")])])]),s("h2",{attrs:{id:"istio-gateway-tls-setup"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#istio-gateway-tls-setup","aria-hidden":"true"}},[a._v("#")]),a._v(" Istio Gateway TLS setup")]),a._v(" "),s("p",[s("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/istio-cert-manager-gke.png",alt:"Istio Let's Encrypt"}})]),a._v(" "),s("p",[a._v("Create a generic Istio Gateway to expose services outside the mesh on HTTPS:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("REPO")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://raw.githubusercontent.com/weaveworks/flagger/master\n\nkubectl apply -f "),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${REPO}")]),a._v("/artifacts/gke/istio-gateway.yaml\n")])])]),s("p",[a._v("Create a service account with Cloud DNS admin role (replace "),s("code",[a._v("my-gcp-project")]),a._v(" with your project ID):")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("GCP_PROJECT")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("my-gcp-project\n\ngcloud iam service-accounts create dns-admin "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--display-name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("dns-admin "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--project"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${GCP_PROJECT}")]),a._v("\n\ngcloud iam service-accounts keys create ./gcp-dns-admin.json "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--iam-account"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("dns-admin@"),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${GCP_PROJECT}")]),a._v(".iam.gserviceaccount.com "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--project"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${GCP_PROJECT}")]),a._v("\n\ngcloud projects add-iam-policy-binding "),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${GCP_PROJECT}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--member"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("serviceAccount:dns-admin@"),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("${GCP_PROJECT}")]),a._v(".iam.gserviceaccount.com "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--role"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("roles/dns.admin\n")])])]),s("p",[a._v("Create a Kubernetes secret with the GCP Cloud DNS admin key:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl create secret generic cert-manager-credentials "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--from-file"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("./gcp-dns-admin.json "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system\n")])])]),s("p",[a._v("Create a letsencrypt issuer for CloudDNS (replace "),s("code",[a._v("email@example.com")]),a._v(" with a valid email address and "),s("code",[a._v("my-gcp-project")]),a._v("with your project ID):")]),a._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" certmanager.k8s.io/v1alpha1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Issuer\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" letsencrypt"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("prod\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("system\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("acme")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("server")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" https"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//acme"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("v02.api.letsencrypt.org/directory\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("email")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" email@example.com\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("privateKeySecretRef")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" letsencrypt"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("prod\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("dns01")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("providers")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" cloud"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("dns\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("clouddns")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("serviceAccountSecretRef")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" cert"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("manager"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("credentials\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("key")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" gcp"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("dns"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("admin.json\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("project")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" my"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("gcp"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("project\n")])])]),s("p",[a._v("Save the above resource as letsencrypt-issuer.yaml and then apply it:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("kubectl apply -f ./letsencrypt-issuer.yaml\n")])])]),s("p",[a._v("Create a wildcard certificate (replace "),s("code",[a._v("example.com")]),a._v(" with your domain):")]),a._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" certmanager.k8s.io/v1alpha1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Certificate\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("gateway\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("system\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("secretName")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("ingressgateway"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("certs\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("issuerRef")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" letsencrypt"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("prod\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("commonName")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"*.example.com"')]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("acme")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("config")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("dns01")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("provider")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" cloud"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("dns\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("domains")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"*.example.com"')]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"example.com"')]),a._v("\n")])])]),s("p",[a._v("Save the above resource as istio-gateway-cert.yaml and then apply it:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("kubectl apply -f ./istio-gateway-cert.yaml\n")])])]),s("p",[a._v("In a couple of seconds cert-manager should fetch a wildcard certificate from letsencrypt.org:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("kubectl -n istio-system describe certificate istio-gateway\n\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal CertIssued 1m52s cert-manager Certificate issued successfully\n")])])]),s("p",[a._v("Recreate Istio ingress gateway pods:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n istio-system get pods -l "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("istio")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("ingressgateway\n")])])]),s("p",[a._v("Note that Istio gateway doesn't reload the certificates from the TLS secret on cert-manager renewal. Since the GKE cluster is made out of preemptible VMs the gateway pods will be replaced once every 24h, if your not using preemptible nodes then you need to manually delete the gateway pods every two months before the certificate expires.")]),a._v(" "),s("h2",{attrs:{id:"install-prometheus"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#install-prometheus","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Prometheus")]),a._v(" "),s("p",[a._v("The GKE Istio add-on does not include a Prometheus instance that scrapes the Istio telemetry service. Because Flagger uses the Istio HTTP metrics to run the canary analysis you have to deploy the following Prometheus configuration that's similar to the one that comes with the official Istio Helm chart.")]),a._v(" "),s("p",[a._v("Find the GKE Istio version with:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n istio-system get deploy istio-pilot -oyaml "),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("|")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("grep")]),a._v(" image:\n")])])]),s("p",[a._v("Install Prometheus in istio-system namespace:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n istio-system apply -f "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\nhttps://storage.googleapis.com/gke-release/istio/release/1.0.6-gke.3/patches/install-prometheus.yaml\n")])])]),s("h2",{attrs:{id:"install-flagger-and-grafana"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#install-flagger-and-grafana","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Flagger and Grafana")]),a._v(" "),s("p",[a._v("Add Flagger Helm repository:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm repo "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" flagger https://flagger.app\n")])])]),s("p",[a._v("Install Flagger's Canary CRD:")]),a._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[a._v("kubectl apply "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("f https"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//raw.githubusercontent.com/weaveworks/flagger/master/artifacts/flagger/crd.yaml\n")])])]),s("p",[a._v("Deploy Flagger in the "),s("code",[a._v("istio-system")]),a._v(" namespace with Slack notifications enabled:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i flagger flagger/flagger "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set crd.create"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("false "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("metricsServer")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://prometheus.istio-system:9090 "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.url"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.channel"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("general "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.user"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("flagger\n")])])]),s("p",[a._v("Deploy Grafana in the "),s("code",[a._v("istio-system")]),a._v(" namespace:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i flagger-grafana flagger/grafana "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("url")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://prometheus.istio-system:9090 "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("user")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("admin "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("password")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("replace-me\n")])])]),s("p",[a._v("Expose Grafana through the public gateway by creating a virtual service (replace "),s("code",[a._v("example.com")]),a._v(" with your domain):")]),a._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" networking.istio.io/v1alpha3\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" VirtualService\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" grafana\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("system\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("hosts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"grafana.example.com"')]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("gateways")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" public"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("gateway.istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("system.svc.cluster.local\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("http")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("route")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("destination")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("host")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("grafana\n")])])]),s("p",[a._v("Save the above resource as grafana-virtual-service.yaml and then apply it:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl apply -f ./grafana-virtual-service.yaml\n")])])]),s("p",[a._v("Navigate to "),s("code",[a._v("http://grafana.example.com")]),a._v(" in your browser and you should be redirected to the HTTPS version.")])])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/13.9104349a.js b/assets/js/13.9104349a.js new file mode 100644 index 00000000..e3f22559 --- /dev/null +++ b/assets/js/13.9104349a.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[13],{273:function(a,e,t){"use strict";t.r(e);var s=t(37),r=Object(s.a)({},function(){var a=this,e=a.$createElement,t=a._self._c||e;return t("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[t("h1",{attrs:{id:"flagger-install-on-kubernetes"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#flagger-install-on-kubernetes","aria-hidden":"true"}},[a._v("#")]),a._v(" Flagger Install on Kubernetes")]),a._v(" "),t("p",[a._v("This guide walks you through setting up Flagger on a Kubernetes cluster with Helm v3 or Kustomize.")]),a._v(" "),t("h2",{attrs:{id:"prerequisites"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[a._v("#")]),a._v(" Prerequisites")]),a._v(" "),t("p",[a._v("Flagger requires a Kubernetes cluster "),t("strong",[a._v("v1.11")]),a._v(" or newer.")]),a._v(" "),t("h2",{attrs:{id:"install-flagger-with-helm"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#install-flagger-with-helm","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Flagger with Helm")]),a._v(" "),t("p",[a._v("Add Flagger Helm repository:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("helm repo "),t("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" flagger https://flagger.app\n")])])]),t("p",[a._v("Install Flagger's Canary CRD:")]),a._v(" "),t("div",{staticClass:"language-yaml extra-class"},[t("pre",{pre:!0,attrs:{class:"language-yaml"}},[t("code",[a._v("kubectl apply "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("f https"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//raw.githubusercontent.com/weaveworks/flagger/master/artifacts/flagger/crd.yaml\n")])])]),t("p",[a._v("Deploy Flagger for Istio:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("helm upgrade -i flagger flagger/flagger "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set crd.create"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("false "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshProvider")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("metricsServer")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://prometheus:9090\n")])])]),t("p",[a._v("Note that Flagger depends on Istio telemetry and Prometheus, if you're installing Istio with istioctl\nthen you should be using the "),t("a",{attrs:{href:"https://istio.io/docs/setup/additional-setup/config-profiles/",target:"_blank",rel:"noopener noreferrer"}},[a._v("default profile"),t("OutboundLink")],1),a._v(".")]),a._v(" "),t("p",[a._v("For Istio multi-cluster shared control plane you can install Flagger\non each remote cluster and set the Istio control plane host cluster kubeconfig:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("helm upgrade -i flagger flagger/flagger "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set crd.create"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("false "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshProvider")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("metricsServer")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://istio-cluster-prometheus:9090 "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set istio.kubeconfig.secretName"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-kubeconfig "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set istio.kubeconfig.key"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("kubeconfig\n")])])]),t("p",[a._v("Note that the Istio kubeconfig must be stored in a Kubernetes secret with a data key named "),t("code",[a._v("kubeconfig")]),a._v(".\nFor more details on how to configure Istio multi-cluster credentials\nread the "),t("a",{attrs:{href:"https://istio.io/docs/setup/install/multicluster/shared-vpn/#credentials",target:"_blank",rel:"noopener noreferrer"}},[a._v("Istio docs"),t("OutboundLink")],1),a._v(".")]),a._v(" "),t("p",[a._v("Deploy Flagger for Linkerd:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("helm upgrade -i flagger flagger/flagger "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("linkerd "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set crd.create"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("false "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshProvider")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("linkerd "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("metricsServer")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://linkerd-prometheus:9090\n")])])]),t("p",[a._v("Deploy Flagger for App Mesh:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("helm upgrade -i flagger flagger/flagger "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("appmesh-system "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set crd.create"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("false "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshProvider")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("appmesh "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("metricsServer")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://appmesh-prometheus:9090\n")])])]),t("p",[a._v("You can install Flagger in any namespace as long as it can talk to the Prometheus service on port 9090.")]),a._v(" "),t("p",[a._v("For ingress controllers, the install instructions are:")]),a._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://docs.flagger.app/tutorials/contour-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[a._v("Contour"),t("OutboundLink")],1)]),a._v(" "),t("li",[t("a",{attrs:{href:"https://docs.flagger.app/tutorials/gloo-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[a._v("Gloo"),t("OutboundLink")],1)]),a._v(" "),t("li",[t("a",{attrs:{href:"https://docs.flagger.app/tutorials/nginx-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[a._v("NGINX"),t("OutboundLink")],1)])]),a._v(" "),t("p",[a._v("Enable "),t("strong",[a._v("Slack")]),a._v(" notifications:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("helm upgrade -i flagger flagger/flagger "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set crd.create"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("false "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.url"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.channel"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("general "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.user"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("flagger\n")])])]),t("p",[a._v("Enable "),t("strong",[a._v("Microsoft Teams")]),a._v(" notifications:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("helm upgrade -i flagger flagger/flagger "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set crd.create"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("false "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set msteams.url"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK\n")])])]),t("p",[a._v("You can use the helm template command and apply the generated yaml with kubectl:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[t("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generate")]),a._v("\nhelm fetch --untar --untardir "),t("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v(".")]),a._v(" flagger/flagger "),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("&&")]),a._v("\nhelm template flagger ./flagger "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("metricsServer")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://prometheus.istio-system:9090 "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v(" flagger.yaml\n\n"),t("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# apply")]),a._v("\nkubectl apply -f flagger.yaml\n")])])]),t("p",[a._v("To uninstall the Flagger release with Helm run:")]),a._v(" "),t("div",{staticClass:"language-text extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[a._v("helm delete flagger\n")])])]),t("p",[a._v("The command removes all the Kubernetes components associated with the chart and deletes the release.")]),a._v(" "),t("blockquote",[t("p",[t("strong",[a._v("Note")]),a._v(" that on uninstall the Canary CRD will not be removed. Deleting the CRD will make Kubernetes\nremove all the objects owned by Flagger like Istio virtual services, Kubernetes deployments and ClusterIP services.")])]),a._v(" "),t("p",[a._v("If you want to remove all the objects created by Flagger you have delete the Canary CRD with kubectl:")]),a._v(" "),t("div",{staticClass:"language-text extra-class"},[t("pre",{pre:!0,attrs:{class:"language-text"}},[t("code",[a._v("kubectl delete crd canaries.flagger.app\n")])])]),t("h2",{attrs:{id:"install-grafana-with-helm"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#install-grafana-with-helm","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Grafana with Helm")]),a._v(" "),t("p",[a._v("Flagger comes with a Grafana dashboard made for monitoring the canary analysis.")]),a._v(" "),t("p",[a._v("Deploy Grafana in the "),t("em",[t("strong",[a._v("istio-system")])]),a._v(" namespace:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("helm upgrade -i flagger-grafana flagger/grafana "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("url")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://prometheus.istio-system:9090 "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("user")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("admin "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),t("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("password")]),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("change-me\n")])])]),t("p",[a._v("Or use helm template command and apply the generated yaml with kubectl:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[t("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generate")]),a._v("\nhelm fetch --untar --untardir "),t("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v(".")]),a._v(" flagger/grafana "),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("&&")]),a._v("\nhelm template flagger-grafana ./grafana "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v(" flagger-grafana.yaml\n\n"),t("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# apply")]),a._v("\nkubectl apply -f flagger-grafana.yaml\n")])])]),t("p",[a._v("You can access Grafana using port forwarding:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("kubectl -n istio-system port-forward svc/flagger-grafana "),t("span",{pre:!0,attrs:{class:"token number"}},[a._v("3000")]),a._v(":80\n")])])]),t("h2",{attrs:{id:"install-flagger-with-kustomize"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#install-flagger-with-kustomize","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Flagger with Kustomize")]),a._v(" "),t("p",[a._v("As an alternative to Helm, Flagger can be installed with Kustomize.")]),a._v(" "),t("p",[t("strong",[a._v("Service mesh specific installers")])]),a._v(" "),t("p",[a._v("Install Flagger for Istio:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/istio\n")])])]),t("p",[a._v("This deploys Flagger in the "),t("code",[a._v("istio-system")]),a._v(" namespace and sets the metrics server URL to Istio's Prometheus instance.")]),a._v(" "),t("p",[a._v("Note that you'll need kubectl 1.14 to run the above the command or you can download\nthe "),t("a",{attrs:{href:"https://github.com/kubernetes-sigs/kustomize/releases",target:"_blank",rel:"noopener noreferrer"}},[a._v("kustomize binary"),t("OutboundLink")],1),a._v(" and run:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("kustomize build github.com/weaveworks/flagger//kustomize/istio "),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("|")]),a._v(" kubectl apply -f -\n")])])]),t("p",[a._v("Install Flagger for AWS App Mesh:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/appmesh\n")])])]),t("p",[a._v("This deploys Flagger and sets the metrics server URL to App Mesh's Prometheus instance.")]),a._v(" "),t("p",[a._v("Install Flagger for Linkerd:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/linkerd\n")])])]),t("p",[a._v("This deploys Flagger in the "),t("code",[a._v("linkerd")]),a._v(" namespace and sets the metrics server URL to Linkerd's Prometheus instance.")]),a._v(" "),t("p",[a._v("If you want to install a specific Flagger release, add the version number to the URL:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/linkerd?ref"),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),t("span",{pre:!0,attrs:{class:"token number"}},[a._v("0.18")]),a._v(".0\n")])])]),t("p",[t("strong",[a._v("Generic installer")])]),a._v(" "),t("p",[a._v("Install Flagger and Prometheus:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/kubernetes\n")])])]),t("p",[a._v("This deploys Flagger and Prometheus in the "),t("code",[a._v("flagger-system")]),a._v(" namespace, sets the metrics server URL\nto "),t("code",[a._v("http://flagger-prometheus.flagger-system:9090")]),a._v(" and the mesh provider to "),t("code",[a._v("kubernetes")]),a._v(".")]),a._v(" "),t("p",[a._v("The Prometheus instance has a two hours data retention and is configured to scrape all pods in your cluster\nthat have the "),t("code",[a._v('prometheus.io/scrape: "true"')]),a._v(" annotation.")]),a._v(" "),t("p",[a._v("To target a different provider you can specify it in the canary custom resource:")]),a._v(" "),t("div",{staticClass:"language-yaml extra-class"},[t("pre",{pre:!0,attrs:{class:"language-yaml"}},[t("code",[t("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),t("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Canary\n"),t("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" app\n "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),t("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),t("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# can be: kubernetes, istio, linkerd, appmesh, nginx, gloo")]),a._v("\n "),t("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# use the kubernetes provider for Blue/Green style deployments")]),a._v("\n "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("provider")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" nginx\n")])])]),t("p",[t("strong",[a._v("Customized installer")])]),a._v(" "),t("p",[a._v("Create a kustomization file using flagger as base:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[t("span",{pre:!0,attrs:{class:"token function"}},[a._v("cat")]),a._v(" "),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v(" kustomization.yaml "),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<<")]),t("span",{pre:!0,attrs:{class:"token string"}},[a._v("EOF\nnamespace: istio-system\nbases:\n - github.com/weaveworks/flagger/kustomize/base/flagger\npatchesStrategicMerge:\n - patch.yaml\nEOF")]),a._v("\n")])])]),t("p",[a._v("Create a patch and enable Slack notifications by setting the slack channel and hook URL:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[t("span",{pre:!0,attrs:{class:"token function"}},[a._v("cat")]),a._v(" "),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v(" patch.yaml "),t("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<<")]),t("span",{pre:!0,attrs:{class:"token string"}},[a._v("EOF\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: flagger\nspec:\n template:\n spec:\n containers:\n - name: flagger\n args:\n - -mesh-provider=istio\n - -metrics-server=http://prometheus.istio-system:9090\n - -slack-user=flagger\n - -slack-channel=alerts\n - -slack-url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK\nEOF")]),a._v("\n")])])]),t("p",[a._v("Install Flagger with Slack:")]),a._v(" "),t("div",{staticClass:"language-bash extra-class"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[a._v("kubectl apply -k "),t("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v(".")]),a._v("\n")])])]),t("p",[a._v("If you want to use MS Teams instead of Slack, replace "),t("code",[a._v("-slack-url")]),a._v(" with "),t("code",[a._v("-msteams-url")]),a._v(" and set the webhook address\nto "),t("code",[a._v("https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK")]),a._v(".")])])},[],!1,null,null,null);e.default=r.exports}}]); \ No newline at end of file diff --git a/assets/js/14.a4f3d810.js b/assets/js/14.a4f3d810.js new file mode 100644 index 00000000..1ba97bcb --- /dev/null +++ b/assets/js/14.a4f3d810.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[14],{274:function(e,r,t){"use strict";t.r(r);var s=t(37),o=Object(s.a)({},function(){var e=this,r=e.$createElement,t=e._self._c||r;return t("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[t("h1",{attrs:{id:"introduction"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#introduction","aria-hidden":"true"}},[e._v("#")]),e._v(" Introduction")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://github.com/weaveworks/flagger",target:"_blank",rel:"noopener noreferrer"}},[e._v("Flagger"),t("OutboundLink")],1),e._v(" is a "),t("strong",[e._v("Kubernetes")]),e._v(" operator that automates the promotion of\ncanary deployments using "),t("strong",[e._v("Istio")]),e._v(", "),t("strong",[e._v("Linkerd")]),e._v(", "),t("strong",[e._v("App Mesh")]),e._v(", "),t("strong",[e._v("NGINX")]),e._v(", "),t("strong",[e._v("Contour")]),e._v(" or "),t("strong",[e._v("Gloo")]),e._v(" routing for\ntraffic shifting and "),t("strong",[e._v("Prometheus")]),e._v(" metrics for canary analysis. The canary analysis can be extended with webhooks for\nrunning system integration/acceptance tests, load tests, or any other custom validation.")]),e._v(" "),t("p",[e._v("Flagger implements a control loop that gradually shifts traffic to the canary while measuring key performance indicators\nlike HTTP requests success rate, requests average duration and pods health.\nBased on analysis of the "),t("strong",[e._v("KPIs")]),e._v(" a canary is promoted or aborted, and the analysis result is published to "),t("strong",[e._v("Slack")]),e._v(" or "),t("strong",[e._v("MS Teams")]),e._v(".")]),e._v(" "),t("p",[t("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-overview.png",alt:"Flagger overview diagram"}})]),e._v(" "),t("p",[e._v("Flagger can be configured with Kubernetes custom resources and is compatible with any CI/CD solutions made for Kubernetes.\nSince Flagger is declarative and reacts to Kubernetes events,\nit can be used in "),t("strong",[e._v("GitOps")]),e._v(" pipelines together with Flux CD or JenkinsX.")]),e._v(" "),t("p",[e._v("To get started with Flagger, chose one of the supported routing providers\nand "),t("a",{attrs:{href:"../install/flagger-install-on-kubernetes"}},[e._v("install")]),e._v(" Flagger with Helm or Kustomize.")]),e._v(" "),t("p",[e._v("After install Flagger, you can follow one of the tutorials:")]),e._v(" "),t("p",[t("strong",[e._v("Service mesh tutorials")])]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"../tutorials/istio-progressive-delivery"}},[e._v("Istio")])]),e._v(" "),t("li",[t("a",{attrs:{href:"../tutorials/linkerd-progressive-delivery"}},[e._v("Linkerd")])]),e._v(" "),t("li",[t("a",{attrs:{href:"../tutorials/appmesh-progressive-delivery"}},[e._v("AWS App Mesh")])])]),e._v(" "),t("p",[t("strong",[e._v("Ingress controller tutorials")])]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"../tutorials/contour-progressive-delivery"}},[e._v("Contour")])]),e._v(" "),t("li",[t("a",{attrs:{href:"../tutorials/gloo-progressive-delivery"}},[e._v("Gloo")])]),e._v(" "),t("li",[t("a",{attrs:{href:"../tutorials/nginx-progressive-delivery"}},[e._v("NGINX Ingress")])])]),e._v(" "),t("p",[t("strong",[e._v("Hands-on GitOps workshops")])]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://github.com/stefanprodan/gitops-istio",target:"_blank",rel:"noopener noreferrer"}},[e._v("Istio"),t("OutboundLink")],1)]),e._v(" "),t("li",[t("a",{attrs:{href:"https://helm.workshop.flagger.dev",target:"_blank",rel:"noopener noreferrer"}},[e._v("Linkerd"),t("OutboundLink")],1)]),e._v(" "),t("li",[t("a",{attrs:{href:"https://eks.hands-on.flagger.dev",target:"_blank",rel:"noopener noreferrer"}},[e._v("AWS App Mesh"),t("OutboundLink")],1)])])])},[],!1,null,null,null);r.default=o.exports}}]); \ No newline at end of file diff --git a/assets/js/15.aada2caa.js b/assets/js/15.aada2caa.js new file mode 100644 index 00000000..01bd05d2 --- /dev/null +++ b/assets/js/15.aada2caa.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[15],{275:function(t,a,s){"use strict";s.r(a);var e=s(37),n=Object(e.a)({},function(){var t=this,a=t.$createElement,s=t._self._c||a;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("h1",{attrs:{id:"frequently-asked-questions"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#frequently-asked-questions","aria-hidden":"true"}},[t._v("#")]),t._v(" Frequently asked questions")]),t._v(" "),s("h2",{attrs:{id:"deployment-strategies"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#deployment-strategies","aria-hidden":"true"}},[t._v("#")]),t._v(" Deployment Strategies")]),t._v(" "),s("p",[s("strong",[t._v("Which deployment strategies are supported by Flagger?")])]),t._v(" "),s("p",[t._v("Flagger implements the following deployment strategies:")]),t._v(" "),s("ul",[s("li",[s("router-link",{attrs:{to:"/usage/deployment-strategies.html#canary-release"}},[t._v("Canary Release")])],1),t._v(" "),s("li",[s("router-link",{attrs:{to:"/usage/deployment-strategies.html#a-b-testing"}},[t._v("A/B Testing")])],1),t._v(" "),s("li",[s("router-link",{attrs:{to:"/usage/deployment-strategies.html#blue-green-deployments"}},[t._v("Blue/Green")])],1),t._v(" "),s("li",[s("router-link",{attrs:{to:"/usage/deployment-strategies.html#blue-green-with-traffic-mirroring"}},[t._v("Blue/Green Mirroring")])],1)]),t._v(" "),s("p",[s("strong",[t._v("When should I use A/B testing instead of progressive traffic shifting?")])]),t._v(" "),s("p",[t._v("For frontend applications that require session affinity you should use HTTP headers or cookies match conditions\nto ensure a set of users will stay on the same version for the whole duration of the canary analysis.")]),t._v(" "),s("p",[s("strong",[t._v("Can I use Flagger to manage applications that live outside of a service mesh?")])]),t._v(" "),s("p",[t._v("For applications that are not deployed on a service mesh, Flagger can orchestrate Blue/Green style deployments\nwith Kubernetes L4 networking.")]),t._v(" "),s("p",[s("strong",[t._v("When can I use traffic mirroring?")])]),t._v(" "),s("p",[t._v("Traffic mirroring can be used for Blue/Green deployment strategy or a pre-stage in a Canary release.\nTraffic mirroring will copy each incoming request, sending one request to the primary and one to the canary service.\nMirroring should be used for requests that are "),s("strong",[t._v("idempotent")]),t._v(" or capable of being processed twice (once by the primary and once by the canary).")]),t._v(" "),s("h2",{attrs:{id:"kubernetes-services"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#kubernetes-services","aria-hidden":"true"}},[t._v("#")]),t._v(" Kubernetes services")]),t._v(" "),s("p",[s("strong",[t._v("How is an application exposed inside the cluster?")])]),t._v(" "),s("p",[t._v("Assuming the app name is podinfo you can define a canary like:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetRef")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# service name (optional)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# ClusterIP port number (required)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# container port name or number")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetPort")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# port name can be http or grpc (default http)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("portName")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n")])])]),s("p",[t._v("If the "),s("code",[t._v("service.name")]),t._v(" is not specified, then "),s("code",[t._v("targetRef.name")]),t._v(" is used for the apex domain and canary/primary services name prefix.\nYou should treat the service name as an immutable field, changing it could result in routing conflicts.")]),t._v(" "),s("p",[t._v("Based on the canary spec service, Flagger generates the following Kubernetes ClusterIP service:")]),t._v(" "),s("ul",[s("li",[s("code",[t._v("..svc.cluster.local")]),s("br"),t._v("\nselector "),s("code",[t._v("app=-primary")])]),t._v(" "),s("li",[s("code",[t._v("-primary..svc.cluster.local")]),s("br"),t._v("\nselector "),s("code",[t._v("app=-primary")])]),t._v(" "),s("li",[s("code",[t._v("-canary..svc.cluster.local")]),s("br"),t._v("\nselector "),s("code",[t._v("app=")])])]),t._v(" "),s("p",[t._v("This ensures that traffic coming from a namespace outside the mesh to "),s("code",[t._v("podinfo.test:9898")]),t._v("\nwill be routed to the latest stable release of your app.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Service\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ClusterIP\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("selector")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("primary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("ports")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("protocol")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" TCP\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetPort")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("---")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Service\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("primary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ClusterIP\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("selector")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("primary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("ports")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("protocol")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" TCP\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetPort")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("---")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Service\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ClusterIP\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("selector")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("ports")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("protocol")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" TCP\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetPort")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n")])])]),s("p",[t._v("The "),s("code",[t._v("podinfo-canary.test:9898")]),t._v(" address is available only during the\ncanary analysis and can be used for conformance testing or load testing.")]),t._v(" "),s("h2",{attrs:{id:"multiple-ports"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#multiple-ports","aria-hidden":"true"}},[t._v("#")]),t._v(" Multiple ports")]),t._v(" "),s("p",[s("strong",[t._v("My application listens on multiple ports, how can I expose them inside the cluster?")])]),t._v(" "),s("p",[t._v("If port discovery is enabled, Flagger scans the deployment spec and extracts the containers\nports excluding the port specified in the canary service and Envoy sidecar ports.\nThese ports will be used when generating the ClusterIP services.")]),t._v(" "),s("p",[t._v("For a deployment that exposes two ports:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("template")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("annotations")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prometheus.io/scrape")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"true"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prometheus.io/port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"9899"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("containers")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" app\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("ports")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("containerPort")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("8080")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("containerPort")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9090")]),t._v("\n")])])]),s("p",[t._v("You can enable port discovery so that Prometheus will be able to reach port "),s("code",[t._v("9090")]),t._v(" over mTLS:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# container port used for canary analysis")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("8080")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# port name can be http or grpc (default http)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("portName")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# add all the other container ports")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# to the ClusterIP services (default false)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("portDiscovery")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token boolean important"}},[t._v("true")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("trafficPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("tls")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mode")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ISTIO_MUTUAL\n")])])]),s("p",[t._v("Both port "),s("code",[t._v("8080")]),t._v(" and "),s("code",[t._v("9090")]),t._v(" will be added to the ClusterIP services.")]),t._v(" "),s("h2",{attrs:{id:"label-selectors"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#label-selectors","aria-hidden":"true"}},[t._v("#")]),t._v(" Label selectors")]),t._v(" "),s("p",[s("strong",[t._v("What labels selectors are supported by Flagger?")])]),t._v(" "),s("p",[t._v("The target deployment must have a single label selector in the format "),s("code",[t._v("app: ")]),t._v(":")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("selector")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("matchLabels")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("template")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("labels")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n")])])]),s("p",[t._v("Besides "),s("code",[t._v("app")]),t._v(" Flagger supports "),s("code",[t._v("name")]),t._v(" and "),s("code",[t._v("app.kubernetes.io/name")]),t._v(" selectors. If you use a different\nconvention you can specify your label with the "),s("code",[t._v("-selector-labels")]),t._v(" flag.")]),t._v(" "),s("p",[s("strong",[t._v("Is pod affinity and anti affinity supported?")])]),t._v(" "),s("p",[t._v("For pod affinity to work you need to use a different label than the "),s("code",[t._v("app")]),t._v(", "),s("code",[t._v("name")]),t._v(" or "),s("code",[t._v("app.kubernetes.io/name")]),t._v(".")]),t._v(" "),s("p",[t._v("Anti affinity example:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("selector")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("matchLabels")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("affinity")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("template")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("labels")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("affinity")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("affinity")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("podAntiAffinity")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("preferredDuringSchedulingIgnoredDuringExecution")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("weight")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("100")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("podAffinityTerm")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("labelSelector")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("matchLabels")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("affinity")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("topologyKey")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" kubernetes.io/hostname\n")])])]),s("h2",{attrs:{id:"metrics"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#metrics","aria-hidden":"true"}},[t._v("#")]),t._v(" Metrics")]),t._v(" "),s("p",[s("strong",[t._v("How does Flagger measures the request success rate and duration?")])]),t._v(" "),s("p",[t._v("Flagger measures the request success rate and duration using Prometheus queries.")]),t._v(" "),s("p",[s("strong",[t._v("HTTP requests success rate percentage")])]),t._v(" "),s("p",[t._v("Spec:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("success"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# minimum req success rate (non 5xx responses)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("99")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n")])])]),s("p",[t._v("Istio query:")]),t._v(" "),s("div",{staticClass:"language-javascript extra-class"},[s("pre",{pre:!0,attrs:{class:"language-javascript"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[t._v("sum")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("rate")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n istio_requests_total"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n reporter"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"destination"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n destination_workload_namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$namespace"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n destination_workload"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$workload"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n response_code"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"5.*"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("$interval"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" \n"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v(" \n"),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("sum")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("rate")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n istio_requests_total"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n reporter"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"destination"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n destination_workload_namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$namespace"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n destination_workload"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$workload"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("$interval"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])])]),s("p",[t._v("Envoy query (App Mesh, Contour or Gloo):")]),t._v(" "),s("div",{staticClass:"language-javascript extra-class"},[s("pre",{pre:!0,attrs:{class:"language-javascript"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[t._v("sum")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("rate")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n envoy_cluster_upstream_rq"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n kubernetes_namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$namespace"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n kubernetes_pod_name"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$workload"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n envoy_response_code"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"5.*"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("$interval"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" \n"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v(" \n"),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("sum")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("rate")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n envoy_cluster_upstream_rq"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n kubernetes_namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$namespace"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n kubernetes_pod_name"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$workload"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("$interval"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])])]),s("p",[s("strong",[t._v("HTTP requests milliseconds duration P99")])]),t._v(" "),s("p",[t._v("Spec:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("duration\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# maximum req duration P99")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# milliseconds")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("500")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n")])])]),s("p",[t._v("Istio query:")]),t._v(" "),s("div",{staticClass:"language-javascript extra-class"},[s("pre",{pre:!0,attrs:{class:"language-javascript"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[t._v("histogram_quantile")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("0.99")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" \n "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("sum")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("irate")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n istio_request_duration_seconds_bucket"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n reporter"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"destination"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n destination_workload"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$workload"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n destination_workload_namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$namespace"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("$interval"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("by")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("le"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])])]),s("p",[t._v("Envoy query (App Mesh, Contour or Gloo):")]),t._v(" "),s("div",{staticClass:"language-javascript extra-class"},[s("pre",{pre:!0,attrs:{class:"language-javascript"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[t._v("histogram_quantile")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("0.99")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" \n "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("sum")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("irate")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("\n envoy_cluster_upstream_rq_time_bucket"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n kubernetes_pod_name"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$workload"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n kubernetes_namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("~")]),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"$namespace"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("$interval"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("by")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("le"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])])]),s("blockquote",[s("p",[s("strong",[t._v("Note")]),t._v(" that the metric interval should be lower or equal to the control loop interval.")])]),t._v(" "),s("p",[s("strong",[t._v("Can I use custom metrics?")])]),t._v(" "),s("p",[t._v("The analysis can be extended with metrics provided by Prometheus, Datadog and AWS CloudWatch. For more details\non how custom metrics can be used please read the "),s("router-link",{attrs:{to:"/usage/metrics.html"}},[t._v("metrics docs")]),t._v(".")],1),t._v(" "),s("h2",{attrs:{id:"istio-routing"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#istio-routing","aria-hidden":"true"}},[t._v("#")]),t._v(" Istio routing")]),t._v(" "),s("p",[s("strong",[t._v("How does Flagger interact with Istio?")])]),t._v(" "),s("p",[t._v("Flagger creates an Istio Virtual Service and Destination Rules based on the Canary service spec.\nThe service configuration lets you expose an app inside or outside the mesh.\nYou can also define traffic policies, HTTP match conditions, URI rewrite rules, CORS policies, timeout and retries.")]),t._v(" "),s("p",[t._v("The following spec exposes the "),s("code",[t._v("frontend")]),t._v(" workload inside the mesh on "),s("code",[t._v("frontend.test.svc.cluster.local:9898")]),t._v("\nand outside the mesh on "),s("code",[t._v("frontend.example.com")]),t._v(". You'll have to specify an Istio ingress gateway for external hosts.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" frontend\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# container port")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v('# service port name (optional, will default to "http")')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("portName")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("frontend\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Istio gateways (optional)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("gateways")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" public"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("gateway.istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system.svc.cluster.local\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" mesh\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Istio virtual service host names (optional)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("hosts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" frontend.example.com\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Istio traffic policy")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("trafficPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("tls")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# use ISTIO_MUTUAL when mTLS is enabled")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mode")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DISABLE\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# HTTP match conditions (optional)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prefix")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# HTTP rewrite (optional)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("rewrite")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Istio retry policy (optional)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retries")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("attempts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("3")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("perTryTimeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retryOn")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"gateway-error,connect-failure,refused-stream"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Add headers (optional)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("headers")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("request")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("add")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("x-some-header")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"value"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# cross-origin resource sharing policy (optional)")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("corsPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("allowOrigin")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" example.com\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("allowMethods")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" GET\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("allowCredentials")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token boolean important"}},[t._v("false")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("allowHeaders")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" x"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("some"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("header\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("maxAge")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 24h\n")])])]),s("p",[t._v("For the above spec Flagger will generate the following virtual service:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" networking.istio.io/v1alpha3\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" VirtualService\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" frontend\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("ownerReferences")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("blockOwnerDeletion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token boolean important"}},[t._v("true")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("controller")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token boolean important"}},[t._v("true")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uid")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 3a4a40dd"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("3875"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("11e9"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("8e1d"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("42010a9c0fd1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("gateways")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" public"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("gateway.istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system.svc.cluster.local\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" mesh\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("hosts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" frontend.example.com\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" frontend\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("http")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("corsPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("allowHeaders")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" x"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("some"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("header\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("allowMethods")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" GET\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("allowOrigin")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" example.com\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("maxAge")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 24h\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("headers")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("request")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("add")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("x-some-header")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"value"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prefix")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("rewrite")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("route")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("destination")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("host")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("primary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("weight")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("100")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("destination")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("host")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("canary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("weight")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retries")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("attempts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("3")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("perTryTimeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retryOn")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"gateway-error,connect-failure,refused-stream"')]),t._v("\n")])])]),s("p",[t._v("For each destination in the virtual service a rule is generated:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" networking.istio.io/v1alpha3\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DestinationRule\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" frontend"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("primary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("host")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" frontend"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("primary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("trafficPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("tls")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mode")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DISABLE\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("---")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" networking.istio.io/v1alpha3\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DestinationRule\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" frontend"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("canary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("host")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" frontend"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("canary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("trafficPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("tls")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mode")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DISABLE\n")])])]),s("p",[t._v("Flagger keeps in sync the virtual service and destination rules with the canary service spec.\nAny direct modification to the virtual service spec will be overwritten.")]),t._v(" "),s("p",[t._v("To expose a workload inside the mesh on "),s("code",[t._v("http://backend.test.svc.cluster.local:9898")]),t._v(",\nthe service spec can contain only the container port and the traffic policy:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" backend\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("trafficPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("tls")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mode")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DISABLE\n")])])]),s("p",[t._v("Based on the above spec, Flagger will create several ClusterIP services like:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Service\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" backend"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("primary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("ownerReferences")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("blockOwnerDeletion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token boolean important"}},[t._v("true")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("controller")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token boolean important"}},[t._v("true")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" backend\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uid")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 2ca1a9c7"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("2ef6"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("11e9"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("bd01"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("42010a9c0145\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ClusterIP\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("ports")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("protocol")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" TCP\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetPort")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("selector")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" backend"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("primary\n")])])]),s("p",[t._v("Flagger works for user facing apps exposed outside the cluster via an ingress gateway\nand for backend HTTP APIs that are accessible only from inside the mesh.")]),t._v(" "),s("h2",{attrs:{id:"istio-ingress-gateway"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#istio-ingress-gateway","aria-hidden":"true"}},[t._v("#")]),t._v(" Istio Ingress Gateway")]),t._v(" "),s("p",[s("strong",[t._v("How can I expose multiple canaries on the same external domain?")])]),t._v(" "),s("p",[t._v("Assuming you have two apps, one that servers the main website and one that serves the REST API.\nFor each app you can define a canary object as:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" website\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("8080")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("gateways")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" public"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("gateway.istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system.svc.cluster.local\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("hosts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" my"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("site.com\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prefix")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("rewrite")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("---")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" webapi\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("8080")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("gateways")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" public"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("gateway.istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system.svc.cluster.local\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("hosts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" my"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("site.com\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prefix")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /api\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("rewrite")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n")])])]),s("p",[t._v("Based on the above configuration, Flagger will create two virtual services bounded to the same ingress gateway and external host.\nIstio Pilot will "),s("a",{attrs:{href:"https://istio.io/help/ops/traffic-management/deploy-guidelines/#multiple-virtual-services-and-destination-rules-for-the-same-host",target:"_blank",rel:"noopener noreferrer"}},[t._v("merge"),s("OutboundLink")],1),t._v("\nthe two services and the website rule will be moved to the end of the list in the merged configuration.")]),t._v(" "),s("p",[t._v("Note that host merging only works if the canaries are bounded to a ingress gateway other than the "),s("code",[t._v("mesh")]),t._v(" gateway.")]),t._v(" "),s("h2",{attrs:{id:"istio-mutual-tls"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#istio-mutual-tls","aria-hidden":"true"}},[t._v("#")]),t._v(" Istio Mutual TLS")]),t._v(" "),s("p",[s("strong",[t._v("How can I enable mTLS for a canary?")])]),t._v(" "),s("p",[t._v("When deploying Istio with global mTLS enabled, you have to set the TLS mode to "),s("code",[t._v("ISTIO_MUTUAL")]),t._v(":")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("trafficPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("tls")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mode")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ISTIO_MUTUAL\n")])])]),s("p",[t._v("If you run Istio in permissive mode you can disable TLS:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("trafficPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("tls")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mode")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DISABLE\n")])])]),s("p",[s("strong",[t._v("If Flagger is outside of the mesh, how can it start the load test?")])]),t._v(" "),s("p",[t._v("In order for Flagger to be able to call the load tester service from outside the mesh, you need to disable mTLS on port 80:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" networking.istio.io/v1alpha3\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DestinationRule\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("host")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"flagger-loadtester.test.svc.cluster.local"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("trafficPolicy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("tls")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mode")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DISABLE\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("---")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" authentication.istio.io/v1alpha1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Policy\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targets")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("ports")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("number")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("80")]),t._v("\n")])])])])},[],!1,null,null,null);a.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/16.3012a10b.js b/assets/js/16.3012a10b.js new file mode 100644 index 00000000..8c163052 --- /dev/null +++ b/assets/js/16.3012a10b.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[16],{276:function(t,e,n){"use strict";n.r(e);var s=n(37),l=Object(s.a)({},function(){var t=this.$createElement;return(this._self._c||t)("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}})},[],!1,null,null,null);e.default=l.exports}}]); \ No newline at end of file diff --git a/assets/js/17.a98b6aa9.js b/assets/js/17.a98b6aa9.js new file mode 100644 index 00000000..585f487a --- /dev/null +++ b/assets/js/17.a98b6aa9.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[17],{277:function(a,t,e){"use strict";e.r(t);var s=e(37),n=Object(s.a)({},function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[e("h1",{attrs:{id:"app-mesh-canary-deployments"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#app-mesh-canary-deployments","aria-hidden":"true"}},[a._v("#")]),a._v(" App Mesh Canary Deployments")]),a._v(" "),e("p",[a._v("This guide shows you how to use App Mesh and Flagger to automate canary deployments. You'll need an EKS cluster configured with App Mesh, you can find the install guide "),e("a",{attrs:{href:"https://docs.flagger.app/install/flagger-install-on-eks-appmesh",target:"_blank",rel:"noopener noreferrer"}},[a._v("here"),e("OutboundLink")],1),a._v(".")]),a._v(" "),e("h2",{attrs:{id:"bootstrap"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#bootstrap","aria-hidden":"true"}},[a._v("#")]),a._v(" Bootstrap")]),a._v(" "),e("p",[a._v("Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), then creates a series of objects (Kubernetes deployments, ClusterIP services, App Mesh virtual nodes and services). These objects expose the application on the mesh and drive the canary analysis and promotion. The only App Mesh object you need to create by yourself is the mesh resource.")]),a._v(" "),e("p",[a._v("Create a mesh called "),e("code",[a._v("global")]),a._v(":")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("cat")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<<")]),a._v(" EOF "),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("|")]),a._v(" kubectl apply -f -\napiVersion: appmesh.k8s.aws/v1beta1\nkind: Mesh\nmetadata:\n name: global\nspec:\n serviceDiscoveryType: dns\nEOF\n")])])]),e("p",[a._v("Create a test namespace with App Mesh sidecar injection enabled:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("cat")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<<")]),a._v(" EOF "),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("|")]),a._v(" kubectl apply -f -\napiVersion: v1\nkind: Namespace\nmetadata:\n name: "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v("\n labels:\n appmesh.k8s.aws/sidecarInjectorWebhook: enabled\nEOF\n")])])]),e("p",[a._v("Create a deployment and a horizontal pod autoscaler:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),e("p",[a._v("Deploy the load testing service to generate traffic during the canary analysis:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm upgrade -i flagger-loadtester flagger/loadtester "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("test "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshName")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("global "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"backends[0]=podinfo.test"')]),a._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"backends[1]=podinfo-canary.test"')]),a._v("\n")])])]),e("p",[a._v("Create a canary custom resource:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# deployment reference")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# the maximum time in seconds for the canary deployment")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# to make progress before it is rollback (default 600s)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("progressDeadlineSeconds")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("60")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# HPA reference (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# container port")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# container port name (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# can be http or grpc")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("portName")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# App Mesh reference")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("meshName")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" global\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# App Mesh ingress (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("hosts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"*"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# App Mesh ingress timeout (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# App Mesh egress (optional) ")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("backends")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" backend.test\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# App Mesh retry policy (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("retries")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("attempts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("3")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("perTryTimeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("retryOn")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"gateway-error,client-error,stream-error"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# define the canary analysis timing and KPIs")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# schedule interval (default 60s)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max number of failed metric checks before rollback")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max traffic percentage routed to canary")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("maxWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("50")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# canary increment step")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("stepWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# App Mesh Prometheus checks")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rate\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# minimum req success rate (non 5xx responses)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("duration\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# maximum req duration P99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# milliseconds")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("500")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# testing (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" acceptance"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("\"curl -sd 'test' http://podinfo-canary.test:9898/token | grep token\"")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"')]),a._v("\n")])])]),e("p",[a._v("Save the above resource as podinfo-canary.yaml and then apply it:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -f ./podinfo-canary.yaml\n")])])]),e("p",[a._v("After a couple of seconds Flagger will create the canary objects:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# applied ")]),a._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\ncanary.flagger.app/podinfo\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generated Kubernetes objects")]),a._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generated App Mesh objects")]),a._v("\nvirtualnode.appmesh.k8s.aws/podinfo\nvirtualnode.appmesh.k8s.aws/podinfo-canary\nvirtualnode.appmesh.k8s.aws/podinfo-primary\nvirtualservice.appmesh.k8s.aws/podinfo.test\nvirtualservice.appmesh.k8s.aws/podinfo-canary.test\n")])])]),e("p",[a._v("After the boostrap, the podinfo deployment will be scaled to zero and the traffic to "),e("code",[a._v("podinfo.test")]),a._v(" will be routed to the primary pods. During the canary analysis, the "),e("code",[a._v("podinfo-canary.test")]),a._v(" address can be used to target directly the canary pods.")]),a._v(" "),e("p",[a._v("The App Mesh specific settings are:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("meshName")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" global\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("backends")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" backend1.test\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" backend2.test\n")])])]),e("p",[a._v("App Mesh blocks all egress traffic by default. If your application needs to call another service, you have to create an App Mesh virtual service for it and add the virtual service name to the backend list.")]),a._v(" "),e("h2",{attrs:{id:"setup-app-mesh-gateway-optional"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#setup-app-mesh-gateway-optional","aria-hidden":"true"}},[a._v("#")]),a._v(" Setup App Mesh Gateway (optional)")]),a._v(" "),e("p",[a._v("In order to expose the podinfo app outside the mesh you'll be using an Envoy-powered ingress gateway and an AWS network load balancer. The gateway binds to an internet domain and forwards the calls into the mesh through the App Mesh sidecar. If podinfo becomes unavailable due to a cluster downscaling or a node restart, the gateway will retry the calls for a short period of time.")]),a._v(" "),e("p",[a._v("Deploy the gateway behind an AWS NLB:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm upgrade -i appmesh-gateway flagger/appmesh-gateway "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set mesh.name"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("global\n")])])]),e("p",[a._v("Find the gateway public address:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("export")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("URL")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"http://'),e("span",{pre:!0,attrs:{class:"token variable"}},[e("span",{pre:!0,attrs:{class:"token variable"}},[a._v("$(")]),a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" get svc/appmesh-gateway -ojson "),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("|")]),a._v(" jq -r "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('".status.loadBalancer.ingress[].hostname"')]),e("span",{pre:!0,attrs:{class:"token variable"}},[a._v(")")])]),a._v('"')]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("echo")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token variable"}},[a._v("$URL")]),a._v("\n")])])]),e("p",[a._v("Wait for the NLB to become active:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" -sS "),e("span",{pre:!0,attrs:{class:"token variable"}},[a._v("$URL")]),a._v("\n")])])]),e("p",[a._v("Open your browser and navigate to the ingress address to access podinfo UI.")]),a._v(" "),e("h2",{attrs:{id:"automated-canary-promotion"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-canary-promotion","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated canary promotion")]),a._v(" "),e("p",[a._v("A canary deployment is triggered by changes in any of the following objects:")]),a._v(" "),e("ul",[e("li",[a._v("Deployment PodSpec (container image, command, ports, env, resources, etc)")]),a._v(" "),e("li",[a._v("ConfigMaps and Secrets mounted as volumes or mapped to environment variables")])]),a._v(" "),e("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.1\n")])])]),e("p",[a._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 0\n Phase: Succeeded\nEvents:\n New revision detected! Scaling up podinfo.test\n Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\n Pre-rollout check acceptance-test passed\n Advance podinfo.test canary weight 5\n Advance podinfo.test canary weight 10\n Advance podinfo.test canary weight 15\n Advance podinfo.test canary weight 20\n Advance podinfo.test canary weight 25\n Advance podinfo.test canary weight 30\n Advance podinfo.test canary weight 35\n Advance podinfo.test canary weight 40\n Advance podinfo.test canary weight 45\n Advance podinfo.test canary weight 50\n Copying podinfo.test template spec to podinfo-primary.test\n Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Routing all traffic to primary\n Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[a._v("When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.")]),a._v(" "),e("p",[e("strong",[a._v("Note")]),a._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),a._v(" "),e("p",[a._v("During the analysis the canary’s progress can be monitored with Grafana. The App Mesh dashboard URL is "),e("a",{attrs:{href:"http://localhost:3000/d/flagger-appmesh/appmesh-canary?refresh=10s&orgId=1&var-namespace=test&var-primary=podinfo-primary&var-canary=podinfo",target:"_blank",rel:"noopener noreferrer"}},[a._v("http://localhost:3000/d/flagger-appmesh/appmesh-canary?refresh=10s&orgId=1&var-namespace=test&var-primary=podinfo-primary&var-canary=podinfo"),e("OutboundLink")],1)]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/flagger-grafana-appmesh.png",alt:"App Mesh Canary Dashboard"}})]),a._v(" "),e("p",[a._v("You can monitor all canaries with:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("15")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-10-02T14:05:07Z\nprod frontend Succeeded "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-10-02T16:15:07Z\nprod backend Failed "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-10-02T17:05:07Z\n")])])]),e("p",[a._v("If you’ve enabled the Slack notifications, you should receive the following messages:")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/slack-canary-notifications.png",alt:"Flagger Slack Notifications"}})]),a._v(" "),e("h2",{attrs:{id:"automated-rollback"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated rollback")]),a._v(" "),e("p",[a._v("During the canary analysis you can generate HTTP 500 errors or high latency to test if Flagger pauses the rollout.")]),a._v(" "),e("p",[a._v("Trigger a canary deployment:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.2\n")])])]),e("p",[a._v("Exec into the load tester pod with:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("exec")]),a._v(" -it deploy/flagger-loadtester "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("bash")]),a._v("\n")])])]),e("p",[a._v("Generate HTTP 500 errors:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("hey -z 1m -c "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v(" -q "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v(" http://podinfo-canary.test:9898/status/500\n")])])]),e("p",[a._v("Generate latency:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" -n "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("1")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" http://podinfo-canary.test:9898/delay/1\n")])])]),e("p",[a._v("When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n appmesh-system logs deploy/flagger -f | jq .msg\n\nNew revision detected! Starting canary analysis for podinfo.test\nPre-rollout check acceptance-test passed\nAdvance podinfo.test canary weight 5\nAdvance podinfo.test canary weight 10\nAdvance podinfo.test canary weight 15\nHalt podinfo.test advancement success rate 69.17% < 99%\nHalt podinfo.test advancement success rate 61.39% < 99%\nHalt podinfo.test advancement success rate 55.06% < 99%\nHalt podinfo.test advancement request duration 1.20s > 0.5s\nHalt podinfo.test advancement request duration 1.45s > 0.5s\nRolling back podinfo.test failed checks threshold reached 5\nCanary failed! Scaling down podinfo.test\n")])])]),e("p",[a._v("If you’ve enabled the Slack notifications, you’ll receive a message if the progress deadline is exceeded, or if the analysis reached the maximum number of failed checks:")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/slack-canary-failed.png",alt:"Flagger Slack Notifications"}})]),a._v(" "),e("h2",{attrs:{id:"a-b-testing"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#a-b-testing","aria-hidden":"true"}},[a._v("#")]),a._v(" A/B Testing")]),a._v(" "),e("p",[a._v("Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions. In an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users. This is particularly useful for frontend applications that require session affinity.")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-abtest-steps.png",alt:"Flagger A/B Testing Stages"}})]),a._v(" "),e("p",[a._v("Edit the canary analysis, remove the max/step weight and add the match conditions and iterations:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("iterations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("10")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("x-canary")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("exact")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"insider"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("\"hey -z 1m -q 10 -c 2 -H 'X-Canary: insider' http://podinfo.test:9898/\"")]),a._v("\n")])])]),e("p",[a._v("The above configuration will run an analysis for ten minutes targeting users that have a "),e("code",[a._v("X-Canary: insider")]),a._v(" header.")]),a._v(" "),e("p",[a._v("You can also use a HTTP cookie, to target all users with a "),e("code",[a._v("canary")]),a._v(" cookie set to "),e("code",[a._v("insider")]),a._v(" the match condition should be:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cookie")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("regex")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"^(.*?;)?(canary=insider)(;.*)?$"')]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("\"hey -z 1m -q 10 -c 2 -H 'Cookie: canary=insider' http://podinfo.test:9898/\"")]),a._v("\n")])])]),e("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.3\n")])])]),e("p",[a._v("Flagger detects that the deployment revision changed and starts the A/B test:")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n appmesh-system logs deploy/flagger -f | jq .msg\n\nNew revision detected! Starting canary analysis for podinfo.test\nAdvance podinfo.test canary iteration 1/10\nAdvance podinfo.test canary iteration 2/10\nAdvance podinfo.test canary iteration 3/10\nAdvance podinfo.test canary iteration 4/10\nAdvance podinfo.test canary iteration 5/10\nAdvance podinfo.test canary iteration 6/10\nAdvance podinfo.test canary iteration 7/10\nAdvance podinfo.test canary iteration 8/10\nAdvance podinfo.test canary iteration 9/10\nAdvance podinfo.test canary iteration 10/10\nCopying podinfo.test template spec to podinfo-primary.test\nWaiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\nRouting all traffic to primary\nPromotion completed! Scaling down podinfo.test\n")])])]),e("p",[a._v("For an in-depth look at the analysis process read the "),e("router-link",{attrs:{to:"/usage/how-it-works.html"}},[a._v("usage docs")]),a._v(".")],1)])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/18.e5188926.js b/assets/js/18.e5188926.js new file mode 100644 index 00000000..782debe2 --- /dev/null +++ b/assets/js/18.e5188926.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[18],{278:function(e,t,a){"use strict";a.r(t);var s=a(37),n=Object(s.a)({},function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[a("h1",{attrs:{id:"canaries-with-helm-charts-and-gitops"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#canaries-with-helm-charts-and-gitops","aria-hidden":"true"}},[e._v("#")]),e._v(" Canaries with Helm charts and GitOps")]),e._v(" "),a("p",[e._v("This guide shows you how to package a web app into a Helm chart, trigger canary deployments on Helm upgrade and automate the chart release process with Weave Flux.")]),e._v(" "),a("h2",{attrs:{id:"packaging"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#packaging","aria-hidden":"true"}},[e._v("#")]),e._v(" Packaging")]),e._v(" "),a("p",[e._v("You'll be using the "),a("a",{attrs:{href:"https://github.com/stefanprodan/k8s-podinfo",target:"_blank",rel:"noopener noreferrer"}},[e._v("podinfo"),a("OutboundLink")],1),e._v(" chart. This chart packages a web app made with Go, it's configuration, a horizontal pod autoscaler (HPA) and the canary configuration file.")]),e._v(" "),a("div",{staticClass:"language-text extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("├── Chart.yaml\n├── README.md\n├── templates\n│ ├── NOTES.txt\n│ ├── _helpers.tpl\n│ ├── canary.yaml\n│ ├── configmap.yaml\n│ ├── deployment.yaml\n│ ├── hpa.yaml\n│ ├── service.yaml\n│ └── tests\n│ ├── test-config.yaml\n│ └── test-pod.yaml\n└── values.yaml\n")])])]),a("p",[e._v("You can find the chart source "),a("a",{attrs:{href:"https://github.com/stefanprodan/flagger/tree/master/charts/podinfo",target:"_blank",rel:"noopener noreferrer"}},[e._v("here"),a("OutboundLink")],1),e._v(".")]),e._v(" "),a("h2",{attrs:{id:"install"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#install","aria-hidden":"true"}},[e._v("#")]),e._v(" Install")]),e._v(" "),a("p",[e._v("Create a test namespace with Istio sidecar injection enabled:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("export")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token assign-left variable"}},[e._v("REPO")]),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("https://raw.githubusercontent.com/weaveworks/flagger/master\n\nkubectl apply -f "),a("span",{pre:!0,attrs:{class:"token variable"}},[e._v("${REPO}")]),e._v("/artifacts/namespaces/test.yaml\n")])])]),a("p",[e._v("Add Flagger Helm repository:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("helm repo "),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("add")]),e._v(" flagger https://flagger.app\n")])])]),a("p",[e._v("Install podinfo with the release name "),a("code",[e._v("frontend")]),e._v(" (replace "),a("code",[e._v("example.com")]),e._v(" with your own domain):")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("helm upgrade -i frontend flagger/podinfo "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--namespace "),a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("test")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set "),a("span",{pre:!0,attrs:{class:"token assign-left variable"}},[e._v("nameOverride")]),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("frontend "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set "),a("span",{pre:!0,attrs:{class:"token assign-left variable"}},[e._v("backend")]),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("http://backend.test:9898/echo "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.enabled"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("true "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.istioIngress.enabled"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("true "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.istioIngress.gateway"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("public-gateway.istio-system.svc.cluster.local "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.istioIngress.host"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("frontend.istio.example.com\n")])])]),a("p",[e._v("Flagger takes a Kubernetes deployment and a horizontal pod autoscaler (HPA), then creates a series of objects (Kubernetes deployments, ClusterIP services and Istio virtual services). These objects expose the application on the mesh and drive the canary analysis and promotion.")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# generated by Helm ")]),e._v("\nconfigmap/frontend\ndeployment.apps/frontend\nhorizontalpodautoscaler.autoscaling/frontend\ncanary.flagger.app/frontend\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# generated by Flagger")]),e._v("\nconfigmap/frontend-primary\ndeployment.apps/frontend-primary\nhorizontalpodautoscaler.autoscaling/frontend-primary\nservice/frontend\nservice/frontend-canary\nservice/frontend-primary\nvirtualservice.networking.istio.io/frontend\n")])])]),a("p",[e._v("When the "),a("code",[e._v("frontend-primary")]),e._v(" deployment comes online, Flagger will route all traffic to the primary pods and scale to zero the "),a("code",[e._v("frontend")]),e._v(" deployment.")]),e._v(" "),a("p",[e._v("Open your browser and navigate to the frontend URL:")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/demo-frontend.png",alt:"Podinfo Frontend"}})]),e._v(" "),a("p",[e._v("Now let's install the "),a("code",[e._v("backend")]),e._v(" release without exposing it outside the mesh:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("helm upgrade -i backend flagger/podinfo "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--namespace "),a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("test")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set "),a("span",{pre:!0,attrs:{class:"token assign-left variable"}},[e._v("nameOverride")]),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("backend "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.enabled"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("true "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.istioIngress.enabled"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("false\n")])])]),a("p",[e._v("Check if Flagger has successfully deployed the canaries:")]),e._v(" "),a("div",{staticClass:"language-text extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("kubectl -n test get canaries\n\nNAME STATUS WEIGHT LASTTRANSITIONTIME\nbackend Initialized 0 2019-02-12T18:53:18Z\nfrontend Initialized 0 2019-02-12T17:50:50Z\n")])])]),a("p",[e._v("Click on the ping button in the "),a("code",[e._v("frontend")]),e._v(" UI to trigger a HTTP POST request that will reach the "),a("code",[e._v("backend")]),e._v(" app:")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/demo-frontend-jaeger.png",alt:"Jaeger Tracing"}})]),e._v(" "),a("p",[e._v("We'll use the "),a("code",[e._v("/echo")]),e._v(" endpoint (same as the one the ping button calls) to generate load on both apps during a canary deployment.")]),e._v(" "),a("h2",{attrs:{id:"upgrade"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#upgrade","aria-hidden":"true"}},[e._v("#")]),e._v(" Upgrade")]),e._v(" "),a("p",[e._v("First let's install a load testing service that will generate traffic during analysis:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("helm upgrade -i flagger-loadtester flagger/loadtester "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--namespace"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("test\n")])])]),a("p",[e._v("Install Flagger's helm test runner in the "),a("code",[e._v("kube-system")]),e._v(" using "),a("code",[e._v("tiller")]),e._v(" service account:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("helm upgrade -i flagger-helmtester flagger/loadtester "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--namespace"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("kube-system "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set "),a("span",{pre:!0,attrs:{class:"token assign-left variable"}},[e._v("serviceAccountName")]),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("tiller\n")])])]),a("p",[e._v("Enable the load and helm tester and deploy a new "),a("code",[e._v("frontend")]),e._v(" version:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("helm upgrade -i frontend flagger/podinfo/ "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--namespace "),a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("test")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--reuse-values "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.loadtest.enabled"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("true "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.helmtest.enabled"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("true "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set image.tag"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("3.1")]),e._v(".1\n")])])]),a("p",[e._v("Flagger detects that the deployment revision changed and starts the canary analysis:")]),e._v(" "),a("div",{staticClass:"language-text extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("kubectl -n istio-system logs deployment/flagger -f | jq .msg\n\nNew revision detected! Scaling up frontend.test\nHalt advancement frontend.test waiting for rollout to finish: 0 of 2 updated replicas are available\nStarting canary analysis for frontend.test\nPre-rollout check helm test passed\nAdvance frontend.test canary weight 5\nAdvance frontend.test canary weight 10\nAdvance frontend.test canary weight 15\nAdvance frontend.test canary weight 20\nAdvance frontend.test canary weight 25\nAdvance frontend.test canary weight 30\nAdvance frontend.test canary weight 35\nAdvance frontend.test canary weight 40\nAdvance frontend.test canary weight 45\nAdvance frontend.test canary weight 50\nCopying frontend.test template spec to frontend-primary.test\nHalt advancement frontend-primary.test waiting for rollout to finish: 1 old replicas are pending termination\nPromotion completed! Scaling down frontend.test\n")])])]),a("p",[e._v("You can monitor the canary deployment with Grafana. Open the Flagger dashboard, select "),a("code",[e._v("test")]),e._v(" from the namespace dropdown, "),a("code",[e._v("frontend-primary")]),e._v(" from the primary dropdown and "),a("code",[e._v("frontend")]),e._v(" from the canary dropdown.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/demo-frontend-dashboard.png",alt:"Flagger Grafana Dashboard"}})]),e._v(" "),a("p",[e._v("Now trigger a canary deployment for the "),a("code",[e._v("backend")]),e._v(" app, but this time you'll change a value in the configmap:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("helm upgrade -i backend flagger/podinfo/ "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--namespace "),a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("test")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--reuse-values "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.loadtest.enabled"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("true "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set canary.helmtest.enabled"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("true "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set httpServer.timeout"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("25s\n")])])]),a("p",[e._v("Generate HTTP 500 errors:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("kubectl -n "),a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("test")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("exec")]),e._v(" -it flagger-loadtester-xxx-yyy "),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("sh")]),e._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("watch")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("curl")]),e._v(" http://backend-canary:9898/status/500\n")])])]),a("p",[e._v("Generate latency:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("kubectl -n "),a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("test")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("exec")]),e._v(" -it flagger-loadtester-xxx-yyy "),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("sh")]),e._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("watch")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("curl")]),e._v(" http://backend-canary:9898/delay/1\n")])])]),a("p",[e._v("Flagger detects the config map change and starts a canary analysis. Flagger will pause the advancement when the HTTP success rate drops under 99% or when the average request duration in the last minute is over 500ms:")]),e._v(" "),a("div",{staticClass:"language-text extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("kubectl -n test describe canary backend\n\nEvents:\n\nConfigMap backend has changed\nNew revision detected! Scaling up backend.test\nStarting canary analysis for backend.test\nAdvance backend.test canary weight 5\nAdvance backend.test canary weight 10\nAdvance backend.test canary weight 15\nAdvance backend.test canary weight 20\nAdvance backend.test canary weight 25\nAdvance backend.test canary weight 30\nAdvance backend.test canary weight 35\nHalt backend.test advancement success rate 62.50% < 99%\nHalt backend.test advancement success rate 88.24% < 99%\nAdvance backend.test canary weight 40\nAdvance backend.test canary weight 45\nHalt backend.test advancement request duration 2.415s > 500ms\nHalt backend.test advancement request duration 2.42s > 500ms\nAdvance backend.test canary weight 50\nConfigMap backend-primary synced\nCopying backend.test template spec to backend-primary.test\nPromotion completed! Scaling down backend.test\n")])])]),a("p",[a("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/demo-backend-dashboard.png",alt:"Flagger Grafana Dashboard"}})]),e._v(" "),a("p",[e._v("If the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("kubectl -n "),a("span",{pre:!0,attrs:{class:"token builtin class-name"}},[e._v("test")]),e._v(" get canary\n\nNAME STATUS WEIGHT LASTTRANSITIONTIME\nbackend Succeeded "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("0")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("2019")]),e._v("-02-12T19:33:11Z\nfrontend Failed "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("0")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("2019")]),e._v("-02-12T19:47:20Z\n")])])]),a("p",[e._v("If you've enabled the Slack notifications, you'll receive an alert with the reason why the "),a("code",[e._v("backend")]),e._v(" promotion failed.")]),e._v(" "),a("h2",{attrs:{id:"gitops-automation"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#gitops-automation","aria-hidden":"true"}},[e._v("#")]),e._v(" GitOps automation")]),e._v(" "),a("p",[e._v("Instead of using Helm CLI from a CI tool to perform the install and upgrade, you could use a Git based approach. GitOps is a way to do Continuous Delivery, it works by using Git as a source of truth for declarative infrastructure and workloads. In the "),a("a",{attrs:{href:"https://www.weave.works/technologies/gitops/",target:"_blank",rel:"noopener noreferrer"}},[e._v("GitOps model"),a("OutboundLink")],1),e._v(", any change to production must be committed in source control prior to being applied on the cluster. This way rollback and audit logs are provided by Git.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-flux-gitops.png",alt:"Helm GitOps Canary Deployment"}})]),e._v(" "),a("p",[e._v("In order to apply the GitOps pipeline model to Flagger canary deployments you'll need a Git repository with your workloads definitions in YAML format, a container registry where your CI system pushes immutable images and an operator that synchronizes the Git repo with the cluster state.")]),e._v(" "),a("p",[e._v("Create a git repository with the following content:")]),e._v(" "),a("div",{staticClass:"language-text extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("├── namespaces\n│ └── test.yaml\n└── releases\n └── test\n ├── backend.yaml\n ├── frontend.yaml\n ├── loadtester.yaml\n └── helmtester.yaml\n")])])]),a("p",[e._v("Define the "),a("code",[e._v("frontend")]),e._v(" release using Flux "),a("code",[e._v("HelmRelease")]),e._v(" custom resource:")]),e._v(" "),a("div",{staticClass:"language-yaml extra-class"},[a("pre",{pre:!0,attrs:{class:"language-yaml"}},[a("code",[a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("apiVersion")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" flux.weave.works/v1beta1\n"),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("kind")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" HelmRelease\n"),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("metadata")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("name")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" frontend\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("namespace")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" test\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("annotations")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("fluxcd.io/automated")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v('"true"')]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("filter.fluxcd.io/chart-image")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" semver"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("~3.1\n"),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("spec")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("releaseName")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" frontend\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("chart")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("git")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" https"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("//github.com/weaveowrks/flagger\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("ref")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" master\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("path")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" charts/podinfo\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("values")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("image")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("repository")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" stefanprodan/podinfo\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("tag")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 3.1.0\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("backend")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" http"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("//backend"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v("podinfo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("9898/echo\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("canary")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("enabled")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token boolean important"}},[e._v("true")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("istioIngress")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("enabled")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token boolean important"}},[e._v("true")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("gateway")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" public"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v("gateway.istio"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v("system.svc.cluster.local\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("host")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" frontend.istio.example.com\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("loadtest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("enabled")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token boolean important"}},[e._v("true")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("helmtest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("enabled")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token boolean important"}},[e._v("true")]),e._v("\n")])])]),a("p",[e._v("In the "),a("code",[e._v("chart")]),e._v(" section I've defined the release source by specifying the Helm repository (hosted on GitHub Pages),\nchart name and version. In the "),a("code",[e._v("values")]),e._v(" section I've overwritten the defaults set in values.yaml.")]),e._v(" "),a("p",[e._v("With the "),a("code",[e._v("fluxcd.io")]),e._v(" annotations I instruct Flux to automate this release.\nWhen an image tag in the sem ver range of "),a("code",[e._v("3.1.0 - 3.1.99")]),e._v(" is pushed to Docker Hub,\nFlux will upgrade the Helm release and from there Flagger will pick up the change and start a canary deployment.")]),e._v(" "),a("p",[e._v("Install "),a("a",{attrs:{href:"https://github.com/fluxcd/flux",target:"_blank",rel:"noopener noreferrer"}},[e._v("Flux"),a("OutboundLink")],1),e._v(" and its\n"),a("a",{attrs:{href:"https://github.com/fluxcd/helm-operator",target:"_blank",rel:"noopener noreferrer"}},[e._v("Helm Operator"),a("OutboundLink")],1),e._v(" by specifying your Git repo URL:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("helm repo "),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("add")]),e._v(" fluxcd https://charts.fluxcd.io\n\nhelm "),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("install")]),e._v(" --name flux "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set git.url"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("git@github.com:"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("<")]),e._v("USERNAME"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v(">")]),e._v("/"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("<")]),e._v("REPOSITORY"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v(">")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--namespace fluxcd "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\nfluxcd/flux\n\nhelm upgrade -i helm-operator fluxcd/helm-operator "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--namespace fluxcd "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("\\")]),e._v("\n--set git.ssh.secretName"),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("=")]),e._v("flux-git-deploy\n")])])]),a("p",[e._v("At startup Flux generates a SSH key and logs the public key. Find the SSH public key with:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[e._v("kubectl -n fluxcd logs deployment/flux "),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("|")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("grep")]),e._v(" identity.pub "),a("span",{pre:!0,attrs:{class:"token operator"}},[e._v("|")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("cut")]),e._v(" -d "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v("'\"'")]),e._v(" -f2\n")])])]),a("p",[e._v("In order to sync your cluster state with Git you need to copy the public key\nand create a deploy key with write access on your GitHub repository.")]),e._v(" "),a("p",[e._v("Open GitHub, navigate to your fork, go to "),a("em",[e._v("Setting > Deploy keys")]),e._v(" click on "),a("em",[e._v("Add deploy key")]),e._v(", check "),a("em",[e._v("Allow write access")]),e._v(",\npaste the Flux public key and click "),a("em",[e._v("Add key")]),e._v(".")]),e._v(" "),a("p",[e._v("After a couple of seconds Flux will apply the Kubernetes resources from Git and\nFlagger will launch the "),a("code",[e._v("frontend")]),e._v(" and "),a("code",[e._v("backend")]),e._v(" apps.")]),e._v(" "),a("p",[e._v("A CI/CD pipeline for the "),a("code",[e._v("frontend")]),e._v(" release could look like this:")]),e._v(" "),a("ul",[a("li",[e._v("cut a release from the master branch of the podinfo code repo with the git tag "),a("code",[e._v("3.1.1")])]),e._v(" "),a("li",[e._v("CI builds the image and pushes the "),a("code",[e._v("podinfo:3.1.1")]),e._v(" image to the container registry")]),e._v(" "),a("li",[e._v("Flux scans the registry and updates the Helm release "),a("code",[e._v("image.tag")]),e._v(" to "),a("code",[e._v("3.1.1")])]),e._v(" "),a("li",[e._v("Flux commits and push the change to the cluster repo")]),e._v(" "),a("li",[e._v("Flux applies the updated Helm release on the cluster")]),e._v(" "),a("li",[e._v("Flux Helm Operator picks up the change and calls Tiller to upgrade the release")]),e._v(" "),a("li",[e._v("Flagger detects a revision change and scales up the "),a("code",[e._v("frontend")]),e._v(" deployment")]),e._v(" "),a("li",[e._v("Flagger runs the helm test before routing traffic to the canary service")]),e._v(" "),a("li",[e._v("Flagger starts the load test and runs the canary analysis")]),e._v(" "),a("li",[e._v("Based on the analysis result the canary deployment is promoted to production or rolled back")]),e._v(" "),a("li",[e._v("Flagger sends a Slack or MS Teams notification with the canary result")])]),e._v(" "),a("p",[e._v("If the canary fails, fix the bug, do another patch release eg "),a("code",[e._v("3.1.2")]),e._v(" and the whole process will run again.")]),e._v(" "),a("p",[e._v("A canary deployment can fail due to any of the following reasons:")]),e._v(" "),a("ul",[a("li",[e._v("the container image can't be downloaded")]),e._v(" "),a("li",[e._v("the deployment replica set is stuck for more then ten minutes (eg. due to a container crash loop)")]),e._v(" "),a("li",[e._v("the webooks (acceptance tests, helm tests, load tests, etc) are returning a non 2xx response")]),e._v(" "),a("li",[e._v("the HTTP success rate (non 5xx responses) metric drops under the threshold")]),e._v(" "),a("li",[e._v("the HTTP average duration metric goes over the threshold")]),e._v(" "),a("li",[e._v("the Istio telemetry service is unable to collect traffic metrics")]),e._v(" "),a("li",[e._v("the metrics server (Prometheus) can't be reached")])]),e._v(" "),a("p",[e._v("If you want to find out more about managing Helm releases with Flux here are two in-depth guides:\n"),a("a",{attrs:{href:"https://github.com/stefanprodan/gitops-helm",target:"_blank",rel:"noopener noreferrer"}},[e._v("gitops-helm"),a("OutboundLink")],1),e._v("\nand "),a("a",{attrs:{href:"https://github.com/stefanprodan/gitops-istio",target:"_blank",rel:"noopener noreferrer"}},[e._v("gitops-istio"),a("OutboundLink")],1),e._v(".")])])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/19.bb9498f7.js b/assets/js/19.bb9498f7.js new file mode 100644 index 00000000..bddda72a --- /dev/null +++ b/assets/js/19.bb9498f7.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[19],{279:function(t,a,e){"use strict";e.r(a);var s=e(37),n=Object(s.a)({},function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("h1",{attrs:{id:"contour-canary-deployments"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#contour-canary-deployments","aria-hidden":"true"}},[t._v("#")]),t._v(" Contour Canary Deployments")]),t._v(" "),e("p",[t._v("This guide shows you how to use "),e("a",{attrs:{href:"https://projectcontour.io/",target:"_blank",rel:"noopener noreferrer"}},[t._v("Contour"),e("OutboundLink")],1),t._v(" ingress controller and Flagger to automate canary releases and A/B testing.")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-contour-overview.png",alt:"Flagger Contour Overview"}})]),t._v(" "),e("h2",{attrs:{id:"prerequisites"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[t._v("#")]),t._v(" Prerequisites")]),t._v(" "),e("p",[t._v("Flagger requires a Kubernetes cluster "),e("strong",[t._v("v1.11")]),t._v(" or newer and Contour "),e("strong",[t._v("v1.0")]),t._v(" or newer.")]),t._v(" "),e("p",[t._v("Install Contour on a cluster with LoadBalancer support:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -f https://projectcontour.io/quickstart/contour.yaml\n")])])]),e("p",[t._v("The above command will deploy Contour and an Envoy daemonset in the "),e("code",[t._v("projectcontour")]),t._v(" namespace.")]),t._v(" "),e("p",[t._v("Install Flagger using Kustomize (kubectl 1.14) in the "),e("code",[t._v("projectcontour")]),t._v(" namespace:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/contour\n")])])]),e("p",[t._v("The above command will deploy Flagger and Prometheus configured to scrape the Contour's Envoy instances.")]),t._v(" "),e("p",[t._v("Or you can install Flagger using Helm:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("helm repo "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("add")]),t._v(" flagger https://flagger.app\n\nhelm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--namespace projectcontour "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("meshProvider")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("contour "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set prometheus.install"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("true\n")])])]),e("p",[t._v("You can also enable Slack, Discord, Rocket or MS Teams notifications,\nsee the alerting "),e("router-link",{attrs:{to:"/usage/alerting.html"}},[t._v("docs")]),t._v(".")],1),t._v(" "),e("h2",{attrs:{id:"bootstrap"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#bootstrap","aria-hidden":"true"}},[t._v("#")]),t._v(" Bootstrap")]),t._v(" "),e("p",[t._v("Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),\nthen creates a series of objects (Kubernetes deployments, ClusterIP services and Contour HTTPProxy).\nThese objects expose the application in the cluster and drive the canary analysis and promotion.")]),t._v(" "),e("p",[t._v("Create a test namespace:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl create ns "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v("\n")])])]),e("p",[t._v("Install the load testing service to generate traffic during the canary analysis:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/tester\n")])])]),e("p",[t._v("Create a deployment and a horizontal pod autoscaler:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),e("p",[t._v("Create a canary custom resource (replace "),e("code",[t._v("app.example.com")]),t._v(" with your own domain):")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# deployment reference")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# HPA reference")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# service port")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("80")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# container port")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetPort")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Contour request timeout")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 15s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Contour retry policy")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retries")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("attempts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("3")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("perTryTimeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# define the canary analysis timing and KPIs")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# schedule interval (default 60s)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max number of failed metric checks before rollback")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max traffic percentage routed to canary")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("maxWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("50")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# canary increment step")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("stepWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Contour Prometheus checks")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# minimum req success rate (non 5xx responses)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("duration\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# maximum req duration P99 in milliseconds")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("500")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# testing")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" acceptance"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"curl -sd 'test' http://podinfo-canary.test/token | grep token\"")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"hey -z 1m -q 10 -c 2 -host app.example.com http://envoy.projectcontour"')]),t._v("\n")])])]),e("p",[t._v("Save the above resource as podinfo-canary.yaml and then apply it:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -f ./podinfo-canary.yaml\n")])])]),e("p",[t._v("The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every half a minute.")]),t._v(" "),e("p",[t._v("After a couple of seconds Flagger will create the canary objects:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# applied ")]),t._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\ncanary.flagger.app/podinfo\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# generated")]),t._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\nhttpproxy.projectcontour.io/podinfo\n")])])]),e("p",[t._v("After the boostrap, the podinfo deployment will be scaled to zero and the traffic to "),e("code",[t._v("podinfo.test")]),t._v("\nwill be routed to the primary pods.\nDuring the canary analysis, the "),e("code",[t._v("podinfo-canary.test")]),t._v(" address can be used to target directly the canary pods.")]),t._v(" "),e("h2",{attrs:{id:"expose-the-app-outside-the-cluster"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#expose-the-app-outside-the-cluster","aria-hidden":"true"}},[t._v("#")]),t._v(" Expose the app outside the cluster")]),t._v(" "),e("p",[t._v("Find the external address of Contour's Envoy load balancer:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("export")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("ADDRESS")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"'),e("span",{pre:!0,attrs:{class:"token variable"}},[e("span",{pre:!0,attrs:{class:"token variable"}},[t._v("$(")]),t._v("kubectl -n projectcontour get svc/envoy -ojson "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" jq -r "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('".status.loadBalancer.ingress[].hostname"')]),e("span",{pre:!0,attrs:{class:"token variable"}},[t._v(")")])]),t._v('"')]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("echo")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token variable"}},[t._v("$ADDRESS")]),t._v("\n")])])]),e("p",[t._v("Configure your DNS server with a CNAME record (AWS) or A record (GKE/AKS/DOKS)\nand point a domain e.g. "),e("code",[t._v("app.example.com")]),t._v(" to the LB address.")]),t._v(" "),e("p",[t._v("Create a HTTPProxy definition and include the podinfo proxy generated by Flagger\n(replace "),e("code",[t._v("app.example.com")]),t._v(" with your own domain):")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" projectcontour.io/v1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" HTTPProxy\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("ingress\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("virtualhost")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("fqdn")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" app.example.com\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("includes")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("conditions")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prefix")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n")])])]),e("p",[t._v("Save the above resource as podinfo-ingress.yaml and then apply it:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -f ./podinfo-ingress.yaml\n")])])]),e("p",[t._v("Verify that Contour processed the proxy definition with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" get httpproxies\n\nNAME FQDN STATUS\npodinfo valid\npodinfo-ingress app.example.com valid\n")])])]),e("p",[t._v("Now you can access podinfo UI using your domain address.")]),t._v(" "),e("p",[t._v("Note that you should be using HTTPS when exposing production workloads on internet.\nYou can obtain free TLS certs from Let's Encrypt, read this "),e("a",{attrs:{href:"https://github.com/stefanprodan/eks-contour-ingress",target:"_blank",rel:"noopener noreferrer"}},[t._v("guide"),e("OutboundLink")],1),t._v("\non how to configure cert-manager to secure Contour with TLS certificates.")]),t._v(" "),e("h2",{attrs:{id:"automated-canary-promotion"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-canary-promotion","aria-hidden":"true"}},[t._v("#")]),t._v(" Automated canary promotion")]),t._v(" "),e("p",[t._v("Flagger implements a control loop that gradually shifts traffic to the canary while measuring\nkey performance indicators like HTTP requests success rate, requests average duration and pod health.\nBased on analysis of the KPIs a canary is promoted or aborted.")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-steps.png",alt:"Flagger Canary Stages"}})]),t._v(" "),e("p",[t._v("A canary deployment is triggered by changes in any of the following objects:")]),t._v(" "),e("ul",[e("li",[t._v("Deployment PodSpec (container image, command, ports, env, resources, etc)")]),t._v(" "),e("li",[t._v("ConfigMaps and Secrets mounted as volumes or mapped to environment variables")])]),t._v(" "),e("p",[t._v("Trigger a canary deployment by updating the container image:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.1\n")])])]),e("p",[t._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 0\n Phase: Succeeded\nEvents:\n New revision detected! Scaling up podinfo.test\n Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\n Pre-rollout check acceptance-test passed\n Advance podinfo.test canary weight 5\n Advance podinfo.test canary weight 10\n Advance podinfo.test canary weight 15\n Advance podinfo.test canary weight 20\n Advance podinfo.test canary weight 25\n Advance podinfo.test canary weight 30\n Advance podinfo.test canary weight 35\n Advance podinfo.test canary weight 40\n Advance podinfo.test canary weight 45\n Advance podinfo.test canary weight 50\n Copying podinfo.test template spec to podinfo-primary.test\n Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Routing all traffic to primary\n Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[t._v("When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.")]),t._v(" "),e("p",[e("strong",[t._v("Note")]),t._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),t._v(" "),e("p",[t._v("You can monitor all canaries with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("15")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-12-20T14:05:07Z\n")])])]),e("p",[t._v("If you’ve enabled the Slack notifications, you should receive the following messages:")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/slack-canary-notifications.png",alt:"Flagger Slack Notifications"}})]),t._v(" "),e("h2",{attrs:{id:"automated-rollback"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[t._v("#")]),t._v(" Automated rollback")]),t._v(" "),e("p",[t._v("During the canary analysis you can generate HTTP 500 errors or high latency to test if Flagger pauses the rollout.")]),t._v(" "),e("p",[t._v("Trigger a canary deployment:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.2\n")])])]),e("p",[t._v("Exec into the load tester pod with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("exec")]),t._v(" -it deploy/flagger-loadtester "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("bash")]),t._v("\n")])])]),e("p",[t._v("Generate HTTP 500 errors:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("hey -z 1m -c "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v(" -q "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v(" http://app.example.com/status/500\n")])])]),e("p",[t._v("Generate latency:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" -n "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("1")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" http://app.example.com/delay/1\n")])])]),e("p",[t._v("When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary,\nthe canary is scaled to zero and the rollout is marked as failed.")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n projectcontour logs deploy/flagger -f | jq .msg\n\nNew revision detected! Starting canary analysis for podinfo.test\nPre-rollout check acceptance-test passed\nAdvance podinfo.test canary weight 5\nAdvance podinfo.test canary weight 10\nAdvance podinfo.test canary weight 15\nHalt podinfo.test advancement success rate 69.17% < 99%\nHalt podinfo.test advancement success rate 61.39% < 99%\nHalt podinfo.test advancement success rate 55.06% < 99%\nHalt podinfo.test advancement request duration 1.20s > 500ms\nHalt podinfo.test advancement request duration 1.45s > 500ms\nRolling back podinfo.test failed checks threshold reached 5\nCanary failed! Scaling down podinfo.test\n")])])]),e("p",[t._v("If you’ve enabled the Slack notifications, you’ll receive a message if the progress deadline is exceeded,\nor if the analysis reached the maximum number of failed checks:")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/slack-canary-failed.png",alt:"Flagger Slack Notifications"}})]),t._v(" "),e("h2",{attrs:{id:"a-b-testing"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#a-b-testing","aria-hidden":"true"}},[t._v("#")]),t._v(" A/B Testing")]),t._v(" "),e("p",[t._v("Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions.\nIn an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users.\nThis is particularly useful for frontend applications that require session affinity.")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-abtest-steps.png",alt:"Flagger A/B Testing Stages"}})]),t._v(" "),e("p",[t._v("Edit the canary analysis, remove the max/step weight and add the match conditions and iterations:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("iterations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("x-canary")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("exact")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"insider"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"hey -z 1m -q 5 -c 5 -H 'X-Canary: insider' -host app.example.com http://envoy.projectcontour\"")]),t._v("\n")])])]),e("p",[t._v("The above configuration will run an analysis for ten minutes targeting users that have a "),e("code",[t._v("X-Canary: insider")]),t._v(" header.")]),t._v(" "),e("p",[t._v("You can also use a HTTP cookie. To target all users with a cookie set to "),e("code",[t._v("insider")]),t._v(", the match condition should be:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cookie")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("suffix")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"insider"')]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"hey -z 1m -q 5 -c 5 -H 'Cookie: canary=insider' -host app.example.com http://envoy.projectcontour\"")]),t._v("\n")])])]),e("p",[t._v("Trigger a canary deployment by updating the container image:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.3\n")])])]),e("p",[t._v("Flagger detects that the deployment revision changed and starts the A/B test:")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n projectcontour logs deploy/flagger -f | jq .msg\n\nNew revision detected! Starting canary analysis for podinfo.test\nAdvance podinfo.test canary iteration 1/10\nAdvance podinfo.test canary iteration 2/10\nAdvance podinfo.test canary iteration 3/10\nAdvance podinfo.test canary iteration 4/10\nAdvance podinfo.test canary iteration 5/10\nAdvance podinfo.test canary iteration 6/10\nAdvance podinfo.test canary iteration 7/10\nAdvance podinfo.test canary iteration 8/10\nAdvance podinfo.test canary iteration 9/10\nAdvance podinfo.test canary iteration 10/10\nCopying podinfo.test template spec to podinfo-primary.test\nWaiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\nRouting all traffic to primary\nPromotion completed! Scaling down podinfo.test\n")])])]),e("p",[t._v("The web browser user agent header allows user segmentation based on device or OS.")]),t._v(" "),e("p",[t._v("For example, if you want to route all mobile users to the canary instance:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("user-agent")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prefix")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Mobile"')]),t._v("\n")])])]),e("p",[t._v("Or if you want to target only Android users:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("user-agent")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prefix")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Android"')]),t._v("\n")])])]),e("p",[t._v("Or a specific browser version:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("user-agent")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("suffix")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Firefox/71.0"')]),t._v("\n")])])]),e("p",[t._v("For an in-depth look at the analysis process read the "),e("router-link",{attrs:{to:"/usage/how-it-works.html"}},[t._v("usage docs")]),t._v(".")],1)])},[],!1,null,null,null);a.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/20.dce5a04e.js b/assets/js/20.dce5a04e.js new file mode 100644 index 00000000..f5694bbb --- /dev/null +++ b/assets/js/20.dce5a04e.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[20],{281:function(a,t,e){"use strict";e.r(t);var s=e(37),n=Object(s.a)({},function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[e("h1",{attrs:{id:"crossover-canary-deployments"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#crossover-canary-deployments","aria-hidden":"true"}},[a._v("#")]),a._v(" Crossover Canary Deployments")]),a._v(" "),e("p",[a._v("This guide shows you how to use Envoy, "),e("a",{attrs:{href:"https://github.com/mumoshu/crossover",target:"_blank",rel:"noopener noreferrer"}},[a._v("Crossover"),e("OutboundLink")],1),a._v(" and Flagger to automate canary deployments.")]),a._v(" "),e("p",[a._v("Crossover is a minimal Envoy xDS implementation supports "),e("a",{attrs:{href:"https://smi-spec.io/",target:"_blank",rel:"noopener noreferrer"}},[a._v("Service Mesh Interface"),e("OutboundLink")],1),a._v(".")]),a._v(" "),e("h2",{attrs:{id:"prerequisites"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[a._v("#")]),a._v(" Prerequisites")]),a._v(" "),e("p",[a._v("Flagger requires a Kubernetes cluster "),e("strong",[a._v("v1.11")]),a._v(" or newer and Envoy paired with "),e("a",{attrs:{href:"https://github.com/mumoshu/crossover",target:"_blank",rel:"noopener noreferrer"}},[a._v("Crossover"),e("OutboundLink")],1),a._v(" sidecar.")]),a._v(" "),e("p",[a._v("Create a test namespace:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl create ns "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v("\n")])])]),e("p",[a._v("Install Envoy along with the Crossover sidecar with Helm:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm repo "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" crossover https://mumoshu.github.io/crossover\n\nhelm upgrade --install envoy crossover/envoy "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n --namespace "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n -f "),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("(")]),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("cat")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<<")]),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("EOF\nsmi:\n apiVersions:\n trafficSplits: v1alpha1\nupstreams:\n podinfo:\n smi:\n enabled: true\n backends:\n podinfo-primary:\n port: 9898\n weight: 100\n podinfo-canary:\n port: 9898\n weight: 0\nEOF")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(")")]),a._v("\n")])])]),e("p",[a._v("Install Flagger and the Prometheus add-on in the same namespace as Envoy:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm repo "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" flagger https://flagger.app\n\nhelm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set prometheus.install"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("true "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshProvider")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("smi:crossover\n")])])]),e("p",[a._v("Optionally you can enable Slack notifications:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--reuse-values "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.url"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.channel"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("general "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.user"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("flagger\n")])])]),e("h2",{attrs:{id:"bootstrap"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#bootstrap","aria-hidden":"true"}},[a._v("#")]),a._v(" Bootstrap")]),a._v(" "),e("p",[a._v("Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),\nthen creates a series of objects (Kubernetes deployments, ClusterIP services, SMI traffic splits).\nThese objects expose the application on the mesh and drive the canary analysis and promotion.\nThere's no SMI object you need to create by yourself.")]),a._v(" "),e("p",[a._v("Create a deployment and a horizontal pod autoscaler:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),e("p",[a._v("Deploy the load testing service to generate traffic during the canary analysis:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm upgrade -i flagger-loadtester flagger/loadtester "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("test\n")])])]),e("p",[a._v("Create a canary custom resource:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# specify mesh provider if it isn't the default one")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v('# provider: "smi:crossover"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# deployment reference")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# the maximum time in seconds for the canary deployment")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# to make progress before it is rollback (default 600s)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("progressDeadlineSeconds")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("60")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# HPA reference (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# ClusterIP port number")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# container port number or name (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("targetPort")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# define the canary analysis timing and KPIs")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# schedule interval (default 60s)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max number of failed metric checks before rollback")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max traffic percentage routed to canary")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("maxWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("50")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# canary increment step")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("stepWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# App Mesh Prometheus checks")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rate\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# minimum req success rate (non 5xx responses)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("duration\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# maximum req duration P99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# milliseconds")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("500")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# testing (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" acceptance"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("\"curl -sd 'test' http://podinfo-canary.test:9898/token | grep token\"")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("\"hey -z 1m -q 10 -c 2 -H 'Host: podinfo.test' http://envoy.test:10000/\"")]),a._v("\n")])])]),e("p",[a._v("Save the above resource as podinfo-canary.yaml and then apply it:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -f ./podinfo-canary.yaml\n")])])]),e("p",[a._v("After a couple of seconds Flagger will create the canary objects:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# applied ")]),a._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\ncanary.flagger.app/podinfo\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generated")]),a._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\ntrafficsplits.split.smi-spec.io/podinfo\n")])])]),e("p",[a._v("After the boostrap, the podinfo deployment will be scaled to zero and the traffic to "),e("code",[a._v("podinfo.test")]),a._v("\nwill be routed to the primary pods. During the canary analysis,\nthe "),e("code",[a._v("podinfo-canary.test")]),a._v(" address can be used to target directly the canary pods.")]),a._v(" "),e("h2",{attrs:{id:"automated-canary-promotion"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-canary-promotion","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated canary promotion")]),a._v(" "),e("p",[a._v("Flagger implements a control loop that gradually shifts traffic to the canary while measuring\nkey performance indicators like HTTP requests success rate, requests average duration and pod health.\nBased on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack.")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-steps.png",alt:"Flagger Canary Stages"}})]),a._v(" "),e("p",[a._v("A canary deployment is triggered by changes in any of the following objects:")]),a._v(" "),e("ul",[e("li",[a._v("Deployment PodSpec (container image, command, ports, env, resources, etc)")]),a._v(" "),e("li",[a._v("ConfigMaps and Secrets mounted as volumes or mapped to environment variables")])]),a._v(" "),e("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.5\n")])])]),e("p",[a._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 0\n Phase: Succeeded\nEvents:\n New revision detected! Scaling up podinfo.test\n Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\n Pre-rollout check acceptance-test passed\n Advance podinfo.test canary weight 5\n Advance podinfo.test canary weight 10\n Advance podinfo.test canary weight 15\n Advance podinfo.test canary weight 20\n Advance podinfo.test canary weight 25\n Advance podinfo.test canary weight 30\n Advance podinfo.test canary weight 35\n Advance podinfo.test canary weight 40\n Advance podinfo.test canary weight 45\n Advance podinfo.test canary weight 50\n Copying podinfo.test template spec to podinfo-primary.test\n Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Routing all traffic to primary\n Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[a._v("When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.")]),a._v(" "),e("p",[e("strong",[a._v("Note")]),a._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),a._v(" "),e("p",[a._v("During the analysis the canary’s progress can be monitored with Grafana.")]),a._v(" "),e("p",[a._v("Flagger comes with a Grafana dashboard made for canary analysis. Install Grafana with Helm:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm upgrade -i flagger-grafana flagger/grafana "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("test "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://flagger-prometheus:9090\n")])])]),e("p",[a._v("Run:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl port-forward --namespace "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" svc/flagger-grafana "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("3000")]),a._v(":80\n")])])]),e("p",[a._v("The Envoy dashboard URL is "),e("a",{attrs:{href:"http://localhost:3000/d/flagger-envoy/envoy-canary?refresh=10s&orgId=1&var-namespace=test&var-target=podinfo",target:"_blank",rel:"noopener noreferrer"}},[a._v("http://localhost:3000/d/flagger-envoy/envoy-canary?refresh=10s&orgId=1&var-namespace=test&var-target=podinfo"),e("OutboundLink")],1)]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/flagger-grafana-appmesh.png",alt:"Envoy Canary Dashboard"}})]),a._v(" "),e("p",[a._v("You can monitor all canaries with:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("15")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-10-02T14:05:07Z\nprod frontend Succeeded "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-10-02T16:15:07Z\nprod backend Failed "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-10-02T17:05:07Z\n")])])]),e("p",[a._v("If you’ve enabled the Slack notifications, you should receive the following messages:")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/slack-canary-notifications.png",alt:"Flagger Slack Notifications"}})]),a._v(" "),e("h2",{attrs:{id:"automated-rollback"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated rollback")]),a._v(" "),e("p",[a._v("During the canary analysis you can generate HTTP 500 errors or high latency to test if Flagger pauses the rollout.")]),a._v(" "),e("p",[a._v("Trigger a canary deployment:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.2\n")])])]),e("p",[a._v("Exec into the load tester pod with:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("exec")]),a._v(" -it deploy/flagger-loadtester "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("bash")]),a._v("\n")])])]),e("p",[a._v("Generate HTTP 500 errors:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("hey -z 1m -c "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v(" -q "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v(" -H "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("'Host: podinfo.test'")]),a._v(" http://envoy.test:10000/status/500\n")])])]),e("p",[a._v("Generate latency:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" -n "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("1")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" -H "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("'Host: podinfo.test'")]),a._v(" http://envoy.test:10000/delay/1\n")])])]),e("p",[a._v("When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary,\nthe canary is scaled to zero and the rollout is marked as failed.")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test logs deploy/flagger -f | jq .msg\n\nNew revision detected! Starting canary analysis for podinfo.test\nPre-rollout check acceptance-test passed\nAdvance podinfo.test canary weight 5\nAdvance podinfo.test canary weight 10\nAdvance podinfo.test canary weight 15\nHalt podinfo.test advancement success rate 69.17% < 99%\nHalt podinfo.test advancement success rate 61.39% < 99%\nHalt podinfo.test advancement success rate 55.06% < 99%\nHalt podinfo.test advancement request duration 1.20s > 0.5s\nHalt podinfo.test advancement request duration 1.45s > 0.5s\nRolling back podinfo.test failed checks threshold reached 5\nCanary failed! Scaling down podinfo.test\n")])])]),e("p",[a._v("If you’ve enabled the Slack notifications, you’ll receive a message if the progress deadline is exceeded,\nor if the analysis reached the maximum number of failed checks:")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/slack-canary-failed.png",alt:"Flagger Slack Notifications"}})])])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/21.df66561d.js b/assets/js/21.df66561d.js new file mode 100644 index 00000000..d96d19b1 --- /dev/null +++ b/assets/js/21.df66561d.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[21],{280:function(a,t,s){"use strict";s.r(t);var e=s(37),n=Object(e.a)({},function(){var a=this,t=a.$createElement,s=a._self._c||t;return s("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[s("h1",{attrs:{id:"smi-istio-canary-deployments"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#smi-istio-canary-deployments","aria-hidden":"true"}},[a._v("#")]),a._v(" SMI Istio Canary Deployments")]),a._v(" "),s("p",[a._v("This guide shows you how to use the SMI Istio adapter and Flagger to automate canary deployments.")]),a._v(" "),s("h2",{attrs:{id:"prerequisites"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[a._v("#")]),a._v(" Prerequisites")]),a._v(" "),s("ul",[s("li",[a._v("Kubernetes > 1.13")]),a._v(" "),s("li",[a._v("Istio > 1.0")])]),a._v(" "),s("h2",{attrs:{id:"install-istio-smi-adapter"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#install-istio-smi-adapter","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Istio SMI adapter")]),a._v(" "),s("p",[a._v("Install the SMI adapter:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl apply -f https://raw.githubusercontent.com/deislabs/smi-adapter-istio/master/deploy/crds/crds.yaml\nkubectl apply -f https://raw.githubusercontent.com/deislabs/smi-adapter-istio/master/deploy/operator-and-rbac.yaml\n")])])]),s("p",[a._v("Create a generic Istio gateway to expose services outside the mesh on HTTP:")]),a._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" networking.istio.io/v1alpha3\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Gateway\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" public"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("gateway\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("system\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("selector")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("istio")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" ingressgateway\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("servers")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("number")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("80")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("protocol")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" HTTP\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("hosts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"*"')]),a._v("\n")])])]),s("p",[a._v("Save the above resource as public-gateway.yaml and then apply it:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl apply -f ./public-gateway.yaml\n")])])]),s("p",[a._v("Find the Gateway load balancer IP and add a DNS record for it:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n istio-system get svc/istio-ingressgateway -ojson "),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("|")]),a._v(" jq -r .status.loadBalancer.ingress"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("[")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("]")]),a._v(".ip\n")])])]),s("h2",{attrs:{id:"install-flagger-and-grafana"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#install-flagger-and-grafana","aria-hidden":"true"}},[a._v("#")]),a._v(" Install Flagger and Grafana")]),a._v(" "),s("p",[a._v("Add Flagger Helm repository:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm repo "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" flagger https://flagger.app\n")])])]),s("p",[a._v("Deploy Flagger in the "),s("em",[s("strong",[a._v("istio-system")])]),a._v(" namespace:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i flagger flagger/flagger "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshProvider")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("smi:istio\n")])])]),s("p",[a._v("Flagger comes with a Grafana dashboard made for monitoring the canary deployments.")]),a._v(" "),s("p",[a._v("Deploy Grafana in the "),s("em",[s("strong",[a._v("istio-system")])]),a._v(" namespace:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i flagger-grafana flagger/grafana "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("url")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://prometheus.istio-system:9090\n")])])]),s("p",[a._v("You can access Grafana using port forwarding:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n istio-system port-forward svc/flagger-grafana "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("3000")]),a._v(":80\n")])])]),s("h2",{attrs:{id:"workloads-bootstrap"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#workloads-bootstrap","aria-hidden":"true"}},[a._v("#")]),a._v(" Workloads bootstrap")]),a._v(" "),s("p",[a._v("Create a test namespace with Istio sidecar injection enabled:")]),a._v(" "),s("p",[a._v("Create a test namespace and enable Linkerd proxy injection:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl create ns "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v("\nkubectl label namespace "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" istio-injection"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("enabled\n")])])]),s("p",[a._v("Create a deployment and a horizontal pod autoscaler:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),s("p",[a._v("Deploy the load testing service to generate traffic during the canary analysis:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/tester\n")])])]),s("p",[a._v("Create a canary custom resource (replace example.com with your own domain):")]),a._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# deployment reference")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("targetRef")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" apps/v1\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Deployment\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# the maximum time in seconds for the canary deployment")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# to make progress before it is rollback (default 600s)")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("progressDeadlineSeconds")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("60")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# HPA reference (optional)")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("autoscalerRef")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" autoscaling/v2beta1\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" HorizontalPodAutoscaler\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# container port")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Istio gateways (optional)")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("gateways")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" public"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("gateway.istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("system.svc.cluster.local\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Istio virtual service host names (optional)")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("hosts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" app.example.com\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# schedule interval (default 60s)")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 10s\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max number of failed metric checks before rollback")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("threshold")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max traffic percentage routed to canary")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("maxWeight")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("50")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# canary increment step")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("stepWeight")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("10")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metrics")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("success"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rate\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# minimum req success rate (non 5xx responses)")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("min")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("99")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("duration\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# maximum req duration P99")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# milliseconds")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("max")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("500")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 30s\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generate traffic during analysis")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" load"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 5s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"hey -z 1m -q 10 -c 2 http://podinfo.test:9898/"')]),a._v("\n")])])]),s("p",[a._v("Save the above resource as podinfo-canary.yaml and then apply it:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl apply -f ./podinfo-canary.yaml\n")])])]),s("p",[a._v("After a couple of seconds Flagger will create the canary objects:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# applied ")]),a._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\ncanary.flagger.app/podinfo\n\n"),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generated ")]),a._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\ntrafficsplits.split.smi-spec.io/podinfo\n")])])]),s("h2",{attrs:{id:"automated-canary-promotion"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#automated-canary-promotion","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated canary promotion")]),a._v(" "),s("p",[a._v("Flagger implements a control loop that gradually shifts traffic to the canary while measuring key performance indicators like HTTP requests success rate, requests average duration and pod health. Based on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack.")]),a._v(" "),s("p",[s("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-steps.png",alt:"Flagger Canary Stages"}})]),a._v(" "),s("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("quay.io/stefanprodan/podinfo:3.1.1\n")])])]),s("p",[a._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("kubectl -n istio-system logs deployment/flagger -f | jq .msg\n\n\nNew revision detected podinfo.test\nScaling up podinfo.test\nWaiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\nAdvance podinfo.test canary weight 5\nAdvance podinfo.test canary weight 10\nAdvance podinfo.test canary weight 15\nAdvance podinfo.test canary weight 20\nAdvance podinfo.test canary weight 25\nAdvance podinfo.test canary weight 30\nAdvance podinfo.test canary weight 35\nAdvance podinfo.test canary weight 40\nAdvance podinfo.test canary weight 45\nAdvance podinfo.test canary weight 50\nCopying podinfo.test template spec to podinfo-primary.test\nWaiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\nPromotion completed! Scaling down podinfo.test\n")])])]),s("p",[s("strong",[a._v("Note")]),a._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),a._v(" "),s("p",[a._v("During the analysis the canary’s progress can be monitored with Grafana. The Istio dashboard URL is "),s("a",{attrs:{href:"http://localhost:3000/d/flagger-istio/istio-canary?refresh=10s&orgId=1&var-namespace=test&var-primary=podinfo-primary&var-canary=podinfo",target:"_blank",rel:"noopener noreferrer"}},[a._v("http://localhost:3000/d/flagger-istio/istio-canary?refresh=10s&orgId=1&var-namespace=test&var-primary=podinfo-primary&var-canary=podinfo"),s("OutboundLink")],1)]),a._v(" "),s("p",[a._v("You can monitor all canaries with:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" podinfo Progressing "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("15")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-16T14:05:07Z\nprod frontend Succeeded "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-15T16:15:07Z\nprod backend Failed "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-14T17:05:07Z\n")])])]),s("h2",{attrs:{id:"automated-rollback"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated rollback")]),a._v(" "),s("p",[a._v("During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses the rollout.")]),a._v(" "),s("p",[a._v("Create a tester pod and exec into it:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" run tester "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--image"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("quay.io/stefanprodan/podinfo:3.1.2 "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n-- ./podinfo --port"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n\nkubectl -n "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("exec")]),a._v(" -it tester-xx-xx "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("sh")]),a._v("\n")])])]),s("p",[a._v("Generate HTTP 500 errors:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" http://podinfo-canary:9898/status/500\n")])])]),s("p",[a._v("Generate latency:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" http://podinfo-canary:9898/delay/1\n")])])]),s("p",[a._v("When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 10\n Phase: Failed\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger Starting canary deployment for podinfo.test\n Normal Synced 3m flagger Advance podinfo.test canary weight 5\n Normal Synced 3m flagger Advance podinfo.test canary weight 10\n Normal Synced 3m flagger Advance podinfo.test canary weight 15\n Normal Synced 3m flagger Halt podinfo.test advancement success rate 69.17% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 61.39% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 55.06% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 47.00% < 99%\n Normal Synced 2m flagger (combined from similar events): Halt podinfo.test advancement success rate 38.08% < 99%\n Warning Synced 1m flagger Rolling back podinfo.test failed checks threshold reached 10\n Warning Synced 1m flagger Canary failed! Scaling down podinfo.test\n")])])])])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/22.1c5d14b9.js b/assets/js/22.1c5d14b9.js new file mode 100644 index 00000000..a45adaac --- /dev/null +++ b/assets/js/22.1c5d14b9.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[22],{282:function(a,t,e){"use strict";e.r(t);var s=e(37),n=Object(s.a)({},function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[e("h1",{attrs:{id:"gloo-canary-deployments"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#gloo-canary-deployments","aria-hidden":"true"}},[a._v("#")]),a._v(" Gloo Canary Deployments")]),a._v(" "),e("p",[a._v("This guide shows you how to use the "),e("a",{attrs:{href:"https://gloo.solo.io/",target:"_blank",rel:"noopener noreferrer"}},[a._v("Gloo"),e("OutboundLink")],1),a._v(" ingress controller and Flagger to automate canary deployments.")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-gloo-overview.png",alt:"Flagger Gloo Ingress Controller"}})]),a._v(" "),e("h2",{attrs:{id:"prerequisites"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[a._v("#")]),a._v(" Prerequisites")]),a._v(" "),e("p",[a._v("Flagger requires a Kubernetes cluster "),e("strong",[a._v("v1.11")]),a._v(" or newer and Gloo ingress "),e("strong",[a._v("1.3.5")]),a._v(" or newer.")]),a._v(" "),e("p",[a._v("Install Gloo with Helm v3:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm repo "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" gloo https://storage.googleapis.com/solo-public-helm\nkubectl create ns gloo-system\nhelm upgrade -i gloo gloo/gloo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace gloo-system\n")])])]),e("p",[a._v("Install Flagger and the Prometheus add-on in the same namespace as Gloo:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm repo "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" flagger https://flagger.app\n\nhelm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace gloo-system "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set prometheus.install"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("true "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshProvider")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("gloo\n")])])]),e("h2",{attrs:{id:"bootstrap"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#bootstrap","aria-hidden":"true"}},[a._v("#")]),a._v(" Bootstrap")]),a._v(" "),e("p",[a._v("Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),\nthen creates a series of objects (Kubernetes deployments, ClusterIP services and Gloo upstream groups).\nThese objects expose the application outside the cluster and drive the canary analysis and promotion.")]),a._v(" "),e("p",[a._v("Create a test namespace:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl create ns "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v("\n")])])]),e("p",[a._v("Create a deployment and a horizontal pod autoscaler:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),e("p",[a._v("Deploy the load testing service to generate traffic during the canary analysis:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" apply -k github.com/weaveworks/flagger//kustomize/tester\n")])])]),e("p",[a._v("Create an virtual service definition that references an upstream group that will be generated by Flagger\n(replace "),e("code",[a._v("app.example.com")]),a._v(" with your own domain):")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" gateway.solo.io/v1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" VirtualService\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("virtualHost")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("domains")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("'app.example.com'")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("routes")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("matchers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("prefix")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" /\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("routeAction")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("upstreamGroup")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n")])])]),e("p",[a._v("Save the above resource as podinfo-virtualservice.yaml and then apply it:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -f ./podinfo-virtualservice.yaml\n")])])]),e("p",[a._v("Create a canary custom resource (replace "),e("code",[a._v("app.example.com")]),a._v(" with your own domain):")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" gloo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# deployment reference")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# HPA reference (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# ClusterIP port number")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# container port number or name (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("targetPort")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# schedule interval (default 60s)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 10s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max number of failed metric checks before rollback")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max traffic percentage routed to canary")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("maxWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("50")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# canary increment step")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("stepWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Gloo Prometheus checks")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rate\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# minimum req success rate (non 5xx responses)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("duration\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# maximum req duration P99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# milliseconds")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("500")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# testing (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" acceptance"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 10s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("\"curl -sd 'test' http://podinfo-canary:9898/token | grep token\"")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" cmd\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"hey -z 2m -q 5 -c 2 -host app.example.com http://gateway-proxy.gloo-system"')]),a._v("\n")])])]),e("p",[a._v("Save the above resource as podinfo-canary.yaml and then apply it:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -f ./podinfo-canary.yaml\n")])])]),e("p",[a._v("After a couple of seconds Flagger will create the canary objects:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# applied ")]),a._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\nvirtualservices.gateway.solo.io/podinfo\ncanary.flagger.app/podinfo\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generated ")]),a._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\nupstreamgroups.gloo.solo.io/podinfo\n")])])]),e("p",[a._v("When the bootstrap finishes Flagger will set the canary status to initialized:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" get canary podinfo\n\nNAME STATUS WEIGHT LASTTRANSITIONTIME\npodinfo Initialized "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-17T08:09:51Z\n")])])]),e("h2",{attrs:{id:"automated-canary-promotion"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-canary-promotion","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated canary promotion")]),a._v(" "),e("p",[a._v("Flagger implements a control loop that gradually shifts traffic to the canary while measuring\nkey performance indicators like HTTP requests success rate, requests average duration and pod health.\nBased on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack.")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-steps.png",alt:"Flagger Canary Stages"}})]),a._v(" "),e("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.1\n")])])]),e("p",[a._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 0\n Phase: Succeeded\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger New revision detected podinfo.test\n Normal Synced 3m flagger Scaling up podinfo.test\n Warning Synced 3m flagger Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\n Normal Synced 3m flagger Advance podinfo.test canary weight 5\n Normal Synced 3m flagger Advance podinfo.test canary weight 10\n Normal Synced 3m flagger Advance podinfo.test canary weight 15\n Normal Synced 2m flagger Advance podinfo.test canary weight 20\n Normal Synced 2m flagger Advance podinfo.test canary weight 25\n Normal Synced 1m flagger Advance podinfo.test canary weight 30\n Normal Synced 1m flagger Advance podinfo.test canary weight 35\n Normal Synced 55s flagger Advance podinfo.test canary weight 40\n Normal Synced 45s flagger Advance podinfo.test canary weight 45\n Normal Synced 35s flagger Advance podinfo.test canary weight 50\n Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test\n Warning Synced 15s flagger Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[e("strong",[a._v("Note")]),a._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),a._v(" "),e("p",[a._v("You can monitor all canaries with:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("15")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-17T14:05:07Z\nprod frontend Succeeded "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-17T16:15:07Z\nprod backend Failed "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-17T17:05:07Z\n")])])]),e("h2",{attrs:{id:"automated-rollback"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated rollback")]),a._v(" "),e("p",[a._v("During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses and rolls back the faulted version.")]),a._v(" "),e("p",[a._v("Trigger another canary deployment:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.2\n")])])]),e("p",[a._v("Generate HTTP 500 errors:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" -H "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("'Host: app.example.com'")]),a._v(" http://gateway-proxy-v2.gloo-system/status/500\n")])])]),e("p",[a._v("Generate high latency:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" -H "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("'Host: app.example.com'")]),a._v(" http://gateway-proxy-v2.gloo-system/delay/2\n")])])]),e("p",[a._v("When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary,\nthe canary is scaled to zero and the rollout is marked as failed.")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 10\n Phase: Failed\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger Starting canary deployment for podinfo.test\n Normal Synced 3m flagger Advance podinfo.test canary weight 5\n Normal Synced 3m flagger Advance podinfo.test canary weight 10\n Normal Synced 3m flagger Advance podinfo.test canary weight 15\n Normal Synced 3m flagger Halt podinfo.test advancement success rate 69.17% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 61.39% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 55.06% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 47.00% < 99%\n Normal Synced 2m flagger (combined from similar events): Halt podinfo.test advancement success rate 38.08% < 99%\n Warning Synced 1m flagger Rolling back podinfo.test failed checks threshold reached 10\n Warning Synced 1m flagger Canary failed! Scaling down podinfo.test\n")])])]),e("h2",{attrs:{id:"custom-metrics"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#custom-metrics","aria-hidden":"true"}},[a._v("#")]),a._v(" Custom metrics")]),a._v(" "),e("p",[a._v("The canary analysis can be extended with Prometheus queries.")]),a._v(" "),e("p",[a._v("The demo app is instrumented with Prometheus so you can create a custom check that will use\nthe HTTP request duration histogram to validate the canary.")]),a._v(" "),e("p",[a._v("Create a metric template and apply it on the cluster:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" MetricTemplate\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" not"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("found"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("percentage\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" prometheus\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("address")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("promethues.gloo"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("system"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9090")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("query")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("|")]),e("span",{pre:!0,attrs:{class:"token scalar string"}},[a._v('\n 100 - sum(\n rate(\n http_request_duration_seconds_count{\n kubernetes_namespace="{{ namespace }}",\n kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"\n status!="{{ interval }}"\n }[1m]\n )\n )\n /\n sum(\n rate(\n http_request_duration_seconds_count{\n kubernetes_namespace="{{ namespace }}",\n kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"\n }[{{ interval }}]\n )\n ) * 100')]),a._v("\n")])])]),e("p",[a._v("Edit the canary analysis and add the following metric:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"404s percentage"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("templateRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" not"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("found"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("percentage\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n")])])]),e("p",[a._v("The above configuration validates the canary by checking if the HTTP 404 req/sec percentage is\nbelow 5 percent of the total traffic. If the 404s rate reaches the 5% threshold, then the canary fails.")]),a._v(" "),e("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.3\n")])])]),e("p",[a._v("Generate 404s:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" -H "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("'Host: app.example.com'")]),a._v(" http://gateway-proxy.gloo-system/status/400\n")])])]),e("p",[a._v("Watch Flagger logs:")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n gloo-system logs deployment/flagger -f | jq .msg\n\nStarting canary deployment for podinfo.test\nAdvance podinfo.test canary weight 5\nAdvance podinfo.test canary weight 10\nAdvance podinfo.test canary weight 15\nHalt podinfo.test advancement 404s percentage 6.20 > 5\nHalt podinfo.test advancement 404s percentage 6.45 > 5\nHalt podinfo.test advancement 404s percentage 7.60 > 5\nHalt podinfo.test advancement 404s percentage 8.69 > 5\nHalt podinfo.test advancement 404s percentage 9.70 > 5\nRolling back podinfo.test failed checks threshold reached 5\nCanary failed! Scaling down podinfo.test\n")])])]),e("p",[a._v("If you have "),e("router-link",{attrs:{to:"/usage/alerting.html"}},[a._v("alerting")]),a._v(" configured,\nFlagger will send a notification with the reason why the canary failed.")],1),a._v(" "),e("p",[a._v("For an in-depth look at the analysis process read the "),e("router-link",{attrs:{to:"/usage/how-it-works.html"}},[a._v("usage docs")]),a._v(".")],1)])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/23.b3dcc355.js b/assets/js/23.b3dcc355.js new file mode 100644 index 00000000..ea5f9616 --- /dev/null +++ b/assets/js/23.b3dcc355.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[23],{283:function(a,t,e){"use strict";e.r(t);var s=e(37),n=Object(s.a)({},function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[e("h1",{attrs:{id:"istio-a-b-testing"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#istio-a-b-testing","aria-hidden":"true"}},[a._v("#")]),a._v(" Istio A/B Testing")]),a._v(" "),e("p",[a._v("This guide shows you how to automate A/B testing with Istio and Flagger.")]),a._v(" "),e("p",[a._v("Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions.\nIn an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users.\nThis is particularly useful for frontend applications that require session affinity.")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-abtest-steps.png",alt:"Flagger A/B Testing Stages"}})]),a._v(" "),e("h2",{attrs:{id:"prerequisites"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[a._v("#")]),a._v(" Prerequisites")]),a._v(" "),e("p",[a._v("Flagger requires a Kubernetes cluster "),e("strong",[a._v("v1.11")]),a._v(" or newer and Istio "),e("strong",[a._v("v1.0")]),a._v(" or newer.")]),a._v(" "),e("p",[a._v("Install Istio with telemetry support and Prometheus:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("istioctl manifest apply --set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("profile")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("default\n")])])]),e("p",[a._v("Install Flagger using Kustomize (kubectl 1.14) in the "),e("code",[a._v("istio-system")]),a._v(" namespace:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/istio\n")])])]),e("p",[a._v("Create an ingress gateway to expose the demo app outside of the mesh:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" networking.istio.io/v1alpha3\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Gateway\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" public"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("gateway\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("system\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("selector")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("istio")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" ingressgateway\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("servers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("number")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("80")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("protocol")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" HTTP\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("hosts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"*"')]),a._v("\n")])])]),e("h2",{attrs:{id:"bootstrap"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#bootstrap","aria-hidden":"true"}},[a._v("#")]),a._v(" Bootstrap")]),a._v(" "),e("p",[a._v("Create a test namespace with Istio sidecar injection enabled:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl create ns "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v("\nkubectl label namespace "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" istio-injection"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("enabled\n")])])]),e("p",[a._v("Create a deployment and a horizontal pod autoscaler:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),e("p",[a._v("Deploy the load testing service to generate traffic during the canary analysis:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/tester\n")])])]),e("p",[a._v("Create a canary custom resource (replace example.com with your own domain):")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# deployment reference")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# the maximum time in seconds for the canary deployment")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# to make progress before it is rollback (default 600s)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("progressDeadlineSeconds")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("60")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# HPA reference (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# container port")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Istio gateways (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("gateways")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" public"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("gateway.istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("system.svc.cluster.local\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Istio virtual service host names (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("hosts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" app.example.com\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Istio traffic policy (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("trafficPolicy")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("tls")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# use ISTIO_MUTUAL when mTLS is enabled")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("mode")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" DISABLE\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# schedule interval (default 60s)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# total number of iterations")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("iterations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("10")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max number of failed iterations before rollback")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# canary match condition")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("user-agent")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("regex")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"^(?!.*Chrome).*Safari.*"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cookie")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("regex")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"^(.*?;)?(type=insider)(;.*)?$"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rate\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# minimum req success rate (non 5xx responses)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("duration\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# maximum req duration P99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# milliseconds")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("500")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generate traffic during analysis")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("\"hey -z 1m -q 10 -c 2 -H 'Cookie: type=insider' http://podinfo.test:9898/\"")]),a._v("\n")])])]),e("p",[a._v("The above configuration will run an analysis for ten minutes targeting Safari users and those that have an insider cookie.")]),a._v(" "),e("p",[a._v("Save the above resource as podinfo-abtest.yaml and then apply it:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -f ./podinfo-abtest.yaml\n")])])]),e("p",[a._v("After a couple of seconds Flagger will create the canary objects:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# applied ")]),a._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\ncanary.flagger.app/podinfo\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generated ")]),a._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\ndestinationrule.networking.istio.io/podinfo-canary\ndestinationrule.networking.istio.io/podinfo-primary\nvirtualservice.networking.istio.io/podinfo\n")])])]),e("h2",{attrs:{id:"automated-canary-promotion"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-canary-promotion","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated canary promotion")]),a._v(" "),e("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.1\n")])])]),e("p",[a._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test describe canary/abtest\n\nStatus:\n Failed Checks: 0\n Phase: Succeeded\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger New revision detected podinfo.test\n Normal Synced 3m flagger Scaling up podinfo.test\n Warning Synced 3m flagger Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\n Normal Synced 3m flagger Advance podinfo.test canary iteration 1/10\n Normal Synced 3m flagger Advance podinfo.test canary iteration 2/10\n Normal Synced 3m flagger Advance podinfo.test canary iteration 3/10\n Normal Synced 2m flagger Advance podinfo.test canary iteration 4/10\n Normal Synced 2m flagger Advance podinfo.test canary iteration 5/10\n Normal Synced 1m flagger Advance podinfo.test canary iteration 6/10\n Normal Synced 1m flagger Advance podinfo.test canary iteration 7/10\n Normal Synced 55s flagger Advance podinfo.test canary iteration 8/10\n Normal Synced 45s flagger Advance podinfo.test canary iteration 9/10\n Normal Synced 35s flagger Advance podinfo.test canary iteration 10/10\n Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test\n Warning Synced 15s flagger Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[e("strong",[a._v("Note")]),a._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),a._v(" "),e("p",[a._v("You can monitor all canaries with:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("100")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-03-16T14:05:07Z\nprod frontend Succeeded "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-03-15T16:15:07Z\nprod backend Failed "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-03-14T17:05:07Z\n")])])]),e("h2",{attrs:{id:"automated-rollback"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated rollback")]),a._v(" "),e("p",[a._v("During the canary analysis you can generate HTTP 500 errors and high latency to test Flagger's rollback.")]),a._v(" "),e("p",[a._v("Generate HTTP 500 errors:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" -b "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("'type=insider'")]),a._v(" http://app.example.com/status/500\n")])])]),e("p",[a._v("Generate latency:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" -b "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("'type=insider'")]),a._v(" http://app.example.com/delay/1\n")])])]),e("p",[a._v("When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary,\nthe canary is scaled to zero and the rollout is marked as failed.")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Failed Checks: 2\n Phase: Failed\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger Starting canary deployment for podinfo.test\n Normal Synced 3m flagger Advance podinfo.test canary iteration 1/10\n Normal Synced 3m flagger Advance podinfo.test canary iteration 2/10\n Normal Synced 3m flagger Advance podinfo.test canary iteration 3/10\n Normal Synced 3m flagger Halt podinfo.test advancement success rate 69.17% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 61.39% < 99%\n Warning Synced 2m flagger Rolling back podinfo.test failed checks threshold reached 2\n Warning Synced 1m flagger Canary failed! Scaling down podinfo.test\n")])])]),e("p",[a._v("The above procedure can be extended with "),e("router-link",{attrs:{to:"/usage/metrics.html"}},[a._v("custom metrics")]),a._v(" checks,\n"),e("router-link",{attrs:{to:"/usage/webhooks.html"}},[a._v("webhooks")]),a._v(",\n"),e("router-link",{attrs:{to:"/usage/webhooks.html#manual-gating"}},[a._v("manual promotion")]),a._v(" approval and\n"),e("router-link",{attrs:{to:"/usage/alerting.html"}},[a._v("Slack or MS Teams")]),a._v(" notifications.")],1)])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/24.4f54d289.js b/assets/js/24.4f54d289.js new file mode 100644 index 00000000..29b683e8 --- /dev/null +++ b/assets/js/24.4f54d289.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[24],{284:function(t,a,e){"use strict";e.r(a);var s=e(37),n=Object(s.a)({},function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("h1",{attrs:{id:"istio-canary-deployments"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#istio-canary-deployments","aria-hidden":"true"}},[t._v("#")]),t._v(" Istio Canary Deployments")]),t._v(" "),e("p",[t._v("This guide shows you how to use Istio and Flagger to automate canary deployments.")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-steps.png",alt:"Flagger Canary Stages"}})]),t._v(" "),e("h2",{attrs:{id:"prerequisites"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[t._v("#")]),t._v(" Prerequisites")]),t._v(" "),e("p",[t._v("Flagger requires a Kubernetes cluster "),e("strong",[t._v("v1.11")]),t._v(" or newer and Istio "),e("strong",[t._v("v1.0")]),t._v(" or newer.")]),t._v(" "),e("p",[t._v("Install Istio with telemetry support and Prometheus:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("istioctl manifest apply --set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("profile")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("default\n")])])]),e("p",[t._v("Install Flagger using Kustomize (kubectl 1.14) in the "),e("code",[t._v("istio-system")]),t._v(" namespace:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/istio\n")])])]),e("p",[t._v("Create an ingress gateway to expose the demo app outside of the mesh:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" networking.istio.io/v1alpha3\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Gateway\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" public"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("gateway\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("selector")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("istio")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ingressgateway\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("servers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("number")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("80")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("protocol")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" HTTP\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("hosts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"*"')]),t._v("\n")])])]),e("h2",{attrs:{id:"bootstrap"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#bootstrap","aria-hidden":"true"}},[t._v("#")]),t._v(" Bootstrap")]),t._v(" "),e("p",[t._v("Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),\nthen creates a series of objects (Kubernetes deployments, ClusterIP services,\nIstio destination rules and virtual services).\nThese objects expose the application inside the mesh and drive the canary analysis and promotion.")]),t._v(" "),e("p",[t._v("Create a test namespace with Istio sidecar injection enabled:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl create ns "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v("\nkubectl label namespace "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" istio-injection"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("enabled\n")])])]),e("p",[t._v("Create a deployment and a horizontal pod autoscaler:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),e("p",[t._v("Deploy the load testing service to generate traffic during the canary analysis:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/tester\n")])])]),e("p",[t._v("Create a canary custom resource (replace example.com with your own domain):")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# deployment reference")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# the maximum time in seconds for the canary deployment")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# to make progress before it is rollback (default 600s)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("progressDeadlineSeconds")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("60")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# HPA reference (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# service port number")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# container port number or name (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetPort")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Istio gateways (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("gateways")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" public"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("gateway.istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system.svc.cluster.local\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Istio virtual service host names (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("hosts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" app.example.com\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Istio traffic policy (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("trafficPolicy")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("tls")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# use ISTIO_MUTUAL when mTLS is enabled")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mode")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" DISABLE\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Istio retry policy (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retries")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("attempts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("3")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("perTryTimeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retryOn")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"gateway-error,connect-failure,refused-stream"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# schedule interval (default 60s)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max number of failed metric checks before rollback")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max traffic percentage routed to canary")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("maxWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("50")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# canary increment step")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("stepWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# minimum req success rate (non 5xx responses)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("duration\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# maximum req duration P99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# milliseconds")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("500")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# testing (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" acceptance"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"curl -sd 'test' http://podinfo-canary:9898/token | grep token\"")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"')]),t._v("\n")])])]),e("p",[t._v("Save the above resource as podinfo-canary.yaml and then apply it:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -f ./podinfo-canary.yaml\n")])])]),e("p",[t._v("When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.\nThe canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every minute.")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-hpa.png",alt:"Flagger Canary Process"}})]),t._v(" "),e("p",[t._v("After a couple of seconds Flagger will create the canary objects:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# applied ")]),t._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\ncanary.flagger.app/podinfo\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# generated ")]),t._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\ndestinationrule.networking.istio.io/podinfo-canary\ndestinationrule.networking.istio.io/podinfo-primary\nvirtualservice.networking.istio.io/podinfo\n")])])]),e("h2",{attrs:{id:"automated-canary-promotion"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-canary-promotion","aria-hidden":"true"}},[t._v("#")]),t._v(" Automated canary promotion")]),t._v(" "),e("p",[t._v("Trigger a canary deployment by updating the container image:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.1\n")])])]),e("p",[t._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 0\n Phase: Succeeded\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger New revision detected podinfo.test\n Normal Synced 3m flagger Scaling up podinfo.test\n Warning Synced 3m flagger Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\n Normal Synced 3m flagger Advance podinfo.test canary weight 5\n Normal Synced 3m flagger Advance podinfo.test canary weight 10\n Normal Synced 3m flagger Advance podinfo.test canary weight 15\n Normal Synced 2m flagger Advance podinfo.test canary weight 20\n Normal Synced 2m flagger Advance podinfo.test canary weight 25\n Normal Synced 1m flagger Advance podinfo.test canary weight 30\n Normal Synced 1m flagger Advance podinfo.test canary weight 35\n Normal Synced 55s flagger Advance podinfo.test canary weight 40\n Normal Synced 45s flagger Advance podinfo.test canary weight 45\n Normal Synced 35s flagger Advance podinfo.test canary weight 50\n Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test\n Warning Synced 15s flagger Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[e("strong",[t._v("Note")]),t._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),t._v(" "),e("p",[t._v("A canary deployment is triggered by changes in any of the following objects:")]),t._v(" "),e("ul",[e("li",[t._v("Deployment PodSpec (container image, command, ports, env, resources, etc)")]),t._v(" "),e("li",[t._v("ConfigMaps mounted as volumes or mapped to environment variables")]),t._v(" "),e("li",[t._v("Secrets mounted as volumes or mapped to environment variables")])]),t._v(" "),e("p",[t._v("You can monitor all canaries with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("15")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-01-16T14:05:07Z\nprod frontend Succeeded "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-01-15T16:15:07Z\nprod backend Failed "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-01-14T17:05:07Z\n")])])]),e("h2",{attrs:{id:"automated-rollback"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[t._v("#")]),t._v(" Automated rollback")]),t._v(" "),e("p",[t._v("During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses the rollout.")]),t._v(" "),e("p",[t._v("Trigger another canary deployment:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.2\n")])])]),e("p",[t._v("Exec into the load tester pod with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("exec")]),t._v(" -it flagger-loadtester-xx-xx "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("sh")]),t._v("\n")])])]),e("p",[t._v("Generate HTTP 500 errors:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" http://podinfo-canary:9898/status/500\n")])])]),e("p",[t._v("Generate latency:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" http://podinfo-canary:9898/delay/1\n")])])]),e("p",[t._v("When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary,\nthe canary is scaled to zero and the rollout is marked as failed.")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 10\n Phase: Failed\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger Starting canary deployment for podinfo.test\n Normal Synced 3m flagger Advance podinfo.test canary weight 5\n Normal Synced 3m flagger Advance podinfo.test canary weight 10\n Normal Synced 3m flagger Advance podinfo.test canary weight 15\n Normal Synced 3m flagger Halt podinfo.test advancement success rate 69.17% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 61.39% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 55.06% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 47.00% < 99%\n Normal Synced 2m flagger (combined from similar events): Halt podinfo.test advancement success rate 38.08% < 99%\n Warning Synced 1m flagger Rolling back podinfo.test failed checks threshold reached 10\n Warning Synced 1m flagger Canary failed! Scaling down podinfo.test\n")])])]),e("h2",{attrs:{id:"traffic-mirroring"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#traffic-mirroring","aria-hidden":"true"}},[t._v("#")]),t._v(" Traffic mirroring")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-traffic-mirroring.png",alt:"Flagger Canary Traffic Shadowing"}})]),t._v(" "),e("p",[t._v("For applications that perform read operations, Flagger can be configured to drive canary releases with traffic mirroring.\nIstio traffic mirroring will copy each incoming request, sending one request to the primary and one to the canary service.\nThe response from the primary is sent back to the user and the response from the canary is discarded.\nMetrics are collected on both requests so that the deployment will only proceed if the canary metrics are within the threshold values.")]),t._v(" "),e("p",[t._v("Note that mirroring should be used for requests that are "),e("strong",[t._v("idempotent")]),t._v(" or capable of being processed twice (once by the primary and once by the canary).")]),t._v(" "),e("p",[t._v("You can enable mirroring by replacing "),e("code",[t._v("stepWeight/maxWeight")]),t._v(" with "),e("code",[t._v("iterations")]),t._v(" and by setting "),e("code",[t._v("analysis.mirror")]),t._v(" to "),e("code",[t._v("true")]),t._v(":")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# schedule interval")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max number of failed metric checks before rollback")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# total number of iterations")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("iterations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# enable traffic shadowing ")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("mirror")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token boolean important"}},[t._v("true")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("duration\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("500")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" acceptance"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"curl -sd 'test' http://podinfo-canary:9898/token | grep token\"")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"hey -z 1m -q 10 -c 2 http://podinfo.test:9898/"')]),t._v("\n")])])]),e("p",[t._v("With the above configuration, Flagger will run a canary release with the following steps:")]),t._v(" "),e("ul",[e("li",[t._v("detect new revision (deployment spec, secrets or configmaps changes)")]),t._v(" "),e("li",[t._v("scale from zero the canary deployment")]),t._v(" "),e("li",[t._v("wait for the HPA to set the canary minimum replicas")]),t._v(" "),e("li",[t._v("check canary pods health")]),t._v(" "),e("li",[t._v("run the acceptance tests")]),t._v(" "),e("li",[t._v("abort the canary release if tests fail")]),t._v(" "),e("li",[t._v("start the load tests")]),t._v(" "),e("li",[t._v("mirror traffic from primary to canary")]),t._v(" "),e("li",[t._v("check request success rate and request duration every minute")]),t._v(" "),e("li",[t._v("abort the canary release if the metrics check failure threshold is reached")]),t._v(" "),e("li",[t._v("stop traffic mirroring after the number of iterations is reached")]),t._v(" "),e("li",[t._v("route live traffic to the canary pods")]),t._v(" "),e("li",[t._v("promote the canary (update the primary secrets, configmaps and deployment spec)")]),t._v(" "),e("li",[t._v("wait for the primary deployment rollout to finish")]),t._v(" "),e("li",[t._v("wait for the HPA to set the primary minimum replicas")]),t._v(" "),e("li",[t._v("check primary pods health")]),t._v(" "),e("li",[t._v("switch live traffic back to primary")]),t._v(" "),e("li",[t._v("scale to zero the canary")]),t._v(" "),e("li",[t._v("send notification with the canary analysis result")])]),t._v(" "),e("p",[t._v("The above procedure can be extended with "),e("router-link",{attrs:{to:"/usage/metrics.html"}},[t._v("custom metrics")]),t._v(" checks,\n"),e("router-link",{attrs:{to:"/usage/webhooks.html"}},[t._v("webhooks")]),t._v(",\n"),e("router-link",{attrs:{to:"/usage/webhooks.html#manual-gating"}},[t._v("manual promotion")]),t._v(" approval and\n"),e("router-link",{attrs:{to:"/usage/alerting.html"}},[t._v("Slack or MS Teams")]),t._v(" notifications.")],1)])},[],!1,null,null,null);a.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/25.914acd1d.js b/assets/js/25.914acd1d.js new file mode 100644 index 00000000..49297f5a --- /dev/null +++ b/assets/js/25.914acd1d.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[25],{285:function(t,a,e){"use strict";e.r(a);var s=e(37),n=Object(s.a)({},function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("h1",{attrs:{id:"blue-green-deployments"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#blue-green-deployments","aria-hidden":"true"}},[t._v("#")]),t._v(" Blue/Green Deployments")]),t._v(" "),e("p",[t._v("This guide shows you how to automate Blue/Green deployments with Flagger and Kubernetes.")]),t._v(" "),e("p",[t._v("For applications that are not deployed on a service mesh, Flagger can orchestrate Blue/Green style deployments\nwith Kubernetes L4 networking. When using a service mesh blue/green can be used as\nspecified "),e("router-link",{attrs:{to:"/usage/deployment-strategies.html"}},[t._v("here")]),t._v(".")],1),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-bluegreen-steps.png",alt:"Flagger Blue/Green Stages"}})]),t._v(" "),e("h2",{attrs:{id:"prerequisites"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[t._v("#")]),t._v(" Prerequisites")]),t._v(" "),e("p",[t._v("Flagger requires a Kubernetes cluster "),e("strong",[t._v("v1.11")]),t._v(" or newer.")]),t._v(" "),e("p",[t._v("Install Flagger and the Prometheus add-on:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("helm repo "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("add")]),t._v(" flagger https://flagger.app\n\nhelm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--namespace flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set prometheus.install"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("true "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("meshProvider")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("kubernetes\n")])])]),e("p",[t._v("If you already have a Prometheus instance running in your cluster, you can point Flagger to the ClusterIP service with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("helm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--namespace flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("metricsServer")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("http://prometheus.monitoring:9090\n")])])]),e("p",[t._v("Optionally you can enable Slack notifications:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("helm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--reuse-values "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--namespace flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set slack.url"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set slack.channel"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("general "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set slack.user"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("flagger\n")])])]),e("h2",{attrs:{id:"bootstrap"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#bootstrap","aria-hidden":"true"}},[t._v("#")]),t._v(" Bootstrap")]),t._v(" "),e("p",[t._v("Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),\nthen creates a series of objects (Kubernetes deployment and ClusterIP services).\nThese objects expose the application inside the cluster and drive the canary analysis and Blue/Green promotion.")]),t._v(" "),e("p",[t._v("Create a test namespace:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl create ns "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v("\n")])])]),e("p",[t._v("Create a deployment and a horizontal pod autoscaler:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),e("p",[t._v("Deploy the load testing service to generate traffic during the analysis:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/tester\n")])])]),e("p",[t._v("Create a canary custom resource:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# service mesh provider can be: kubernetes, istio, appmesh, nginx, gloo")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" kubernetes\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# deployment reference")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# the maximum time in seconds for the canary deployment")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# to make progress before rollback (default 600s)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("progressDeadlineSeconds")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("60")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# HPA reference (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("portDiscovery")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token boolean important"}},[t._v("true")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# schedule interval (default 60s)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max number of failed checks before rollback")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# number of checks to run before rollback")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("iterations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Prometheus checks based on ")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# http_request_duration_seconds histogram")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# minimum req success rate (non 5xx responses)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("duration\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# maximum req duration P99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# milliseconds")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("500")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# acceptance/load testing hooks")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" smoke"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 15s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"curl -sd 'anon' http://podinfo-canary.test:9898/token | grep token\"")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cmd\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"')]),t._v("\n")])])]),e("p",[t._v("The above configuration will run an analysis for five minutes.")]),t._v(" "),e("p",[t._v("Save the above resource as podinfo-canary.yaml and then apply it:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -f ./podinfo-canary.yaml\n")])])]),e("p",[t._v("After a couple of seconds Flagger will create the canary objects:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# applied ")]),t._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\ncanary.flagger.app/podinfo\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# generated ")]),t._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\n")])])]),e("p",[t._v("Blue/Green scenario:")]),t._v(" "),e("ul",[e("li",[t._v("on bootstrap, Flagger will create three ClusterIP services ("),e("code",[t._v("app-primary")]),t._v(","),e("code",[t._v("app-canary")]),t._v(", "),e("code",[t._v("app")]),t._v(")\nand a shadow deployment named "),e("code",[t._v("app-primary")]),t._v(" that represents the blue version")]),t._v(" "),e("li",[t._v("when a new version is detected, Flagger would scale up the green version and run the conformance tests\n(the tests should target the "),e("code",[t._v("app-canary")]),t._v(" ClusterIP service to reach the green version)")]),t._v(" "),e("li",[t._v("if the conformance tests are passing, Flagger would start the load tests and validate them with custom Prometheus queries")]),t._v(" "),e("li",[t._v("if the load test analysis is successful, Flagger will promote the new version to "),e("code",[t._v("app-primary")]),t._v(" and scale down the green version")])]),t._v(" "),e("h2",{attrs:{id:"automated-blue-green-promotion"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-blue-green-promotion","aria-hidden":"true"}},[t._v("#")]),t._v(" Automated Blue/Green promotion")]),t._v(" "),e("p",[t._v("Trigger a deployment by updating the container image:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.1\n")])])]),e("p",[t._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n test describe canary/podinfo\n\nEvents:\n\nNew revision detected podinfo.test\nWaiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\nPre-rollout check acceptance-test passed\nAdvance podinfo.test canary iteration 1/10\nAdvance podinfo.test canary iteration 2/10\nAdvance podinfo.test canary iteration 3/10\nAdvance podinfo.test canary iteration 4/10\nAdvance podinfo.test canary iteration 5/10\nAdvance podinfo.test canary iteration 6/10\nAdvance podinfo.test canary iteration 7/10\nAdvance podinfo.test canary iteration 8/10\nAdvance podinfo.test canary iteration 9/10\nAdvance podinfo.test canary iteration 10/10\nCopying podinfo.test template spec to podinfo-primary.test\nWaiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\nPromotion completed! Scaling down podinfo.test\n")])])]),e("p",[e("strong",[t._v("Note")]),t._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),t._v(" "),e("p",[t._v("You can monitor all canaries with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("100")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-06-16T14:05:07Z\nprod frontend Succeeded "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-06-15T16:15:07Z\nprod backend Failed "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-06-14T17:05:07Z\n")])])]),e("h2",{attrs:{id:"automated-rollback"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[t._v("#")]),t._v(" Automated rollback")]),t._v(" "),e("p",[t._v("During the analysis you can generate HTTP 500 errors and high latency to test Flagger's rollback.")]),t._v(" "),e("p",[t._v("Exec into the load tester pod with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("exec")]),t._v(" -it flagger-loadtester-xx-xx "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("sh")]),t._v("\n")])])]),e("p",[t._v("Generate HTTP 500 errors:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" http://podinfo-canary.test:9898/status/500\n")])])]),e("p",[t._v("Generate latency:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" http://podinfo-canary.test:9898/delay/1\n")])])]),e("p",[t._v("When the number of failed checks reaches the analysis threshold, the green version is scaled to zero and the rollout is marked as failed.")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Failed Checks: 2\n Phase: Failed\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger New revision detected podinfo.test\n Normal Synced 3m flagger Advance podinfo.test canary iteration 1/10\n Normal Synced 3m flagger Advance podinfo.test canary iteration 2/10\n Normal Synced 3m flagger Advance podinfo.test canary iteration 3/10\n Normal Synced 3m flagger Halt podinfo.test advancement success rate 69.17% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 61.39% < 99%\n Warning Synced 2m flagger Rolling back podinfo.test failed checks threshold reached 2\n Warning Synced 1m flagger Canary failed! Scaling down podinfo.test\n")])])]),e("h2",{attrs:{id:"custom-metrics"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#custom-metrics","aria-hidden":"true"}},[t._v("#")]),t._v(" Custom metrics")]),t._v(" "),e("p",[t._v("The analysis can be extended with Prometheus queries. The demo app is instrumented with Prometheus so you can\ncreate a custom check that will use the HTTP request duration histogram to validate the canary (green version).")]),t._v(" "),e("p",[t._v("Create a metric template and apply it on the cluster:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" MetricTemplate\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" not"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("found"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("percentage\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" prometheus\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("address")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("promethues.flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9090")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("query")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("|")]),e("span",{pre:!0,attrs:{class:"token scalar string"}},[t._v('\n 100 - sum(\n rate(\n http_request_duration_seconds_count{\n kubernetes_namespace="{{ namespace }}",\n kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"\n status!="{{ interval }}"\n }[1m]\n )\n )\n /\n sum(\n rate(\n http_request_duration_seconds_count{\n kubernetes_namespace="{{ namespace }}",\n kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"\n }[{{ interval }}]\n )\n ) * 100')]),t._v("\n")])])]),e("p",[t._v("Edit the canary analysis and add the following metric:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"404s percentage"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("templateRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" not"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("found"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("percentage\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n")])])]),e("p",[t._v("The above configuration validates the canary (green version) by checking if the HTTP 404 req/sec percentage is\nbelow 5 percent of the total traffic. If the 404s rate reaches the 5% threshold, then the rollout is rolled back.")]),t._v(" "),e("p",[t._v("Trigger a deployment by updating the container image:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.3\n")])])]),e("p",[t._v("Generate 404s:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" http://podinfo-canary.test:9898/status/400\n")])])]),e("p",[t._v("Watch Flagger logs:")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n flagger logs deployment/flagger -f | jq .msg\n\nNew revision detected podinfo.test\nScaling up podinfo.test\nAdvance podinfo.test canary iteration 1/10\nHalt podinfo.test advancement 404s percentage 6.20 > 5\nHalt podinfo.test advancement 404s percentage 6.45 > 5\nRolling back podinfo.test failed checks threshold reached 2\nCanary failed! Scaling down podinfo.test\n")])])]),e("p",[t._v("If you have "),e("router-link",{attrs:{to:"/usage/alerting.html"}},[t._v("alerting")]),t._v(" configured,\nFlagger will send a notification with the reason why the canary failed.")],1),t._v(" "),e("h2",{attrs:{id:"conformance-testing-with-helm"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#conformance-testing-with-helm","aria-hidden":"true"}},[t._v("#")]),t._v(" Conformance Testing with Helm")]),t._v(" "),e("p",[t._v("Flagger comes with a testing service that can run Helm tests when configured as a pre-rollout webhook.")]),t._v(" "),e("p",[t._v("Deploy the Helm test runner in the "),e("code",[t._v("kube-system")]),t._v(" namespace using the "),e("code",[t._v("tiller")]),t._v(" service account:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("helm repo "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("add")]),t._v(" flagger https://flagger.app\n\nhelm upgrade -i flagger-helmtester flagger/loadtester "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--namespace"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("kube-system "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("serviceAccountName")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("tiller\n")])])]),e("p",[t._v("When deployed the Helm tester API will be available at "),e("code",[t._v("http://flagger-helmtester.kube-system/")]),t._v(".")]),t._v(" "),e("p",[t._v("Add a helm test pre-rollout hook to your chart:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"conformance testing"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("helmtester.kube"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 3m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"helm"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"test {{ .Release.Name }} --cleanup"')]),t._v("\n")])])]),e("p",[t._v("When the canary analysis starts, Flagger will call the pre-rollout webhooks.\nIf the helm test fails, Flagger will retry until the analysis threshold is reached and the canary is rolled back.")]),t._v(" "),e("p",[t._v("For an in-depth look at the analysis process read the "),e("router-link",{attrs:{to:"/usage/how-it-works.html"}},[t._v("usage docs")]),t._v(".")],1)])},[],!1,null,null,null);a.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/26.b2d4f402.js b/assets/js/26.b2d4f402.js new file mode 100644 index 00000000..54f82443 --- /dev/null +++ b/assets/js/26.b2d4f402.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[26],{286:function(t,a,e){"use strict";e.r(a);var s=e(37),n=Object(s.a)({},function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("h1",{attrs:{id:"linkerd-canary-deployments"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#linkerd-canary-deployments","aria-hidden":"true"}},[t._v("#")]),t._v(" Linkerd Canary Deployments")]),t._v(" "),e("p",[t._v("This guide shows you how to use Linkerd and Flagger to automate canary deployments.")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-linkerd-traffic-split.png",alt:"Flagger Linkerd Traffic Split"}})]),t._v(" "),e("h2",{attrs:{id:"prerequisites"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[t._v("#")]),t._v(" Prerequisites")]),t._v(" "),e("p",[t._v("Flagger requires a Kubernetes cluster "),e("strong",[t._v("v1.11")]),t._v(" or newer and Linkerd "),e("strong",[t._v("2.4")]),t._v(" or newer.")]),t._v(" "),e("p",[t._v("Install Flagger in the linkerd namespace:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/linkerd\n")])])]),e("p",[t._v("Note that you'll need kubectl 1.14 or newer to run the above command.")]),t._v(" "),e("h2",{attrs:{id:"bootstrap"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#bootstrap","aria-hidden":"true"}},[t._v("#")]),t._v(" Bootstrap")]),t._v(" "),e("p",[t._v("Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),\nthen creates a series of objects (Kubernetes deployments, ClusterIP services and SMI traffic split).\nThese objects expose the application inside the mesh and drive the canary analysis and promotion.")]),t._v(" "),e("p",[t._v("Create a test namespace and enable Linkerd proxy injection:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl create ns "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v("\nkubectl annotate namespace "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" linkerd.io/inject"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("enabled\n")])])]),e("p",[t._v("Install the load testing service to generate traffic during the canary analysis:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/tester\n")])])]),e("p",[t._v("Create a deployment and a horizontal pod autoscaler:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),e("p",[t._v("Create a canary custom resource for the podinfo deployment:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# deployment reference")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# HPA reference (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# the maximum time in seconds for the canary deployment")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# to make progress before it is rollback (default 600s)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("progressDeadlineSeconds")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("60")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# ClusterIP port number")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# container port number or name (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetPort")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# schedule interval (default 60s)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max number of failed metric checks before rollback")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max traffic percentage routed to canary")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("maxWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("50")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# canary increment step")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("stepWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Linkerd Prometheus checks")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# minimum req success rate (non 5xx responses)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("duration\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# maximum req duration P99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# milliseconds")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("500")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# testing (optional)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" acceptance"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"curl -sd 'test' http://podinfo-canary.test:9898/token | grep token\"")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"hey -z 2m -q 10 -c 2 http://podinfo-canary.test:9898/"')]),t._v("\n")])])]),e("p",[t._v("Save the above resource as podinfo-canary.yaml and then apply it:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl apply -f ./podinfo-canary.yaml\n")])])]),e("p",[t._v("When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.\nThe canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every half a minute.")]),t._v(" "),e("p",[t._v("After a couple of seconds Flagger will create the canary objects:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# applied")]),t._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\ningresses.extensions/podinfo\ncanary.flagger.app/podinfo\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# generated")]),t._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\ntrafficsplits.split.smi-spec.io/podinfo\n")])])]),e("p",[t._v("After the boostrap, the podinfo deployment will be scaled to zero and the traffic to "),e("code",[t._v("podinfo.test")]),t._v("\nwill be routed to the primary pods.\nDuring the canary analysis, the "),e("code",[t._v("podinfo-canary.test")]),t._v(" address can be used to target directly the canary pods.")]),t._v(" "),e("h2",{attrs:{id:"automated-canary-promotion"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-canary-promotion","aria-hidden":"true"}},[t._v("#")]),t._v(" Automated canary promotion")]),t._v(" "),e("p",[t._v("Flagger implements a control loop that gradually shifts traffic to the canary while measuring\nkey performance indicators like HTTP requests success rate, requests average duration and pod health.\nBased on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack.")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-steps.png",alt:"Flagger Canary Stages"}})]),t._v(" "),e("p",[t._v("Trigger a canary deployment by updating the container image:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.1\n")])])]),e("p",[t._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 0\n Phase: Succeeded\nEvents:\n New revision detected! Scaling up podinfo.test\n Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\n Pre-rollout check acceptance-test passed\n Advance podinfo.test canary weight 5\n Advance podinfo.test canary weight 10\n Advance podinfo.test canary weight 15\n Advance podinfo.test canary weight 20\n Advance podinfo.test canary weight 25\n Waiting for podinfo.test rollout to finish: 1 of 2 updated replicas are available\n Advance podinfo.test canary weight 30\n Advance podinfo.test canary weight 35\n Advance podinfo.test canary weight 40\n Advance podinfo.test canary weight 45\n Advance podinfo.test canary weight 50\n Copying podinfo.test template spec to podinfo-primary.test\n Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[e("strong",[t._v("Note")]),t._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),t._v(" "),e("p",[t._v("A canary deployment is triggered by changes in any of the following objects:")]),t._v(" "),e("ul",[e("li",[t._v("Deployment PodSpec (container image, command, ports, env, resources, etc)")]),t._v(" "),e("li",[t._v("ConfigMaps mounted as volumes or mapped to environment variables")]),t._v(" "),e("li",[t._v("Secrets mounted as volumes or mapped to environment variables")])]),t._v(" "),e("p",[t._v("You can monitor all canaries with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("15")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-06-30T14:05:07Z\nprod frontend Succeeded "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-06-30T16:15:07Z\nprod backend Failed "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-06-30T17:05:07Z\n")])])]),e("h2",{attrs:{id:"automated-rollback"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[t._v("#")]),t._v(" Automated rollback")]),t._v(" "),e("p",[t._v("During the canary analysis you can generate HTTP 500 errors and high latency to\ntest if Flagger pauses and rolls back the faulted version.")]),t._v(" "),e("p",[t._v("Trigger another canary deployment:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.2\n")])])]),e("p",[t._v("Exec into the load tester pod with:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("exec")]),t._v(" -it flagger-loadtester-xx-xx "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("sh")]),t._v("\n")])])]),e("p",[t._v("Generate HTTP 500 errors:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" -n "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("1")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" http://podinfo-canary.test:9898/status/500\n")])])]),e("p",[t._v("Generate latency:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" -n "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("1")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" http://podinfo-canary.test:9898/delay/1\n")])])]),e("p",[t._v("When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary,\nthe canary is scaled to zero and the rollout is marked as failed.")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 10\n Phase: Failed\nEvents:\n Starting canary analysis for podinfo.test\n Pre-rollout check acceptance-test passed\n Advance podinfo.test canary weight 5\n Advance podinfo.test canary weight 10\n Advance podinfo.test canary weight 15\n Halt podinfo.test advancement success rate 69.17% < 99%\n Halt podinfo.test advancement success rate 61.39% < 99%\n Halt podinfo.test advancement success rate 55.06% < 99%\n Halt podinfo.test advancement request duration 1.20s > 0.5s\n Halt podinfo.test advancement request duration 1.45s > 0.5s\n Rolling back podinfo.test failed checks threshold reached 5\n Canary failed! Scaling down podinfo.test\n")])])]),e("h2",{attrs:{id:"custom-metrics"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#custom-metrics","aria-hidden":"true"}},[t._v("#")]),t._v(" Custom metrics")]),t._v(" "),e("p",[t._v("The canary analysis can be extended with Prometheus queries.")]),t._v(" "),e("p",[t._v("Let's a define a check for not found errors. Edit the canary analysis and add the following metric:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"404s percentage"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("3")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("query")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("|")]),e("span",{pre:!0,attrs:{class:"token scalar string"}},[t._v('\n 100 - sum(\n rate(\n response_total{\n namespace="test",\n deployment="podinfo",\n status_code!="404",\n direction="inbound"\n }[1m]\n )\n )\n /\n sum(\n rate(\n response_total{\n namespace="test",\n deployment="podinfo",\n direction="inbound"\n }[1m]\n )\n )\n * 100')]),t._v("\n")])])]),e("p",[t._v("The above configuration validates the canary version by checking if the HTTP 404 req/sec percentage\nis below three percent of the total traffic.\nIf the 404s rate reaches the 3% threshold, then the analysis is aborted and the canary is marked as failed.")]),t._v(" "),e("p",[t._v("Trigger a canary deployment by updating the container image:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.3\n")])])]),e("p",[t._v("Generate 404s:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[t._v("watch")]),t._v(" -n "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("1")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" http://podinfo-canary:9898/status/404\n")])])]),e("p",[t._v("Watch Flagger logs:")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n linkerd logs deployment/flagger -f | jq .msg\n\nStarting canary deployment for podinfo.test\nPre-rollout check acceptance-test passed\nAdvance podinfo.test canary weight 5\nHalt podinfo.test advancement 404s percentage 6.20 > 3\nHalt podinfo.test advancement 404s percentage 6.45 > 3\nHalt podinfo.test advancement 404s percentage 7.22 > 3\nHalt podinfo.test advancement 404s percentage 6.50 > 3\nHalt podinfo.test advancement 404s percentage 6.34 > 3\nRolling back podinfo.test failed checks threshold reached 5\nCanary failed! Scaling down podinfo.test\n")])])]),e("p",[t._v("If you have Slack configured, Flagger will send a notification with the reason why the canary failed.")]),t._v(" "),e("h2",{attrs:{id:"linkerd-ingress"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#linkerd-ingress","aria-hidden":"true"}},[t._v("#")]),t._v(" Linkerd Ingress")]),t._v(" "),e("p",[t._v("There are two ingress controllers that are compatible with both Flagger and Linkerd: NGINX and Gloo.")]),t._v(" "),e("p",[t._v("Install NGINX:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("helm upgrade -i nginx-ingress stable/nginx-ingress "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--namespace ingress-nginx\n")])])]),e("p",[t._v("Create an ingress definition for podinfo that rewrites the incoming header\nto the internal service name (required by Linkerd):")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" extensions/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Ingress\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("labels")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("annotations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kubernetes.io/ingress.class")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"nginx"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("nginx.ingress.kubernetes.io/configuration-snippet")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("|")]),e("span",{pre:!0,attrs:{class:"token scalar string"}},[t._v("\n proxy_set_header l5d-dst-override $service_name.$namespace.svc.cluster.local:9898;\n proxy_hide_header l5d-remote-ip;\n proxy_hide_header l5d-server-id;")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("rules")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("host")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" app.example.com\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("http")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("paths")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("backend")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("serviceName")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("servicePort")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n")])])]),e("p",[t._v("When using an ingress controller, the Linkerd traffic split does not apply to incoming traffic\nsince NGINX in running outside of the mesh. In order to run a canary analysis for a frontend app,\nFlagger creates a shadow ingress and sets the NGINX specific annotations.")]),t._v(" "),e("h2",{attrs:{id:"a-b-testing"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#a-b-testing","aria-hidden":"true"}},[t._v("#")]),t._v(" A/B Testing")]),t._v(" "),e("p",[t._v("Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions.\nIn an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users.\nThis is particularly useful for frontend applications that require session affinity.")]),t._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-nginx-linkerd.png",alt:"Flagger Linkerd Ingress"}})]),t._v(" "),e("p",[t._v("Edit podinfo canary analysis, set the provider to "),e("code",[t._v("nginx")]),t._v(", add the ingress reference,\nremove the max/step weight and add the match conditions and iterations:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# ingress reference")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" nginx\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("ingressRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" extensions/v1beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Ingress\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# container port")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("iterations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# curl -H 'X-Canary: always' http://app.example.com")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("x-canary")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("exact")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"always"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# curl -b 'canary=always' http://app.example.com")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cookie")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("exact")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"canary"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# Linkerd Prometheus checks")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("duration\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("500")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" acceptance"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"curl -sd 'test' http://podinfo-canary:9898/token | grep token\"")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"hey -z 2m -q 10 -c 2 -H 'Cookie: canary=always' http://app.example.com\"")]),t._v("\n")])])]),e("p",[t._v("The above configuration will run an analysis for ten minutes targeting users that have\na "),e("code",[t._v("canary")]),t._v(" cookie set to "),e("code",[t._v("always")]),t._v(" or those that call the service using the "),e("code",[t._v("X-Canary: always")]),t._v(" header.")]),t._v(" "),e("p",[e("strong",[t._v("Note")]),t._v(" that the load test now targets the external address and uses the canary cookie.")]),t._v(" "),e("p",[t._v("Trigger a canary deployment by updating the container image:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.1.4\n")])])]),e("p",[t._v("Flagger detects that the deployment revision changed and starts the A/B testing:")]),t._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[t._v("kubectl -n test describe canary/podinfo\n\nEvents:\n Starting canary deployment for podinfo.test\n Pre-rollout check acceptance-test passed\n Advance podinfo.test canary iteration 1/10\n Advance podinfo.test canary iteration 2/10\n Advance podinfo.test canary iteration 3/10\n Advance podinfo.test canary iteration 4/10\n Advance podinfo.test canary iteration 5/10\n Advance podinfo.test canary iteration 6/10\n Advance podinfo.test canary iteration 7/10\n Advance podinfo.test canary iteration 8/10\n Advance podinfo.test canary iteration 9/10\n Advance podinfo.test canary iteration 10/10\n Copying podinfo.test template spec to podinfo-primary.test\n Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[t._v("The above procedure can be extended with "),e("router-link",{attrs:{to:"/usage/metrics.html"}},[t._v("custom metrics")]),t._v(" checks,\n"),e("router-link",{attrs:{to:"/usage/webhooks.html"}},[t._v("webhooks")]),t._v(",\n"),e("router-link",{attrs:{to:"/usage/webhooks.html#manual-gating"}},[t._v("manual promotion")]),t._v(" approval and\n"),e("router-link",{attrs:{to:"/usage/alerting.html"}},[t._v("Slack or MS Teams")]),t._v(" notifications.")],1)])},[],!1,null,null,null);a.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/27.ea9f1f9c.js b/assets/js/27.ea9f1f9c.js new file mode 100644 index 00000000..651e1b42 --- /dev/null +++ b/assets/js/27.ea9f1f9c.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[27],{287:function(a,t,e){"use strict";e.r(t);var s=e(37),n=Object(s.a)({},function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[e("h1",{attrs:{id:"nginx-canary-deployments"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#nginx-canary-deployments","aria-hidden":"true"}},[a._v("#")]),a._v(" NGINX Canary Deployments")]),a._v(" "),e("p",[a._v("This guide shows you how to use the NGINX ingress controller and Flagger to automate canary deployments and A/B testing.")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-nginx-overview.png",alt:"Flagger NGINX Ingress Controller"}})]),a._v(" "),e("h2",{attrs:{id:"prerequisites"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prerequisites","aria-hidden":"true"}},[a._v("#")]),a._v(" Prerequisites")]),a._v(" "),e("p",[a._v("Flagger requires a Kubernetes cluster "),e("strong",[a._v("v1.11")]),a._v(" or newer and NGINX ingress "),e("strong",[a._v("0.24")]),a._v(" or newer.")]),a._v(" "),e("p",[a._v("Install NGINX with Helm v3:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl create ns ingress-nginx\nhelm upgrade -i nginx-ingress stable/nginx-ingress "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace ingress-nginx "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set controller.stats.enabled"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("true "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set controller.metrics.enabled"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("true "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set controller.podAnnotations."),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"prometheus\\.io/scrape"')]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("true "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set controller.podAnnotations."),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"prometheus\\.io/port"')]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("10254")]),a._v("\n")])])]),e("p",[a._v("Install Flagger and the Prometheus add-on in the same namespace as NGINX:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm repo "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" flagger https://flagger.app\n\nhelm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace ingress-nginx "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set prometheus.install"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("true "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("meshProvider")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("nginx\n")])])]),e("p",[a._v("Optionally you can enable Slack notifications:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--reuse-values "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace ingress-nginx "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.url"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.channel"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("general "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.user"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("flagger\n")])])]),e("h2",{attrs:{id:"bootstrap"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#bootstrap","aria-hidden":"true"}},[a._v("#")]),a._v(" Bootstrap")]),a._v(" "),e("p",[a._v("Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA),\nthen creates a series of objects (Kubernetes deployments, ClusterIP services and canary ingress).\nThese objects expose the application outside the cluster and drive the canary analysis and promotion.")]),a._v(" "),e("p",[a._v("Create a test namespace:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl create ns "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v("\n")])])]),e("p",[a._v("Create a deployment and a horizontal pod autoscaler:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo\n")])])]),e("p",[a._v("Deploy the load testing service to generate traffic during the canary analysis:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm upgrade -i flagger-loadtester flagger/loadtester "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("test\n")])])]),e("p",[a._v("Create an ingress definition (replace "),e("code",[a._v("app.example.com")]),a._v(" with your own domain):")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" extensions/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Ingress\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("labels")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("app")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("annotations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kubernetes.io/ingress.class")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"nginx"')]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("rules")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("host")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" app.example.com\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("http")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("paths")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("backend")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("serviceName")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("servicePort")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("80")]),a._v("\n")])])]),e("p",[a._v("Save the above resource as podinfo-ingress.yaml and then apply it:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -f ./podinfo-ingress.yaml\n")])])]),e("p",[a._v("Create a canary custom resource (replace "),e("code",[a._v("app.example.com")]),a._v(" with your own domain):")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" nginx\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# deployment reference")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# ingress reference")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("ingressRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" extensions/v1beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Ingress\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# HPA reference (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# the maximum time in seconds for the canary deployment")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# to make progress before it is rollback (default 600s)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("progressDeadlineSeconds")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("60")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# ClusterIP port number")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("80")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# container port number or name")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("targetPort")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9898")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# schedule interval (default 60s)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 10s\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max number of failed metric checks before rollback")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("10")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# max traffic percentage routed to canary")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("maxWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("50")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# canary increment step")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("stepWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# NGINX Prometheus checks")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rate\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# minimum req success rate (non 5xx responses)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# percentage (0-100)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# testing (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" acceptance"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" pre"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rollout\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 30s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" bash\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("\"curl -sd 'test' http://podinfo-canary/token | grep token\"")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"hey -z 1m -q 10 -c 2 http://app.example.com/"')]),a._v("\n")])])]),e("p",[a._v("Save the above resource as podinfo-canary.yaml and then apply it:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl apply -f ./podinfo-canary.yaml\n")])])]),e("p",[a._v("After a couple of seconds Flagger will create the canary objects:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# applied ")]),a._v("\ndeployment.apps/podinfo\nhorizontalpodautoscaler.autoscaling/podinfo\ningresses.extensions/podinfo\ncanary.flagger.app/podinfo\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# generated ")]),a._v("\ndeployment.apps/podinfo-primary\nhorizontalpodautoscaler.autoscaling/podinfo-primary\nservice/podinfo\nservice/podinfo-canary\nservice/podinfo-primary\ningresses.extensions/podinfo-canary\n")])])]),e("h2",{attrs:{id:"automated-canary-promotion"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-canary-promotion","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated canary promotion")]),a._v(" "),e("p",[a._v("Flagger implements a control loop that gradually shifts traffic to the canary while measuring\nkey performance indicators like HTTP requests success rate, requests average duration and pod health.\nBased on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack or MS Teams.")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-steps.png",alt:"Flagger Canary Stages"}})]),a._v(" "),e("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.1\n")])])]),e("p",[a._v("Flagger detects that the deployment revision changed and starts a new rollout:")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 0\n Phase: Succeeded\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger New revision detected podinfo.test\n Normal Synced 3m flagger Scaling up podinfo.test\n Warning Synced 3m flagger Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\n Normal Synced 3m flagger Advance podinfo.test canary weight 5\n Normal Synced 3m flagger Advance podinfo.test canary weight 10\n Normal Synced 3m flagger Advance podinfo.test canary weight 15\n Normal Synced 2m flagger Advance podinfo.test canary weight 20\n Normal Synced 2m flagger Advance podinfo.test canary weight 25\n Normal Synced 1m flagger Advance podinfo.test canary weight 30\n Normal Synced 1m flagger Advance podinfo.test canary weight 35\n Normal Synced 55s flagger Advance podinfo.test canary weight 40\n Normal Synced 45s flagger Advance podinfo.test canary weight 45\n Normal Synced 35s flagger Advance podinfo.test canary weight 50\n Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test\n Warning Synced 15s flagger Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[e("strong",[a._v("Note")]),a._v(" that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.")]),a._v(" "),e("p",[a._v("You can monitor all canaries with:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("15")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-06T14:05:07Z\nprod frontend Succeeded "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-05T16:15:07Z\nprod backend Failed "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("2019")]),a._v("-05-04T17:05:07Z\n")])])]),e("h2",{attrs:{id:"automated-rollback"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#automated-rollback","aria-hidden":"true"}},[a._v("#")]),a._v(" Automated rollback")]),a._v(" "),e("p",[a._v("During the canary analysis you can generate HTTP 500 errors to test if Flagger pauses and rolls back the faulted version.")]),a._v(" "),e("p",[a._v("Trigger another canary deployment:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.2\n")])])]),e("p",[a._v("Generate HTTP 500 errors:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" http://app.example.com/status/500\n")])])]),e("p",[a._v("When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary,\nthe canary is scaled to zero and the rollout is marked as failed.")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Canary Weight: 0\n Failed Checks: 10\n Phase: Failed\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger Starting canary deployment for podinfo.test\n Normal Synced 3m flagger Advance podinfo.test canary weight 5\n Normal Synced 3m flagger Advance podinfo.test canary weight 10\n Normal Synced 3m flagger Advance podinfo.test canary weight 15\n Normal Synced 3m flagger Halt podinfo.test advancement success rate 69.17% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 61.39% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 55.06% < 99%\n Normal Synced 2m flagger Halt podinfo.test advancement success rate 47.00% < 99%\n Normal Synced 2m flagger (combined from similar events): Halt podinfo.test advancement success rate 38.08% < 99%\n Warning Synced 1m flagger Rolling back podinfo.test failed checks threshold reached 10\n Warning Synced 1m flagger Canary failed! Scaling down podinfo.test\n")])])]),e("h2",{attrs:{id:"custom-metrics"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#custom-metrics","aria-hidden":"true"}},[a._v("#")]),a._v(" Custom metrics")]),a._v(" "),e("p",[a._v("The canary analysis can be extended with Prometheus queries.")]),a._v(" "),e("p",[a._v("The demo app is instrumented with Prometheus so you can create a custom check that will use the\nHTTP request duration histogram to validate the canary.")]),a._v(" "),e("p",[a._v("Create a metric template and apply it on the cluster:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" MetricTemplate\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" latency\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" test\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" prometheus\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("address")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("promethues.ingress"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("nginx"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("9090")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("query")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("|")]),e("span",{pre:!0,attrs:{class:"token scalar string"}},[a._v('\n histogram_quantile(0.99,\n sum(\n rate(\n http_request_duration_seconds_bucket{\n kubernetes_namespace="{{ namespace }}",\n kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"\n }[1m]\n )\n ) by (le)\n )')]),a._v("\n")])])]),e("p",[a._v("Edit the canary analysis and add the latency check:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"latency"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("templateRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" latency\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("0.5")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n")])])]),e("p",[a._v("The threshold is set to 500ms so if the average request duration in the last minute goes over half a second\nthen the analysis will fail and the canary will not be promoted.")]),a._v(" "),e("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.3\n")])])]),e("p",[a._v("Generate high response latency:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token function"}},[a._v("watch")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[a._v("curl")]),a._v(" http://app.exmaple.com/delay/2\n")])])]),e("p",[a._v("Watch Flagger logs:")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n nginx-ingress logs deployment/flagger -f | jq .msg\n\nStarting canary deployment for podinfo.test\nAdvance podinfo.test canary weight 5\nAdvance podinfo.test canary weight 10\nAdvance podinfo.test canary weight 15\nHalt podinfo.test advancement latency 1.20 > 0.5\nHalt podinfo.test advancement latency 1.45 > 0.5\nHalt podinfo.test advancement latency 1.60 > 0.5\nHalt podinfo.test advancement latency 1.69 > 0.5\nHalt podinfo.test advancement latency 1.70 > 0.5\nRolling back podinfo.test failed checks threshold reached 5\nCanary failed! Scaling down podinfo.test\n")])])]),e("p",[a._v("If you have alerting configured, Flagger will send a notification with the reason why the canary failed.")]),a._v(" "),e("h2",{attrs:{id:"a-b-testing"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#a-b-testing","aria-hidden":"true"}},[a._v("#")]),a._v(" A/B Testing")]),a._v(" "),e("p",[a._v("Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions.\nIn an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users.\nThis is particularly useful for frontend applications that require session affinity.")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-abtest-steps.png",alt:"Flagger A/B Testing Stages"}})]),a._v(" "),e("p",[a._v("Edit the canary analysis, remove the max/step weight and add the match conditions and iterations:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("10")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("iterations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("10")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# curl -H 'X-Canary: insider' http://app.example.com")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("x-canary")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("exact")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"insider"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# curl -b 'canary=always' http://app.example.com")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("headers")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cookie")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("exact")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"canary"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("rate\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[a._v("99")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 5s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v("\"hey -z 1m -q 10 -c 2 -H 'Cookie: canary=always' http://app.example.com/\"")]),a._v("\n")])])]),e("p",[a._v("The above configuration will run an analysis for ten minutes targeting users that have a "),e("code",[a._v("canary")]),a._v(" cookie\nset to "),e("code",[a._v("always")]),a._v(" or those that call the service using the "),e("code",[a._v("X-Canary: insider")]),a._v(" header.")]),a._v(" "),e("p",[a._v("Trigger a canary deployment by updating the container image:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("stefanprodan/podinfo:3.1.4\n")])])]),e("p",[a._v("Flagger detects that the deployment revision changed and starts the A/B testing:")]),a._v(" "),e("div",{staticClass:"language-text extra-class"},[e("pre",{pre:!0,attrs:{class:"language-text"}},[e("code",[a._v("kubectl -n test describe canary/podinfo\n\nStatus:\n Failed Checks: 0\n Phase: Succeeded\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal Synced 3m flagger New revision detected podinfo.test\n Normal Synced 3m flagger Scaling up podinfo.test\n Warning Synced 3m flagger Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available\n Normal Synced 3m flagger Advance podinfo.test canary iteration 1/10\n Normal Synced 3m flagger Advance podinfo.test canary iteration 2/10\n Normal Synced 3m flagger Advance podinfo.test canary iteration 3/10\n Normal Synced 2m flagger Advance podinfo.test canary iteration 4/10\n Normal Synced 2m flagger Advance podinfo.test canary iteration 5/10\n Normal Synced 1m flagger Advance podinfo.test canary iteration 6/10\n Normal Synced 1m flagger Advance podinfo.test canary iteration 7/10\n Normal Synced 55s flagger Advance podinfo.test canary iteration 8/10\n Normal Synced 45s flagger Advance podinfo.test canary iteration 9/10\n Normal Synced 35s flagger Advance podinfo.test canary iteration 10/10\n Normal Synced 25s flagger Copying podinfo.test template spec to podinfo-primary.test\n Warning Synced 15s flagger Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available\n Normal Synced 5s flagger Promotion completed! Scaling down podinfo.test\n")])])]),e("p",[a._v("The above procedure can be extended with "),e("router-link",{attrs:{to:"/usage/metrics.html"}},[a._v("custom metrics")]),a._v(" checks,\n"),e("router-link",{attrs:{to:"/usage/webhooks.html"}},[a._v("webhooks")]),a._v(",\n"),e("router-link",{attrs:{to:"/usage/webhooks.html#manual-gating"}},[a._v("manual promotion")]),a._v(" approval and\n"),e("router-link",{attrs:{to:"/usage/alerting.html"}},[a._v("Slack or MS Teams")]),a._v(" notifications.")],1)])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/28.deae273d.js b/assets/js/28.deae273d.js new file mode 100644 index 00000000..720dd7a6 --- /dev/null +++ b/assets/js/28.deae273d.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[28],{288:function(t,a,s){"use strict";s.r(a);var e=s(37),n=Object(e.a)({},function(){var t=this,a=t.$createElement,s=t._self._c||a;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("h1",{attrs:{id:"zero-downtime-deployments"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#zero-downtime-deployments","aria-hidden":"true"}},[t._v("#")]),t._v(" Zero downtime deployments")]),t._v(" "),s("p",[t._v("This is a list of things you should consider when dealing with a high traffic production environment if you want to minimise the impact of rolling updates and downscaling.")]),t._v(" "),s("h2",{attrs:{id:"deployment-strategy"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#deployment-strategy","aria-hidden":"true"}},[t._v("#")]),t._v(" Deployment strategy")]),t._v(" "),s("p",[t._v("Limit the number of unavailable pods during a rolling update:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("progressDeadlineSeconds")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("120")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("strategy")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" RollingUpdate\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("rollingUpdate")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("maxUnavailable")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v("\n")])])]),s("p",[t._v("The default progress deadline for a deployment is ten minutes. You should consider adjusting this value to make the deployment process fail faster.")]),t._v(" "),s("h2",{attrs:{id:"liveness-health-check"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#liveness-health-check","aria-hidden":"true"}},[t._v("#")]),t._v(" Liveness health check")]),t._v(" "),s("p",[t._v("You application should expose a HTTP endpoint that Kubernetes can call to determine if your app transitioned to a broken state from which it can't recover and needs to be restarted.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("livenessProbe")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("exec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("command")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" wget\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("quiet\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("tries=1\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("timeout=4\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("spider\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//localhost"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("8080/healthz\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeoutSeconds")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("initialDelaySeconds")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n")])])]),s("p",[t._v("If you've enabled mTLS, you'll have to use "),s("code",[t._v("exec")]),t._v(" for liveness and readiness checks since kubelet is not part of the service mesh and doesn't have access to the TLS cert.")]),t._v(" "),s("h2",{attrs:{id:"readiness-health-check"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#readiness-health-check","aria-hidden":"true"}},[t._v("#")]),t._v(" Readiness health check")]),t._v(" "),s("p",[t._v("You application should expose a HTTP endpoint that Kubernetes can call to determine if your app is ready to receive traffic.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("readinessProbe")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("exec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("command")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" wget\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("quiet\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("tries=1\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("timeout=4\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("spider\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//localhost"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("8080/readyz\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeoutSeconds")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("initialDelaySeconds")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("periodSeconds")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n")])])]),s("p",[t._v("If your app depends on external services, you should check if those services are available before allowing Kubernetes to route traffic to an app instance. Keep in mind that the Envoy sidecar can have a slower startup than your app. This means that on application start you should retry for at least a couple of seconds any external connection.")]),t._v(" "),s("h2",{attrs:{id:"graceful-shutdown"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#graceful-shutdown","aria-hidden":"true"}},[t._v("#")]),t._v(" Graceful shutdown")]),t._v(" "),s("p",[t._v("Before a pod gets terminated, Kubernetes sends a "),s("code",[t._v("SIGTERM")]),t._v(" signal to every container and waits for period of time (30s by default) for all containers to exit gracefully. If your app doesn't handle the "),s("code",[t._v("SIGTERM")]),t._v(" signal or if it doesn't exit within the grace period, Kubernetes will kill the container and any inflight requests that your app is processing will fail.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("template")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("terminationGracePeriodSeconds")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("60")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("containers")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" app\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("lifecycle")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("preStop")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("exec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("command")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" sleep\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"10"')]),t._v("\n")])])]),s("p",[t._v("Your app container should have a "),s("code",[t._v("preStop")]),t._v(" hook that delays the container shutdown. This will allow the service mesh to drain the traffic and remove this pod from all other Envoy sidecars before your app becomes unavailable.")]),t._v(" "),s("h2",{attrs:{id:"delay-envoy-shutdown"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#delay-envoy-shutdown","aria-hidden":"true"}},[t._v("#")]),t._v(" Delay Envoy shutdown")]),t._v(" "),s("p",[t._v("Even if your app reacts to "),s("code",[t._v("SIGTERM")]),t._v(" and tries to complete the inflight requests before shutdown, that doesn't mean that the response will make it back to the caller. If the Envoy sidecar shuts down before your app, then the caller will receive a 503 error.")]),t._v(" "),s("p",[t._v("To mitigate this issue you can add a "),s("code",[t._v("preStop")]),t._v(" hook to the Istio proxy and wait for the main app to exist before Envoy exists.")]),t._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token shebang important"}},[t._v("#!/bin/bash")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" -e\n"),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!")]),t._v(" pidof envoy "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("&>")]),t._v("/dev/null"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("then")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("exit")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("fi")]),t._v("\n\n"),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!")]),t._v(" pidof pilot-agent "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("&>")]),t._v("/dev/null"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("then")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("exit")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("fi")]),t._v("\n\n"),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("while")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token variable"}},[s("span",{pre:!0,attrs:{class:"token variable"}},[t._v("$(")]),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("netstat")]),t._v(" -plunt "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("grep")]),t._v(" tcp "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("grep")]),t._v(" -v envoy "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("wc")]),t._v(" -l "),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("xargs")]),s("span",{pre:!0,attrs:{class:"token variable"}},[t._v(")")])]),t._v(" -ne "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("do")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("sleep")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("1")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("done")]),t._v("\n\n"),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("exit")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v("\n")])])]),s("p",[t._v("You'll have to build your own Envoy docker image with the above script and modify the Istio injection webhook with the "),s("code",[t._v("preStop")]),t._v(" directive.")]),t._v(" "),s("p",[t._v("Thanks to Stono for his excellent "),s("a",{attrs:{href:"https://github.com/istio/istio/issues/12183",target:"_blank",rel:"noopener noreferrer"}},[t._v("tips"),s("OutboundLink")],1),t._v(" on minimising 503s.")]),t._v(" "),s("h2",{attrs:{id:"resource-requests-and-limits"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#resource-requests-and-limits","aria-hidden":"true"}},[t._v("#")]),t._v(" Resource requests and limits")]),t._v(" "),s("p",[t._v("Setting CPU and memory requests/limits for all workloads is a mandatory step if you're running a production system. Without limits your nodes could run out of memory or become unresponsive due to CPU exhausting. Without CPU and memory requests, the Kubernetes scheduler will not be able to make decisions about which nodes to place pods on.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("template")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("containers")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" app\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("resources")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("limits")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cpu")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1000m\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("memory")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1Gi\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("requests")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cpu")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 100m\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("memory")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 128Mi\n")])])]),s("p",[t._v("Note that without resource requests the horizontal pod autoscaler can't determine when to scale your app.")]),t._v(" "),s("h2",{attrs:{id:"autoscaling"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#autoscaling","aria-hidden":"true"}},[t._v("#")]),t._v(" Autoscaling")]),t._v(" "),s("p",[t._v("A production environment should be able to handle traffic bursts without impacting the quality of service. This can be achieved with Kubernetes autoscaling capabilities. Autoscaling in Kubernetes has two dimensions: the Cluster Autoscaler that deals with node scaling operations and the Horizontal Pod Autoscaler that automatically scales the number of pods in a deployment.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" autoscaling/v2beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" HorizontalPodAutoscaler\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("scaleTargetRef")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" app\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("minReplicas")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("2")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("maxReplicas")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("4")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Resource\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("resource")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cpu\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetAverageValue")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 900m\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Resource\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("resource")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" memory\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetAverageValue")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 768Mi\n")])])]),s("p",[t._v("The above HPA ensures your app will be scaled up before the pods reach the CPU or memory limits.")]),t._v(" "),s("h2",{attrs:{id:"ingress-retries"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#ingress-retries","aria-hidden":"true"}},[t._v("#")]),t._v(" Ingress retries")]),t._v(" "),s("p",[t._v("To minimise the impact of downscaling operations you can make use of Envoy retry capabilities.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("gateways")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" public"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("gateway.istio"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system.svc.cluster.local\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("hosts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" app.example.com\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retries")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("attempts")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("perTryTimeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retryOn")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"gateway-error,connect-failure,refused-stream"')]),t._v("\n")])])]),s("p",[t._v("When the HPA scales down your app, your users could run into 503 errors. The above configuration will make Envoy retry the HTTP requests that failed due to gateway errors.")])])},[],!1,null,null,null);a.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/29.17c547e6.js b/assets/js/29.17c547e6.js new file mode 100644 index 00000000..4755337d --- /dev/null +++ b/assets/js/29.17c547e6.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[29],{289:function(t,e,n){"use strict";n.r(e);var s=n(37),l=Object(s.a)({},function(){var t=this.$createElement;return(this._self._c||t)("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}})},[],!1,null,null,null);e.default=l.exports}}]); \ No newline at end of file diff --git a/assets/js/3.12052f78.js b/assets/js/3.8b254d5d.js similarity index 87% rename from assets/js/3.12052f78.js rename to assets/js/3.8b254d5d.js index 10d7142b..c199b3d6 100644 --- a/assets/js/3.12052f78.js +++ b/assets/js/3.8b254d5d.js @@ -1 +1 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{238:function(t,e,n){},262:function(t,e,n){"use strict";var i=n(238);n.n(i).a},266:function(t,e,n){"use strict";n.r(e);var i={functional:!0,props:{type:{type:String,default:"tip"},text:String,vertical:{type:String,default:"top"}},render:function(t,e){var n=e.props,i=e.slots;return t("span",{class:["badge",n.type],style:{verticalAlign:n.vertical}},n.text||i().default)}},r=(n(262),n(37)),a=Object(r.a)(i,void 0,void 0,!1,null,"c13ee5b0",null);e.default=a.exports}}]); \ No newline at end of file +(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{238:function(t,e,n){},262:function(t,e,n){"use strict";var i=n(238);n.n(i).a},296:function(t,e,n){"use strict";n.r(e);var i={functional:!0,props:{type:{type:String,default:"tip"},text:String,vertical:{type:String,default:"top"}},render:function(t,e){var n=e.props,i=e.slots;return t("span",{class:["badge",n.type],style:{verticalAlign:n.vertical}},n.text||i().default)}},r=(n(262),n(37)),a=Object(r.a)(i,void 0,void 0,!1,null,"c13ee5b0",null);e.default=a.exports}}]); \ No newline at end of file diff --git a/assets/js/30.38bd809e.js b/assets/js/30.38bd809e.js new file mode 100644 index 00000000..321599ef --- /dev/null +++ b/assets/js/30.38bd809e.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[30],{290:function(a,t,e){"use strict";e.r(t);var s=e(37),n=Object(s.a)({},function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[e("h1",{attrs:{id:"alerting"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#alerting","aria-hidden":"true"}},[a._v("#")]),a._v(" Alerting")]),a._v(" "),e("p",[a._v("Flagger can be configured to send alerts to various chat platforms. You can define a global alert provider at\ninstall time or configure alerts on a per canary basis.")]),a._v(" "),e("h2",{attrs:{id:"global-configuration"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#global-configuration","aria-hidden":"true"}},[a._v("#")]),a._v(" Global configuration")]),a._v(" "),e("p",[a._v("Flagger can be configured to send Slack notifications:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.url"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.channel"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("general "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set slack.user"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("flagger\n")])])]),e("p",[a._v("Once configured with a Slack incoming "),e("strong",[a._v("webhook")]),a._v(", Flagger will post messages when a canary deployment\nhas been initialised, when a new revision has been detected and if the canary analysis failed or succeeded.")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/slack-canary-notifications.png",alt:"Slack Notifications"}})]),a._v(" "),e("p",[a._v("A canary deployment will be rolled back if the progress deadline exceeded or if the analysis reached the\nmaximum number of failed checks:")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/slack-canary-failed.png",alt:"Slack Notifications"}})]),a._v(" "),e("p",[a._v("Flagger can be configured to send notifications to Microsoft Teams:")]),a._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[a._v("helm upgrade -i flagger flagger/flagger "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set msteams.url"),e("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK\n")])])]),e("p",[a._v("Similar to Slack, Flagger alerts on canary analysis events:")]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/flagger-ms-teams-notifications.png",alt:"MS Teams Notifications"}})]),a._v(" "),e("p",[e("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/flagger-ms-teams-failed.png",alt:"MS Teams Notifications"}})]),a._v(" "),e("h2",{attrs:{id:"canary-configuration"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#canary-configuration","aria-hidden":"true"}},[a._v("#")]),a._v(" Canary configuration")]),a._v(" "),e("p",[a._v("Configuring alerting globally has several limitations as it's not possible to specify different channels\nor configure the verbosity on a per canary basis.\nTo make the alerting move flexible, the canary analysis can be extended\nwith a list of alerts that reference an alert provider.\nFor each alert, users can configure the severity level.\nThe alerts section overrides the global setting.")]),a._v(" "),e("p",[a._v("Slack example:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" AlertProvider\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" on"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("call\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" slack\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("channel")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" on"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("call"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("alerts\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("username")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# webhook address (ignored if secretRef is specified)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("address")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" https"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//hooks.slack.com/services/YOUR/SLACK/WEBHOOK\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# secret containing the webhook address (optional)")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("secretRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" on"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("call"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("url\n"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("---")]),a._v("\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" v1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" Secret\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" on"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("call"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("url\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("data")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("address")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" ")]),a._v("\n")])])]),e("p",[a._v("The alert provider "),e("strong",[a._v("type")]),a._v(" can be: "),e("code",[a._v("slack")]),a._v(", "),e("code",[a._v("msteams")]),a._v(", "),e("code",[a._v("rocket")]),a._v(" or "),e("code",[a._v("discord")]),a._v(". When set to "),e("code",[a._v("discord")]),a._v(",\nFlagger will use "),e("a",{attrs:{href:"https://birdie0.github.io/discord-webhooks-guide/other/slack_formatting.html",target:"_blank",rel:"noopener noreferrer"}},[a._v("Slack formatting"),e("OutboundLink")],1),a._v("\nand will append "),e("code",[a._v("/slack")]),a._v(" to the Discord address.")]),a._v(" "),e("p",[a._v("When not specified, "),e("strong",[a._v("channel")]),a._v(" defaults to "),e("code",[a._v("general")]),a._v(" and "),e("strong",[a._v("username")]),a._v(" defaults to "),e("code",[a._v("flagger")]),a._v(".")]),a._v(" "),e("p",[a._v("When "),e("strong",[a._v("secretRef")]),a._v(" is specified, the Kubernetes secret must contain a data field named "),e("code",[a._v("address")]),a._v(",\nthe address in the secret will take precedence over the "),e("strong",[a._v("address")]),a._v(" field in the provider spec.")]),a._v(" "),e("p",[a._v("The canary analysis can have a list of alerts, each alert referencing an alert provider:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("alerts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"on-call Slack"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("severity")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" error\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("providerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" on"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("call\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"qa Discord"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("severity")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" warn\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("providerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" qa"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("discord\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"dev MS Teams"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("severity")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" info\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("providerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" dev"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("msteams\n")])])]),e("p",[a._v("Alert fields:")]),a._v(" "),e("ul",[e("li",[e("strong",[a._v("name")]),a._v(" (required)")]),a._v(" "),e("li",[e("strong",[a._v("severity")]),a._v(" levels: "),e("code",[a._v("info")]),a._v(", "),e("code",[a._v("warn")]),a._v(", "),e("code",[a._v("error")]),a._v(" (default info)")]),a._v(" "),e("li",[e("strong",[a._v("providerRef.name")]),a._v(" alert provider name (required)")]),a._v(" "),e("li",[e("strong",[a._v("providerRef.namespace")]),a._v(" alert provider namespace (defaults to the canary namespace)")])]),a._v(" "),e("p",[a._v("When the severity is set to "),e("code",[a._v("warn")]),a._v(", Flagger will alert when waiting on manual confirmation or if the analysis fails.\nWhen the severity is set to "),e("code",[a._v("error")]),a._v(", Flagger will alert only if the canary analysis fails.")]),a._v(" "),e("h2",{attrs:{id:"prometheus-alert-manager"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prometheus-alert-manager","aria-hidden":"true"}},[a._v("#")]),a._v(" Prometheus Alert Manager")]),a._v(" "),e("p",[a._v("You can use Alertmanager to trigger alerts when a canary deployment failed:")]),a._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[a._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("alert")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" canary_rollback\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("expr")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" flagger_canary_status "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(">")]),a._v(" 1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("for")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("labels")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("severity")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" warning\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("annotations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("summary")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"Canary failed"')]),a._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("description")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[a._v('"Workload {{ $labels.name }} namespace {{ $labels.namespace }}"')]),a._v("\n")])])])])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/31.fbfb336c.js b/assets/js/31.fbfb336c.js new file mode 100644 index 00000000..56532043 --- /dev/null +++ b/assets/js/31.fbfb336c.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[31],{291:function(e,t,a){"use strict";a.r(t);var s=a(37),n=Object(s.a)({},function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[a("h1",{attrs:{id:"deployment-strategies"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#deployment-strategies","aria-hidden":"true"}},[e._v("#")]),e._v(" Deployment Strategies")]),e._v(" "),a("p",[e._v("Flagger can run automated application analysis, promotion and rollback for the following deployment strategies:")]),e._v(" "),a("ul",[a("li",[a("strong",[e._v("Canary Release")]),e._v(" (progressive traffic shifting)\n"),a("ul",[a("li",[e._v("Istio, Linkerd, App Mesh, NGINX, Contour, Gloo")])])]),e._v(" "),a("li",[a("strong",[e._v("A/B Testing")]),e._v(" (HTTP headers and cookies traffic routing)\n"),a("ul",[a("li",[e._v("Istio, App Mesh, NGINX, Contour")])])]),e._v(" "),a("li",[a("strong",[e._v("Blue/Green")]),e._v(" (traffic switching)\n"),a("ul",[a("li",[e._v("Kubernetes CNI, Istio, Linkerd, App Mesh, NGINX, Contour, Gloo")])])]),e._v(" "),a("li",[a("strong",[e._v("Blue/Green Mirroring")]),e._v(" (traffic shadowing)\n"),a("ul",[a("li",[e._v("Istio")])])])]),e._v(" "),a("p",[e._v("For Canary releases and A/B testing you'll need a Layer 7 traffic management solution like a service mesh or an ingress controller.\nFor Blue/Green deployments no service mesh or ingress controller is required.")]),e._v(" "),a("p",[e._v("A canary analysis is triggered by changes in any of the following objects:")]),e._v(" "),a("ul",[a("li",[e._v("Deployment PodSpec (container image, command, ports, env, resources, etc)")]),e._v(" "),a("li",[e._v("ConfigMaps mounted as volumes or mapped to environment variables")]),e._v(" "),a("li",[e._v("Secrets mounted as volumes or mapped to environment variables")])]),e._v(" "),a("h2",{attrs:{id:"canary-release"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#canary-release","aria-hidden":"true"}},[e._v("#")]),e._v(" Canary Release")]),e._v(" "),a("p",[e._v("Flagger implements a control loop that gradually shifts traffic to the canary while measuring key performance\nindicators like HTTP requests success rate, requests average duration and pod health.\nBased on analysis of the KPIs a canary is promoted or aborted.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-steps.png",alt:"Flagger Canary Stages"}})]),e._v(" "),a("p",[e._v("The canary analysis runs periodically until it reaches the maximum traffic weight or the failed checks threshold.")]),e._v(" "),a("p",[e._v("Spec:")]),e._v(" "),a("div",{staticClass:"language-yaml extra-class"},[a("pre",{pre:!0,attrs:{class:"language-yaml"}},[a("code",[e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("analysis")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# schedule interval (default 60s)")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("interval")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 1m\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# max number of failed metric checks before rollback")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("threshold")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("10")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# max traffic percentage routed to canary")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# percentage (0-100)")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("maxWeight")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("50")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# canary increment step")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# percentage (0-100)")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("stepWeight")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("2")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# deploy straight to production without")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# the metrics and webhook checks")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("skipAnalysis")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token boolean important"}},[e._v("false")]),e._v("\n")])])]),a("p",[e._v("The above analysis, if it succeeds, will run for 25 minutes while validating the HTTP metrics and webhooks every minute.\nYou can determine the minimum time that it takes to validate and promote a canary deployment using this formula:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("interval * (maxWeight / stepWeight)\n")])])]),a("p",[e._v("And the time it takes for a canary to be rollback when the metrics or webhook checks are failing:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("interval * threshold \n")])])]),a("p",[e._v("In emergency cases, you may want to skip the analysis phase and ship changes directly to production.\nAt any time you can set the "),a("code",[e._v("spec.skipAnalysis: true")]),e._v(".\nWhen skip analysis is enabled, Flagger checks if the canary deployment is healthy and\npromotes it without analysing it. If an analysis is underway, Flagger cancels it and runs the promotion.")]),e._v(" "),a("p",[e._v("Gated canary promotion stages:")]),e._v(" "),a("ul",[a("li",[e._v("scan for canary deployments")]),e._v(" "),a("li",[e._v("check primary and canary deployment status\n"),a("ul",[a("li",[e._v("halt advancement if a rolling update is underway")]),e._v(" "),a("li",[e._v("halt advancement if pods are unhealthy")])])]),e._v(" "),a("li",[e._v("call confirm-rollout webhooks and check results\n"),a("ul",[a("li",[e._v("halt advancement if any hook returns a non HTTP 2xx result")])])]),e._v(" "),a("li",[e._v("call pre-rollout webhooks and check results\n"),a("ul",[a("li",[e._v("halt advancement if any hook returns a non HTTP 2xx result")]),e._v(" "),a("li",[e._v("increment the failed checks counter")])])]),e._v(" "),a("li",[e._v("increase canary traffic weight percentage from 0% to 2% (step weight)")]),e._v(" "),a("li",[e._v("call rollout webhooks and check results")]),e._v(" "),a("li",[e._v("check canary HTTP request success rate and latency\n"),a("ul",[a("li",[e._v("halt advancement if any metric is under the specified threshold")]),e._v(" "),a("li",[e._v("increment the failed checks counter")])])]),e._v(" "),a("li",[e._v("check if the number of failed checks reached the threshold\n"),a("ul",[a("li",[e._v("route all traffic to primary")]),e._v(" "),a("li",[e._v("scale to zero the canary deployment and mark it as failed")]),e._v(" "),a("li",[e._v("call post-rollout webhooks")]),e._v(" "),a("li",[e._v("post the analysis result to Slack")]),e._v(" "),a("li",[e._v("wait for the canary deployment to be updated and start over")])])]),e._v(" "),a("li",[e._v("increase canary traffic weight by 2% (step weight) till it reaches 50% (max weight)\n"),a("ul",[a("li",[e._v("halt advancement if any webhook call fails")]),e._v(" "),a("li",[e._v("halt advancement while canary request success rate is under the threshold")]),e._v(" "),a("li",[e._v("halt advancement while canary request duration P99 is over the threshold")]),e._v(" "),a("li",[e._v("halt advancement while any custom metric check fails")]),e._v(" "),a("li",[e._v("halt advancement if the primary or canary deployment becomes unhealthy")]),e._v(" "),a("li",[e._v("halt advancement while canary deployment is being scaled up/down by HPA")])])]),e._v(" "),a("li",[e._v("call confirm-promotion webhooks and check results\n"),a("ul",[a("li",[e._v("halt advancement if any hook returns a non HTTP 2xx result")])])]),e._v(" "),a("li",[e._v("promote canary to primary\n"),a("ul",[a("li",[e._v("copy ConfigMaps and Secrets from canary to primary")]),e._v(" "),a("li",[e._v("copy canary deployment spec template over primary")])])]),e._v(" "),a("li",[e._v("wait for primary rolling update to finish\n"),a("ul",[a("li",[e._v("halt advancement if pods are unhealthy")])])]),e._v(" "),a("li",[e._v("route all traffic to primary")]),e._v(" "),a("li",[e._v("scale to zero the canary deployment")]),e._v(" "),a("li",[e._v("mark rollout as finished")]),e._v(" "),a("li",[e._v("call post-rollout webhooks")]),e._v(" "),a("li",[e._v("send notification with the canary analysis result")]),e._v(" "),a("li",[e._v("wait for the canary deployment to be updated and start over")])]),e._v(" "),a("h2",{attrs:{id:"a-b-testing"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#a-b-testing","aria-hidden":"true"}},[e._v("#")]),e._v(" A/B Testing")]),e._v(" "),a("p",[e._v("For frontend applications that require session affinity you should use HTTP headers or cookies match conditions\nto ensure a set of users will stay on the same version for the whole duration of the canary analysis.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-abtest-steps.png",alt:"Flagger A/B Testing Stages"}})]),e._v(" "),a("p",[e._v("You can enable A/B testing by specifying the HTTP match conditions and the number of iterations.\nIf Flagger finds a HTTP match condition, it will ignore the "),a("code",[e._v("maxWeight")]),e._v(" and "),a("code",[e._v("stepWeight")]),e._v(" settings.")]),e._v(" "),a("p",[e._v("Istio example:")]),e._v(" "),a("div",{staticClass:"language-yaml extra-class"},[a("pre",{pre:!0,attrs:{class:"language-yaml"}},[a("code",[e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("analysis")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# schedule interval (default 60s)")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("interval")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 1m\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# total number of iterations")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("iterations")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("10")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# max number of failed iterations before rollback")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("threshold")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("2")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# canary match condition")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("match")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("headers")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("x-canary")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("regex")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v('".*insider.*"')]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("headers")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("cookie")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("regex")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v('"^(.*?;)?(canary=always)(;.*)?$"')]),e._v("\n")])])]),a("p",[e._v("The above configuration will run an analysis for ten minutes targeting the Safari users and those that have a test cookie.\nYou can determine the minimum time that it takes to validate and promote a canary deployment using this formula:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("interval * iterations\n")])])]),a("p",[e._v("And the time it takes for a canary to be rollback when the metrics or webhook checks are failing:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("interval * threshold \n")])])]),a("p",[e._v("App Mesh example:")]),e._v(" "),a("div",{staticClass:"language-yaml extra-class"},[a("pre",{pre:!0,attrs:{class:"language-yaml"}},[a("code",[e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("analysis")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("interval")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 1m\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("threshold")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("10")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("iterations")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("2")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("match")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("headers")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("user-agent")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("regex")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v('".*Chrome.*"')]),e._v("\n")])])]),a("p",[e._v("Note that App Mesh supports a single condition.")]),e._v(" "),a("p",[e._v("Contour example:")]),e._v(" "),a("div",{staticClass:"language-yaml extra-class"},[a("pre",{pre:!0,attrs:{class:"language-yaml"}},[a("code",[e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("analysis")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("interval")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 1m\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("threshold")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("10")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("iterations")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("2")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("match")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("headers")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("user-agent")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("prefix")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v('"Chrome"')]),e._v("\n")])])]),a("p",[e._v("Note that Contour does not support regex, you can use prefix, suffix or exact.")]),e._v(" "),a("p",[e._v("NGINX example:")]),e._v(" "),a("div",{staticClass:"language-yaml extra-class"},[a("pre",{pre:!0,attrs:{class:"language-yaml"}},[a("code",[e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("analysis")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("interval")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 1m\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("threshold")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("10")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("iterations")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("2")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("match")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("headers")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("x-canary")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("exact")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v('"insider"')]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("headers")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("cookie")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("exact")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v('"canary"')]),e._v("\n")])])]),a("p",[e._v("Note that the NGINX ingress controller supports only exact matching for a single header and the cookie value is set to "),a("code",[e._v("always")]),e._v(".")]),e._v(" "),a("p",[e._v("The above configurations will route users with the x-canary header or canary cookie to the canary instance during analysis:")]),e._v(" "),a("div",{staticClass:"language-bash extra-class"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[a("span",{pre:!0,attrs:{class:"token function"}},[e._v("curl")]),e._v(" -H "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v("'X-Canary: insider'")]),e._v(" http://app.example.com\n"),a("span",{pre:!0,attrs:{class:"token function"}},[e._v("curl")]),e._v(" -b "),a("span",{pre:!0,attrs:{class:"token string"}},[e._v("'canary=always'")]),e._v(" http://app.example.com\n")])])]),a("h2",{attrs:{id:"blue-green-deployments"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#blue-green-deployments","aria-hidden":"true"}},[e._v("#")]),e._v(" Blue/Green Deployments")]),e._v(" "),a("p",[e._v("For applications that are not deployed on a service mesh, Flagger can orchestrate blue/green style deployments\nwith Kubernetes L4 networking. When using Istio you have the option to mirror traffic between blue and green.")]),e._v(" "),a("p",[a("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-bluegreen-steps.png",alt:"Flagger Blue/Green Stages"}})]),e._v(" "),a("p",[e._v("You can use the blue/green deployment strategy by replacing "),a("code",[e._v("stepWeight/maxWeight")]),e._v(" with "),a("code",[e._v("iterations")]),e._v(" in the "),a("code",[e._v("canaryAnalysis")]),e._v(" spec:")]),e._v(" "),a("div",{staticClass:"language-yaml extra-class"},[a("pre",{pre:!0,attrs:{class:"language-yaml"}},[a("code",[e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("analysis")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# schedule interval (default 60s)")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("interval")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 1m\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# total number of iterations")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("iterations")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("10")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# max number of failed iterations before rollback")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("threshold")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("2")]),e._v("\n")])])]),a("p",[e._v("With the above configuration Flagger will run conformance and load tests on the canary pods for ten minutes.\nIf the metrics analysis succeeds, live traffic will be switched from the old version to the new one when the\ncanary is promoted.")]),e._v(" "),a("p",[e._v("The blue/green deployment strategy is supported for all service mesh providers.")]),e._v(" "),a("p",[e._v("Blue/Green rollout steps for service mesh:")]),e._v(" "),a("ul",[a("li",[e._v("detect new revision (deployment spec, secrets or configmaps changes)")]),e._v(" "),a("li",[e._v("scale up the canary (green)")]),e._v(" "),a("li",[e._v("run conformance tests for the canary pods")]),e._v(" "),a("li",[e._v("run load tests and metric checks for the canary pods every minute")]),e._v(" "),a("li",[e._v("abort the canary release if the failure threshold is reached")]),e._v(" "),a("li",[e._v("route traffic to canary")]),e._v(" "),a("li",[e._v("promote canary spec over primary (blue)")]),e._v(" "),a("li",[e._v("wait for primary rollout")]),e._v(" "),a("li",[e._v("route traffic to primary")]),e._v(" "),a("li",[e._v("scale down canary")])]),e._v(" "),a("p",[e._v("After the analysis finishes, the traffic is routed to the canary (green) before triggering the primary (blue)\nrolling update, this ensures a smooth transition to the new version avoiding dropping in-flight requests during\nthe Kubernetes deployment rollout.")]),e._v(" "),a("h2",{attrs:{id:"blue-green-with-traffic-mirroring"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#blue-green-with-traffic-mirroring","aria-hidden":"true"}},[e._v("#")]),e._v(" Blue/Green with Traffic Mirroring")]),e._v(" "),a("p",[e._v("Traffic Mirroring is a pre-stage in a Canary (progressive traffic shifting) or\nBlue/Green deployment strategy. Traffic mirroring will copy each incoming\nrequest, sending one request to the primary and one to the canary service.\nThe response from the primary is sent back to the user. The response from the canary\nis discarded. Metrics are collected on both requests so that the deployment will\nonly proceed if the canary metrics are healthy.")]),e._v(" "),a("p",[e._v("Mirroring should be used for requests that are "),a("strong",[e._v("idempotent")]),e._v(" or capable of\nbeing processed twice (once by the primary and once by the canary). Reads are\nidempotent. Before using mirroring on requests that may be writes, you should\nconsider what will happen if a write is duplicated and handled by the primary\nand canary.")]),e._v(" "),a("p",[e._v("To use mirroring, set "),a("code",[e._v("spec.canaryAnalysis.mirror")]),e._v(" to "),a("code",[e._v("true")]),e._v(".")]),e._v(" "),a("p",[e._v("Istio example:")]),e._v(" "),a("div",{staticClass:"language-yaml extra-class"},[a("pre",{pre:!0,attrs:{class:"language-yaml"}},[a("code",[e._v(" "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("analysis")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# schedule interval (default 60s)")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("interval")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 1m\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# total number of iterations")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("iterations")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("10")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# max number of failed iterations before rollback")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("threshold")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[e._v("2")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[e._v("# Traffic shadowing (compatible with Istio only)")]),e._v("\n "),a("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("mirror")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),a("span",{pre:!0,attrs:{class:"token boolean important"}},[e._v("true")]),e._v("\n")])])]),a("p",[e._v("Mirroring rollout steps for service mesh:")]),e._v(" "),a("ul",[a("li",[e._v("detect new revision (deployment spec, secrets or configmaps changes)")]),e._v(" "),a("li",[e._v("scale from zero the canary deployment")]),e._v(" "),a("li",[e._v("wait for the HPA to set the canary minimum replicas")]),e._v(" "),a("li",[e._v("check canary pods health")]),e._v(" "),a("li",[e._v("run the acceptance tests")]),e._v(" "),a("li",[e._v("abort the canary release if tests fail")]),e._v(" "),a("li",[e._v("start the load tests")]),e._v(" "),a("li",[e._v("mirror traffic from primary to canary")]),e._v(" "),a("li",[e._v("check request success rate and request duration every minute")]),e._v(" "),a("li",[e._v("abort the canary release if the failure threshold is reached")]),e._v(" "),a("li",[e._v("stop traffic mirroring after the number of iterations is reached")]),e._v(" "),a("li",[e._v("route live traffic to the canary pods")]),e._v(" "),a("li",[e._v("promote the canary (update the primary secrets, configmaps and deployment spec)")]),e._v(" "),a("li",[e._v("wait for the primary deployment rollout to finish")]),e._v(" "),a("li",[e._v("wait for the HPA to set the primary minimum replicas")]),e._v(" "),a("li",[e._v("check primary pods health")]),e._v(" "),a("li",[e._v("switch live traffic back to primary")]),e._v(" "),a("li",[e._v("scale to zero the canary")]),e._v(" "),a("li",[e._v("send notification with the canary analysis result")])]),e._v(" "),a("p",[e._v("After the analysis finishes, the traffic is routed to the canary (green) before triggering the primary (blue)\nrolling update, this ensures a smooth transition to the new version avoiding dropping in-flight requests during\nthe Kubernetes deployment rollout.")])])},[],!1,null,null,null);t.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/32.591c3f2b.js b/assets/js/32.591c3f2b.js new file mode 100644 index 00000000..a584cc29 --- /dev/null +++ b/assets/js/32.591c3f2b.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[32],{292:function(t,a,e){"use strict";e.r(a);var s=e(37),n=Object(s.a)({},function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("h1",{attrs:{id:"how-it-works"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#how-it-works","aria-hidden":"true"}},[t._v("#")]),t._v(" How it works")]),t._v(" "),e("p",[e("a",{attrs:{href:"https://github.com/weaveworks/flagger",target:"_blank",rel:"noopener noreferrer"}},[t._v("Flagger"),e("OutboundLink")],1),t._v(" can be configured to automate the release process\nfor Kubernetes workloads with a custom resource named canary.")]),t._v(" "),e("h2",{attrs:{id:"canary-resource"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#canary-resource","aria-hidden":"true"}},[t._v("#")]),t._v(" Canary resource")]),t._v(" "),e("p",[t._v("The canary custom resource defines the release process of an application running on Kubernetes\nand is portable across clusters, service meshes and ingress providers.")]),t._v(" "),e("p",[t._v("For a deployment named "),e("em",[t._v("podinfo")]),t._v(", a canary release with progressive traffic shifting can be defined as:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("maxWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("50")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("stepWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("duration\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("500")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"')]),t._v("\n")])])]),e("p",[t._v("When you deploy a new version of an app, Flagger gradually shifts traffic to the canary,\nand at the same time, measures the requests success rate as well as the average response duration.\nYou can extend the canary analysis with custom metrics, acceptance and load testing\nto harden the validation process of your app release process.")]),t._v(" "),e("p",[t._v("If you are running multiple service meshes or ingress controllers in the same cluster,\nyou can override the global provider for a specific canary with "),e("code",[t._v("spec.provider")]),t._v(".")]),t._v(" "),e("h2",{attrs:{id:"canary-target"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#canary-target","aria-hidden":"true"}},[t._v("#")]),t._v(" Canary target")]),t._v(" "),e("p",[t._v("A canary resource can target a Kubernetes Deployment or DaemonSet.")]),t._v(" "),e("p",[t._v("Kubernetes Deployment example:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("progressDeadlineSeconds")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("60")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("autoscalerRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" autoscaling/v2beta1\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" HorizontalPodAutoscaler\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n")])])]),e("p",[t._v("Based on the above configuration, Flagger generates the following Kubernetes objects:")]),t._v(" "),e("ul",[e("li",[e("code",[t._v("deployment/-primary")])]),t._v(" "),e("li",[e("code",[t._v("hpa/-primary")])])]),t._v(" "),e("p",[t._v("The primary deployment is considered the stable release of your app, by default all traffic is routed to this version\nand the target deployment is scaled to zero.\nFlagger will detect changes to the target deployment (including secrets and configmaps) and will perform a\ncanary analysis before promoting the new version as primary.")]),t._v(" "),e("p",[t._v("If the target deployment uses secrets and/or configmaps, Flagger will create a copy of each object using the "),e("code",[t._v("-primary")]),t._v("\nprefix and will reference these objects in the primary deployment. You can disable the secrets/configmaps tracking\nwith the "),e("code",[t._v("-enable-config-tracking=false")]),t._v(" command flag in the Flagger deployment manifest under containers args\nor by setting "),e("code",[t._v("--set configTracking.enabled=false")]),t._v(" when installing Flagger with Helm.")]),t._v(" "),e("p",[e("strong",[t._v("Note")]),t._v(" that the target deployment must have a single label selector in the format "),e("code",[t._v("app: ")]),t._v(":")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" apps/v1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Deployment\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("selector")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("matchLabels")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("template")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("labels")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("app")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n")])])]),e("p",[t._v("Besides "),e("code",[t._v("app")]),t._v(" Flagger supports "),e("code",[t._v("name")]),t._v(" and "),e("code",[t._v("app.kubernetes.io/name")]),t._v(" selectors.\nIf you use a different convention you can specify your label with\nthe "),e("code",[t._v("-selector-labels=my-app-label")]),t._v(" command flag in the Flagger deployment manifest under containers args\nor by setting "),e("code",[t._v("--set selectorLabels=my-app-label")]),t._v(" when installing Flagger with Helm.")]),t._v(" "),e("p",[t._v("The autoscaler reference is optional, when specified, Flagger will pause the traffic increase while the\ntarget and primary deployments are scaled up or down. HPA can help reduce the resource usage during the canary analysis.")]),t._v(" "),e("p",[t._v("The progress deadline represents the maximum time in seconds for the canary deployment to make progress\nbefore it is rolled back, defaults to ten minutes.")]),t._v(" "),e("h2",{attrs:{id:"canary-service"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#canary-service","aria-hidden":"true"}},[t._v("#")]),t._v(" Canary service")]),t._v(" "),e("p",[t._v("A canary resource dictates how the target workload is exposed inside the cluster.\nThe canary target should expose a TCP port that will be used by Flagger to create the ClusterIP Services.")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" podinfo\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("portName")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("targetPort")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("portDiscovery")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token boolean important"}},[t._v("true")]),t._v("\n")])])]),e("p",[t._v("The container port from the target workload should match the "),e("code",[t._v("service.port")]),t._v(" or "),e("code",[t._v("service.targetPort")]),t._v(".\nThe "),e("code",[t._v("service.name")]),t._v(" is optional, defaults to "),e("code",[t._v("spec.targetRef.name")]),t._v(".\nThe "),e("code",[t._v("service.targetPort")]),t._v(" can be a container port number or name.\nThe "),e("code",[t._v("service.portName")]),t._v(" is optional (defaults to "),e("code",[t._v("http")]),t._v("), if your workload uses gPRC then set the port name to "),e("code",[t._v("grcp")]),t._v(".")]),t._v(" "),e("p",[t._v("If port discovery is enabled, Flagger scans the target workload and extracts the containers\nports excluding the port specified in the canary service and service mesh sidecar ports.\nThese ports will be used when generating the ClusterIP services.")]),t._v(" "),e("p",[t._v("Based on the canary spec service, Flagger creates the following Kubernetes ClusterIP service:")]),t._v(" "),e("ul",[e("li",[e("code",[t._v("..svc.cluster.local")]),e("br"),t._v("\nselector "),e("code",[t._v("app=-primary")])]),t._v(" "),e("li",[e("code",[t._v("-primary..svc.cluster.local")]),e("br"),t._v("\nselector "),e("code",[t._v("app=-primary")])]),t._v(" "),e("li",[e("code",[t._v("-canary..svc.cluster.local")]),e("br"),t._v("\nselector "),e("code",[t._v("app=")])])]),t._v(" "),e("p",[t._v("This ensures that traffic to "),e("code",[t._v("podinfo.test:9898")]),t._v(" will be routed to the latest stable release of your app.\nThe "),e("code",[t._v("podinfo-canary.test:9898")]),t._v(" address is available only during the\ncanary analysis and can be used for conformance testing or load testing.")]),t._v(" "),e("p",[t._v("Besides the port mapping, the service specification can contain URI match and rewrite rules,\ntimeout and retry polices:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("service")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("port")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9898")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("prefix")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("rewrite")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("uri")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" /\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("retries")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("attempts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("3")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("perTryTimeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1s\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n")])])]),e("p",[t._v("When using "),e("strong",[t._v("Istio")]),t._v(" as the mesh provider, you can also specify\nHTTP header operations, CORS and traffic policies, Istio gateways and hosts.\nThe Istio routing configuration can be found "),e("router-link",{attrs:{to:"/faq.html#istio-routing"}},[t._v("here")]),t._v(".")],1),t._v(" "),e("h2",{attrs:{id:"canary-status"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#canary-status","aria-hidden":"true"}},[t._v("#")]),t._v(" Canary status")]),t._v(" "),e("p",[t._v("You can use kubectl to get the current status of canary deployments cluster wide:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl get canaries --all-namespaces\n\nNAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME\n"),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" podinfo Progressing "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("15")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-06-30T14:05:07Z\nprod frontend Succeeded "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-06-30T16:15:07Z\nprod backend Failed "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),t._v("-06-30T17:05:07Z\n")])])]),e("p",[t._v("The status condition reflects the last known state of the canary analysis:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl -n "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" get canary/podinfo -oyaml "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("awk")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/status/,0'")]),t._v("\n")])])]),e("p",[t._v("A successful rollout status:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("status")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("canaryWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("failedChecks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("iterations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("lastAppliedSpec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"14788816656920327485"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("lastPromotedSpec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"14788816656920327485"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("conditions")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("lastTransitionTime")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"2019-07-10T08:23:18Z"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("lastUpdateTime")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"2019-07-10T08:23:18Z"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("message")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Canary analysis completed successfully"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" promotion finished.\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("reason")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Succeeded\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("status")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"True"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Promoted\n")])])]),e("p",[t._v("The "),e("code",[t._v("Promoted")]),t._v(" status condition can have one of the following reasons:\nInitialized, Waiting, Progressing, Promoting, Finalising, Succeeded or Failed.\nA failed canary will have the promoted status set to "),e("code",[t._v("false")]),t._v(",\nthe reason to "),e("code",[t._v("failed")]),t._v(" and the last applied spec will be different to the last promoted one.")]),t._v(" "),e("p",[t._v("Wait for a successful rollout:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[t._v("kubectl "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("wait")]),t._v(" canary/podinfo --for"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("condition"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("promoted\n")])])]),e("p",[t._v("CI example:")]),t._v(" "),e("div",{staticClass:"language-bash extra-class"},[e("pre",{pre:!0,attrs:{class:"language-bash"}},[e("code",[e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# update the container image")]),t._v("\nkubectl "),e("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("set")]),t._v(" image deployment/podinfo "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("podinfod")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("stefanprodan/podinfo:3.0.1\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# wait for Flagger to detect the change")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("ok")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("false\n"),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("until")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token variable"}},[t._v("${ok}")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("do")]),t._v("\n kubectl get canary/podinfo "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("grep")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Progressing'")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("&&")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("ok")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("true "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("||")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("ok")]),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("false\n "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("sleep")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n"),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("done")]),t._v("\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# wait for the canary analysis to finish")]),t._v("\nkubectl "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("wait")]),t._v(" canary/podinfo --for"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("condition"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("promoted --timeout"),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("5m\n\n"),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# check if the deployment was successful ")]),t._v("\nkubectl get canary/podinfo "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("|")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("grep")]),t._v(" Succeeded\n")])])]),e("h2",{attrs:{id:"canary-analysis"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#canary-analysis","aria-hidden":"true"}},[t._v("#")]),t._v(" Canary analysis")]),t._v(" "),e("p",[t._v("The canary analysis defines:")]),t._v(" "),e("ul",[e("li",[t._v("the type of "),e("router-link",{attrs:{to:"/usage/deployment-strategies.html"}},[t._v("deployment strategy")])],1),t._v(" "),e("li",[t._v("the "),e("router-link",{attrs:{to:"/usage/metrics.html"}},[t._v("metrics")]),t._v(" used to validate the canary version")],1),t._v(" "),e("li",[t._v("the "),e("router-link",{attrs:{to:"/usage/webhooks.html"}},[t._v("webhooks")]),t._v(" used for conformance testing, load testing and manual gating")],1),t._v(" "),e("li",[t._v("the "),e("router-link",{attrs:{to:"/usage/alerting.html"}},[t._v("alerting settings")])],1)]),t._v(" "),e("p",[t._v("Spec:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# schedule interval (default 60s)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max number of failed metric checks before rollback")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("threshold")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# max traffic percentage routed to canary")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("maxWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# canary increment step")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("stepWeight")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# total number of iterations")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# used for A/B Testing and Blue/Green")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("iterations")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# canary match conditions")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# used for A/B Testing")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("match")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# HTTP header")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# key performance indicators")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# metric check")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# alerting")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("alerts")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# alert provider")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# external checks")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# hook")]),t._v("\n")])])]),e("p",[t._v("The canary analysis runs periodically until it reaches the maximum traffic weight or the number of iterations.\nOn each run, Flagger calls the webhooks, checks the metrics and if the failed checks threshold is reached, stops the\nanalysis and rolls back the canary. If alerting is configured, Flagger will post the analysis result using the alert providers.")])])},[],!1,null,null,null);a.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/33.c456709f.js b/assets/js/33.c456709f.js new file mode 100644 index 00000000..9c4f95a5 --- /dev/null +++ b/assets/js/33.c456709f.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[33],{293:function(t,a,e){"use strict";e.r(a);var s=e(37),n=Object(s.a)({},function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("h1",{attrs:{id:"metrics-analysis"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#metrics-analysis","aria-hidden":"true"}},[t._v("#")]),t._v(" Metrics Analysis")]),t._v(" "),e("p",[t._v("As part of the analysis process, Flagger can validate service level objectives (SLOs) like\navailability, error rate percentage, average response time and any other objective based on app specific metrics.\nIf a drop in performance is noticed during the SLOs analysis,\nthe release will be automatically rolled back with minimum impact to end-users.")]),t._v(" "),e("h2",{attrs:{id:"builtin-metrics"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#builtin-metrics","aria-hidden":"true"}},[t._v("#")]),t._v(" Builtin metrics")]),t._v(" "),e("p",[t._v("Flagger comes with two builtin metric checks: HTTP request success rate and duration.")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("success"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# minimum req success rate (non 5xx responses)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# percentage (0-100)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" request"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("duration\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# maximum req duration P99")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# milliseconds")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("500")]),t._v("\n")])])]),e("p",[t._v("For each metric you can specify a range of accepted values with "),e("code",[t._v("thresholdRange")]),t._v("\nand the window size or the time series with "),e("code",[t._v("interval")]),t._v(".\nThe builtin checks are available for every service mesh / ingress controller\nand are implemented with "),e("router-link",{attrs:{to:"/faq.html#metrics"}},[t._v("Prometheus queries")]),t._v(".")],1),t._v(" "),e("h2",{attrs:{id:"custom-metrics"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#custom-metrics","aria-hidden":"true"}},[t._v("#")]),t._v(" Custom metrics")]),t._v(" "),e("p",[t._v("The canary analysis can be extended with custom metric checks. Using a "),e("code",[t._v("MetricTemplate")]),t._v(" custom resource, you\nconfigure Flagger to connect to a metric provider and run a query that returns a "),e("code",[t._v("float64")]),t._v(" value.\nThe query result is used to validate the canary based on the specified threshold range.")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" MetricTemplate\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" my"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("metric\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# can be prometheus or datadog")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("address")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# API URL")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("secretRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# name of the secret containing the API credentials")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("query")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# metric query")]),t._v("\n")])])]),e("p",[t._v("The following variables are available in query templates:")]),t._v(" "),e("ul",[e("li",[e("code",[t._v("name")]),t._v(" (canary.metadata.name)")]),t._v(" "),e("li",[e("code",[t._v("namespace")]),t._v(" (canary.metadata.namespace)")]),t._v(" "),e("li",[e("code",[t._v("target")]),t._v(" (canary.spec.targetRef.name)")]),t._v(" "),e("li",[e("code",[t._v("service")]),t._v(" (canary.spec.service.name)")]),t._v(" "),e("li",[e("code",[t._v("ingress")]),t._v(" (canary.spec.ingresRef.name)")]),t._v(" "),e("li",[e("code",[t._v("interval")]),t._v(" (canary.spec.canaryAnalysis.metrics[].interval)")])]),t._v(" "),e("p",[t._v("A canary analysis metric can reference a template with "),e("code",[t._v("templateRef")]),t._v(":")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"my metric"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("templateRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" my"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("metric\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# namespace is optional")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# when not specified, the canary namespace will be used")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# accepted values")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("min")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("1000")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# metric query time window")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n")])])]),e("h2",{attrs:{id:"prometheus"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#prometheus","aria-hidden":"true"}},[t._v("#")]),t._v(" Prometheus")]),t._v(" "),e("p",[t._v("You can create custom metric checks targeting a Prometheus server\nby setting the provider type to "),e("code",[t._v("prometheus")]),t._v(" and writing the query in PromQL.")]),t._v(" "),e("p",[t._v("Prometheus template example:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" MetricTemplate\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" not"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("found"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("percentage\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" prometheus\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("address")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//promethues.istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9090")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("query")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("|")]),e("span",{pre:!0,attrs:{class:"token scalar string"}},[t._v('\n 100 - sum(\n rate(\n istio_requests_total{\n reporter="destination",\n destination_workload_namespace="{{ namespace }}",\n destination_workload="{{ target }}",\n response_code!="404"\n }[{{ interval }}]\n )\n )\n /\n sum(\n rate(\n istio_requests_total{\n reporter="destination",\n destination_workload_namespace="{{ namespace }}",\n destination_workload="{{ target }}"\n }[{{ interval }}]\n )\n ) * 100')]),t._v("\n")])])]),e("p",[t._v("Reference the template in the canary analysis:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"404s percentage"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("templateRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" not"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("found"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("percentage\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n")])])]),e("p",[t._v("The above configuration validates the canary by checking\nif the HTTP 404 req/sec percentage is below 5 percent of the total traffic.\nIf the 404s rate reaches the 5% threshold, then the canary fails.")]),t._v(" "),e("p",[t._v("Prometheus gRPC error rate example:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" MetricTemplate\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" grpc"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("error"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("percentage\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" prometheus\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("address")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("promethues.flagger"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("9090")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("query")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("|")]),e("span",{pre:!0,attrs:{class:"token scalar string"}},[t._v('\n 100 - sum(\n rate(\n grpc_server_handled_total{\n grpc_code!="OK",\n kubernetes_namespace="{{ namespace }}",\n kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"\n }[{{ interval }}]\n )\n )\n /\n sum(\n rate(\n grpc_server_started_total{\n kubernetes_namespace="{{ namespace }}",\n kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"\n }[{{ interval }}]\n )\n ) * 100')]),t._v("\n")])])]),e("p",[t._v("The above template is for gPRC services instrumented with "),e("a",{attrs:{href:"https://github.com/grpc-ecosystem/go-grpc-prometheus",target:"_blank",rel:"noopener noreferrer"}},[t._v("go-grpc-prometheus"),e("OutboundLink")],1),t._v(".")]),t._v(" "),e("h2",{attrs:{id:"datadog"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#datadog","aria-hidden":"true"}},[t._v("#")]),t._v(" Datadog")]),t._v(" "),e("p",[t._v("You can create custom metric checks using the Datadog provider.")]),t._v(" "),e("p",[t._v("Create a secret with your Datadog API credentials:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" v1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Secret\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" datadog\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("data")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("datadog_api_key")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" your"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("datadog"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("api"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("key\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("datadog_application_key")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" your"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("datadog"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("application"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("key\n")])])]),e("p",[t._v("Datadog template example:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1beta1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" MetricTemplate\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" not"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("found"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("percentage\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" datadog\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("address")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" https"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//api.datadoghq.com\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("secretRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" datadog\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("query")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("|")]),e("span",{pre:!0,attrs:{class:"token scalar string"}},[t._v("\n 100 - (\n sum:istio.mesh.request.count{\n reporter:destination,\n destination_workload_namespace:{{ namespace }},\n destination_workload:{{ target }},\n !response_code:404\n }.as_count()\n / \n sum:istio.mesh.request.count{\n reporter:destination,\n destination_workload_namespace:{{ namespace }},\n destination_workload:{{ target }}\n }.as_count()\n ) * 100")]),t._v("\n")])])]),e("p",[t._v("Reference the template in the canary analysis:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"404s percentage"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("templateRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" not"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("found"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("percentage\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("namespace")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" istio"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n")])])]),e("h2",{attrs:{id:"amazon-cloudwatch"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#amazon-cloudwatch","aria-hidden":"true"}},[t._v("#")]),t._v(" Amazon CloudWatch")]),t._v(" "),e("p",[t._v("You can create custom metric checks using the CloudWatch metrics provider.")]),t._v(" "),e("p",[t._v("CloudWatch template example:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("apiVersion")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" flagger.app/v1alpha1\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("kind")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" MetricTemplate\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cloudwatch"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("error"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n"),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("spec")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("provider")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cloudwatch\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("region")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ap"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("northeast"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("1 ")]),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# specify the region of your metrics")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("query")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("|")]),e("span",{pre:!0,attrs:{class:"token scalar string"}},[t._v('\n [\n {\n "Id": "e1",\n "Expression": "m1 / m2",\n "Label": "ErrorRate"\n },\n {\n "Id": "m1",\n "MetricStat": {\n "Metric": {\n "Namespace": "MyKubernetesCluster",\n "MetricName": "ErrorCount",\n "Dimensions": [\n {\n "Name": "appName",\n "Value": "{{ name }}.{{ namespace }}"\n }\n ]\n },\n "Period": 60,\n "Stat": "Sum",\n "Unit": "Count"\n },\n "ReturnData": false\n },\n {\n "Id": "m2",\n "MetricStat": {\n "Metric": {\n "Namespace": "MyKubernetesCluster",\n "MetricName": "RequestCount",\n "Dimensions": [\n {\n "Name": "appName",\n "Value": "{{ name }}.{{ namespace }}"\n }\n ]\n },\n "Period": 60,\n "Stat": "Sum",\n "Unit": "Count"\n },\n "ReturnData": false\n }\n ]')]),t._v("\n")])])]),e("p",[t._v("The query format documentation can be found "),e("a",{attrs:{href:"https://aws.amazon.com/premiumsupport/knowledge-center/cloudwatch-getmetricdata-api/",target:"_blank",rel:"noopener noreferrer"}},[t._v("here"),e("OutboundLink")],1),t._v(".")]),t._v(" "),e("p",[t._v("Reference the template in the canary analysis:")]),t._v(" "),e("div",{staticClass:"language-yaml extra-class"},[e("pre",{pre:!0,attrs:{class:"language-yaml"}},[e("code",[t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metrics")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"app error rate"')]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("templateRef")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cloudwatch"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("error"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rate\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("thresholdRange")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("max")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0.1")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("interval")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 1m\n")])])]),e("p",[e("strong",[t._v("Note")]),t._v(" that Flagger need AWS IAM permission to perform "),e("code",[t._v("cloudwatch:GetMetricData")]),t._v(" to use this provider.")])])},[],!1,null,null,null);a.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/34.5a071e36.js b/assets/js/34.5a071e36.js new file mode 100644 index 00000000..c13e5067 --- /dev/null +++ b/assets/js/34.5a071e36.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[34],{294:function(a,t,s){"use strict";s.r(t);var n=s(37),e=Object(n.a)({},function(){var a=this,t=a.$createElement,s=a._self._c||t;return s("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[s("h1",{attrs:{id:"monitoring"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#monitoring","aria-hidden":"true"}},[a._v("#")]),a._v(" Monitoring")]),a._v(" "),s("h2",{attrs:{id:"grafana"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#grafana","aria-hidden":"true"}},[a._v("#")]),a._v(" Grafana")]),a._v(" "),s("p",[a._v("Flagger comes with a Grafana dashboard made for canary analysis. Install Grafana with Helm:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i flagger-grafana flagger/grafana "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# or appmesh-system")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("url")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://prometheus:9090\n")])])]),s("p",[a._v("The dashboard shows the RED and USE metrics for the primary and canary workloads:")]),a._v(" "),s("p",[s("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/screens/grafana-canary-analysis.png",alt:"Canary Dashboard"}})]),a._v(" "),s("h2",{attrs:{id:"logging"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#logging","aria-hidden":"true"}},[a._v("#")]),a._v(" Logging")]),a._v(" "),s("p",[a._v("The canary errors and latency spikes have been recorded as Kubernetes events and logged by Flagger in json format:")]),a._v(" "),s("div",{staticClass:"language-text extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[a._v("kubectl -n istio-system logs deployment/flagger --tail=100 | jq .msg\n\nStarting canary deployment for podinfo.test\nAdvance podinfo.test canary weight 5\nAdvance podinfo.test canary weight 10\nAdvance podinfo.test canary weight 15\nAdvance podinfo.test canary weight 20\nAdvance podinfo.test canary weight 25\nAdvance podinfo.test canary weight 30\nAdvance podinfo.test canary weight 35\nHalt podinfo.test advancement success rate 98.69% < 99%\nAdvance podinfo.test canary weight 40\nHalt podinfo.test advancement request duration 1.515s > 500ms\nAdvance podinfo.test canary weight 45\nAdvance podinfo.test canary weight 50\nCopying podinfo.test template spec to podinfo-primary.test\nHalt podinfo-primary.test advancement waiting for rollout to finish: 1 old replicas are pending termination\nScaling down podinfo.test\nPromotion completed! podinfo.test\n")])])]),s("h2",{attrs:{id:"event-webhook"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#event-webhook","aria-hidden":"true"}},[a._v("#")]),a._v(" Event Webhook")]),a._v(" "),s("p",[a._v("Flagger can be configured to send event payloads to a specified webhook:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("helm upgrade -i flagger flagger/flagger "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("eventWebhook")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("https://example.com/flagger-canary-event-webhook\n")])])]),s("p",[a._v("The environment variable "),s("em",[a._v("EVENT_WEBHOOK_URL")]),a._v(" can be used for activating the event-webhook, too. This is handy for using a secret to store a sensible value that could contain api keys for example.")]),a._v(" "),s("p",[a._v("When configured, every action that Flagger takes during a canary deployment will be sent as JSON via an HTTP POST request. The JSON payload has the following schema:")]),a._v(" "),s("div",{staticClass:"language-javascript extra-class"},[s("pre",{pre:!0,attrs:{class:"language-javascript"}},[s("code",[s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"name"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"string (canary name)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"namespace"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"string (canary namespace)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"phase"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"string (canary phase)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"metadata"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"eventMessage"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"string (canary event message)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"eventType"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"string (canary event type)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"timestamp"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"string (unix timestamp ms)"')]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v("\n")])])]),s("p",[a._v("Example:")]),a._v(" "),s("div",{staticClass:"language-javascript extra-class"},[s("pre",{pre:!0,attrs:{class:"language-javascript"}},[s("code",[s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"name"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"podinfo"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"namespace"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"default"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"phase"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"Progressing"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"metadata"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"eventMessage"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"New revision detected! Scaling up podinfo.default"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"eventType"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"Normal"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(",")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"timestamp"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"1578607635167"')]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v("\n")])])]),s("p",[a._v("The event webhook can be overwritten at canary level with:")]),a._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"send to Slack"')]),a._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" event\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[a._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v(":")]),a._v("//event"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("-")]),a._v("recevier.notifications/slack\n")])])]),s("h2",{attrs:{id:"metrics"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#metrics","aria-hidden":"true"}},[a._v("#")]),a._v(" Metrics")]),a._v(" "),s("p",[a._v("Flagger exposes Prometheus metrics that can be used to determine the canary analysis status and the destination weight values:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Flagger version and mesh provider gauge")]),a._v("\nflagger_info"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("version"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"0.10.0"')]),a._v(", "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("mesh_provider")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"istio"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("1")]),a._v("\n\n"),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Canaries total gauge")]),a._v("\nflagger_canary_total"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"test"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("1")]),a._v("\n\n"),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Canary promotion last known status gauge")]),a._v("\n"),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# 0 - running, 1 - successful, 2 - failed")]),a._v("\nflagger_canary_status"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"podinfo"')]),a._v(" "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("namespace")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"test"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("1")]),a._v("\n\n"),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Canary traffic weight gauge")]),a._v("\nflagger_canary_weight"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("workload"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"podinfo-primary"')]),a._v(" "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("namespace")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"test"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("95")]),a._v("\nflagger_canary_weight"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("workload"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"podinfo"')]),a._v(" "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("namespace")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"test"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("5")]),a._v("\n\n"),s("span",{pre:!0,attrs:{class:"token comment"}},[a._v("# Seconds spent performing canary analysis histogram")]),a._v("\nflagger_canary_duration_seconds_bucket"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"podinfo"')]),a._v(",namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"test"')]),a._v(",le"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"10"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("6")]),a._v("\nflagger_canary_duration_seconds_bucket"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"podinfo"')]),a._v(",namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"test"')]),a._v(",le"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"+Inf"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("6")]),a._v("\nflagger_canary_duration_seconds_sum"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"podinfo"')]),a._v(",namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"test"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("17.3561329")]),a._v("\nflagger_canary_duration_seconds_count"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("{")]),a._v("name"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"podinfo"')]),a._v(",namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token string"}},[a._v('"test"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("}")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("6")]),a._v("\n")])])])])},[],!1,null,null,null);t.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/35.fc27c09e.js b/assets/js/35.fc27c09e.js new file mode 100644 index 00000000..8b609e4b --- /dev/null +++ b/assets/js/35.fc27c09e.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[35],{295:function(t,a,s){"use strict";s.r(a);var e=s(37),n=Object(e.a)({},function(){var t=this,a=t.$createElement,s=t._self._c||a;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("h1",{attrs:{id:"webhooks"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#webhooks","aria-hidden":"true"}},[t._v("#")]),t._v(" Webhooks")]),t._v(" "),s("p",[t._v("The canary analysis can be extended with webhooks. Flagger will call each webhook URL and\ndetermine from the response status code (HTTP 2xx) if the canary is failing or not.")]),t._v(" "),s("p",[t._v("There are several types of hooks:")]),t._v(" "),s("ul",[s("li",[s("strong",[t._v("confirm-rollout")]),t._v(" hooks are executed before scaling up the canary deployment and can be used for manual approval.\nThe rollout is paused until the hook returns a successful HTTP status code.")]),t._v(" "),s("li",[s("strong",[t._v("pre-rollout")]),t._v(" hooks are executed before routing traffic to canary.\nThe canary advancement is paused if a pre-rollout hook fails and if the number of failures reach the\nthreshold the canary will be rollback.")]),t._v(" "),s("li",[s("strong",[t._v("rollout")]),t._v(" hooks are executed during the analysis on each iteration before the metric checks.\nIf a rollout hook call fails the canary advancement is paused and eventfully rolled back.")]),t._v(" "),s("li",[s("strong",[t._v("confirm-promotion")]),t._v(" hooks are executed before the promotion step.\nThe canary promotion is paused until the hooks return HTTP 200.\nWhile the promotion is paused, Flagger will continue to run the metrics checks and rollout hooks.")]),t._v(" "),s("li",[s("strong",[t._v("post-rollout")]),t._v(" hooks are executed after the canary has been promoted or rolled back.\nIf a post rollout hook fails the error is logged.")]),t._v(" "),s("li",[s("strong",[t._v("rollback")]),t._v(" hooks are executed while a canary deployment is in either Progressing or Waiting status.\nThis provides the ability to rollback during analysis or while waiting for a confirmation. If a rollback hook\nreturns a successful HTTP status code, Flagger will stop the analysis and mark the canary release as failed.")]),t._v(" "),s("li",[s("strong",[t._v("event")]),t._v(" hooks are executed every time Flagger emits a Kubernetes event. When configured,\nevery action that Flagger takes during a canary deployment will be sent as JSON via an HTTP POST request.")])]),t._v(" "),s("p",[t._v("Spec:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"start gate"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" confirm"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/gate/approve\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"helm test"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("helmtester.flagger/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 3m\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"helmv3"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"test podinfo -n test"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"load test"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 15s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"hey -z 1m -q 5 -c 2 http://podinfo-canary.test:9898/"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"promotion gate"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" confirm"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("promotion\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/gate/approve\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"notify"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" post"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//telegram.bot"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("8080/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("some")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"message"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"rollback gate"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" rollback\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/rollback/check\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"send to Slack"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" event\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//event"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("recevier.notifications/slack\n")])])]),s("blockquote",[s("p",[s("strong",[t._v("Note")]),t._v(" that the sum of all rollout webhooks timeouts should be lower than the analysis interval.")])]),t._v(" "),s("p",[t._v("Webhook payload (HTTP POST):")]),t._v(" "),s("div",{staticClass:"language-json extra-class"},[s("pre",{pre:!0,attrs:{class:"language-json"}},[s("code",[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"name"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"podinfo"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"namespace"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"test"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"phase"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Progressing"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" \n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"metadata"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"test"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"all"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"token"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"16688eb5e9f289f1991c"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])])]),s("p",[t._v("Response status codes:")]),t._v(" "),s("ul",[s("li",[t._v("200-202 - advance canary by increasing the traffic weight")]),t._v(" "),s("li",[t._v("timeout or non-2xx - halt advancement and increment failed checks")])]),t._v(" "),s("p",[t._v("On a non-2xx response Flagger will include the response body (if any) in the failed checks log and Kubernetes events.")]),t._v(" "),s("p",[t._v("Event payload (HTTP POST):")]),t._v(" "),s("div",{staticClass:"language-json extra-class"},[s("pre",{pre:!0,attrs:{class:"language-json"}},[s("code",[s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"name"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"string (canary name)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"namespace"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"string (canary namespace)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"phase"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"string (canary phase)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"metadata"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"eventMessage"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"string (canary event message)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"eventType"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"string (canary event type)"')]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token property"}},[t._v('"timestamp"')]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"string (unix timestamp ms)"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])])]),s("p",[t._v("The event receiver can create alerts based on the received phase\n(possible values: "),s("code",[t._v("Initialized")]),t._v(", "),s("code",[t._v("Waiting")]),t._v(", "),s("code",[t._v("Progressing")]),t._v(", "),s("code",[t._v("Promoting")]),t._v(", "),s("code",[t._v("Finalising")]),t._v(", "),s("code",[t._v("Succeeded")]),t._v(" or "),s("code",[t._v("Failed")]),t._v(").")]),t._v(" "),s("h2",{attrs:{id:"load-testing"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#load-testing","aria-hidden":"true"}},[t._v("#")]),t._v(" Load Testing")]),t._v(" "),s("p",[t._v('For workloads that are not receiving constant traffic Flagger can be configured with a webhook,\nthat when called, will start a load test for the target workload.\nIf the target workload doesn\'t receive any traffic during the canary analysis,\nFlagger metric checks will fail with "no values found for metric request-success-rate".')]),t._v(" "),s("p",[t._v("Flagger comes with a load testing service based on "),s("a",{attrs:{href:"https://github.com/rakyll/hey",target:"_blank",rel:"noopener noreferrer"}},[t._v("rakyll/hey"),s("OutboundLink")],1),t._v("\nthat generates traffic during analysis when configured as a webhook.")]),t._v(" "),s("p",[s("img",{attrs:{src:"https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-load-testing.png",alt:"Flagger Load Testing Webhook"}})]),t._v(" "),s("p",[t._v("First you need to deploy the load test runner in a namespace with sidecar injection enabled:")]),t._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[t._v("kubectl apply -k github.com/weaveworks/flagger//kustomize/tester\n")])])]),s("p",[t._v("Or by using Helm:")]),t._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[t._v("helm repo "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("add")]),t._v(" flagger https://flagger.app\n\nhelm upgrade -i flagger-loadtester flagger/loadtester "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("test "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set cmd.timeout"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("1h\n")])])]),s("p",[t._v("When deployed the load tester API will be available at "),s("code",[t._v("http://flagger-loadtester.test/")]),t._v(".")]),t._v(" "),s("p",[t._v("Now you can add webhooks to the canary analysis spec:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("get\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cmd\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("post\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cmd\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v("\"hey -z 1m -q 10 -c 2 -m POST -d '{test: 2}' http://podinfo-canary.test:9898/echo\"")]),t._v("\n")])])]),s("p",[t._v("When the canary analysis starts, Flagger will call the webhooks and the load tester will run the "),s("code",[t._v("hey")]),t._v(" commands\nin the background, if they are not already running. This will ensure that during the\nanalysis, the "),s("code",[t._v("podinfo-canary.test")]),t._v(" service will receive a steady stream of GET and POST requests.")]),t._v(" "),s("p",[t._v("If your workload is exposed outside the mesh you can point "),s("code",[t._v("hey")]),t._v(" to the\npublic URL and use HTTP2.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("get\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cmd\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"hey -z 1m -q 10 -c 2 -h2 https://podinfo.example.com/"')]),t._v("\n")])])]),s("p",[t._v("For gRPC services you can use "),s("a",{attrs:{href:"https://github.com/bojand/ghz",target:"_blank",rel:"noopener noreferrer"}},[t._v("bojand/ghz"),s("OutboundLink")],1),t._v(" which is a similar tool to Hey but for gPRC:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" grpc"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("load"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cmd\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"ghz -z 1m -q 10 -c 2 --insecure podinfo.test:9898"')]),t._v("\n")])])]),s("p",[s("code",[t._v("ghz")]),t._v(" uses reflection to identify which gRPC method to call. If you do not wish to enable reflection for your gRPC service you can implement a standardized health check from the "),s("a",{attrs:{href:"https://github.com/grpc/grpc-proto",target:"_blank",rel:"noopener noreferrer"}},[t._v("grpc-proto"),s("OutboundLink")],1),t._v(" library. To use this "),s("a",{attrs:{href:"https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto",target:"_blank",rel:"noopener noreferrer"}},[t._v("health check schema"),s("OutboundLink")],1),t._v(" without reflection you can pass a parameter to "),s("code",[t._v("ghz")]),t._v(" like this")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" grpc"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("load"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("no"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("reflection\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" cmd\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"ghz --insecure --proto=/tmp/ghz/health.proto --call=grpc.health.v1.Health/Check podinfo.test:9898"')]),t._v("\n")])])]),s("p",[t._v("The load tester can run arbitrary commands as long as the binary is present in the container image.\nFor example if you you want to replace "),s("code",[t._v("hey")]),t._v(" with another CLI, you can create your own Docker image:")]),t._v(" "),s("div",{staticClass:"language-dockerfile extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[t._v("FROM weaveworks/flagger-loadtester:\n\nRUN curl -Lo /usr/local/bin/my-cli https://github.com/user/repo/releases/download/ver/my-cli \\\n && chmod +x /usr/local/bin/my-cli\n")])])]),s("h2",{attrs:{id:"load-testing-delegation"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#load-testing-delegation","aria-hidden":"true"}},[t._v("#")]),t._v(" Load Testing Delegation")]),t._v(" "),s("p",[t._v("The load tester can also forward testing tasks to external tools, by now "),s("a",{attrs:{href:"https://github.com/naver/ngrinder",target:"_blank",rel:"noopener noreferrer"}},[t._v("nGrinder"),s("OutboundLink")],1),t._v("\nis supported.")]),t._v(" "),s("p",[t._v("To use this feature, add a load test task of type 'ngrinder' to the canary analysis spec:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" load"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("test"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("post\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# type of this load test task, cmd or ngrinder")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" ngrinder\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# base url of your nGrinder controller server")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("server")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//ngrinder"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("server"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("port\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# id of the test to clone from, the test must have been defined.")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("clone")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("100")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# user name and base64 encoded password to authenticate against the nGrinder server")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("username")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" admin\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("passwd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" YWRtaW4=\n "),s("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# the interval between between nGrinder test status polling, default to 1s")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("pollInterval")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5s\n")])])]),s("p",[t._v("When the canary analysis starts, the load tester will initiate a "),s("a",{attrs:{href:"https://github.com/naver/ngrinder/wiki/REST-API-PerfTest",target:"_blank",rel:"noopener noreferrer"}},[t._v("clone_and_start request"),s("OutboundLink")],1),t._v("\nto the nGrinder server and start a new performance test. the load tester will periodically poll the nGrinder server\nfor the status of the test, and prevent duplicate requests from being sent in subsequent analysis loops.")]),t._v(" "),s("h2",{attrs:{id:"integration-testing"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#integration-testing","aria-hidden":"true"}},[t._v("#")]),t._v(" Integration Testing")]),t._v(" "),s("p",[t._v("Flagger comes with a testing service that can run Helm tests or Bats tests when configured as a webhook.")]),t._v(" "),s("p",[t._v("Deploy the Helm test runner in the "),s("code",[t._v("kube-system")]),t._v(" namespace using the "),s("code",[t._v("tiller")]),t._v(" service account:")]),t._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[t._v("helm repo "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("add")]),t._v(" flagger https://flagger.app\n\nhelm upgrade -i flagger-helmtester flagger/loadtester "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--namespace"),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("kube-system "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("\\")]),t._v("\n--set "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[t._v("serviceAccountName")]),s("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("tiller\n")])])]),s("p",[t._v("When deployed the Helm tester API will be available at "),s("code",[t._v("http://flagger-helmtester.kube-system/")]),t._v(".")]),t._v(" "),s("p",[t._v("Now you can add pre-rollout webhooks to the canary analysis spec:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"smoke test"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("helmtester.kube"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 3m\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"helm"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"test {{ .Release.Name }} --cleanup"')]),t._v("\n")])])]),s("p",[t._v("When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.\nIf the helm test fails, Flagger will retry until the analysis threshold is reached and the canary is rolled back.")]),t._v(" "),s("p",[t._v("If you are using Helm v3, you'll have to create a dedicated service account and add the release namespace to the test command:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"smoke test"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("helmtester.kube"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("system/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 3m\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"helmv3"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"test {{ .Release.Name }} --timeout 3m -n {{ .Release.Namespace }}"')]),t._v("\n")])])]),s("p",[t._v("As an alternative to Helm you can use the "),s("a",{attrs:{href:"https://github.com/bats-core/bats-core",target:"_blank",rel:"noopener noreferrer"}},[t._v("Bash Automated Testing System"),s("OutboundLink")],1),t._v(" to run your tests.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"acceptance tests"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" pre"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("batstester.default/\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("timeout")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" 5m\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("metadata")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"bash"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("cmd")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"bats /tests/acceptance.bats"')]),t._v("\n")])])]),s("p",[t._v("Note that you should create a ConfigMap with your Bats tests and mount it inside the tester container.")]),t._v(" "),s("h2",{attrs:{id:"manual-gating"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#manual-gating","aria-hidden":"true"}},[t._v("#")]),t._v(" Manual Gating")]),t._v(" "),s("p",[t._v("For manual approval of a canary deployment you can use the "),s("code",[t._v("confirm-rollout")]),t._v(" and "),s("code",[t._v("confirm-promotion")]),t._v(" webhooks.\nThe confirmation rollout hooks are executed before the pre-rollout hooks.\nFlagger will halt the canary traffic shifting and analysis until the confirm webhook returns HTTP status 200.")]),t._v(" "),s("p",[t._v("For manual rollback of a canary deployment you can use the "),s("code",[t._v("rollback")]),t._v(" webhook. The rollback hook will be called\nduring the analysis and confirmation states. If a rollback webhook returns a successful HTTP status code, Flagger\nwill shift all traffic back to the primary instance and fail the canary.")]),t._v(" "),s("p",[t._v("Manual gating with Flagger's tester:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"gate"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" confirm"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/gate/halt\n")])])]),s("p",[t._v("The "),s("code",[t._v("/gate/halt")]),t._v(" returns HTTP 403 thus blocking the rollout.")]),t._v(" "),s("p",[t._v("If you have notifications enabled, Flagger will post a message to Slack or MS Teams if a canary rollout is waiting for approval.")]),t._v(" "),s("p",[t._v("Change the URL to "),s("code",[t._v("/gate/approve")]),t._v(" to start the canary analysis:")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"gate"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" confirm"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/gate/approve\n")])])]),s("p",[t._v("Manual gating can be driven with Flagger's tester API. Set the confirmation URL to "),s("code",[t._v("/gate/check")]),t._v(":")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"ask for confirmation"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" confirm"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("rollout\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/gate/check\n")])])]),s("p",[t._v("By default the gate is closed, you can start or resume the canary rollout with:")]),t._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[t._v("kubectl -n "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("exec")]),t._v(" -it flagger-loadtester-xxxx-xxxx "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("sh")]),t._v("\n\n"),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" -d "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('\'{"name": "podinfo","namespace":"test"}\'')]),t._v(" http://localhost:8080/gate/open \n")])])]),s("p",[t._v("You can pause the rollout at any time with:")]),t._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" -d "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('\'{"name": "podinfo","namespace":"test"}\'')]),t._v(" http://localhost:8080/gate/close \n")])])]),s("p",[t._v("If a canary analysis is paused the status will change to waiting:")]),t._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[t._v("kubectl get canary/podinfo\n\nNAME STATUS WEIGHT\npodinfo Waiting "),s("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),t._v("\n")])])]),s("p",[t._v("The "),s("code",[t._v("confirm-promotion")]),t._v(" hook type can be used to manually approve the canary promotion.\nWhile the promotion is paused, Flagger will continue to run the metrics checks and load tests.")]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"promotion gate"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" confirm"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("promotion\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/gate/halt\n")])])]),s("p",[t._v("The "),s("code",[t._v("rollback")]),t._v(" hook type can be used to manually rollback the canary promotion. As with gating, rollbacks can be driven\nwith Flagger's tester API by setting the rollback URL to "),s("code",[t._v("/rollback/check")])]),t._v(" "),s("div",{staticClass:"language-yaml extra-class"},[s("pre",{pre:!0,attrs:{class:"language-yaml"}},[s("code",[t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("analysis")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("webhooks")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("name")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('"rollback"')]),t._v("\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("type")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" rollback\n "),s("span",{pre:!0,attrs:{class:"token key atrule"}},[t._v("url")]),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" http"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("//flagger"),s("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("-")]),t._v("loadtester.test/rollback/check\n")])])]),s("p",[t._v("By default rollback is closed, you can rollback a canary rollout with:")]),t._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[t._v("kubectl -n "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("test")]),t._v(" "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[t._v("exec")]),t._v(" -it flagger-loadtester-xxxx-xxxx "),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("sh")]),t._v("\n\n"),s("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" -d "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('\'{"name": "podinfo","namespace":"test"}\'')]),t._v(" http://localhost:8080/rollback/open \n")])])]),s("p",[t._v("You can close the rollback with:")]),t._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" -d "),s("span",{pre:!0,attrs:{class:"token string"}},[t._v('\'{"name": "podinfo","namespace":"test"}\'')]),t._v(" http://localhost:8080/rollback/close \n")])])]),s("p",[t._v("If you have notifications enabled, Flagger will post a message to Slack or MS Teams if a canary has been rolled back.")])])},[],!1,null,null,null);a.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/36.144098aa.js b/assets/js/36.144098aa.js new file mode 100644 index 00000000..c01f3052 --- /dev/null +++ b/assets/js/36.144098aa.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[36],{223:function(n,w,o){}}]); \ No newline at end of file diff --git a/assets/js/5.ecaf47a1.js b/assets/js/5.ecaf47a1.js deleted file mode 100644 index 21c2065b..00000000 --- a/assets/js/5.ecaf47a1.js +++ /dev/null @@ -1 +0,0 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[5],{265:function(e,r,t){"use strict";t.r(r);var a=t(37),n=Object(a.a)({},function(){var e=this,r=e.$createElement,t=e._self._c||r;return t("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[t("h2",{attrs:{id:"progressive-delivery"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#progressive-delivery","aria-hidden":"true"}},[e._v("#")]),e._v(" Progressive Delivery")]),e._v(" "),t("p",[e._v("Flagger was designed to give developers confidence in automating production releases with progressive delivery techniques.")]),e._v(" "),t("div",{staticClass:"tip custom-block"},[t("p",{staticClass:"custom-block-title"},[e._v("Canary release")]),e._v(" "),t("p",[e._v("A benefit of using canary releases is the ability to do capacity testing of the new version in a production environment\nwith a safe rollback strategy if issues are found. By slowly ramping up the load, you can monitor and capture metrics\nabout how the new version impacts the production environment.")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://martinfowler.com/bliki/CanaryRelease.html",target:"_blank",rel:"noopener noreferrer"}},[e._v("Martin Fowler"),t("OutboundLink")],1)])]),e._v(" "),t("p",[e._v("Flagger can run automated application analysis, testing, promotion and rollback for the following deployment strategies:")]),e._v(" "),t("ul",[t("li",[t("p",[t("strong",[e._v("Canary")]),e._v(" (progressive traffic shifting)")]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://docs.flagger.app/usage/progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("Istio"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/usage/linkerd-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("Linkerd"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/usage/appmesh-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("App Mesh"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/usage/contour-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("Contour"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/usage/gloo-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("Gloo"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/usage/nginx-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("NGINX"),t("OutboundLink")],1)])])]),e._v(" "),t("li",[t("p",[t("strong",[e._v("A/B Testing")]),e._v(" (HTTP headers and cookies traffic routing)")]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://docs.flagger.app/usage/ab-testing",target:"_blank",rel:"noopener noreferrer"}},[e._v("Istio"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"hhttps://docs.flagger.app/usage/appmesh-progressive-delivery#a-b-testing"}},[e._v("App Mesh")]),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/usage/contour-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("Contour"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/usage/nginx-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("NGINX"),t("OutboundLink")],1)])])]),e._v(" "),t("li",[t("p",[t("strong",[e._v("Blue/Green")]),e._v(" (traffic switching)")]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://docs.flagger.app/usage/blue-green",target:"_blank",rel:"noopener noreferrer"}},[e._v("Kubernetes CNI"),t("OutboundLink")],1),e._v(",\nIstio, Linkerd, App Mesh, Contour, Gloo, NGINX")])])])]),e._v(" "),t("p",[e._v("Flagger can be configured to send notifications to a\n"),t("a",{attrs:{href:"https://docs.flagger.app/usage/alerting#slack",target:"_blank",rel:"noopener noreferrer"}},[e._v("Slack"),t("OutboundLink")],1),e._v(" or\n"),t("a",{attrs:{href:"https://docs.flagger.app/usage/alerting#microsoft-teams",target:"_blank",rel:"noopener noreferrer"}},[e._v("Microsoft Teams"),t("OutboundLink")],1),e._v(" channel.\nIt will post messages when a deployment has been initialised,\nwhen a new revision has been detected and if the canary analysis failed or succeeded.")]),e._v(" "),t("h2",{attrs:{id:"gitops"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#gitops","aria-hidden":"true"}},[e._v("#")]),e._v(" GitOps")]),e._v(" "),t("p",[t("img",{attrs:{src:"/flagger-gitops.png",alt:"GtiOps with Flagger and FluxCD"}})]),e._v(" "),t("p",[e._v("You can build fully automated GitOps pipelines for canary deployments with Flagger and\n"),t("a",{attrs:{href:"https://github.com/fluxcd/flux",target:"_blank",rel:"noopener noreferrer"}},[e._v("FluxCD"),t("OutboundLink")],1),e._v(" (CNCF sandbox project).")]),e._v(" "),t("div",{staticClass:"tip custom-block"},[t("p",{staticClass:"custom-block-title"},[e._v("GitOps")]),e._v(" "),t("p",[e._v("GitOps is a way to do Kubernetes cluster management and application delivery.\nIt works by using Git as a single source of truth for declarative infrastructure and applications.\nWith Git at the center of your delivery pipelines, developers can make pull requests\nto accelerate and simplify application deployments and operations tasks to Kubernetes.")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://www.weave.works/technologies/gitops/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Weaveworks"),t("OutboundLink")],1)])]),e._v(" "),t("p",[e._v("GitOps tutorials:")]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://github.com/stefanprodan/gitops-istio",target:"_blank",rel:"noopener noreferrer"}},[e._v("Progressive Delivery for Istio with Flagger and FluxCD"),t("OutboundLink")],1)]),e._v(" "),t("li",[t("a",{attrs:{href:"https://eks.hands-on.flagger.dev",target:"_blank",rel:"noopener noreferrer"}},[e._v("Progressive Delivery for AWS App Mesh with Flagger and FluxCD"),t("OutboundLink")],1)]),e._v(" "),t("li",[t("a",{attrs:{href:"https://helm.workshop.flagger.dev",target:"_blank",rel:"noopener noreferrer"}},[e._v("Progressive Delivery for Linkerd with Flagger, FluxCD and Helm v3"),t("OutboundLink")],1)])]),e._v(" "),t("h2",{attrs:{id:"getting-help"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#getting-help","aria-hidden":"true"}},[e._v("#")]),e._v(" Getting Help")]),e._v(" "),t("p",[e._v("If you have any questions about Flagger and progressive delivery:")]),e._v(" "),t("ul",[t("li",[e._v("Read the Flagger "),t("a",{attrs:{href:"https://docs.flagger.app",target:"_blank",rel:"noopener noreferrer"}},[e._v("docs"),t("OutboundLink")],1),e._v(".")]),e._v(" "),t("li",[e._v("Invite yourself to the "),t("a",{attrs:{href:"https://slack.weave.works/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Weave community slack"),t("OutboundLink")],1),e._v("\nand join the "),t("a",{attrs:{href:"https://weave-community.slack.com/messages/flagger/",target:"_blank",rel:"noopener noreferrer"}},[e._v("#flagger"),t("OutboundLink")],1),e._v(" channel.")]),e._v(" "),t("li",[e._v("Join the "),t("a",{attrs:{href:"https://www.meetup.com/pro/Weave/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Weave User Group"),t("OutboundLink")],1),e._v(" and get invited to online talks,\nhands-on training and meetups in your area.")]),e._v(" "),t("li",[e._v("File an "),t("a",{attrs:{href:"https://github.com/weaveworks/flagger/issues/new",target:"_blank",rel:"noopener noreferrer"}},[e._v("issue"),t("OutboundLink")],1),e._v(".")])]),e._v(" "),t("p",[e._v("Your feedback is always welcome!")])])},[],!1,null,null,null);r.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/5.f92a6aca.js b/assets/js/5.f92a6aca.js new file mode 100644 index 00000000..c92b9214 --- /dev/null +++ b/assets/js/5.f92a6aca.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[5],{265:function(e,r,t){"use strict";t.r(r);var a=t(37),n=Object(a.a)({},function(){var e=this,r=e.$createElement,t=e._self._c||r;return t("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[t("h2",{attrs:{id:"progressive-delivery"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#progressive-delivery","aria-hidden":"true"}},[e._v("#")]),e._v(" Progressive Delivery")]),e._v(" "),t("p",[e._v("Flagger was designed to give developers confidence in automating production releases with progressive delivery techniques.")]),e._v(" "),t("div",{staticClass:"tip custom-block"},[t("p",{staticClass:"custom-block-title"},[e._v("Canary release")]),e._v(" "),t("p",[e._v("A benefit of using canary releases is the ability to do capacity testing of the new version in a production environment\nwith a safe rollback strategy if issues are found. By slowly ramping up the load, you can monitor and capture metrics\nabout how the new version impacts the production environment.")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://martinfowler.com/bliki/CanaryRelease.html",target:"_blank",rel:"noopener noreferrer"}},[e._v("Martin Fowler"),t("OutboundLink")],1)])]),e._v(" "),t("p",[e._v("Flagger can run automated application analysis, testing, promotion and rollback for the following deployment strategies:")]),e._v(" "),t("ul",[t("li",[t("p",[t("strong",[e._v("Canary")]),e._v(" (progressive traffic shifting)")]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://docs.flagger.app/tutorials/istio-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("Istio"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/tutorials/linkerd-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("Linkerd"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/tutorials/appmesh-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("App Mesh"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/tutorials/contour-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("Contour"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/tutorials/gloo-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("Gloo"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/tutorials/nginx-progressive-delivery",target:"_blank",rel:"noopener noreferrer"}},[e._v("NGINX"),t("OutboundLink")],1)])])]),e._v(" "),t("li",[t("p",[t("strong",[e._v("A/B Testing")]),e._v(" (HTTP headers and cookies traffic routing)")]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://docs.flagger.app/tutorials/istio-ab-testing",target:"_blank",rel:"noopener noreferrer"}},[e._v("Istio"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"hhttps://docs.flagger.app/tutorials/appmesh-progressive-delivery#a-b-testing"}},[e._v("App Mesh")]),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/tutorials/contour-progressive-delivery#a-b-testing",target:"_blank",rel:"noopener noreferrer"}},[e._v("Contour"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/tutorials/nginx-progressive-delivery#a-b-testing",target:"_blank",rel:"noopener noreferrer"}},[e._v("NGINX"),t("OutboundLink")],1)])])]),e._v(" "),t("li",[t("p",[t("strong",[e._v("Blue/Green")]),e._v(" (traffic switching and mirroring)")]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://docs.flagger.app/tutorials/kubernetes-blue-green",target:"_blank",rel:"noopener noreferrer"}},[e._v("Kubernetes CNI"),t("OutboundLink")],1),e._v(",\n"),t("a",{attrs:{href:"https://docs.flagger.app/tutorials/istio-progressive-delivery#traffic-mirroring",target:"_blank",rel:"noopener noreferrer"}},[e._v("Istio"),t("OutboundLink")],1),e._v(",\nLinkerd, App Mesh, Contour, Gloo, NGINX")])])])]),e._v(" "),t("p",[e._v("Flagger can be configured to "),t("a",{attrs:{href:"https://docs.flagger.app/usage/alerting",target:"_blank",rel:"noopener noreferrer"}},[e._v("send notifications"),t("OutboundLink")],1),e._v(" to\nSlack, Microsoft Teams, Discord or Rocket.\nIt will post messages when a deployment has been initialised,\nwhen a new revision has been detected and if the canary analysis failed or succeeded.")]),e._v(" "),t("h2",{attrs:{id:"gitops"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#gitops","aria-hidden":"true"}},[e._v("#")]),e._v(" GitOps")]),e._v(" "),t("p",[t("img",{attrs:{src:"/flagger-gitops.png",alt:"GtiOps with Flagger and FluxCD"}})]),e._v(" "),t("p",[e._v("You can build fully automated GitOps pipelines for canary deployments with Flagger and\n"),t("a",{attrs:{href:"https://github.com/fluxcd/flux",target:"_blank",rel:"noopener noreferrer"}},[e._v("FluxCD"),t("OutboundLink")],1),e._v(" (CNCF sandbox project).")]),e._v(" "),t("div",{staticClass:"tip custom-block"},[t("p",{staticClass:"custom-block-title"},[e._v("GitOps")]),e._v(" "),t("p",[e._v("GitOps is a way to do Kubernetes cluster management and application delivery.\nIt works by using Git as a single source of truth for declarative infrastructure and applications.\nWith Git at the center of your delivery pipelines, developers can make pull requests\nto accelerate and simplify application deployments and operations tasks to Kubernetes.")]),e._v(" "),t("p",[t("a",{attrs:{href:"https://www.weave.works/technologies/gitops/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Weaveworks"),t("OutboundLink")],1)])]),e._v(" "),t("p",[e._v("GitOps tutorials:")]),e._v(" "),t("ul",[t("li",[t("a",{attrs:{href:"https://github.com/stefanprodan/gitops-istio",target:"_blank",rel:"noopener noreferrer"}},[e._v("Progressive Delivery for Istio with Flagger and FluxCD"),t("OutboundLink")],1)]),e._v(" "),t("li",[t("a",{attrs:{href:"https://eks.hands-on.flagger.dev",target:"_blank",rel:"noopener noreferrer"}},[e._v("Progressive Delivery for AWS App Mesh with Flagger and FluxCD"),t("OutboundLink")],1)]),e._v(" "),t("li",[t("a",{attrs:{href:"https://helm.workshop.flagger.dev",target:"_blank",rel:"noopener noreferrer"}},[e._v("Progressive Delivery for Linkerd with Flagger, FluxCD and Helm v3"),t("OutboundLink")],1)])]),e._v(" "),t("h2",{attrs:{id:"getting-help"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#getting-help","aria-hidden":"true"}},[e._v("#")]),e._v(" Getting Help")]),e._v(" "),t("p",[e._v("If you have any questions about Flagger and progressive delivery:")]),e._v(" "),t("ul",[t("li",[e._v("Read the Flagger "),t("a",{attrs:{href:"https://docs.flagger.app",target:"_blank",rel:"noopener noreferrer"}},[e._v("docs"),t("OutboundLink")],1),e._v(".")]),e._v(" "),t("li",[e._v("Invite yourself to the "),t("a",{attrs:{href:"https://slack.weave.works/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Weave community slack"),t("OutboundLink")],1),e._v("\nand join the "),t("a",{attrs:{href:"https://weave-community.slack.com/messages/flagger/",target:"_blank",rel:"noopener noreferrer"}},[e._v("#flagger"),t("OutboundLink")],1),e._v(" channel.")]),e._v(" "),t("li",[e._v("Join the "),t("a",{attrs:{href:"https://www.meetup.com/pro/Weave/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Weave User Group"),t("OutboundLink")],1),e._v(" and get invited to online talks,\nhands-on training and meetups in your area.")]),e._v(" "),t("li",[e._v("File an "),t("a",{attrs:{href:"https://github.com/weaveworks/flagger/issues/new",target:"_blank",rel:"noopener noreferrer"}},[e._v("issue"),t("OutboundLink")],1),e._v(".")])]),e._v(" "),t("p",[e._v("Your feedback is always welcome!")])])},[],!1,null,null,null);r.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/6.5f0b255b.js b/assets/js/6.5f0b255b.js deleted file mode 100644 index 7e758e46..00000000 --- a/assets/js/6.5f0b255b.js +++ /dev/null @@ -1 +0,0 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[6],{223:function(n,w,o){}}]); \ No newline at end of file diff --git a/assets/js/6.ee5ffca3.js b/assets/js/6.ee5ffca3.js new file mode 100644 index 00000000..0a3fbbbb --- /dev/null +++ b/assets/js/6.ee5ffca3.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[6],{266:function(t,e,n){"use strict";n.r(e);var s=n(37),l=Object(s.a)({},function(){var t=this.$createElement;return(this._self._c||t)("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}})},[],!1,null,null,null);e.default=l.exports}}]); \ No newline at end of file diff --git a/assets/js/7.6bf27a87.js b/assets/js/7.6bf27a87.js new file mode 100644 index 00000000..323325d7 --- /dev/null +++ b/assets/js/7.6bf27a87.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[7],{267:function(a,e,s){"use strict";s.r(e);var t=s(37),r=Object(t.a)({},function(){var a=this,e=a.$createElement,s=a._self._c||e;return s("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[s("h1",{attrs:{id:"development-guide"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#development-guide","aria-hidden":"true"}},[a._v("#")]),a._v(" Development Guide")]),a._v(" "),s("p",[a._v("This document describes how to build, test and run Flagger from source.")]),a._v(" "),s("h2",{attrs:{id:"setup-dev-environment"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#setup-dev-environment","aria-hidden":"true"}},[a._v("#")]),a._v(" Setup dev environment")]),a._v(" "),s("p",[a._v("Flagger is written in Go and uses Go modules for dependency management.")]),a._v(" "),s("p",[a._v("On your dev machine install the following tools:")]),a._v(" "),s("ul",[s("li",[a._v("go >= 1.13")]),a._v(" "),s("li",[a._v("git >= 2.20")]),a._v(" "),s("li",[a._v("bash >= 5.0")]),a._v(" "),s("li",[a._v("make >= 3.81")]),a._v(" "),s("li",[a._v("kubectl >= 1.16")]),a._v(" "),s("li",[a._v("kustomize >= 3.5")]),a._v(" "),s("li",[a._v("helm >= 3.0")]),a._v(" "),s("li",[a._v("docker >= 19.03")])]),a._v(" "),s("p",[a._v("You'll also need a Kubernetes cluster for testing Flagger.\nYou can use Minikube, Kind, Docker desktop or any remote cluster\n(AKS/EKS/GKE/etc) Kubernetes version 1.14 or newer.")]),a._v(" "),s("p",[a._v("To start contributing to Flagger, fork the "),s("a",{attrs:{href:"https://github.com/weaveworks/flagger",target:"_blank",rel:"noopener noreferrer"}},[a._v("repository"),s("OutboundLink")],1),a._v(" on GitHub.")]),a._v(" "),s("p",[a._v("Create a dir inside your "),s("code",[a._v("GOPATH")]),a._v(":")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("mkdir")]),a._v(" -p "),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("$GOPATH")]),a._v("/src/github.com/weaveworks\n")])])]),s("p",[a._v("Clone your fork:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("cd")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token variable"}},[a._v("$GOPATH")]),a._v("/src/github.com/weaveworks\n"),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("git")]),a._v(" clone https://github.com/YOUR_USERNAME/flagger\n"),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("cd")]),a._v(" flagger\n")])])]),s("p",[a._v("Set Flagger repository as upstream:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("git")]),a._v(" remote "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("add")]),a._v(" upstream https://github.com/weaveworks/flagger.git\n")])])]),s("p",[a._v("Sync your fork regularly to keep it up-to-date with upstream:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("git")]),a._v(" fetch upstream\n"),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("git")]),a._v(" checkout master\n"),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("git")]),a._v(" merge upstream/master\n")])])]),s("h2",{attrs:{id:"build"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#build","aria-hidden":"true"}},[a._v("#")]),a._v(" Build")]),a._v(" "),s("p",[a._v("Download Go modules:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("go mod download\n")])])]),s("p",[a._v("Build Flagger binary and container image:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("make")]),a._v(" build\n")])])]),s("p",[a._v("Build load tester binary and container image:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("make")]),a._v(" loadtester-build\n")])])]),s("h2",{attrs:{id:"code-changes"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#code-changes","aria-hidden":"true"}},[a._v("#")]),a._v(" Code changes")]),a._v(" "),s("p",[a._v("Before submitting a PR, make sure your changes are covered by unit tests.")]),a._v(" "),s("p",[a._v("If you made changes to "),s("code",[a._v("go.mod")]),a._v(" run:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("go mod tidy\n")])])]),s("p",[a._v("If you made changes to "),s("code",[a._v("pkg/apis")]),a._v(" regenerate Kubernetes client sets with:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("make")]),a._v(" codegen\n")])])]),s("p",[a._v("Run code formatters:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("make")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("fmt")]),a._v("\n")])])]),s("p",[a._v("Run unit tests:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("make")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("test")]),a._v("\n")])])]),s("h2",{attrs:{id:"api-changes"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#api-changes","aria-hidden":"true"}},[a._v("#")]),a._v(" API changes")]),a._v(" "),s("p",[a._v("If you made changes to "),s("code",[a._v("pkg/apis")]),a._v(" regenerate the Kubernetes client sets with:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("make")]),a._v(" codegen\n")])])]),s("p",[a._v("Update the validation spec in "),s("code",[a._v("artifacts/flagger/crd.yaml")]),a._v(" and run:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("make")]),a._v(" crd\n")])])]),s("p",[a._v("Note that any change to the CRDs must be accompanied by an update to the Open API schema.")]),a._v(" "),s("h2",{attrs:{id:"manual-testing"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#manual-testing","aria-hidden":"true"}},[a._v("#")]),a._v(" Manual testing")]),a._v(" "),s("p",[a._v("Install a service mesh and/or an ingress controller on your cluster and deploy Flagger\nusing one of the install options "),s("a",{attrs:{href:"../install/flagger-install-on-kubernetes"}},[a._v("listed here")]),a._v(".")]),a._v(" "),s("p",[a._v("If you made changes to the CRDs, apply your local copy with:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl apply -f artifacts/flagger/crd.yaml\n")])])]),s("p",[a._v("Shutdown the Flagger instance installed on your cluster (replace the namespace with your mesh/ingress one):")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n istio-system scale deployment/flagger --replicas"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("0")]),a._v("\n")])])]),s("p",[a._v("Port forward to your Prometheus instance:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n istio-system port-forward svc/prometheus "),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("9090")]),a._v(":9090\n")])])]),s("p",[a._v("Run Flagger locally against your remote cluster by specifying a kubeconfig path:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("go run cmd/flagger/ -kubeconfig"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token environment constant"}},[a._v("$HOME")]),a._v("/.kube/config "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n-log-level"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("info "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n-mesh-provider"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("istio "),s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("\\")]),a._v("\n-metrics-server"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("http://localhost:9090\n")])])]),s("p",[a._v("Another option to manually test your changes is to build and push the image to your container registry:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("make")]),a._v(" build\ndocker tag weaveworks/flagger:latest "),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<")]),a._v("YOUR-DOCKERHUB-USERNAME"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v("/flagger:"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<")]),a._v("YOUR-TAG"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v("\ndocker push "),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<")]),a._v("YOUR-DOCKERHUB-USERNAME"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v("/flagger:"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<")]),a._v("YOUR-TAG"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v("\n")])])]),s("p",[a._v("Deploy your image on the cluster and scale up Flagger:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl -n istio-system "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/flagger "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("flagger")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<")]),a._v("YOUR-DOCKERHUB-USERNAME"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v("/flagger:"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("<")]),a._v("YOUR-TAG"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v(">")]),a._v("\nkubectl -n istio-system scale deployment/flagger --replicas"),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),s("span",{pre:!0,attrs:{class:"token number"}},[a._v("1")]),a._v("\n")])])]),s("p",[a._v("Now you can use one of the "),s("router-link",{attrs:{to:"/intro/"}},[a._v("tutorials")]),a._v(" to manually test your changes.")],1),a._v(" "),s("h2",{attrs:{id:"integration-testing"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#integration-testing","aria-hidden":"true"}},[a._v("#")]),a._v(" Integration testing")]),a._v(" "),s("p",[a._v("Flagger end-to-end tests can be run locally with "),s("a",{attrs:{href:"https://github.com/kubernetes-sigs/kind",target:"_blank",rel:"noopener noreferrer"}},[a._v("Kubernetes Kind"),s("OutboundLink")],1),a._v(".")]),a._v(" "),s("p",[a._v("Create a Kind cluster:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kind create cluster\n")])])]),s("p",[a._v("Install a service mesh and/or an ingress controller in Kind.")]),a._v(" "),s("p",[a._v("Linkerd example:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("linkerd "),s("span",{pre:!0,attrs:{class:"token function"}},[a._v("install")]),a._v(" "),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("|")]),a._v(" kubectl apply -f -\nlinkerd check\n")])])]),s("p",[a._v("Build Flagger container image and load it on the cluster:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[s("span",{pre:!0,attrs:{class:"token function"}},[a._v("make")]),a._v(" build\ndocker tag weaveworks/flagger:latest test/flagger:latest\nkind load docker-image test/flagger:latest\n")])])]),s("p",[a._v("Install Flagger on the cluster and set the test image:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("kubectl apply -k ./kustomize/linkerd\nkubectl -n linkerd "),s("span",{pre:!0,attrs:{class:"token builtin class-name"}},[a._v("set")]),a._v(" image deployment/flagger "),s("span",{pre:!0,attrs:{class:"token assign-left variable"}},[a._v("flagger")]),s("span",{pre:!0,attrs:{class:"token operator"}},[a._v("=")]),a._v("test/flagger:latest\nkubectl -n linkerd rollout status deployment/flagger\n")])])]),s("p",[a._v("Run the Linkerd e2e tests:")]),a._v(" "),s("div",{staticClass:"language-bash extra-class"},[s("pre",{pre:!0,attrs:{class:"language-bash"}},[s("code",[a._v("./test/e2e-linkerd-tests.sh\n")])])]),s("p",[a._v("For each service mesh and ingress controller there is a dedicated e2e test suite,\nchose one that matches your changes from this "),s("a",{attrs:{href:"https://github.com/weaveworks/flagger/tree/master/test",target:"_blank",rel:"noopener noreferrer"}},[a._v("list"),s("OutboundLink")],1),a._v(".")]),a._v(" "),s("p",[a._v("When you open a pull request on Flagger repo, the unit and integration tests will be run in CI.")])])},[],!1,null,null,null);e.default=r.exports}}]); \ No newline at end of file diff --git a/assets/js/8.8b72a8d3.js b/assets/js/8.8b72a8d3.js new file mode 100644 index 00000000..c781fe1f --- /dev/null +++ b/assets/js/8.8b72a8d3.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[8],{269:function(e,t,a){"use strict";a.r(t);var s=a(37),r=Object(s.a)({},function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[a("h1",{attrs:{id:"release-guide"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#release-guide","aria-hidden":"true"}},[e._v("#")]),e._v(" Release Guide")]),e._v(" "),a("p",[e._v("This document describes how to release Flagger.")]),e._v(" "),a("h2",{attrs:{id:"release"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#release","aria-hidden":"true"}},[e._v("#")]),e._v(" Release")]),e._v(" "),a("p",[e._v("To release a new Flagger version (e.g. "),a("code",[e._v("2.0.0")]),e._v(") follow these steps:")]),e._v(" "),a("ul",[a("li",[e._v("create a branch "),a("code",[e._v("git checkout -b prep-2.0.0")])]),e._v(" "),a("li",[e._v("set the version in code and manifests "),a("code",[e._v("TAG=2.0.0 make version-set")])]),e._v(" "),a("li",[e._v("commit changes and merge PR")]),e._v(" "),a("li",[e._v("checkout master "),a("code",[e._v("git checkout master && git pull")])]),e._v(" "),a("li",[e._v("tag master "),a("code",[e._v("make release")])])]),e._v(" "),a("h2",{attrs:{id:"ci"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ci","aria-hidden":"true"}},[e._v("#")]),e._v(" CI")]),e._v(" "),a("p",[e._v("After the tag has been pushed to GitHub, the CI release pipeline does the following:")]),e._v(" "),a("ul",[a("li",[e._v("creates a GitHub release")]),e._v(" "),a("li",[e._v("pushes the Flagger binary and change log to GitHub release")]),e._v(" "),a("li",[e._v("pushes the Flagger container image to Docker Hub")]),e._v(" "),a("li",[e._v("pushes the Helm chart to github-pages branch")]),e._v(" "),a("li",[e._v("GitHub pages publishes the new chart version on the Helm repository")])]),e._v(" "),a("h2",{attrs:{id:"docs"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#docs","aria-hidden":"true"}},[e._v("#")]),e._v(" Docs")]),e._v(" "),a("p",[e._v("The documentation "),a("a",{attrs:{href:"https://docs.flagger.app",target:"_blank",rel:"noopener noreferrer"}},[e._v("website"),a("OutboundLink")],1),e._v(" is built from the "),a("code",[e._v("docs")]),e._v(" branch.")]),e._v(" "),a("p",[e._v("After a Flagger release, publish the docs with:")]),e._v(" "),a("ul",[a("li",[a("code",[e._v("git checkout master && git pull")])]),e._v(" "),a("li",[a("code",[e._v("git checkout docs")])]),e._v(" "),a("li",[a("code",[e._v("git rebase master")])]),e._v(" "),a("li",[a("code",[e._v("git push origin docs")])])])])},[],!1,null,null,null);t.default=r.exports}}]); \ No newline at end of file diff --git a/assets/js/9.7919fc1d.js b/assets/js/9.7919fc1d.js new file mode 100644 index 00000000..3cd69ace --- /dev/null +++ b/assets/js/9.7919fc1d.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[9],{268:function(e,a,t){"use strict";t.r(a);var s=t(37),r=Object(s.a)({},function(){var e=this,a=e.$createElement,t=e._self._c||a;return t("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[t("h1",{attrs:{id:"upgrade-guide"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#upgrade-guide","aria-hidden":"true"}},[e._v("#")]),e._v(" Upgrade Guide")]),e._v(" "),t("p",[e._v("This document describes how to upgrade Flagger.")]),e._v(" "),t("h2",{attrs:{id:"upgrade-canaries-v1alpha3-to-v1beta1"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#upgrade-canaries-v1alpha3-to-v1beta1","aria-hidden":"true"}},[e._v("#")]),e._v(" Upgrade canaries v1alpha3 to v1beta1")]),e._v(" "),t("p",[e._v("Canary CRD changes in "),t("code",[e._v("canaries.flagger.app/v1beta1")]),e._v(":")]),e._v(" "),t("ul",[t("li",[e._v("the "),t("code",[e._v("spec.canaryAnalysis")]),e._v(" field has been deprecated and replaced with "),t("code",[e._v("spec.analysis")])]),e._v(" "),t("li",[e._v("the "),t("code",[e._v("spec.analysis.interval")]),e._v(" and "),t("code",[e._v("spec.analysis.threshold")]),e._v(" fields are required")]),e._v(" "),t("li",[e._v("the "),t("code",[e._v("status.lastAppliedSpec")]),e._v(" and "),t("code",[e._v("status.lastPromotedSpec")]),e._v(" hashing algorithm changed to "),t("code",[e._v("hash/fnv")])]),e._v(" "),t("li",[e._v("the "),t("code",[e._v("spec.analysis.alerts")]),e._v(" array can reference "),t("code",[e._v("alertproviders.flagger.app/v1beta1")]),e._v(" resources")]),e._v(" "),t("li",[e._v("the "),t("code",[e._v("spec.analysis.metrics[].templateRef")]),e._v(" can reference a "),t("code",[e._v("metrictemplate.flagger.app/v1beta1")]),e._v(" resource")]),e._v(" "),t("li",[e._v("the "),t("code",[e._v("metric.threshold")]),e._v(" field has been deprecated and replaced with "),t("code",[e._v("metric.thresholdRange")])]),e._v(" "),t("li",[e._v("the "),t("code",[e._v("spec.targetRef")]),e._v(" can reference "),t("code",[e._v("DaemonSet")]),e._v(" kind")])]),e._v(" "),t("p",[e._v("Upgrade procedure:")]),e._v(" "),t("ul",[t("li",[e._v("install the "),t("code",[e._v("v1beta1")]),e._v(" CRDs")]),e._v(" "),t("li",[e._v("update Flagger deployment")]),e._v(" "),t("li",[e._v("replace "),t("code",[e._v("apiVersion: flagger.app/v1alpha3")]),e._v(" with "),t("code",[e._v("apiVersion: flagger.app/v1beta1")]),e._v(" in all canary manifests")]),e._v(" "),t("li",[e._v("replace "),t("code",[e._v("spec.canaryAnalysis")]),e._v(" with "),t("code",[e._v("spec.analysis")]),e._v(" in all canary manifests")]),e._v(" "),t("li",[e._v("update canary manifests in cluster")])]),e._v(" "),t("p",[t("strong",[e._v("Note")]),e._v(" that after upgrading Flagger, all canaries will be triggered as the hash value used for tracking changes\nis computed differently. You can set "),t("code",[e._v("spec.skipAnalysis: true")]),e._v(" in all canary manifests before upgrading Flagger,\ndo the upgrade, wait for Flagger to finish the no-op promotions and finally set "),t("code",[e._v("skipAnalysis")]),e._v(" to "),t("code",[e._v("false")]),e._v(".")]),e._v(" "),t("p",[e._v("Update builtin metrics:")]),e._v(" "),t("ul",[t("li",[e._v("replace "),t("code",[e._v("threshold")]),e._v(" with "),t("code",[e._v("thresholdRange.min")]),e._v(" for request-success-rate")]),e._v(" "),t("li",[e._v("replace "),t("code",[e._v("threshold")]),e._v(" with "),t("code",[e._v("thresholdRange.max")]),e._v(" for request-duration")])]),e._v(" "),t("div",{staticClass:"language-yaml extra-class"},[t("pre",{pre:!0,attrs:{class:"language-yaml"}},[t("code",[t("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("metrics")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v(" "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("name")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" request"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v("success"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v("rate\n "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("thresholdRange")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("min")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),t("span",{pre:!0,attrs:{class:"token number"}},[e._v("99")]),e._v("\n "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("interval")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 1m\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v(" "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("name")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" request"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v("-")]),e._v("duration\n "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("thresholdRange")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v("\n "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("max")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" "),t("span",{pre:!0,attrs:{class:"token number"}},[e._v("500")]),e._v("\n "),t("span",{pre:!0,attrs:{class:"token key atrule"}},[e._v("interval")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[e._v(":")]),e._v(" 1m\n")])])])])},[],!1,null,null,null);a.default=r.exports}}]); \ No newline at end of file diff --git a/assets/js/app.b4b27991.js b/assets/js/app.b4b27991.js new file mode 100644 index 00000000..13af667f --- /dev/null +++ b/assets/js/app.b4b27991.js @@ -0,0 +1,8 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[0],[]]);!function(t){function e(e){for(var r,a,s=e[0],u=e[1],c=e[2],f=0,p=[];f0?o(r(t),9007199254740991):0}},function(t,e,n){var r=n(7),o=n(17),i=n(22),a=n(41)("src"),s=n(143),u=(""+s).split("toString");n(28).inspectSource=function(t){return s.call(t)},(t.exports=function(t,e,n,s){var c="function"==typeof n;c&&(i(n,"name")||o(n,"name",e)),t[e]!==n&&(c&&(i(n,a)||o(n,a,t[e]?""+t[e]:u.join(String(e)))),t===r?t[e]=n:s?t[e]?t[e]=n:o(t,e,n):(delete t[e],o(t,e,n)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[a]||s.call(this)})},function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},function(t,e,n){var r=n(45);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}}},function(t,e){t.exports={}},function(t,e,n){var r=n(165),o=n(68);t.exports=function(t){return r(o(t))}},function(t,e){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,e,n){"use strict";var r=n(16);t.exports=function(t,e){return!!t&&r(function(){e?t.call(null,function(){},1):t.call(null)})}},function(t,e){var n=t.exports={version:"2.6.9"};"number"==typeof __e&&(__e=n)},function(t,e,n){var r=n(88);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}}},function(t,e,n){var r=n(26);t.exports=function(t){return Object(r(t))}},function(t,e){t.exports={}},function(t,e,n){var r=n(58),o=n(26);t.exports=function(t){return r(o(t))}},function(t,e){t.exports=!0},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},function(t,e,n){"use strict";function r(t,e,n,r,o,i,a,s){var u,c="function"==typeof t?t.options:t;if(e&&(c.render=e,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId="data-v-"+i),a?(u=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(a)},c._ssrRegister=u):o&&(u=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),u)if(c.functional){c._injectStyles=u;var l=c.render;c.render=function(t,e){return u.call(e),l(t,e)}}else{var f=c.beforeCreate;c.beforeCreate=f?[].concat(f,u):[u]}return{exports:t,options:c}}n.d(e,"a",function(){return r})},function(t,e,n){for(var r=n(146),o=n(61),i=n(21),a=n(7),s=n(17),u=n(31),c=n(2),l=c("iterator"),f=c("toStringTag"),p=u.Array,h={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},d=o(h),v=0;v0?r:n)(t)}},function(t,e,n){"use strict";var r=n(162)(!0);n(101)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,e=this._t,n=this._i;return n>=e.length?{value:void 0,done:!0}:(t=r(e,n),this._i+=t.length,{value:t,done:!1})})},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},function(t,e,n){var r=n(9).f,o=n(19),i=n(4)("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,i)&&r(t,i,{configurable:!0,value:e})}},function(t,e,n){var r=n(68);t.exports=function(t){return Object(r(t))}},function(t,e,n){var r=n(12).f,o=Function.prototype,i=/^\s*function ([^ (]*)/;"name"in o||n(13)&&r(o,"name",{configurable:!0,get:function(){try{return(""+this).match(i)[1]}catch(t){return""}}})},function(t,e,n){},function(t,e,n){"use strict";var r=n(127),o=n.n(r);var i=n(128),a=n.n(i),s=n(129),u=n.n(s);function c(t){return function(t){if(o()(t)){for(var e=0,n=new Array(t.length);e
'};function o(t,e,n){return tn?n:t}function i(t){return 100*(-1+t)}n.configure=function(t){var e,n;for(e in t)void 0!==(n=t[e])&&t.hasOwnProperty(e)&&(r[e]=n);return this},n.status=null,n.set=function(t){var e=n.isStarted();t=o(t,r.minimum,1),n.status=1===t?null:t;var u=n.render(!e),c=u.querySelector(r.barSelector),l=r.speed,f=r.easing;return u.offsetWidth,a(function(e){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),s(c,function(t,e,n){var o;return(o="translate3d"===r.positionUsing?{transform:"translate3d("+i(t)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+i(t)+"%,0)"}:{"margin-left":i(t)+"%"}).transition="all "+e+"ms "+n,o}(t,l,f)),1===t?(s(u,{transition:"none",opacity:1}),u.offsetWidth,setTimeout(function(){s(u,{transition:"all "+l+"ms linear",opacity:0}),setTimeout(function(){n.remove(),e()},l)},l)):setTimeout(e,l)}),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var t=function(){setTimeout(function(){n.status&&(n.trickle(),t())},r.trickleSpeed)};return r.trickle&&t(),this},n.done=function(t){return t||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(t){var e=n.status;return e?("number"!=typeof t&&(t=(1-e)*o(Math.random()*e,.1,.95)),e=o(e+t,0,.994),n.set(e)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},t=0,e=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===e&&n.start(),t++,e++,r.always(function(){0==--e?(t=0,n.done()):n.set((t-e)/t)}),this):this},n.render=function(t){if(n.isRendered())return document.getElementById("nprogress");c(document.documentElement,"nprogress-busy");var e=document.createElement("div");e.id="nprogress",e.innerHTML=r.template;var o,a=e.querySelector(r.barSelector),u=t?"-100":i(n.status||0),l=document.querySelector(r.parent);return s(a,{transition:"all 0 linear",transform:"translate3d("+u+"%,0,0)"}),r.showSpinner||(o=e.querySelector(r.spinnerSelector))&&p(o),l!=document.body&&c(l,"nprogress-custom-parent"),l.appendChild(e),e},n.remove=function(){l(document.documentElement,"nprogress-busy"),l(document.querySelector(r.parent),"nprogress-custom-parent");var t=document.getElementById("nprogress");t&&p(t)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var t=document.body.style,e="WebkitTransform"in t?"Webkit":"MozTransform"in t?"Moz":"msTransform"in t?"ms":"OTransform"in t?"O":"";return e+"Perspective"in t?"translate3d":e+"Transform"in t?"translate":"margin"};var a=function(){var t=[];function e(){var n=t.shift();n&&n(e)}return function(n){t.push(n),1==t.length&&e()}}(),s=function(){var t=["Webkit","O","Moz","ms"],e={};function n(n){return n=n.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(t,e){return e.toUpperCase()}),e[n]||(e[n]=function(e){var n=document.body.style;if(e in n)return e;for(var r,o=t.length,i=e.charAt(0).toUpperCase()+e.slice(1);o--;)if((r=t[o]+i)in n)return r;return e}(n))}function r(t,e,r){e=n(e),t.style[e]=r}return function(t,e){var n,o,i=arguments;if(2==i.length)for(n in e)void 0!==(o=e[n])&&e.hasOwnProperty(n)&&r(t,n,o);else r(t,i[1],i[2])}}();function u(t,e){return("string"==typeof t?t:f(t)).indexOf(" "+e+" ")>=0}function c(t,e){var n=f(t),r=n+e;u(n,e)||(t.className=r.substring(1))}function l(t,e){var n,r=f(t);u(t,e)&&(n=r.replace(" "+e+" "," "),t.className=n.substring(1,n.length-1))}function f(t){return(" "+(t.className||"")+" ").replace(/\s+/gi," ")}function p(t){t&&t.parentNode&&t.parentNode.removeChild(t)}return n})?r.call(e,n,e,t):r)||(t.exports=o)},function(t,e,n){"use strict";var r=n(1),o=n(55)(1);r(r.P+r.F*!n(27)([].map,!0),"Array",{map:function(t){return o(this,t,arguments[1])}})},function(t,e,n){var r=n(29),o=n(58),i=n(30),a=n(20),s=n(144);t.exports=function(t,e){var n=1==t,u=2==t,c=3==t,l=4==t,f=6==t,p=5==t||f,h=e||s;return function(e,s,d){for(var v,m,y=i(e),g=o(y),b=r(s,d,3),_=a(g.length),w=0,x=n?h(e,_):u?h(e,0):void 0;_>w;w++)if((p||w in g)&&(m=b(v=g[w],w,y),t))if(n)x[w]=m;else if(m)switch(t){case 3:return!0;case 5:return v;case 6:return w;case 2:x.push(v)}else if(l)return!1;return f?-1:c||l?l:x}}},function(t,e,n){"use strict";var r=n(1),o=n(55)(0),i=n(27)([].forEach,!0);r(r.P+r.F*!i,"Array",{forEach:function(t){return o(this,t,arguments[1])}})},function(t,e,n){var r=n(28),o=n(7),i=o["__core-js_shared__"]||(o["__core-js_shared__"]={});(t.exports=function(t,e){return i[t]||(i[t]=void 0!==e?e:{})})("versions",[]).push({version:r.version,mode:n(94)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(t,e,n){var r=n(42);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==r(t)?t.split(""):Object(t)}},function(t,e,n){"use strict";var r=n(94),o=n(1),i=n(21),a=n(17),s=n(31),u=n(147),c=n(65),l=n(151),f=n(2)("iterator"),p=!([].keys&&"next"in[].keys()),h=function(){return this};t.exports=function(t,e,n,d,v,m,y){u(n,e,d);var g,b,_,w=function(t){if(!p&&t in S)return S[t];switch(t){case"keys":case"values":return function(){return new n(this,t)}}return function(){return new n(this,t)}},x=e+" Iterator",k="values"==v,O=!1,S=t.prototype,C=S[f]||S["@@iterator"]||v&&S[v],A=C||w(v),E=v?k?w("entries"):A:void 0,$="Array"==e&&S.entries||C;if($&&(_=l($.call(new t)))!==Object.prototype&&_.next&&(c(_,x,!0),r||"function"==typeof _[f]||a(_,f,h)),k&&C&&"values"!==C.name&&(O=!0,A=function(){return C.call(this)}),r&&!y||!p&&!O&&S[f]||a(S,f,A),s[e]=A,s[x]=h,v)if(g={values:k?A:w("values"),keys:m?A:w("keys"),entries:E},y)for(b in g)b in S||i(S,b,g[b]);else o(o.P+o.F*(p||O),e,g);return g}},function(t,e,n){var r=n(8),o=n(148),i=n(64),a=n(63)("IE_PROTO"),s=function(){},u=function(){var t,e=n(92)("iframe"),r=i.length;for(e.style.display="none",n(150).appendChild(e),e.src="javascript:",(t=e.contentWindow.document).open(),t.write(" + + diff --git a/dev/index.html b/dev/index.html new file mode 100644 index 00000000..d9557640 --- /dev/null +++ b/dev/index.html @@ -0,0 +1,35 @@ + + + + + + Flagger + + + + + + + + + + + + + + + + + diff --git a/dev/release-guide.html b/dev/release-guide.html new file mode 100644 index 00000000..be4f36d5 --- /dev/null +++ b/dev/release-guide.html @@ -0,0 +1,43 @@ + + + + + + Release Guide | Flagger + + + + + + + + + + + + + +

Release Guide

This document describes how to release Flagger.

Release

To release a new Flagger version (e.g. 2.0.0) follow these steps:

  • create a branch git checkout -b prep-2.0.0
  • set the version in code and manifests TAG=2.0.0 make version-set
  • commit changes and merge PR
  • checkout master git checkout master && git pull
  • tag master make release

CI

After the tag has been pushed to GitHub, the CI release pipeline does the following:

  • creates a GitHub release
  • pushes the Flagger binary and change log to GitHub release
  • pushes the Flagger container image to Docker Hub
  • pushes the Helm chart to github-pages branch
  • GitHub pages publishes the new chart version on the Helm repository

Docs

The documentation website is built from the docs branch.

After a Flagger release, publish the docs with:

  • git checkout master && git pull
  • git checkout docs
  • git rebase master
  • git push origin docs
+ + + diff --git a/dev/upgrade-guide.html b/dev/upgrade-guide.html new file mode 100644 index 00000000..23668aab --- /dev/null +++ b/dev/upgrade-guide.html @@ -0,0 +1,50 @@ + + + + + + Upgrade Guide | Flagger + + + + + + + + + + + + + +

Upgrade Guide

This document describes how to upgrade Flagger.

Upgrade canaries v1alpha3 to v1beta1

Canary CRD changes in canaries.flagger.app/v1beta1:

  • the spec.canaryAnalysis field has been deprecated and replaced with spec.analysis
  • the spec.analysis.interval and spec.analysis.threshold fields are required
  • the status.lastAppliedSpec and status.lastPromotedSpec hashing algorithm changed to hash/fnv
  • the spec.analysis.alerts array can reference alertproviders.flagger.app/v1beta1 resources
  • the spec.analysis.metrics[].templateRef can reference a metrictemplate.flagger.app/v1beta1 resource
  • the metric.threshold field has been deprecated and replaced with metric.thresholdRange
  • the spec.targetRef can reference DaemonSet kind

Upgrade procedure:

  • install the v1beta1 CRDs
  • update Flagger deployment
  • replace apiVersion: flagger.app/v1alpha3 with apiVersion: flagger.app/v1beta1 in all canary manifests
  • replace spec.canaryAnalysis with spec.analysis in all canary manifests
  • update canary manifests in cluster

Note that after upgrading Flagger, all canaries will be triggered as the hash value used for tracking changes +is computed differently. You can set spec.skipAnalysis: true in all canary manifests before upgrading Flagger, +do the upgrade, wait for Flagger to finish the no-op promotions and finally set skipAnalysis to false.

Update builtin metrics:

  • replace threshold with thresholdRange.min for request-success-rate
  • replace threshold with thresholdRange.max for request-duration
metrics:
+- name: request-success-rate
+  thresholdRange:
+    min: 99
+  interval: 1m
+- name: request-duration
+  thresholdRange:
+    max: 500
+  interval: 1m
+
+ + + diff --git a/index.html b/index.html index 716e16e0..b3b692e0 100644 --- a/index.html +++ b/index.html @@ -7,17 +7,17 @@ - + - + -

Safer Releases

Reduce the risk of introducing a new software version in production by gradually shifting traffic to the new version while measuring metrics like HTTP/gRPC request success rate and latency.

Flexible Traffic Routing

Shift and route traffic between app versions using a service mesh like Istio, Linkerd or AWS App Mesh. Or if a service mesh does not meet your needs, use an Ingress controller like Contour, Gloo or NGINX.

Extensible Validation

Besides the builtin metrics checks, you can extend your application analysis with custom Prometheus, Datadog, CloudWatch metrics and webooks for running acceptance tests, load tests, or any other custom validation.

Progressive Delivery

Flagger was designed to give developers confidence in automating production releases with progressive delivery techniques.

Canary release

A benefit of using canary releases is the ability to do capacity testing of the new version in a production environment with a safe rollback strategy if issues are found. By slowly ramping up the load, you can monitor and capture metrics -about how the new version impacts the production environment.

Martin Fowler

Flagger can run automated application analysis, testing, promotion and rollback for the following deployment strategies:

Flagger can be configured to send notifications to a -Slack or -Microsoft Teams channel. +about how the new version impacts the production environment.

Martin Fowler

Flagger can run automated application analysis, testing, promotion and rollback for the following deployment strategies:

Flagger can be configured to send notifications to +Slack, Microsoft Teams, Discord or Rocket. It will post messages when a deployment has been initialised, when a new revision has been detected and if the canary analysis failed or succeeded.

GitOps

GtiOps with Flagger and FluxCD

You can build fully automated GitOps pipelines for canary deployments with Flagger and FluxCD (CNCF sandbox project).

GitOps

GitOps is a way to do Kubernetes cluster management and application delivery. @@ -57,6 +57,6 @@ and join the

  • File an issue.
  • Your feedback is always welcome!

    - + diff --git a/install/flagger-install-on-eks-appmesh.html b/install/flagger-install-on-eks-appmesh.html new file mode 100644 index 00000000..ab5ade52 --- /dev/null +++ b/install/flagger-install-on-eks-appmesh.html @@ -0,0 +1,97 @@ + + + + + + Flagger Install on EKS App Mesh | Flagger + + + + + + + + + + + + + +

    Flagger Install on EKS App Mesh

    This guide walks you through setting up Flagger and AWS App Mesh on EKS.

    App Mesh

    The App Mesh integration with EKS is made out of the following components:

    • Kubernetes custom resources +
      • mesh.appmesh.k8s.aws defines a logical boundary for network traffic between the services
      • virtualnode.appmesh.k8s.aws defines a logical pointer to a Kubernetes workload
      • virtualservice.appmesh.k8s.aws defines the routing rules for a workload inside the mesh
    • CRD controller - keeps the custom resources in sync with the App Mesh control plane
    • Admission controller - injects the Envoy sidecar and assigns Kubernetes pods to App Mesh virtual nodes
    • Telemetry service - Prometheus instance that collects and stores Envoy's metrics

    Create a Kubernetes cluster

    In order to create an EKS cluster you can use eksctl. Eksctl is an open source command-line utility made by Weaveworks in collaboration with Amazon.

    On MacOS you can install eksctl with Homebrew:

    brew tap weaveworks/tap
    +brew install weaveworks/tap/eksctl
    +

    Create an EKS cluster:

    eksctl create cluster --name=appmesh \
    +--region=us-west-2 \
    +--nodes 3 \
    +--node-volume-size=120 \
    +--appmesh-access
    +

    The above command will create a two nodes cluster with App Mesh IAM policy attached to the EKS node instance role.

    Verify the install with:

    kubectl get nodes
    +

    Install Helm

    Install the Helm command-line tool:

    brew install kubernetes-helm
    +

    Create a service account and a cluster role binding for Tiller:

    kubectl -n kube-system create sa tiller
    +
    +kubectl create clusterrolebinding tiller-cluster-rule \
    +--clusterrole=cluster-admin \
    +--serviceaccount=kube-system:tiller
    +

    Deploy Tiller in the kube-system namespace:

    helm init --service-account tiller
    +

    You should consider using SSL between Helm and Tiller, for more information on securing your Helm installation see docs.helm.sh.

    Enable horizontal pod auto-scaling

    Install the Horizontal Pod Autoscaler (HPA) metrics provider:

    helm upgrade -i metrics-server stable/metrics-server \
    +--namespace kube-system \
    +--set args[0]=--kubelet-preferred-address-types=InternalIP
    +

    After a minute, the metrics API should report CPU and memory usage for pods. You can very the metrics API with:

    kubectl -n kube-system top pods
    +

    Install the App Mesh components

    Create the appmesh-system namespace:

    kubectl create ns appmesh-system
    +

    Apply the App Mesh CRDs:

    kubectl apply -k github.com/aws/eks-charts/stable/appmesh-controller//crds
    +

    Add the EKS repository to Helm:

    helm repo add eks https://aws.github.io/eks-charts
    +

    Install the App Mesh CRD controller:

    helm upgrade -i appmesh-controller eks/appmesh-controller \
    +--wait --namespace appmesh-system
    +

    Install the App Mesh admission controller and create a mesh called global:

    helm upgrade -i appmesh-inject eks/appmesh-inject \
    +--wait --namespace appmesh-system \
    +--set mesh.create=true \
    +--set mesh.name=global
    +

    Verify that the global mesh is active:

    kubectl describe mesh
    +
    +Status:
    +  Mesh Condition:
    +    Status:                True
    +    Type:                  MeshActive
    +

    In order to collect the App Mesh metrics that Flagger needs to run the canary analysis, you'll need to setup a Prometheus instance to scrape the Envoy sidecars.

    Install the App Mesh Prometheus:

    helm upgrade -i appmesh-prometheus eks/appmesh-prometheus \
    +--wait --namespace appmesh-system
    +

    Install Flagger and Grafana

    Add Flagger Helm repository:

    helm repo add flagger https://flagger.app
    +

    Install Flagger's Canary CRD:

    kubectl apply -f https://raw.githubusercontent.com/weaveworks/flagger/master/artifacts/flagger/crd.yaml
    +

    Deploy Flagger in the appmesh-system namespace:

    helm upgrade -i flagger flagger/flagger \
    +--namespace=appmesh-system \
    +--set crd.create=false \
    +--set meshProvider=appmesh \
    +--set metricsServer=http://appmesh-prometheus:9090
    +

    You can enable Slack or MS Teams notifications with:

    helm upgrade -i flagger flagger/flagger \
    +--reuse-values \
    +--namespace=appmesh-system \
    +--set slack.url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
    +--set slack.channel=general \
    +--set slack.user=flagger
    +

    Flagger comes with a Grafana dashboard made for monitoring the canary analysis. Deploy Grafana in the appmesh-system namespace:

    helm upgrade -i flagger-grafana flagger/grafana \
    +--namespace=appmesh-system \
    +--set url=http://appmesh-prometheus:9090
    +

    You can access Grafana using port forwarding:

    kubectl -n appmesh-system port-forward svc/flagger-grafana 3000:80
    +

    Now that you have Flagger running you can try the App Mesh canary deployments tutorial.

    + + + diff --git a/install/flagger-install-on-google-cloud.html b/install/flagger-install-on-google-cloud.html new file mode 100644 index 00000000..6902ee06 --- /dev/null +++ b/install/flagger-install-on-google-cloud.html @@ -0,0 +1,208 @@ + + + + + + Flagger Install on GKE Istio | Flagger + + + + + + + + + + + + + +

    Flagger Install on GKE Istio

    This guide walks you through setting up Flagger and Istio on Google Kubernetes Engine.

    GKE Cluster Overview

    Prerequisites

    You will be creating a cluster on Google’s Kubernetes Engine (GKE), if you don’t have an account you can sign up here for free credits.

    Login into Google Cloud, create a project and enable billing for it.

    Install the gcloud command line utility and configure your project with gcloud init.

    Set the default project (replace PROJECT_ID with your own project):

    gcloud config set project PROJECT_ID
    +

    Set the default compute region and zone:

    gcloud config set compute/region us-central1
    +gcloud config set compute/zone us-central1-a
    +

    Enable the Kubernetes and Cloud DNS services for your project:

    gcloud services enable container.googleapis.com
    +gcloud services enable dns.googleapis.com
    +

    Install the kubectl command-line tool:

    gcloud components install kubectl
    +

    GKE cluster setup

    Create a cluster with the Istio add-on:

    K8S_VERSION=$(gcloud container get-server-config --format=json \
    +| jq -r '.validMasterVersions[0]')
    +
    +gcloud beta container clusters create istio \
    +--cluster-version=${K8S_VERSION} \
    +--zone=us-central1-a \
    +--num-nodes=2 \
    +--machine-type=n1-highcpu-4 \
    +--preemptible \
    +--no-enable-cloud-logging \
    +--no-enable-cloud-monitoring \
    +--disk-size=30 \
    +--enable-autorepair \
    +--addons=HorizontalPodAutoscaling,Istio \
    +--istio-config=auth=MTLS_PERMISSIVE
    +

    The above command will create a default node pool consisting of two n1-highcpu-4 (vCPU: 4, RAM 3.60GB, DISK: 30GB) preemptible VMs. Preemptible VMs are up to 80% cheaper than regular instances and are terminated and replaced after a maximum of 24 hours.

    Set up credentials for kubectl:

    gcloud container clusters get-credentials istio
    +

    Create a cluster admin role binding:

    kubectl create clusterrolebinding "cluster-admin-$(whoami)" \
    +--clusterrole=cluster-admin \
    +--user="$(gcloud config get-value core/account)"
    +

    Validate your setup with:

    kubectl -n istio-system get svc
    +

    In a couple of seconds GCP should allocate an external IP to the istio-ingressgateway service.

    Cloud DNS setup

    You will need an internet domain and access to the registrar to change the name servers to Google Cloud DNS.

    Create a managed zone named istio in Cloud DNS (replace example.com with your domain):

    gcloud dns managed-zones create \
    +--dns-name="example.com." \
    +--description="Istio zone" "istio"
    +

    Look up your zone's name servers:

    gcloud dns managed-zones describe istio
    +

    Update your registrar's name server records with the records returned by the above command.

    Wait for the name servers to change (replace example.com with your domain):

    watch dig +short NS example.com
    +

    Create a static IP address named istio-gateway using the Istio ingress IP:

    export GATEWAY_IP=$(kubectl -n istio-system get svc/istio-ingressgateway -ojson \
    +| jq -r .status.loadBalancer.ingress[0].ip)
    +
    +gcloud compute addresses create istio-gateway --addresses ${GATEWAY_IP} --region us-central1
    +

    Create the following DNS records (replace example.com with your domain):

    DOMAIN="example.com"
    +
    +gcloud dns record-sets transaction start --zone=istio
    +
    +gcloud dns record-sets transaction add --zone=istio \
    +--name="${DOMAIN}" --ttl=300 --type=A ${GATEWAY_IP}
    +
    +gcloud dns record-sets transaction add --zone=istio \
    +--name="www.${DOMAIN}" --ttl=300 --type=A ${GATEWAY_IP}
    +
    +gcloud dns record-sets transaction add --zone=istio \
    +--name="*.${DOMAIN}" --ttl=300 --type=A ${GATEWAY_IP}
    +
    +gcloud dns record-sets transaction execute --zone istio
    +

    Verify that the wildcard DNS is working (replace example.com with your domain):

    watch host test.example.com
    +

    Install Helm

    Install the Helm command-line tool:

    brew install kubernetes-helm
    +

    Create a service account and a cluster role binding for Tiller:

    kubectl -n kube-system create sa tiller
    +
    +kubectl create clusterrolebinding tiller-cluster-rule \
    +--clusterrole=cluster-admin \
    +--serviceaccount=kube-system:tiller
    +

    Deploy Tiller in the kube-system namespace:

    helm init --service-account tiller
    +

    You should consider using SSL between Helm and Tiller, for more information on securing your Helm installation see docs.helm.sh.

    Install cert-manager

    Jetstack's cert-manager is a Kubernetes operator that automatically creates and manages TLS certs issued by Let’s Encrypt.

    You'll be using cert-manager to provision a wildcard certificate for the Istio ingress gateway.

    Install cert-manager's CRDs:

    CERT_REPO=https://raw.githubusercontent.com/jetstack/cert-manager
    +
    +kubectl apply -f ${CERT_REPO}/release-0.10/deploy/manifests/00-crds.yaml
    +

    Create the cert-manager namespace and disable resource validation:

    kubectl create namespace cert-manager
    +
    +kubectl label namespace cert-manager certmanager.k8s.io/disable-validation=true
    +

    Install cert-manager with Helm:

    helm repo add jetstack https://charts.jetstack.io && \
    +helm repo update && \
    +helm upgrade -i cert-manager \
    +--namespace cert-manager \
    +--version v0.10.0 \
    +jetstack/cert-manager
    +

    Istio Gateway TLS setup

    Istio Let's Encrypt

    Create a generic Istio Gateway to expose services outside the mesh on HTTPS:

    REPO=https://raw.githubusercontent.com/weaveworks/flagger/master
    +
    +kubectl apply -f ${REPO}/artifacts/gke/istio-gateway.yaml
    +

    Create a service account with Cloud DNS admin role (replace my-gcp-project with your project ID):

    GCP_PROJECT=my-gcp-project
    +
    +gcloud iam service-accounts create dns-admin \
    +--display-name=dns-admin \
    +--project=${GCP_PROJECT}
    +
    +gcloud iam service-accounts keys create ./gcp-dns-admin.json \
    +--iam-account=dns-admin@${GCP_PROJECT}.iam.gserviceaccount.com \
    +--project=${GCP_PROJECT}
    +
    +gcloud projects add-iam-policy-binding ${GCP_PROJECT} \
    +--member=serviceAccount:dns-admin@${GCP_PROJECT}.iam.gserviceaccount.com \
    +--role=roles/dns.admin
    +

    Create a Kubernetes secret with the GCP Cloud DNS admin key:

    kubectl create secret generic cert-manager-credentials \
    +--from-file=./gcp-dns-admin.json \
    +--namespace=istio-system
    +

    Create a letsencrypt issuer for CloudDNS (replace email@example.com with a valid email address and my-gcp-projectwith your project ID):

    apiVersion: certmanager.k8s.io/v1alpha1
    +kind: Issuer
    +metadata:
    +  name: letsencrypt-prod
    +  namespace: istio-system
    +spec:
    +  acme:
    +    server: https://acme-v02.api.letsencrypt.org/directory
    +    email: email@example.com
    +    privateKeySecretRef:
    +      name: letsencrypt-prod
    +    dns01:
    +      providers:
    +      - name: cloud-dns
    +        clouddns:
    +          serviceAccountSecretRef:
    +            name: cert-manager-credentials
    +            key: gcp-dns-admin.json
    +          project: my-gcp-project
    +

    Save the above resource as letsencrypt-issuer.yaml and then apply it:

    kubectl apply -f ./letsencrypt-issuer.yaml
    +

    Create a wildcard certificate (replace example.com with your domain):

    apiVersion: certmanager.k8s.io/v1alpha1
    +kind: Certificate
    +metadata:
    +  name: istio-gateway
    +  namespace: istio-system
    +spec:
    +  secretName: istio-ingressgateway-certs
    +  issuerRef:
    +    name: letsencrypt-prod
    +  commonName: "*.example.com"
    +  acme:
    +    config:
    +    - dns01:
    +        provider: cloud-dns
    +      domains:
    +      - "*.example.com"
    +      - "example.com"
    +

    Save the above resource as istio-gateway-cert.yaml and then apply it:

    kubectl apply -f ./istio-gateway-cert.yaml
    +

    In a couple of seconds cert-manager should fetch a wildcard certificate from letsencrypt.org:

    kubectl -n istio-system describe certificate istio-gateway
    +
    +Events:
    +  Type    Reason         Age    From          Message
    +  ----    ------         ----   ----          -------
    +  Normal  CertIssued     1m52s  cert-manager  Certificate issued successfully
    +

    Recreate Istio ingress gateway pods:

    kubectl -n istio-system get pods -l istio=ingressgateway
    +

    Note that Istio gateway doesn't reload the certificates from the TLS secret on cert-manager renewal. Since the GKE cluster is made out of preemptible VMs the gateway pods will be replaced once every 24h, if your not using preemptible nodes then you need to manually delete the gateway pods every two months before the certificate expires.

    Install Prometheus

    The GKE Istio add-on does not include a Prometheus instance that scrapes the Istio telemetry service. Because Flagger uses the Istio HTTP metrics to run the canary analysis you have to deploy the following Prometheus configuration that's similar to the one that comes with the official Istio Helm chart.

    Find the GKE Istio version with:

    kubectl -n istio-system get deploy istio-pilot -oyaml | grep image:
    +

    Install Prometheus in istio-system namespace:

    kubectl -n istio-system apply -f \
    +https://storage.googleapis.com/gke-release/istio/release/1.0.6-gke.3/patches/install-prometheus.yaml
    +

    Install Flagger and Grafana

    Add Flagger Helm repository:

    helm repo add flagger https://flagger.app
    +

    Install Flagger's Canary CRD:

    kubectl apply -f https://raw.githubusercontent.com/weaveworks/flagger/master/artifacts/flagger/crd.yaml
    +

    Deploy Flagger in the istio-system namespace with Slack notifications enabled:

    helm upgrade -i flagger flagger/flagger \
    +--namespace=istio-system \
    +--set crd.create=false \
    +--set metricsServer=http://prometheus.istio-system:9090 \
    +--set slack.url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
    +--set slack.channel=general \
    +--set slack.user=flagger
    +

    Deploy Grafana in the istio-system namespace:

    helm upgrade -i flagger-grafana flagger/grafana \
    +--namespace=istio-system \
    +--set url=http://prometheus.istio-system:9090 \
    +--set user=admin \
    +--set password=replace-me
    +

    Expose Grafana through the public gateway by creating a virtual service (replace example.com with your domain):

    apiVersion: networking.istio.io/v1alpha3
    +kind: VirtualService
    +metadata:
    +  name: grafana
    +  namespace: istio-system
    +spec:
    +  hosts:
    +  - "grafana.example.com"
    +  gateways:
    +  - public-gateway.istio-system.svc.cluster.local
    +  http:
    +  - route:
    +    - destination:
    +        host: flagger-grafana
    +

    Save the above resource as grafana-virtual-service.yaml and then apply it:

    kubectl apply -f ./grafana-virtual-service.yaml
    +

    Navigate to http://grafana.example.com in your browser and you should be redirected to the HTTPS version.

    + + + diff --git a/install/flagger-install-on-kubernetes.html b/install/flagger-install-on-kubernetes.html new file mode 100644 index 00000000..83d7754e --- /dev/null +++ b/install/flagger-install-on-kubernetes.html @@ -0,0 +1,151 @@ + + + + + + Flagger Install on Kubernetes | Flagger + + + + + + + + + + + + + +

    Flagger Install on Kubernetes

    This guide walks you through setting up Flagger on a Kubernetes cluster with Helm v3 or Kustomize.

    Prerequisites

    Flagger requires a Kubernetes cluster v1.11 or newer.

    Install Flagger with Helm

    Add Flagger Helm repository:

    helm repo add flagger https://flagger.app
    +

    Install Flagger's Canary CRD:

    kubectl apply -f https://raw.githubusercontent.com/weaveworks/flagger/master/artifacts/flagger/crd.yaml
    +

    Deploy Flagger for Istio:

    helm upgrade -i flagger flagger/flagger \
    +--namespace=istio-system \
    +--set crd.create=false \
    +--set meshProvider=istio \
    +--set metricsServer=http://prometheus:9090
    +

    Note that Flagger depends on Istio telemetry and Prometheus, if you're installing Istio with istioctl +then you should be using the default profile.

    For Istio multi-cluster shared control plane you can install Flagger +on each remote cluster and set the Istio control plane host cluster kubeconfig:

    helm upgrade -i flagger flagger/flagger \
    +--namespace=istio-system \
    +--set crd.create=false \
    +--set meshProvider=istio \
    +--set metricsServer=http://istio-cluster-prometheus:9090 \
    +--set istio.kubeconfig.secretName=istio-kubeconfig \
    +--set istio.kubeconfig.key=kubeconfig
    +

    Note that the Istio kubeconfig must be stored in a Kubernetes secret with a data key named kubeconfig. +For more details on how to configure Istio multi-cluster credentials +read the Istio docs.

    Deploy Flagger for Linkerd:

    helm upgrade -i flagger flagger/flagger \
    +--namespace=linkerd \
    +--set crd.create=false \
    +--set meshProvider=linkerd \
    +--set metricsServer=http://linkerd-prometheus:9090
    +

    Deploy Flagger for App Mesh:

    helm upgrade -i flagger flagger/flagger \
    +--namespace=appmesh-system \
    +--set crd.create=false \
    +--set meshProvider=appmesh \
    +--set metricsServer=http://appmesh-prometheus:9090
    +

    You can install Flagger in any namespace as long as it can talk to the Prometheus service on port 9090.

    For ingress controllers, the install instructions are:

    Enable Slack notifications:

    helm upgrade -i flagger flagger/flagger \
    +--namespace=istio-system \
    +--set crd.create=false \
    +--set slack.url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
    +--set slack.channel=general \
    +--set slack.user=flagger
    +

    Enable Microsoft Teams notifications:

    helm upgrade -i flagger flagger/flagger \
    +--namespace=istio-system \
    +--set crd.create=false \
    +--set msteams.url=https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK
    +

    You can use the helm template command and apply the generated yaml with kubectl:

    # generate
    +helm fetch --untar --untardir . flagger/flagger &&
    +helm template flagger ./flagger \
    +--namespace=istio-system \
    +--set metricsServer=http://prometheus.istio-system:9090 \
    +> flagger.yaml
    +
    +# apply
    +kubectl apply -f flagger.yaml
    +

    To uninstall the Flagger release with Helm run:

    helm delete flagger
    +

    The command removes all the Kubernetes components associated with the chart and deletes the release.

    Note that on uninstall the Canary CRD will not be removed. Deleting the CRD will make Kubernetes +remove all the objects owned by Flagger like Istio virtual services, Kubernetes deployments and ClusterIP services.

    If you want to remove all the objects created by Flagger you have delete the Canary CRD with kubectl:

    kubectl delete crd canaries.flagger.app
    +

    Install Grafana with Helm

    Flagger comes with a Grafana dashboard made for monitoring the canary analysis.

    Deploy Grafana in the istio-system namespace:

    helm upgrade -i flagger-grafana flagger/grafana \
    +--namespace=istio-system \
    +--set url=http://prometheus.istio-system:9090 \
    +--set user=admin \
    +--set password=change-me
    +

    Or use helm template command and apply the generated yaml with kubectl:

    # generate
    +helm fetch --untar --untardir . flagger/grafana &&
    +helm template flagger-grafana ./grafana \
    +--namespace=istio-system \
    +> flagger-grafana.yaml
    +
    +# apply
    +kubectl apply -f flagger-grafana.yaml
    +

    You can access Grafana using port forwarding:

    kubectl -n istio-system port-forward svc/flagger-grafana 3000:80
    +

    Install Flagger with Kustomize

    As an alternative to Helm, Flagger can be installed with Kustomize.

    Service mesh specific installers

    Install Flagger for Istio:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/istio
    +

    This deploys Flagger in the istio-system namespace and sets the metrics server URL to Istio's Prometheus instance.

    Note that you'll need kubectl 1.14 to run the above the command or you can download +the kustomize binary and run:

    kustomize build github.com/weaveworks/flagger//kustomize/istio | kubectl apply -f -
    +

    Install Flagger for AWS App Mesh:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/appmesh
    +

    This deploys Flagger and sets the metrics server URL to App Mesh's Prometheus instance.

    Install Flagger for Linkerd:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/linkerd
    +

    This deploys Flagger in the linkerd namespace and sets the metrics server URL to Linkerd's Prometheus instance.

    If you want to install a specific Flagger release, add the version number to the URL:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/linkerd?ref=0.18.0
    +

    Generic installer

    Install Flagger and Prometheus:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/kubernetes
    +

    This deploys Flagger and Prometheus in the flagger-system namespace, sets the metrics server URL +to http://flagger-prometheus.flagger-system:9090 and the mesh provider to kubernetes.

    The Prometheus instance has a two hours data retention and is configured to scrape all pods in your cluster +that have the prometheus.io/scrape: "true" annotation.

    To target a different provider you can specify it in the canary custom resource:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: app
    +  namespace: test
    +spec:
    +  # can be: kubernetes, istio, linkerd, appmesh, nginx, gloo
    +  # use the kubernetes provider for Blue/Green style deployments
    +  provider: nginx
    +

    Customized installer

    Create a kustomization file using flagger as base:

    cat > kustomization.yaml <<EOF
    +namespace: istio-system
    +bases:
    +  - github.com/weaveworks/flagger/kustomize/base/flagger
    +patchesStrategicMerge:
    +  - patch.yaml
    +EOF
    +

    Create a patch and enable Slack notifications by setting the slack channel and hook URL:

    cat > patch.yaml <<EOF
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: flagger
    +spec:
    +  template:
    +    spec:
    +      containers:
    +        - name: flagger
    +          args:
    +            - -mesh-provider=istio
    +            - -metrics-server=http://prometheus.istio-system:9090
    +            - -slack-user=flagger
    +            - -slack-channel=alerts
    +            - -slack-url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
    +EOF
    +

    Install Flagger with Slack:

    kubectl apply -k .
    +

    If you want to use MS Teams instead of Slack, replace -slack-url with -msteams-url and set the webhook address +to https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK.

    + + + diff --git a/install/index.html b/install/index.html new file mode 100644 index 00000000..e2541096 --- /dev/null +++ b/install/index.html @@ -0,0 +1,35 @@ + + + + + + Flagger + + + + + + + + + + + + + + + + + diff --git a/intro/faq.html b/intro/faq.html new file mode 100644 index 00000000..ad689fe1 --- /dev/null +++ b/intro/faq.html @@ -0,0 +1,483 @@ + + + + + + Frequently asked questions | Flagger + + + + + + + + + + + + + +

    Frequently asked questions

    Deployment Strategies

    Which deployment strategies are supported by Flagger?

    Flagger implements the following deployment strategies:

    When should I use A/B testing instead of progressive traffic shifting?

    For frontend applications that require session affinity you should use HTTP headers or cookies match conditions +to ensure a set of users will stay on the same version for the whole duration of the canary analysis.

    Can I use Flagger to manage applications that live outside of a service mesh?

    For applications that are not deployed on a service mesh, Flagger can orchestrate Blue/Green style deployments +with Kubernetes L4 networking.

    When can I use traffic mirroring?

    Traffic mirroring can be used for Blue/Green deployment strategy or a pre-stage in a Canary release. +Traffic mirroring will copy each incoming request, sending one request to the primary and one to the canary service. +Mirroring should be used for requests that are idempotent or capable of being processed twice (once by the primary and once by the canary).

    Kubernetes services

    How is an application exposed inside the cluster?

    Assuming the app name is podinfo you can define a canary like:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  targetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: podinfo
    +  service:
    +    # service name (optional)
    +    name: podinfo
    +    # ClusterIP port number (required)
    +    port: 9898
    +    # container port name or number
    +    targetPort: http
    +    # port name can be http or grpc (default http)
    +    portName: http
    +

    If the service.name is not specified, then targetRef.name is used for the apex domain and canary/primary services name prefix. +You should treat the service name as an immutable field, changing it could result in routing conflicts.

    Based on the canary spec service, Flagger generates the following Kubernetes ClusterIP service:

    • <service.name>.<namespace>.svc.cluster.local
      +selector app=<name>-primary
    • <service.name>-primary.<namespace>.svc.cluster.local
      +selector app=<name>-primary
    • <service.name>-canary.<namespace>.svc.cluster.local
      +selector app=<name>

    This ensures that traffic coming from a namespace outside the mesh to podinfo.test:9898 +will be routed to the latest stable release of your app.

    apiVersion: v1
    +kind: Service
    +metadata:
    +  name: podinfo
    +spec:
    +  type: ClusterIP
    +  selector:
    +    app: podinfo-primary
    +  ports:
    +  - name: http
    +    port: 9898
    +    protocol: TCP
    +    targetPort: http
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +  name: podinfo-primary
    +spec:
    +  type: ClusterIP
    +  selector:
    +    app: podinfo-primary
    +  ports:
    +  - name: http
    +    port: 9898
    +    protocol: TCP
    +    targetPort: http
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +  name: podinfo-canary
    +spec:
    +  type: ClusterIP
    +  selector:
    +    app: podinfo
    +  ports:
    +  - name: http
    +    port: 9898
    +    protocol: TCP
    +    targetPort: http
    +

    The podinfo-canary.test:9898 address is available only during the +canary analysis and can be used for conformance testing or load testing.

    Multiple ports

    My application listens on multiple ports, how can I expose them inside the cluster?

    If port discovery is enabled, Flagger scans the deployment spec and extracts the containers +ports excluding the port specified in the canary service and Envoy sidecar ports. +These ports will be used when generating the ClusterIP services.

    For a deployment that exposes two ports:

    apiVersion: apps/v1
    +kind: Deployment
    +spec:
    +  template:
    +    metadata:
    +      annotations:
    +        prometheus.io/scrape: "true"
    +        prometheus.io/port: "9899"
    +    spec:
    +      containers:
    +      - name: app
    +        ports:
    +        - containerPort: 8080
    +        - containerPort: 9090
    +

    You can enable port discovery so that Prometheus will be able to reach port 9090 over mTLS:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +spec:
    +  service:
    +    # container port used for canary analysis
    +    port: 8080
    +    # port name can be http or grpc (default http)
    +    portName: http
    +    # add all the other container ports
    +    # to the ClusterIP services (default false)
    +    portDiscovery: true
    +    trafficPolicy:
    +      tls:
    +        mode: ISTIO_MUTUAL
    +

    Both port 8080 and 9090 will be added to the ClusterIP services.

    Label selectors

    What labels selectors are supported by Flagger?

    The target deployment must have a single label selector in the format app: <DEPLOYMENT-NAME>:

    apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: podinfo
    +spec:
    +  selector:
    +    matchLabels:
    +      app: podinfo
    +  template:
    +    metadata:
    +      labels:
    +        app: podinfo
    +

    Besides app Flagger supports name and app.kubernetes.io/name selectors. If you use a different +convention you can specify your label with the -selector-labels flag.

    Is pod affinity and anti affinity supported?

    For pod affinity to work you need to use a different label than the app, name or app.kubernetes.io/name.

    Anti affinity example:

    apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: podinfo
    +spec:
    +  selector:
    +    matchLabels:
    +      app: podinfo
    +      affinity: podinfo
    +  template:
    +    metadata:
    +      labels:
    +        app: podinfo
    +        affinity: podinfo
    +    spec:
    +      affinity:
    +        podAntiAffinity:
    +          preferredDuringSchedulingIgnoredDuringExecution:
    +          - weight: 100
    +            podAffinityTerm:
    +              labelSelector:
    +                matchLabels:
    +                  affinity: podinfo
    +              topologyKey: kubernetes.io/hostname
    +

    Metrics

    How does Flagger measures the request success rate and duration?

    Flagger measures the request success rate and duration using Prometheus queries.

    HTTP requests success rate percentage

    Spec:

      analysis:
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +

    Istio query:

    sum(
    +    rate(
    +        istio_requests_total{
    +          reporter="destination",
    +          destination_workload_namespace=~"$namespace",
    +          destination_workload=~"$workload",
    +          response_code!~"5.*"
    +        }[$interval]
    +    )
    +) 
    +/ 
    +sum(
    +    rate(
    +        istio_requests_total{
    +          reporter="destination",
    +          destination_workload_namespace=~"$namespace",
    +          destination_workload=~"$workload"
    +        }[$interval]
    +    )
    +)
    +

    Envoy query (App Mesh, Contour or Gloo):

    sum(
    +    rate(
    +        envoy_cluster_upstream_rq{
    +          kubernetes_namespace="$namespace",
    +          kubernetes_pod_name=~"$workload",
    +          envoy_response_code!~"5.*"
    +        }[$interval]
    +    )
    +) 
    +/ 
    +sum(
    +    rate(
    +        envoy_cluster_upstream_rq{
    +          kubernetes_namespace="$namespace",
    +          kubernetes_pod_name=~"$workload"
    +        }[$interval]
    +    )
    +)
    +

    HTTP requests milliseconds duration P99

    Spec:

      analysis:
    +    metrics:
    +    - name: request-duration
    +      # maximum req duration P99
    +      # milliseconds
    +      thresholdRange:
    +        max: 500
    +      interval: 1m
    +

    Istio query:

    histogram_quantile(0.99, 
    +  sum(
    +    irate(
    +      istio_request_duration_seconds_bucket{
    +        reporter="destination",
    +        destination_workload=~"$workload",
    +        destination_workload_namespace=~"$namespace"
    +      }[$interval]
    +    )
    +  ) by (le)
    +)
    +

    Envoy query (App Mesh, Contour or Gloo):

    histogram_quantile(0.99, 
    +  sum(
    +    irate(
    +      envoy_cluster_upstream_rq_time_bucket{
    +        kubernetes_pod_name=~"$workload",
    +        kubernetes_namespace=~"$namespace"
    +      }[$interval]
    +    )
    +  ) by (le)
    +)
    +

    Note that the metric interval should be lower or equal to the control loop interval.

    Can I use custom metrics?

    The analysis can be extended with metrics provided by Prometheus, Datadog and AWS CloudWatch. For more details +on how custom metrics can be used please read the metrics docs.

    Istio routing

    How does Flagger interact with Istio?

    Flagger creates an Istio Virtual Service and Destination Rules based on the Canary service spec. +The service configuration lets you expose an app inside or outside the mesh. +You can also define traffic policies, HTTP match conditions, URI rewrite rules, CORS policies, timeout and retries.

    The following spec exposes the frontend workload inside the mesh on frontend.test.svc.cluster.local:9898 +and outside the mesh on frontend.example.com. You'll have to specify an Istio ingress gateway for external hosts.

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: frontend
    +  namespace: test
    +spec:
    +  service:
    +    # container port
    +    port: 9898
    +    # service port name (optional, will default to "http")
    +    portName: http-frontend
    +    # Istio gateways (optional)
    +    gateways:
    +    - public-gateway.istio-system.svc.cluster.local
    +    - mesh
    +    # Istio virtual service host names (optional)
    +    hosts:
    +    - frontend.example.com
    +    # Istio traffic policy
    +    trafficPolicy:
    +      tls:
    +        # use ISTIO_MUTUAL when mTLS is enabled
    +        mode: DISABLE
    +    # HTTP match conditions (optional)
    +    match:
    +      - uri:
    +          prefix: /
    +    # HTTP rewrite (optional)
    +    rewrite:
    +      uri: /
    +    # Istio retry policy (optional)
    +    retries:
    +      attempts: 3
    +      perTryTimeout: 1s
    +      retryOn: "gateway-error,connect-failure,refused-stream"
    +    # Add headers (optional)
    +    headers:
    +      request:
    +        add:
    +          x-some-header: "value"
    +    # cross-origin resource sharing policy (optional)
    +    corsPolicy:
    +      allowOrigin:
    +        - example.com
    +      allowMethods:
    +        - GET
    +      allowCredentials: false
    +      allowHeaders:
    +        - x-some-header
    +      maxAge: 24h
    +

    For the above spec Flagger will generate the following virtual service:

    apiVersion: networking.istio.io/v1alpha3
    +kind: VirtualService
    +metadata:
    +  name: frontend
    +  namespace: test
    +  ownerReferences:
    +    - apiVersion: flagger.app/v1beta1
    +      blockOwnerDeletion: true
    +      controller: true
    +      kind: Canary
    +      name: podinfo
    +      uid: 3a4a40dd-3875-11e9-8e1d-42010a9c0fd1
    +spec:
    +  gateways:
    +    - public-gateway.istio-system.svc.cluster.local
    +    - mesh
    +  hosts:
    +    - frontend.example.com
    +    - frontend
    +  http:
    +  - corsPolicy:
    +      allowHeaders:
    +      - x-some-header
    +      allowMethods:
    +      - GET
    +      allowOrigin:
    +      - example.com
    +      maxAge: 24h
    +    headers:
    +      request:
    +        add:
    +          x-some-header: "value"
    +    match:
    +    - uri:
    +        prefix: /
    +    rewrite:
    +      uri: /
    +    route:
    +    - destination:
    +        host: podinfo-primary
    +      weight: 100
    +    - destination:
    +        host: podinfo-canary
    +      weight: 0
    +    retries:
    +      attempts: 3
    +      perTryTimeout: 1s
    +      retryOn: "gateway-error,connect-failure,refused-stream"
    +

    For each destination in the virtual service a rule is generated:

    apiVersion: networking.istio.io/v1alpha3
    +kind: DestinationRule
    +metadata:
    +  name: frontend-primary
    +  namespace: test
    +spec:
    +  host: frontend-primary
    +  trafficPolicy:
    +    tls:
    +      mode: DISABLE
    +---
    +apiVersion: networking.istio.io/v1alpha3
    +kind: DestinationRule
    +metadata:
    +  name: frontend-canary
    +  namespace: test
    +spec:
    +  host: frontend-canary
    +  trafficPolicy:
    +    tls:
    +      mode: DISABLE
    +

    Flagger keeps in sync the virtual service and destination rules with the canary service spec. +Any direct modification to the virtual service spec will be overwritten.

    To expose a workload inside the mesh on http://backend.test.svc.cluster.local:9898, +the service spec can contain only the container port and the traffic policy:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: backend
    +  namespace: test
    +spec:
    +  service:
    +    port: 9898
    +    trafficPolicy:
    +      tls:
    +        mode: DISABLE
    +

    Based on the above spec, Flagger will create several ClusterIP services like:

    apiVersion: v1
    +kind: Service
    +metadata:
    +  name: backend-primary
    +  ownerReferences:
    +  - apiVersion: flagger.app/v1beta1
    +    blockOwnerDeletion: true
    +    controller: true
    +    kind: Canary
    +    name: backend
    +    uid: 2ca1a9c7-2ef6-11e9-bd01-42010a9c0145
    +spec:
    +  type: ClusterIP
    +  ports:
    +  - name: http
    +    port: 9898
    +    protocol: TCP
    +    targetPort: 9898
    +  selector:
    +    app: backend-primary
    +

    Flagger works for user facing apps exposed outside the cluster via an ingress gateway +and for backend HTTP APIs that are accessible only from inside the mesh.

    Istio Ingress Gateway

    How can I expose multiple canaries on the same external domain?

    Assuming you have two apps, one that servers the main website and one that serves the REST API. +For each app you can define a canary object as:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: website
    +spec:
    +  service:
    +    port: 8080
    +    gateways:
    +    - public-gateway.istio-system.svc.cluster.local
    +    hosts:
    +    - my-site.com
    +    match:
    +      - uri:
    +          prefix: /
    +    rewrite:
    +      uri: /
    +---
    +apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: webapi
    +spec:
    +  service:
    +    port: 8080
    +    gateways:
    +    - public-gateway.istio-system.svc.cluster.local
    +    hosts:
    +    - my-site.com
    +    match:
    +      - uri:
    +          prefix: /api
    +    rewrite:
    +      uri: /
    +

    Based on the above configuration, Flagger will create two virtual services bounded to the same ingress gateway and external host. +Istio Pilot will merge +the two services and the website rule will be moved to the end of the list in the merged configuration.

    Note that host merging only works if the canaries are bounded to a ingress gateway other than the mesh gateway.

    Istio Mutual TLS

    How can I enable mTLS for a canary?

    When deploying Istio with global mTLS enabled, you have to set the TLS mode to ISTIO_MUTUAL:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +spec:
    +  service:
    +    trafficPolicy:
    +      tls:
    +        mode: ISTIO_MUTUAL
    +

    If you run Istio in permissive mode you can disable TLS:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +spec:
    +  service:
    +    trafficPolicy:
    +      tls:
    +        mode: DISABLE
    +

    If Flagger is outside of the mesh, how can it start the load test?

    In order for Flagger to be able to call the load tester service from outside the mesh, you need to disable mTLS on port 80:

    apiVersion: networking.istio.io/v1alpha3
    +kind: DestinationRule
    +metadata:
    +  name: flagger-loadtester
    +  namespace: test
    +spec:
    +  host: "flagger-loadtester.test.svc.cluster.local"
    +  trafficPolicy:
    +    tls:
    +      mode: DISABLE
    +---
    +apiVersion: authentication.istio.io/v1alpha1
    +kind: Policy
    +metadata:
    +  name: flagger-loadtester
    +  namespace: test
    +spec:
    +  targets:
    +  - name: flagger-loadtester
    +    ports:
    +    - number: 80
    +
    + + + diff --git a/intro/index.html b/intro/index.html new file mode 100644 index 00000000..ea94770a --- /dev/null +++ b/intro/index.html @@ -0,0 +1,51 @@ + + + + + + Introduction | Flagger + + + + + + + + + + + + + +

    Introduction

    Flagger is a Kubernetes operator that automates the promotion of +canary deployments using Istio, Linkerd, App Mesh, NGINX, Contour or Gloo routing for +traffic shifting and Prometheus metrics for canary analysis. The canary analysis can be extended with webhooks for +running system integration/acceptance tests, load tests, or any other custom validation.

    Flagger implements a control loop that gradually shifts traffic to the canary while measuring key performance indicators +like HTTP requests success rate, requests average duration and pods health. +Based on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack or MS Teams.

    Flagger overview diagram

    Flagger can be configured with Kubernetes custom resources and is compatible with any CI/CD solutions made for Kubernetes. +Since Flagger is declarative and reacts to Kubernetes events, +it can be used in GitOps pipelines together with Flux CD or JenkinsX.

    To get started with Flagger, chose one of the supported routing providers +and install Flagger with Helm or Kustomize.

    After install Flagger, you can follow one of the tutorials:

    Service mesh tutorials

    Ingress controller tutorials

    Hands-on GitOps workshops

    + + + diff --git a/tutorials/appmesh-progressive-delivery.html b/tutorials/appmesh-progressive-delivery.html new file mode 100644 index 00000000..9be64f57 --- /dev/null +++ b/tutorials/appmesh-progressive-delivery.html @@ -0,0 +1,268 @@ + + + + + + App Mesh Canary Deployments | Flagger + + + + + + + + + + + + + +

    App Mesh Canary Deployments

    This guide shows you how to use App Mesh and Flagger to automate canary deployments. You'll need an EKS cluster configured with App Mesh, you can find the install guide here.

    Bootstrap

    Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), then creates a series of objects (Kubernetes deployments, ClusterIP services, App Mesh virtual nodes and services). These objects expose the application on the mesh and drive the canary analysis and promotion. The only App Mesh object you need to create by yourself is the mesh resource.

    Create a mesh called global:

    cat << EOF | kubectl apply -f -
    +apiVersion: appmesh.k8s.aws/v1beta1
    +kind: Mesh
    +metadata:
    +  name: global
    +spec:
    +  serviceDiscoveryType: dns
    +EOF
    +

    Create a test namespace with App Mesh sidecar injection enabled:

    cat << EOF | kubectl apply -f -
    +apiVersion: v1
    +kind: Namespace
    +metadata:
    +  name: test
    +  labels:
    +    appmesh.k8s.aws/sidecarInjectorWebhook: enabled
    +EOF
    +

    Create a deployment and a horizontal pod autoscaler:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Deploy the load testing service to generate traffic during the canary analysis:

    helm upgrade -i flagger-loadtester flagger/loadtester \
    +--namespace=test \
    +--set meshName=global \
    +--set "backends[0]=podinfo.test" \
    +--set "backends[1]=podinfo-canary.test"
    +

    Create a canary custom resource:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  # 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
    +  # HPA reference (optional)
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  service:
    +    # container port
    +    port: 9898
    +    # container port name (optional)
    +    # can be http or grpc
    +    portName: http
    +    # App Mesh reference
    +    meshName: global
    +    # App Mesh ingress (optional)
    +    hosts:
    +      - "*"
    +    # App Mesh ingress timeout (optional)
    +    timeout: 5s
    +    # App Mesh egress (optional) 
    +    backends:
    +      - backend.test
    +    # App Mesh retry policy (optional)
    +    retries:
    +      attempts: 3
    +      perTryTimeout: 5s
    +      retryOn: "gateway-error,client-error,stream-error"
    +  # define the canary analysis timing and KPIs
    +  analysis:
    +    # schedule interval (default 60s)
    +    interval: 1m
    +    # max number of failed metric checks before rollback
    +    threshold: 5
    +    # max traffic percentage routed to canary
    +    # percentage (0-100)
    +    maxWeight: 50
    +    # canary increment step
    +    # percentage (0-100)
    +    stepWeight: 5
    +    # App Mesh Prometheus checks
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      # maximum req duration P99
    +      # milliseconds
    +      thresholdRange:
    +        max: 500
    +      interval: 30s
    +    # testing (optional)
    +    webhooks:
    +    - name: acceptance-test
    +      type: pre-rollout
    +      url: http://flagger-loadtester.test/
    +      timeout: 30s
    +      metadata:
    +        type: bash
    +        cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
    +    - name: load-test
    +      url: http://flagger-loadtester.test/
    +      timeout: 5s
    +      metadata:
    +        cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"
    +

    Save the above resource as podinfo-canary.yaml and then apply it:

    kubectl apply -f ./podinfo-canary.yaml
    +

    After a couple of seconds Flagger will create the canary objects:

    # applied 
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated Kubernetes objects
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +
    +# generated App Mesh objects
    +virtualnode.appmesh.k8s.aws/podinfo
    +virtualnode.appmesh.k8s.aws/podinfo-canary
    +virtualnode.appmesh.k8s.aws/podinfo-primary
    +virtualservice.appmesh.k8s.aws/podinfo.test
    +virtualservice.appmesh.k8s.aws/podinfo-canary.test
    +

    After the boostrap, the podinfo deployment will be scaled to zero and the traffic to podinfo.test will be routed to the primary pods. During the canary analysis, the podinfo-canary.test address can be used to target directly the canary pods.

    The App Mesh specific settings are:

      service:
    +    port: 9898
    +    meshName: global
    +    backends:
    +      - backend1.test
    +      - backend2.test
    +

    App Mesh blocks all egress traffic by default. If your application needs to call another service, you have to create an App Mesh virtual service for it and add the virtual service name to the backend list.

    Setup App Mesh Gateway (optional)

    In order to expose the podinfo app outside the mesh you'll be using an Envoy-powered ingress gateway and an AWS network load balancer. The gateway binds to an internet domain and forwards the calls into the mesh through the App Mesh sidecar. If podinfo becomes unavailable due to a cluster downscaling or a node restart, the gateway will retry the calls for a short period of time.

    Deploy the gateway behind an AWS NLB:

    helm upgrade -i appmesh-gateway flagger/appmesh-gateway \
    +--namespace test \
    +--set mesh.name=global
    +

    Find the gateway public address:

    export URL="http://$(kubectl -n test get svc/appmesh-gateway -ojson | jq -r ".status.loadBalancer.ingress[].hostname")"
    +echo $URL
    +

    Wait for the NLB to become active:

     watch curl -sS $URL
    +

    Open your browser and navigate to the ingress address to access podinfo UI.

    Automated canary promotion

    A canary deployment is triggered by changes in any of the following objects:

    • Deployment PodSpec (container image, command, ports, env, resources, etc)
    • ConfigMaps and Secrets mounted as volumes or mapped to environment variables

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.1
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         0
    +  Phase:                 Succeeded
    +Events:
    + New revision detected! Scaling up podinfo.test
    + Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    + Pre-rollout check acceptance-test passed
    + Advance podinfo.test canary weight 5
    + Advance podinfo.test canary weight 10
    + Advance podinfo.test canary weight 15
    + Advance podinfo.test canary weight 20
    + Advance podinfo.test canary weight 25
    + Advance podinfo.test canary weight 30
    + Advance podinfo.test canary weight 35
    + Advance podinfo.test canary weight 40
    + Advance podinfo.test canary weight 45
    + Advance podinfo.test canary weight 50
    + Copying podinfo.test template spec to podinfo-primary.test
    + Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    + Routing all traffic to primary
    + Promotion completed! Scaling down podinfo.test
    +

    When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    During the analysis the canary’s progress can be monitored with Grafana. The App Mesh dashboard URL is http://localhost:3000/d/flagger-appmesh/appmesh-canary?refresh=10s&orgId=1&var-namespace=test&var-primary=podinfo-primary&var-canary=podinfo

    App Mesh Canary Dashboard

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   15       2019-10-02T14:05:07Z
    +prod        frontend  Succeeded     0        2019-10-02T16:15:07Z
    +prod        backend   Failed        0        2019-10-02T17:05:07Z
    +

    If you’ve enabled the Slack notifications, you should receive the following messages:

    Flagger Slack Notifications

    Automated rollback

    During the canary analysis you can generate HTTP 500 errors or high latency to test if Flagger pauses the rollout.

    Trigger a canary deployment:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.2
    +

    Exec into the load tester pod with:

    kubectl -n test exec -it deploy/flagger-loadtester bash
    +

    Generate HTTP 500 errors:

    hey -z 1m -c 5 -q 5 http://podinfo-canary.test:9898/status/500
    +

    Generate latency:

    watch -n 1 curl http://podinfo-canary.test:9898/delay/1
    +

    When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n appmesh-system logs deploy/flagger -f | jq .msg
    +
    +New revision detected! Starting canary analysis for podinfo.test
    +Pre-rollout check acceptance-test passed
    +Advance podinfo.test canary weight 5
    +Advance podinfo.test canary weight 10
    +Advance podinfo.test canary weight 15
    +Halt podinfo.test advancement success rate 69.17% < 99%
    +Halt podinfo.test advancement success rate 61.39% < 99%
    +Halt podinfo.test advancement success rate 55.06% < 99%
    +Halt podinfo.test advancement request duration 1.20s > 0.5s
    +Halt podinfo.test advancement request duration 1.45s > 0.5s
    +Rolling back podinfo.test failed checks threshold reached 5
    +Canary failed! Scaling down podinfo.test
    +

    If you’ve enabled the Slack notifications, you’ll receive a message if the progress deadline is exceeded, or if the analysis reached the maximum number of failed checks:

    Flagger Slack Notifications

    A/B Testing

    Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions. In an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users. This is particularly useful for frontend applications that require session affinity.

    Flagger A/B Testing Stages

    Edit the canary analysis, remove the max/step weight and add the match conditions and iterations:

      analysis:
    +    interval: 1m
    +    threshold: 5
    +    iterations: 10
    +    match:
    +    - headers:
    +        x-canary:
    +          exact: "insider"
    +    webhooks:
    +    - name: load-test
    +      url: http://flagger-loadtester.test/
    +      metadata:
    +        cmd: "hey -z 1m -q 10 -c 2 -H 'X-Canary: insider' http://podinfo.test:9898/"
    +

    The above configuration will run an analysis for ten minutes targeting users that have a X-Canary: insider header.

    You can also use a HTTP cookie, to target all users with a canary cookie set to insider the match condition should be:

    match:
    +- headers:
    +    cookie:
    +      regex: "^(.*?;)?(canary=insider)(;.*)?$"
    +webhooks:
    +- name: load-test
    +  url: http://flagger-loadtester.test/
    +  metadata:
    +    cmd: "hey -z 1m -q 10 -c 2 -H 'Cookie: canary=insider' http://podinfo.test:9898/"
    +

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.3
    +

    Flagger detects that the deployment revision changed and starts the A/B test:

    kubectl -n appmesh-system logs deploy/flagger -f | jq .msg
    +
    +New revision detected! Starting canary analysis for podinfo.test
    +Advance podinfo.test canary iteration 1/10
    +Advance podinfo.test canary iteration 2/10
    +Advance podinfo.test canary iteration 3/10
    +Advance podinfo.test canary iteration 4/10
    +Advance podinfo.test canary iteration 5/10
    +Advance podinfo.test canary iteration 6/10
    +Advance podinfo.test canary iteration 7/10
    +Advance podinfo.test canary iteration 8/10
    +Advance podinfo.test canary iteration 9/10
    +Advance podinfo.test canary iteration 10/10
    +Copying podinfo.test template spec to podinfo-primary.test
    +Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    +Routing all traffic to primary
    +Promotion completed! Scaling down podinfo.test
    +

    For an in-depth look at the analysis process read the usage docs.

    + + + diff --git a/tutorials/canary-helm-gitops.html b/tutorials/canary-helm-gitops.html new file mode 100644 index 00000000..1cbd0d63 --- /dev/null +++ b/tutorials/canary-helm-gitops.html @@ -0,0 +1,221 @@ + + + + + + Canaries with Helm charts and GitOps | Flagger + + + + + + + + + + + + + +

    Canaries with Helm charts and GitOps

    This guide shows you how to package a web app into a Helm chart, trigger canary deployments on Helm upgrade and automate the chart release process with Weave Flux.

    Packaging

    You'll be using the podinfo chart. This chart packages a web app made with Go, it's configuration, a horizontal pod autoscaler (HPA) and the canary configuration file.

    ├── Chart.yaml
    +├── README.md
    +├── templates
    +│   ├── NOTES.txt
    +│   ├── _helpers.tpl
    +│   ├── canary.yaml
    +│   ├── configmap.yaml
    +│   ├── deployment.yaml
    +│   ├── hpa.yaml
    +│   ├── service.yaml
    +│   └── tests
    +│       ├── test-config.yaml
    +│       └── test-pod.yaml
    +└── values.yaml
    +

    You can find the chart source here.

    Install

    Create a test namespace with Istio sidecar injection enabled:

    export REPO=https://raw.githubusercontent.com/weaveworks/flagger/master
    +
    +kubectl apply -f ${REPO}/artifacts/namespaces/test.yaml
    +

    Add Flagger Helm repository:

    helm repo add flagger https://flagger.app
    +

    Install podinfo with the release name frontend (replace example.com with your own domain):

    helm upgrade -i frontend flagger/podinfo \
    +--namespace test \
    +--set nameOverride=frontend \
    +--set backend=http://backend.test:9898/echo \
    +--set canary.enabled=true \
    +--set canary.istioIngress.enabled=true \
    +--set canary.istioIngress.gateway=public-gateway.istio-system.svc.cluster.local \
    +--set canary.istioIngress.host=frontend.istio.example.com
    +

    Flagger takes a Kubernetes deployment and a horizontal pod autoscaler (HPA), then creates a series of objects (Kubernetes deployments, ClusterIP services and Istio virtual services). These objects expose the application on the mesh and drive the canary analysis and promotion.

    # generated by Helm 
    +configmap/frontend
    +deployment.apps/frontend
    +horizontalpodautoscaler.autoscaling/frontend
    +canary.flagger.app/frontend
    +
    +# generated by Flagger
    +configmap/frontend-primary
    +deployment.apps/frontend-primary
    +horizontalpodautoscaler.autoscaling/frontend-primary
    +service/frontend
    +service/frontend-canary
    +service/frontend-primary
    +virtualservice.networking.istio.io/frontend
    +

    When the frontend-primary deployment comes online, Flagger will route all traffic to the primary pods and scale to zero the frontend deployment.

    Open your browser and navigate to the frontend URL:

    Podinfo Frontend

    Now let's install the backend release without exposing it outside the mesh:

    helm upgrade -i backend flagger/podinfo \
    +--namespace test \
    +--set nameOverride=backend \
    +--set canary.enabled=true \
    +--set canary.istioIngress.enabled=false
    +

    Check if Flagger has successfully deployed the canaries:

    kubectl -n test get canaries
    +
    +NAME       STATUS        WEIGHT   LASTTRANSITIONTIME
    +backend    Initialized   0        2019-02-12T18:53:18Z
    +frontend   Initialized   0        2019-02-12T17:50:50Z
    +

    Click on the ping button in the frontend UI to trigger a HTTP POST request that will reach the backend app:

    Jaeger Tracing

    We'll use the /echo endpoint (same as the one the ping button calls) to generate load on both apps during a canary deployment.

    Upgrade

    First let's install a load testing service that will generate traffic during analysis:

    helm upgrade -i flagger-loadtester flagger/loadtester \
    +--namespace=test
    +

    Install Flagger's helm test runner in the kube-system using tiller service account:

    helm upgrade -i flagger-helmtester flagger/loadtester \
    +--namespace=kube-system \
    +--set serviceAccountName=tiller
    +

    Enable the load and helm tester and deploy a new frontend version:

    helm upgrade -i frontend flagger/podinfo/ \
    +--namespace test \
    +--reuse-values \
    +--set canary.loadtest.enabled=true \
    +--set canary.helmtest.enabled=true \
    +--set image.tag=3.1.1
    +

    Flagger detects that the deployment revision changed and starts the canary analysis:

    kubectl -n istio-system logs deployment/flagger -f | jq .msg
    +
    +New revision detected! Scaling up frontend.test
    +Halt advancement frontend.test waiting for rollout to finish: 0 of 2 updated replicas are available
    +Starting canary analysis for frontend.test
    +Pre-rollout check helm test passed
    +Advance frontend.test canary weight 5
    +Advance frontend.test canary weight 10
    +Advance frontend.test canary weight 15
    +Advance frontend.test canary weight 20
    +Advance frontend.test canary weight 25
    +Advance frontend.test canary weight 30
    +Advance frontend.test canary weight 35
    +Advance frontend.test canary weight 40
    +Advance frontend.test canary weight 45
    +Advance frontend.test canary weight 50
    +Copying frontend.test template spec to frontend-primary.test
    +Halt advancement frontend-primary.test waiting for rollout to finish: 1 old replicas are pending termination
    +Promotion completed! Scaling down frontend.test
    +

    You can monitor the canary deployment with Grafana. Open the Flagger dashboard, select test from the namespace dropdown, frontend-primary from the primary dropdown and frontend from the canary dropdown.

    Flagger Grafana Dashboard

    Now trigger a canary deployment for the backend app, but this time you'll change a value in the configmap:

    helm upgrade -i backend flagger/podinfo/ \
    +--namespace test \
    +--reuse-values \
    +--set canary.loadtest.enabled=true \
    +--set canary.helmtest.enabled=true \
    +--set httpServer.timeout=25s
    +

    Generate HTTP 500 errors:

    kubectl -n test exec -it flagger-loadtester-xxx-yyy sh
    +
    +watch curl http://backend-canary:9898/status/500
    +

    Generate latency:

    kubectl -n test exec -it flagger-loadtester-xxx-yyy sh
    +
    +watch curl http://backend-canary:9898/delay/1
    +

    Flagger detects the config map change and starts a canary analysis. Flagger will pause the advancement when the HTTP success rate drops under 99% or when the average request duration in the last minute is over 500ms:

    kubectl -n test describe canary backend
    +
    +Events:
    +
    +ConfigMap backend has changed
    +New revision detected! Scaling up backend.test
    +Starting canary analysis for backend.test
    +Advance backend.test canary weight 5
    +Advance backend.test canary weight 10
    +Advance backend.test canary weight 15
    +Advance backend.test canary weight 20
    +Advance backend.test canary weight 25
    +Advance backend.test canary weight 30
    +Advance backend.test canary weight 35
    +Halt backend.test advancement success rate 62.50% < 99%
    +Halt backend.test advancement success rate 88.24% < 99%
    +Advance backend.test canary weight 40
    +Advance backend.test canary weight 45
    +Halt backend.test advancement request duration 2.415s > 500ms
    +Halt backend.test advancement request duration 2.42s > 500ms
    +Advance backend.test canary weight 50
    +ConfigMap backend-primary synced
    +Copying backend.test template spec to backend-primary.test
    +Promotion completed! Scaling down backend.test
    +

    Flagger Grafana Dashboard

    If the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n test get canary
    +
    +NAME       STATUS        WEIGHT   LASTTRANSITIONTIME
    +backend    Succeeded     0        2019-02-12T19:33:11Z
    +frontend   Failed        0        2019-02-12T19:47:20Z
    +

    If you've enabled the Slack notifications, you'll receive an alert with the reason why the backend promotion failed.

    GitOps automation

    Instead of using Helm CLI from a CI tool to perform the install and upgrade, you could use a Git based approach. GitOps is a way to do Continuous Delivery, it works by using Git as a source of truth for declarative infrastructure and workloads. In the GitOps model, any change to production must be committed in source control prior to being applied on the cluster. This way rollback and audit logs are provided by Git.

    Helm GitOps Canary Deployment

    In order to apply the GitOps pipeline model to Flagger canary deployments you'll need a Git repository with your workloads definitions in YAML format, a container registry where your CI system pushes immutable images and an operator that synchronizes the Git repo with the cluster state.

    Create a git repository with the following content:

    ├── namespaces
    +│   └── test.yaml
    +└── releases
    +    └── test
    +        ├── backend.yaml
    +        ├── frontend.yaml
    +        ├── loadtester.yaml
    +        └── helmtester.yaml
    +

    Define the frontend release using Flux HelmRelease custom resource:

    apiVersion: flux.weave.works/v1beta1
    +kind: HelmRelease
    +metadata:
    +  name: frontend
    +  namespace: test
    +  annotations:
    +    fluxcd.io/automated: "true"
    +    filter.fluxcd.io/chart-image: semver:~3.1
    +spec:
    +  releaseName: frontend
    +  chart:
    +    git: https://github.com/weaveowrks/flagger
    +    ref: master
    +    path: charts/podinfo
    +  values:
    +    image:
    +      repository: stefanprodan/podinfo
    +      tag: 3.1.0
    +      backend: http://backend-podinfo:9898/echo
    +      canary:
    +        enabled: true
    +        istioIngress:
    +          enabled: true
    +          gateway: public-gateway.istio-system.svc.cluster.local
    +          host: frontend.istio.example.com
    +        loadtest:
    +          enabled: true
    +        helmtest:
    +          enabled: true
    +

    In the chart section I've defined the release source by specifying the Helm repository (hosted on GitHub Pages), +chart name and version. In the values section I've overwritten the defaults set in values.yaml.

    With the fluxcd.io annotations I instruct Flux to automate this release. +When an image tag in the sem ver range of 3.1.0 - 3.1.99 is pushed to Docker Hub, +Flux will upgrade the Helm release and from there Flagger will pick up the change and start a canary deployment.

    Install Flux and its +Helm Operator by specifying your Git repo URL:

    helm repo add fluxcd https://charts.fluxcd.io
    +
    +helm install --name flux \
    +--set git.url=git@github.com:<USERNAME>/<REPOSITORY> \
    +--namespace fluxcd \
    +fluxcd/flux
    +
    +helm upgrade -i helm-operator fluxcd/helm-operator \
    +--namespace fluxcd \
    +--set git.ssh.secretName=flux-git-deploy
    +

    At startup Flux generates a SSH key and logs the public key. Find the SSH public key with:

    kubectl -n fluxcd logs deployment/flux | grep identity.pub | cut -d '"' -f2
    +

    In order to sync your cluster state with Git you need to copy the public key +and create a deploy key with write access on your GitHub repository.

    Open GitHub, navigate to your fork, go to Setting > Deploy keys click on Add deploy key, check Allow write access, +paste the Flux public key and click Add key.

    After a couple of seconds Flux will apply the Kubernetes resources from Git and +Flagger will launch the frontend and backend apps.

    A CI/CD pipeline for the frontend release could look like this:

    • cut a release from the master branch of the podinfo code repo with the git tag 3.1.1
    • CI builds the image and pushes the podinfo:3.1.1 image to the container registry
    • Flux scans the registry and updates the Helm release image.tag to 3.1.1
    • Flux commits and push the change to the cluster repo
    • Flux applies the updated Helm release on the cluster
    • Flux Helm Operator picks up the change and calls Tiller to upgrade the release
    • Flagger detects a revision change and scales up the frontend deployment
    • Flagger runs the helm test before routing traffic to the canary service
    • Flagger starts the load test and runs the canary analysis
    • Based on the analysis result the canary deployment is promoted to production or rolled back
    • Flagger sends a Slack or MS Teams notification with the canary result

    If the canary fails, fix the bug, do another patch release eg 3.1.2 and the whole process will run again.

    A canary deployment can fail due to any of the following reasons:

    • the container image can't be downloaded
    • the deployment replica set is stuck for more then ten minutes (eg. due to a container crash loop)
    • the webooks (acceptance tests, helm tests, load tests, etc) are returning a non 2xx response
    • the HTTP success rate (non 5xx responses) metric drops under the threshold
    • the HTTP average duration metric goes over the threshold
    • the Istio telemetry service is unable to collect traffic metrics
    • the metrics server (Prometheus) can't be reached

    If you want to find out more about managing Helm releases with Flux here are two in-depth guides: +gitops-helm +and gitops-istio.

    + + + diff --git a/tutorials/contour-progressive-delivery.html b/tutorials/contour-progressive-delivery.html new file mode 100644 index 00000000..44775882 --- /dev/null +++ b/tutorials/contour-progressive-delivery.html @@ -0,0 +1,273 @@ + + + + + + Contour Canary Deployments | Flagger + + + + + + + + + + + + + +

    Contour Canary Deployments

    This guide shows you how to use Contour ingress controller and Flagger to automate canary releases and A/B testing.

    Flagger Contour Overview

    Prerequisites

    Flagger requires a Kubernetes cluster v1.11 or newer and Contour v1.0 or newer.

    Install Contour on a cluster with LoadBalancer support:

    kubectl apply -f https://projectcontour.io/quickstart/contour.yaml
    +

    The above command will deploy Contour and an Envoy daemonset in the projectcontour namespace.

    Install Flagger using Kustomize (kubectl 1.14) in the projectcontour namespace:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/contour
    +

    The above command will deploy Flagger and Prometheus configured to scrape the Contour's Envoy instances.

    Or you can install Flagger using Helm:

    helm repo add flagger https://flagger.app
    +
    +helm upgrade -i flagger flagger/flagger \
    +--namespace projectcontour \
    +--set meshProvider=contour \
    +--set prometheus.install=true
    +

    You can also enable Slack, Discord, Rocket or MS Teams notifications, +see the alerting docs.

    Bootstrap

    Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), +then creates a series of objects (Kubernetes deployments, ClusterIP services and Contour HTTPProxy). +These objects expose the application in the cluster and drive the canary analysis and promotion.

    Create a test namespace:

    kubectl create ns test
    +

    Install the load testing service to generate traffic during the canary analysis:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/tester
    +

    Create a deployment and a horizontal pod autoscaler:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Create a canary custom resource (replace app.example.com with your own domain):

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  # deployment reference
    +  targetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: podinfo
    +  # HPA reference
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  service:
    +    # service port
    +    port: 80
    +    # container port
    +    targetPort: 9898
    +    # Contour request timeout
    +    timeout: 15s
    +    # Contour retry policy
    +    retries:
    +      attempts: 3
    +      perTryTimeout: 5s
    +  # define the canary analysis timing and KPIs
    +  analysis:
    +    # schedule interval (default 60s)
    +    interval: 30s
    +    # max number of failed metric checks before rollback
    +    threshold: 5
    +    # max traffic percentage routed to canary
    +    # percentage (0-100)
    +    maxWeight: 50
    +    # canary increment step
    +    # percentage (0-100)
    +    stepWeight: 5
    +    # Contour Prometheus checks
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      # maximum req duration P99 in milliseconds
    +      thresholdRange:
    +        max: 500
    +      interval: 30s
    +    # testing
    +    webhooks:
    +    - name: acceptance-test
    +      type: pre-rollout
    +      url: http://flagger-loadtester.test/
    +      timeout: 30s
    +      metadata:
    +        type: bash
    +        cmd: "curl -sd 'test' http://podinfo-canary.test/token | grep token"
    +    - name: load-test
    +      url: http://flagger-loadtester.test/
    +      type: rollout
    +      timeout: 5s
    +      metadata:
    +        cmd: "hey -z 1m -q 10 -c 2 -host app.example.com http://envoy.projectcontour"
    +

    Save the above resource as podinfo-canary.yaml and then apply it:

    kubectl apply -f ./podinfo-canary.yaml
    +

    The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every half a minute.

    After a couple of seconds Flagger will create the canary objects:

    # applied 
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +httpproxy.projectcontour.io/podinfo
    +

    After the boostrap, the podinfo deployment will be scaled to zero and the traffic to podinfo.test +will be routed to the primary pods. +During the canary analysis, the podinfo-canary.test address can be used to target directly the canary pods.

    Expose the app outside the cluster

    Find the external address of Contour's Envoy load balancer:

    export ADDRESS="$(kubectl -n projectcontour get svc/envoy -ojson \
    +| jq -r ".status.loadBalancer.ingress[].hostname")"
    +echo $ADDRESS
    +

    Configure your DNS server with a CNAME record (AWS) or A record (GKE/AKS/DOKS) +and point a domain e.g. app.example.com to the LB address.

    Create a HTTPProxy definition and include the podinfo proxy generated by Flagger +(replace app.example.com with your own domain):

    apiVersion: projectcontour.io/v1
    +kind: HTTPProxy
    +metadata:
    +  name: podinfo-ingress
    +  namespace: test
    +spec:
    +  virtualhost:
    +    fqdn: app.example.com
    +  includes:
    +    - name: podinfo
    +      namespace: test
    +      conditions:
    +        - prefix: /
    +

    Save the above resource as podinfo-ingress.yaml and then apply it:

    kubectl apply -f ./podinfo-ingress.yaml
    +

    Verify that Contour processed the proxy definition with:

    kubectl -n test get httpproxies
    +
    +NAME              FQDN                STATUS
    +podinfo                               valid
    +podinfo-ingress   app.example.com     valid
    +

    Now you can access podinfo UI using your domain address.

    Note that you should be using HTTPS when exposing production workloads on internet. +You can obtain free TLS certs from Let's Encrypt, read this guide +on how to configure cert-manager to secure Contour with TLS certificates.

    Automated canary promotion

    Flagger implements a control loop that gradually shifts traffic to the canary while measuring +key performance indicators like HTTP requests success rate, requests average duration and pod health. +Based on analysis of the KPIs a canary is promoted or aborted.

    Flagger Canary Stages

    A canary deployment is triggered by changes in any of the following objects:

    • Deployment PodSpec (container image, command, ports, env, resources, etc)
    • ConfigMaps and Secrets mounted as volumes or mapped to environment variables

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.1
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         0
    +  Phase:                 Succeeded
    +Events:
    + New revision detected! Scaling up podinfo.test
    + Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    + Pre-rollout check acceptance-test passed
    + Advance podinfo.test canary weight 5
    + Advance podinfo.test canary weight 10
    + Advance podinfo.test canary weight 15
    + Advance podinfo.test canary weight 20
    + Advance podinfo.test canary weight 25
    + Advance podinfo.test canary weight 30
    + Advance podinfo.test canary weight 35
    + Advance podinfo.test canary weight 40
    + Advance podinfo.test canary weight 45
    + Advance podinfo.test canary weight 50
    + Copying podinfo.test template spec to podinfo-primary.test
    + Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    + Routing all traffic to primary
    + Promotion completed! Scaling down podinfo.test
    +

    When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   15       2019-12-20T14:05:07Z
    +

    If you’ve enabled the Slack notifications, you should receive the following messages:

    Flagger Slack Notifications

    Automated rollback

    During the canary analysis you can generate HTTP 500 errors or high latency to test if Flagger pauses the rollout.

    Trigger a canary deployment:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.2
    +

    Exec into the load tester pod with:

    kubectl -n test exec -it deploy/flagger-loadtester bash
    +

    Generate HTTP 500 errors:

    hey -z 1m -c 5 -q 5 http://app.example.com/status/500
    +

    Generate latency:

    watch -n 1 curl http://app.example.com/delay/1
    +

    When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, +the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n projectcontour logs deploy/flagger -f | jq .msg
    +
    +New revision detected! Starting canary analysis for podinfo.test
    +Pre-rollout check acceptance-test passed
    +Advance podinfo.test canary weight 5
    +Advance podinfo.test canary weight 10
    +Advance podinfo.test canary weight 15
    +Halt podinfo.test advancement success rate 69.17% < 99%
    +Halt podinfo.test advancement success rate 61.39% < 99%
    +Halt podinfo.test advancement success rate 55.06% < 99%
    +Halt podinfo.test advancement request duration 1.20s > 500ms
    +Halt podinfo.test advancement request duration 1.45s > 500ms
    +Rolling back podinfo.test failed checks threshold reached 5
    +Canary failed! Scaling down podinfo.test
    +

    If you’ve enabled the Slack notifications, you’ll receive a message if the progress deadline is exceeded, +or if the analysis reached the maximum number of failed checks:

    Flagger Slack Notifications

    A/B Testing

    Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions. +In an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users. +This is particularly useful for frontend applications that require session affinity.

    Flagger A/B Testing Stages

    Edit the canary analysis, remove the max/step weight and add the match conditions and iterations:

    analysis:
    +  interval: 1m
    +  threshold: 5
    +  iterations: 10
    +  match:
    +  - headers:
    +      x-canary:
    +        exact: "insider"
    +  webhooks:
    +  - name: load-test
    +    url: http://flagger-loadtester.test/
    +    metadata:
    +      cmd: "hey -z 1m -q 5 -c 5 -H 'X-Canary: insider' -host app.example.com http://envoy.projectcontour"
    +

    The above configuration will run an analysis for ten minutes targeting users that have a X-Canary: insider header.

    You can also use a HTTP cookie. To target all users with a cookie set to insider, the match condition should be:

    match:
    +- headers:
    +    cookie:
    +      suffix: "insider"
    +webhooks:
    +- name: load-test
    +  url: http://flagger-loadtester.test/
    +  metadata:
    +    cmd: "hey -z 1m -q 5 -c 5 -H 'Cookie: canary=insider' -host app.example.com http://envoy.projectcontour"
    +

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.3
    +

    Flagger detects that the deployment revision changed and starts the A/B test:

    kubectl -n projectcontour logs deploy/flagger -f | jq .msg
    +
    +New revision detected! Starting canary analysis for podinfo.test
    +Advance podinfo.test canary iteration 1/10
    +Advance podinfo.test canary iteration 2/10
    +Advance podinfo.test canary iteration 3/10
    +Advance podinfo.test canary iteration 4/10
    +Advance podinfo.test canary iteration 5/10
    +Advance podinfo.test canary iteration 6/10
    +Advance podinfo.test canary iteration 7/10
    +Advance podinfo.test canary iteration 8/10
    +Advance podinfo.test canary iteration 9/10
    +Advance podinfo.test canary iteration 10/10
    +Copying podinfo.test template spec to podinfo-primary.test
    +Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    +Routing all traffic to primary
    +Promotion completed! Scaling down podinfo.test
    +

    The web browser user agent header allows user segmentation based on device or OS.

    For example, if you want to route all mobile users to the canary instance:

    match:
    +- headers:
    +    user-agent:
    +      prefix: "Mobile"
    +

    Or if you want to target only Android users:

    match:
    +- headers:
    +    user-agent:
    +      prefix: "Android"
    +

    Or a specific browser version:

    match:
    +- headers:
    +    user-agent:
    +      suffix: "Firefox/71.0"
    +

    For an in-depth look at the analysis process read the usage docs.

    + + + diff --git a/tutorials/crossover-progressive-delivery.html b/tutorials/crossover-progressive-delivery.html new file mode 100644 index 00000000..f7de19a7 --- /dev/null +++ b/tutorials/crossover-progressive-delivery.html @@ -0,0 +1,215 @@ + + + + + + Crossover Canary Deployments | Flagger + + + + + + + + + + + + + +

    Crossover Canary Deployments

    This guide shows you how to use Envoy, Crossover and Flagger to automate canary deployments.

    Crossover is a minimal Envoy xDS implementation supports Service Mesh Interface.

    Prerequisites

    Flagger requires a Kubernetes cluster v1.11 or newer and Envoy paired with Crossover sidecar.

    Create a test namespace:

    kubectl create ns test
    +

    Install Envoy along with the Crossover sidecar with Helm:

    helm repo add crossover https://mumoshu.github.io/crossover
    +
    +helm upgrade --install envoy crossover/envoy \
    +  --namespace test \
    +  -f <(cat <<EOF
    +smi:
    +  apiVersions:
    +    trafficSplits: v1alpha1
    +upstreams:
    +  podinfo:
    +    smi:
    +      enabled: true
    +    backends:
    +      podinfo-primary:
    +        port: 9898
    +        weight: 100
    +      podinfo-canary:
    +        port: 9898
    +        weight: 0
    +EOF
    +)
    +

    Install Flagger and the Prometheus add-on in the same namespace as Envoy:

    helm repo add flagger https://flagger.app
    +
    +helm upgrade -i flagger flagger/flagger \
    +--namespace test \
    +--set prometheus.install=true \
    +--set meshProvider=smi:crossover
    +

    Optionally you can enable Slack notifications:

    helm upgrade -i flagger flagger/flagger \
    +--reuse-values \
    +--namespace test \
    +--set slack.url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
    +--set slack.channel=general \
    +--set slack.user=flagger
    +

    Bootstrap

    Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), +then creates a series of objects (Kubernetes deployments, ClusterIP services, SMI traffic splits). +These objects expose the application on the mesh and drive the canary analysis and promotion. +There's no SMI object you need to create by yourself.

    Create a deployment and a horizontal pod autoscaler:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Deploy the load testing service to generate traffic during the canary analysis:

    helm upgrade -i flagger-loadtester flagger/loadtester \
    +--namespace=test
    +

    Create a canary custom resource:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  # specify mesh provider if it isn't the default one
    +  # provider: "smi:crossover"
    +  # 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
    +  # HPA reference (optional)
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  service:
    +    # ClusterIP port number
    +    port: 9898
    +    # container port number or name (optional)
    +    targetPort: 9898
    +  # define the canary analysis timing and KPIs
    +  analysis:
    +    # schedule interval (default 60s)
    +    interval: 1m
    +    # max number of failed metric checks before rollback
    +    threshold: 5
    +    # max traffic percentage routed to canary
    +    # percentage (0-100)
    +    maxWeight: 50
    +    # canary increment step
    +    # percentage (0-100)
    +    stepWeight: 5
    +    # App Mesh Prometheus checks
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      # maximum req duration P99
    +      # milliseconds
    +      thresholdRange:
    +        max: 500
    +      interval: 30s
    +    # testing (optional)
    +    webhooks:
    +    - name: acceptance-test
    +      type: pre-rollout
    +      url: http://flagger-loadtester.test/
    +      timeout: 30s
    +      metadata:
    +        type: bash
    +        cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
    +    - name: load-test
    +      url: http://flagger-loadtester.test/
    +      timeout: 5s
    +      metadata:
    +        cmd: "hey -z 1m -q 10 -c 2 -H 'Host: podinfo.test' http://envoy.test:10000/"
    +

    Save the above resource as podinfo-canary.yaml and then apply it:

    kubectl apply -f ./podinfo-canary.yaml
    +

    After a couple of seconds Flagger will create the canary objects:

    # applied 
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +trafficsplits.split.smi-spec.io/podinfo
    +

    After the boostrap, the podinfo deployment will be scaled to zero and the traffic to podinfo.test +will be routed to the primary pods. During the canary analysis, +the podinfo-canary.test address can be used to target directly the canary pods.

    Automated canary promotion

    Flagger implements a control loop that gradually shifts traffic to the canary while measuring +key performance indicators like HTTP requests success rate, requests average duration and pod health. +Based on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack.

    Flagger Canary Stages

    A canary deployment is triggered by changes in any of the following objects:

    • Deployment PodSpec (container image, command, ports, env, resources, etc)
    • ConfigMaps and Secrets mounted as volumes or mapped to environment variables

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.5
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         0
    +  Phase:                 Succeeded
    +Events:
    + New revision detected! Scaling up podinfo.test
    + Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    + Pre-rollout check acceptance-test passed
    + Advance podinfo.test canary weight 5
    + Advance podinfo.test canary weight 10
    + Advance podinfo.test canary weight 15
    + Advance podinfo.test canary weight 20
    + Advance podinfo.test canary weight 25
    + Advance podinfo.test canary weight 30
    + Advance podinfo.test canary weight 35
    + Advance podinfo.test canary weight 40
    + Advance podinfo.test canary weight 45
    + Advance podinfo.test canary weight 50
    + Copying podinfo.test template spec to podinfo-primary.test
    + Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    + Routing all traffic to primary
    + Promotion completed! Scaling down podinfo.test
    +

    When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    During the analysis the canary’s progress can be monitored with Grafana.

    Flagger comes with a Grafana dashboard made for canary analysis. Install Grafana with Helm:

    helm upgrade -i flagger-grafana flagger/grafana \
    +--namespace=test \
    +--set url=http://flagger-prometheus:9090
    +

    Run:

    kubectl port-forward --namespace test svc/flagger-grafana 3000:80
    +

    The Envoy dashboard URL is http://localhost:3000/d/flagger-envoy/envoy-canary?refresh=10s&orgId=1&var-namespace=test&var-target=podinfo

    Envoy Canary Dashboard

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   15       2019-10-02T14:05:07Z
    +prod        frontend  Succeeded     0        2019-10-02T16:15:07Z
    +prod        backend   Failed        0        2019-10-02T17:05:07Z
    +

    If you’ve enabled the Slack notifications, you should receive the following messages:

    Flagger Slack Notifications

    Automated rollback

    During the canary analysis you can generate HTTP 500 errors or high latency to test if Flagger pauses the rollout.

    Trigger a canary deployment:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.2
    +

    Exec into the load tester pod with:

    kubectl -n test exec -it deploy/flagger-loadtester bash
    +

    Generate HTTP 500 errors:

    hey -z 1m -c 5 -q 5 -H 'Host: podinfo.test' http://envoy.test:10000/status/500
    +

    Generate latency:

    watch -n 1 curl -H 'Host: podinfo.test' http://envoy.test:10000/delay/1
    +

    When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, +the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n test logs deploy/flagger -f | jq .msg
    +
    +New revision detected! Starting canary analysis for podinfo.test
    +Pre-rollout check acceptance-test passed
    +Advance podinfo.test canary weight 5
    +Advance podinfo.test canary weight 10
    +Advance podinfo.test canary weight 15
    +Halt podinfo.test advancement success rate 69.17% < 99%
    +Halt podinfo.test advancement success rate 61.39% < 99%
    +Halt podinfo.test advancement success rate 55.06% < 99%
    +Halt podinfo.test advancement request duration 1.20s > 0.5s
    +Halt podinfo.test advancement request duration 1.45s > 0.5s
    +Rolling back podinfo.test failed checks threshold reached 5
    +Canary failed! Scaling down podinfo.test
    +

    If you’ve enabled the Slack notifications, you’ll receive a message if the progress deadline is exceeded, +or if the analysis reached the maximum number of failed checks:

    Flagger Slack Notifications

    + + + diff --git a/tutorials/flagger-smi-istio.html b/tutorials/flagger-smi-istio.html new file mode 100644 index 00000000..0c25b20e --- /dev/null +++ b/tutorials/flagger-smi-istio.html @@ -0,0 +1,192 @@ + + + + + + SMI Istio Canary Deployments | Flagger + + + + + + + + + + + + + +

    SMI Istio Canary Deployments

    This guide shows you how to use the SMI Istio adapter and Flagger to automate canary deployments.

    Prerequisites

    • Kubernetes > 1.13
    • Istio > 1.0

    Install Istio SMI adapter

    Install the SMI adapter:

    kubectl apply -f https://raw.githubusercontent.com/deislabs/smi-adapter-istio/master/deploy/crds/crds.yaml
    +kubectl apply -f https://raw.githubusercontent.com/deislabs/smi-adapter-istio/master/deploy/operator-and-rbac.yaml
    +

    Create a generic Istio gateway to expose services outside the mesh on HTTP:

    apiVersion: networking.istio.io/v1alpha3
    +kind: Gateway
    +metadata:
    +  name: public-gateway
    +  namespace: istio-system
    +spec:
    +  selector:
    +    istio: ingressgateway
    +  servers:
    +    - port:
    +        number: 80
    +        name: http
    +        protocol: HTTP
    +      hosts:
    +        - "*"
    +

    Save the above resource as public-gateway.yaml and then apply it:

    kubectl apply -f ./public-gateway.yaml
    +

    Find the Gateway load balancer IP and add a DNS record for it:

    kubectl -n istio-system get svc/istio-ingressgateway -ojson | jq -r .status.loadBalancer.ingress[0].ip
    +

    Install Flagger and Grafana

    Add Flagger Helm repository:

    helm repo add flagger https://flagger.app
    +

    Deploy Flagger in the istio-system namespace:

    helm upgrade -i flagger flagger/flagger \
    +--namespace=istio-system \
    +--set meshProvider=smi:istio
    +

    Flagger comes with a Grafana dashboard made for monitoring the canary deployments.

    Deploy Grafana in the istio-system namespace:

    helm upgrade -i flagger-grafana flagger/grafana \
    +--namespace=istio-system \
    +--set url=http://prometheus.istio-system:9090
    +

    You can access Grafana using port forwarding:

    kubectl -n istio-system port-forward svc/flagger-grafana 3000:80
    +

    Workloads bootstrap

    Create a test namespace with Istio sidecar injection enabled:

    Create a test namespace and enable Linkerd proxy injection:

    kubectl create ns test
    +kubectl label namespace test istio-injection=enabled
    +

    Create a deployment and a horizontal pod autoscaler:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Deploy the load testing service to generate traffic during the canary analysis:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/tester
    +

    Create a canary custom resource (replace example.com with your own domain):

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  # 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
    +  # HPA reference (optional)
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  service:
    +    # container port
    +    port: 9898
    +    # Istio gateways (optional)
    +    gateways:
    +    - public-gateway.istio-system.svc.cluster.local
    +    # Istio virtual service host names (optional)
    +    hosts:
    +    - app.example.com
    +  analysis:
    +    # schedule interval (default 60s)
    +    interval: 10s
    +    # max number of failed metric checks before rollback
    +    threshold: 5
    +    # max traffic percentage routed to canary
    +    # percentage (0-100)
    +    maxWeight: 50
    +    # canary increment step
    +    # percentage (0-100)
    +    stepWeight: 10
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      # maximum req duration P99
    +      # milliseconds
    +      thresholdRange:
    +        max: 500
    +      interval: 30s
    +    # generate traffic during analysis
    +    webhooks:
    +      - name: load-test
    +        url: http://flagger-loadtester.test/
    +        timeout: 5s
    +        metadata:
    +          cmd: "hey -z 1m -q 10 -c 2 http://podinfo.test:9898/"
    +

    Save the above resource as podinfo-canary.yaml and then apply it:

    kubectl apply -f ./podinfo-canary.yaml
    +

    After a couple of seconds Flagger will create the canary objects:

    # applied 
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated 
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +trafficsplits.split.smi-spec.io/podinfo
    +

    Automated canary promotion

    Flagger implements a control loop that gradually shifts traffic to the canary while measuring key performance indicators like HTTP requests success rate, requests average duration and pod health. Based on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack.

    Flagger Canary Stages

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=quay.io/stefanprodan/podinfo:3.1.1
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n istio-system logs deployment/flagger -f | jq .msg
    +
    +
    +New revision detected podinfo.test
    +Scaling up podinfo.test
    +Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    +Advance podinfo.test canary weight 5
    +Advance podinfo.test canary weight 10
    +Advance podinfo.test canary weight 15
    +Advance podinfo.test canary weight 20
    +Advance podinfo.test canary weight 25
    +Advance podinfo.test canary weight 30
    +Advance podinfo.test canary weight 35
    +Advance podinfo.test canary weight 40
    +Advance podinfo.test canary weight 45
    +Advance podinfo.test canary weight 50
    +Copying podinfo.test template spec to podinfo-primary.test
    +Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    +Promotion completed! Scaling down podinfo.test
    +

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    During the analysis the canary’s progress can be monitored with Grafana. The Istio dashboard URL is http://localhost:3000/d/flagger-istio/istio-canary?refresh=10s&orgId=1&var-namespace=test&var-primary=podinfo-primary&var-canary=podinfo

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   15       2019-05-16T14:05:07Z
    +prod        frontend  Succeeded     0        2019-05-15T16:15:07Z
    +prod        backend   Failed        0        2019-05-14T17:05:07Z
    +

    Automated rollback

    During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses the rollout.

    Create a tester pod and exec into it:

    kubectl -n test run tester \
    +--image=quay.io/stefanprodan/podinfo:3.1.2 \
    +-- ./podinfo --port=9898
    +
    +kubectl -n test exec -it tester-xx-xx sh
    +

    Generate HTTP 500 errors:

    watch curl http://podinfo-canary:9898/status/500
    +

    Generate latency:

    watch curl http://podinfo-canary:9898/delay/1
    +

    When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         10
    +  Phase:                 Failed
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  Starting canary deployment for podinfo.test
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 5
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 15
    +  Normal   Synced  3m    flagger  Halt podinfo.test advancement success rate 69.17% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 61.39% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 55.06% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 47.00% < 99%
    +  Normal   Synced  2m    flagger  (combined from similar events): Halt podinfo.test advancement success rate 38.08% < 99%
    +  Warning  Synced  1m    flagger  Rolling back podinfo.test failed checks threshold reached 10
    +  Warning  Synced  1m    flagger  Canary failed! Scaling down podinfo.test
    +
    + + + diff --git a/tutorials/gloo-progressive-delivery.html b/tutorials/gloo-progressive-delivery.html new file mode 100644 index 00000000..8588a9dd --- /dev/null +++ b/tutorials/gloo-progressive-delivery.html @@ -0,0 +1,271 @@ + + + + + + Gloo Canary Deployments | Flagger + + + + + + + + + + + + + +

    Gloo Canary Deployments

    This guide shows you how to use the Gloo ingress controller and Flagger to automate canary deployments.

    Flagger Gloo Ingress Controller

    Prerequisites

    Flagger requires a Kubernetes cluster v1.11 or newer and Gloo ingress 1.3.5 or newer.

    Install Gloo with Helm v3:

    helm repo add gloo https://storage.googleapis.com/solo-public-helm
    +kubectl create ns gloo-system
    +helm upgrade -i gloo gloo/gloo \
    +--namespace gloo-system
    +

    Install Flagger and the Prometheus add-on in the same namespace as Gloo:

    helm repo add flagger https://flagger.app
    +
    +helm upgrade -i flagger flagger/flagger \
    +--namespace gloo-system \
    +--set prometheus.install=true \
    +--set meshProvider=gloo
    +

    Bootstrap

    Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), +then creates a series of objects (Kubernetes deployments, ClusterIP services and Gloo upstream groups). +These objects expose the application outside the cluster and drive the canary analysis and promotion.

    Create a test namespace:

    kubectl create ns test
    +

    Create a deployment and a horizontal pod autoscaler:

    kubectl -n test apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Deploy the load testing service to generate traffic during the canary analysis:

    kubectl -n test apply -k github.com/weaveworks/flagger//kustomize/tester
    +

    Create an virtual service definition that references an upstream group that will be generated by Flagger +(replace app.example.com with your own domain):

    apiVersion: gateway.solo.io/v1
    +kind: VirtualService
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  virtualHost:
    +    domains:
    +      - 'app.example.com'
    +    routes:
    +      - matchers:
    +         - prefix: /
    +        routeAction:
    +          upstreamGroup:
    +            name: podinfo
    +            namespace: test
    +

    Save the above resource as podinfo-virtualservice.yaml and then apply it:

    kubectl apply -f ./podinfo-virtualservice.yaml
    +

    Create a canary custom resource (replace app.example.com with your own domain):

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  provider: gloo
    +  # deployment reference
    +  targetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: podinfo
    +  # HPA reference (optional)
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  service:
    +    # ClusterIP port number
    +    port: 9898
    +    # container port number or name (optional)
    +    targetPort: 9898
    +  analysis:
    +    # schedule interval (default 60s)
    +    interval: 10s
    +    # max number of failed metric checks before rollback
    +    threshold: 5
    +    # max traffic percentage routed to canary
    +    # percentage (0-100)
    +    maxWeight: 50
    +    # canary increment step
    +    # percentage (0-100)
    +    stepWeight: 5
    +    # Gloo Prometheus checks
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      # maximum req duration P99
    +      # milliseconds
    +      thresholdRange:
    +        max: 500
    +      interval: 30s
    +    # testing (optional)
    +    webhooks:
    +      - name: acceptance-test
    +        type: pre-rollout
    +        url: http://flagger-loadtester.test/
    +        timeout: 10s
    +        metadata:
    +          type: bash
    +          cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
    +      - name: load-test
    +        url: http://flagger-loadtester.test/
    +        timeout: 5s
    +        metadata:
    +          type: cmd
    +          cmd: "hey -z 2m -q 5 -c 2 -host app.example.com http://gateway-proxy.gloo-system"
    +

    Save the above resource as podinfo-canary.yaml and then apply it:

    kubectl apply -f ./podinfo-canary.yaml
    +

    After a couple of seconds Flagger will create the canary objects:

    # applied 
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +virtualservices.gateway.solo.io/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated 
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +upstreamgroups.gloo.solo.io/podinfo
    +

    When the bootstrap finishes Flagger will set the canary status to initialized:

    kubectl -n test get canary podinfo
    +
    +NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +podinfo   Initialized   0        2019-05-17T08:09:51Z
    +

    Automated canary promotion

    Flagger implements a control loop that gradually shifts traffic to the canary while measuring +key performance indicators like HTTP requests success rate, requests average duration and pod health. +Based on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack.

    Flagger Canary Stages

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.1
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         0
    +  Phase:                 Succeeded
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  New revision detected podinfo.test
    +  Normal   Synced  3m    flagger  Scaling up podinfo.test
    +  Warning  Synced  3m    flagger  Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 5
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 15
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary weight 20
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary weight 25
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary weight 30
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary weight 35
    +  Normal   Synced  55s   flagger  Advance podinfo.test canary weight 40
    +  Normal   Synced  45s   flagger  Advance podinfo.test canary weight 45
    +  Normal   Synced  35s   flagger  Advance podinfo.test canary weight 50
    +  Normal   Synced  25s   flagger  Copying podinfo.test template spec to podinfo-primary.test
    +  Warning  Synced  15s   flagger  Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    +  Normal   Synced  5s    flagger  Promotion completed! Scaling down podinfo.test
    +

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   15       2019-05-17T14:05:07Z
    +prod        frontend  Succeeded     0        2019-05-17T16:15:07Z
    +prod        backend   Failed        0        2019-05-17T17:05:07Z
    +

    Automated rollback

    During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses and rolls back the faulted version.

    Trigger another canary deployment:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.2
    +

    Generate HTTP 500 errors:

    watch curl -H 'Host: app.example.com' http://gateway-proxy-v2.gloo-system/status/500
    +

    Generate high latency:

    watch curl -H 'Host: app.example.com' http://gateway-proxy-v2.gloo-system/delay/2
    +

    When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, +the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         10
    +  Phase:                 Failed
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  Starting canary deployment for podinfo.test
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 5
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 15
    +  Normal   Synced  3m    flagger  Halt podinfo.test advancement success rate 69.17% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 61.39% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 55.06% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 47.00% < 99%
    +  Normal   Synced  2m    flagger  (combined from similar events): Halt podinfo.test advancement success rate 38.08% < 99%
    +  Warning  Synced  1m    flagger  Rolling back podinfo.test failed checks threshold reached 10
    +  Warning  Synced  1m    flagger  Canary failed! Scaling down podinfo.test
    +

    Custom metrics

    The canary analysis can be extended with Prometheus queries.

    The demo app is instrumented with Prometheus so you can create a custom check that will use +the HTTP request duration histogram to validate the canary.

    Create a metric template and apply it on the cluster:

    apiVersion: flagger.app/v1beta1
    +kind: MetricTemplate
    +metadata:
    +  name: not-found-percentage
    +  namespace: test
    +spec:
    +  provider:
    +    type: prometheus
    +    address: http://flagger-promethues.gloo-system:9090
    +  query: |
    +    100 - sum(
    +        rate(
    +            http_request_duration_seconds_count{
    +              kubernetes_namespace="{{ namespace }}",
    +              kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
    +              status!="{{ interval }}"
    +            }[1m]
    +        )
    +    )
    +    /
    +    sum(
    +        rate(
    +            http_request_duration_seconds_count{
    +              kubernetes_namespace="{{ namespace }}",
    +              kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
    +            }[{{ interval }}]
    +        )
    +    ) * 100
    +

    Edit the canary analysis and add the following metric:

      analysis:
    +    metrics:
    +      - name: "404s percentage"
    +        templateRef:
    +          name: not-found-percentage
    +        thresholdRange:
    +          max: 5
    +        interval: 1m
    +

    The above configuration validates the canary by checking if the HTTP 404 req/sec percentage is +below 5 percent of the total traffic. If the 404s rate reaches the 5% threshold, then the canary fails.

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.3
    +

    Generate 404s:

    watch curl -H 'Host: app.example.com' http://gateway-proxy.gloo-system/status/400
    +

    Watch Flagger logs:

    kubectl -n gloo-system logs deployment/flagger -f | jq .msg
    +
    +Starting canary deployment for podinfo.test
    +Advance podinfo.test canary weight 5
    +Advance podinfo.test canary weight 10
    +Advance podinfo.test canary weight 15
    +Halt podinfo.test advancement 404s percentage 6.20 > 5
    +Halt podinfo.test advancement 404s percentage 6.45 > 5
    +Halt podinfo.test advancement 404s percentage 7.60 > 5
    +Halt podinfo.test advancement 404s percentage 8.69 > 5
    +Halt podinfo.test advancement 404s percentage 9.70 > 5
    +Rolling back podinfo.test failed checks threshold reached 5
    +Canary failed! Scaling down podinfo.test
    +

    If you have alerting configured, +Flagger will send a notification with the reason why the canary failed.

    For an in-depth look at the analysis process read the usage docs.

    + + + diff --git a/tutorials/index.html b/tutorials/index.html new file mode 100644 index 00000000..32e74ce7 --- /dev/null +++ b/tutorials/index.html @@ -0,0 +1,35 @@ + + + + + + Flagger + + + + + + + + + + + + + + + + + diff --git a/tutorials/istio-ab-testing.html b/tutorials/istio-ab-testing.html new file mode 100644 index 00000000..5464b7c9 --- /dev/null +++ b/tutorials/istio-ab-testing.html @@ -0,0 +1,203 @@ + + + + + + Istio A/B Testing | Flagger + + + + + + + + + + + + + +

    Istio A/B Testing

    This guide shows you how to automate A/B testing with Istio and Flagger.

    Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions. +In an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users. +This is particularly useful for frontend applications that require session affinity.

    Flagger A/B Testing Stages

    Prerequisites

    Flagger requires a Kubernetes cluster v1.11 or newer and Istio v1.0 or newer.

    Install Istio with telemetry support and Prometheus:

    istioctl manifest apply --set profile=default
    +

    Install Flagger using Kustomize (kubectl 1.14) in the istio-system namespace:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/istio
    +

    Create an ingress gateway to expose the demo app outside of the mesh:

    apiVersion: networking.istio.io/v1alpha3
    +kind: Gateway
    +metadata:
    +  name: public-gateway
    +  namespace: istio-system
    +spec:
    +  selector:
    +    istio: ingressgateway
    +  servers:
    +    - port:
    +        number: 80
    +        name: http
    +        protocol: HTTP
    +      hosts:
    +        - "*"
    +

    Bootstrap

    Create a test namespace with Istio sidecar injection enabled:

    kubectl create ns test
    +kubectl label namespace test istio-injection=enabled
    +

    Create a deployment and a horizontal pod autoscaler:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Deploy the load testing service to generate traffic during the canary analysis:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/tester
    +

    Create a canary custom resource (replace example.com with your own domain):

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  # 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
    +  # HPA reference (optional)
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  service:
    +    # container port
    +    port: 9898
    +    # Istio gateways (optional)
    +    gateways:
    +    - public-gateway.istio-system.svc.cluster.local
    +    # Istio virtual service host names (optional)
    +    hosts:
    +    - app.example.com
    +    # Istio traffic policy (optional)
    +    trafficPolicy:
    +      tls:
    +        # use ISTIO_MUTUAL when mTLS is enabled
    +        mode: DISABLE
    +  analysis:
    +    # schedule interval (default 60s)
    +    interval: 1m
    +    # total number of iterations
    +    iterations: 10
    +    # max number of failed iterations before rollback
    +    threshold: 2
    +    # canary match condition
    +    match:
    +      - headers:
    +          user-agent:
    +            regex: "^(?!.*Chrome).*Safari.*"
    +      - headers:
    +          cookie:
    +            regex: "^(.*?;)?(type=insider)(;.*)?$"
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      # maximum req duration P99
    +      # milliseconds
    +      thresholdRange:
    +        max: 500
    +      interval: 30s
    +    # generate traffic during analysis
    +    webhooks:
    +      - name: load-test
    +        url: http://flagger-loadtester.test/
    +        timeout: 5s
    +        metadata:
    +          cmd: "hey -z 1m -q 10 -c 2 -H 'Cookie: type=insider' http://podinfo.test:9898/"
    +

    The above configuration will run an analysis for ten minutes targeting Safari users and those that have an insider cookie.

    Save the above resource as podinfo-abtest.yaml and then apply it:

    kubectl apply -f ./podinfo-abtest.yaml
    +

    After a couple of seconds Flagger will create the canary objects:

    # applied 
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated 
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +destinationrule.networking.istio.io/podinfo-canary
    +destinationrule.networking.istio.io/podinfo-primary
    +virtualservice.networking.istio.io/podinfo
    +

    Automated canary promotion

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.1
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n test describe canary/abtest
    +
    +Status:
    +  Failed Checks:         0
    +  Phase:                 Succeeded
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  New revision detected podinfo.test
    +  Normal   Synced  3m    flagger  Scaling up podinfo.test
    +  Warning  Synced  3m    flagger  Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 1/10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 2/10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 3/10
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary iteration 4/10
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary iteration 5/10
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary iteration 6/10
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary iteration 7/10
    +  Normal   Synced  55s   flagger  Advance podinfo.test canary iteration 8/10
    +  Normal   Synced  45s   flagger  Advance podinfo.test canary iteration 9/10
    +  Normal   Synced  35s   flagger  Advance podinfo.test canary iteration 10/10
    +  Normal   Synced  25s   flagger  Copying podinfo.test template spec to podinfo-primary.test
    +  Warning  Synced  15s   flagger  Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    +  Normal   Synced  5s    flagger  Promotion completed! Scaling down podinfo.test
    +

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   100      2019-03-16T14:05:07Z
    +prod        frontend  Succeeded     0        2019-03-15T16:15:07Z
    +prod        backend   Failed        0        2019-03-14T17:05:07Z
    +

    Automated rollback

    During the canary analysis you can generate HTTP 500 errors and high latency to test Flagger's rollback.

    Generate HTTP 500 errors:

    watch curl -b 'type=insider' http://app.example.com/status/500
    +

    Generate latency:

    watch curl -b 'type=insider' http://app.example.com/delay/1
    +

    When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, +the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Failed Checks:         2
    +  Phase:                 Failed
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  Starting canary deployment for podinfo.test
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 1/10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 2/10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 3/10
    +  Normal   Synced  3m    flagger  Halt podinfo.test advancement success rate 69.17% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 61.39% < 99%
    +  Warning  Synced  2m    flagger  Rolling back podinfo.test failed checks threshold reached 2
    +  Warning  Synced  1m    flagger  Canary failed! Scaling down podinfo.test
    +

    The above procedure can be extended with custom metrics checks, +webhooks, +manual promotion approval and +Slack or MS Teams notifications.

    + + + diff --git a/tutorials/istio-progressive-delivery.html b/tutorials/istio-progressive-delivery.html new file mode 100644 index 00000000..56075c2b --- /dev/null +++ b/tutorials/istio-progressive-delivery.html @@ -0,0 +1,263 @@ + + + + + + Istio Canary Deployments | Flagger + + + + + + + + + + + + + +

    Istio Canary Deployments

    This guide shows you how to use Istio and Flagger to automate canary deployments.

    Flagger Canary Stages

    Prerequisites

    Flagger requires a Kubernetes cluster v1.11 or newer and Istio v1.0 or newer.

    Install Istio with telemetry support and Prometheus:

    istioctl manifest apply --set profile=default
    +

    Install Flagger using Kustomize (kubectl 1.14) in the istio-system namespace:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/istio
    +

    Create an ingress gateway to expose the demo app outside of the mesh:

    apiVersion: networking.istio.io/v1alpha3
    +kind: Gateway
    +metadata:
    +  name: public-gateway
    +  namespace: istio-system
    +spec:
    +  selector:
    +    istio: ingressgateway
    +  servers:
    +    - port:
    +        number: 80
    +        name: http
    +        protocol: HTTP
    +      hosts:
    +        - "*"
    +

    Bootstrap

    Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), +then creates a series of objects (Kubernetes deployments, ClusterIP services, +Istio destination rules and virtual services). +These objects expose the application inside the mesh and drive the canary analysis and promotion.

    Create a test namespace with Istio sidecar injection enabled:

    kubectl create ns test
    +kubectl label namespace test istio-injection=enabled
    +

    Create a deployment and a horizontal pod autoscaler:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Deploy the load testing service to generate traffic during the canary analysis:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/tester
    +

    Create a canary custom resource (replace example.com with your own domain):

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  # 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
    +  # HPA reference (optional)
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  service:
    +    # service port number
    +    port: 9898
    +    # container port number or name (optional)
    +    targetPort: 9898
    +    # Istio gateways (optional)
    +    gateways:
    +    - public-gateway.istio-system.svc.cluster.local
    +    # Istio virtual service host names (optional)
    +    hosts:
    +    - app.example.com
    +    # Istio traffic policy (optional)
    +    trafficPolicy:
    +      tls:
    +        # use ISTIO_MUTUAL when mTLS is enabled
    +        mode: DISABLE
    +    # Istio retry policy (optional)
    +    retries:
    +      attempts: 3
    +      perTryTimeout: 1s
    +      retryOn: "gateway-error,connect-failure,refused-stream"
    +  analysis:
    +    # schedule interval (default 60s)
    +    interval: 1m
    +    # max number of failed metric checks before rollback
    +    threshold: 5
    +    # max traffic percentage routed to canary
    +    # percentage (0-100)
    +    maxWeight: 50
    +    # canary increment step
    +    # percentage (0-100)
    +    stepWeight: 10
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      # maximum req duration P99
    +      # milliseconds
    +      thresholdRange:
    +        max: 500
    +      interval: 30s
    +    # testing (optional)
    +    webhooks:
    +      - name: acceptance-test
    +        type: pre-rollout
    +        url: http://flagger-loadtester.test/
    +        timeout: 30s
    +        metadata:
    +          type: bash
    +          cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
    +      - name: load-test
    +        url: http://flagger-loadtester.test/
    +        timeout: 5s
    +        metadata:
    +          cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"
    +

    Save the above resource as podinfo-canary.yaml and then apply it:

    kubectl apply -f ./podinfo-canary.yaml
    +

    When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary. +The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every minute.

    Flagger Canary Process

    After a couple of seconds Flagger will create the canary objects:

    # applied 
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated 
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +destinationrule.networking.istio.io/podinfo-canary
    +destinationrule.networking.istio.io/podinfo-primary
    +virtualservice.networking.istio.io/podinfo
    +

    Automated canary promotion

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.1
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         0
    +  Phase:                 Succeeded
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  New revision detected podinfo.test
    +  Normal   Synced  3m    flagger  Scaling up podinfo.test
    +  Warning  Synced  3m    flagger  Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 5
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 15
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary weight 20
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary weight 25
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary weight 30
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary weight 35
    +  Normal   Synced  55s   flagger  Advance podinfo.test canary weight 40
    +  Normal   Synced  45s   flagger  Advance podinfo.test canary weight 45
    +  Normal   Synced  35s   flagger  Advance podinfo.test canary weight 50
    +  Normal   Synced  25s   flagger  Copying podinfo.test template spec to podinfo-primary.test
    +  Warning  Synced  15s   flagger  Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    +  Normal   Synced  5s    flagger  Promotion completed! Scaling down podinfo.test
    +

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    A canary deployment is triggered by changes in any of the following objects:

    • Deployment PodSpec (container image, command, ports, env, resources, etc)
    • ConfigMaps mounted as volumes or mapped to environment variables
    • Secrets mounted as volumes or mapped to environment variables

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   15       2019-01-16T14:05:07Z
    +prod        frontend  Succeeded     0        2019-01-15T16:15:07Z
    +prod        backend   Failed        0        2019-01-14T17:05:07Z
    +

    Automated rollback

    During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses the rollout.

    Trigger another canary deployment:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.2
    +

    Exec into the load tester pod with:

    kubectl -n test exec -it flagger-loadtester-xx-xx sh
    +

    Generate HTTP 500 errors:

    watch curl http://podinfo-canary:9898/status/500
    +

    Generate latency:

    watch curl http://podinfo-canary:9898/delay/1
    +

    When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, +the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         10
    +  Phase:                 Failed
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  Starting canary deployment for podinfo.test
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 5
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 15
    +  Normal   Synced  3m    flagger  Halt podinfo.test advancement success rate 69.17% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 61.39% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 55.06% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 47.00% < 99%
    +  Normal   Synced  2m    flagger  (combined from similar events): Halt podinfo.test advancement success rate 38.08% < 99%
    +  Warning  Synced  1m    flagger  Rolling back podinfo.test failed checks threshold reached 10
    +  Warning  Synced  1m    flagger  Canary failed! Scaling down podinfo.test
    +

    Traffic mirroring

    Flagger Canary Traffic Shadowing

    For applications that perform read operations, Flagger can be configured to drive canary releases with traffic mirroring. +Istio traffic mirroring will copy each incoming request, sending one request to the primary and one to the canary service. +The response from the primary is sent back to the user and the response from the canary is discarded. +Metrics are collected on both requests so that the deployment will only proceed if the canary metrics are within the threshold values.

    Note that mirroring should be used for requests that are idempotent or capable of being processed twice (once by the primary and once by the canary).

    You can enable mirroring by replacing stepWeight/maxWeight with iterations and by setting analysis.mirror to true:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  analysis:
    +    # schedule interval
    +    interval: 1m
    +    # max number of failed metric checks before rollback
    +    threshold: 5
    +    # total number of iterations
    +    iterations: 10
    +    # enable traffic shadowing 
    +    mirror: true
    +    metrics:
    +    - name: request-success-rate
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      thresholdRange:
    +        max: 500
    +      interval: 1m
    +    webhooks:
    +      - name: acceptance-test
    +        type: pre-rollout
    +        url: http://flagger-loadtester.test/
    +        timeout: 30s
    +        metadata:
    +          type: bash
    +          cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
    +      - name: load-test
    +        url: http://flagger-loadtester.test/
    +        timeout: 5s
    +        metadata:
    +          cmd: "hey -z 1m -q 10 -c 2 http://podinfo.test:9898/"
    +

    With the above configuration, Flagger will run a canary release with the following steps:

    • detect new revision (deployment spec, secrets or configmaps changes)
    • scale from zero the canary deployment
    • wait for the HPA to set the canary minimum replicas
    • check canary pods health
    • run the acceptance tests
    • abort the canary release if tests fail
    • start the load tests
    • mirror traffic from primary to canary
    • check request success rate and request duration every minute
    • abort the canary release if the metrics check failure threshold is reached
    • stop traffic mirroring after the number of iterations is reached
    • route live traffic to the canary pods
    • promote the canary (update the primary secrets, configmaps and deployment spec)
    • wait for the primary deployment rollout to finish
    • wait for the HPA to set the primary minimum replicas
    • check primary pods health
    • switch live traffic back to primary
    • scale to zero the canary
    • send notification with the canary analysis result

    The above procedure can be extended with custom metrics checks, +webhooks, +manual promotion approval and +Slack or MS Teams notifications.

    + + + diff --git a/tutorials/kubernetes-blue-green.html b/tutorials/kubernetes-blue-green.html new file mode 100644 index 00000000..e5321698 --- /dev/null +++ b/tutorials/kubernetes-blue-green.html @@ -0,0 +1,253 @@ + + + + + + Blue/Green Deployments | Flagger + + + + + + + + + + + + + +

    Blue/Green Deployments

    This guide shows you how to automate Blue/Green deployments with Flagger and Kubernetes.

    For applications that are not deployed on a service mesh, Flagger can orchestrate Blue/Green style deployments +with Kubernetes L4 networking. When using a service mesh blue/green can be used as +specified here.

    Flagger Blue/Green Stages

    Prerequisites

    Flagger requires a Kubernetes cluster v1.11 or newer.

    Install Flagger and the Prometheus add-on:

    helm repo add flagger https://flagger.app
    +
    +helm upgrade -i flagger flagger/flagger \
    +--namespace flagger \
    +--set prometheus.install=true \
    +--set meshProvider=kubernetes
    +

    If you already have a Prometheus instance running in your cluster, you can point Flagger to the ClusterIP service with:

    helm upgrade -i flagger flagger/flagger \
    +--namespace flagger \
    +--set metricsServer=http://prometheus.monitoring:9090
    +

    Optionally you can enable Slack notifications:

    helm upgrade -i flagger flagger/flagger \
    +--reuse-values \
    +--namespace flagger \
    +--set slack.url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
    +--set slack.channel=general \
    +--set slack.user=flagger
    +

    Bootstrap

    Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), +then creates a series of objects (Kubernetes deployment and ClusterIP services). +These objects expose the application inside the cluster and drive the canary analysis and Blue/Green promotion.

    Create a test namespace:

    kubectl create ns test
    +

    Create a deployment and a horizontal pod autoscaler:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Deploy the load testing service to generate traffic during the analysis:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/tester
    +

    Create a canary custom resource:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  # service mesh provider can be: kubernetes, istio, appmesh, nginx, gloo
    +  provider: kubernetes
    +  # deployment reference
    +  targetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: podinfo
    +  # the maximum time in seconds for the canary deployment
    +  # to make progress before rollback (default 600s)
    +  progressDeadlineSeconds: 60
    +  # HPA reference (optional)
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  service:
    +    port: 9898
    +    portDiscovery: true
    +  analysis:
    +    # schedule interval (default 60s)
    +    interval: 30s
    +    # max number of failed checks before rollback
    +    threshold: 2
    +    # number of checks to run before rollback
    +    iterations: 10
    +    # Prometheus checks based on 
    +    # http_request_duration_seconds histogram
    +    metrics:
    +      - name: request-success-rate
    +        # minimum req success rate (non 5xx responses)
    +        # percentage (0-100)
    +        thresholdRange:
    +          min: 99
    +        interval: 1m
    +      - name: request-duration
    +        # maximum req duration P99
    +        # milliseconds
    +        thresholdRange:
    +          max: 500
    +        interval: 30s
    +    # acceptance/load testing hooks
    +    webhooks:
    +      - name: smoke-test
    +        type: pre-rollout
    +        url: http://flagger-loadtester.test/
    +        timeout: 15s
    +        metadata:
    +          type: bash
    +          cmd: "curl -sd 'anon' http://podinfo-canary.test:9898/token | grep token"
    +      - name: load-test
    +        url: http://flagger-loadtester.test/
    +        timeout: 5s
    +        metadata:
    +          type: cmd
    +          cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"
    +

    The above configuration will run an analysis for five minutes.

    Save the above resource as podinfo-canary.yaml and then apply it:

    kubectl apply -f ./podinfo-canary.yaml
    +

    After a couple of seconds Flagger will create the canary objects:

    # applied 
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated 
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +

    Blue/Green scenario:

    • on bootstrap, Flagger will create three ClusterIP services (app-primary,app-canary, app) +and a shadow deployment named app-primary that represents the blue version
    • when a new version is detected, Flagger would scale up the green version and run the conformance tests +(the tests should target the app-canary ClusterIP service to reach the green version)
    • if the conformance tests are passing, Flagger would start the load tests and validate them with custom Prometheus queries
    • if the load test analysis is successful, Flagger will promote the new version to app-primary and scale down the green version

    Automated Blue/Green promotion

    Trigger a deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.1
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n test describe canary/podinfo
    +
    +Events:
    +
    +New revision detected podinfo.test
    +Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    +Pre-rollout check acceptance-test passed
    +Advance podinfo.test canary iteration 1/10
    +Advance podinfo.test canary iteration 2/10
    +Advance podinfo.test canary iteration 3/10
    +Advance podinfo.test canary iteration 4/10
    +Advance podinfo.test canary iteration 5/10
    +Advance podinfo.test canary iteration 6/10
    +Advance podinfo.test canary iteration 7/10
    +Advance podinfo.test canary iteration 8/10
    +Advance podinfo.test canary iteration 9/10
    +Advance podinfo.test canary iteration 10/10
    +Copying podinfo.test template spec to podinfo-primary.test
    +Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    +Promotion completed! Scaling down podinfo.test
    +

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   100      2019-06-16T14:05:07Z
    +prod        frontend  Succeeded     0        2019-06-15T16:15:07Z
    +prod        backend   Failed        0        2019-06-14T17:05:07Z
    +

    Automated rollback

    During the analysis you can generate HTTP 500 errors and high latency to test Flagger's rollback.

    Exec into the load tester pod with:

    kubectl -n test exec -it flagger-loadtester-xx-xx sh
    +

    Generate HTTP 500 errors:

    watch curl http://podinfo-canary.test:9898/status/500
    +

    Generate latency:

    watch curl http://podinfo-canary.test:9898/delay/1
    +

    When the number of failed checks reaches the analysis threshold, the green version is scaled to zero and the rollout is marked as failed.

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Failed Checks:         2
    +  Phase:                 Failed
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  New revision detected podinfo.test
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 1/10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 2/10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 3/10
    +  Normal   Synced  3m    flagger  Halt podinfo.test advancement success rate 69.17% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 61.39% < 99%
    +  Warning  Synced  2m    flagger  Rolling back podinfo.test failed checks threshold reached 2
    +  Warning  Synced  1m    flagger  Canary failed! Scaling down podinfo.test
    +

    Custom metrics

    The analysis can be extended with Prometheus queries. The demo app is instrumented with Prometheus so you can +create a custom check that will use the HTTP request duration histogram to validate the canary (green version).

    Create a metric template and apply it on the cluster:

    apiVersion: flagger.app/v1beta1
    +kind: MetricTemplate
    +metadata:
    +  name: not-found-percentage
    +  namespace: test
    +spec:
    +  provider:
    +    type: prometheus
    +    address: http://flagger-promethues.flagger:9090
    +  query: |
    +    100 - sum(
    +        rate(
    +            http_request_duration_seconds_count{
    +              kubernetes_namespace="{{ namespace }}",
    +              kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
    +              status!="{{ interval }}"
    +            }[1m]
    +        )
    +    )
    +    /
    +    sum(
    +        rate(
    +            http_request_duration_seconds_count{
    +              kubernetes_namespace="{{ namespace }}",
    +              kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
    +            }[{{ interval }}]
    +        )
    +    ) * 100
    +

    Edit the canary analysis and add the following metric:

      analysis:
    +    metrics:
    +      - name: "404s percentage"
    +        templateRef:
    +          name: not-found-percentage
    +        thresholdRange:
    +          max: 5
    +        interval: 1m
    +

    The above configuration validates the canary (green version) by checking if the HTTP 404 req/sec percentage is +below 5 percent of the total traffic. If the 404s rate reaches the 5% threshold, then the rollout is rolled back.

    Trigger a deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.3
    +

    Generate 404s:

    watch curl http://podinfo-canary.test:9898/status/400
    +

    Watch Flagger logs:

    kubectl -n flagger logs deployment/flagger -f | jq .msg
    +
    +New revision detected podinfo.test
    +Scaling up podinfo.test
    +Advance podinfo.test canary iteration 1/10
    +Halt podinfo.test advancement 404s percentage 6.20 > 5
    +Halt podinfo.test advancement 404s percentage 6.45 > 5
    +Rolling back podinfo.test failed checks threshold reached 2
    +Canary failed! Scaling down podinfo.test
    +

    If you have alerting configured, +Flagger will send a notification with the reason why the canary failed.

    Conformance Testing with Helm

    Flagger comes with a testing service that can run Helm tests when configured as a pre-rollout webhook.

    Deploy the Helm test runner in the kube-system namespace using the tiller service account:

    helm repo add flagger https://flagger.app
    +
    +helm upgrade -i flagger-helmtester flagger/loadtester \
    +--namespace=kube-system \
    +--set serviceAccountName=tiller
    +

    When deployed the Helm tester API will be available at http://flagger-helmtester.kube-system/.

    Add a helm test pre-rollout hook to your chart:

      analysis:
    +    webhooks:
    +      - name: "conformance testing"
    +        type: pre-rollout
    +        url: http://flagger-helmtester.kube-system/
    +        timeout: 3m
    +        metadata:
    +          type: "helm"
    +          cmd: "test {{ .Release.Name }} --cleanup"
    +

    When the canary analysis starts, Flagger will call the pre-rollout webhooks. +If the helm test fails, Flagger will retry until the analysis threshold is reached and the canary is rolled back.

    For an in-depth look at the analysis process read the usage docs.

    + + + diff --git a/tutorials/linkerd-progressive-delivery.html b/tutorials/linkerd-progressive-delivery.html new file mode 100644 index 00000000..0cab342d --- /dev/null +++ b/tutorials/linkerd-progressive-delivery.html @@ -0,0 +1,345 @@ + + + + + + Linkerd Canary Deployments | Flagger + + + + + + + + + + + + + +

    Linkerd Canary Deployments

    This guide shows you how to use Linkerd and Flagger to automate canary deployments.

    Flagger Linkerd Traffic Split

    Prerequisites

    Flagger requires a Kubernetes cluster v1.11 or newer and Linkerd 2.4 or newer.

    Install Flagger in the linkerd namespace:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/linkerd
    +

    Note that you'll need kubectl 1.14 or newer to run the above command.

    Bootstrap

    Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), +then creates a series of objects (Kubernetes deployments, ClusterIP services and SMI traffic split). +These objects expose the application inside the mesh and drive the canary analysis and promotion.

    Create a test namespace and enable Linkerd proxy injection:

    kubectl create ns test
    +kubectl annotate namespace test linkerd.io/inject=enabled
    +

    Install the load testing service to generate traffic during the canary analysis:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/tester
    +

    Create a deployment and a horizontal pod autoscaler:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Create a canary custom resource for the podinfo deployment:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  # deployment reference
    +  targetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: podinfo
    +  # HPA reference (optional)
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  # the maximum time in seconds for the canary deployment
    +  # to make progress before it is rollback (default 600s)
    +  progressDeadlineSeconds: 60
    +  service:
    +    # ClusterIP port number
    +    port: 9898
    +    # container port number or name (optional)
    +    targetPort: 9898
    +  analysis:
    +    # schedule interval (default 60s)
    +    interval: 30s
    +    # max number of failed metric checks before rollback
    +    threshold: 5
    +    # max traffic percentage routed to canary
    +    # percentage (0-100)
    +    maxWeight: 50
    +    # canary increment step
    +    # percentage (0-100)
    +    stepWeight: 5
    +    # Linkerd Prometheus checks
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      # maximum req duration P99
    +      # milliseconds
    +      thresholdRange:
    +        max: 500
    +      interval: 30s
    +    # testing (optional)
    +    webhooks:
    +      - name: acceptance-test
    +        type: pre-rollout
    +        url: http://flagger-loadtester.test/
    +        timeout: 30s
    +        metadata:
    +          type: bash
    +          cmd: "curl -sd 'test' http://podinfo-canary.test:9898/token | grep token"
    +      - name: load-test
    +        type: rollout
    +        url: http://flagger-loadtester.test/
    +        metadata:
    +          cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.test:9898/"
    +

    Save the above resource as podinfo-canary.yaml and then apply it:

    kubectl apply -f ./podinfo-canary.yaml
    +

    When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary. +The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every half a minute.

    After a couple of seconds Flagger will create the canary objects:

    # applied
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +ingresses.extensions/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +trafficsplits.split.smi-spec.io/podinfo
    +

    After the boostrap, the podinfo deployment will be scaled to zero and the traffic to podinfo.test +will be routed to the primary pods. +During the canary analysis, the podinfo-canary.test address can be used to target directly the canary pods.

    Automated canary promotion

    Flagger implements a control loop that gradually shifts traffic to the canary while measuring +key performance indicators like HTTP requests success rate, requests average duration and pod health. +Based on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack.

    Flagger Canary Stages

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.1
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         0
    +  Phase:                 Succeeded
    +Events:
    + New revision detected! Scaling up podinfo.test
    + Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    + Pre-rollout check acceptance-test passed
    + Advance podinfo.test canary weight 5
    + Advance podinfo.test canary weight 10
    + Advance podinfo.test canary weight 15
    + Advance podinfo.test canary weight 20
    + Advance podinfo.test canary weight 25
    + Waiting for podinfo.test rollout to finish: 1 of 2 updated replicas are available
    + Advance podinfo.test canary weight 30
    + Advance podinfo.test canary weight 35
    + Advance podinfo.test canary weight 40
    + Advance podinfo.test canary weight 45
    + Advance podinfo.test canary weight 50
    + Copying podinfo.test template spec to podinfo-primary.test
    + Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    + Promotion completed! Scaling down podinfo.test
    +

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    A canary deployment is triggered by changes in any of the following objects:

    • Deployment PodSpec (container image, command, ports, env, resources, etc)
    • ConfigMaps mounted as volumes or mapped to environment variables
    • Secrets mounted as volumes or mapped to environment variables

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   15       2019-06-30T14:05:07Z
    +prod        frontend  Succeeded     0        2019-06-30T16:15:07Z
    +prod        backend   Failed        0        2019-06-30T17:05:07Z
    +

    Automated rollback

    During the canary analysis you can generate HTTP 500 errors and high latency to +test if Flagger pauses and rolls back the faulted version.

    Trigger another canary deployment:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.2
    +

    Exec into the load tester pod with:

    kubectl -n test exec -it flagger-loadtester-xx-xx sh
    +

    Generate HTTP 500 errors:

    watch -n 1 curl http://podinfo-canary.test:9898/status/500
    +

    Generate latency:

    watch -n 1 curl http://podinfo-canary.test:9898/delay/1
    +

    When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, +the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         10
    +  Phase:                 Failed
    +Events:
    + Starting canary analysis for podinfo.test
    + Pre-rollout check acceptance-test passed
    + Advance podinfo.test canary weight 5
    + Advance podinfo.test canary weight 10
    + Advance podinfo.test canary weight 15
    + Halt podinfo.test advancement success rate 69.17% < 99%
    + Halt podinfo.test advancement success rate 61.39% < 99%
    + Halt podinfo.test advancement success rate 55.06% < 99%
    + Halt podinfo.test advancement request duration 1.20s > 0.5s
    + Halt podinfo.test advancement request duration 1.45s > 0.5s
    + Rolling back podinfo.test failed checks threshold reached 5
    + Canary failed! Scaling down podinfo.test
    +

    Custom metrics

    The canary analysis can be extended with Prometheus queries.

    Let's a define a check for not found errors. Edit the canary analysis and add the following metric:

      analysis:
    +    metrics:
    +    - name: "404s percentage"
    +      threshold: 3
    +      query: |
    +        100 - sum(
    +            rate(
    +                response_total{
    +                    namespace="test",
    +                    deployment="podinfo",
    +                    status_code!="404",
    +                    direction="inbound"
    +                }[1m]
    +            )
    +        )
    +        /
    +        sum(
    +            rate(
    +                response_total{
    +                    namespace="test",
    +                    deployment="podinfo",
    +                    direction="inbound"
    +                }[1m]
    +            )
    +        )
    +        * 100
    +

    The above configuration validates the canary version by checking if the HTTP 404 req/sec percentage +is below three percent of the total traffic. +If the 404s rate reaches the 3% threshold, then the analysis is aborted and the canary is marked as failed.

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.3
    +

    Generate 404s:

    watch -n 1 curl http://podinfo-canary:9898/status/404
    +

    Watch Flagger logs:

    kubectl -n linkerd logs deployment/flagger -f | jq .msg
    +
    +Starting canary deployment for podinfo.test
    +Pre-rollout check acceptance-test passed
    +Advance podinfo.test canary weight 5
    +Halt podinfo.test advancement 404s percentage 6.20 > 3
    +Halt podinfo.test advancement 404s percentage 6.45 > 3
    +Halt podinfo.test advancement 404s percentage 7.22 > 3
    +Halt podinfo.test advancement 404s percentage 6.50 > 3
    +Halt podinfo.test advancement 404s percentage 6.34 > 3
    +Rolling back podinfo.test failed checks threshold reached 5
    +Canary failed! Scaling down podinfo.test
    +

    If you have Slack configured, Flagger will send a notification with the reason why the canary failed.

    Linkerd Ingress

    There are two ingress controllers that are compatible with both Flagger and Linkerd: NGINX and Gloo.

    Install NGINX:

    helm upgrade -i nginx-ingress stable/nginx-ingress \
    +--namespace ingress-nginx
    +

    Create an ingress definition for podinfo that rewrites the incoming header +to the internal service name (required by Linkerd):

    apiVersion: extensions/v1beta1
    +kind: Ingress
    +metadata:
    +  name: podinfo
    +  namespace: test
    +  labels:
    +    app: podinfo
    +  annotations:
    +    kubernetes.io/ingress.class: "nginx"
    +    nginx.ingress.kubernetes.io/configuration-snippet: |
    +      proxy_set_header l5d-dst-override $service_name.$namespace.svc.cluster.local:9898;
    +      proxy_hide_header l5d-remote-ip;
    +      proxy_hide_header l5d-server-id;
    +spec:
    +  rules:
    +    - host: app.example.com
    +      http:
    +        paths:
    +          - backend:
    +              serviceName: podinfo
    +              servicePort: 9898
    +

    When using an ingress controller, the Linkerd traffic split does not apply to incoming traffic +since NGINX in running outside of the mesh. In order to run a canary analysis for a frontend app, +Flagger creates a shadow ingress and sets the NGINX specific annotations.

    A/B Testing

    Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions. +In an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users. +This is particularly useful for frontend applications that require session affinity.

    Flagger Linkerd Ingress

    Edit podinfo canary analysis, set the provider to nginx, add the ingress reference, +remove the max/step weight and add the match conditions and iterations:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  # ingress reference
    +  provider: nginx
    +  ingressRef:
    +    apiVersion: extensions/v1beta1
    +    kind: Ingress
    +    name: podinfo
    +  targetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: podinfo
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  service:
    +    # container port
    +    port: 9898
    +  analysis:
    +    interval: 1m
    +    threshold: 10
    +    iterations: 10
    +    match:
    +      # curl -H 'X-Canary: always' http://app.example.com
    +      - headers:
    +          x-canary:
    +            exact: "always"
    +      # curl -b 'canary=always' http://app.example.com
    +      - headers:
    +          cookie:
    +            exact: "canary"
    +    # Linkerd Prometheus checks
    +    metrics:
    +    - name: request-success-rate
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    - name: request-duration
    +      thresholdRange:
    +        max: 500
    +      interval: 30s
    +    webhooks:
    +      - name: acceptance-test
    +        type: pre-rollout
    +        url: http://flagger-loadtester.test/
    +        timeout: 30s
    +        metadata:
    +          type: bash
    +          cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
    +      - name: load-test
    +        type: rollout
    +        url: http://flagger-loadtester.test/
    +        metadata:
    +          cmd: "hey -z 2m -q 10 -c 2 -H 'Cookie: canary=always' http://app.example.com"
    +

    The above configuration will run an analysis for ten minutes targeting users that have +a canary cookie set to always or those that call the service using the X-Canary: always header.

    Note that the load test now targets the external address and uses the canary cookie.

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.4
    +

    Flagger detects that the deployment revision changed and starts the A/B testing:

    kubectl -n test describe canary/podinfo
    +
    +Events:
    + Starting canary deployment for podinfo.test
    + Pre-rollout check acceptance-test passed
    + Advance podinfo.test canary iteration 1/10
    + Advance podinfo.test canary iteration 2/10
    + Advance podinfo.test canary iteration 3/10
    + Advance podinfo.test canary iteration 4/10
    + Advance podinfo.test canary iteration 5/10
    + Advance podinfo.test canary iteration 6/10
    + Advance podinfo.test canary iteration 7/10
    + Advance podinfo.test canary iteration 8/10
    + Advance podinfo.test canary iteration 9/10
    + Advance podinfo.test canary iteration 10/10
    + Copying podinfo.test template spec to podinfo-primary.test
    + Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    + Promotion completed! Scaling down podinfo.test
    +

    The above procedure can be extended with custom metrics checks, +webhooks, +manual promotion approval and +Slack or MS Teams notifications.

    + + + diff --git a/tutorials/nginx-progressive-delivery.html b/tutorials/nginx-progressive-delivery.html new file mode 100644 index 00000000..9ab0c9e5 --- /dev/null +++ b/tutorials/nginx-progressive-delivery.html @@ -0,0 +1,324 @@ + + + + + + NGINX Canary Deployments | Flagger + + + + + + + + + + + + + +

    NGINX Canary Deployments

    This guide shows you how to use the NGINX ingress controller and Flagger to automate canary deployments and A/B testing.

    Flagger NGINX Ingress Controller

    Prerequisites

    Flagger requires a Kubernetes cluster v1.11 or newer and NGINX ingress 0.24 or newer.

    Install NGINX with Helm v3:

    kubectl create ns ingress-nginx
    +helm upgrade -i nginx-ingress stable/nginx-ingress \
    +--namespace ingress-nginx \
    +--set controller.stats.enabled=true \
    +--set controller.metrics.enabled=true \
    +--set controller.podAnnotations."prometheus\.io/scrape"=true \
    +--set controller.podAnnotations."prometheus\.io/port"=10254
    +

    Install Flagger and the Prometheus add-on in the same namespace as NGINX:

    helm repo add flagger https://flagger.app
    +
    +helm upgrade -i flagger flagger/flagger \
    +--namespace ingress-nginx \
    +--set prometheus.install=true \
    +--set meshProvider=nginx
    +

    Optionally you can enable Slack notifications:

    helm upgrade -i flagger flagger/flagger \
    +--reuse-values \
    +--namespace ingress-nginx \
    +--set slack.url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
    +--set slack.channel=general \
    +--set slack.user=flagger
    +

    Bootstrap

    Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), +then creates a series of objects (Kubernetes deployments, ClusterIP services and canary ingress). +These objects expose the application outside the cluster and drive the canary analysis and promotion.

    Create a test namespace:

    kubectl create ns test
    +

    Create a deployment and a horizontal pod autoscaler:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo
    +

    Deploy the load testing service to generate traffic during the canary analysis:

    helm upgrade -i flagger-loadtester flagger/loadtester \
    +--namespace=test
    +

    Create an ingress definition (replace app.example.com with your own domain):

    apiVersion: extensions/v1beta1
    +kind: Ingress
    +metadata:
    +  name: podinfo
    +  namespace: test
    +  labels:
    +    app: podinfo
    +  annotations:
    +    kubernetes.io/ingress.class: "nginx"
    +spec:
    +  rules:
    +    - host: app.example.com
    +      http:
    +        paths:
    +          - backend:
    +              serviceName: podinfo
    +              servicePort: 80
    +

    Save the above resource as podinfo-ingress.yaml and then apply it:

    kubectl apply -f ./podinfo-ingress.yaml
    +

    Create a canary custom resource (replace app.example.com with your own domain):

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +  namespace: test
    +spec:
    +  provider: nginx
    +  # deployment reference
    +  targetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: podinfo
    +  # ingress reference
    +  ingressRef:
    +    apiVersion: extensions/v1beta1
    +    kind: Ingress
    +    name: podinfo
    +  # HPA reference (optional)
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +  # the maximum time in seconds for the canary deployment
    +  # to make progress before it is rollback (default 600s)
    +  progressDeadlineSeconds: 60
    +  service:
    +    # ClusterIP port number
    +    port: 80
    +    # container port number or name
    +    targetPort: 9898
    +  analysis:
    +    # 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
    +    # NGINX Prometheus checks
    +    metrics:
    +    - name: request-success-rate
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    # testing (optional)
    +    webhooks:
    +      - name: acceptance-test
    +        type: pre-rollout
    +        url: http://flagger-loadtester.test/
    +        timeout: 30s
    +        metadata:
    +          type: bash
    +          cmd: "curl -sd 'test' http://podinfo-canary/token | grep token"
    +      - name: load-test
    +        url: http://flagger-loadtester.test/
    +        timeout: 5s
    +        metadata:
    +          cmd: "hey -z 1m -q 10 -c 2 http://app.example.com/"
    +

    Save the above resource as podinfo-canary.yaml and then apply it:

    kubectl apply -f ./podinfo-canary.yaml
    +

    After a couple of seconds Flagger will create the canary objects:

    # applied 
    +deployment.apps/podinfo
    +horizontalpodautoscaler.autoscaling/podinfo
    +ingresses.extensions/podinfo
    +canary.flagger.app/podinfo
    +
    +# generated 
    +deployment.apps/podinfo-primary
    +horizontalpodautoscaler.autoscaling/podinfo-primary
    +service/podinfo
    +service/podinfo-canary
    +service/podinfo-primary
    +ingresses.extensions/podinfo-canary
    +

    Automated canary promotion

    Flagger implements a control loop that gradually shifts traffic to the canary while measuring +key performance indicators like HTTP requests success rate, requests average duration and pod health. +Based on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack or MS Teams.

    Flagger Canary Stages

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.1
    +

    Flagger detects that the deployment revision changed and starts a new rollout:

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         0
    +  Phase:                 Succeeded
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  New revision detected podinfo.test
    +  Normal   Synced  3m    flagger  Scaling up podinfo.test
    +  Warning  Synced  3m    flagger  Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 5
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 15
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary weight 20
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary weight 25
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary weight 30
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary weight 35
    +  Normal   Synced  55s   flagger  Advance podinfo.test canary weight 40
    +  Normal   Synced  45s   flagger  Advance podinfo.test canary weight 45
    +  Normal   Synced  35s   flagger  Advance podinfo.test canary weight 50
    +  Normal   Synced  25s   flagger  Copying podinfo.test template spec to podinfo-primary.test
    +  Warning  Synced  15s   flagger  Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    +  Normal   Synced  5s    flagger  Promotion completed! Scaling down podinfo.test
    +

    Note that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis.

    You can monitor all canaries with:

    watch kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   15       2019-05-06T14:05:07Z
    +prod        frontend  Succeeded     0        2019-05-05T16:15:07Z
    +prod        backend   Failed        0        2019-05-04T17:05:07Z
    +

    Automated rollback

    During the canary analysis you can generate HTTP 500 errors to test if Flagger pauses and rolls back the faulted version.

    Trigger another canary deployment:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.2
    +

    Generate HTTP 500 errors:

    watch curl http://app.example.com/status/500
    +

    When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, +the canary is scaled to zero and the rollout is marked as failed.

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Canary Weight:         0
    +  Failed Checks:         10
    +  Phase:                 Failed
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  Starting canary deployment for podinfo.test
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 5
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary weight 15
    +  Normal   Synced  3m    flagger  Halt podinfo.test advancement success rate 69.17% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 61.39% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 55.06% < 99%
    +  Normal   Synced  2m    flagger  Halt podinfo.test advancement success rate 47.00% < 99%
    +  Normal   Synced  2m    flagger  (combined from similar events): Halt podinfo.test advancement success rate 38.08% < 99%
    +  Warning  Synced  1m    flagger  Rolling back podinfo.test failed checks threshold reached 10
    +  Warning  Synced  1m    flagger  Canary failed! Scaling down podinfo.test
    +

    Custom metrics

    The canary analysis can be extended with Prometheus queries.

    The demo app is instrumented with Prometheus so you can create a custom check that will use the +HTTP request duration histogram to validate the canary.

    Create a metric template and apply it on the cluster:

    apiVersion: flagger.app/v1beta1
    +kind: MetricTemplate
    +metadata:
    +  name: latency
    +  namespace: test
    +spec:
    +  provider:
    +    type: prometheus
    +    address: http://flagger-promethues.ingress-nginx:9090
    +  query: |
    +    histogram_quantile(0.99,
    +      sum(
    +        rate(
    +          http_request_duration_seconds_bucket{
    +            kubernetes_namespace="{{ namespace }}",
    +            kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
    +          }[1m]
    +        )
    +      ) by (le)
    +    )
    +

    Edit the canary analysis and add the latency check:

      analysis:
    +    metrics:
    +    - name: "latency"
    +      templateRef:
    +        name: latency
    +      thresholdRange:
    +        max: 0.5
    +      interval: 1m
    +

    The threshold is set to 500ms so if the average request duration in the last minute goes over half a second +then the analysis will fail and the canary will not be promoted.

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.3
    +

    Generate high response latency:

    watch curl http://app.exmaple.com/delay/2
    +

    Watch Flagger logs:

    kubectl -n nginx-ingress logs deployment/flagger -f | jq .msg
    +
    +Starting canary deployment for podinfo.test
    +Advance podinfo.test canary weight 5
    +Advance podinfo.test canary weight 10
    +Advance podinfo.test canary weight 15
    +Halt podinfo.test advancement latency 1.20 > 0.5
    +Halt podinfo.test advancement latency 1.45 > 0.5
    +Halt podinfo.test advancement latency 1.60 > 0.5
    +Halt podinfo.test advancement latency 1.69 > 0.5
    +Halt podinfo.test advancement latency 1.70 > 0.5
    +Rolling back podinfo.test failed checks threshold reached 5
    +Canary failed! Scaling down podinfo.test
    +

    If you have alerting configured, Flagger will send a notification with the reason why the canary failed.

    A/B Testing

    Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions. +In an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users. +This is particularly useful for frontend applications that require session affinity.

    Flagger A/B Testing Stages

    Edit the canary analysis, remove the max/step weight and add the match conditions and iterations:

      analysis:
    +    interval: 1m
    +    threshold: 10
    +    iterations: 10
    +    match:
    +      # curl -H 'X-Canary: insider' http://app.example.com
    +      - headers:
    +          x-canary:
    +            exact: "insider"
    +      # curl -b 'canary=always' http://app.example.com
    +      - headers:
    +          cookie:
    +            exact: "canary"
    +    metrics:
    +    - name: request-success-rate
    +      thresholdRange:
    +        min: 99
    +      interval: 1m
    +    webhooks:
    +      - name: load-test
    +        url: http://flagger-loadtester.test/
    +        timeout: 5s
    +        metadata:
    +          cmd: "hey -z 1m -q 10 -c 2 -H 'Cookie: canary=always' http://app.example.com/"
    +

    The above configuration will run an analysis for ten minutes targeting users that have a canary cookie +set to always or those that call the service using the X-Canary: insider header.

    Trigger a canary deployment by updating the container image:

    kubectl -n test set image deployment/podinfo \
    +podinfod=stefanprodan/podinfo:3.1.4
    +

    Flagger detects that the deployment revision changed and starts the A/B testing:

    kubectl -n test describe canary/podinfo
    +
    +Status:
    +  Failed Checks:         0
    +  Phase:                 Succeeded
    +Events:
    +  Type     Reason  Age   From     Message
    +  ----     ------  ----  ----     -------
    +  Normal   Synced  3m    flagger  New revision detected podinfo.test
    +  Normal   Synced  3m    flagger  Scaling up podinfo.test
    +  Warning  Synced  3m    flagger  Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 1/10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 2/10
    +  Normal   Synced  3m    flagger  Advance podinfo.test canary iteration 3/10
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary iteration 4/10
    +  Normal   Synced  2m    flagger  Advance podinfo.test canary iteration 5/10
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary iteration 6/10
    +  Normal   Synced  1m    flagger  Advance podinfo.test canary iteration 7/10
    +  Normal   Synced  55s   flagger  Advance podinfo.test canary iteration 8/10
    +  Normal   Synced  45s   flagger  Advance podinfo.test canary iteration 9/10
    +  Normal   Synced  35s   flagger  Advance podinfo.test canary iteration 10/10
    +  Normal   Synced  25s   flagger  Copying podinfo.test template spec to podinfo-primary.test
    +  Warning  Synced  15s   flagger  Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available
    +  Normal   Synced  5s    flagger  Promotion completed! Scaling down podinfo.test
    +

    The above procedure can be extended with custom metrics checks, +webhooks, +manual promotion approval and +Slack or MS Teams notifications.

    + + + diff --git a/tutorials/zero-downtime-deployments.html b/tutorials/zero-downtime-deployments.html new file mode 100644 index 00000000..3875ed89 --- /dev/null +++ b/tutorials/zero-downtime-deployments.html @@ -0,0 +1,140 @@ + + + + + + Zero downtime deployments | Flagger + + + + + + + + + + + + + +

    Zero downtime deployments

    This is a list of things you should consider when dealing with a high traffic production environment if you want to minimise the impact of rolling updates and downscaling.

    Deployment strategy

    Limit the number of unavailable pods during a rolling update:

    apiVersion: apps/v1
    +kind: Deployment
    +spec:
    +  progressDeadlineSeconds: 120
    +  strategy:
    +    type: RollingUpdate
    +    rollingUpdate:
    +      maxUnavailable: 0
    +

    The default progress deadline for a deployment is ten minutes. You should consider adjusting this value to make the deployment process fail faster.

    Liveness health check

    You application should expose a HTTP endpoint that Kubernetes can call to determine if your app transitioned to a broken state from which it can't recover and needs to be restarted.

    livenessProbe:
    +  exec:
    +    command:
    +    - wget
    +    - --quiet
    +    - --tries=1
    +    - --timeout=4
    +    - --spider
    +    - http://localhost:8080/healthz
    +  timeoutSeconds: 5
    +  initialDelaySeconds: 5
    +

    If you've enabled mTLS, you'll have to use exec for liveness and readiness checks since kubelet is not part of the service mesh and doesn't have access to the TLS cert.

    Readiness health check

    You application should expose a HTTP endpoint that Kubernetes can call to determine if your app is ready to receive traffic.

    readinessProbe:
    +  exec:
    +    command:
    +    - wget
    +    - --quiet
    +    - --tries=1
    +    - --timeout=4
    +    - --spider
    +    - http://localhost:8080/readyz
    +  timeoutSeconds: 5
    +  initialDelaySeconds: 5
    +  periodSeconds: 5
    +

    If your app depends on external services, you should check if those services are available before allowing Kubernetes to route traffic to an app instance. Keep in mind that the Envoy sidecar can have a slower startup than your app. This means that on application start you should retry for at least a couple of seconds any external connection.

    Graceful shutdown

    Before a pod gets terminated, Kubernetes sends a SIGTERM signal to every container and waits for period of time (30s by default) for all containers to exit gracefully. If your app doesn't handle the SIGTERM signal or if it doesn't exit within the grace period, Kubernetes will kill the container and any inflight requests that your app is processing will fail.

    apiVersion: apps/v1
    +kind: Deployment
    +spec:
    +  template:
    +    spec:
    +      terminationGracePeriodSeconds: 60
    +      containers:
    +      - name: app
    +        lifecycle:
    +          preStop:
    +            exec:
    +              command:
    +              - sleep
    +              - "10"
    +

    Your app container should have a preStop hook that delays the container shutdown. This will allow the service mesh to drain the traffic and remove this pod from all other Envoy sidecars before your app becomes unavailable.

    Delay Envoy shutdown

    Even if your app reacts to SIGTERM and tries to complete the inflight requests before shutdown, that doesn't mean that the response will make it back to the caller. If the Envoy sidecar shuts down before your app, then the caller will receive a 503 error.

    To mitigate this issue you can add a preStop hook to the Istio proxy and wait for the main app to exist before Envoy exists.

    #!/bin/bash
    +set -e
    +if ! pidof envoy &>/dev/null; then
    +  exit 0
    +fi
    +
    +if ! pidof pilot-agent &>/dev/null; then
    +  exit 0
    +fi
    +
    +while [ $(netstat -plunt | grep tcp | grep -v envoy | wc -l | xargs) -ne 0 ]; do
    +  sleep 1;
    +done
    +
    +exit 0
    +

    You'll have to build your own Envoy docker image with the above script and modify the Istio injection webhook with the preStop directive.

    Thanks to Stono for his excellent tips on minimising 503s.

    Resource requests and limits

    Setting CPU and memory requests/limits for all workloads is a mandatory step if you're running a production system. Without limits your nodes could run out of memory or become unresponsive due to CPU exhausting. Without CPU and memory requests, the Kubernetes scheduler will not be able to make decisions about which nodes to place pods on.

    apiVersion: apps/v1
    +kind: Deployment
    +spec:
    +  template:
    +    spec:
    +      containers:
    +      - name: app
    +        resources:
    +          limits:
    +            cpu: 1000m
    +            memory: 1Gi
    +          requests:
    +            cpu: 100m
    +            memory: 128Mi
    +

    Note that without resource requests the horizontal pod autoscaler can't determine when to scale your app.

    Autoscaling

    A production environment should be able to handle traffic bursts without impacting the quality of service. This can be achieved with Kubernetes autoscaling capabilities. Autoscaling in Kubernetes has two dimensions: the Cluster Autoscaler that deals with node scaling operations and the Horizontal Pod Autoscaler that automatically scales the number of pods in a deployment.

    apiVersion: autoscaling/v2beta1
    +kind: HorizontalPodAutoscaler
    +spec:
    +  scaleTargetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: app
    +  minReplicas: 2
    +  maxReplicas: 4
    +  metrics:
    +  - type: Resource
    +    resource:
    +      name: cpu
    +      targetAverageValue: 900m
    +  - type: Resource
    +    resource:
    +      name: memory
    +      targetAverageValue: 768Mi
    +

    The above HPA ensures your app will be scaled up before the pods reach the CPU or memory limits.

    Ingress retries

    To minimise the impact of downscaling operations you can make use of Envoy retry capabilities.

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +spec:
    +  service:
    +    port: 9898
    +    gateways:
    +    - public-gateway.istio-system.svc.cluster.local
    +    hosts:
    +    - app.example.com
    +    retries:
    +      attempts: 10
    +      perTryTimeout: 5s
    +      retryOn: "gateway-error,connect-failure,refused-stream"
    +

    When the HPA scales down your app, your users could run into 503 errors. The above configuration will make Envoy retry the HTTP requests that failed due to gateway errors.

    + + + diff --git a/usage/alerting.html b/usage/alerting.html new file mode 100644 index 00000000..c97c98fc --- /dev/null +++ b/usage/alerting.html @@ -0,0 +1,106 @@ + + + + + + Alerting | Flagger + + + + + + + + + + + + + +

    Alerting

    Flagger can be configured to send alerts to various chat platforms. You can define a global alert provider at +install time or configure alerts on a per canary basis.

    Global configuration

    Flagger can be configured to send Slack notifications:

    helm upgrade -i flagger flagger/flagger \
    +--set slack.url=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
    +--set slack.channel=general \
    +--set slack.user=flagger
    +

    Once configured with a Slack incoming webhook, Flagger will post messages when a canary deployment +has been initialised, when a new revision has been detected and if the canary analysis failed or succeeded.

    Slack Notifications

    A canary deployment will be rolled back if the progress deadline exceeded or if the analysis reached the +maximum number of failed checks:

    Slack Notifications

    Flagger can be configured to send notifications to Microsoft Teams:

    helm upgrade -i flagger flagger/flagger \
    +--set msteams.url=https://outlook.office.com/webhook/YOUR/TEAMS/WEBHOOK
    +

    Similar to Slack, Flagger alerts on canary analysis events:

    MS Teams Notifications

    MS Teams Notifications

    Canary configuration

    Configuring alerting globally has several limitations as it's not possible to specify different channels +or configure the verbosity on a per canary basis. +To make the alerting move flexible, the canary analysis can be extended +with a list of alerts that reference an alert provider. +For each alert, users can configure the severity level. +The alerts section overrides the global setting.

    Slack example:

    apiVersion: flagger.app/v1beta1
    +kind: AlertProvider
    +metadata:
    +  name: on-call
    +  namespace: flagger
    +spec:
    +  type: slack
    +  channel: on-call-alerts
    +  username: flagger
    +  # webhook address (ignored if secretRef is specified)
    +  address: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
    +  # secret containing the webhook address (optional)
    +  secretRef:
    +    name: on-call-url
    +---
    +apiVersion: v1
    +kind: Secret
    +metadata:
    +  name: on-call-url
    +  namespace: flagger
    +data:
    +  address: <encoded-url>
    +

    The alert provider type can be: slack, msteams, rocket or discord. When set to discord, +Flagger will use Slack formatting +and will append /slack to the Discord address.

    When not specified, channel defaults to general and username defaults to flagger.

    When secretRef is specified, the Kubernetes secret must contain a data field named address, +the address in the secret will take precedence over the address field in the provider spec.

    The canary analysis can have a list of alerts, each alert referencing an alert provider:

      analysis:
    +    alerts:
    +      - name: "on-call Slack"
    +        severity: error
    +        providerRef:
    +          name: on-call
    +          namespace: flagger
    +      - name: "qa Discord"
    +        severity: warn
    +        providerRef:
    +          name: qa-discord
    +      - name: "dev MS Teams"
    +        severity: info
    +        providerRef:
    +          name: dev-msteams
    +

    Alert fields:

    • name (required)
    • severity levels: info, warn, error (default info)
    • providerRef.name alert provider name (required)
    • providerRef.namespace alert provider namespace (defaults to the canary namespace)

    When the severity is set to warn, Flagger will alert when waiting on manual confirmation or if the analysis fails. +When the severity is set to error, Flagger will alert only if the canary analysis fails.

    Prometheus Alert Manager

    You can use Alertmanager to trigger alerts when a canary deployment failed:

      - alert: canary_rollback
    +    expr: flagger_canary_status > 1
    +    for: 1m
    +    labels:
    +      severity: warning
    +    annotations:
    +      summary: "Canary failed"
    +      description: "Workload {{ $labels.name }} namespace {{ $labels.namespace }}"
    +
    + + + diff --git a/usage/deployment-strategies.html b/usage/deployment-strategies.html new file mode 100644 index 00000000..c62c38cf --- /dev/null +++ b/usage/deployment-strategies.html @@ -0,0 +1,160 @@ + + + + + + Deployment Strategies | Flagger + + + + + + + + + + + + + +

    Deployment Strategies

    Flagger can run automated application analysis, promotion and rollback for the following deployment strategies:

    • Canary Release (progressive traffic shifting) +
      • Istio, Linkerd, App Mesh, NGINX, Contour, Gloo
    • A/B Testing (HTTP headers and cookies traffic routing) +
      • Istio, App Mesh, NGINX, Contour
    • Blue/Green (traffic switching) +
      • Kubernetes CNI, Istio, Linkerd, App Mesh, NGINX, Contour, Gloo
    • Blue/Green Mirroring (traffic shadowing) +
      • Istio

    For Canary releases and A/B testing you'll need a Layer 7 traffic management solution like a service mesh or an ingress controller. +For Blue/Green deployments no service mesh or ingress controller is required.

    A canary analysis is triggered by changes in any of the following objects:

    • Deployment PodSpec (container image, command, ports, env, resources, etc)
    • ConfigMaps mounted as volumes or mapped to environment variables
    • Secrets mounted as volumes or mapped to environment variables

    Canary Release

    Flagger implements a control loop that gradually shifts traffic to the canary while measuring key performance +indicators like HTTP requests success rate, requests average duration and pod health. +Based on analysis of the KPIs a canary is promoted or aborted.

    Flagger Canary Stages

    The canary analysis runs periodically until it reaches the maximum traffic weight or the failed checks threshold.

    Spec:

      analysis:
    +    # schedule interval (default 60s)
    +    interval: 1m
    +    # 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: 2
    +  # deploy straight to production without
    +  # the metrics and webhook checks
    +  skipAnalysis: false
    +

    The above analysis, if it succeeds, will run for 25 minutes while validating the HTTP metrics and webhooks every minute. +You can determine the minimum time that it takes to validate and promote a canary deployment using this formula:

    interval * (maxWeight / stepWeight)
    +

    And the time it takes for a canary to be rollback when the metrics or webhook checks are failing:

    interval * threshold 
    +

    In emergency cases, you may want to skip the analysis phase and ship changes directly to production. +At any time you can set the spec.skipAnalysis: true. +When skip analysis is enabled, Flagger checks if the canary deployment is healthy and +promotes it without analysing it. If an analysis is underway, Flagger cancels it and runs the promotion.

    Gated canary promotion stages:

    • scan for canary deployments
    • check primary and canary deployment status +
      • halt advancement if a rolling update is underway
      • halt advancement if pods are unhealthy
    • call confirm-rollout webhooks and check results +
      • halt advancement if any hook returns a non HTTP 2xx result
    • call pre-rollout webhooks and check results +
      • halt advancement if any hook returns a non HTTP 2xx result
      • increment the failed checks counter
    • increase canary traffic weight percentage from 0% to 2% (step weight)
    • call rollout webhooks and check results
    • check canary HTTP request success rate and latency +
      • halt advancement if any metric is under the specified threshold
      • increment the failed checks counter
    • check if the number of failed checks reached the threshold +
      • route all traffic to primary
      • scale to zero the canary deployment and mark it as failed
      • call post-rollout webhooks
      • post the analysis result to Slack
      • wait for the canary deployment to be updated and start over
    • increase canary traffic weight by 2% (step weight) till it reaches 50% (max weight) +
      • halt advancement if any webhook call fails
      • halt advancement while canary request success rate is under the threshold
      • halt advancement while canary request duration P99 is over the threshold
      • halt advancement while any custom metric check fails
      • halt advancement if the primary or canary deployment becomes unhealthy
      • halt advancement while canary deployment is being scaled up/down by HPA
    • call confirm-promotion webhooks and check results +
      • halt advancement if any hook returns a non HTTP 2xx result
    • promote canary to primary +
      • copy ConfigMaps and Secrets from canary to primary
      • copy canary deployment spec template over primary
    • wait for primary rolling update to finish +
      • halt advancement if pods are unhealthy
    • route all traffic to primary
    • scale to zero the canary deployment
    • mark rollout as finished
    • call post-rollout webhooks
    • send notification with the canary analysis result
    • wait for the canary deployment to be updated and start over

    A/B Testing

    For frontend applications that require session affinity you should use HTTP headers or cookies match conditions +to ensure a set of users will stay on the same version for the whole duration of the canary analysis.

    Flagger A/B Testing Stages

    You can enable A/B testing by specifying the HTTP match conditions and the number of iterations. +If Flagger finds a HTTP match condition, it will ignore the maxWeight and stepWeight settings.

    Istio example:

      analysis:
    +    # schedule interval (default 60s)
    +    interval: 1m
    +    # total number of iterations
    +    iterations: 10
    +    # max number of failed iterations before rollback
    +    threshold: 2
    +    # canary match condition
    +    match:
    +      - headers:
    +          x-canary:
    +            regex: ".*insider.*"
    +      - headers:
    +          cookie:
    +            regex: "^(.*?;)?(canary=always)(;.*)?$"
    +

    The above configuration will run an analysis for ten minutes targeting the Safari users and those that have a test cookie. +You can determine the minimum time that it takes to validate and promote a canary deployment using this formula:

    interval * iterations
    +

    And the time it takes for a canary to be rollback when the metrics or webhook checks are failing:

    interval * threshold 
    +

    App Mesh example:

      analysis:
    +    interval: 1m
    +    threshold: 10
    +    iterations: 2
    +    match:
    +      - headers:
    +          user-agent:
    +            regex: ".*Chrome.*"
    +

    Note that App Mesh supports a single condition.

    Contour example:

      analysis:
    +    interval: 1m
    +    threshold: 10
    +    iterations: 2
    +    match:
    +      - headers:
    +          user-agent:
    +            prefix: "Chrome"
    +

    Note that Contour does not support regex, you can use prefix, suffix or exact.

    NGINX example:

      analysis:
    +    interval: 1m
    +    threshold: 10
    +    iterations: 2
    +    match:
    +      - headers:
    +          x-canary:
    +            exact: "insider"
    +      - headers:
    +          cookie:
    +            exact: "canary"
    +

    Note that the NGINX ingress controller supports only exact matching for a single header and the cookie value is set to always.

    The above configurations will route users with the x-canary header or canary cookie to the canary instance during analysis:

    curl -H 'X-Canary: insider' http://app.example.com
    +curl -b 'canary=always' http://app.example.com
    +

    Blue/Green Deployments

    For applications that are not deployed on a service mesh, Flagger can orchestrate blue/green style deployments +with Kubernetes L4 networking. When using Istio you have the option to mirror traffic between blue and green.

    Flagger Blue/Green Stages

    You can use the blue/green deployment strategy by replacing stepWeight/maxWeight with iterations in the canaryAnalysis spec:

      analysis:
    +    # schedule interval (default 60s)
    +    interval: 1m
    +    # total number of iterations
    +    iterations: 10
    +    # max number of failed iterations before rollback
    +    threshold: 2
    +

    With the above configuration Flagger will run conformance and load tests on the canary pods for ten minutes. +If the metrics analysis succeeds, live traffic will be switched from the old version to the new one when the +canary is promoted.

    The blue/green deployment strategy is supported for all service mesh providers.

    Blue/Green rollout steps for service mesh:

    • detect new revision (deployment spec, secrets or configmaps changes)
    • scale up the canary (green)
    • run conformance tests for the canary pods
    • run load tests and metric checks for the canary pods every minute
    • abort the canary release if the failure threshold is reached
    • route traffic to canary
    • promote canary spec over primary (blue)
    • wait for primary rollout
    • route traffic to primary
    • scale down canary

    After the analysis finishes, the traffic is routed to the canary (green) before triggering the primary (blue) +rolling update, this ensures a smooth transition to the new version avoiding dropping in-flight requests during +the Kubernetes deployment rollout.

    Blue/Green with Traffic Mirroring

    Traffic Mirroring is a pre-stage in a Canary (progressive traffic shifting) or +Blue/Green deployment strategy. Traffic mirroring will copy each incoming +request, sending one request to the primary and one to the canary service. +The response from the primary is sent back to the user. The response from the canary +is discarded. Metrics are collected on both requests so that the deployment will +only proceed if the canary metrics are healthy.

    Mirroring should be used for requests that are idempotent or capable of +being processed twice (once by the primary and once by the canary). Reads are +idempotent. Before using mirroring on requests that may be writes, you should +consider what will happen if a write is duplicated and handled by the primary +and canary.

    To use mirroring, set spec.canaryAnalysis.mirror to true.

    Istio example:

      analysis:
    +    # schedule interval (default 60s)
    +    interval: 1m
    +    # total number of iterations
    +    iterations: 10
    +    # max number of failed iterations before rollback
    +    threshold: 2
    +    # Traffic shadowing (compatible with Istio only)
    +    mirror: true
    +

    Mirroring rollout steps for service mesh:

    • detect new revision (deployment spec, secrets or configmaps changes)
    • scale from zero the canary deployment
    • wait for the HPA to set the canary minimum replicas
    • check canary pods health
    • run the acceptance tests
    • abort the canary release if tests fail
    • start the load tests
    • mirror traffic from primary to canary
    • check request success rate and request duration every minute
    • abort the canary release if the failure threshold is reached
    • stop traffic mirroring after the number of iterations is reached
    • route live traffic to the canary pods
    • promote the canary (update the primary secrets, configmaps and deployment spec)
    • wait for the primary deployment rollout to finish
    • wait for the HPA to set the primary minimum replicas
    • check primary pods health
    • switch live traffic back to primary
    • scale to zero the canary
    • send notification with the canary analysis result

    After the analysis finishes, the traffic is routed to the canary (green) before triggering the primary (blue) +rolling update, this ensures a smooth transition to the new version avoiding dropping in-flight requests during +the Kubernetes deployment rollout.

    + + + diff --git a/usage/how-it-works.html b/usage/how-it-works.html new file mode 100644 index 00000000..9c9ce86a --- /dev/null +++ b/usage/how-it-works.html @@ -0,0 +1,213 @@ + + + + + + How it works | Flagger + + + + + + + + + + + + + +

    How it works

    Flagger can be configured to automate the release process +for Kubernetes workloads with a custom resource named canary.

    Canary resource

    The canary custom resource defines the release process of an application running on Kubernetes +and is portable across clusters, service meshes and ingress providers.

    For a deployment named podinfo, a canary release with progressive traffic shifting can be defined as:

    apiVersion: flagger.app/v1beta1
    +kind: Canary
    +metadata:
    +  name: podinfo
    +spec:
    +  targetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: podinfo
    +  service:
    +    port: 9898
    +  analysis:
    +    interval: 1m
    +    threshold: 10
    +    maxWeight: 50
    +    stepWeight: 5
    +    metrics:
    +      - name: request-success-rate
    +        thresholdRange:
    +          min: 99
    +        interval: 1m
    +      - name: request-duration
    +        thresholdRange:
    +          max: 500
    +        interval: 1m
    +    webhooks:
    +      - name: load-test
    +        url: http://flagger-loadtester.test/
    +        metadata:
    +          cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"
    +

    When you deploy a new version of an app, Flagger gradually shifts traffic to the canary, +and at the same time, measures the requests success rate as well as the average response duration. +You can extend the canary analysis with custom metrics, acceptance and load testing +to harden the validation process of your app release process.

    If you are running multiple service meshes or ingress controllers in the same cluster, +you can override the global provider for a specific canary with spec.provider.

    Canary target

    A canary resource can target a Kubernetes Deployment or DaemonSet.

    Kubernetes Deployment example:

    spec:
    +  progressDeadlineSeconds: 60
    +  targetRef:
    +    apiVersion: apps/v1
    +    kind: Deployment
    +    name: podinfo
    +  autoscalerRef:
    +    apiVersion: autoscaling/v2beta1
    +    kind: HorizontalPodAutoscaler
    +    name: podinfo
    +

    Based on the above configuration, Flagger generates the following Kubernetes objects:

    • deployment/<targetRef.name>-primary
    • hpa/<autoscalerRef.name>-primary

    The primary deployment is considered the stable release of your app, by default all traffic is routed to this version +and the target deployment is scaled to zero. +Flagger will detect changes to the target deployment (including secrets and configmaps) and will perform a +canary analysis before promoting the new version as primary.

    If the target deployment uses secrets and/or configmaps, Flagger will create a copy of each object using the -primary +prefix and will reference these objects in the primary deployment. You can disable the secrets/configmaps tracking +with the -enable-config-tracking=false command flag in the Flagger deployment manifest under containers args +or by setting --set configTracking.enabled=false when installing Flagger with Helm.

    Note that the target deployment must have a single label selector in the format app: <DEPLOYMENT-NAME>:

    apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: podinfo
    +spec:
    +  selector:
    +    matchLabels:
    +      app: podinfo
    +  template:
    +    metadata:
    +      labels:
    +        app: podinfo
    +

    Besides app Flagger supports name and app.kubernetes.io/name selectors. +If you use a different convention you can specify your label with +the -selector-labels=my-app-label command flag in the Flagger deployment manifest under containers args +or by setting --set selectorLabels=my-app-label when installing Flagger with Helm.

    The autoscaler reference is optional, when specified, Flagger will pause the traffic increase while the +target and primary deployments are scaled up or down. HPA can help reduce the resource usage during the canary analysis.

    The progress deadline represents the maximum time in seconds for the canary deployment to make progress +before it is rolled back, defaults to ten minutes.

    Canary service

    A canary resource dictates how the target workload is exposed inside the cluster. +The canary target should expose a TCP port that will be used by Flagger to create the ClusterIP Services.

    spec:
    +  service:
    +    name: podinfo
    +    port: 9898
    +    portName: http
    +    targetPort: 9898
    +    portDiscovery: true
    +

    The container port from the target workload should match the service.port or service.targetPort. +The service.name is optional, defaults to spec.targetRef.name. +The service.targetPort can be a container port number or name. +The service.portName is optional (defaults to http), if your workload uses gPRC then set the port name to grcp.

    If port discovery is enabled, Flagger scans the target workload and extracts the containers +ports excluding the port specified in the canary service and service mesh sidecar ports. +These ports will be used when generating the ClusterIP services.

    Based on the canary spec service, Flagger creates the following Kubernetes ClusterIP service:

    • <service.name>.<namespace>.svc.cluster.local
      +selector app=<name>-primary
    • <service.name>-primary.<namespace>.svc.cluster.local
      +selector app=<name>-primary
    • <service.name>-canary.<namespace>.svc.cluster.local
      +selector app=<name>

    This ensures that traffic to podinfo.test:9898 will be routed to the latest stable release of your app. +The podinfo-canary.test:9898 address is available only during the +canary analysis and can be used for conformance testing or load testing.

    Besides the port mapping, the service specification can contain URI match and rewrite rules, +timeout and retry polices:

    spec:
    +  service:
    +    port: 9898
    +    match:
    +      - uri:
    +          prefix: /
    +    rewrite:
    +      uri: /
    +    retries:
    +      attempts: 3
    +      perTryTimeout: 1s
    +    timeout: 5s
    +

    When using Istio as the mesh provider, you can also specify +HTTP header operations, CORS and traffic policies, Istio gateways and hosts. +The Istio routing configuration can be found here.

    Canary status

    You can use kubectl to get the current status of canary deployments cluster wide:

    kubectl get canaries --all-namespaces
    +
    +NAMESPACE   NAME      STATUS        WEIGHT   LASTTRANSITIONTIME
    +test        podinfo   Progressing   15       2019-06-30T14:05:07Z
    +prod        frontend  Succeeded     0        2019-06-30T16:15:07Z
    +prod        backend   Failed        0        2019-06-30T17:05:07Z
    +

    The status condition reflects the last known state of the canary analysis:

    kubectl -n test get canary/podinfo -oyaml | awk '/status/,0'
    +

    A successful rollout status:

    status:
    +  canaryWeight: 0
    +  failedChecks: 0
    +  iterations: 0
    +  lastAppliedSpec: "14788816656920327485"
    +  lastPromotedSpec: "14788816656920327485"
    +  conditions:
    +  - lastTransitionTime: "2019-07-10T08:23:18Z"
    +    lastUpdateTime: "2019-07-10T08:23:18Z"
    +    message: Canary analysis completed successfully, promotion finished.
    +    reason: Succeeded
    +    status: "True"
    +    type: Promoted
    +

    The Promoted status condition can have one of the following reasons: +Initialized, Waiting, Progressing, Promoting, Finalising, Succeeded or Failed. +A failed canary will have the promoted status set to false, +the reason to failed and the last applied spec will be different to the last promoted one.

    Wait for a successful rollout:

    kubectl wait canary/podinfo --for=condition=promoted
    +

    CI example:

    # update the container image
    +kubectl set image deployment/podinfo podinfod=stefanprodan/podinfo:3.0.1
    +
    +# wait for Flagger to detect the change
    +ok=false
    +until ${ok}; do
    +    kubectl get canary/podinfo | grep 'Progressing' && ok=true || ok=false
    +    sleep 5
    +done
    +
    +# wait for the canary analysis to finish
    +kubectl wait canary/podinfo --for=condition=promoted --timeout=5m
    +
    +# check if the deployment was successful 
    +kubectl get canary/podinfo | grep Succeeded
    +

    Canary analysis

    The canary analysis defines:

    Spec:

      analysis:
    +    # schedule interval (default 60s)
    +    interval:
    +    # max number of failed metric checks before rollback
    +    threshold:
    +    # max traffic percentage routed to canary
    +    # percentage (0-100)
    +    maxWeight:
    +    # canary increment step
    +    # percentage (0-100)
    +    stepWeight:
    +    # total number of iterations
    +    # used for A/B Testing and Blue/Green
    +    iterations:
    +    # canary match conditions
    +    # used for A/B Testing
    +    match:
    +      - # HTTP header
    +    # key performance indicators
    +    metrics:
    +      - # metric check
    +    # alerting
    +    alerts:
    +      - # alert provider
    +    # external checks
    +    webhooks:
    +      - # hook
    +

    The canary analysis runs periodically until it reaches the maximum traffic weight or the number of iterations. +On each run, Flagger calls the webhooks, checks the metrics and if the failed checks threshold is reached, stops the +analysis and rolls back the canary. If alerting is configured, Flagger will post the analysis result using the alert providers.

    + + + diff --git a/usage/index.html b/usage/index.html new file mode 100644 index 00000000..882ef551 --- /dev/null +++ b/usage/index.html @@ -0,0 +1,35 @@ + + + + + + Flagger + + + + + + + + + + + + + + + + + diff --git a/usage/metrics.html b/usage/metrics.html new file mode 100644 index 00000000..4d285ffc --- /dev/null +++ b/usage/metrics.html @@ -0,0 +1,265 @@ + + + + + + Metrics Analysis | Flagger + + + + + + + + + + + + + +

    Metrics Analysis

    As part of the analysis process, Flagger can validate service level objectives (SLOs) like +availability, error rate percentage, average response time and any other objective based on app specific metrics. +If a drop in performance is noticed during the SLOs analysis, +the release will be automatically rolled back with minimum impact to end-users.

    Builtin metrics

    Flagger comes with two builtin metric checks: HTTP request success rate and duration.

      analysis:
    +    metrics:
    +    - name: request-success-rate
    +      interval: 1m
    +      # minimum req success rate (non 5xx responses)
    +      # percentage (0-100)
    +      thresholdRange:
    +        min: 99
    +    - name: request-duration
    +      interval: 1m
    +      # maximum req duration P99
    +      # milliseconds
    +      thresholdRange:
    +        max: 500
    +

    For each metric you can specify a range of accepted values with thresholdRange +and the window size or the time series with interval. +The builtin checks are available for every service mesh / ingress controller +and are implemented with Prometheus queries.

    Custom metrics

    The canary analysis can be extended with custom metric checks. Using a MetricTemplate custom resource, you +configure Flagger to connect to a metric provider and run a query that returns a float64 value. +The query result is used to validate the canary based on the specified threshold range.

    apiVersion: flagger.app/v1beta1
    +kind: MetricTemplate
    +metadata:
    +  name: my-metric
    +spec:
    +  provider:
    +    type: # can be prometheus or datadog
    +    address: # API URL
    +    secretRef:
    +      name: # name of the secret containing the API credentials
    +  query: # metric query
    +

    The following variables are available in query templates:

    • name (canary.metadata.name)
    • namespace (canary.metadata.namespace)
    • target (canary.spec.targetRef.name)
    • service (canary.spec.service.name)
    • ingress (canary.spec.ingresRef.name)
    • interval (canary.spec.canaryAnalysis.metrics[].interval)

    A canary analysis metric can reference a template with templateRef:

      analysis:
    +    metrics:
    +      - name: "my metric"
    +        templateRef:
    +          name: my-metric
    +          # namespace is optional
    +          # when not specified, the canary namespace will be used
    +          namespace: flagger
    +        # accepted values
    +        thresholdRange:
    +          min: 10
    +          max: 1000
    +        # metric query time window
    +        interval: 1m
    +

    Prometheus

    You can create custom metric checks targeting a Prometheus server +by setting the provider type to prometheus and writing the query in PromQL.

    Prometheus template example:

    apiVersion: flagger.app/v1beta1
    +kind: MetricTemplate
    +metadata:
    +  name: not-found-percentage
    +  namespace: istio-system
    +spec:
    +  provider:
    +    type: prometheus
    +    address: http://promethues.istio-system:9090
    +  query: |
    +    100 - sum(
    +        rate(
    +            istio_requests_total{
    +              reporter="destination",
    +              destination_workload_namespace="{{ namespace }}",
    +              destination_workload="{{ target }}",
    +              response_code!="404"
    +            }[{{ interval }}]
    +        )
    +    )
    +    /
    +    sum(
    +        rate(
    +            istio_requests_total{
    +              reporter="destination",
    +              destination_workload_namespace="{{ namespace }}",
    +              destination_workload="{{ target }}"
    +            }[{{ interval }}]
    +        )
    +    ) * 100
    +

    Reference the template in the canary analysis:

      analysis:
    +    metrics:
    +      - name: "404s percentage"
    +        templateRef:
    +          name: not-found-percentage
    +          namespace: istio-system
    +        thresholdRange:
    +          max: 5
    +        interval: 1m
    +

    The above configuration validates the canary by checking +if the HTTP 404 req/sec percentage is below 5 percent of the total traffic. +If the 404s rate reaches the 5% threshold, then the canary fails.

    Prometheus gRPC error rate example:

    apiVersion: flagger.app/v1beta1
    +kind: MetricTemplate
    +metadata:
    +  name: grpc-error-rate-percentage
    +  namespace: flagger
    +spec:
    +  provider:
    +    type: prometheus
    +    address: http://flagger-promethues.flagger-system:9090
    +  query: |
    +    100 - sum(
    +        rate(
    +            grpc_server_handled_total{
    +              grpc_code!="OK",
    +              kubernetes_namespace="{{ namespace }}",
    +              kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
    +            }[{{ interval }}]
    +        )
    +    )
    +    /
    +    sum(
    +        rate(
    +            grpc_server_started_total{
    +              kubernetes_namespace="{{ namespace }}",
    +              kubernetes_pod_name=~"{{ target }}-[0-9a-zA-Z]+(-[0-9a-zA-Z]+)"
    +            }[{{ interval }}]
    +        )
    +    ) * 100
    +

    The above template is for gPRC services instrumented with go-grpc-prometheus.

    Datadog

    You can create custom metric checks using the Datadog provider.

    Create a secret with your Datadog API credentials:

    apiVersion: v1
    +kind: Secret
    +metadata:
    +  name: datadog
    +  namespace: istio-system
    +data:
    +  datadog_api_key: your-datadog-api-key
    +  datadog_application_key: your-datadog-application-key
    +

    Datadog template example:

    apiVersion: flagger.app/v1beta1
    +kind: MetricTemplate
    +metadata:
    +  name: not-found-percentage
    +  namespace: istio-system
    +spec:
    +  provider:
    +    type: datadog
    +    address: https://api.datadoghq.com
    +    secretRef:
    +      name: datadog
    +  query: |
    +    100 - (
    +      sum:istio.mesh.request.count{
    +        reporter:destination,
    +        destination_workload_namespace:{{ namespace }},
    +        destination_workload:{{ target }},
    +        !response_code:404
    +      }.as_count()
    +      / 
    +      sum:istio.mesh.request.count{
    +        reporter:destination,
    +        destination_workload_namespace:{{ namespace }},
    +        destination_workload:{{ target }}
    +      }.as_count()
    +    ) * 100
    +

    Reference the template in the canary analysis:

      analysis:
    +    metrics:
    +      - name: "404s percentage"
    +        templateRef:
    +          name: not-found-percentage
    +          namespace: istio-system
    +        thresholdRange:
    +          max: 5
    +        interval: 1m
    +

    Amazon CloudWatch

    You can create custom metric checks using the CloudWatch metrics provider.

    CloudWatch template example:

    apiVersion: flagger.app/v1alpha1
    +kind: MetricTemplate
    +metadata:
    +  name: cloudwatch-error-rate
    +spec:
    +  provider:
    +    type: cloudwatch
    +    region: ap-northeast-1 # specify the region of your metrics
    +  query: |
    +    [
    +        {
    +            "Id": "e1",
    +            "Expression": "m1 / m2",
    +            "Label": "ErrorRate"
    +        },
    +        {
    +            "Id": "m1",
    +            "MetricStat": {
    +                "Metric": {
    +                    "Namespace": "MyKubernetesCluster",
    +                    "MetricName": "ErrorCount",
    +                    "Dimensions": [
    +                        {
    +                            "Name": "appName",
    +                            "Value": "{{ name }}.{{ namespace }}"
    +                        }
    +                    ]
    +                },
    +                "Period": 60,
    +                "Stat": "Sum",
    +                "Unit": "Count"
    +            },
    +            "ReturnData": false
    +        },
    +        {
    +            "Id": "m2",
    +            "MetricStat": {
    +                "Metric": {
    +                    "Namespace": "MyKubernetesCluster",
    +                    "MetricName": "RequestCount",
    +                    "Dimensions": [
    +                        {
    +                            "Name": "appName",
    +                            "Value": "{{ name }}.{{ namespace }}"
    +                        }
    +                    ]
    +                },
    +                "Period": 60,
    +                "Stat": "Sum",
    +                "Unit": "Count"
    +            },
    +            "ReturnData": false
    +        }
    +    ]
    +

    The query format documentation can be found here.

    Reference the template in the canary analysis:

      analysis:
    +    metrics:
    +      - name: "app error rate"
    +        templateRef:
    +          name: cloudwatch-error-rate
    +        thresholdRange:
    +          max: 0.1
    +        interval: 1m
    +

    Note that Flagger need AWS IAM permission to perform cloudwatch:GetMetricData to use this provider.

    + + + diff --git a/usage/monitoring.html b/usage/monitoring.html new file mode 100644 index 00000000..65439bf3 --- /dev/null +++ b/usage/monitoring.html @@ -0,0 +1,111 @@ + + + + + + Monitoring | Flagger + + + + + + + + + + + + + +

    Monitoring

    Grafana

    Flagger comes with a Grafana dashboard made for canary analysis. Install Grafana with Helm:

    helm upgrade -i flagger-grafana flagger/grafana \
    +--namespace=istio-system \ # or appmesh-system
    +--set url=http://prometheus:9090
    +

    The dashboard shows the RED and USE metrics for the primary and canary workloads:

    Canary Dashboard

    Logging

    The canary errors and latency spikes have been recorded as Kubernetes events and logged by Flagger in json format:

    kubectl -n istio-system logs deployment/flagger --tail=100 | jq .msg
    +
    +Starting canary deployment for podinfo.test
    +Advance podinfo.test canary weight 5
    +Advance podinfo.test canary weight 10
    +Advance podinfo.test canary weight 15
    +Advance podinfo.test canary weight 20
    +Advance podinfo.test canary weight 25
    +Advance podinfo.test canary weight 30
    +Advance podinfo.test canary weight 35
    +Halt podinfo.test advancement success rate 98.69% < 99%
    +Advance podinfo.test canary weight 40
    +Halt podinfo.test advancement request duration 1.515s > 500ms
    +Advance podinfo.test canary weight 45
    +Advance podinfo.test canary weight 50
    +Copying podinfo.test template spec to podinfo-primary.test
    +Halt podinfo-primary.test advancement waiting for rollout to finish: 1 old replicas are pending termination
    +Scaling down podinfo.test
    +Promotion completed! podinfo.test
    +

    Event Webhook

    Flagger can be configured to send event payloads to a specified webhook:

    helm upgrade -i flagger flagger/flagger \
    +--set eventWebhook=https://example.com/flagger-canary-event-webhook
    +

    The environment variable EVENT_WEBHOOK_URL can be used for activating the event-webhook, too. This is handy for using a secret to store a sensible value that could contain api keys for example.

    When configured, every action that Flagger takes during a canary deployment will be sent as JSON via an HTTP POST request. The JSON payload has the following schema:

    {
    +  "name": "string (canary name)",
    +  "namespace": "string (canary namespace)",
    +  "phase": "string (canary phase)",
    +  "metadata": {
    +    "eventMessage": "string (canary event message)",
    +    "eventType": "string (canary event type)",
    +    "timestamp": "string (unix timestamp ms)"
    +  }
    +}
    +

    Example:

    {
    +  "name": "podinfo",
    +  "namespace": "default",
    +  "phase": "Progressing",
    +  "metadata": {
    +    "eventMessage": "New revision detected! Scaling up podinfo.default",
    +    "eventType": "Normal",
    +    "timestamp": "1578607635167"
    +  }
    +}
    +

    The event webhook can be overwritten at canary level with:

      analysis:
    +    webhooks:
    +      - name: "send to Slack"
    +        type: event
    +        url: http://event-recevier.notifications/slack
    +

    Metrics

    Flagger exposes Prometheus metrics that can be used to determine the canary analysis status and the destination weight values:

    # Flagger version and mesh provider gauge
    +flagger_info{version="0.10.0", mesh_provider="istio"} 1
    +
    +# Canaries total gauge
    +flagger_canary_total{namespace="test"} 1
    +
    +# Canary promotion last known status gauge
    +# 0 - running, 1 - successful, 2 - failed
    +flagger_canary_status{name="podinfo" namespace="test"} 1
    +
    +# Canary traffic weight gauge
    +flagger_canary_weight{workload="podinfo-primary" namespace="test"} 95
    +flagger_canary_weight{workload="podinfo" namespace="test"} 5
    +
    +# Seconds spent performing canary analysis histogram
    +flagger_canary_duration_seconds_bucket{name="podinfo",namespace="test",le="10"} 6
    +flagger_canary_duration_seconds_bucket{name="podinfo",namespace="test",le="+Inf"} 6
    +flagger_canary_duration_seconds_sum{name="podinfo",namespace="test"} 17.3561329
    +flagger_canary_duration_seconds_count{name="podinfo",namespace="test"} 6
    +
    + + + diff --git a/usage/webhooks.html b/usage/webhooks.html new file mode 100644 index 00000000..915580a3 --- /dev/null +++ b/usage/webhooks.html @@ -0,0 +1,254 @@ + + + + + + Webhooks | Flagger + + + + + + + + + + + + + +

    Webhooks

    The canary analysis can be extended with webhooks. Flagger will call each webhook URL and +determine from the response status code (HTTP 2xx) if the canary is failing or not.

    There are several types of hooks:

    • confirm-rollout hooks are executed before scaling up the canary deployment and can be used for manual approval. +The rollout is paused until the hook returns a successful HTTP status code.
    • pre-rollout hooks are executed before routing traffic to canary. +The canary advancement is paused if a pre-rollout hook fails and if the number of failures reach the +threshold the canary will be rollback.
    • rollout hooks are executed during the analysis on each iteration before the metric checks. +If a rollout hook call fails the canary advancement is paused and eventfully rolled back.
    • confirm-promotion hooks are executed before the promotion step. +The canary promotion is paused until the hooks return HTTP 200. +While the promotion is paused, Flagger will continue to run the metrics checks and rollout hooks.
    • post-rollout hooks are executed after the canary has been promoted or rolled back. +If a post rollout hook fails the error is logged.
    • rollback hooks are executed while a canary deployment is in either Progressing or Waiting status. +This provides the ability to rollback during analysis or while waiting for a confirmation. If a rollback hook +returns a successful HTTP status code, Flagger will stop the analysis and mark the canary release as failed.
    • event hooks are executed every time Flagger emits a Kubernetes event. When configured, +every action that Flagger takes during a canary deployment will be sent as JSON via an HTTP POST request.

    Spec:

      analysis:
    +    webhooks:
    +      - name: "start gate"
    +        type: confirm-rollout
    +        url: http://flagger-loadtester.test/gate/approve
    +      - name: "helm test"
    +        type: pre-rollout
    +        url: http://flagger-helmtester.flagger/
    +        timeout: 3m
    +        metadata:
    +          type: "helmv3"
    +          cmd: "test podinfo -n test"
    +      - name: "load test"
    +        type: rollout
    +        url: http://flagger-loadtester.test/
    +        timeout: 15s
    +        metadata:
    +          cmd: "hey -z 1m -q 5 -c 2 http://podinfo-canary.test:9898/"
    +      - name: "promotion gate"
    +        type: confirm-promotion
    +        url: http://flagger-loadtester.test/gate/approve
    +      - name: "notify"
    +        type: post-rollout
    +        url: http://telegram.bot:8080/
    +        timeout: 5s
    +        metadata:
    +          some: "message"
    +      - name: "rollback gate"
    +        type: rollback
    +        url: http://flagger-loadtester.test/rollback/check
    +      - name: "send to Slack"
    +        type: event
    +        url: http://event-recevier.notifications/slack
    +

    Note that the sum of all rollout webhooks timeouts should be lower than the analysis interval.

    Webhook payload (HTTP POST):

    {
    +    "name": "podinfo",
    +    "namespace": "test",
    +    "phase": "Progressing", 
    +    "metadata": {
    +        "test":  "all",
    +        "token":  "16688eb5e9f289f1991c"
    +    }
    +}
    +

    Response status codes:

    • 200-202 - advance canary by increasing the traffic weight
    • timeout or non-2xx - halt advancement and increment failed checks

    On a non-2xx response Flagger will include the response body (if any) in the failed checks log and Kubernetes events.

    Event payload (HTTP POST):

    {
    +  "name": "string (canary name)",
    +  "namespace": "string (canary namespace)",
    +  "phase": "string (canary phase)",
    +  "metadata": {
    +    "eventMessage": "string (canary event message)",
    +    "eventType": "string (canary event type)",
    +    "timestamp": "string (unix timestamp ms)"
    +  }
    +}
    +

    The event receiver can create alerts based on the received phase +(possible values: Initialized, Waiting, Progressing, Promoting, Finalising, Succeeded or Failed).

    Load Testing

    For workloads that are not receiving constant traffic Flagger can be configured with a webhook, +that when called, will start a load test for the target workload. +If the target workload doesn't receive any traffic during the canary analysis, +Flagger metric checks will fail with "no values found for metric request-success-rate".

    Flagger comes with a load testing service based on rakyll/hey +that generates traffic during analysis when configured as a webhook.

    Flagger Load Testing Webhook

    First you need to deploy the load test runner in a namespace with sidecar injection enabled:

    kubectl apply -k github.com/weaveworks/flagger//kustomize/tester
    +

    Or by using Helm:

    helm repo add flagger https://flagger.app
    +
    +helm upgrade -i flagger-loadtester flagger/loadtester \
    +--namespace=test \
    +--set cmd.timeout=1h
    +

    When deployed the load tester API will be available at http://flagger-loadtester.test/.

    Now you can add webhooks to the canary analysis spec:

    webhooks:
    +  - name: load-test-get
    +    url: http://flagger-loadtester.test/
    +    timeout: 5s
    +    metadata:
    +      type: cmd
    +      cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"
    +  - name: load-test-post
    +    url: http://flagger-loadtester.test/
    +    timeout: 5s
    +    metadata:
    +      type: cmd
    +      cmd: "hey -z 1m -q 10 -c 2 -m POST -d '{test: 2}' http://podinfo-canary.test:9898/echo"
    +

    When the canary analysis starts, Flagger will call the webhooks and the load tester will run the hey commands +in the background, if they are not already running. This will ensure that during the +analysis, the podinfo-canary.test service will receive a steady stream of GET and POST requests.

    If your workload is exposed outside the mesh you can point hey to the +public URL and use HTTP2.

    webhooks:
    +  - name: load-test-get
    +    url: http://flagger-loadtester.test/
    +    timeout: 5s
    +    metadata:
    +      type: cmd
    +      cmd: "hey -z 1m -q 10 -c 2 -h2 https://podinfo.example.com/"
    +

    For gRPC services you can use bojand/ghz which is a similar tool to Hey but for gPRC:

    webhooks:
    +  - name: grpc-load-test
    +    url: http://flagger-loadtester.test/
    +    timeout: 5s
    +    metadata:
    +      type: cmd
    +      cmd: "ghz -z 1m -q 10 -c 2 --insecure podinfo.test:9898"
    +

    ghz uses reflection to identify which gRPC method to call. If you do not wish to enable reflection for your gRPC service you can implement a standardized health check from the grpc-proto library. To use this health check schema without reflection you can pass a parameter to ghz like this

    webhooks:
    +  - name: grpc-load-test-no-reflection
    +    url: http://flagger-loadtester.test/
    +    timeout: 5s
    +    metadata:
    +      type: cmd
    +      cmd: "ghz --insecure --proto=/tmp/ghz/health.proto --call=grpc.health.v1.Health/Check podinfo.test:9898"
    +

    The load tester can run arbitrary commands as long as the binary is present in the container image. +For example if you you want to replace hey with another CLI, you can create your own Docker image:

    FROM weaveworks/flagger-loadtester:<VER>
    +
    +RUN curl -Lo /usr/local/bin/my-cli https://github.com/user/repo/releases/download/ver/my-cli \
    +    && chmod +x /usr/local/bin/my-cli
    +

    Load Testing Delegation

    The load tester can also forward testing tasks to external tools, by now nGrinder +is supported.

    To use this feature, add a load test task of type 'ngrinder' to the canary analysis spec:

    webhooks:
    +  - name: load-test-post
    +    url: http://flagger-loadtester.test/
    +    timeout: 5s
    +    metadata:
    +      # type of this load test task, cmd or ngrinder
    +      type: ngrinder
    +      # base url of your nGrinder controller server
    +      server: http://ngrinder-server:port
    +      # id of the test to clone from, the test must have been defined.
    +      clone: 100
    +      # user name and base64 encoded password to authenticate against the nGrinder server
    +      username: admin
    +      passwd: YWRtaW4=
    +      # the interval between between nGrinder test status polling, default to 1s
    +      pollInterval: 5s
    +

    When the canary analysis starts, the load tester will initiate a clone_and_start request +to the nGrinder server and start a new performance test. the load tester will periodically poll the nGrinder server +for the status of the test, and prevent duplicate requests from being sent in subsequent analysis loops.

    Integration Testing

    Flagger comes with a testing service that can run Helm tests or Bats tests when configured as a webhook.

    Deploy the Helm test runner in the kube-system namespace using the tiller service account:

    helm repo add flagger https://flagger.app
    +
    +helm upgrade -i flagger-helmtester flagger/loadtester \
    +--namespace=kube-system \
    +--set serviceAccountName=tiller
    +

    When deployed the Helm tester API will be available at http://flagger-helmtester.kube-system/.

    Now you can add pre-rollout webhooks to the canary analysis spec:

      analysis:
    +    webhooks:
    +      - name: "smoke test"
    +        type: pre-rollout
    +        url: http://flagger-helmtester.kube-system/
    +        timeout: 3m
    +        metadata:
    +          type: "helm"
    +          cmd: "test {{ .Release.Name }} --cleanup"
    +

    When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary. +If the helm test fails, Flagger will retry until the analysis threshold is reached and the canary is rolled back.

    If you are using Helm v3, you'll have to create a dedicated service account and add the release namespace to the test command:

      analysis:
    +    webhooks:
    +      - name: "smoke test"
    +        type: pre-rollout
    +        url: http://flagger-helmtester.kube-system/
    +        timeout: 3m
    +        metadata:
    +          type: "helmv3"
    +          cmd: "test {{ .Release.Name }} --timeout 3m -n {{ .Release.Namespace }}"
    +

    As an alternative to Helm you can use the Bash Automated Testing System to run your tests.

      analysis:
    +    webhooks:
    +      - name: "acceptance tests"
    +        type: pre-rollout
    +        url: http://flagger-batstester.default/
    +        timeout: 5m
    +        metadata:
    +          type: "bash"
    +          cmd: "bats /tests/acceptance.bats"
    +

    Note that you should create a ConfigMap with your Bats tests and mount it inside the tester container.

    Manual Gating

    For manual approval of a canary deployment you can use the confirm-rollout and confirm-promotion webhooks. +The confirmation rollout hooks are executed before the pre-rollout hooks. +Flagger will halt the canary traffic shifting and analysis until the confirm webhook returns HTTP status 200.

    For manual rollback of a canary deployment you can use the rollback webhook. The rollback hook will be called +during the analysis and confirmation states. If a rollback webhook returns a successful HTTP status code, Flagger +will shift all traffic back to the primary instance and fail the canary.

    Manual gating with Flagger's tester:

      analysis:
    +    webhooks:
    +      - name: "gate"
    +        type: confirm-rollout
    +        url: http://flagger-loadtester.test/gate/halt
    +

    The /gate/halt returns HTTP 403 thus blocking the rollout.

    If you have notifications enabled, Flagger will post a message to Slack or MS Teams if a canary rollout is waiting for approval.

    Change the URL to /gate/approve to start the canary analysis:

      analysis:
    +    webhooks:
    +      - name: "gate"
    +        type: confirm-rollout
    +        url: http://flagger-loadtester.test/gate/approve
    +

    Manual gating can be driven with Flagger's tester API. Set the confirmation URL to /gate/check:

      analysis:
    +    webhooks:
    +      - name: "ask for confirmation"
    +        type: confirm-rollout
    +        url: http://flagger-loadtester.test/gate/check
    +

    By default the gate is closed, you can start or resume the canary rollout with:

    kubectl -n test exec -it flagger-loadtester-xxxx-xxxx sh
    +
    +curl -d '{"name": "podinfo","namespace":"test"}' http://localhost:8080/gate/open 
    +

    You can pause the rollout at any time with:

    curl -d '{"name": "podinfo","namespace":"test"}' http://localhost:8080/gate/close 
    +

    If a canary analysis is paused the status will change to waiting:

    kubectl get canary/podinfo
    +
    +NAME      STATUS        WEIGHT
    +podinfo   Waiting       0
    +

    The confirm-promotion hook type can be used to manually approve the canary promotion. +While the promotion is paused, Flagger will continue to run the metrics checks and load tests.

      analysis:
    +    webhooks:
    +      - name: "promotion gate"
    +        type: confirm-promotion
    +        url: http://flagger-loadtester.test/gate/halt
    +

    The rollback hook type can be used to manually rollback the canary promotion. As with gating, rollbacks can be driven +with Flagger's tester API by setting the rollback URL to /rollback/check

      analysis:
    +    webhooks:
    +      - name: "rollback"
    +        type: rollback
    +        url: http://flagger-loadtester.test/rollback/check
    +

    By default rollback is closed, you can rollback a canary rollout with:

    kubectl -n test exec -it flagger-loadtester-xxxx-xxxx sh
    +
    +curl -d '{"name": "podinfo","namespace":"test"}' http://localhost:8080/rollback/open 
    +

    You can close the rollback with:

    curl -d '{"name": "podinfo","namespace":"test"}' http://localhost:8080/rollback/close 
    +

    If you have notifications enabled, Flagger will post a message to Slack or MS Teams if a canary has been rolled back.

    + + + diff --git a/website.css b/website.css index 5252aa8a..73f9d1fb 100644 --- a/website.css +++ b/website.css @@ -7,3 +7,6 @@ background: url(favicon.png) left 50% no-repeat; background-size: 20px 20px; } +/*.theme-container .theme-default-content:not(.custom) {*/ +/* max-width: 920px;*/ +/*}*/ \ No newline at end of file