Flux 如何监听镜像标签更新实现 GitOps
前面我们在使用 Flux 进行 Gitops 实践的过程中,我们每次都需要在 CI 流水线去手动更新 Git 代码仓库中的 Values 文件的镜像版本,这样就会比较麻烦,和 Argo CD 类似,Flux 也提供了一个 Image Automation 控制器的功能。
原理
当新的容器镜像可用时,image-reflector-controller 和 image-automation-controller 可以协同工作来更新 Git 存储库。
- 
image-reflector-controller 扫描镜像存储库并反射到 Kubernetes 资源中的镜像元数据。 - 
image-automation-controller 根据扫描的最新镜像更新 YAML 文件,并将更改提交到指定的 Git 存储库。 

但是需要注意的是默认情况下 Flux 不会自动安装 image-reflector-controller 和 image-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 来获取。

然后,可以通过引用 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.repository 和 image.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 仓库中的提交记录来验证:

当然这个 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文章转载自公众号:k8s技术圈




















