利用 Flux2 為 Kubernetes 達成 Configuration Management

smalltown
Starbugs Weekly 星巴哥技術專欄
24 min readJan 19, 2021

Background

自己管理的 Kubernetes Cluster 已經十幾座了,而且會越來越多,為什麼有這麼多座呢?因為隨著組織的成長,就會開始有不同的部門,同部門內又會依照環境至少分成開發,測試以及正式三個 K8s Cluster,因此預期 K8s Cluster 的座數在未來只會增加不會減少;或許有人會想說怎麼不準備一個大 K8s Cluster 給大家一起共用就好?!但為了安全性和其他考量,最終還是決定維持多個 K8s Cluster 的模式,有興趣想要知道怎麼決定組織內該使用一座或是多座 K8s Cluster 的人可以參考 “這篇文章” 的利害分析

不同座 K8s Cluster 分別是給不同部門和環境使用,每座 K8s Cluster 一定都會有一些重複的東西需要去做設定,並讓維運人員去做後續的管理,例如 Prometheus, Ingress Controller…等,秉持著 Infrastructure as Code 的精神, 在這幾年管理 K8s Cluster 大致上可以分成三個時期:

1. helm

helm-repo
├── charts
├── develop
├── production
├── staging
└── ...

在多年前 helm 剛被提出時,我們讓一個部門使用一個 Git Repository,在裡面透過資料夾結構安排,將不同的環境切開來,並且將 helm chart 放在 charts 的資料夾內,至於安裝套件間的相依性,雜七雜八需要注意的,就透過 makefile 和文件去補足

2. helmfile

helmfile
├── develop
│ └── helmfile.d
├── production
│ └── helmfile.d
└── staging
└── helmfile.d

隨著需要管理的 K8s Cluster 越來越多,以及安裝的東西越來越複雜,所以開始研究類似 helmfile 跟 helmsman,這類型的工具有點像是在模仿 Terraform 的操作模式,並且可以把不同環境安裝時的組態都先定義好,也把 helm chart repository 都抽象化,套件之間的相依性也可以幫忙解決

3. GitOps

但科技始終來自於惰性,雖然使用 helmfile 可以應付大部分的需求,但總是覺得不夠安全,也不夠自動化,所以這時候就將目光擺向的這篇要提的 GitOps 工具

Flux vs. Argo CD vs. Jenkins X

大家對於 GitOps 的工具應該都很熟了,跟傳統部署最大的差異就是在於從 Push 改成 Pull 的模式,所以在安全性上比較好,因此在此並不是要介紹這三個工具,而是要討論他們 1) 適不適合用來管理多個 K8s Cluster 跟 2) 有沒有辦法做到 Multi-Tenancy

Flux

Multi-Tenancy: 因為一個 Flux Instance 只支援跟單一個 Git Repository 同步,所以會需要多個 Git Repository 和多個 Flux Instance 來達成

Multi-Cluster: 透過監看同一個 Git Repository 中的不同資料夾,或是不同環境切分不同的 Git Repository 來達成

Conclusion: 算是三個裡面最簡單易用的,但自己覺得在整體管理和資源利用上不太完美

Argo CD

Multi-Tenancy: 原生就支援,利用 Project 的概念讓不同的應用程式可以對應到團隊上,算是很到位的解決方案

Multi-Cluster: 目前看到的做法是利用單一個 ArgoCD Instance 去存取外部 K8s Cluster 的方式來達成

Conclusion: 可以算是 Flux 的進化版,而且還擁有精美的 UI (雖然我有 CLI 就夠了XD),而且原生支援 Multi-Tenancy,多個 Cluster 的管理也有,以安全性為考量的話,感覺可以拆成正式跟非正式環境兩套的架構

Jenkins X

Multi-Tenancy:不支援

Multi-Cluster:可以透過 Environment Controller 去達成的樣子

