Skip to content

51.Flux

Flux 是一套针对 Kubernetes 的持续交付和渐进式交付的解决方案,可以让我们以 GitOps 的方式轻松地交付应用。和另一个同类的 CNCF 孵化项目 Argo CD 不同,Flux CD 是许多工具集的集合,天然松耦合并且有良好的扩展性,用户可按需取用。最新版本的 Flux 引入了许多新功能,使其更加灵活多样。Flux 已经是 CNCF 毕业项目。

img

组件

Flux 是使用 GitOps Toolkit 组件构建的,它是一组:

  • 专用工具和 Flux 控制器
  • 可组合的 API
  • 在 fluxcd GitHub 组织下,为构建基于 Kubernetes 的持续交付提供可重用的 Go 依赖包。

用于在 Kubernetes 之上构建持续交付。

img

这些 API 包括 Kubernetes 自定义资源,可以由集群用户或其他自动化工具进行创建和更新。我们可以使用这个工具包扩展 Flux,并构建自己的持续交付系统。

Flux 的核心工具包包括以下 5 个:

  • Source Controller:源管理组件的主要作用是为制品获取提供一个通用接口。负责从配置的源存储库和分支持续拉取清单。
  • Kustomize Controller:负责对源控制器检索到的 Kubernetes 清单进行 kustomize 构建,并通过与 Kubernetes APIServer 的通信将它们应用于集群。Kustomize 是一个工具,它允许你声明性地定义 Kubernetes 清单,并通过插值将其模板化。
  • Helm Controller:负责从源存储库调谐 HelmRepositoryHelmChart 资源并将其部署到集群上。通过这个控制器,你可以像平时使用 values 文件一样创建一个 Helm 部署清单。然后 Helm Controller 将负责获取 Helm 资源库。你可以配置 Chart 的来源,通过 HelmRepository 资源从上游的 Helm 仓库中提取,或者通过 GitRepository 资源从本地的 Git 仓库中提取。
  • Notification Controller:负责处理入站和出站事件。一方面处理来自外部系统(GitHub、GitLab、Bitbucket、Harbor、Jenkins)的事件,然后通知 GitOps Toolkit 控制器有关源更改的信息。另一方面处理由 GitOps Toolkit 控制器发出的事件,然后根据事件的严重性和涉及的对象将它们转发至外部系统(Slack、Microsoft Teams、Discord、Rocker)。
  • Image Automation Controllers:Image Automation Controllers 包含 image-reflectorimage-automation 两个控制器,image-reflector-controller 负责扫描镜像仓库,然后将镜像元数据反应在 Kubernetes 资源中,image-automation-controller 负责根据最新的镜像扫描结果更新 YAML 文件,然后将更改后的内容提交到给定的 Git Repository 中。

每个工具包都是一个控制器,都有自己的自定义资源定义 (CRD),用于定义其行为。

安装

Flux 项目由命令行工具(FLux CLI)和一系列 Kubernetes 控制器组成。要安装 Flux,首先需要下载 Flux CLI,然后使用 CLI,可以在集群上部署 Flux 控制器并配置 GitOps 交付流水线。

Flux CLI 是一个二进制可执行文件,可以从 GitHub 发布页面下载直接下载即可。

对于 Mac 用户可以直接使用 homebrew 进行一键安装:

$ brew install fluxcd/tap/flux

如果是 Linux 用户也可以使用下面的命令进行安装:

$ curl -s https://fluxcd.io/install.sh | sudo bash

安装完成后,可以使用 flux 命令来验证是否安装成功:

$ flux --version
flux version 2.1.1

接下来我们就可以使用 Flux CLI 来安装 Flux 控制器了。

Flux CLI 提供了一个 bootstrap 命令在 Kubernetes 集群上部署 Flux 控制器,并配置控制器以从 Git 存储库同步集群状态。除了安装控制器之外,bootstrap 命令还将 Flux 清单推送到 Git 存储库,并将 Flux 配置为从 Git 进行自我更新。

img

如果集群上存在 Flux 控制器,则 bootstrap 命令将在需要时执行升级。Bootstrap 是幂等的,可以安全地运行该命令任意多次。

Flux 与主流的 Git 提供商进行集成,以简化部署密钥和其他身份验证机制的初始设置。比如我们这里选择和 GitLab 进行集成。那么我们可以使用 bootstrap gitlab 命令在 Kubernetes 集群上部署 Flux 控制器,并配置控制器从 GitLab 项目同步集群状态。除了安装控制器之外,bootstrap 命令还将 Flux 清单推送到 GitLab 项目,并将 Flux 配置为从 Git 进行自我更新。运行 bootstrap 命令后,对集群的任何操作都可以通过 Git 推送完成,无需连接到 Kubernetes 集群。

要启动 Flux,运行命令的人员必须拥有目标 Kubernetes 集群的集群管理员权限,还要求运行该命令的人是 GitLab 项目的所有者,或者具有 GitLab 组的管理员权限。

为了访问 GitLab API,boostrap 命令需要具有对 GitLab API 的完整读/写访问权限的 GitLab 个人访问令牌 (PAT)。GitLab PAT 可以导出为环境变量:

export GITLAB_TOKEN=<gh-token>

如果未设置 GITLAB_TOKEN 环境变量,boostrap 命令将提示你输入 token,当然可以使用管道提供,例如:echo "<gl-token>" | flux bootstrap gitlab

比如我们这里可以前往 GitLab 个人访问令牌页面 页面创建一个 PAT:

img

然后我们可以使用下面的命令来安装 Flux:

export GITLAB_TOKEN=glpat-RzooW-ViSatx6zgzmb6d
flux bootstrap gitlab \
  --deploy-token-auth \
  --hostname=gitlab.k8s.local \
  --owner=cnych \
  --repository=flux \
  --branch=main \
  --path=clusters/my-cluster \
  --personal

