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技术圈