基于 Flux 的 GitOps 实战(上)

Overview

前文介绍了 GitOps 的概念,Kubernetes 上 GitOps 最佳实践以及对比了 CNCF 基金会下 云原生的 GitOps 工具(ArgoCD 和 Flux)。本篇将带你按照 Flux 的最佳实践在跨VPC跨账户的 Kubernetes 上实践 GitOps 的持续集成,轻松管理数十数百乃至更多的集群及部署在上面的应用。

0. 必备条件

  • 假设业务对稳定性的需求,使用3个 Kubernetes 集群分别对应 DEV, STAGINGPRODUCT 环境。这些集群环境根据企业的需求 可能会分布在不同的云账户和VPC网络中。读者可根据实际企业情况创建一个或多个集群。本文以 Amazon EKS 为例,EKS集群的创建请参阅其文档
  • Git 仓库用于保存集群的声明式配置。Flux 支持 Git 在线服务(包括 Github, Gitlab, Bitbucket)和其他任意 Git 服务。本文将使用 Github 托管 Git 仓库为例。
  • 安装 Flux CLI

1. Kubernetes 集群安装配置 Flux

Github repo 为例,执行以下命令,

1export GITHUB_TOKEN=<your-token>
2
3flux bootstrap github \
4  --components-extra=image-reflector-controller,image-automation-controller \
5  --owner=zxkane \
6  --repository=eks-gitops \
7  --path=clusters/cluster-dev \
8  --personal
重要

请确保 Flux CLI 执行环境可以通过 kubectl 连接到 Kubernetes 集群,且用户具备 admin 权限。

重要

创建的 Github Personal Accesss Token 需要至少同时选中全部 repouser 的权限。

注意

如需在 DEV 环境 启用镜像自动更新功能,bootstrap Flux 时需要加上 --components-extra=image-reflector-controller,image-automation-controller 参数。

通过类似的步骤在 STAGINGPRODUCT 集群安装配置 Flux 。

 1export KUBECONFIG=$HOME/.kube/config-cluster-staging
 2flux bootstrap github \
 3  --owner=zxkane \
 4  --repository=eks-gitops \
 5  --path=clusters/cluster-staging \
 6  --personal
 7  
 8export KUBECONFIG=$HOME/.kube/config-cluster-product
 9flux bootstrap github \
10  --owner=zxkane \
11  --repository=eks-gitops \
12  --path=clusters/cluster-product \
13  --personal  

以上步骤是手动安装及配置 Flux ,Flux 也支持同现有的 IaC 代码集成,如 eksctl, Terraform

最佳实践

上面示例对多环境集群的支持并没有采用多仓库/多分支的策略,而是用的使用不同路径来管理不同的集群。 这也是 Flux 推荐的策略,可以减少代码维护和合并的难度。

 1./clusters/
 2├── cluster-dev                     [集群名称]
 3│   ├── flux-system                 [命名空间]
 4│     ├── gotk-components.yaml      [默认 Flux 配置,请勿手动修改]
 5│     ├── gotk-sync.yaml            [默认 Flux 配置,请勿手动修改]
 6│     └── kustomize.yaml            [Kustomize 配置入口文件,将通过此入口聚合了集群的全部配置]    
 7├── cluster-product
 8│   ├── flux-system
 9│     ├── gotk-components.yaml
10│     ├── gotk-sync.yaml
11│     └── kustomize.yaml
12├── cluster-staging
13│   ├── flux-system
14│     ├── gotk-components.yaml
15│     ├── gotk-sync.yaml
16│     └── kustomize.yaml

在完成初始化不同的环境集群后,将在我们的Git仓库中查看到如上目录结构。 我们可以看到 Flux 自身的配置也是通过 GitOps 的方式来管理的。

2. 管理集群共享的组件

在企业中通常会由 Infrastructure 团队统一管理集群的共享组件,例如,Namespace, CSI Driver, Ingress Class, Persist Volume, Service Account, Secret, DaemonSet, NetworkPolicy,CustomResource 等等 Kubernetes 对象。 接下来将演示如何在多集群中创建集群内共享组件,例如,AWS Load Balancer ControllerExternal DNS, 并且逐步将这些组件部署在不同的环境中。

Flux 自身大量依赖了 Kustomize,通过 Flux 的 Kustomize Controller 来渲染最终的 Kubernetes 声明式配置,并集成了 Hook,ServiceAccount,超时等额外配置。

