├── namespaces ├── adm.yaml ├── dev.yaml ├── prod.yaml └── stg.yaml ├── diagrams ├── flux-flagger.png ├── flux-helm-operator.png ├── flux-istio-operator.png ├── flux-multi-tenancy.png ├── flux-helm-operator-registry.png ├── flux-helm-operator-sealed-secrets.png └── flux-open-policy-agent-gatekeeper.png ├── releases ├── adm │ └── sealed-secrets.yaml ├── prod │ └── podinfo.yaml ├── dev │ └── podinfo.yaml └── stg │ └── podinfo.yaml ├── .gitignore ├── charts └── podinfo │ ├── Chart.yaml │ ├── templates │ ├── serviceaccount.yaml │ ├── tests │ │ ├── grpc.yaml │ │ ├── service.yaml │ │ └── jwt.yaml │ ├── hpa.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── linkerd.yaml │ └── deployment.yaml │ ├── .helmignore │ ├── values.yaml │ └── README.md ├── .travis.yml ├── hack ├── ci-mock.sh └── Dockerfile.ci ├── LICENSE └── README.md /namespaces/adm.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: adm 6 | -------------------------------------------------------------------------------- /namespaces/dev.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: dev 6 | -------------------------------------------------------------------------------- /namespaces/prod.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: prod 6 | -------------------------------------------------------------------------------- /namespaces/stg.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: stg 6 | -------------------------------------------------------------------------------- /diagrams/flux-flagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluxcd/helm-operator-get-started/HEAD/diagrams/flux-flagger.png -------------------------------------------------------------------------------- /diagrams/flux-helm-operator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluxcd/helm-operator-get-started/HEAD/diagrams/flux-helm-operator.png -------------------------------------------------------------------------------- /diagrams/flux-istio-operator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluxcd/helm-operator-get-started/HEAD/diagrams/flux-istio-operator.png -------------------------------------------------------------------------------- /diagrams/flux-multi-tenancy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluxcd/helm-operator-get-started/HEAD/diagrams/flux-multi-tenancy.png -------------------------------------------------------------------------------- /diagrams/flux-helm-operator-registry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluxcd/helm-operator-get-started/HEAD/diagrams/flux-helm-operator-registry.png -------------------------------------------------------------------------------- /diagrams/flux-helm-operator-sealed-secrets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluxcd/helm-operator-get-started/HEAD/diagrams/flux-helm-operator-sealed-secrets.png -------------------------------------------------------------------------------- /diagrams/flux-open-policy-agent-gatekeeper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluxcd/helm-operator-get-started/HEAD/diagrams/flux-open-policy-agent-gatekeeper.png -------------------------------------------------------------------------------- /releases/adm/sealed-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.fluxcd.io/v1 2 | kind: HelmRelease 3 | metadata: 4 | name: sealed-secrets 5 | namespace: adm 6 | spec: 7 | releaseName: sealed-secrets 8 | chart: 9 | repository: https://kubernetes-charts.storage.googleapis.com/ 10 | name: sealed-secrets 11 | version: 1.6.1 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /charts/podinfo/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | version: 3.1.5 3 | appVersion: 3.1.5 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: C 3 | 4 | env: 5 | global: 6 | - HELM_URL=https://storage.googleapis.com/kubernetes-helm 7 | - HELM_TAR=helm-v2.4.1-linux-amd64.tar.gz 8 | 9 | install: 10 | - wget -q ${HELM_URL}/${HELM_TAR} 11 | - tar xzfv ${HELM_TAR} 12 | - PATH=`pwd`/linux-amd64/:$PATH 13 | - helm init --client-only 14 | 15 | script: 16 | - > 17 | for chart in `ls ./charts`; do 18 | helm lint ./charts/${chart} 19 | if [ $? != 0 ]; then 20 | travis_terminate 1 21 | fi 22 | done 23 | -------------------------------------------------------------------------------- /releases/prod/podinfo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-prod 6 | namespace: prod 7 | annotations: 8 | fluxcd.io/automated: "true" 9 | filter.fluxcd.io/chart-image: semver:~0.4 10 | spec: 11 | releaseName: podinfo-prod 12 | chart: 13 | git: git@github.com:fluxcd/helm-operator-get-started 14 | path: charts/podinfo 15 | ref: master 16 | values: 17 | image: 18 | repository: stefanprodan/podinfo 19 | tag: 0.4.11 20 | replicaCount: 3 21 | -------------------------------------------------------------------------------- /releases/dev/podinfo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-dev 6 | namespace: dev 7 | annotations: 8 | fluxcd.io/automated: "true" 9 | filter.fluxcd.io/chart-image: glob:dev-* 10 | spec: 11 | releaseName: podinfo-dev 12 | chart: 13 | git: git@github.com:fluxcd/helm-operator-get-started 14 | path: charts/podinfo 15 | ref: master 16 | values: 17 | image: 18 | repository: stefanprodan/podinfo 19 | tag: dev-hdtwcel9 20 | replicaCount: 1 21 | hpa: 22 | enabled: false 23 | -------------------------------------------------------------------------------- /releases/stg/podinfo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: helm.fluxcd.io/v1 3 | kind: HelmRelease 4 | metadata: 5 | name: podinfo-rc 6 | namespace: stg 7 | annotations: 8 | fluxcd.io/automated: "true" 9 | filter.fluxcd.io/chart-image: glob:stg-* 10 | spec: 11 | releaseName: podinfo-rc 12 | chart: 13 | git: git@github.com:fluxcd/helm-operator-get-started 14 | path: charts/podinfo 15 | ref: master 16 | values: 17 | image: 18 | repository: stefanprodan/podinfo 19 | tag: stg-9ij63o4c 20 | replicaCount: 2 21 | hpa: 22 | enabled: true 23 | maxReplicas: 10 24 | cpu: 50 25 | memory: 256Mi 26 | -------------------------------------------------------------------------------- /charts/podinfo/templates/tests/grpc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: {{ template "podinfo.fullname" . }}-grpc-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 | "helm.sh/hook": test-success 12 | sidecar.istio.io/inject: "false" 13 | linkerd.io/inject: disabled 14 | appmesh.k8s.aws/sidecarInjectorWebhook: disabled 15 | spec: 16 | containers: 17 | - name: grpc-health-probe 18 | image: stefanprodan/grpc_health_probe:v0.3.0 19 | command: ['grpc_health_probe'] 20 | args: ['-addr={{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}:{{ .Values.service.grpcPort }}'] 21 | restartPolicy: Never 22 | -------------------------------------------------------------------------------- /hack/ci-mock.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | repository="stefanprodan/podinfo" 4 | branch="master" 5 | version="" 6 | commit=$(cat /dev/urandom | env LC_CTYPE=C tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1 | awk '{print tolower($0)}') 7 | 8 | while getopts :r:b:v: o; do 9 | case "${o}" in 10 | r) 11 | repository=${OPTARG} 12 | ;; 13 | b) 14 | branch=${OPTARG} 15 | ;; 16 | v) 17 | version=${OPTARG} 18 | ;; 19 | esac 20 | done 21 | shift $((OPTIND-1)) 22 | 23 | if [ -z "${version}" ]; then 24 | image="${repository}:${branch}-${commit}" 25 | version="0.4.0" 26 | else 27 | image="${repository}:${version}" 28 | fi 29 | 30 | echo ">>>> Building image ${image} <<<<" 31 | 32 | docker build --build-arg GITCOMMIT=${commit} --build-arg VERSION=${version} -t ${image} -f Dockerfile.ci . 33 | 34 | docker push ${image} 35 | -------------------------------------------------------------------------------- /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 | "helm.sh/hook": test-success 12 | sidecar.istio.io/inject: "false" 13 | linkerd.io/inject: disabled 14 | appmesh.k8s.aws/sidecarInjectorWebhook: disabled 15 | spec: 16 | containers: 17 | - name: curl 18 | image: giantswarm/tiny-tools 19 | command: 20 | - sh 21 | - -c 22 | - | 23 | curl -s ${PODINFO_SVC}/api/info | grep version 24 | env: 25 | - name: PODINFO_SVC 26 | value: {{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}:{{ .Values.service.externalPort }} 27 | restartPolicy: Never 28 | -------------------------------------------------------------------------------- /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/v1 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 | -------------------------------------------------------------------------------- /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 }} -------------------------------------------------------------------------------- /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 | "helm.sh/hook": test-success 12 | sidecar.istio.io/inject: "false" 13 | linkerd.io/inject: disabled 14 | appmesh.k8s.aws/sidecarInjectorWebhook: disabled 15 | spec: 16 | containers: 17 | - name: tools 18 | image: giantswarm/tiny-tools 19 | command: 20 | - sh 21 | - -c 22 | - | 23 | TOKEN=$(curl -sd 'test' ${PODINFO_SVC}/token | jq -r .token) && 24 | curl -sH "Authorization: Bearer ${TOKEN}" ${PODINFO_SVC}/token/validate | grep test 25 | env: 26 | - name: PODINFO_SVC 27 | value: {{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}:{{ .Values.service.externalPort }} 28 | restartPolicy: Never 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Stefan Prodan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /hack/Dockerfile.ci: -------------------------------------------------------------------------------- 1 | FROM golang:1.13 as builder 2 | 3 | ARG VERSION=unknown 4 | ARG GITCOMMIT=unknown 5 | 6 | RUN mkdir -p /podinfo/ 7 | 8 | WORKDIR /podinfo 9 | 10 | ADD https://github.com/stefanprodan/podinfo/archive/3.1.5.tar.gz . 11 | 12 | RUN tar xzf 3.1.5.tar.gz --strip 1 13 | 14 | RUN go mod download 15 | 16 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w \ 17 | -X github.com/stefanprodan/podinfo/pkg/version.GITCOMMIT=${GITCOMMIT} \ 18 | -X github.com/stefanprodan/podinfo/pkg/version.VERSION=${VERSION}" \ 19 | -a -o bin/podinfo cmd/podinfo/* 20 | 21 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w \ 22 | -X github.com/stefanprodan/podinfo/pkg/version.GITCOMMIT=${GITCOMMIT} \ 23 | -X github.com/stefanprodan/podinfo/pkg/version.VERSION=${VERSION}" \ 24 | -a -o bin/podcli cmd/podcli/* 25 | 26 | FROM alpine:3.10 27 | 28 | RUN addgroup -S app \ 29 | && adduser -S -g app app \ 30 | && apk --no-cache add \ 31 | curl openssl netcat-openbsd 32 | 33 | WORKDIR /home/app 34 | 35 | COPY --from=builder /podinfo/bin/podinfo . 36 | COPY --from=builder /podinfo/bin/podcli /usr/local/bin/podcli 37 | COPY --from=builder /podinfo/ui ./ui 38 | RUN chown -R app:app ./ 39 | 40 | USER app 41 | 42 | CMD ["./podinfo"] -------------------------------------------------------------------------------- /charts/podinfo/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "podinfo.fullname" . -}} 3 | {{- $ingressPath := .Values.ingress.path -}} 4 | apiVersion: extensions/v1beta1 5 | kind: Ingress 6 | metadata: 7 | name: {{ $fullName }} 8 | labels: 9 | app: {{ template "podinfo.name" . }} 10 | chart: {{ template "podinfo.chart" . }} 11 | release: {{ .Release.Name }} 12 | heritage: {{ .Release.Service }} 13 | {{- with .Values.ingress.annotations }} 14 | annotations: 15 | {{ toYaml . | indent 4 }} 16 | {{- end }} 17 | spec: 18 | {{- if .Values.ingress.tls }} 19 | tls: 20 | {{- range .Values.ingress.tls }} 21 | - hosts: 22 | {{- range .hosts }} 23 | - {{ . }} 24 | {{- end }} 25 | secretName: {{ .secretName }} 26 | {{- end }} 27 | {{- end }} 28 | rules: 29 | {{- range .Values.ingress.hosts }} 30 | - host: {{ . }} 31 | http: 32 | paths: 33 | - path: {{ $ingressPath }} 34 | backend: 35 | serviceName: {{ $fullName }} 36 | servicePort: http 37 | {{- end }} 38 | {{- if not .Values.ingress.hosts }} 39 | - http: 40 | paths: 41 | - path: {{ $ingressPath }} 42 | backend: 43 | serviceName: {{ $fullName }} 44 | servicePort: http 45 | {{- end }} 46 | {{- end }} 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -}} -------------------------------------------------------------------------------- /charts/podinfo/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for podinfo. 2 | 3 | replicaCount: 1 4 | logLevel: info 5 | backend: #http://backend-podinfo:9898/echo 6 | backends: [] 7 | 8 | ui: 9 | color: "#34577c" 10 | message: "" 11 | logo: "" 12 | 13 | faults: 14 | delay: false 15 | error: false 16 | 17 | h2c: 18 | enabled: false 19 | 20 | image: 21 | repository: stefanprodan/podinfo 22 | tag: 3.1.5 23 | pullPolicy: IfNotPresent 24 | 25 | service: 26 | enabled: true 27 | type: ClusterIP 28 | metricsPort: 9797 29 | httpPort: 9898 30 | externalPort: 9898 31 | grpcPort: 9999 32 | grpcService: podinfo 33 | nodePort: 31198 34 | 35 | # metrics-server add-on required 36 | hpa: 37 | enabled: false 38 | maxReplicas: 10 39 | # average total CPU usage per pod (1-100) 40 | cpu: 41 | # average memory usage per pod (100Mi-1Gi) 42 | memory: 43 | # average http requests per second per pod (k8s-prometheus-adapter) 44 | requests: 45 | 46 | serviceAccount: 47 | # Specifies whether a service account should be created 48 | enabled: false 49 | # The name of the service account to use. 50 | # If not set and create is true, a name is generated using the fullname template 51 | name: 52 | 53 | linkerd: 54 | profile: 55 | enabled: false 56 | 57 | ingress: 58 | enabled: false 59 | annotations: {} 60 | # kubernetes.io/ingress.class: nginx 61 | # kubernetes.io/tls-acme: "true" 62 | path: /* 63 | hosts: [] 64 | # - podinfo.local 65 | tls: [] 66 | # - secretName: chart-example-tls 67 | # hosts: 68 | # - chart-example.local 69 | 70 | resources: 71 | limits: 72 | requests: 73 | cpu: 1m 74 | memory: 16Mi 75 | 76 | nodeSelector: {} 77 | 78 | tolerations: [] 79 | 80 | affinity: {} 81 | 82 | -------------------------------------------------------------------------------- /charts/podinfo/templates/linkerd.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.linkerd.profile.enabled -}} 2 | apiVersion: linkerd.io/v1alpha2 3 | kind: ServiceProfile 4 | metadata: 5 | name: {{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local 6 | spec: 7 | routes: 8 | - condition: 9 | method: GET 10 | pathRegex: / 11 | name: GET / 12 | - condition: 13 | method: POST 14 | pathRegex: /api/echo 15 | name: POST /api/echo 16 | - condition: 17 | method: GET 18 | pathRegex: /api/info 19 | name: GET /api/info 20 | - condition: 21 | method: GET 22 | pathRegex: /chunked/[^/]* 23 | name: GET /chunked/{seconds} 24 | - condition: 25 | method: GET 26 | pathRegex: /delay/[^/]* 27 | name: GET /delay/{seconds} 28 | - condition: 29 | method: GET 30 | pathRegex: /env 31 | name: GET /env 32 | - condition: 33 | method: GET 34 | pathRegex: /headers 35 | name: GET /headers 36 | - condition: 37 | method: GET 38 | pathRegex: /healthz 39 | name: GET /healthz 40 | - condition: 41 | method: GET 42 | pathRegex: /metrics 43 | name: GET /metrics 44 | - condition: 45 | method: GET 46 | pathRegex: /panic 47 | name: GET /panic 48 | - condition: 49 | method: GET 50 | pathRegex: /readyz 51 | name: GET /readyz 52 | - condition: 53 | method: POST 54 | pathRegex: /readyz/disable 55 | name: POST /readyz/disable 56 | - condition: 57 | method: POST 58 | pathRegex: /readyz/enable 59 | name: POST /readyz/enable 60 | - condition: 61 | method: GET 62 | pathRegex: /status/[^/]* 63 | name: GET /status/{code} 64 | - condition: 65 | method: POST 66 | pathRegex: /store 67 | name: POST /store 68 | - condition: 69 | method: GET 70 | pathRegex: /store/[^/]* 71 | name: GET /store/{hash} 72 | - condition: 73 | method: POST 74 | pathRegex: /token 75 | name: POST /token 76 | - condition: 77 | method: POST 78 | pathRegex: /token/validate 79 | name: POST /token/validate 80 | - condition: 81 | method: GET 82 | pathRegex: /version 83 | name: GET /version 84 | - condition: 85 | method: POST 86 | pathRegex: /ws/echo 87 | name: POST /ws/echo 88 | {{- end }} -------------------------------------------------------------------------------- /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/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 | `backend` | echo backend URL | None 36 | `backends` | echo backend URL array | 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 | `image.pullPolicy` | image pull policy | `IfNotPresent` 45 | `image.repository` | image repository | `stefanprodan/podinfo` 46 | `image.tag` | image tag | `` 47 | `ingress.enabled` | enables ingress | `false` 48 | `ingress.annotations` | ingress annotations | None 49 | `ingress.hosts` | ingress accepted hostnames | None 50 | `ingress.tls` | ingress TLS configuration | None 51 | `message` | UI greetings message | None 52 | `nodeSelector` | node labels for pod assignment | `{}` 53 | `replicaCount` | desired number of pods | `2` 54 | `resources.requests/cpu` | pod CPU request | `1m` 55 | `resources.requests/memory` | pod memory request | `16Mi` 56 | `resources.limits/cpu` | pod CPU limit | None 57 | `resources.limits/memory` | pod memory limit | None 58 | `service.enabled` | create Kubernetes service (should be disabled when using Flagger) | `true` 59 | `service.metricsPort` | Prometheus metrics endpoint port | `9797` 60 | `service.externalPort` | ClusterIP HTTP port | `9898` 61 | `service.httpPort` | container HTTP port | `9898` 62 | `service.nodePort` | NodePort for the HTTP endpoint | `31198` 63 | `service.grpcPort` | ClusterIP gPRC port | `9999` 64 | `service.grpcService` | gPRC service name | `podinfo` 65 | `service.type` | type of service | `ClusterIP` 66 | `tolerations` | list of node taints to tolerate | `[]` 67 | `serviceAccount.enabled` | specifies whether a service account should be created | `false` 68 | `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 69 | `linkerd.profile.enabled` | create Linkerd service profile | `false` 70 | 71 | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, 72 | 73 | ```console 74 | $ helm install stable/podinfo --name my-release \ 75 | --set=image.tag=0.0.2,service.type=NodePort 76 | ``` 77 | 78 | Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example, 79 | 80 | ```console 81 | $ helm install stable/podinfo --name my-release -f values.yaml 82 | ``` 83 | 84 | > **Tip**: You can use the default [values.yaml](values.yaml) 85 | ``` 86 | 87 | -------------------------------------------------------------------------------- /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 | {{- range .Values.backends }} 52 | - --backend-url={{ . }} 53 | {{- end }} 54 | - --level={{ .Values.logLevel }} 55 | - --random-delay={{ .Values.faults.delay }} 56 | - --random-error={{ .Values.faults.error }} 57 | {{- if .Values.h2c.enabled }} 58 | - --h2c 59 | {{- end }} 60 | env: 61 | {{- if .Values.ui.message }} 62 | - name: PODINFO_UI_MESSAGE 63 | value: {{ .Values.ui.message }} 64 | {{- end }} 65 | {{- if .Values.ui.logo }} 66 | - name: PODINFO_UI_LOGO 67 | value: {{ .Values.ui.logo }} 68 | {{- end }} 69 | {{- if .Values.ui.color }} 70 | - name: PODINFO_UI_COLOR 71 | value: {{ .Values.ui.color }} 72 | {{- end }} 73 | {{- if .Values.backend }} 74 | - name: PODINFO_BACKEND_URL 75 | value: {{ .Values.backend }} 76 | {{- end }} 77 | ports: 78 | - name: http 79 | containerPort: {{ .Values.service.httpPort | default 9898 }} 80 | protocol: TCP 81 | {{- if .Values.service.metricsPort }} 82 | - name: http-metrics 83 | containerPort: {{ .Values.service.metricsPort }} 84 | protocol: TCP 85 | {{- end }} 86 | {{- if .Values.service.grpcPort }} 87 | - name: grpc 88 | containerPort: {{ .Values.service.grpcPort }} 89 | protocol: TCP 90 | {{- end }} 91 | livenessProbe: 92 | exec: 93 | command: 94 | - podcli 95 | - check 96 | - http 97 | - localhost:{{ .Values.service.httpPort | default 9898 }}/healthz 98 | initialDelaySeconds: 1 99 | timeoutSeconds: 5 100 | readinessProbe: 101 | exec: 102 | command: 103 | - podcli 104 | - check 105 | - http 106 | - localhost:{{ .Values.service.httpPort | default 9898 }}/readyz 107 | initialDelaySeconds: 1 108 | timeoutSeconds: 5 109 | volumeMounts: 110 | - name: data 111 | mountPath: /data 112 | resources: 113 | {{ toYaml .Values.resources | indent 12 }} 114 | {{- with .Values.nodeSelector }} 115 | nodeSelector: 116 | {{ toYaml . | indent 8 }} 117 | {{- end }} 118 | {{- with .Values.affinity }} 119 | affinity: 120 | {{ toYaml . | indent 8 }} 121 | {{- end }} 122 | {{- with .Values.tolerations }} 123 | tolerations: 124 | {{ toYaml . | indent 8 }} 125 | {{- end }} 126 | volumes: 127 | - name: data 128 | emptyDir: {} 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Managing Helm releases the GitOps way 2 | 3 | ## We are moving to Flux v2 4 | 5 | > ⚠️ Please note: In preparation of [Flux v2](https://toolkit.fluxcd.io/) GA this repository with Flux v1 examples has been archived. The Flux v2 equivalent of what is shown here can be found at [flux2-kustomize-helm-example](https://github.com/fluxcd/flux2-kustomize-helm-example). 6 | > 7 | > Thanks a lot for your interest. 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 declarative infrastructure and workloads. 12 | For Kubernetes this means using `git push` instead of `kubectl create/apply` or `helm install/upgrade`. 13 | 14 | In a traditional CICD pipeline, CD is an implementation extension powered by the 15 | continuous integration tooling to promote build artifacts to production. 16 | In the GitOps pipeline model, any change to production must be committed in source control 17 | (preferable via a pull request) prior to being applied on the cluster. 18 | This way rollback and audit logs are provided by Git. 19 | If the entire production state is under version control and described in a single Git repository, when disaster strikes, 20 | the whole infrastructure can be quickly restored from that repository. 21 | 22 | To better understand the benefits of this approach to CD and what the differences between GitOps and 23 | Infrastructure-as-Code tools are, head to the Weaveworks website and read [GitOps - What you need to know](https://www.weave.works/technologies/gitops/) article. 24 | 25 | In order to apply the GitOps pipeline model to Kubernetes you need three things: 26 | 27 | * a Git repository with your workloads definitions in YAML format, Helm charts and any other Kubernetes custom resource that defines your cluster desired state (I will refer to this as the *config* repository) 28 | * a container registry where your CI system pushes immutable images (no *latest* tags, use *semantic versioning* or git *commit sha*) 29 | * an operator that runs in your cluster and does a two-way synchronization: 30 | * watches the registry for new image releases and based on deployment policies updates the workload definitions with the new image tag and commits the changes to the config repository 31 | * watches for changes in the config repository and applies them to your cluster 32 | 33 | I will be using GitHub to host the config repo, Docker Hub as the container registry and Flux as the GitOps Kubernetes Operator. 34 | 35 | ![gitops](https://github.com/fluxcd/helm-operator-get-started/blob/master/diagrams/flux-helm-operator-registry.png) 36 | 37 | ### Prerequisites 38 | 39 | You'll need a Kubernetes cluster v1.11 or newer, a GitHub account, git and kubectl installed locally. 40 | 41 | Install Helm v3 and fluxctl for macOS with Homebrew: 42 | 43 | ```sh 44 | brew install helm fluxctl 45 | ``` 46 | 47 | On Windows you can use Chocolatey: 48 | 49 | ```sh 50 | choco install kubernetes-helm fluxctl 51 | ``` 52 | 53 | On Linux you can download the [helm](https://github.com/helm/helm/releases) 54 | and [fluxctl](https://github.com/fluxcd/flux/releases) binaries from GitHub. 55 | 56 | ### Install Flux 57 | 58 | The first step in automating Helm releases with [Flux](https://github.com/fluxcd/flux) is to create a Git repository with your charts source code. 59 | 60 | On GitHub, fork this repository and clone it locally 61 | (replace `fluxcd` with your GitHub username): 62 | 63 | ```sh 64 | git clone https://github.com/fluxcd/helm-operator-get-started 65 | cd helm-operator-get-started 66 | ``` 67 | 68 | *If you fork, update the release definitions with your Docker Hub repository and GitHub username located in 69 | \releases\(dev/stg/prod)\podinfo.yaml in your master branch before proceeding. 70 | 71 | Add FluxCD repository to Helm repos: 72 | 73 | ```bash 74 | helm repo add fluxcd https://charts.fluxcd.io 75 | ``` 76 | 77 | Create the `fluxcd` namespace: 78 | 79 | ```sh 80 | kubectl create ns fluxcd 81 | ``` 82 | 83 | Install Flux by specifying your fork URL (replace `fluxcd` with your GitHub username): 84 | 85 | ```bash 86 | helm upgrade -i flux fluxcd/flux --wait \ 87 | --namespace fluxcd \ 88 | --set git.url=git@github.com:fluxcd/helm-operator-get-started 89 | ``` 90 | 91 | Install the `HelmRelease` Kubernetes custom resource definition: 92 | 93 | ```sh 94 | kubectl apply -f https://raw.githubusercontent.com/fluxcd/helm-operator/master/deploy/crds.yaml 95 | ``` 96 | 97 | Install Flux Helm Operator with ***Helm v3*** support: 98 | 99 | ```bash 100 | helm upgrade -i helm-operator fluxcd/helm-operator --wait \ 101 | --namespace fluxcd \ 102 | --set git.ssh.secretName=flux-git-deploy \ 103 | --set helm.versions=v3 104 | ``` 105 | 106 | The Flux Helm operator provides an extension to Flux that automates Helm Chart releases for it. 107 | A Chart release is described through a Kubernetes custom resource named HelmRelease. 108 | The Flux daemon synchronizes these resources from git to the cluster, 109 | and the Flux Helm operator makes sure Helm charts are released as specified in the resources. 110 | 111 | Note that Flux Helm Operator works with Kubernetes 1.11 or newer. 112 | 113 | At startup, Flux generates a SSH key and logs the public key. Find the public key with: 114 | 115 | ```bash 116 | fluxctl identity --k8s-fwd-ns fluxcd 117 | ``` 118 | 119 | In order to sync your cluster state with Git you need to copy the public key and 120 | create a **deploy key** with **write access** on your GitHub repository. 121 | 122 | Open GitHub, navigate to your fork, go to _Setting > Deploy keys_ click on _Add deploy key_, check 123 | _Allow write access_, paste the Flux public key and click _Add key_. 124 | 125 | ### GitOps pipeline example 126 | 127 | The config repo has the following structure: 128 | 129 | ``` 130 | ├── charts 131 | │   └── podinfo 132 | │   ├── Chart.yaml 133 | │   ├── README.md 134 | │   ├── templates 135 | │   └── values.yaml 136 | ├── hack 137 | │   ├── Dockerfile.ci 138 | │   └── ci-mock.sh 139 | ├── namespaces 140 | │   ├── dev.yaml 141 | │   └── stg.yaml 142 | └── releases 143 | ├── dev 144 | │   └── podinfo.yaml 145 | └── stg 146 | └── podinfo.yaml 147 | ``` 148 | 149 | I will be using [podinfo](https://github.com/stefanprodan/podinfo) to demonstrate a full CI/CD pipeline including promoting releases between environments. 150 | 151 | I'm assuming the following Git branching model: 152 | * dev branch (feature-ready state) 153 | * stg branch (release-candidate state) 154 | * master branch (production-ready state) 155 | 156 | When a PR is merged in the dev or stg branch will produce a immutable container image as in `repo/app:branch-commitsha`. 157 | 158 | Inside the *hack* dir you can find a script that simulates the CI process for dev and stg. 159 | The *ci-mock.sh* script does the following: 160 | * pulls the podinfo source code from GitHub 161 | * generates a random string and modifies the code 162 | * generates a random Git commit short SHA 163 | * builds a Docker image with the format: `yourname/podinfo:branch-sha` 164 | * pushes the image to Docker Hub 165 | 166 | Let's create an image corresponding to the `dev` branch (replace `stefanprodan` with your Docker Hub username): 167 | 168 | ``` 169 | $ cd hack && ./ci-mock.sh -r stefanprodan/podinfo -b dev 170 | 171 | Sending build context to Docker daemon 4.096kB 172 | Step 1/15 : FROM golang:1.13 as builder 173 | .... 174 | Step 9/15 : FROM alpine:3.10 175 | .... 176 | Step 12/15 : COPY --from=builder /go/src/github.com/stefanprodan/k8s-podinfo/podinfo . 177 | .... 178 | Step 15/15 : CMD ["./podinfo"] 179 | .... 180 | Successfully built 71bee4549fb2 181 | Successfully tagged stefanprodan/podinfo:dev-kb9lm91e 182 | The push refers to repository [docker.io/stefanprodan/podinfo] 183 | 36ced78d2ca2: Pushed 184 | ``` 185 | 186 | Inside the *charts* directory there is a podinfo Helm chart. 187 | Using this chart I want to create a release in the `dev` namespace with the image I've just published to Docker Hub. 188 | Instead of editing the `values.yaml` from the chart source, I create a `HelmRelease` definition (located in /releases/dev/podinfo.yaml): 189 | 190 | ```yaml 191 | apiVersion: helm.fluxcd.io/v1 192 | kind: HelmRelease 193 | metadata: 194 | name: podinfo-dev 195 | namespace: dev 196 | annotations: 197 | fluxcd.io/automated: "true" 198 | filter.fluxcd.io/chart-image: glob:dev-* 199 | spec: 200 | releaseName: podinfo-dev 201 | chart: 202 | git: git@github.com:fluxcd/helm-operator-get-started 203 | path: charts/podinfo 204 | ref: master 205 | values: 206 | image: 207 | repository: stefanprodan/podinfo 208 | tag: dev-kb9lm91e 209 | replicaCount: 1 210 | ``` 211 | 212 | Flux Helm release fields: 213 | 214 | * `metadata.name` is mandatory and needs to follow Kubernetes naming conventions 215 | * `metadata.namespace` is optional and determines where the release is created 216 | * `spec.releaseName` is optional and if not provided the release name will be $namespace-$name 217 | * `spec.chart.path` is the directory containing the chart, given relative to the repository root 218 | * `spec.values` are user customizations of default parameter values from the chart itself 219 | 220 | The options specified in the HelmRelease `spec.values` will override the ones in `values.yaml` from the chart source. 221 | 222 | With the `fluxcd.io/automated` annotations I instruct Flux to automate this release. 223 | When a new tag with the prefix `dev` is pushed to Docker Hub, Flux will update the image field in the yaml file, 224 | will commit and push the change to Git and finally will apply the change on the cluster. 225 | 226 | ![gitops-automation](https://github.com/stefanprodan/openfaas-flux/blob/master/docs/screens/flux-helm-image-update.png) 227 | 228 | When the `podinfo-dev` HelmRelease object changes inside the cluster, 229 | Kubernetes API will notify the Flux Helm Operator and the operator will perform a Helm release upgrade. 230 | 231 | ``` 232 | $ helm -n dev history podinfo-dev 233 | 234 | REVISION STATUS CHART DESCRIPTION 235 | 1 superseded podinfo-0.2.0 Install complete 236 | 2 deployed podinfo-0.2.0 Upgrade complete 237 | ``` 238 | 239 | The Flux Helm Operator reacts to changes in the HelmRelease collection but will also detect changes in the charts source files. 240 | If I make a change to the podinfo chart, the operator will pick that up and run an upgrade. 241 | 242 | ![gitops-chart-change](https://github.com/stefanprodan/openfaas-flux/blob/master/docs/screens/flux-helm-chart-update.png) 243 | 244 | ``` 245 | $ helm -n dev history podinfo-dev 246 | 247 | REVISION STATUS CHART DESCRIPTION 248 | 1 superseded podinfo-0.2.0 Install complete 249 | 2 superseded podinfo-0.2.0 Upgrade complete 250 | 3 deployed podinfo-0.2.1 Upgrade complete 251 | ``` 252 | 253 | Now let's assume that I want to promote the code from the `dev` branch into a more stable environment for others to test it. 254 | I would create a release candidate by merging the podinfo code from `dev` into the `stg` branch. 255 | The CI would kick in and publish a new image: 256 | 257 | ```bash 258 | $ cd hack && ./ci-mock.sh -r stefanprodan/podinfo -b stg 259 | 260 | Successfully tagged stefanprodan/podinfo:stg-9ij63o4c 261 | The push refers to repository [docker.io/stefanprodan/podinfo] 262 | 8f21c3669055: Pushed 263 | ``` 264 | 265 | Assuming the staging environment has some sort of automated load testing in place, 266 | I want to have a different configuration than dev: 267 | 268 | ```yaml 269 | apiVersion: helm.fluxcd.io/v1 270 | kind: HelmRelease 271 | metadata: 272 | name: podinfo-rc 273 | namespace: stg 274 | annotations: 275 | fluxcd.io/automated: "true" 276 | filter.fluxcd.io/chart-image: glob:stg-* 277 | spec: 278 | releaseName: podinfo-rc 279 | chart: 280 | git: git@github.com:fluxcd/helm-operator-get-started 281 | path: charts/podinfo 282 | ref: master 283 | values: 284 | image: 285 | repository: stefanprodan/podinfo 286 | tag: stg-9ij63o4c 287 | replicaCount: 2 288 | hpa: 289 | enabled: true 290 | maxReplicas: 10 291 | cpu: 50 292 | memory: 128Mi 293 | ``` 294 | 295 | With Flux Helm releases it's easy to manage different configurations per environment. 296 | When adding a new option in the chart source make sure it's turned off by default so it will not affect all environments. 297 | 298 | If I want to create a new environment, let's say for hotfixes testing, I would do the following: 299 | * create a new namespace definition in `namespaces/hotfix.yaml` 300 | * create a dir `releases/hotfix` 301 | * create a HelmRelease named `podinfo-hotfix` 302 | * set the automation filter to `glob:hotfix-*` 303 | * make the CI tooling publish images from my hotfix branch to `stefanprodan/podinfo:hotfix-sha` 304 | 305 | ### Production promotions with sem ver 306 | 307 | For production, instead of tagging the images with the Git commit, I will use [Semantic Versioning](https://semver.org). 308 | 309 | Let's assume that I want to promote the code from the `stg` branch into `master` and do a production release. 310 | After merging `stg` into `master` via a pull request, I would cut a release by tagging `master` with version `0.4.10`. 311 | 312 | When I push the git tag, the CI will publish a new image in the `repo/app:git_tag` format: 313 | 314 | ```bash 315 | $ cd hack && ./ci-mock.sh -r stefanprodan/podinfo -v 0.4.10 316 | 317 | Successfully built f176482168f8 318 | Successfully tagged stefanprodan/podinfo:0.4.10 319 | ``` 320 | 321 | If I want to automate the production deployment based on version tags, I would use `semver` filters instead of `glob`: 322 | 323 | ```yaml 324 | apiVersion: helm.fluxcd.io/v1 325 | kind: HelmRelease 326 | metadata: 327 | name: podinfo-prod 328 | namespace: prod 329 | annotations: 330 | fluxcd.io/automated: "true" 331 | filter.fluxcd.io/chart-image: semver:~0.4 332 | spec: 333 | releaseName: podinfo-prod 334 | chart: 335 | git: git@github.com:fluxcd/helm-operator-get-started 336 | path: charts/podinfo 337 | ref: master 338 | values: 339 | image: 340 | repository: stefanprodan/podinfo 341 | tag: 0.4.10 342 | replicaCount: 3 343 | ``` 344 | 345 | Now if I release a new patch, let's say `0.4.11`, Flux will automatically deploy it. 346 | 347 | ```bash 348 | $ cd hack && ./ci-mock.sh -r stefanprodan/podinfo -v 0.4.11 349 | 350 | Successfully tagged stefanprodan/podinfo:0.4.11 351 | ``` 352 | 353 | ![gitops-semver](https://github.com/stefanprodan/openfaas-flux/blob/master/docs/screens/flux-helm-semver.png) 354 | 355 | ### Managing Kubernetes secrets 356 | 357 | In order to store secrets safely in a public Git repo you can use the Bitnami [Sealed Secrets controller](https://github.com/bitnami-labs/sealed-secrets) 358 | and encrypt your Kubernetes Secrets into SealedSecrets. 359 | The SealedSecret can be decrypted only by the controller running in your cluster. 360 | 361 | The Sealed Secrets Helm chart is available on [Helm Hub](https://hub.helm.sh/charts/stable/sealed-secrets), 362 | so I can use the Helm repository instead of a git repo. This is the sealed-secrets controller release: 363 | 364 | ```yaml 365 | apiVersion: helm.fluxcd.io/v1 366 | kind: HelmRelease 367 | metadata: 368 | name: sealed-secrets 369 | namespace: adm 370 | spec: 371 | releaseName: sealed-secrets 372 | chart: 373 | repository: https://kubernetes-charts.storage.googleapis.com/ 374 | name: sealed-secrets 375 | version: 1.6.1 376 | ``` 377 | 378 | Note that this release is not automated, since this is a critical component I prefer to update it manually. 379 | 380 | Install the kubeseal CLI: 381 | 382 | ```bash 383 | brew install kubeseal 384 | ``` 385 | 386 | At startup, the sealed-secrets controller generates a RSA key and logs the public key. 387 | Using kubeseal you can save your public key as `pub-cert.pem`, 388 | the public key can be safely stored in Git, and can be used to encrypt secrets without direct access to the Kubernetes cluster: 389 | 390 | ```bash 391 | kubeseal --fetch-cert \ 392 | --controller-namespace=adm \ 393 | --controller-name=sealed-secrets \ 394 | > pub-cert.pem 395 | ``` 396 | 397 | You can generate a Kubernetes secret locally with kubectl and encrypt it with kubeseal: 398 | 399 | ```bash 400 | kubectl -n dev create secret generic basic-auth \ 401 | --from-literal=user=admin \ 402 | --from-literal=password=admin \ 403 | --dry-run \ 404 | -o json > basic-auth.json 405 | 406 | kubeseal --format=yaml --cert=pub-cert.pem < basic-auth.json > basic-auth.yaml 407 | ``` 408 | 409 | This generates a custom resource of type `SealedSecret` that contains the encrypted credentials: 410 | 411 | ```yaml 412 | apiVersion: bitnami.com/v1alpha1 413 | kind: SealedSecret 414 | metadata: 415 | name: basic-auth 416 | namespace: adm 417 | spec: 418 | encryptedData: 419 | password: AgAR5nzhX2TkJ....... 420 | user: AgAQDO58WniIV3gTk....... 421 | ``` 422 | 423 | Delete the `basic-auth.json` file and push the `pub-cert.pem` and `basic-auth.yaml` to Git: 424 | 425 | ```bash 426 | rm basic-auth.json 427 | mv basic-auth.yaml /releases/dev/ 428 | 429 | git commit -a -m "Add basic auth credentials to dev namespace" && git push 430 | ``` 431 | 432 | Flux will apply the sealed secret on your cluster and sealed-secrets controller will then decrypt it into a 433 | Kubernetes secret. 434 | 435 | ![SealedSecrets](https://github.com/fluxcd/helm-operator-get-started/blob/master/diagrams/flux-helm-operator-sealed-secrets.png) 436 | 437 | To prepare for disaster recovery you should backup the sealed-secrets controller private key with: 438 | 439 | ```bash 440 | kubectl get secret -n adm sealed-secrets-key -o yaml --export > sealed-secrets-key.yaml 441 | ``` 442 | 443 | To restore from backup after a disaster, replace the newly-created secret and restart the controller: 444 | 445 | ```bash 446 | kubectl replace secret -n adm sealed-secrets-key -f sealed-secrets-key.yaml 447 | kubectl delete pod -n adm -l app=sealed-secrets 448 | ``` 449 | 450 | ### Getting Help 451 | 452 | If you have any questions about Helm Operator and continuous delivery: 453 | 454 | - Read [the Helm Operator docs](https://docs.fluxcd.io/projects/helm-operator/en/latest/). 455 | - Read [the Flux integration with the Helm operator docs](https://docs.fluxcd.io/en/latest/references/helm-operator-integration.html). 456 | - Invite yourself to the CNCF community 457 | slack and ask a question on the [#flux](https://cloud-native.slack.com/messages/flux/) 458 | channel. 459 | - To be part of the conversation about Helm Operator's development, join the 460 | [flux-dev mailing list](https://lists.cncf.io/g/cncf-flux-dev). 461 | - [File an issue.](https://github.com/fluxcd/flux/issues/new) 462 | 463 | Your feedback is always welcome! 464 | --------------------------------------------------------------------------------