如果指定的项目不存在,Flux 将为你创建一个私有项目,如果希望创建公共项目,需要设置 --private=false 参数。使用 --deploy-token-auth 时,CLI 会生成一个 GitLab 项目部署令牌,并将其作为 Kubernetes Secret 存储在集群中 flux-system 命名空间内。

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

上面的命令执行后会出现如下所示的错误信息:

► connecting to https://gitlab.k8s.local
✗ failed to get Git repository "https://gitlab.k8s.local/cnych/flux": provider error: Get "https://gitlab.k8s.local/api/v4/projects/cnych%2Fflux": tls: failed to verify certificate: x509: certificate is valid for ingress.local, not gitlab.k8s.local

这是因为我们这里安装的 GitLab 使用的是 http 的方式,而且该命令下面并没有跳过对 https 证书校验的参数,所以这里我们需要换另外一种方式来安装,使用通用的 git 方式来安装,命令如下所示:

flux bootstrap git \
  --url=http://gitlab.k8s.local/cnych/flux \
  --username=cnych \
  --password=<gh-token> \
  --token-auth=true \
  --path=clusters/my-cluster
  --allow-insecure-http=true  # 运行不安全的 http 方式

上面的命令会在 flux-system 命名空间中部署一系列 Kubernetes 控制器及其 CRD、RBAC 和网络策略,默认的组件包括: source-controllerkustomize-controllerhelm-controllernotification-controller,可以通过 --components 参数来指定要安装的组件,但是需要注意最小安装需要 source-controllerkustomize-controller 两个组件。

上面的命令执行后会正常情况下会出现如下所示的输出信息:

► cloning branch "main" from Git repository "http://gitlab.k8s.local/cnych/flux"
✔ cloned repository
► generating component manifests
✔ generated component manifests
✔ component manifests are up to date
✔ reconciled components
► determining if source secret "flux-system/flux-system" exists
► generating source secret
► applying source secret "flux-system/flux-system"
✔ reconciled source secret
► generating sync manifests
✔ generated sync manifests
✔ sync manifests are up to date
► applying sync manifests
✔ reconciled sync configuration
◎ waiting for Kustomization "flux-system/flux-system" to be reconciled
✔ Kustomization reconciled successfully
► confirming components are healthy
✔ helm-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ notification-controller: deployment ready
✔ source-controller: deployment ready
✔ all components are healthy

flux-system 命名空间中可以查看到部署的 Flux 控制器:

$ kubectl get pods -n flux-system
NAME                                      READY   STATUS    RESTARTS   AGE
helm-controller-7c8b698656-gftdr          1/1     Running   0          65m
kustomize-controller-858996fc8d-k2dlx     1/1     Running   0          65m
notification-controller-ddf44665d-49vtp   1/1     Running   0          65m
source-controller-594c848975-sq2pp        1/1     Running   0          29m

此时在对于的 GitLab 代码仓库 http://gitlab.k8s.local/cnych/flux 中可以看到 Flux 为我们创建了一个 clusters/my-cluster/flux-system 的目录,里面包含了一些 Flux 的配置文件。

img

到这里我们就完成了 Flux 的安装。

示例

这里我们还是以前面 Jenkins Pipeline 章节中的示例来进行说明,如何通过 Flux 来实现 GitOps 的持续交付。

部署应用

示例中要用到的 Git 资源清单仓库为 http://gitlab.k8s.local/cnych/k8s-demo-config,该项目中包含一个 helm 目录,目录下面就是一个 helm chart 包:

img

当然在继续之前,为避免产生影响,我们可以将前面通过 Argo CD 部署的应用删除掉。

接下来我们就可以通过 Flux 来部署应用了,首先需要为 FluxCD 创建一个仓库连接信息,这就需要用到一个名为 GitRepository 的 CRD 对象,该对象可以定义一个 Source 代码源来为 Git 存储库的一个版本生成一个制品,如下所示:

# k8s-demo-git-repo.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: k8s-demo
spec:
  url: http://gitlab.k8s.local/cnych/k8s-demo-config
  timeout: 60s
  interval: 30s
  ref:
    branch: main
  secretRef:
    name: k8s-demo
---
apiVersion: v1
stringData:
  password: <gl-token>
  username: cnych
kind: Secret
metadata:
  name: k8s-demo
type: Opaque

这里我们创建了一个名为 k8s-demo 的 GitRepository 对象,其中 spec 字段定义了如何从 Git 存储库提取数据,url 字段指定了 Git 存储库的 URL,ref 字段指定了要提取的代码分支,interval 字段指定了从 Git 存储库提取数据的频率,secretRef 字段指定了包含 GitRepository 身份验证凭据的 Secret。

对于 HTTPS 仓库,Secret 必须包含基本认证的 usernamepassword 字段,或者令牌认证的 bearerToken 字段。对于 SSH 仓库,Secret 必须包含 identityknown_hosts 字段。

直接应用该资源对象即可:

$ kubectl apply -f k8s-demo-git-repo.yaml
gitrepository.source.toolkit.fluxcd.io/k8s-demo created
secret/k8s-demo created
$ kubectl get gitrepositories
NAME       URL                                             AGE   READY   STATUS
k8s-demo   http://gitlab.k8s.local/cnych/k8s-demo-config   53s   True    stored artifact for revision 'main@sha1:cbb0226130da3ff5e1d4ccad9407e210ab65e551'

创建后我们可以通过 kubectl describe gitrepository k8s-demo 命令来查看 GitRepository 的各种状态:

$ kubectl describe gitrepositories k8s-demo
Name:         k8s-demo
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  source.toolkit.fluxcd.io/v1
Kind:         GitRepository
Metadata:
  Creation Timestamp:  2023-09-23T09:29:35Z
  Finalizers:
    finalizers.fluxcd.io
  Generation:        1
  Resource Version:  485193
  UID:               fef6070d-56bf-452b-8aeb-c821a76ab8f2