通过如下Flux Kustomize对象声明为DEV环境声明了共享 Infrastructure 配置所在的路径(该配置文件放置在cluster/cluster-dev目录下),

 1apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
 2kind: Kustomization
 3metadata:
 4  name: infrastructure
 5  namespace: flux-system
 6spec:
 7  interval: 10m0s
 8  path: ./infrastructure/overlays/development
 9  prune: true
10  sourceRef:
11    kind: GitRepository
12    name: flux-system

以 External DNS(一个 CNCF 基金会项目,为 K8S Service LoadBalancer / Ingress 对象提供 DNS 域名解析注册) 为完整示例。

使用 Flux 的 Helm Repositories 自定义对象,注册 bitnami 的 Helm Charts 仓库。

1apiVersion: source.toolkit.fluxcd.io/v1beta1
2kind: HelmRepository
3metadata:
4  name: bitnami
5spec:
6  interval: 30m
7  url: https://charts.bitnami.com/bitnami

按照 External DNS for Amazon Route 53 的文档为 external-dns POD 创建执行 IAM 角色, 可以通过 Route 53 API 来创建修改相应的域名解析。针对 External DNS 部署的在 K8S 集群配置如下,

  1. 为 External DNS 创建独立的 service account,同对应的 AWS IAM Role 绑定,限制该 Pod 仅拥有必需的最小权限。 关于 EKS 上如何绑定最小 AWS 权限到 pod 上请参考IAM roles for service accounts
 1apiVersion: v1
 2kind: ServiceAccount
 3metadata:
 4  name: external-dns
 5  annotations:
 6    # create IAM role via following docs,
 7    # https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html
 8    # https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#iam-permissions
 9    # the role specified by kustomize
10    # eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/external-dns-role
11    eks.amazonaws.com/sts-regional-endpoints: true
  1. 定义 HelmRelease Flux 对象 从 Bitnami 的 Helm Charts 仓库安装 external-dns
 1apiVersion: helm.toolkit.fluxcd.io/v2beta1
 2kind: HelmRelease
 3metadata:
 4  name: external-dns
 5spec:
 6  releaseName: external-dns
 7  targetNamespace: kube-system
 8  chart:
 9    spec:
10      chart: external-dns
11      version: '>=6.2.1 <7'
12      sourceRef:
13        kind: HelmRepository
14        name: bitnami
15        namespace: kube-system
16  interval: 1h0m0s
17  install:
18    remediation:
19      retries: 3
20  values:
21    provider: aws
22    aws:
23      zoneType: public
24    serviceAccount:
25      create: false
26      name: external-dns
27    podSecurityContext:
28      fsGroup: 65534
29      runAsUser: 0
  1. 使用 Kustomization 将相关的配置整合。
1apiVersion: kustomize.config.k8s.io/v1beta1
2kind: Kustomization
3namespace: kube-system
4resources:
5  - serviceaccount.yaml
6  - release.yaml
  1. 用 Kustomization 整合多个组件的配置。
1apiVersion: kustomize.config.k8s.io/v1beta1
2kind: Kustomization
3resources:
4  - sources
5  - aws-load-balancer-controller
6  - dns

以上所有配置都保存在Git仓库infrastructure/base下(详见下图)作为多套环境通用的配置,按照 Kustomize 的 Overlays 布局。

 1./infrastructure/
 2|-- base
 3|   |-- aws-load-balancer-controller
 4|   |   |-- kustomization.yaml
 5|   |   |-- release.yaml
 6|   |   `-- serviceaccount.yaml
 7|   |-- dns
 8|   |   |-- kustomization.yaml
 9|   |   |-- release.yaml
10|   |   `-- serviceaccount.yaml
11|   |-- kustomization.yaml
12|   `-- sources
13|       |-- bitnami.yaml
14|       |-- eks-charts.yaml
15|       `-- kustomization.yaml
16`-- overlays
17    |-- development
18    |   |-- aws-load-balancer-controller-patch.yaml
19    |   |-- aws-load-balancer-serviceaccount-patch.yaml
20    |   |-- dns-patch.yaml
21    |   |-- dns-sa-patch.yaml
22    |   `-- kustomization.yaml

DEV环境对应的 overlay 下面创建如下的补丁来覆盖跟DEV环境相关的信息声明,如集群名称、域名、 为External DNS pod所创建AWS IAM Role的ARN。

 1apiVersion: helm.toolkit.fluxcd.io/v2beta1
 2kind: HelmRelease
 3metadata:
 4  name: external-dns
 5spec:
 6  values:
 7    txtOwnerId: gitops-cluster
 8    domainFilters[0]: test.kane.mx
 9    policy: sync
