Helm-manager
說明用 ArgoCD ApplicationSet 集中管理多 cluster 的 infra Helm charts 的動機,並整理各種 generator 的用法。
Outlines
- Why
- GC k8s 專案 infra 包含的元素
- AWS infra, like vpc/eks/route53
- Infra Charts, like ingress nginx/prometheus/load balancer controller … etc.
- ArgoCD applications, like applications themselves
- 原有 helm chart & infra 部署方式:
- AWS infra & infra charts: terraform
- ArgoCD application: helm chart (read gitlab repo)
- 這樣做確切的痛點
- terraform 沒辦法識別 k8s_manifest 裡面包含的資源是否是 CRD,因此如果一次 apply helm chart controller 和 CRD,就算有在 CRD 裡寫 depends_on controller,一就算是在 controller 沒建好之前就安裝 CRD。此時因為 controller 尚未建立完成,terraform 打 request 給 api server 說要安裝這東西,但因為 api server 不認得這個 CRD,因此會造成報錯。
- workaround 是寫 local-exec 的 provisioner,但官方建議非必要盡量不要使用 provisioner。這是因為 terraform 是宣告式的語言,而 provisioner 是提供命令式語言的工具,會造成以下幾點壞處:
- 容易造成不一致的部署結果,依據 provisioner 裡面的內容,執行的程式若失敗了不一定能夠讓 terraform 自動 rollback
- Provisioner 是在 Terraform 認為「資源建立成功」後才執行,若 provisioner 失敗,資源仍會被標記為成功建立,導致狀態不一致
- GC k8s 專案 infra 包含的元素
- What
- 我們是如何發現可以用 applicationset 來管理 helm charts 的?
- 整包 helm chart 一起用
helm部署時就不會遇到這樣的問題 - 每一個專案就會建一個 cluster,cluster 的基礎設施都差不多,重複部署會花比較多時間去個別管理
- 整包 helm chart 一起用
- Introduction to ApplicationSet
- Architecture
- centralized cluster controlling all argocd applications
- repo structure
- 我們是如何發現可以用 applicationset 來管理 helm charts 的?
- Summary
舊的 helm-manager 文件
Project Layout
# List Generator
.
├── internal
│ ├── Chart.yaml
│ ├── templates
│ │ └── infra charts 的 ApplicationSets
│ └── values.yaml
├── stable
│ ├── Chart.yaml
│ ├── templates
│ │ └── infra charts 的 ApplicationSets
│ └── values.yaml
├── shared-values
│ └── infra charts 的共用 values
└── charts/
└── prometheus/
│ ├── templates/
│ ├── Charts.yaml (with dependency chart)
│ └── values.yaml
└── infra charts/
- Generator 參數使用法:
{{"{{"}} var_name {{"}}"}}- 第一層 render 是 helm 填入 values.yaml 的值
- 第二層 render 是 ApplicationSet 填入 generators 的
- infra charts
- ingress-nginx
- client_a(裡面是這個 app 應該要有的 custom values)
- mo
- load-balancer-controller
- xxx
- karpenter
- external-secrets
- keda
- image-updater
- prometheus-stack
- ingress-nginx
已知問題
- 會用到 generator 帶的變數的欄位要寫在 valuesObject 裡面(generator 參數傳不進 value files)
- list generator 不一定是最佳解
- 用 stable/internal branch 的優缺點
- 之後要加服務時,要同時給兩個分支加 → 重工?
- git basic,類似 release 到 staging/prod 的分支策略
- 之後要加服務時,要同時給兩個分支加 → 重工?
- AppSet 長出來的 App 不能放在跟 AppSet 不同的 ns 裡
- Solution:
- AppSet 放在自己的 appset ns
⚠️ 去訂閱那個 issue
- Solution:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: ingress-nginx
namespace: {{ .Values.appset_ns }}
spec:
generators:
- list:
elements:
- namespace: client_a-internal
name: client_a-internal
- namespace: client_b-internal
name: client_b-internal
template:
metadata:
# https://github.com/argoproj/applicationset/issues/553#issuecomment-1075581614
name: '{{"{{"}}namespace{{"}}"}}-ingress-nginx'
namespace: '{{"{{"}}namespace{{"}}"}}'
spec:
project: {{ .Values.project }}
source:
repoURL: https://kubernetes.github.io/ingress-nginx
chart: ingress-nginx
targetRevision: 4.8.2
destination:
name: '{{"{{"}}name{{"}}"}}'
namespace: kube-system
# This sync policy pertains to the ApplicationSet, not to the Applications it creates.
syncPolicy:
# Determines whether the controller will delete Applications when an ApplicationSet is deleted.
preserveResourcesOnDeletion: true
Generators
List Generator
-
基於 list 裡面的 element,為每個 element 分別長出各自的 App
-
element 沒有必填欄位
-
List Generator Example
spec: generators: - list: elements: # v0.1.0 form - requires cluster/url keys: - cluster: engineering-dev url: https://kubernetes.default.svc values: additional: value # v0.2.0+ form - does not require cluster/URL keys # (but they are still supported). - staging: "true" gitRepo: https://kubernetes.default.svc # (...) -
可以用 matrix_generator 混合 list + git generator
- 用 git generator 指到包含 list element 的檔案
- 用 list generator 的 elementsYaml 解析上述檔案的 yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: elementsYaml
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- matrix:
generators:
- git:
repoURL: https://github.com/argoproj/argo-cd.git
revision: HEAD
files:
- path: applicationset/examples/list-generator/list-elementsYaml-example.yaml
- list:
elementsYaml: "{{ .key.components | toJson }}"
template:
metadata:
name: '{{.name}}'
spec:
project: default
sources:
- chart: '{{.chart}}'
repoURL: '{{.repoUrl}}'
targetRevision: '{{.version}}'
helm:
releaseName: '{{.releaseName}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{.namespace}}'
---
# list-elementsYaml-example.yaml
key:
components:
- name: component1
chart: podinfo
version: "6.3.2"
releaseName: component1
repoUrl: "https://stefanprodan.github.io/podinfo"
namespace: component1
- name: component2
chart: podinfo
version: "6.3.3"
releaseName: component2
repoUrl: "ghcr.io/stefanprodan/charts"
namespace: component2
Cluster Generator
-
根據 argo 已經管理的 clusters 作為 generators
- 被 argo 管理的 clusters 儲存在 argo ns 的 secret 裡,AppSet 拿 secret 當作分辨 available cluster 的依據 → argo-cluster-secret-${project_name}
- 為避免與 external-secret 的 ClusterSecret 搞混,以下會稱此 secret 為 argo cluster secret
-
可用的 parameters
- name / nameNormalized
- server
- metadata.labels.<key> (for each label in the Secret)
- metadata.annotations.<key> (for each annotation in the Secret)
-
可以用 values 傳入自定義的 parameters
-
Cluster GeneratorExample
spec: goTemplate: true goTemplateOptions: ["missingkey=error"] generators: - clusters: {} # Automatically use all clusters defined within Argo CD -
可以用 Label Selector 來選取特定的 cluster,會對應到含有特定 label 的 argo cluster secret
- 要包含 local cluster 的話,可以幫 local cluster 建立 argo cluster secret
Git Generator
分為 directory 與 file 兩種 generator
Git Directory Generator
-
可以長出指定路徑下各個 folder 作為 Application
-
可以使用 exclude 指定特定 folder 不要被生成 Application [details]
-
要讀取 repo root,path 可以放 ’*’
-
可以用 values 傳入自定義的 parameters,呼叫時需使用 {{.values.xxx}}
-
使用 git directory generator 時可以使用的 parameters
{{.path.path}}: Git repo 中符合 directory path wildcard 規則的 path{{index .path.segments n}}: 所有符合 path wildcard 的 path 陣列 (n - array index){{.path.basename}}: path 中最右邊的一段 (e.g. /directory/directory2 → directory2).{{.path.basenameNormalized}}: 把 path.basename 中不支援的字元替換成 - (e.g. path.basename = directory_2 → directory-2)
-
Generator Example
generators: - git: repoURL: https://github.com/argoproj/argo-cd.git revision: HEAD directories: - path: applicationset/examples/git-generator-directory/cluster-addons/* - path: applicationset/examples/git-generator-directory/excludes/cluster-addons/exclude-helm-guestbook exclude: true values: cluster: '{{.branch}}-{{.path.basename}}'
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-addons
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/argoproj/argo-cd.git
revision: HEAD
directories:
- path: applicationset/examples/git-generator-directory/cluster-addons/*
template:
metadata:
name: '{{.path.basename}}'
spec:
project: "my-project"
source:
repoURL: https://github.com/argoproj/argo-cd.git
targetRevision: HEAD
path: '{{.path.path}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{.path.basename}}'
syncPolicy:
syncOptions:
- CreateNamespace=true
---
# Directory tree under cluster-addons/ (path)
├── argo-workflows
│ ├── kustomization.yaml
│ └── namespace-install.yaml
└── prometheus-operator
├── Chart.yaml
├── README.md
├── requirements.yaml
└── values.yaml
Git File Generator
-
為每個符合規則的檔案各自生成 Application
-
可以用 values 傳入自定義的 parameters
-
可以傳入 json/yaml 兩種檔案,被 generator 讀取之後會變成 template parameters
→ 所以 goTemplate 要設定為 true
-
可用的 parameters 與 directory generator 類似
-
Example
spec: goTemplate: true goTemplateOptions: ["missingkey=error"] generators: - git: repoURL: https://github.com/argoproj/argo-cd.git revision: HEAD files: - path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json" # Directory tree under cluster-addons/ (path) ├── cluster-config │ └── engineering │ ├── dev │ │ └── config.json │ └── prod │ └── config.json └── git-generator-files.yaml # Content inside config.json { "aws_account": "123456", "asset_id": "11223344", "cluster": { "owner": "cluster-admin@company.com", "name": "engineering-dev", "address": "https://1.2.3.4" } }
Webhook Config
預設 AppSet 會每三分鐘 poll 一次 git repo,如果想要避免這個 delay 的話,可以透過設定 webhook 讓 gitlab/github 主動戳 argo
SCM Provider Generator ¶
- 利用 SCMaaS provider API (eg GitHub) 自動偵測 organization 有沒有新的 repo
- 適用於把 microservices 切到各個 repo 的架構
Pull Request Generator
-
需要搭配 SCMaaS provider API 使用
-
跟 git generator 一樣,PR generator 可以設定 webhook 避免三分鐘才 poll 一次的 delay
-
apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: myapps spec: goTemplate: true goTemplateOptions: ["missingkey=error"] generators: - pullRequest: gitlab: # The GitLab project. project: myproject # For self-hosted GitLab (optional) api: https://git.example.com/ # Reference to a Secret containing an access token. (optional) tokenRef: secretName: gitlab-token key: token # Labels is used to filter the MRs that you want to target. (optional) labels: - preview # MR state is used to filter MRs only with a certain state. (optional) pullRequestState: opened # If true, skips validating the SCM provider's TLS certificate - useful for self-signed certificates. insecure: false requeueAfterSeconds: 1800 template: # ...
Post Selector All Generator
-
在最後生成的 parameters 裡面選擇只保留符合的參數組
-
Example
generators: - list: elements: - cluster: engineering-dev url: https://kubernetes.default.svc env: staging - cluster: engineering-prod url: https://kubernetes.default.svc env: prod selector: # -> Post selector matchLabels: env: staging # 實際上 valid 的 parameters - cluster: engineering-dev url: https://kubernetes.default.svc env: staging
Matrix Generator
- 可以將兩個 generator 生成的 parameters 做結合,並對兩個 generator 的所有組合作迭代
-
Example: Git Directory + Cluster
- Git Directory → Premetheus Operator(PO) & Grafana Dashboard(GD)
- Cluster → client_a-internal(d1) & client_b-internal(m1)
$$ → (PO, GD) \times(d1, m1)=(PO, d1), (GD, d1), (PO, m1), (GD, m1) $$
-
- Example Combination
- SCM + Cluster:讀取某個 org 把新的 repo 佈在所有 cluster 上
- Git File + List:透過 config files 部署 apps 到特定 list 中的所有 cluster 裡
- Git Directory + Cluster Decision Resource:把在 git dir 中的 app 部署到透過 external custom resource 提供的 cluster 中
- Git Directory: infra charts using dependency chart with custom values
- Cluster: list out all remote clusters
Merge Generator
-
可以將多個 generator 生成的 parameter 結合,但會依據 merge keys 做合併
-
後面的 generator 會覆蓋掉前面的設定
generators: # merge 'parent' generator - merge: mergeKeys: - server generators: - clusters: values: kafka: 'true' redis: 'false' # For clusters with a specific label, enable Kafka. - clusters: selector: matchLabels: use-kafka: 'false' values: kafka: 'false' # For a specific cluster, enable Redis. - list: elements: - server: https://2.4.6.8 values.redis: 'true'
-
拿到所有 clusters, with value
kafka: true&redis: false→ 生成以下 parameter 組合:
- name: staging server: https://1.2.3.4 values.kafka: 'true' values.redis: 'false' - name: production server: https://2.4.6.8 values.kafka: 'true' values.redis: 'false' -
選定 argo cluster secret 的 label 符合 use-kafka: false 的那組 parameters,
with value
kafka: false→ 生成以下 parameter 組合:
# Staging cluster has the label `use-kafka: false` - name: staging server: https://1.2.3.4 values.kafka: 'false' values.redis: 'false' - name: production server: https://2.4.6.8 values.kafka: 'true' values.redis: 'false' -
選定 server: https://2.4.6.8 的 parameter,代入 value redis: true
→ 生成以下 parameter 組合:
- name: staging server: https://1.2.3.4 values.kafka: 'false' values.redis: 'false' - name: production server: https://2.4.6.8 values.kafka: 'true' values.redis: 'true'generators: # merge 'parent' generator: # Use the selector set by both child generators to combine them. - merge: mergeKeys: # Note that this would not work with goTemplate enabled, # nested merge keys are not supported there. - values.selector generators: # Assuming, all configured clusters have a label for their location: # Set the selector to this location. - clusters: values: selector: '{{index .metadata.labels "location"}}' # The git repo may have different directories which correspond to the # cluster locations, using these as a selector. - git: repoURL: https://github.com/argoproj/argocd-example-apps/ revision: HEAD directories: - path: '*' values: selector: '{{.path.path}}'
-
假設 cluster 叫做 germany01,location label = Germany,且 git repo 有個 directory 叫 Hamburger,此時生成的 parameter 如下:
# From the cluster generator - name: germany01 server: https://1.2.3.4 # From the git generator path: Hamburger # Combining selector with the merge generator values.selector: 'Hamburger' # original is 'Germany' # More values from cluster & git generator # […] -
merge in merge/matrix + post selector 要搭配 enable spec.applyNestedSelectors 才能用
🚧 Cluster Decision Resource Generator
- 針對自己寫的 CRD 的 generator
🚧 Plugin Generator
- 自定義的 Generator
Helm-Manager Slide