Spec:
  Interval:  30s
  Ref:
    Branch:  main
  Secret Ref:
    Name:   k8s-demo
  Timeout:  60s
  URL:      http://gitlab.k8s.local/cnych/k8s-demo-config
Status:
  Artifact:
    Digest:            sha256:b1944afa22fe6b0ce816100b7ac307fc142506287af9d8ac2cc693f7af73364b
    Last Update Time:  2023-09-23T09:29:36Z
    Path:              gitrepository/default/k8s-demo/cbb0226130da3ff5e1d4ccad9407e210ab65e551.tar.gz
    Revision:          main@sha1:cbb0226130da3ff5e1d4ccad9407e210ab65e551
    Size:              7240
    URL:               http://source-controller.flux-system.svc.cluster.local./gitrepository/default/k8s-demo/cbb0226130da3ff5e1d4ccad9407e210ab65e551.tar.gz
  Conditions:
    Last Transition Time:  2023-09-23T09:29:36Z
    Message:               stored artifact for revision 'main@sha1:cbb0226130da3ff5e1d4ccad9407e210ab65e551'
    Observed Generation:   1
    Reason:                Succeeded
    Status:                True
    Type:                  Ready
    Last Transition Time:  2023-09-23T09:29:36Z
    Message:               stored artifact for revision 'main@sha1:cbb0226130da3ff5e1d4ccad9407e210ab65e551'
    Observed Generation:   1
    Reason:                Succeeded
    Status:                True
    Type:                  ArtifactInStorage
  Observed Generation:     1
Events:
  Type    Reason                 Age                From               Message
  ----    ------                 ----               ----               -------
  Normal  NewArtifact            103s               source-controller  stored artifact for commit 'build: automatic update of devops-demo2'
  Normal  GitOperationSucceeded  14s (x3 over 72s)  source-controller  no changes since last reconcilation: observed revision 'main@sha1:cbb0226130da3ff5e1d4ccad9407e210ab65e551'

通过 .status.artifact.revision 字段可以查看到当前的 Git 版本,新的制品在 .status.artifact 字段中可以查看到。

接下来我们只需要为该应用创建一个部署策略即可,由于我们这里要发布的是 Helm Chart,所以我们需要创建一个 HelmRelease 对象,该对象可以定义一个包含 Chart 的源(可以是 HelmRepositoryGitRepositoryBucket)告知 source-controller,以便 HelmRelease 能够引用它,很明显我们这里的源就是上面定义的 GitRepository 对象。

我们这里创建的 HelmRelease 对象如下所示:

# k8s-demo-helm-release.yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: k8s-demo
  namespace: default
spec:
  interval: 30s
  chart:
    spec:
      chart: helm # Chart 是 Helm chart 在 SourceRef 中可用的名称或路径
      reconcileStrategy: Revision
      sourceRef:
        kind: GitRepository
        name: k8s-demo
        namespace: default
      valuesFiles:
        - helm/values.yaml
        - helm/my-values.yaml
      interval: 30s
  values:
    replicaCount: 2

上面我们定义了一个 HelmRelease 对象,其中 chart 字段指定了 Helm Chart 的源,因为我们这里的 Helm Chart 是存储在 Git 代码仓库中的,所以我们通过 sourceRef 字段来指定 GitRepository 对象,interval 字段指定了从 Git 存储库提取数据的频率,values 字段指定了 Chart 的 values 值。

chart 字段下面还有一个非常重要的字段 reconcileStrategy,用来指定调谐的策略,有效值为:ChartVersionRevision,默认为 ChartVersion,表示根据 Chart 版本的变化来重新更新,而为我们这里的 Chart 包是托管在 Git 仓库中的,所以我们希望有一次 Git Commit 操作就认为 Chart 版本发生了变化,所以我们需要明确指定为 Revision,这样当 Git 代码仓库中的 Chart 版本发生变化时,Flux 就会自动更新应用。

其中的 valuesFiles 字段是一个备选的值文件列表,用作 Chart 的 Values 值(默认情况下不包括 values.yaml),是相对于 SourceRef 的路径,Values 文件按照此列表的顺序合并,最后一个文件将覆盖第一个文件。

同样直接应用该资源对象即可:

$ kubectl apply -f k8s-demo-helm-release.yaml
helmrelease.helm.toolkit.fluxcd.io/k8s-demo created
$ kubectl get helmrelease
NAME       AGE     READY   STATUS
k8s-demo   5m56s   True    Release reconciliation succeeded

我们可以看到已经成功部署了应用,如果出现了任何问题,可以通过 kubectl describe helmrelease k8s-demo 命令来查看 HelmRelease 的各种状态:

$ kubectl describe helmrelease k8s-demo
Name:         k8s-demo
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  helm.toolkit.fluxcd.io/v2beta1
Kind:         HelmRelease
Metadata:
  Creation Timestamp:  2023-09-23T09:53:49Z
  Finalizers:
    finalizers.fluxcd.io
  Generation:        2
  Resource Version:  491438
  UID:               7dc72409-57ee-4e4a-8950-ea5d09e43bc1
Spec:
  Chart:
    Spec:
      Chart:               helm
      Interval:            30s
      Reconcile Strategy:  Revision
      Source Ref:
        Kind:       GitRepository
        Name:       k8s-demo
        Namespace:  default
      Values Files:
        helm/values.yaml
        helm/my-values.yaml
      Version:  *
  Interval:     30s
  Values:
    Replica Count:  2