ConclusionJenkins X 跟上面兩者最大的差異在於它包山包海,不像上面兩者只能做到 Continuous Deployment, 他使用 Tekton 達成 Continuous Integration,使用 Skaffold & Kaniko 建置 Container Image, ,使用 Lighthouse 實作 ChatBot…等,真得是要你命 3000 XD 他是一個野心超級大的專案,他其實跟 Jenkins 老爺爺一點關係也沒有,而自己只是先想解決多座 K8s Cluster 的設定管理問題,所以就先暫時不考慮這麼複雜的解決方案

Flux2 Introduction

當初做完上面的詳細分析,順理成章地選擇了 Argo CD 並且開始導入的工作,但就在這個時候突然看到 Flux2 推出的消息,仔細研究之後,發現他在設計上有考量過 Multi-Tenancy 和 Multi-Cluster,而且在安全性和管理上都有考慮,因此在評估完後進一步把心得分享出來,提供給有需要的人參考,底下先來介紹他主要的核心概念,順道一提,Flux2 所有的元件都是使用 K8s CRD 來定義,所以使用起來相當的清爽

Source Controller

這類型的 K8s CRD 最主要的功能就是提供一個共通的介面來定義獲取 Artifact 的方式,例如從 Git 或是 Helm Repository,甚至可以從 Object Storage,例如 AWS S3

Kustomize Controller

此 K8s CRD 專門用來處理使用 Kustomize 定義的 Kubernetes Manifest 以供後續的部署使用

Helm Controller

定義要部署的一般應用服務或是共用基礎設施時所需要使用到的 Helm Chart,自定義的組態值,多久要同步一次…等

Notification Controller

這類的 K8s CRD 還滿酷的,專門用來負責接收或是傳送 Event,例如收到什麼 Event 就要開始做同步,或是同步完就把 Event 送到 Slack 之類的

Demonstration

假設我們想要把一般應用服務和共用基礎設施透過 GitOps 的方式部署到多個K8s Cluster 中,那麼我們該如何使用 Flux2 來達成呢?就讓我用底下的例子來簡單示範

Prepare K8s Environment

這邊使用 Kind 來建置兩個 K8s Cluster,分別代表 staging 和 production 環境

# 安裝 kind,其他平台安裝方式請參閱官方文件
~$ brew install kind
# 建立 staging cluster
~$ kind create cluster --name staging
# 建立 production K8s cluster
~$ kind create cluster --name production

Prepare GitHub Resource

首先請 folk 這個 GitHub Repository: https://github.com/smalltown/flux2-demo,接下來需要一把 GitHub access token,到 GitHub 頁面的 Settings -> Developer settings -> Personal access tokens 產生一把新的 token,權限給 repo 那一欄 即可

Prepare Flux2

接著安裝 Flux2 CLI,並且檢查前面的動作是否正確

# 安裝 Flux2 CLI,其他平台安裝方式請參考官方文件
~$ brew install fluxcd/tap/flux
# 將 GitHub 資訊設定到環境變數中
~$ export GITHUB_TOKEN=#{上面所產生的 GitHub Access Token}
~$ export GITHUB_USER=#{GitHub 帳號名稱}
~$ export GITHUB_REPO=flux2-demo
# 使用 Flux2 CLI 檢查是否可以開始 Provision K8s Cluster
~$ kubectl cluster-info --context kind-staging
~$ flux check --pre
► checking prerequisites
✔ kubectl 1.18.3 >=1.18.0
✔ Kubernetes 1.19.1 >=1.16.0
✔ prerequisites checks passed
~$ kubectl cluster-info --context kind-production
~$ flux check --pre
► checking prerequisites
✔ kubectl 1.18.3 >=1.18.0
✔ Kubernetes 1.19.1 >=1.16.0
✔ prerequisites checks passed

Bootstrap K8s Environment

接著讓我們利用 flux2-demo 裡面所定義的內容來 Provision Staging K8s Cluster

