├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cluster ├── canaries │ └── podinfo.yaml ├── charts │ └── podinfo │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── README.md │ │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ ├── ingress.yaml │ │ ├── profile.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── tests │ │ │ ├── jwt.yaml │ │ │ └── service.yaml │ │ └── values.yaml ├── namespaces │ ├── account.yaml │ ├── ingress-nginx.yaml │ └── prod.yaml └── releases │ ├── helm-tester.yaml │ ├── load-tester.yaml │ ├── nginx-ingress.yaml │ ├── podinfo.yaml │ └── sealed-secrets.yaml ├── docs ├── .vuepress │ ├── config.js │ └── public │ │ ├── favicon.png │ │ ├── gitops-helm-workshop.png │ │ ├── linkerd-dashboard.png │ │ ├── podinfo-3.0.0.png │ │ ├── podinfo-3.0.5.png │ │ └── website.css ├── README.md ├── canary │ └── README.md ├── helm │ └── README.md ├── intro │ └── README.md ├── prerequisites │ └── README.md └── test │ └── README.md ├── package.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | push-gh-pages: 4 | docker: 5 | - image: circleci/node 6 | steps: 7 | - checkout 8 | - restore_cache: 9 | name: Restore Yarn Package Cache 10 | keys: 11 | - yarn-packages-{{ checksum "yarn.lock" }} 12 | - run: 13 | name: Install Dependencies 14 | command: yarn install --frozen-lockfile 15 | - save_cache: 16 | name: Save Yarn Package Cache 17 | key: yarn-packages-{{ checksum "yarn.lock" }} 18 | paths: 19 | - ~/.cache/yarn 20 | - run: 21 | name: Build 22 | command: | 23 | yarn docs:build 24 | mkdir $HOME/site 25 | sudo apt-get update && sudo apt-get install rsync 26 | rsync -avzh docs/.vuepress/dist/ $HOME/site 27 | - run: 28 | name: Publish 29 | command: | 30 | if [[ $GITHUB_TOKEN ]]; then 31 | REPOSITORY="https://stefanprodan:${GITHUB_TOKEN}@github.com/stefanprodan/gitops-helm-workshop.git" 32 | git config user.email stefanprodan@users.noreply.github.com 33 | git config user.name stefanprodan 34 | git remote set-url origin ${REPOSITORY} 35 | git checkout gh-pages 36 | rm -rf assets 37 | rsync -avzh $HOME/site/ . 38 | git add . 39 | git commit -m "Publish docs" 40 | git push origin gh-pages 41 | else 42 | echo "No GitHub token found! Skip publishing" 43 | fi 44 | 45 | workflows: 46 | version: 2 47 | gh-pages: 48 | jobs: 49 | - push-gh-pages: 50 | filters: 51 | branches: 52 | only: master -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | docs/.vuepress/dist/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MSG?="up" 2 | 3 | sync: 4 | git add -A && git commit -m $(MSG) && git push origin master 5 | fluxctl --k8s-fwd-ns=fluxcd sync 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitops-helm-workshop 2 | 3 | Sessions: 4 | 5 | - [Helm Summit EU 2019](https://events.linuxfoundation.org/events/helm-summit-2019/) 6 | -------------------------------------------------------------------------------- /cluster/canaries/podinfo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: flagger.app/v1beta1 2 | kind: Canary 3 | metadata: 4 | name: podinfo 5 | namespace: prod 6 | annotations: 7 | fluxcd.io/ignore: "true" 8 | spec: 9 | targetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: podinfo 13 | service: 14 | port: 9898 15 | analysis: 16 | interval: 10s 17 | maxWeight: 100 18 | stepWeight: 5 19 | threshold: 5 20 | metrics: 21 | - name: request-success-rate 22 | thresholdRange: 23 | min: 99 24 | interval: 1m 25 | - name: request-duration 26 | thresholdRange: 27 | max: 500 28 | interval: 1m 29 | webhooks: 30 | - name: acceptance-test 31 | type: pre-rollout 32 | url: http://flagger-loadtester.prod/ 33 | timeout: 30s 34 | metadata: 35 | type: bash 36 | cmd: "curl -sd 'test' http://podinfo-canary.prod:9898/token | grep token" 37 | - name: load-test 38 | type: rollout 39 | url: http://flagger-loadtester.prod/ 40 | metadata: 41 | cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.prod:9898/" -------------------------------------------------------------------------------- /cluster/charts/podinfo/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | version: 2.1.3 3 | appVersion: 2.1.3 4 | name: podinfo 5 | engine: gotpl 6 | description: Podinfo Helm chart for Kubernetes 7 | home: https://github.com/stefanprodan/podinfo 8 | maintainers: 9 | - email: stefanprodan@users.noreply.github.com 10 | name: stefanprodan 11 | sources: 12 | - https://github.com/stefanprodan/podinfo 13 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/README.md: -------------------------------------------------------------------------------- 1 | # Podinfo 2 | 3 | Podinfo is a tiny web application made with Go 4 | that showcases best practices of running microservices in Kubernetes. 5 | 6 | ## Installing the Chart 7 | 8 | To install the chart with the release name `my-release`: 9 | 10 | ```console 11 | $ helm repo add sp https://stefanprodan.github.io/k8s-podinfo 12 | $ helm upgrade my-release --install sp/podinfo 13 | ``` 14 | 15 | The command deploys podinfo on the Kubernetes cluster in the default namespace. 16 | The [configuration](#configuration) section lists the parameters that can be configured during installation. 17 | 18 | ## Uninstalling the Chart 19 | 20 | To uninstall/delete the `my-release` deployment: 21 | 22 | ```console 23 | $ helm delete --purge my-release 24 | ``` 25 | 26 | The command removes all the Kubernetes components associated with the chart and deletes the release. 27 | 28 | ## Configuration 29 | 30 | The following tables lists the configurable parameters of the podinfo chart and their default values. 31 | 32 | Parameter | Description | Default 33 | --- | --- | --- 34 | `affinity` | node/pod affinities | None 35 | `color` | UI color | blue 36 | `backend` | echo backend URL | None 37 | `faults.delay` | random HTTP response delays between 0 and 5 seconds | `false` 38 | `faults.error` | 1/3 chances of a random HTTP response error | `false` 39 | `hpa.enabled` | enables HPA | `false` 40 | `hpa.cpu` | target CPU usage per pod | None 41 | `hpa.memory` | target memory usage per pod | None 42 | `hpa.requests` | target requests per second per pod | None 43 | `hpa.maxReplicas` | maximum pod replicas | `10` 44 | `ingress.hosts` | ingress accepted hostnames | None 45 | `ingress.tls` | ingress TLS configuration | None: 46 | `image.pullPolicy` | image pull policy | `IfNotPresent` 47 | `image.repository` | image repository | `stefanprodan/podinfo` 48 | `image.tag` | image tag | `0.0.1` 49 | `ingress.enabled` | enables ingress | `false` 50 | `ingress.annotations` | ingress annotations | None 51 | `ingress.hosts` | ingress accepted hostnames | None 52 | `ingress.tls` | ingress TLS configuration | None 53 | `message` | UI greetings message | None 54 | `nodeSelector` | node labels for pod assignment | `{}` 55 | `replicaCount` | desired number of pods | `2` 56 | `resources.requests/cpu` | pod CPU request | `1m` 57 | `resources.requests/memory` | pod memory request | `16Mi` 58 | `resources.limits/cpu` | pod CPU limit | None 59 | `resources.limits/memory` | pod memory limit | None 60 | `service.enabled` | create Kubernetes service (should be disabled when using Flagger) | `true` 61 | `service.externalPort` | external port for the service | `9898` 62 | `service.internalPort` | internal port for the service | `9898` 63 | `service.nodePort` | node port for the service | `31198` 64 | `service.type` | type of service | `ClusterIP` 65 | `tolerations` | list of node taints to tolerate | `[]` 66 | `serviceAccount.enabled` | specifies whether a service account should be created | `false` 67 | `serviceAccount.name` | the name of the service account to use, if not set and create is true, a name is generated using the fullname template | None 68 | 69 | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, 70 | 71 | ```console 72 | $ helm install stable/podinfo --name my-release \ 73 | --set=image.tag=0.0.2,service.type=NodePort 74 | ``` 75 | 76 | Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example, 77 | 78 | ```console 79 | $ helm install stable/podinfo --name my-release -f values.yaml 80 | ``` 81 | 82 | > **Tip**: You can use the default [values.yaml](values.yaml) 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range .Values.ingress.hosts }} 4 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }} 5 | {{- end }} 6 | {{- else if contains "NodePort" .Values.service.type }} 7 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "podinfo.fullname" . }}) 8 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 9 | echo http://$NODE_IP:$NODE_PORT 10 | {{- else if contains "LoadBalancer" .Values.service.type }} 11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 12 | You can watch the status of by running 'kubectl get svc -w {{ template "podinfo.fullname" . }}' 13 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "podinfo.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 14 | echo http://$SERVICE_IP:{{ .Values.service.port }} 15 | {{- else if contains "ClusterIP" .Values.service.type }} 16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "podinfo.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 17 | echo "Visit http://127.0.0.1:8080 to use your application" 18 | kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "podinfo.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "podinfo.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "podinfo.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create the name of the service account to use 36 | */}} 37 | {{- define "podinfo.serviceAccountName" -}} 38 | {{- if .Values.serviceAccount.enabled -}} 39 | {{ default (include "podinfo.fullname" .) .Values.serviceAccount.name }} 40 | {{- else -}} 41 | {{ default "default" .Values.serviceAccount.name }} 42 | {{- end -}} 43 | {{- end -}} -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "podinfo.fullname" . }} 5 | labels: 6 | app: {{ template "podinfo.name" . }} 7 | chart: {{ template "podinfo.chart" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | {{- if not .Values.hpa.enabled }} 12 | replicas: {{ .Values.replicaCount }} 13 | {{- end }} 14 | strategy: 15 | type: RollingUpdate 16 | rollingUpdate: 17 | maxUnavailable: 1 18 | selector: 19 | matchLabels: 20 | app: {{ template "podinfo.name" . }} 21 | release: {{ .Release.Name }} 22 | template: 23 | metadata: 24 | labels: 25 | app: {{ template "podinfo.name" . }} 26 | release: {{ .Release.Name }} 27 | annotations: 28 | prometheus.io/scrape: "true" 29 | prometheus.io/port: "{{ .Values.service.httpPort }}" 30 | spec: 31 | terminationGracePeriodSeconds: 30 32 | {{- if .Values.serviceAccount.enabled }} 33 | serviceAccountName: {{ template "podinfo.serviceAccountName" . }} 34 | {{- end }} 35 | containers: 36 | - name: {{ .Chart.Name }} 37 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 38 | imagePullPolicy: {{ .Values.image.pullPolicy }} 39 | command: 40 | - ./podinfo 41 | - --port={{ .Values.service.httpPort | default 9898 }} 42 | {{- if .Values.service.metricsPort }} 43 | - --port-metrics={{ .Values.service.metricsPort }} 44 | {{- end }} 45 | {{- if .Values.service.grpcPort }} 46 | - --grpc-port={{ .Values.service.grpcPort }} 47 | {{- end }} 48 | {{- if .Values.service.grpcService }} 49 | - --grpc-service-name={{ .Values.service.grpcService }} 50 | {{- end }} 51 | - --level={{ .Values.logLevel }} 52 | - --random-delay={{ .Values.faults.delay }} 53 | - --random-error={{ .Values.faults.error }} 54 | env: 55 | {{- if .Values.message }} 56 | - name: PODINFO_UI_MESSAGE 57 | value: {{ .Values.message }} 58 | {{- end }} 59 | {{- if .Values.backend }} 60 | - name: PODINFO_BACKEND_URL 61 | value: {{ .Values.backend }} 62 | {{- end }} 63 | ports: 64 | - name: http 65 | containerPort: {{ .Values.service.httpPort | default 9898 }} 66 | protocol: TCP 67 | {{- if .Values.service.metricsPort }} 68 | - name: http-metrics 69 | containerPort: {{ .Values.service.metricsPort }} 70 | protocol: TCP 71 | {{- end }} 72 | {{- if .Values.service.grpcPort }} 73 | - name: grpc 74 | containerPort: {{ .Values.service.grpcPort }} 75 | protocol: TCP 76 | {{- end }} 77 | livenessProbe: 78 | exec: 79 | command: 80 | - podcli 81 | - check 82 | - http 83 | - localhost:{{ .Values.service.httpPort | default 9898 }}/healthz 84 | initialDelaySeconds: 1 85 | timeoutSeconds: 5 86 | readinessProbe: 87 | exec: 88 | command: 89 | - podcli 90 | - check 91 | - http 92 | - localhost:{{ .Values.service.httpPort | default 9898 }}/readyz 93 | initialDelaySeconds: 1 94 | timeoutSeconds: 5 95 | volumeMounts: 96 | - name: data 97 | mountPath: /data 98 | resources: 99 | {{ toYaml .Values.resources | indent 12 }} 100 | {{- with .Values.nodeSelector }} 101 | nodeSelector: 102 | {{ toYaml . | indent 8 }} 103 | {{- end }} 104 | {{- with .Values.affinity }} 105 | affinity: 106 | {{ toYaml . | indent 8 }} 107 | {{- end }} 108 | {{- with .Values.tolerations }} 109 | tolerations: 110 | {{ toYaml . | indent 8 }} 111 | {{- end }} 112 | volumes: 113 | - name: data 114 | emptyDir: {} 115 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.hpa.enabled -}} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ template "podinfo.fullname" . }} 6 | spec: 7 | scaleTargetRef: 8 | apiVersion: apps/v1beta2 9 | kind: Deployment 10 | name: {{ template "podinfo.fullname" . }} 11 | minReplicas: {{ .Values.replicaCount }} 12 | maxReplicas: {{ .Values.hpa.maxReplicas }} 13 | metrics: 14 | {{- if .Values.hpa.cpu }} 15 | - type: Resource 16 | resource: 17 | name: cpu 18 | targetAverageUtilization: {{ .Values.hpa.cpu }} 19 | {{- end }} 20 | {{- if .Values.hpa.memory }} 21 | - type: Resource 22 | resource: 23 | name: memory 24 | targetAverageValue: {{ .Values.hpa.memory }} 25 | {{- end }} 26 | {{- if .Values.hpa.requests }} 27 | - type: Pod 28 | pods: 29 | metricName: http_requests 30 | targetAverageValue: {{ .Values.hpa.requests }} 31 | {{- end }} 32 | {{- end }} 33 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "podinfo.fullname" . -}} 3 | {{- $servicePort := .Values.service.port -}} 4 | {{- $ingressPath := .Values.ingress.path -}} 5 | apiVersion: extensions/v1beta1 6 | kind: Ingress 7 | metadata: 8 | name: {{ $fullName }} 9 | labels: 10 | app: {{ template "podinfo.name" . }} 11 | chart: {{ template "podinfo.chart" . }} 12 | release: {{ .Release.Name }} 13 | heritage: {{ .Release.Service }} 14 | {{- with .Values.ingress.annotations }} 15 | annotations: 16 | {{ toYaml . | indent 4 }} 17 | {{- end }} 18 | spec: 19 | {{- if .Values.ingress.tls }} 20 | tls: 21 | {{- range .Values.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.ingress.hosts }} 31 | - host: {{ . }} 32 | http: 33 | paths: 34 | - path: {{ $ingressPath }} 35 | backend: 36 | serviceName: {{ $fullName }} 37 | servicePort: http 38 | {{- end }} 39 | {{- if not .Values.ingress.hosts }} 40 | - http: 41 | paths: 42 | - path: {{ $ingressPath }} 43 | backend: 44 | serviceName: {{ $fullName }} 45 | servicePort: http 46 | {{- end }} 47 | {{- end }} 48 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/profile.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: linkerd.io/v1alpha1 2 | kind: ServiceProfile 3 | metadata: 4 | name: "{{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local" 5 | spec: 6 | routes: 7 | - condition: 8 | method: GET 9 | pathRegex: / 10 | name: GET / 11 | - condition: 12 | method: POST 13 | pathRegex: /api/echo 14 | name: POST /api/echo 15 | - condition: 16 | method: GET 17 | pathRegex: /api/info 18 | name: GET /api/info 19 | - condition: 20 | method: GET 21 | pathRegex: /chunked/[^/]* 22 | name: GET /chunked/{seconds} 23 | - condition: 24 | method: GET 25 | pathRegex: /delay/[^/]* 26 | name: GET /delay/{seconds} 27 | - condition: 28 | method: GET 29 | pathRegex: /env 30 | name: GET /env 31 | - condition: 32 | method: GET 33 | pathRegex: /headers 34 | name: GET /headers 35 | - condition: 36 | method: GET 37 | pathRegex: /healthz 38 | name: GET /healthz 39 | - condition: 40 | method: GET 41 | pathRegex: /metrics 42 | name: GET /metrics 43 | - condition: 44 | method: GET 45 | pathRegex: /panic 46 | name: GET /panic 47 | - condition: 48 | method: GET 49 | pathRegex: /readyz 50 | name: GET /readyz 51 | - condition: 52 | method: POST 53 | pathRegex: /readyz/disable 54 | name: POST /readyz/disable 55 | - condition: 56 | method: POST 57 | pathRegex: /readyz/enable 58 | name: POST /readyz/enable 59 | - condition: 60 | method: GET 61 | pathRegex: /status/[^/]* 62 | name: GET /status/{code} 63 | - condition: 64 | method: POST 65 | pathRegex: /store 66 | name: POST /store 67 | - condition: 68 | method: GET 69 | pathRegex: /store/[^/]* 70 | name: GET /store/{hash} 71 | - condition: 72 | method: POST 73 | pathRegex: /token 74 | name: POST /token 75 | - condition: 76 | method: POST 77 | pathRegex: /token/validate 78 | name: POST /token/validate 79 | - condition: 80 | method: GET 81 | pathRegex: /version 82 | name: GET /version 83 | - condition: 84 | method: POST 85 | pathRegex: /ws/echo 86 | name: POST /ws/echo 87 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.service.enabled -}} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ template "podinfo.fullname" . }} 6 | labels: 7 | app: {{ template "podinfo.name" . }} 8 | chart: {{ template "podinfo.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | spec: 12 | type: {{ .Values.service.type }} 13 | ports: 14 | - port: {{ .Values.service.externalPort }} 15 | targetPort: http 16 | protocol: TCP 17 | name: http 18 | {{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }} 19 | nodePort: {{ .Values.service.nodePort }} 20 | {{- end }} 21 | {{- if .Values.service.grpcPort }} 22 | - port: {{ .Values.service.grpcPort }} 23 | targetPort: grpc 24 | protocol: TCP 25 | name: grpc 26 | {{- end }} 27 | selector: 28 | app: {{ template "podinfo.name" . }} 29 | release: {{ .Release.Name }} 30 | {{- end }} -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.enabled -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "podinfo.serviceAccountName" . }} 6 | labels: 7 | app: {{ template "podinfo.name" . }} 8 | chart: {{ template "podinfo.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | {{- end -}} 12 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/tests/jwt.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: {{ template "podinfo.fullname" . }}-jwt-test-{{ randAlphaNum 5 | lower }} 5 | labels: 6 | heritage: {{ .Release.Service }} 7 | release: {{ .Release.Name }} 8 | chart: {{ .Chart.Name }}-{{ .Chart.Version }} 9 | app: {{ template "podinfo.name" . }} 10 | annotations: 11 | linkerd.io/inject: disabled 12 | "helm.sh/hook": test-success 13 | spec: 14 | containers: 15 | - name: tools 16 | image: giantswarm/tiny-tools 17 | command: 18 | - sh 19 | - -c 20 | - | 21 | TOKEN=$(curl -sd 'test' ${PODINFO_SVC}/token | jq -r .token) && 22 | curl -H "Authorization: Bearer ${TOKEN}" ${PODINFO_SVC}/token/validate | grep test 23 | env: 24 | - name: PODINFO_SVC 25 | value: {{ template "podinfo.fullname" . }}:{{ .Values.service.externalPort }} 26 | restartPolicy: Never 27 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/templates/tests/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: {{ template "podinfo.fullname" . }}-service-test-{{ randAlphaNum 5 | lower }} 5 | labels: 6 | heritage: {{ .Release.Service }} 7 | release: {{ .Release.Name }} 8 | chart: {{ .Chart.Name }}-{{ .Chart.Version }} 9 | app: {{ template "podinfo.name" . }} 10 | annotations: 11 | linkerd.io/inject: disabled 12 | "helm.sh/hook": test-success 13 | spec: 14 | containers: 15 | - name: curl 16 | image: radial/busyboxplus:curl 17 | command: ['curl'] 18 | args: ['{{ template "podinfo.fullname" . }}:{{ .Values.service.externalPort }}'] 19 | restartPolicy: Never 20 | -------------------------------------------------------------------------------- /cluster/charts/podinfo/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for podinfo. 2 | 3 | replicaCount: 2 4 | logLevel: info 5 | backend: #http://backend-podinfo:9898/echo 6 | message: #UI greetings 7 | 8 | faults: 9 | delay: false 10 | error: false 11 | 12 | image: 13 | repository: stefanprodan/podinfo 14 | tag: 3.1.0 15 | pullPolicy: IfNotPresent 16 | 17 | service: 18 | enabled: true 19 | type: ClusterIP 20 | metricsPort: 9797 21 | httpPort: 9898 22 | externalPort: 9898 23 | grpcPort: 9999 24 | grpcService: podinfo 25 | nodePort: 31198 26 | 27 | # metrics-server add-on required 28 | hpa: 29 | enabled: false 30 | maxReplicas: 10 31 | # average total CPU usage per pod (1-100) 32 | cpu: 33 | # average memory usage per pod (100Mi-1Gi) 34 | memory: 35 | # average http requests per second per pod (k8s-prometheus-adapter) 36 | requests: 37 | 38 | serviceAccount: 39 | # Specifies whether a service account should be created 40 | enabled: false 41 | # The name of the service account to use. 42 | # If not set and create is true, a name is generated using the fullname template 43 | name: 44 | 45 | ingress: 46 | enabled: false 47 | annotations: {} 48 | # kubernetes.io/ingress.class: nginx 49 | # kubernetes.io/tls-acme: "true" 50 | path: /* 51 | hosts: [] 52 | # - podinfo.local 53 | tls: [] 54 | # - secretName: chart-example-tls 55 | # hosts: 56 | # - chart-example.local 57 | 58 | resources: 59 | limits: 60 | requests: 61 | cpu: 1m 62 | memory: 16Mi 63 | 64 | nodeSelector: {} 65 | 66 | tolerations: [] 67 | 68 | affinity: {} 69 | -------------------------------------------------------------------------------- /cluster/namespaces/account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: helm-tester 5 | namespace: prod 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRoleBinding 9 | metadata: 10 | name: helm-tester 11 | roleRef: 12 | apiGroup: rbac.authorization.k8s.io 13 | kind: ClusterRole 14 | name: cluster-admin 15 | subjects: 16 | - kind: ServiceAccount 17 | name: helm-tester 18 | namespace: prod 19 | -------------------------------------------------------------------------------- /cluster/namespaces/ingress-nginx.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | annotations: 5 | fluxcd.io/ignore: "false" 6 | linkerd.io/inject: enabled 7 | name: ingress-nginx 8 | -------------------------------------------------------------------------------- /cluster/namespaces/prod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | annotations: 5 | fluxcd.io/ignore: "false" 6 | linkerd.io/inject: enabled 7 | name: prod 8 | -------------------------------------------------------------------------------- /cluster/releases/helm-tester.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: helm-tester 5 | namespace: prod 6 | annotations: 7 | fluxcd.io/ignore: "true" 8 | spec: 9 | releaseName: helm-tester 10 | chart: 11 | git: https://github.com/weaveworks/flagger 12 | ref: 0.18.4 13 | path: charts/loadtester 14 | values: 15 | fullnameOverride: helm-tester 16 | serviceAccountName: helm-tester 17 | -------------------------------------------------------------------------------- /cluster/releases/load-tester.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: load-tester 5 | namespace: prod 6 | annotations: 7 | fluxcd.io/ignore: "true" 8 | spec: 9 | releaseName: load-tester 10 | chart: 11 | git: https://github.com/weaveworks/flagger 12 | ref: 0.18.4 13 | path: charts/loadtester 14 | values: 15 | fullnameOverride: load-tester 16 | -------------------------------------------------------------------------------- /cluster/releases/nginx-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: nginx-ingress 5 | namespace: ingress-nginx 6 | annotations: 7 | fluxcd.io/ignore: "true" 8 | spec: 9 | releaseName: nginx-ingress 10 | chart: 11 | repository: https://kubernetes-charts.storage.googleapis.com/ 12 | name: nginx-ingress 13 | version: 1.33.4 14 | values: 15 | controller: 16 | service: 17 | type: LoadBalancer 18 | -------------------------------------------------------------------------------- /cluster/releases/podinfo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo 6 | namespace: prod 7 | annotations: 8 | fluxcd.io/ignore: "true" 9 | fluxcd.io/automated: "false" 10 | fluxcd.io/tag.chart-image: semver:~3.0 11 | spec: 12 | releaseName: podinfo 13 | chart: 14 | git: git@github.com:stefanprodan/gitops-helm-workshop 15 | ref: master 16 | path: cluster/charts/podinfo 17 | values: 18 | image: 19 | repository: stefanprodan/podinfo 20 | tag: 3.1.0 21 | service: 22 | enabled: true 23 | type: ClusterIP 24 | ingress: 25 | enabled: true 26 | annotations: 27 | kubernetes.io/ingress.class: "nginx" 28 | nginx.ingress.kubernetes.io/configuration-snippet: | 29 | proxy_set_header l5d-dst-override $service_name.$namespace.svc.cluster.local:9898; 30 | proxy_hide_header l5d-remote-ip; 31 | proxy_hide_header l5d-server-id; 32 | path: / 33 | hosts: 34 | - 35.198.97.122.nip.io 35 | -------------------------------------------------------------------------------- /cluster/releases/sealed-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: sealed-secrets 5 | namespace: fluxcd 6 | annotations: 7 | fluxcd.io/ignore: "true" 8 | spec: 9 | releaseName: sealed-secrets 10 | chart: 11 | repository: https://kubernetes-charts.storage.googleapis.com/ 12 | name: sealed-secrets 13 | version: 1.8.0 14 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'GitOps Helm Workshop', 3 | description: 'Progressive Delivery for Kubernetes with Flux, Helm v3, Linkerd and Flagger', 4 | themeConfig: { 5 | displayAllHeaders: true, 6 | repo: 'stefanprodan/gitops-helm-workshop', 7 | docsDir: 'docs', 8 | editLinks: false, 9 | editLinkText: 'Help us improve this page!', 10 | nav: [ 11 | { text: 'Home', link: '/' }, 12 | ], 13 | sidebar: [ 14 | '/', 15 | '/intro/', 16 | '/prerequisites/', 17 | '/helm/', 18 | '/canary/', 19 | '/test/' 20 | ] 21 | }, 22 | head: [ 23 | ['link', { rel: 'icon', href: '/favicon.png' }], 24 | ['link', { rel: 'stylesheet', href: '/website.css' }] 25 | ] 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/gitops-helm-workshop/ec95acea31c544861376f006e0df5c26b217e127/docs/.vuepress/public/favicon.png -------------------------------------------------------------------------------- /docs/.vuepress/public/gitops-helm-workshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/gitops-helm-workshop/ec95acea31c544861376f006e0df5c26b217e127/docs/.vuepress/public/gitops-helm-workshop.png -------------------------------------------------------------------------------- /docs/.vuepress/public/linkerd-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/gitops-helm-workshop/ec95acea31c544861376f006e0df5c26b217e127/docs/.vuepress/public/linkerd-dashboard.png -------------------------------------------------------------------------------- /docs/.vuepress/public/podinfo-3.0.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/gitops-helm-workshop/ec95acea31c544861376f006e0df5c26b217e127/docs/.vuepress/public/podinfo-3.0.0.png -------------------------------------------------------------------------------- /docs/.vuepress/public/podinfo-3.0.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/gitops-helm-workshop/ec95acea31c544861376f006e0df5c26b217e127/docs/.vuepress/public/podinfo-3.0.5.png -------------------------------------------------------------------------------- /docs/.vuepress/public/website.css: -------------------------------------------------------------------------------- 1 | .site-name { 2 | padding-left: 30px; 3 | position: relative; 4 | background: url(favicon.png) left 50% no-repeat; 5 | background-size: 20px 20px; 6 | } 7 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GitOps Helm Workshop 3 | meta: 4 | - name: keywords 5 | content: gitops kubernetes helm fluxcd linkerd flagger 6 | home: true 7 | sidebar: auto 8 | heroImage: /gitops-helm-workshop.png 9 | heroText: Progressive Delivery for Kubernetes 10 | tagline: Welcome to the GitOps Helm Workshop 11 | actionText: Get Started → 12 | actionLink: /intro/ 13 | features: 14 | - title: Flux CD 15 | details: Flux is a Kubernetes controller that automatically ensures that the state of a cluster matches the config in git. 16 | Helm Operator is a Kubernetes CRD controller that manages the Helm release lifecycle. 17 | - title: Linkerd 18 | details: Linkerd v2 is a lightweight, easy to install and maintain, service mesh for Kubernetes. 19 | For HTTP and gRPC apps, Linkerd automatically enables load balancing, tracing, Prometheus metrics 20 | and mTLS with zero configuration. 21 | - title: Flagger 22 | details: Flagger is a Kubernetes operator that automates the promotion of canary deployments using 23 | Linkerd routing for traffic shifting, Prometheus metrics for canary analysis and Helm for testing. 24 | footer: Apache License 2.0 | Copyright © 2019 Weaveworks 25 | --- 26 | -------------------------------------------------------------------------------- /docs/canary/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Canary Releases 3 | --- 4 | 5 | # Canary Releases 6 | 7 | A canary release is described with a Kubernetes custom resource named **Canary**. 8 | 9 | ## Application bootstrap 10 | 11 | Edit the podinfo Helm release and disable the image updates and the ClusterIP service: 12 | 13 | ```yaml{7,13,15} 14 | apiVersion: helm.fluxcd.io/v1 15 | kind: HelmRelease 16 | metadata: 17 | name: podinfo 18 | namespace: prod 19 | annotations: 20 | fluxcd.io/automated: "false" 21 | spec: 22 | releaseName: podinfo 23 | values: 24 | image: 25 | repository: stefanprodan/podinfo 26 | tag: 3.1.0 27 | service: 28 | enabled: false 29 | type: ClusterIP 30 | ``` 31 | 32 | Apply changes: 33 | 34 | ```sh 35 | git add -A && \ 36 | git commit -m "prep canary" && \ 37 | git push origin master && \ 38 | fluxctl sync 39 | ``` 40 | 41 | Create a canary release for podinfo: 42 | 43 | ```yaml{7} 44 | apiVersion: flagger.app/v1beta1 45 | kind: Canary 46 | metadata: 47 | name: podinfo 48 | namespace: prod 49 | annotations: 50 | fluxcd.io/ignore: "false" 51 | spec: 52 | targetRef: 53 | apiVersion: apps/v1 54 | kind: Deployment 55 | name: podinfo 56 | service: 57 | port: 9898 58 | analysis: 59 | interval: 10s 60 | maxWeight: 100 61 | stepWeight: 5 62 | threshold: 5 63 | metrics: 64 | - name: request-success-rate 65 | thresholdRange: 66 | min: 99 67 | interval: 1m 68 | - name: request-duration 69 | thresholdRange: 70 | max: 500 71 | interval: 1m 72 | webhooks: 73 | - name: acceptance-test 74 | type: pre-rollout 75 | url: http://flagger-loadtester.prod/ 76 | timeout: 30s 77 | metadata: 78 | type: bash 79 | cmd: "curl -sd 'test' http://podinfo-canary.prod:9898/token | grep token" 80 | - name: load-test 81 | type: rollout 82 | url: http://flagger-loadtester.prod/ 83 | metadata: 84 | cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.prod:9898/" 85 | ``` 86 | 87 | Apply changes: 88 | 89 | ```sh 90 | git add -A && \ 91 | git commit -m "add canary" && \ 92 | git push origin master && \ 93 | fluxctl sync 94 | ``` 95 | 96 | Validate that Flagger has initialized the canary: 97 | 98 | ```sh 99 | kubectl -n prod get canary 100 | ``` 101 | 102 | ## Automated canary promotion 103 | 104 | Install the load testing service to generate traffic during the canary analysis: 105 | 106 | ```yaml{7} 107 | apiVersion: helm.fluxcd.io/v1 108 | kind: HelmRelease 109 | metadata: 110 | name: load-tester 111 | namespace: prod 112 | annotations: 113 | fluxcd.io/ignore: "false" 114 | spec: 115 | releaseName: load-tester 116 | chart: 117 | git: https://github.com/weaveworks/flagger 118 | ref: 1.0.0-rc.1 119 | path: charts/loadtester 120 | values: 121 | fullnameOverride: load-tester 122 | ``` 123 | 124 | When you deploy a new podinfo version, Flagger gradually shifts traffic to the canary, 125 | and at the same time, measures the requests success rate as well as the average response duration. 126 | Based on an analysis of these Linkerd provided metrics, a canary deployment is either promoted or rolled back. 127 | 128 | Trigger a canary deployment by updating the container image: 129 | 130 | ```yaml{7} 131 | apiVersion: helm.fluxcd.io/v1 132 | kind: HelmRelease 133 | spec: 134 | releaseName: podinfo 135 | values: 136 | image: 137 | tag: 3.1.1 138 | ``` 139 | 140 | Apply changes: 141 | 142 | ```sh 143 | git add -A && \ 144 | git commit -m "update podinfo" && \ 145 | git push origin master && \ 146 | fluxctl sync 147 | ``` 148 | 149 | When Flagger detects that the deployment revision changed it will start a new rollout. 150 | You can monitor the traffic shifting with: 151 | 152 | ```sh 153 | watch kubectl -n prod get canaries 154 | ``` 155 | 156 | ## Automated rollback 157 | 158 | During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses and 159 | rolls back the faulted version. 160 | 161 | Trigger another canary release: 162 | 163 | ```yaml{7} 164 | apiVersion: helm.fluxcd.io/v1 165 | kind: HelmRelease 166 | spec: 167 | releaseName: podinfo 168 | values: 169 | image: 170 | tag: 3.1.2 171 | ``` 172 | 173 | Apply changes: 174 | 175 | ```sh 176 | git add -A && \ 177 | git commit -m "update podinfo" && \ 178 | git push origin master && \ 179 | fluxctl sync 180 | ``` 181 | 182 | Exec into the tester pod and generate HTTP 500 errors: 183 | 184 | ```sh 185 | kubectl -n prod exec -it $(kubectl -n prod get pods -o name | grep -m1 load-tester | cut -d'/' -f 2) bash 186 | 187 | $ hey -z 1m -c 5 -q 5 http://podinfo-canary:9898/status/500 188 | $ hey -z 1m -c 5 -q 5 http://podinfo-canary:9898/delay/1 189 | ``` 190 | 191 | When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary and 192 | the canary is scaled to zero. 193 | 194 | Watch Flagger logs with: 195 | 196 | ``` 197 | $ kubectl -n linkerd logs deployment/flagger -f | jq .msg 198 | 199 | Starting canary analysis for podinfo.prod 200 | Advance podinfo.test canary weight 5 201 | Advance podinfo.test canary weight 10 202 | Advance podinfo.test canary weight 15 203 | Halt podinfo.test advancement success rate 69.17% < 99% 204 | Halt podinfo.test advancement success rate 61.39% < 99% 205 | Halt podinfo.test advancement success rate 55.06% < 99% 206 | Halt podinfo.test advancement request duration 1.20s > 0.5s 207 | Halt podinfo.test advancement request duration 1.45s > 0.5s 208 | Rolling back podinfo.prod failed checks threshold reached 5 209 | Canary failed! Scaling down podinfo.test 210 | ``` 211 | 212 | ## Monitoring with Linkerd 213 | 214 | The Linkerd dashboard provides a high level view of what is happening with your services in real time. 215 | It can be used to visualize service dependencies, traffic splitting and understand the health of specific service routes. 216 | 217 | Open the dashboard by running: 218 | 219 | ```sh 220 | linkerd dashboard --port=50750 221 | ``` 222 | 223 | During the canary analysis, navigate to: 224 | 225 | ``` 226 | http://127.0.0.1:50750/namespaces/ingress-nginx/deployments/nginx-ingress-controller 227 | ``` 228 | 229 | ![linkerd](/linkerd-dashboard.png) 230 | 231 | You can monitor the live traffic for the production namespace from the command line with: 232 | 233 | ```sh 234 | linkerd -n prod top deploy 235 | ``` 236 | 237 | And you can view all the routes exposed by podinfo with: 238 | 239 | ```sh 240 | linkerd -n prod routes service/podinfo 241 | ``` 242 | 243 | The above routes have been generated from the podinfo swagger spec and exported as Linkerd service profile. 244 | 245 | 246 | -------------------------------------------------------------------------------- /docs/helm/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Helm Releases 3 | --- 4 | 5 | # Helm Releases 6 | 7 | A chart release is described through a Kubernetes custom resource named **HelmRelease**. 8 | 9 | A Helm release can refer a chart from: 10 | * public or private Helm repositories over HTTPS 11 | * public or private Git repositories over SSH 12 | 13 | ## Install NGINX 14 | 15 | To expose applications outside of the cluster you'll be using the NGINX ingress controller. 16 | The controller will run inside the Linkerd mesh. 17 | 18 | Create a namespace with linkerd injection enabled: 19 | 20 | ```yaml{5} 21 | apiVersion: v1 22 | kind: Namespace 23 | metadata: 24 | annotations: 25 | fluxcd.io/ignore: "false" 26 | linkerd.io/inject: enabled 27 | name: ingress-nginx 28 | ``` 29 | 30 | Create a Helm release to install the NGINX ingress controller: 31 | 32 | ```yaml{7} 33 | apiVersion: helm.fluxcd.io/v1 34 | kind: HelmRelease 35 | metadata: 36 | name: nginx-ingress 37 | namespace: ingress-nginx 38 | annotations: 39 | fluxcd.io/ignore: "false" 40 | spec: 41 | releaseName: nginx-ingress 42 | chart: 43 | repository: https://kubernetes-charts.storage.googleapis.com/ 44 | name: nginx-ingress 45 | version: 1.33.4 46 | values: 47 | controller: 48 | service: 49 | type: LoadBalancer 50 | ``` 51 | 52 | Apply changes: 53 | 54 | ```sh 55 | git add -A && \ 56 | git commit -m "install ingress" && \ 57 | git push origin master && \ 58 | fluxctl sync 59 | ``` 60 | 61 | Validate that the Helm operator has installed the release: 62 | 63 | ```sh 64 | kubectl -n ingress-nginx get hr 65 | ``` 66 | 67 | Find the public IP of the ingress controller: 68 | 69 | ```sh 70 | kubectl -n ingress-nginx get svc 71 | ``` 72 | 73 | ## Install podinfo 74 | 75 | [Podinfo](http://github.com/stefanprodan/podinfo) is tiny Go web application. 76 | You'll be installing podinfo using a Helm chart stored in the git repository at `cluster/charts/podinfo`. 77 | 78 | Create the `prod` namespace with linkerd injection enabled: 79 | 80 | ```yaml{5} 81 | apiVersion: v1 82 | kind: Namespace 83 | metadata: 84 | annotations: 85 | fluxcd.io/ignore: "false" 86 | linkerd.io/inject: enabled 87 | name: prod 88 | ``` 89 | 90 | Create a Helm release to install the podinfo chart 91 | (replace `GHUSER` with your GitHub username and `LB-PUBLIC-IP` with your ingress IP): 92 | 93 | ```yaml{7,11,31} 94 | apiVersion: helm.fluxcd.io/v1 95 | kind: HelmRelease 96 | metadata: 97 | name: podinfo 98 | namespace: prod 99 | annotations: 100 | fluxcd.io/ignore: "false" 101 | spec: 102 | releaseName: podinfo 103 | chart: 104 | git: git@github.com:GHUSER/gitops-helm-workshop 105 | ref: master 106 | path: cluster/charts/podinfo 107 | values: 108 | image: 109 | repository: stefanprodan/podinfo 110 | tag: 3.1.0 111 | service: 112 | enabled: true 113 | type: ClusterIP 114 | ingress: 115 | enabled: true 116 | annotations: 117 | kubernetes.io/ingress.class: "nginx" 118 | nginx.ingress.kubernetes.io/configuration-snippet: | 119 | proxy_set_header l5d-dst-override $service_name.$namespace.svc.cluster.local:9898; 120 | proxy_hide_header l5d-remote-ip; 121 | proxy_hide_header l5d-server-id; 122 | path: / 123 | hosts: 124 | - LB-PUBLIC-IP.nip.io 125 | ``` 126 | 127 | Note that if you are on EKS, the host should be set to the `elb.amazonaws.com` address: 128 | 129 | ```sh 130 | kubectl -n ingress-nginx get svc | grep Ingress 131 | ``` 132 | 133 | Apply changes: 134 | 135 | ```sh 136 | git add -A && \ 137 | git commit -m "install podinfo" && \ 138 | git push origin master && \ 139 | fluxctl sync 140 | ``` 141 | 142 | Validate that the Helm operator has installed podinfo: 143 | 144 | ```sh 145 | kubectl -n prod get hr 146 | ``` 147 | 148 | Open your browser and navigate to `http://LB-PUBLIC-IP.nip.io/`, you should see podinfo v3.1.0 UI. 149 | 150 | ## Automated upgrade 151 | 152 | Flux can be used to automate container image updates in your cluster. 153 | You can enable the automate image tag updates by annotating Helm release objects. 154 | You can also control what tags should be considered for an 155 | update by using glob, regex or semantic version expressions. 156 | 157 | Edit the podinfo Helm release and enable Flux automated image updates: 158 | 159 | ```yaml{5,6} 160 | apiVersion: helm.fluxcd.io/v1 161 | kind: HelmRelease 162 | metadata: 163 | annotations: 164 | fluxcd.io/automated: "true" 165 | fluxcd.io/tag.chart-image: semver:~3.1 166 | ``` 167 | 168 | Apply changes: 169 | 170 | ```sh 171 | git add -A && \ 172 | git commit -m "automate podinfo" && \ 173 | git push origin master && \ 174 | fluxctl sync 175 | ``` 176 | 177 | Validate that the Helm operator has upgraded podinfo: 178 | 179 | ```sh 180 | kubectl -n prod get hr 181 | ``` 182 | 183 | Pull the changes made by Flux locally: 184 | 185 | ```sh 186 | git pull origin master 187 | ``` 188 | 189 | Open your browser and navigate to `http://LB-PUBLIC-IP.nip.io/`, you should see podinfo v3.1.5 UI. 190 | 191 | ## Sealed secrets 192 | 193 | In order to store secrets safely in a public Git repo you can use the 194 | [Sealed Secrets controller](https://github.com/bitnami-labs/sealed-secrets) 195 | and encrypt your Kubernetes Secrets into **SealedSecrets**. 196 | The sealed secret can be decrypted only by the controller running in your cluster. 197 | 198 | Create the Sealed Secrets Helm release: 199 | 200 | ```yaml{7} 201 | apiVersion: helm.fluxcd.io/v1 202 | kind: HelmRelease 203 | metadata: 204 | name: sealed-secrets 205 | namespace: fluxcd 206 | annotations: 207 | fluxcd.io/ignore: "false" 208 | spec: 209 | releaseName: sealed-secrets 210 | chart: 211 | repository: https://kubernetes-charts.storage.googleapis.com/ 212 | name: sealed-secrets 213 | version: 1.8.0 214 | ``` 215 | 216 | Apply changes: 217 | 218 | ```sh 219 | git add -A && \ 220 | git commit -m "install sealed-secrets" && \ 221 | git push origin master && \ 222 | fluxctl sync 223 | ``` 224 | 225 | Install the kubeseal CLI: 226 | 227 | ```sh 228 | wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v1.8.0/kubeseal-darwin-amd64 229 | sudo install -m 755 kubeseal-darwin-amd64 /usr/local/bin/kubeseal 230 | ``` 231 | 232 | At startup, the sealed-secrets controller generates a RSA key and logs the public key. 233 | Using kubeseal you can save your public key as pub-cert.pem, 234 | the public key can be safely stored in Git, and can be used to encrypt secrets 235 | without direct access to the Kubernetes cluster: 236 | 237 | ```sh 238 | kubeseal --fetch-cert \ 239 | --controller-namespace=fluxcd \ 240 | --controller-name=sealed-secrets \ 241 | > pub-cert.pem 242 | ``` 243 | 244 | You can generate a Kubernetes secret locally with kubectl and encrypt it with kubeseal: 245 | 246 | ```sh 247 | kubectl -n prod create secret generic basic-auth \ 248 | --from-literal=user=admin \ 249 | --from-literal=password=admin \ 250 | --dry-run \ 251 | -o json > basic-auth.json 252 | 253 | kubeseal --format=yaml --cert=pub-cert.pem < basic-auth.json > basic-auth.yaml 254 | ``` 255 | 256 | This generates a custom resource of type SealedSecret that contains the encrypted credentials. 257 | 258 | Flux will apply the sealed secret on your cluster and sealed-secrets controller will 259 | then decrypt it into a Kubernetes secret. 260 | 261 | To prepare for disaster recovery you should backup the Sealed Secrets controller private key with: 262 | 263 | ```sh 264 | kubectl get secret -n fluxcd sealed-secrets-key -o yaml \ 265 | --export > sealed-secrets-key.yaml 266 | ``` 267 | 268 | To restore from backup after a disaster, replace the newly-created secret and restart the controller: 269 | 270 | ```sh 271 | kubectl replace secret -n fluxcd sealed-secrets-key -f sealed-secrets-key.yaml 272 | kubectl delete pod -n fluxcd -l app=sealed-secrets 273 | ``` 274 | -------------------------------------------------------------------------------- /docs/intro/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | --- 4 | 5 | # Introduction 6 | 7 | This guide walks you through setting up a progressive delivery GitOps pipeline on a Kubernetes cluster. 8 | 9 | ## What is GitOps? 10 | 11 | GitOps is a way to do Continuous Delivery, it works by using Git as a source of truth for 12 | declarative infrastructure and workloads. For Kubernetes this means using `git push` instead 13 | of `kubectl create/apply` or `helm install/upgrade`. 14 | 15 | ::: tip GitOps vs CiOps 16 | 17 | In a traditional CI/CD pipeline, CD is an implementation extension powered by the continuous integration tooling 18 | to promote build artifacts to production. In the GitOps pipeline model, any change to production must be committed 19 | in source control (preferable via a pull request) prior to being applied on the cluster. 20 | If the entire production state is under version control and described in a single Git repository, 21 | when disaster strikes, the whole infrastructure can be quickly restored without rerunning the CI pipelines. 22 | 23 | [Kubernetes anti-patterns: Let's do GitOps, not CIOps!](https://www.weave.works/blog/kubernetes-anti-patterns-let-s-do-gitops-not-ciops) 24 | ::: 25 | 26 | In order to apply the GitOps model to Kubernetes you need three things: 27 | 28 | * a Git repository with your workloads definitions in YAML format, 29 | Helm charts and any other Kubernetes custom resource that defines your cluster desired state 30 | * a container registry where your CI system pushes immutable images 31 | (no *latest* tags, use *semantic versioning* or git *commit sha*) 32 | * a Kubernetes controller that does a two-way synchronization: 33 | * watches for changes in the config repository and applies them to your cluster 34 | * watches the container registry for new images and updates the workload 35 | definitions based on deployment policies 36 | 37 | In this workshop you'll be using 38 | GitHub to host the config repo, 39 | Docker Hub as the container registry, 40 | [Flux](https://github.com/fluxcd/flux) as the GitOps controller and 41 | [Helm Operator](https://github.com/fluxcd/helm-operator) for app lifecycle management. 42 | 43 | ## What is Progressive Delivery? 44 | 45 | Progressive delivery is an umbrella term for advanced deployment patterns like canaries, feature flags and A/B testing. 46 | Progressive delivery techniques are used to reduce the risk of introducing a new software version in production 47 | by giving app developers and SRE teams a fine-grained control over the blast radius. 48 | 49 | ::: tip Canary release 50 | 51 | A benefit of using canary releases is the ability to do capacity testing of the new version in a production environment 52 | with a safe rollback strategy if issues are found. By slowly ramping up the load, you can monitor and capture metrics 53 | about how the new version impacts the production environment. 54 | 55 | [Martin Fowler blog](https://martinfowler.com/bliki/CanaryRelease.html) 56 | ::: 57 | 58 | In this workshop you'll be using 59 | [Flagger](https://github.com/weaveworks/flagger), 60 | [Linkerd](https://github.com/linkerd/linkerd2) and 61 | [Prometheus](https://github.com/prometheus) 62 | to automate canary releases for Helm charts. 63 | -------------------------------------------------------------------------------- /docs/prerequisites/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prerequisites 3 | --- 4 | 5 | # Prerequisites 6 | 7 | In order to install the workshop prerequisites you'll need a Kubernetes cluster **1.13** 8 | or newer with **Load Balancer** support and **RBAC** enabled. 9 | Make sure you have the following tools installed locally: 10 | * kubectl 1.16 11 | * git 2.20 12 | 13 | ## Helm v3 14 | 15 | Install the Helm v3 CLI on macOS: 16 | 17 | ```sh 18 | brew install helm 19 | ``` 20 | 21 | On Linux or Windows you can download the binary from the [official releases page](https://github.com/helm/helm/releases). 22 | 23 | ## Git 24 | 25 | Fork the [workshop](https://github.com/stefanprodan/gitops-helm-workshop) repository 26 | and clone it locally (replace the `GHUSER` value with your GitHub username): 27 | 28 | ```sh 29 | export GHUSER=stefanprodan 30 | git clone https://github.com/${GHUSER}/gitops-helm-workshop 31 | ``` 32 | 33 | Set your GitHub username and email: 34 | 35 | ```sh 36 | cd gitops-helm-workshop 37 | git config user.name "${GHUSER}" 38 | git config user.email "your@main.address" 39 | ``` 40 | 41 | Cluster state directory structure: 42 | 43 | ``` 44 | ├── cluster 45 | ├── canaries 46 | ├── charts 47 | │   └── podinfo 48 | ├── namespaces 49 | └── releases 50 | ``` 51 | 52 | ## Flux 53 | 54 | Add Flux repository to Helm repos: 55 | 56 | ```sh 57 | helm repo add fluxcd https://charts.fluxcd.io 58 | ``` 59 | 60 | Create the fluxcd namespace: 61 | 62 | ```sh 63 | kubectl create ns fluxcd 64 | ``` 65 | 66 | Install Flux by providing your GitHub repository URL: 67 | 68 | ```sh 69 | helm upgrade -i flux fluxcd/flux --wait \ 70 | --namespace fluxcd \ 71 | --set registry.pollInterval=1m \ 72 | --set git.pollInterval=1m \ 73 | --set git.url=git@github.com:${GHUSER}/gitops-helm-workshop 74 | ``` 75 | 76 | Install fluxctl: 77 | 78 | ```sh 79 | # macOS and linux 80 | curl -sL https://fluxcd.io/install | sh 81 | export PATH=$PATH:$HOME/.fluxcd/bin 82 | 83 | # windows 84 | https://github.com/fluxcd/flux/releases 85 | ``` 86 | 87 | Find the Git SSH public key: 88 | 89 | ```sh 90 | export FLUX_FORWARD_NAMESPACE=fluxcd 91 | 92 | fluxctl identity 93 | ``` 94 | 95 | Copy the public key and create a deploy key with write access on your GitHub repository. 96 | Go to `Settings > Deploy keys` click on `Add deploy key`, check `Allow write access`, 97 | paste the Flux public key and click `Add key`. 98 | 99 | ## Helm Operator 100 | 101 | Install Flux Helm Operator in the `fluxcd` namespace: 102 | 103 | ```sh 104 | helm upgrade -i helm-operator fluxcd/helm-operator --wait \ 105 | --namespace fluxcd \ 106 | --set git.ssh.secretName=flux-git-deploy \ 107 | --set git.pollInterval=1m \ 108 | --set chartsSyncInterval=1m \ 109 | --set helm.versions=v3 110 | ``` 111 | 112 | ## Linkerd 113 | 114 | Download the Linkerd v2 CLI: 115 | 116 | ```sh 117 | # macOS and linux 118 | curl -sL https://run.linkerd.io/install | sh 119 | export PATH=$PATH:$HOME/.linkerd2/bin 120 | 121 | # windows 122 | https://github.com/linkerd/linkerd2/releases 123 | ``` 124 | 125 | Install the Linkerd control plane in the `linkerd` namespace: 126 | 127 | ```sh 128 | linkerd install | kubectl apply -f - 129 | ``` 130 | 131 | Validate the install with: 132 | 133 | ```sh 134 | linkerd check 135 | ``` 136 | 137 | ## Flagger 138 | 139 | Add Flagger Helm repository: 140 | 141 | ```sh 142 | helm repo add flagger https://flagger.app 143 | ``` 144 | 145 | Install Flagger's Canary CRD: 146 | 147 | ```sh 148 | kubectl apply -f https://raw.githubusercontent.com/weaveworks/flagger/master/artifacts/flagger/crd.yaml 149 | ``` 150 | 151 | Install Flagger in the `linkerd` namespace: 152 | 153 | ```sh 154 | helm upgrade -i flagger flagger/flagger --wait \ 155 | --namespace linkerd \ 156 | --set crd.create=false \ 157 | --set metricsServer=http://linkerd-prometheus:9090 \ 158 | --set meshProvider=linkerd 159 | ``` 160 | -------------------------------------------------------------------------------- /docs/test/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Canary Helm Tests 3 | --- 4 | 5 | # Helm Tests 6 | 7 | Flagger comes with a testing service that can run Helm tests when configured as a webhook. 8 | 9 | ## Create tests 10 | 11 | Create a test for the podinfo token API: 12 | 13 | ```yaml{11} 14 | apiVersion: v1 15 | kind: Pod 16 | metadata: 17 | name: {{ template "podinfo.fullname" . }}-jwt-test-{{ randAlphaNum 5 | lower }} 18 | labels: 19 | heritage: {{ .Release.Service }} 20 | release: {{ .Release.Name }} 21 | chart: {{ .Chart.Name }}-{{ .Chart.Version }} 22 | app: {{ template "podinfo.name" . }} 23 | annotations: 24 | linkerd.io/inject: disabled 25 | "helm.sh/hook": test-success 26 | spec: 27 | containers: 28 | - name: tools 29 | image: giantswarm/tiny-tools 30 | command: 31 | - sh 32 | - -c 33 | - | 34 | TOKEN=$(curl -sd 'test' ${PODINFO_SVC}/token | jq -r .token) && 35 | curl -H "Authorization: Bearer ${TOKEN}" ${PODINFO_SVC}/token/validate | grep test 36 | env: 37 | - name: PODINFO_SVC 38 | value: {{ template "podinfo.fullname" . }}:{{ .Values.service.externalPort }} 39 | restartPolicy: Never 40 | ``` 41 | 42 | Save the above file in `cluster/charts/podinfo/tests`. 43 | 44 | Deploy the Helm test runner in the `prod` namespace: 45 | 46 | ```yaml{7} 47 | apiVersion: helm.fluxcd.io/v1 48 | kind: HelmRelease 49 | metadata: 50 | name: helm-tester 51 | namespace: prod 52 | annotations: 53 | fluxcd.io/ignore: "false" 54 | spec: 55 | releaseName: helm-tester 56 | chart: 57 | git: https://github.com/weaveworks/flagger 58 | ref: 1.0.0-rc.1 59 | path: charts/loadtester 60 | values: 61 | fullnameOverride: helm-tester 62 | serviceAccountName: helm-tester 63 | ``` 64 | 65 | Apply changes: 66 | 67 | ```sh 68 | git add -A && \ 69 | git commit -m "install helm-tester" && \ 70 | git push origin master && \ 71 | fluxctl sync 72 | ``` 73 | 74 | ## Run tests 75 | 76 | Add the helm test as a pre-rollout webhook: 77 | 78 | ```yaml{9,10,11,12,13,14,15} 79 | apiVersion: flagger.app/v1beta1 80 | kind: Canary 81 | metadata: 82 | name: podinfo 83 | namespace: prod 84 | spec: 85 | analysis: 86 | webhooks: 87 | - name: "helm test" 88 | type: pre-rollout 89 | url: http://helm-tester.prod/ 90 | timeout: 2m 91 | metadata: 92 | type: "helmv3" 93 | cmd: "test podinfo" 94 | - name: load-test 95 | url: http://load-tester.prod/ 96 | metadata: 97 | cmd: "hey -z 2m -q 10 -c 2 http://podinfo-canary.prod:9898/" 98 | ``` 99 | 100 | Apply changes: 101 | 102 | ```sh 103 | git add -A && \ 104 | git commit -m "update podinfo" && \ 105 | git push origin master && \ 106 | fluxctl sync 107 | ``` 108 | 109 | When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary. 110 | If the helm test fails, Flagger will retry until the analysis threshold is reached and the canary is rolled back. 111 | 112 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "vuepress": "^1.0.3" 4 | }, 5 | "scripts": { 6 | "docs:dev": "vuepress dev docs", 7 | "docs:build": "vuepress build docs" 8 | } 9 | } 10 | --------------------------------------------------------------------------------