Status:
  Conditions:
    Last Transition Time:          2023-09-23T09:59:44Z
    Message:                       Release reconciliation succeeded
    Reason:                        ReconciliationSucceeded
    Status:                        True
    Type:                          Ready
    Last Transition Time:          2023-09-23T09:59:44Z
    Message:                       Helm install succeeded
    Reason:                        InstallSucceeded
    Status:                        True
    Type:                          Released
  Helm Chart:                      default/default-k8s-demo
  Last Applied Revision:           0.1.0+2
  Last Attempted Revision:         0.1.0+2
  Last Attempted Values Checksum:  1dd4966b30314fd329b48c5892b6a85412fd0236
  Last Release Revision:           1
  Observed Generation:             2
Events:
  Type    Reason  Age                   From             Message
  ----    ------  ----                  ----             -------
  Normal  info    41s (x13 over 6m31s)  helm-controller  HelmChart 'default/default-k8s-demo' is not ready
  Normal  info    41s                   helm-controller  Helm install has started
  Normal  info    36s                   helm-controller  Helm install succeeded

事实上我们虽然是在 HelmRelease 中去为 chart 源关联的 GitRepository 对象,但是实际上 HelmRelease 会自动创建一个 HelmChart 对象,该对象可以定义一个包含 Chart 的源,以便 HelmRelease 能够引用它,如下所示:

$ kubectl get helmchart default-k8s-demo
NAME               CHART   VERSION   SOURCE KIND     SOURCE NAME   AGE     READY   STATUS
default-k8s-demo   helm    *         GitRepository   k8s-demo      7m35s   True    packaged 'devops-demo' chart with version '0.1.0+2' and merged values files [helm/values.yaml helm/my-values.yaml]
$ kubectl get helmchart default-k8s-demo -o yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmChart
metadata:
  name: default-k8s-demo
  namespace: default
spec:
  chart: helm
  interval: 30s
  reconcileStrategy: ChartVersion
  sourceRef:
    kind: GitRepository
    name: k8s-demo
  valuesFiles:
  - helm/values.yaml
  - helm/my-values.yaml
  version: '*'
# ......

最后我们也可以通过 helm 命令来验证我们的应用是否已经部署成功:

$ helm ls
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                 APP VERSION
k8s-demo        default         1               2023-09-23 09:59:39.906949292 +0000 UTC deployed        devops-demo-0.1.0+2   1.0

这样接下来我们只需要在 CI 流水线中去将镜像构建完成后,将镜像推送到镜像仓库,然后更新 Git 代码仓库中的 Values 文件的镜像版本即可,Flux 会自动检测到 Chart 版本的变化,然后自动更新应用。

镜像自动化更新

但是这样的话,我们每次都需要在 CI 流水线去手动更新 Git 代码仓库中的 Values 文件的镜像版本,这样就会比较麻烦,和 Argo CD 类似,Flux 也提供了一个 Image Automation 控制器的功能。

当新的容器镜像可用时,image-reflector-controllerimage-automation-controller 可以协同工作来更新 Git 存储库。

  • image-reflector-controller 扫描镜像存储库并反射到 Kubernetes 资源中的镜像元数据。
  • image-automation-controller 根据扫描的最新镜像更新 YAML 文件,并将更改提交到指定的 Git 存储库。

img

但是需要注意的是默认情况下 Flux 不会自动安装 image-reflector-controllerimage-automation-controller,所以我们需要手动安装这两个控制器,可以通过 --components-extra 参数来指定要安装的组件,如下所示:

# gh-token 为 GitLab PAT(例如:glpat-p17PB_dgWEDfWiUSAqUo)
flux bootstrap git \
  --url=http://gitlab.k8s.local/cnych/flux \
  --username=cnych \
  --password=<gh-token> \
  --token-auth=true \
  --path=clusters/my-cluster \
  --allow-insecure-http=true \
  --components-extra image-reflector-controller,image-automation-controller

这两个控制器安装完成后,我们就可以使用 Flux 配置容器镜像扫描和部署发布了。对于容器镜像,可以将 Flux 配置为:

  • 扫描镜像仓库并获取镜像标签
  • 根据定义的策略(semver、calver、regex)选择最新的标签
  • 替换 Kubernetes 清单中的标签(YAML 格式)
  • 检出分支、提交并将更改推送到远程 Git 存储库
  • 在集群中应用更改并变更容器镜像

对于生产环境,此功能允许你自动部署应用程序补丁(CVE 和错误修复),并在 Git 历史记录中保留所有部署的记录。

下面我们来思考下对于生产环境和测试环境 CI/CD 的工作流是怎样的?

生产环境 CI/CD 工作流

  • DEV:将错误修复推送到应用程序存储库
  • DEV:修改补丁版本并发布,例如 v1.0.1
  • CI:构建并推送标记为 registry.domain/org/app:v1.0.1 的容器镜像
  • CD:从镜像仓库中提取最新的镜像元数据(Flux 镜像扫描)
  • CD:将应用程序清单中的镜像标签更新为 v1.0.1(Flux 集群到 Git 调谐)
  • CD:将 v1.0.1 部署到生产集群(Flux Git 到集群调谐)

对于 Staging 环境,此功能允许你部署分支的最新版本,而无需在 Git 中手动编辑应用程序部署清单。

Staging 环境 CI/CD 工作流

  • DEV:将代码更改推送到应用程序存储库主分支
  • CI:构建并推送标记为 ${GIT_BRANCH}-${GIT_SHA:0:7}-$(date +%s) 的容器镜像
  • CD:从镜像仓库中提取最新的镜像元数据(Flux 镜像扫描)
  • CD:将应用程序清单中的镜像标记更新为 main-2d3fcbd-1611906956(Flux 集群到 Git 调谐)
  • CD:将 main-2d3fcbd-1611906956 部署到 Staging 环境集群(Flux Git 到集群调谐)

比如这里的示例使用的镜像地址为 cnych/devops-demo,我们可以先创建一个 ImageRepository 来告诉 Flux 扫描哪个镜像仓库以查找新标签:

$ flux create image repository k8s-demo \
--namespace=default \
--image=cnych/devops-demo \
--interval=30s \
--export > k8s-demo-registry.yaml

不知道 ImageRepository 如何编写,我们可以通过上面的 flux 命令来帮我们生成即可,上面的命令会生成如下所示的资源清单文件:

apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: k8s-demo
  namespace: default
spec:
  image: docker.io/cnych/devops-demo
  interval: 30s

对于私有镜像,可以使用 kubectl create secret docker-registry 在与 ImageRepository 相同的命名空间中创建 Kubernetes Secret。同样使用的密码需要通过 Docker Hub 后台创建 Access Token 来获取。

img

然后,可以通过引用 ImageRepository 中的 Kubernetes Secret 来配置 Flux 以使用凭据:

kind: ImageRepository
spec:
  secretRef:
    name: regcred

然后就可以应用上面的资源清单文件了:

$ kubectl apply -f k8s-demo-registry.yaml
imagerepository.image.toolkit.fluxcd.io/k8s-demo created
$ kubectl get imagerepository
NAME       LAST SCAN              TAGS
k8s-demo   2023-09-24T03:11:05Z   21

可以看到 ImageRepository 对象已经创建成功了,并且扫描到了 21 个镜像标签。当然我们也可以通过 kubectl describe imagerepository k8s-demo 命令来查看 ImageRepository 的各种状态:

$ kubectl describe imagerepository k8s-demo
# ......
Status:
  Canonical Image Name:  index.docker.io/cnych/devops-demo
  Conditions:
    Last Transition Time:  2023-09-24T03:11:05Z
    Message:               successful scan: found 21 tags
    Observed Generation:   1
    Reason:                Succeeded
    Status:                True
    Type:                  Ready
  Last Scan Result:
    Latest Tags:
      main-25de3e1a-1695469144
      f4fc1ec
      e0b13c5
      dc9922d
      d9b7083
      d3fc3d5
      ae94ccf
      a6268b3
      9813cf9
      8ede4fb
    Scan Time:  2023-09-24T03:12:09Z
    Tag Count:  21
  Observed Exclusion List:
    ^.*\.sig$
  Observed Generation:  1
Events:
  Type    Reason     Age                From                        Message
  ----    ------     ----               ----                        -------
  Normal  Succeeded  87s                image-reflector-controller  successful scan: found 21 tags
  Normal  Succeeded  23s (x2 over 55s)  image-reflector-controller  no new tags found, next scan in 30s

如果你要告诉 Flux 在过滤标签时使用哪个 semver 版本范围的标签,则开源创建一个 ImagePolicy 对象。比如选择标签为 ${GIT_BRANCH}-${GIT_SHA:0:7}-$(date +%s) 的最新主分支构建,则可以使用以下 ImagePolicy

kind: ImagePolicy
spec:
  filterTags:
    pattern: "^main-[a-fA-F0-9]+-(?P<ts>.*)"
    extract: "$ts"
  policy:
    numerical:
      order: asc

选择最新的稳定版本(semver):

kind: ImagePolicy
spec:
  policy:
    semver:
      range: ">=1.0.0"

选择 1.x 范围内的最新稳定补丁版本 (semver):

kind: ImagePolicy
spec:
  policy:
    semver:
      range: ">=1.0.0 <2.0.0"

选择最新版本,包括预发行版 (semver):

kind: ImagePolicy
spec:
  policy:
    semver:
      range: ">=1.0.0-0"

由于 ImagePolicy 对象的策略只支持三种:

  • SemVer:语义版本
  • Alphabetical:字母顺序
  • Numerical:数字顺序

而前面我们的镜像标签是根据 git commit id 来生成的,不符合这里的规范,所以我们可以更改下镜像的 Tag 生成策略:

def myRepo = checkout scm
def gitCommit = myRepo.GIT_COMMIT.substring(0,8)
def gitBranch = myRepo.GIT_BRANCH
gitBranch = gitBranch.replace("origin/", "")

def unixTime = (new Date().time.intdiv(1000))
def imageTag = "${gitBranch}-${gitCommit}-${unixTime}"

然后我们可以通过如下所示的 ImagePolicy 对象来告诉 Flux 如何过滤镜像标签:

# k8s-demo-image-policy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
  name: k8s-demo
spec:
  imageRepositoryRef:
    name: k8s-demo
  filterTags:
    pattern: "^main-[a-fA-F0-9]+-(?P<ts>.*)"
    extract: "$ts"
  policy:
    numerical:
      order: asc

直接应用该资源对象即可:

$ kubectl apply -f k8s-demo-image-policy.yaml
$ kubectl get imagepolicy
NAME       LATESTIMAGE
k8s-demo   docker.io/cnych/devops-demo:main-25de3e1a-1695469144

可以看到 ImagePolicy 对象创建后过滤到了最新的镜像标签为 main-25de3e1a-1695469144

接下来我们就可以创建一个 ImageUpdateAutomation 对象来告诉 Flux 将镜像更新写入哪个 Git 存储库,但是这里还有一个问题就是我们的应用是通过 Helm Chart 来部署的,ImageUpdateAutomation 如何知道要把我们更新后的镜像标签写入到哪个 Values 文件中呢?写入到哪个位置呢?

这就需要用到 marker 功能了,用来标记 Image Automation Controller 自动更新的位置。比如我们这里使用的是 Helm Chart 来部署应用,决定使用哪个版本的镜像是通过 my-values.yaml 这个 Values 文件来指定的:

# my-values.yaml
image:
  repository: cnych/devops-demo
  tag: d3fc3d5
  pullPolicy: IfNotPresent
  imagePullSecrets:
    - name: docker-auth
# ......