# 可以先驗證看看本來有哪一些 Pod 存在於 K8s Cluster 中
~$ kubectl cluster-info --context kind-staging
~$ kubectl get pod --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-f9fd979d6-tgg7b 1/1 Running 0 35m
kube-system coredns-f9fd979d6-w7xfc 1/1 Running 0 35m
kube-system etcd-production-control-plane 1/1 Running 0 35m
kube-system kindnet-zhqvc 1/1 Running 0 35m
kube-system kube-apiserver-production-control-plane 1/1 Running 0 35m
kube-system kube-controller-manager-production-control-plane 1/1 Running 1 35m
kube-system kube-proxy-dx7s2 1/1 Running 0 35m
kube-system kube-scheduler-production-control-plane 1/1 Running 1 35m
local-path-storage local-path-provisioner-78776bfc44-z4zhh 1/1 Running 1 35m
# 利用 flux 來設定要同步的 Git Repository,而 Bootstrap 除了透過 Flux2 CLI 執行之外,也可以透過 Terraform 來完成
~$ flux bootstrap github \
--context=kind-staging \
--owner=${GITHUB_USER} \
--repository=${GITHUB_REPO} \
--branch=main \
--personal \
--path=clusters/staging

可以發現定義在 Git Repository 的東西都被建立起來了,包含 podinfo, nginx-ingress-controller, redis, 當然還有在 namespace flux-system 中的各種 controller

~$ kubectl pod --all-namespacesNAMESPACE            NAME                                                       READY   STATUS    RESTARTS   AGE
flux-system helm-controller-86d6475c46-br4tr 1/1 Running 0 9m59s
flux-system kustomize-controller-689f679f79-4b689 1/1 Running 0 9m59s
flux-system notification-controller-b8fbd5997-955tn 1/1 Running 0 9m59s
flux-system source-controller-5bb54b4c66-gfd9n 1/1 Running 0 9m59s
kube-system coredns-f9fd979d6-k5mtr 1/1 Running 0 39m
kube-system coredns-f9fd979d6-z9g66 1/1 Running 0 39m
kube-system etcd-staging-control-plane 1/1 Running 0 39m
kube-system kindnet-fzjgq 1/1 Running 0 39m
kube-system kube-apiserver-staging-control-plane 1/1 Running 0 39m
kube-system kube-controller-manager-staging-control-plane 1/1 Running 1 39m
kube-system kube-proxy-7d9ft 1/1 Running 0 39m
kube-system kube-scheduler-staging-control-plane 1/1 Running 1 39m
local-path-storage local-path-provisioner-78776bfc44-vs6sx 1/1 Running 1 39m
nginx nginx-ingress-controller-674f7d767b-wxh92 1/1 Running 0 7m59s
nginx nginx-ingress-controller-default-backend-7c6cd8759-kh7vn 1/1 Running 0 7m59s
podinfo podinfo-747dff6746-9tkpq 1/1 Running 0 7m54s
redis redis-master-0

假如要 Provision Production K8s Cluster 也是只需要一行指令就可以完成

~$ flux bootstrap github \
--context=kind-production \
--owner=${GITHUB_USER} \
--repository=${GITHUB_REPO} \
--branch=main \
--personal \
--path=clusters/production

Dive Deep into Flux2

接著來詳細看一下剛剛的 flux2-demo Git Repository 裡面都定義了什麼東西,依序從 apps, clusters 和 infrastructure 資料夾慢慢看起

Multi-Cluster Folder Structure

flux2-demo
├── apps
│ ├── base
│ │ └── podinfo
│ ├── production
│ └── staging
├── clusters
│ ├── production
│ └── staging
└── infrastructure
├── kustomization.yaml
├── nginx
├── redis
└── sources

1. clusters

clusters 這個資料夾在上面 flux2 CLI Bootstrap 的 — path flag 有被使用到,裡面的 infrastructure.yaml 和 apps.yaml 分別定義著該環境 K8s Cluster 的共用基礎設施和一般應用程式

