基于 Flux 的 GitOps 实战(下)

Overview

上篇介绍基于 CNCF 下的 GitOps 工具 FluxCD v2 实现了管理多账户的 Kubernetes 集群的共享组件,Secrets 使用的最佳实践, GitOps 流水线事件同 IM(Slack) 的集成,以及对 GitOps 代码的 CI 流程。

本文将围绕如何使用 Flux 的多租户管理最佳实践,打造基于 GitOps 工作流程的共享服务平台, 实现租户(业务/应用团队)可自助的持续部署。

一、基于 GitOps 的共享服务平台设定

Kubernetes 提供了命名空间作为一种机制将同一集群中的资源划分为相互隔离的组。 同一个集群中多租户多团队的应用管理将沿用 Kubernetes 内置的各种机制来为不同的租户、团队或应用进行隔离,包括且不限于,

基于 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 的 KustomizationHelm Releases 禁止引用其他命名空间定义的 Source。 同时,启用强制模拟功能,将 KustomizationHelm 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版本后,新的镜像版本被发布到镜像仓库。 通过前面配置的 ImageRepositoryImagePolicy 扫描到 front-end 镜像符合策略的新版本发布, 根据 ImageUpdateAutomation 配置的 Sock Shop 应用仓库,查找指定的镜像变量, Flux 的 image-automation-controller 自动将更新的镜像信息提交到应用仓库实现持续部署。

镜像自动更新消息通知
图1:镜像自动更新消息通知

六、小结及展望

本文介绍了如何使用 GitOps 工具 FluxCD v2 构建企业内部在 Kubernetes 上持续交付共享服务平台, 将平台团队和应用/业务团队统一在同样的 Git 工作流程下,同时授权应用/业务团队用自服务的方式持续交付应用的敏捷部署。 此方案将安全效率有效的结合在一起。前述的示例可在此仓库获取完整的 GitOps 代码。

同时面对复杂的企业场景,还有一些方面还可以持续的优化,例如,

将在后续的文章中逐个探讨这些问题。

Posts in this Series