所以我们需要在该文件中添加一个 marker 来告诉 Flux 将镜像标签写入到哪个位置,这个镜像策略的 marker 标记的格式有如下几种:

  • {"$imagepolicy": "<policy-namespace>:<policy-name>"}
  • {"$imagepolicy": "<policy-namespace>:<policy-name>:tag"}
  • {"$imagepolicy": "<policy-namespace>:<policy-name>:name"}

这些标记作为注解内联放置在目标 YAML 中,Setter 策略是指 Flux 可以在调谐期间找到并替换的 kyaml setter

我们这里重新修改下 my-values.yaml 文件,添加上 marker 标记:

# my-values.yaml
image:
  repository: cnych/devops-demo # {"$imagepolicy": "default:k8s-demo:name"}
  tag: d3fc3d5 # {"$imagepolicy": "default:k8s-demo:tag"}
  pullPolicy: IfNotPresent
  imagePullSecrets:
    - name: docker-auth
# ......

我们在上面的 Values 文件中的 image.repositoryimage.tag 字段添加了 marker 标记,分别使用的是上面创建的 ImagePolicy 对象的 name 和 tag,这样 Flux 就知道要将镜像标签写入到哪个位置了。

接下来我们再创建一个 ImageUpdateAutomation 对象来告诉 Flux 将镜像更新写入哪个 Git 存储库,同样开源使用 flux 命令来创建:

flux create image update k8s-demo \
--namespace=default \
--interval=1m \
--git-repo-ref=k8s-demo \
--git-repo-path="./helm/my-values.yaml" \
--checkout-branch=main \
--push-branch=main \
--author-name=fluxcdbot \
--author-email=fluxcdbot@youdianzhishi.com \
--commit-template="{{range .Updated.Images}}{{println .}}{{end}}" \
--export > k8s-demo-automation.yaml

该命令生成的 ImageUpdateAutomation 对象如下所示:

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: k8s-demo
  namespace: default
spec:
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@youdianzhishi.com
        name: fluxcdbot
      messageTemplate: "{{range .Updated.Images}}{{println .}}{{end}}"
    push:
      branch: main
  interval: 1m0s
  sourceRef:
    kind: GitRepository
    name: k8s-demo
  update:
    path: ./helm/my-values.yaml
    strategy: Setters # 指定如何对 git 存储库进行更新,目前只有 Setters 一种策略

同样直接更新该资源对象即可:

$ kubectl apply -f k8s-demo-automation.yaml
imageupdateautomation.image.toolkit.fluxcd.io/k8s-demo created
$ kubectl get imageupdateautomation
NAME       LAST RUN
k8s-demo   2023-09-23T10:58:06Z

现在我们去修改我们的应用程序代码,然后提交,通过 Jenkins Pipeline 构建并推送镜像到镜像仓库即可,正常情况下 ImageRepository 对象会自动扫描到新的镜像标签,然后 ImagePolicy 对象会过滤到最新的镜像标签,最后通过 ImageUpdateAutomation 对象会自动将镜像标签更新到 Git 代码仓库中的 Values 文件中,然后 Flux 就会自动更新应用了。

$ kubectl describe imageupdateautomation k8s-demo
# ......
Status:
  Conditions:
    Last Transition Time:    2023-09-24T03:34:45Z
    Message:                 no updates made; last commit 7bcce72 at 2023-09-24T03:37:48Z
    Reason:                  ReconciliationSucceeded
    Status:                  True
    Type:                    Ready
  Last Automation Run Time:  2023-09-24T03:38:45Z
  Last Push Commit:          7bcce72bf66268c6df9800b30dd2877091f8116b
  Last Push Time:            2023-09-24T03:37:48Z
  Observed Generation:       1
Events:
  Type    Reason  Age    From                         Message
  ----    ------  ----   ----                         -------
  Normal  info    4m11s  image-automation-controller  committed and pushed commit '62263c95f9ecc727939831d4905f65289ebc4ebf' to branch 'main'
docker.io/cnych/devops-demo:main-25de3e1a-1695469144
  Normal  info  67s  image-automation-controller  committed and pushed commit '7bcce72bf66268c6df9800b30dd2877091f8116b' to branch 'main'
docker.io/cnych/devops-demo:main-e21d5f90-1695526572

从上面的事件中可以看到,Flux 已经自动将镜像标签更新到了 Git 代码仓库中的 Values 文件中了,然后 Flux 就会自动更新应用了。当然可以查看 Git 仓库中的提交记录来验证:

img

当然这个 git commit 的信息模板是可以根据自己的需求来定义的,这个模板可以使用 Go Template 和大部分 Sprig 库 的语法,比如:

kind: ImageUpdateAutomation
metadata:
  name: flux-system
spec:
  git:
    commit:
      messageTemplate: |
        Automated image update

        Automation name: {{ .AutomationObject }}

        Files:
        {{ range $filename, $_ := .Updated.Files -}}
        - {{ $filename }}
        {{ end -}}

        Objects:
        {{ range $resource, $_ := .Updated.Objects -}}
        - {{ $resource.Kind | lower }} {{ $resource.Name | lower  }}
        {{ end -}}

        Images:
        {{ range $image, $_ := .Updated.Images -}}
            {{ if contains "1.0.0" $image -}}
                - {{ $image }}
            {{ else -}}
                [skip ci] wrong image
            {{ end -}}        
        {{ end -}}
      author:
        email: fluxcdbot@users.noreply.github.com
        name: fluxcdbot

Web UI

Flux 项目原本提供了一个 Web UI 来管理 Flux 集群,但该项目已经存档,FluxCD 组织不再开发,所以我们这里不再介绍了,如果你想使用 Web UI 来管理 Flux 集群,可以使用 Weaveworks 提供的 weave-gitops 项目,该下面为 Flux 提供了一个免费的开源 GUI。