flux2-demo/clusters
├── production
│ ├── apps.yaml
│ └── infrastructure.yaml
└── staging
  • infrastructure.yaml

這個檔案很簡單定義了一個 Kustomization CRD,要 flux2 去資料夾 infrastructure 參照需要安裝哪些東西

apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
interval: 10m0s
sourceRef:
kind: GitRepository
name: flux-system
path: ./infrastructure
prune: true
validation: client
  • apps.yaml

一樣是 Kustomization CRD 不過參照的路徑有依據不同的環境切分開來,讓不同的環境可以使用到不同的組態,而且使用到 dependsOn 讓 flux2 幫忙安裝時的相依性

apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 10m0s
dependsOn:
- name: infrastructure
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/production
prune: true
validation: client
healthChecks:
- apiVersion: helm.toolkit.fluxcd.io/v1beta1
kind: HelmRelease
name: podinfo
namespace: podinfo

2. apps

app 資料夾內為了可以讓不同環境使用到不同的組態,且讓一樣的設定可以共用,所以多了一個 base/podinfo 的資料夾

flux2-demo/apps
├── base
│ └── podinfo
│ ├── kustomization.yaml
│ ├── namespace.yaml
│ └── release.yaml
├── production
│ ├── kustomization.yaml
│ └── podinfo-values.yaml
└── staging
  • base/podinfo/kustomization.yaml

這個資料夾的內容是要給其他環境應用程式共用的,裡面定義需要去參照兩個 YAML 檔案,namespace.yaml 就不多說了,就是一般用來建立 K8s Namespace 的 Manifest

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: podinfo
resources:
- namespace.yaml
- release.yaml
  • base/podinfo/release.yaml

而 release.yaml 就比較特殊了,他是 HelmRelease CRD,裡頭定義 podinfo 這個應用程式應該要怎麼部署,跟以前自定義給 Helm Chart 讀取的 value.yaml 檔案有點像,不過在上半部分清楚的定義了要使用哪一個 helm chart,以及部署的機制,例如:多久要同步一次,重試的次數…等

apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo
namespace: podinfo
spec:
releaseName: podinfo
chart:
spec:
chart: podinfo
sourceRef:
kind: HelmRepository
name: podinfo
namespace: flux-system
interval: 5m
install:
remediation:
retries: 3
# Default values
# https://github.com/stefanprodan/podinfo/blob/master/charts/podinfo/values.yaml
values:
image:
repository: stefanprodan/podinfo
tag: 5.1.2
pullPolicy: IfNotPresent
cache: redis-master.redis:6379
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
path: "/"
  • base/production/kustomization.yaml

一樣是 Kustomization CRD,定義好 Production 環境的應用程式部署的時候,主要是去參考 base/podinfo 這個資料夾內的檔案,不過在某些變數就需要透過 podinfo-values.yaml 定義的值來決定了

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base/podinfo
patchesStrategicMerge:
- podinfo-values.yaml

3. infrastructure

這個範例的 infrastructure 比較簡單,不會根據不同的環境有不一樣的設定值,而且剛剛跟上面提到的 CRD 大致上都差不多,所以這邊只就 sources 這個資料夾做詳細介紹

flux2-demo/infrastructure
├── kustomization.yaml
├── nginx
├── redis
└── sources
├── bitnami.yaml
├── kustomization.yaml
└── podinfo.yaml
  • sources/bitnami.yaml

這個檔案使用 HelmRepository CRD 定義 bitnami 的 Helm Repository,然後每 30 mins 會去同步一次,看看有沒有新的 Helm Chart,podinfo.yaml 也是定義一樣的東西,kustomization.yaml 則是把所有會使用到的 HelmRepository 做個總整理的概念

apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmRepository
metadata:
name: bitnami
spec:
interval: 30m
url: https://charts.bitnami.com/bitnami

Add Prometheus Stack into Infrastructure