10---
11apiVersion: v1
12kind: ServiceAccount
13metadata:
14  name: external-dns
15  annotations:
16    eks.amazonaws.com/role-arn: >-
17      arn:aws:iam::845861764576:role/gitops-cluster-external-dns-role      
最佳实践

充分利用 Kustomize 的 Overlays 机制来抽象通用的配置和覆盖每个环境所对应的特殊部分。

最佳实践

将共享组件部署在非 Flux 命名空间(默认flux-system),避免清理 Flux 时影响运行中的部署。

同样在DEV环境验证External DNS组件部署成功后,将相似的配置应用到STAGINGPRODUCT环境。 通过Kustomize的Overlays分别设置STAGINGPRODUCT环境相关的配置。再将变更推送到Git仓库, Flux将会为我们部署这些声明在Git仓库中的组件!可查阅DEV, STAGING, PRODUCT这三个提交查看完整实现。

3. 密钥的管理

最佳实践

2022/06更新:使用成熟的K8S集群外置的密钥管理服务可以很好的将成熟密钥管理最佳实践和K8S原生生态集成在一起。 详见博文使用外部Secrets Manager管理Kubernetes密钥

GitOps 的理念是将一切配置以声明式文本方式保存在仓库中。而对保存 Kubernetes Secrets 是个挑战, 因为 Git 仓库对所有读权限的用户公开,甚至项目的仓库是开源。Flux 通过支持 Bitnami Sealed SecretsMozilla SOPS 安全的在 Git 仓库中管理密钥。接下来将示例如何使用 Sealed Secrets 为 MariaDB 创建密码。

  1. 首先使用 HelmRelease 部署 Bitnami Sealed Secrets。类似上面部署 External DNS,将 sealed secrets 添加到 infrastructure/base 里作为共享组件。
 1apiVersion: helm.toolkit.fluxcd.io/v2beta1
 2kind: HelmRelease
 3metadata:
 4  name: sealed-secrets
 5  namespace: kube-system
 6spec:
 7  chart:
 8    spec:
 9      chart: sealed-secrets
10      sourceRef:
11        kind: HelmRepository
12        name: sealed-secrets
13      version: ">=1.15.0-0"
14  interval: 1h0m0s
15  releaseName: sealed-secrets-controller
16  targetNamespace: kube-system
17  install:
18    crds: Create
19  upgrade:
20    crds: CreateReplace
  1. 按照 Flux 的 Sealed Secrets 文档,安装 kubeseal
  2. 使用 kubeseal 从集群中下载公钥。
1kubeseal --fetch-cert \
2--controller-name=sealed-secrets-controller \
3--controller-namespace=kube-system \
4> pub-sealed-secrets-dev.pem
  1. Bitnami MariaDB 生成密钥。
1kubectl -n kube-system create secret generic prestashop-mariadb \
2--from-literal=mariadb-root-password=<put the ariadb root password here> \
3--from-literal=mariadb-replication-password=<put the replication password here> \
4--from-literal=mariadb-password=<put the mariadb password here> \
5--dry-run=client \
6-o yaml > /tmp/mariadb-secrets.yaml
  1. 从 K8S 内置的 Opaque Secrets 格式文件生成 sealed secret。
1kubeseal --format=yaml --cert=pub-sealed-secrets-dev.pem \
2< /tmp/mariadb-secrets.yaml > infrastructure/overlays/development/prestashop-mariadb-secrets.yaml
  1. 部署 Bitnami Helm Chart MariaDB,使用提前创建的密钥作为 DB 的密钥。
 1apiVersion: helm.toolkit.fluxcd.io/v2beta1
 2kind: HelmRelease
 3metadata:
 4  name: prestashop-mariadb
 5spec:
 6  releaseName: mariadb
 7  chart:
 8    spec:
 9      chart: mariadb
10      sourceRef:
11        kind: HelmRepository
12        name: bitnami
13        namespace: kube-system
14      version: 10.4.0
15  interval: 1h0m0s
16  install:
17    remediation:
18      retries: 3
19  valuesFrom:
20    - kind: ConfigMap
21      name: prestashop-values
22---
23auth:
24  existingSecret: prestashop-mariadb
25primary:
26  persistence:
27    enabled: false
28    storageClass: standard
最佳实践

切记使用 Sealed Secrets, SOPS 等工具仅将加密后的密钥提交到 Git 仓库,避免密钥的泄露!

查阅DEV, STAGING, PRODUCT这三个提交查看完整 sealed secrets 使用。

4. 通知集成