weave gitops

Weave GitOps 改善了开发人员体验,简化了在 Kubernetes 上部署和管理云原生应用程序的复杂性和认知负担,使团队可以更快地开展工作,它是 Flux 的一个强大扩展。

Weave GitOps 可以帮助应用程序运维人员轻松发现和解决问题 - 简化和扩展 GitOps 和持续交付的采用。UI 提供了引导式体验,可帮助用户轻松发现 Flux 对象之间的关系并加深理解,同时提供对应用程序部署的见解。

Weave GitOps 初了提供了一个开源的版本之外,还有一个企业版本,其 OSS 版本是一个简单的开源开发者平台,适合那些没有 Kubernetes 专业知识但想要云原生应用程序的人。它包括 UI 和许多其他功能,使团队超越简单的 CI/CD 系统。体验启用 GitOps 并在集群中运行应用程序是多么容易。我们这里当然是使用开源版本了。

Weave GitOps 提供了一个命令行界面,可帮助用户创建和管理资源。gitops CLI 目前在 Mac(x86 和 Arm)和 Linux 上受支持,包括 Windows Subsystem for Linux (WSL)。

有多种方法可以安装 gitops CLI:

$ curl --silent --location "https://github.com/weaveworks/weave-gitops/releases/download/v0.32.0/gitops-$(uname)-$(uname -m).tar.gz" | tar xz -C /tmp
$ sudo mv /tmp/gitops /usr/local/bin
$ gitops version
Current Version: 0.32.0
GitCommit: 49a4249d8c205f14f0777c921cd69c04951e208f
BuildTime: 2023-09-13T17:23:13Z
Branch: releases/v0.32.0

当然如果是 Mac 用户可以使用 Homebrew 进行一键安装:

brew tap weaveworks/tap
brew install weaveworks/tap/gitops

CLI 工具安装完成后,接下来就可以来部署 Weave GitOps 了,我们将:

  • 使用 GitOps CLI 工具生成 HelmReleaseHelmRepository 对象。
  • 创建一些登录凭据来访问 Dashboard。
  • 将生成的 yaml 提交到我们的基础设施代码库。
  • 观察它们是否已同步到集群。

前面我们安装 Flux 的基础设施代码仓库为 http://gitlab.k8s.local/cnych/flux,现在我们 Clone 这个仓库到本地:

$ git clone http://gitlab.k8s.local/cnych/flux
$ cd flux

然后运行以下命令创建 HelmRepositoryHelmRelease 来部署 Weave GitOps:

PASSWORD="gitops321"  # 设置登录密码
gitops create dashboard ww-gitops \
  --password=$PASSWORD \
  --export > ./clusters/my-cluster/weave-gitops-dashboard.yaml

该命令会存储密码的哈希值,这在演示和测试的时候相对安全,但我们强烈建议在生产系统中使用更安全的密钥存储方法(如 Flux 的 SOPS 集成)。

然后提交 weave-gitops-dashboard.yaml 并将其推送到 flux 基础存储库中去:

git add -A && git commit -m "Add Weave GitOps Dashboard"
git push

然后隔一会儿可以去检测下是否已经部署成功了:

$ kubectl get pods -n flux-system

如果想要定制 Weave GitOps 的部署,可以通过配置 Helm Chart 的 Values 来实现,可配置的参数可以参考文档:https://docs.gitops.weave.works/docs/references/helm-reference/。

现在我们想要访问 Weave GitOps 的 Web UI,可以通过 kubectl port-forward 命令来实现:

kubectl port-forward svc/ww-gitops-weave-gitops -n flux-system 9001:9001

然后我们就可以通过浏览器访问 http://localhost:9001 来访问 Weave GitOps 的 Web UI 了,输入用户名为 admin,密码为前面配置的 PASSWORD 的值,即可登录:

img

登录后,可以将进入 APPLICATIONS 视图,该视图可以快速了解部署的状态并显示来自 KustomizationHelmRelease 对象的摘要信息。我们还可以使用右上角的切换开关应用深色模式。

img

让我们来探索下 flux-system 这个基础设施 Kustomization 对象,返回到应用程序视图,并点击 flux-system 对象。

img

可能需要一些时间来加载数据。一旦加载完成,应该会得到一个类似上面截图的结果。在这里,可以找到有关资源定义的关键信息。

  • 它正在读取哪个来源 Source
  • 最新应用的提交
  • 正在部署的源存储库的确切路径
  • Flux 将寻求调谐声明状态和实时状态之间任何差异的时间间隔 Interval。例如,如果集群上应用了 kubectl 补丁,它将被有效地恢复。如果该对象报告了较长的错误消息,您将能够在此页面上看到完整的错误消息。

在 UI 的左侧菜单中,单击 SOURCES 视图,这将向你显示 Flux 从何处提取其应用程序定义(例如 Git 存储库)以及该同步的当前状态。 Sources 显示来自 GitRepositoryHelmRepositoryHelmChartBucket 对象的摘要信息。

img

Sources 表视图显示有关状态的信息,以便可以查看 Flux 是否能够从给定源成功拉取,以及最后检测到哪个特定提交。它向你显示诸如 INTERVAL 之类的关键信息,即 Flux 在指定源位置检查更新的频率。你还可以查看该来源是否经过验证。

除此之外还有其他的一些功能,比如 IMAGE AUTOMATION 页面、POLICIES 也没、NOTIFICATIONS 页面等等。但是可以发现这个 UI 主要是用于展示的,我们并不能通过页面去创建资源对象,所以我们还是推荐使用命令行来管理 Flux 集群。

Flamingo