看完上面的詳細解說之後,讓我們來練習看看把 K8s Prometheus Stack 加到 Infrastructure 中,不過礙於篇幅關係,詳細需要新增與修改的內容可以參考這個 Git PR,然後把變更 Push 到 Remote Git Repository 中

~$ git add .
~$ git commit -m "add K8s prometheus stack into infrastructure"
~$ git pull
~$ git push origin main

flux2 這時候就會自己把 prometheus stack 給安裝好,而且是多個 Cluster 都同時安裝完畢

> kubectl get pod -n monitoringNAME                                                        READY   STATUS      RESTARTS   AGEalertmanager-kube-prometheus-stack-alertmanager-0           2/2     Running     0          6h48mkube-prometheus-stack-admission-create-hjnzh                0/1     Completed   0          6h39mkube-prometheus-stack-grafana-6874b46d6d-7fmlr              2/2     Running     0          6h49mkube-prometheus-stack-kube-state-metrics-5cf575d8f8-9lh8r   1/1     Running     0          6h49mkube-prometheus-stack-operator-5674bb4568-22z7f             1/1     Running     0          6h49mkube-prometheus-stack-prometheus-node-exporter-pwrwm        1/1     Running     0          6h49mprometheus-kube-prometheus-stack-prometheus-0               2/2     Running     6          6h48m

Misc

Credential

GitOps 一定會遇到一個血淋淋的問題就是,假如有使用 K8s Secret 儲存 Credential 的話,那就要把他也存在 Git Repository 中,不然 GitOps Tool 就無法幫忙建立,解決方法分為兩類:

  • 使用第三方的 Secret Mangement Tool, 例如 HashiCorp Vault, AWS Secret Manager…等
  • 上面提到的工具雖然可以提升安全性,但都需要花時間去整合,假如想要繼續使用K8s Secret 的話,Flux2 這邊提供 Mozilla SOPS 的方式,讓使用者把 Credentials 儲存到 Git Repository 之前先進行加密,詳細使用方式可以參考官方文件

Multi-Tenancy

礙於篇幅關係,這篇文章準備的範例只有展示了 Multi-Cluster 的使用情境,假如對於 Multi-Tenancy 有需求的人,可以參考官方提供的範例

Conclusion

所以到底要用 Flux2 或是 Argo CD 呢?其實只要可以解決遇到的痛點和問題就行了,自己覺得兩個都不錯,不過 Flux2 沒有推出很久,使用上遇到問題的機率比較高(不過看他的開發速度滿快的,假如是嚴重的問題應該不會拖延太久才修掉),而 Argo CD 已經推出好一段時間讓大家幫忙使用與測試,所以相對穩定很多,自己則是想要再多測試一下各種使用情境再做最終決定,目前有點傾向 Flux2

而為什麼標題要特地提到 Configuration Mamagement 呢?因為老實說自己覺得 GitOps 算是 Configuration Mamagement 的進化版,他們兩者都是用來確保維護的系統跟定義的 IaC 程式碼可以達成最終一致性,不過 GitOps 跟 Git 的結合更為緊密且自動化,在整個流程控管上可以做得更漂亮,就如同我上一篇文章 “Kubernetes Distribution 元年: EKS Distro 想要說的事” 討論的,其實 K8s 的發展史跟當初 Linux Server 越來越像,當初維運人員需要管理很多台機器時,Configuration Management 被發明了出來,例如 Ansible, Chef, Puppet 和 SaltStack,讓單一個維運人員就可以透過自動化管理超多台機器,而現在大家遇到管理多座 K8s Cluster 的問題時,Flux2 和 Argo CD 也做出相對應的功能來滿足大家的需求,目前應該還算是相當前期的階段,期待有更多的競爭對手一起加入這個市場

Reference

--

--

smalltown
Starbugs Weekly 星巴哥技術專欄

原來只是一介草 QA,但開始研究自動化維運雲端服務後,便一頭栽進 DevOps 的世界裏,熱愛鑽研各種可以提升雲端服務品質及增進團隊開發效率的開源技術