在运维集群的时候,不同的团队有订阅不同的 GitOps 流水线通知的需求。 例如,oncall 团队将收到有关集群中协调失败的警报, 而开发团队可能希望在部署新版本的应用程序以及部署是否健康时收到警报。

Flux 内置了同 Slack, MS Teams, Discord 等知名 IM 工具的集成,也支持将消息发送到 webhook 接口, 由用户自行实现消息通知。

下面以 Slack 为例,示例如何集成 GitOps 流水线消息。

  1. 定义一个名为 slack 的 Flux 自定义资源 Provider
1apiVersion: notification.toolkit.fluxcd.io/v1beta1
2kind: Provider
3metadata:
4  name: slack
5  namespace: kube-system
6spec:
7  type: slack
8  secretRef:
9    name: slack-url

因为 Slack WebHook 并没有额外的鉴权保护,这里我们使用上一节的密钥管理机制加密保存在 Git 仓库的 slack webhook url, 同时 Provider 引用 Secrets 对象中保存的 url。 2. 创建 Flux Alert 对象订阅命名空间的各类 Flux 对象事件,并且同第一步定义的 Provider 关联。 3. 当创建多个 Alert 和不同的 Provider 关联,可以将消息发送到不同的 Slack channel 甚至是不同的 IM。

 1apiVersion: notification.toolkit.fluxcd.io/v1beta1
 2kind: Alert
 3metadata:
 4  name: flux-alert
 5  namespace: kube-system
 6spec:
 7  providerRef:
 8    name: slack
 9  eventSeverity: info
10  eventSources:
11    - kind: GitRepository
12      name: '*'
13    - kind: Kustomization
14      name: '*'
15    - kind: HelmRelease
16      name: '*'
17---
18apiVersion: notification.toolkit.fluxcd.io/v1beta1
19kind: Alert
20metadata:
21  name: kube-system-alert
22  namespace: kube-system
23spec:
24  providerRef:
25    name: slack
26  eventSeverity: info
27  eventSources:
28    - kind: Kustomization
29      name: '*'
30      namespace: 'kube-system'
31    - kind: HelmRelease
32      name: '*'
33      namespace: 'kube-system'

查阅DEV, STAGING, PRODUCT这三个提交查看完整 commits 如何在不同环境集群中部署了 Slack 通知集成。

Slack channel 订阅 GitOps 流水线消息通知
图1:Slack channel 订阅 GitOps 流水线消息通知

最佳实践

针对订阅不同命名空间(非Alert对象定义的命令空间)的事件通知,需要显示指定命名空间属性。

5. GitOps 代码的 CI

GitOps 模式带来的又一个好处是可以使用企业成熟且惯用的代码管理工作流来自动化验证变更及代码审核审批。 针对 GitOps 代码可以引入如下 CI 步骤,

  • 由于 Flux 大量使用 Kustomize 来生成最终的声明式配置,可以实现在每次提交 Pull Request/Merge Request 后的验证阶段引入 kustomize CLI 验证 GitOps 配置是否可以正确的被生成。同时,使用 Flux OpenAPI 结合 kubeconform 验证 Kubernetes 内置资源和 Flux CRD 类型是否配置正确。
  • 借助 KIND(Kubernetes in Docker) 实现完整的端到端测试。KIND 实现了 Docker 容器打包的 Kubernetes 环境, 可以每次 PR 验证阶段启动新的 KIND 环境且安装 Flux 后,执行 GitOps 代码的 reconciliation, 验证 GitOps 代码配置的资源是否可以被创建且状态为READY
  • 借助 Git 服务的 CI 能力,如 Github Actions, Gitlab CI/CD 等, 实现 GitOps 代码的上述两种自动化检查,以及同代码审核审批集成。

查阅此 Github Actions workflow 配置实现在 KIND 环境 End-To-End 验证 GitOps 配置,和 声明式配置 Manifests 验证

最佳实践

利用 Github Actions 或 Gitlab CI/CD 非常容易的将 GitOps 代码集成到 CI 环境, 通过 KIND/Kubeconform 验证代码的正确性。

6. 小结

本文介绍使用 GitOps 工具 FluxCD v2 实现了管理多账户多 VPC 环境下的 Kubernetes 集群的共享组件,实践了 Secrets 使用的最佳实践, CD 部署事件同 IM(Slack) 的集成,最终示例了通过 GitOps 代码的 CI 流程来提高 GitOps 代码的质量,减少部署中断事故。 可在此仓库获取完整的 GitOps 代码。

下篇将介绍基于 Flux 实现 GitOps 工作模型下的共享服务平台

Posts in this Series