Flamingo 是 Argo 的 Flux 子系统(FSA),Flamingo 的容器镜像可以作为等效的 ArgoCD 版本的插件扩展,与 Argo CD 一起可视化和管理 Flux 工作负载。Loopback Reconciliation 是 Flamingo 的一项功能,有助于同步使用 GitOps 方法部署的应用程序。当 Argo CD 用户界面中启用 FluxSubsystem 功能时,它会被激活。其工作原理如下:

  1. KustomizationHelm 模式下创建 Argo CD 应用程序清单并将其部署到集群。
  2. Flamingo 将 Argo CD 应用程序清单转换为等效的 Flux 对象,可以是 Kustomization 对象,也可以是带有 Source 的 HelmRelease 对象,具体取决于 Argo CD 清单中使用的模式。如果应用程序中已存在 Flux 对象,Flamingo 将使用它们作为引用,而不是创建新对象。
  3. Flamingo 通过使用 Flux 对象的状态作为所需状态来同步或协调 Argo CD 应用程序的状态与其 Flux 对应项。为此,环回调谐机制绕过了 Argo CD 中的本机协调过程,而是依赖于 Flux 调谐。然后它使用 Flux 对象的结果向 Argo CD 报告。

Loopback Reconciliation 通过使应用程序的状态与 Flux 对象中定义的所需状态保持同步,有助于确保基于 GitOps 的部署的可靠性和一致性。

img

要安装 Flamingo 比较简单,我们可以通过 Flux 的 Kustomization 对象来部署,创建如下所示的资源对象:

# flamingo.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
  name: fsa-demo
  namespace: flux-system
spec:
  interval: 30s
  url: oci://ghcr.io/flux-subsystem-argo/flamingo/manifests
  ref:
    tag: latest
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: fsa-demo
  namespace: flux-system
spec:
  prune: true
  interval: 2m
  path: "./demo"
  sourceRef:
    kind: OCIRepository
    name: fsa-demo
  timeout: 3m

然后直接应用该资源对象即可:

$ kubectl apply -f flamingo.yaml
$ kubectl get ocirepository fsa-demo -n flux-system
NAME       URL                                                    READY   STATUS                                                                                                        AGE
fsa-demo   oci://ghcr.io/flux-subsystem-argo/flamingo/manifests   True    stored artifact for digest 'latest@sha256:e9df1250e9e9ae2931a6121b11f30f989cb20aaa4326659fd8ee5b6dd8ddec94'   72s
$ kubectl get kustomization fsa-demo -n flux-system
NAME          AGE     READY   STATUS
fsa-demo      82s     True    Applied revision: latest@sha256:e9df1250e9e9ae2931a6121b11f30f989cb20aaa4326659fd8ee5b6dd8ddec94

上面的资源对象会创建一个 argocd 的命名空间,并会部署 Argo CD 的所有组件:

$ kubectl get pods -n argocd
NAME                                               READY   STATUS    RESTARTS   AGE
argocd-application-controller-0                    1/1     Running   0          2m16s
argocd-applicationset-controller-c9b9899d6-t4gh6   1/1     Running   0          2m16s
argocd-dex-server-798cc6585d-dtc4k                 1/1     Running   0          2m16s
argocd-notifications-controller-5b77dc9659-7t9cw   1/1     Running   0          2m16s
argocd-redis-b5d6bf5f5-6kdgc                       1/1     Running   0          2m16s
argocd-repo-server-785445967d-xxrq7                1/1     Running   0          2m16s
argocd-server-758f648df4-8zhc5                     1/1     Running   0          2m16s

argocd 部署完成后,我们可以通过 kubectl port-forward 命令来访问 Argo CD 的 Web UI:

$ kubectl -n argocd port-forward svc/argocd-server 8003:443

然后我们就可以通过浏览器访问 https://localhost:8003 来访问 Argo CD 的 Web UI 了。

img

默认情况下会有 2 个 FSA 应用,每个应用程序由 1 个 Flux 的 Kustomization 和 1 个 Flux 的 GitRepository 组成。

img

如果你现有集群中已经安装了 Argo CD,那么我们可以使用下面的命令来升级现有的 Argo CD。如果已经安装了 Argo CD,可以使用来自 FSA 的镜像替换现有安装的镜像。

kustomize build https://github.com/flux-subsystem-argo/flamingo//release?ref=${FSA_VERSION} \
  | yq e '. | select(.kind=="Deployment" or .kind=="StatefulSet")' - \
  | kubectl -n argocd apply -f -

如果想要将现有的 Flux 应用转换成 FSA 应用,可以使用 flamingo 的 CLI 命令来实现,所以首先需要安装 flamingo 命令行工具,前往 Flamingo CLI Release 页面下载对应的版本即可,然后解压后将二进制文件移动到 /usr/local/bin 目录下即可:

$ flamingo --version
flamingo version 0.3.1

CLI 工具安装完成后,接下来我们就可以将现有的 Flux 应用转换成 FSA 应用了,比如现在我们创建一个如下所示的 Flux Kustomization 对象:

cat << EOF | kubectl apply -f -
---
apiVersion: v1
kind: Namespace
metadata:
  name: podinfo-kustomize
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
  name: podinfo
  namespace: podinfo-kustomize
spec:
  interval: 10m
  url: oci://ghcr.io/stefanprodan/manifests/podinfo
  ref:
    tag: latest
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: podinfo
  namespace: podinfo-kustomize
spec:
  interval: 10m
  targetNamespace: podinfo-kustomize
  prune: true
  sourceRef:
    kind: OCIRepository
    name: podinfo
  path: ./
EOF

创建后我们可以使用下面的命令来生成一个 FSA 应用:

flamingo generate-app \
  --app-name=podinfo-ks \
  -n podinfo-kustomize ks/podinfo

创建完成后我们再去访问 Argo CD 的 Web UI,可以看到已经生成了一个 FSA 应用:

img

这样我们就可以通过 Argo CD 的 Web UI 界面来管理 Flux 的应用了。

消息通知

TODO?