基于 Flux 的 GitOps 实战(下)
Overview
在上篇介绍基于 CNCF 下的 GitOps 工具 FluxCD v2 实现了管理多账户的 Kubernetes 集群的共享组件,Secrets 使用的最佳实践, GitOps 流水线事件同 IM(Slack) 的集成,以及对 GitOps 代码的 CI 流程。
本文将围绕如何使用 Flux 的多租户管理最佳实践,打造基于 GitOps 工作流程的共享服务平台, 实现租户(业务/应用团队)可自助的持续部署。
一、基于 GitOps 的共享服务平台设定
Kubernetes 提供了命名空间作为一种机制将同一集群中的资源划分为相互隔离的组。 同一个集群中多租户多团队的应用管理将沿用 Kubernetes 内置的各种机制来为不同的租户、团队或应用进行隔离,包括且不限于,
- 命名空间(Namespaces)
- 资源配额(Resource Quotas),限制应用的资源总量
- RBAC 鉴权,限制应用的权限,如可创建 Ingress,不可创建密钥可读取指定名称的密钥,不可创建持久卷等
- 网络策略(Network Policies)
基于 Kubernetes 以上能力,为基于 GitOps 的共享服务平台设定如下,
- 平台团队通过一个 Git 仓库来管理多个跨网络跨账户跨云平台的 Kubernetes 集群,平台团队通过 GitOps 管理如下资源,
- GitOps Toolkit 组件,如 Flux
- 集群共享组件,如 CNI, CSI Driver, Ingress Class,Service Accounts, CRD, DNS 等
- 可观测性的共享组件,如 Log, Metrics, Trace
- 每个租户/团队/应用的基础资源,如 Namespaces, Resource Quotas, Open Policy,Service Accounts,密钥等
- 为集群中的每个租户/团队/应用使用独立的 Git 仓库来隔离其持续部署,假设有应用名为
app-a
,- 应用
app-a
相关的资源都将部署在命名空间app-a
- 限制应用使用的总资源,如不超过 2 vCPU, 4 GiB 内存
- 应用团队使用独立的 Git 仓库来管理应用编排,应用团队将负责应用发布到不同 stage 环境的节奏
- 应用团队可以使用 Kustomization、Helm 部署应用
- 应用团队无法创建集群相关的组件,如持久卷、CRD 等资源
- 应用团队无法创建密钥、Service Account等资源,但仅可使用 infra 团队提前为应用创建的这类资源
- 应用
二、Flux 多租户的安全设置
对于一个使用命名空间在隔离多租户的集群,Flux 提供了选项来禁止跨命令空间的引用, 例如,Flux 的 Kustomization 或 Helm Releases 禁止引用其他命名空间定义的 Source。 同时,启用强制模拟功能,将 Kustomization 或 Helm Releases 资源的部署默认限制到最小来显示的提升部署的安全性。
遵循以上 Flux 的多租户安全最佳实践,进行如下 Flux Toolkits 配置(./cluster/cluster-dev/kustomization.yaml
)
来禁用跨命名空间引用和强制模拟限制 Kustomization 和 Helm 部署的默认权限,
1apiVersion: kustomize.config.k8s.io/v1beta1
2kind: Kustomization
3resources:
4 - gotk-components.yaml
5 - gotk-sync.yaml
6patches:
7 - patch: |
8 - op: add
9 path: /spec/template/spec/containers/0/args/0
10 value: --no-cross-namespace-refs=true
11 target:
12 kind: Deployment
13 name: >-
14 (kustomize-controller|helm-controller|notification-controller|image-reflector-controller|image-automation-controller)
15 - patch: |
16 - op: add
17 path: /spec/template/spec/containers/0/args/0
18 value: --default-service-account=default
19 target:
20 kind: Deployment
21 name: (kustomize-controller|helm-controller)
22 - patch: |
23 - op: add
24 path: /spec/serviceAccountName
25 value: kustomize-controller
26 target:
27 kind: Kustomization
28 name: flux-system
29 - patch: |
30 - op: add
31 path: /spec/serviceAccountName
32 value: helm-controller
33 target:
34 kind: HelmRelease
35 name: flux-system
同时为 infra 团队管理的共享 Kustomization/Helm 组件部署显示的指定部署权限,
例如,DEV
环境 infrastructure 配置的入口./clusters/cluster-dev/infrastructure.yaml
1apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
2kind: Kustomization
3metadata:
4 name: infrastructure
5 namespace: flux-system
6spec:
7 interval: 10m0s
8 serviceAccountName: kustomize-controller
9 path: ./infrastructure/overlays/product
10 prune: true
11 sourceRef:
12 kind: GitRepository
13 name: flux-system
或通过 Kustomize 的补丁机制为所有的 Kustomization/Helm Flux 自定义资源指定部署权限,
例如DEV
环境的overlay的入口./infrastructure/overlays/development/kustomization.yaml
配置,
1apiVersion: kustomize.config.k8s.io/v1beta1
2kind: Kustomization
3resources:
4 - ../../base
5 - ./secrets.yaml
6patches:
7 - path: ./aws-load-balancer-controller-patch.yaml
8 - path: ./aws-load-balancer-serviceaccount-patch.yaml
9 - path: ./dns-patch.yaml
10 - path: ./dns-sa-patch.yaml
11 - path: ./slack-patch.yaml
12 - patch: |
13 - op: add
14 path: /spec/serviceAccountName
15 value: kustomize-controller
16 target:
17 kind: Kustomization
18 namespace: (flux-system|kube-system|mariadb)
19 - patch: |
20 - op: add
21 path: /spec/serviceAccountName
22 value: helm-controller
23 target:
24 kind: HelmRelease
25 namespace: (flux-system|kube-system|mariadb)
通过在平台团队管理的 Kustomization 配置中,强制为应用团队 Git 仓库的 Kustomization
, HelmRelease
等部署对象指定部署时使用的 Service Account
。
三、租户的集群资源管理
基于前面的管理需求假设,在 infrastructure Git 仓库中,专门为多租户/多团队/多应用创建如下目录结构, 共享 apps 通常的租户配置,例如,命名空间,RBAC(通过 Service Account)加上 Policy 实现等。
1apps
2|-- base
3| |-- app-a
4| | |-- bitnami.yaml
5| | |-- kustomization.yaml
6| | |-- namespace.yaml
7| | |-- policies.yaml
8| | `-- rbac.yaml
9| `-- kustomization.yaml
10`-- overlays
11 `-- development
12 |-- app-a
13 | |-- kustomization.yaml
14 | `-- prestashop-sealed-secrets.yaml
15 `-- kustomization.yaml
同时创建一个 apps 的 Kustomization 入口配置同集群集成,例如 ./clusters/cluster-dev/apps.yaml
文件内容如下,
1apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
2kind: Kustomization
3metadata:
4 name: apps
5 namespace: flux-system
6spec:
7 dependsOn:
8 - name: infrastructure
9 interval: 3m0s
10 serviceAccountName: kustomize-controller
11 path: ./apps/overlays/development
12 prune: true
13 sourceRef:
14 kind: GitRepository
15 name: flux-system
Kubernetes 原生的 RBAC 权限控制无法细粒度的控制资源权限,如资源创建必须指定某些 Label 等。 但结合 Policy as Code,如 Gatekeeper, Kyverno 可以满足细粒度的管理需求。
为应用 app-a
创建了如下 Policy,仅允许应用通过自助的 Git 仓库在部署时仅可创建 Helm Chart 部署必须的 Secrets。
1apiVersion: kyverno.io/v1
2kind: Policy
3metadata:
4 name: restrict-secrets-by-type
5 namespace: app-a
6 annotations:
7 policies.kyverno.io/title: Restrict Secrets by Name
8 policies.kyverno.io/category: security
9 policies.kyverno.io/subject: Secret
10 policies.kyverno.io/description: >-
11 Disallow creating/deleting secrets in namespace 'app-a' beside the helm
12 storage.
13spec:
14 background: false
15 validationFailureAction: enforce
16 rules:
17 - name: safe-secrets-for-helm-storage
18 match:
19 resources:
20 kinds:
21 - Secret
22 preconditions:
23 all:
24 - key: '{{request.operation}}'
25 operator: In
26 value:
27 - CREATE
28 - UPDATE
29 - DELETE
30 - key: '{{serviceAccountName}}'
31 operator: Equals
32 value: app-a-reconciler
33 validate:
34 message: Only Secrets are created by Helm v3+
35 pattern:
36 type: helm.sh/release.v1
四、租户隔离且自服务的应用持续部署
上一步为租户/应用 app-a
配置了独立的命令空间,部署权限,策略等。同时为应用 app-a
创建了独立的 GitOps 仓库,
应用团队可以通过独立的 Git 仓库自主的发布其应用程序到不同的 STAGING 集群。
如示例中的仓库,应用团队使用 Kustomize 管理不同 STAGING 环境的部署,且通过 Helm 方式部署了电商应用 Prestashop。
应用团队的部署可以使用由 infrastructure 团队统一管理的 External DNS, Ingress Class, 应用所在命名空间的 Secrets。
最终平台团队将应用 app-a
独立的仓库作为一个新的 GitOps 来源,通过如下配置将应用仓库的部署同集群关联上,
1apiVersion: source.toolkit.fluxcd.io/v1beta1
2kind: GitRepository
3metadata:
4 name: app-a-tenant
5spec:
6 interval: 1m
7 url: https://github.com/zxkane/eks-gitops-app-a.git
8 ref:
9 branch: main
10---
11apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
12kind: Kustomization
13metadata:
14 name: app-a-tenant
15spec:
16 serviceAccountName: app-a-reconciler
17 interval: 5m0s
18 retryInterval: 5m0s
19 prune: true
20 sourceRef:
21 kind: GitRepository
22 name: app-a-tenant
23 namespace: app-a
24 patches:
25 - patch: |-
26 - op: replace
27 path: /spec/serviceAccountName
28 value: app-a-reconciler
29 - op: replace
30 path: /metadata/namespace
31 value: app-a
32 target:
33 group: helm.toolkit.fluxcd.io
34 version: v2beta1
35 kind: HelmRelease
36 - patch: |-
37 - op: replace
38 path: /spec/serviceAccountName
39 value: app-a-reconciler
40 - op: replace
41 path: /metadata/namespace
42 value: app-a
43 target:
44 group: kustomize.toolkit.fluxcd.io
45 version: v1beta2
46 kind: Kustomization
47 - patch: |-
48 - op: replace
49 path: /namespace
50 value: app-a
51 target:
52 group: kustomize.config.k8s.io
53 version: v1beta1
54 kind: Kustomization
应用 app-a
团队将自助的通过独立的应用 GitOps 仓库持续发布团队的应用。
如下示例 app-a
在其自助的 Git 仓库通过 HelmRelease
部署了 Web 应用。
1apiVersion: helm.toolkit.fluxcd.io/v2beta1
2kind: HelmRelease
3metadata:
4 name: prestashop
5spec:
6 releaseName: prestashop
7 chart:
8 spec:
9 chart: prestashop
10 sourceRef:
11 kind: HelmRepository
12 name: bitnami
13 namespace: app-a
14 version: 14.0.10
15 values:
16 existingSecret: prestashop
17 service:
18 type: ClusterIP
19 ingress:
20 enabled: true
21 path: '/*'
22 annotations:
23 alb.ingress.kubernetes.io/scheme: internet-facing
24 alb.ingress.kubernetes.io/inbound-cidrs: '0.0.0.0/0'
25 alb.ingress.kubernetes.io/auth-type: none
26 alb.ingress.kubernetes.io/target-type: ip
27 kubernetes.io/ingress.class: alb
28 alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-2-Ext-2018-06
29 alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
30 alb.ingress.kubernetes.io/backend-protocol: HTTP
31 alb.ingress.kubernetes.io/healthcheck-path: '/'
32 persistence:
33 enabled: false
34 storageClass: gp2
35 # for mariadb
36 mariadb:
37 enabled: false
38 externalDatabase:
39 host: mariadb.kube-system.svc.cluster.local
40 user: prestashop
41 database: prestashop
42 existingSecret: prestashop-db-secrets
43 allowEmptyPassword: false
44 interval: 1h0m0s
45 install:
46 remediation:
47 retries: 3
五、自动发布镜像更新
在本节实践中我们将使用 Sock Shop(一个使用 Spring Boot, Go kit, Node.js 容器化的微服务示例应用)。
同在第三,第四章节配置应用 app-a
一样,为 sock-shop
应用在 infrastructure GitOps 仓库中创建了单独的命名空间、RBAC、独立的 Git 仓库来管理应用的发布,
具体实现可参考 commit1, commit2。
1. 部署微服务应用程序 Sock Shop
在我们分叉的 Sock Shop 通过 Kustomization 实现了多集群部署的支持,
同时将 front-end 服务通过 LoadBalancer
类型对外暴露出来,利用 Amazon EKS 同 Amazon Elastic Load Balancing
的集成来负载均衡 Sock Shop 应用的入口 front-end 服务。
1apiVersion: kustomize.config.k8s.io/v1beta1
2kind: Kustomization
3resources:
4 - ./complete-demo.yaml
5patchesStrategicMerge:
6 - delete-ns.yaml
7patches:
8 - patch: |-
9 - op: replace
10 path: /spec/type
11 value: LoadBalancer
12 - op: replace
13 path: /metadata/annotations/service.beta.kubernetes.io~1aws-load-balancer-type
14 value: external
15 - op: replace
16 path: /metadata/annotations/service.beta.kubernetes.io~1aws-load-balancer-nlb-target-type
17 value: ip
18 - op: replace
19 path: /metadata/annotations/service.beta.kubernetes.io~1aws-load-balancer-scheme
20 value: internet-facing
21 target:
22 version: v1
23 kind: Service
24 name: front-end
通过定制化 front-end 微服务为我们的 Sock Shop 应用持续改进,最新的 front-end
通过自动化测试后打包的镜像版本通过 Github packages 容器镜像仓库对外发布。
我们在 DEV
环境将使用 Kustomization overlays 将 front-end 微服务替换为定制化更新的版本。
1apiVersion: kustomize.config.k8s.io/v1beta1
2kind: Kustomization
3resources:
4 - ../../base
5patches:
6 - patch: |-
7 - op: replace
8 path: /metadata/annotations/external-dns.alpha.kubernetes.io~1hostname
9 value: socks-dev.test.kane.mx
10 target:
11 version: v1
12 kind: Service
13 name: front-end
14images:
15- name: weaveworksdemos/front-end
16 newName: ghcr.io/zxkane/weaveworksdemos/front-end
17 newTag: 0.3.13-rc0
在 DEV
等可持续集成的敏捷环境,在构建新服务镜像且发布后,通过人工或脚本更新 GitOps 代码仓库显得过于繁琐。
Flux 自身提供了完善且强大的 Git 仓库镜像自动升级功能。下面在我们的 GitOps 部署仓库来实现该能力。
镜像自动更新功能需要确保 Flux 在安装配置时已启用镜像自动更新组件。如未启用,可重复 bootstrap Flux 时加上
--components-extra=image-reflector-controller,image-automation-controller
参数来启用。
2. 注册 front-end
微服务的镜像仓库
1apiVersion: image.toolkit.fluxcd.io/v1beta1
2kind: ImageRepository
3metadata:
4 name: sock-shop-front-end
5spec:
6 image: ghcr.io/zxkane/weaveworksdemos/front-end
7 interval: 1m0s
3. 设置镜像更新策略
如下规则 ^0.3.x-0
将匹配 0.3.13-rc0
, 0.3.13-rc1
, 0.3.13
等镜像版本。
1apiVersion: image.toolkit.fluxcd.io/v1beta1
2kind: ImagePolicy
3metadata:
4 name: sock-shop-front-end
5spec:
6 imageRepositoryRef:
7 name: sock-shop-front-end
8 policy:
9 semver:
10 range: '^0.3.x-0'
4. 创建自动镜像更新配置
Flux 自动镜像配置会指定应用配置的 Git 仓库,包括分支、路径等信息。
1apiVersion: image.toolkit.fluxcd.io/v1beta1
2kind: ImageUpdateAutomation
3metadata:
4 name: sock-shop-front-end
5spec:
6 git:
7 checkout:
8 ref:
9 branch: gitops
10 commit:
11 author:
12 email: fluxcdbot@users.noreply.github.com
13 name: fluxcdbot
14 messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'
15 push:
16 branch: gitops
17 interval: 1m0s
18 sourceRef:
19 kind: GitRepository
20 name: sock-shop-tenant
21 namespace: sock-shop
22 update:
23 path: ./deploy/kubernetes/overlays/development
24 strategy: Setters
5. 为应用 GitOps 仓库配置读写凭证
由于 Flux 需要将更新后的镜像版本信息提交回应用仓库,需要为 Flux 中配置的应用 GitRepository
指定可读写的访问凭证。
下面提供参考步骤创建 Git 仓库访问凭证并配置。
1. 创建 Sealed Secret 保存 Git 仓库读写权限的私钥
1kubectl -n sock-shop create secret generic flux-image-automation \
2--from-file=identity=/path/gitops-image-update-id-ecdsa \
3--from-file=identity.pub=/path/gitops-image-update-id-ecdsa.pub \ # 确保此公钥已配置在 Git 仓库且具有读写权限,如 Github 仓库的 `Deploy Keys`
4--from-literal=known_hosts="github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=" \
5--dry-run=client \
6-o yaml > flux-image-automation-secrets.yaml
7
8kubeseal --format=yaml --cert=pub-sealed-secrets-dev.pem \
9< flux-image-automation-secrets.yaml > ./apps/overlays/development/sock-shop/sealed-git-token.yaml
2. 通过 Kustomize 为 DEV
环境的 GitRepository
配置指定访问凭证
1apiVersion: kustomize.config.k8s.io/v1beta1
2kind: Kustomization
3namespace: sock-shop
4resources:
5 - ../../../base/sock-shop
6 - ./sealed-slack-secrets.yaml
7 - ./sealed-git-token.yaml
8 - ./registry.yaml
9 - ./policy.yaml
10 - ./image-automation.yaml
11patches:
12 - patch: |-
13 - op: replace
14 path: /spec/path
15 value: ./deploy/kubernetes/overlays/development
16 target:
17 group: kustomize.toolkit.fluxcd.io
18 version: v1beta2
19 kind: Kustomization
20 name: sock-shop-tenant
21 - patch: |
22 - op: replace
23 path: /spec/channel
24 value: gitops-flux
25 target:
26 group: notification.toolkit.fluxcd.io
27 version: v1beta1
28 kind: Provider
29 name: slack
30 - patch: |
31 - op: replace
32 path: /spec/url
33 value: git@github.com:zxkane/microservices-demo.git
34 - op: replace
35 path: /spec/secretRef
36 value: {}
37 - op: replace
38 path: /spec/secretRef/name
39 value: flux-image-automation
40 target:
41 group: source.toolkit.fluxcd.io
42 version: v1beta1
43 kind: GitRepository
44 name: sock-shop-tenant
6. 验证镜像自动更新
更新微服务 front-end
代码且tag版本后,新的镜像版本被发布到镜像仓库。
通过前面配置的 ImageRepository
和 ImagePolicy
扫描到 front-end
镜像符合策略的新版本发布,
根据 ImageUpdateAutomation
配置的 Sock Shop 应用仓库,查找指定的镜像变量,
Flux 的 image-automation-controller
自动将更新的镜像信息提交到应用仓库实现持续部署。
六、小结及展望
本文介绍了如何使用 GitOps 工具 FluxCD v2 构建企业内部在 Kubernetes 上持续交付共享服务平台, 将平台团队和应用/业务团队统一在同样的 Git 工作流程下,同时授权应用/业务团队用自服务的方式持续交付应用的敏捷部署。 此方案将安全和效率有效的结合在一起。前述的示例可在此仓库获取完整的 GitOps 代码。
同时面对复杂的企业场景,还有一些方面还可以持续的优化,例如,
- 面对关键的线上生产系统,如何安全增量的灰度发布?
- Sealed Secrets 引入了额外的私钥管理需求,在云计算环境如何改善 GitOps 密钥的管理?
- 如何将云平台的资源 IaC 同 Kubernetes 内资源 GitOps 协同管理?
- 如何更加高效的开发 Kubernetes manifests(YAML)?
将在后续的文章中逐个探讨这些问题。