【导读】作者用一个构建流程作为例子,在这个场景下用 operator 实现了需求,本文比较概括地介绍了这个流程,详细实现在文末 github 链接内。
本文中介绍了创建一个简单 K8s operator 应用的方法,读完本文你将会理解有关 Operator 的主要概念和其作用。
首先我会讲解基本概念,之后将从最基础开始讲解如何着手编码。主要包括:
kubernetes 官方文档对 Operator 模式的定义如下:
Operator 模式旨在捕获(正在管理一个或一组服务的)运维人员的关键目标。负责特定应用和 service 的运维人员,在系统应该如何运行、如何部署以及出现问题时如何处理等方面有深入的了解。
在 Kubernetes 上运行工作负载的人们都喜欢通过自动化来处理重复的任务。Operator 模式会封装你编写的(Kubernetes 本身提供功能以外的)任务自动化代码。
Kubernetes Operator 一书中的解释是:
Operator 通过扩展 Kubernetes 控制面和 API 进行工作。最简单的模式是一个 Operator 给 Kubernetes API 加了一个 endpoint,这个增加的 Encpoint 被称作用户定义资源(CR);同时增加一个控制面组件,该组件监控和维护着新资源。
Operator 包括了一组 controller,这些 controller 会不断检查 k8s 上的资源的情况。每个 controller 都会实现一个和解循环(reconciliation loop),在资源被创建、删除、更新的时候触发 controller 逻辑。每个 controller 都只负责某一种资源。
可用于创建 Kubernetes Operator 的开源项目有:
controller 是一个不断检查系统状态的无限循环,controller 会把资源状态不断向着“期望”的状态改变(就是和解循环)。
一旦设置了温度,这个设置的温度就是期望的状态。实际室温是当前状态。恒温器的作用是通过打开或关闭设备,使当前状态更接近理想状态。
Kubernetes 内有一些内置的 controller,运行在 master 节点的 controller manager 内。
你可以创建自定义的 operator,和上图中的 controller 一样,管理有状态和无状态的应用资源。
Operator-SDK 是 Operator 框架的脚手架组件,用于编写 kubernetes 原生应用。
Operator 框架是用高效、自动化、可扩展的方式管理叫做 Operator 的 kubernetes 原生应用的开源工具套件。
Operator 框架由下面几部分组成:
本文中只讲 Operator SDK。
Operator SDK 有三种类型:
后面有一节会专门讲基于 go 的 Operator 实现 CI/CD。
Operator 会利用一些库和 kubernetes api 交互。可用的库有 client-go 和 controller-runtime 等。既然要编程管理资源,这就需要对 k8s 有足够了解,比如 informer,listener,workQueue,runtime.Object 和 scheme 等。
这里推荐一篇分享,详细介绍了这些库。https://speakerdeck.com/govargo/inside-of-kubernetes-controller
以前有两个 github 开源项目,他们的功能和 star 数量和开发者数量都不相上下。现在这两个项目的开发者们决定共同努力为一个目标做贡献。以前两个工具创建都项目目录结构不同,现在项目目录结构已经一样了。
本文中所有的例子都是使用 Operator SDK1.0 版本生成的。
有时最好的学习办法是看看真实案例,找一些通用范式和最佳实践,最好能在自己的 operator 里用一用。
通用的模式显而易见,Operator 通常对资源(包括 deployment、job、secret、service、pod 等)进行 crud 操作,最后会把这些资源的状态变更掉。创建或编辑资源既可以程序化也可以是声明式的,还可以像 Manisfestival(https://github.com/manifestival/manifestival)这种做成第三方仓库的模式。所谓第三方仓库模式就是用非结构化数据一次更新多个运行时对象。
既然说到最佳实践,成功执行了创建、排队和重试的操作后,进行持续的资源状态检查是一个比较建议的模式。有这个概念后再看其他 operator 项目的代码就可以很容易地理解其中逻辑。
awesome-operators 项目(github.com/operator-framework/awesome-operators)中有很多例子,这里面的很多项目都是用本文中介绍的脚手架创建的。
Kubernetes 社区有一个集中放 Operator 的地方,叫 Operator Hub,你可以为你的 Operator 写一些开始引导和代码贡献说明后把它发布到这个仓库上。这个仓库和 Docker、Helm 的 Hub 类似。
既然 Operator 管理 kubernetes 资源的生命周期,为什么不直接用 Helm?
下面链接中的这篇分享中分享者提出了 Helm 是为了解决 Day 1 问题而 Operator 目标是做 Day 2 的操作。youtube.com/watch?v=N9QVJk6kjwg
Day 0/Day 1/Day 2 是什么意思?
Day 0:软件开发中的设计阶段。在这个阶段,我们要收集解决方案的所有需求。
Day 1:第一次在基础架构中部署应用时。
Day 2:管理应用的生命周期时。这时还要保证应用可用,需要考虑备份、重启、故障转移和回退等。
比如你通过 Helm chart 装了一个应用,这个应用包含 deployment、service、ingress,然后有人删掉了 service。这时应用将不可用,但是对于 helm 来说一切正常,因为 helm 负责管理资源到状态,直到我们变更了部署信息后 helm 才会知道 service 不存在。这种情况下需要用 Operator 才能满足需求。
刚刚的例子中,有人删掉了的 service 是某个 operator 监控的,这个 operator 会负责把 service 重新创建好,然后应用就恢复正常了。因此 Helm 并不能完全替代 Operator。
下面我们开始着手写一个 CI/CD operator,代码托管在 GitHub/Bitbucket,基于 git 仓库上的代码进行编译、构建镜像和把镜像推镜像仓库,最终部署到 k8s。
当然用 Operator 是要利用自动化的方式降低复杂度啦!
首先创建一个 k8s 集群用来部署和测试自定义 Operator。这里用基于 docker 容器的 KinD 集群部署工具快速部署 k8s 集群。
创建 KinD 集群这个任务非常明确。我创建了一个 master 节点、两个 worker 节点的集群,还部署了一个 docker registry 用来放我们自己的镜像。
下面的脚本可以快速启动 KinD 集群。运行脚本之前要装好 docker 和 kubectl。
#!/bin/sh
set -o errexit
# create registry container unless it already exists
reg_name='kind-registry'
reg_port='5000'
running="$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)"
if [ "${running}" != 'true' ]; then
docker run \
-d --restart=always -p "${reg_port}:5000" --name "${reg_name}" \
registry:2
fi
# create a cluster with the local registry enabled in containerd
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraMounts:
- hostPath: /Users/victorp/projects/kind
containerPath: /data
- role: worker
extraMounts:
- hostPath: /Users/victorp/projects/kind
containerPath: /data
- role: worker
extraMounts:
- hostPath: /Users/victorp/projects/kind
containerPath: /data
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."${reg_name}:${reg_port}"]
endpoint = ["http://${reg_name}:${reg_port}"]
EOF
执行脚本:
# Running the script to create a KinD cluster:
$ chmod +x kind-with-registry.sh
$ ./kind-with-registry.sh
$ kind get nodes
$ export KUBECONFIG=`kind get kubeconfig`
$ kubectl get all --all-namespaces
执行过上面的脚本后 k8s 集群就部署好了,没有任何 VM 依赖。此外还留了一个挂载点,今后有需要创建持久化存储可以挂到这里。
下面的图展示了 CICD operator 的架构:
a) operator 部署到 kubernetes 集群后,需要用 kubectl 人工或自动地 创建 CR。
b) operator controller 通过 API server 循环检查资源情况,触发 reconcile 方法。
c) CI 控制器会触发 reconcile 方法,这个方法会创建 Job 编译代码。
d) Job 包含了一个装了 git-sync 的 init container,这个 init container 把代码拉到一个 init container 和业务容器都挂载了卷上。
e) 装着 Kaniko 镜像的业务容器会在 init container 执行完任务后对代码进行编译,编译结果上传到 docker 仓库。
f) job 跑完后 CD 控制器就会被触发,它会负责创建一个 deployment。
g)这个 deployment 里会从 docker 仓库拉 e 步骤里打的镜像。
目前为止还没有给这个应用创建 service 和 ingress 的需求。
下面用 Operator-SDK 命令行工具生成脚手架代码,先把 Operator-SDK、Go 和 git 都装到环境里。安装步骤略。
# create the directory of your project
$ mkdir cicd-operator
$ cd cicd-operator
# This command will create the structure for your project
$ operator-sdk init --domain=cicd.com --repo=github.com/victorpaulo/cicd-operator
# This command will create the CRDs and Controller for your operator
$ operator-sdk create api --group=cicd --version=v1alpha1 --kind=CIBuild --resource=true --controller=true
执行了上面的步骤后 operator 的脚手架代码就创建完了,目前创建的代码可以监听自定义的资源定义,并创建一个被 operator 管理的 pod。下面要删掉自动生成的代码,写我们自己的逻辑。
步骤如下:
这个例子里有几个值要放进代码里,包括 github 仓库 endpoint 链接、SSH key 等,如下图:
# 下面的 make 命令会编译 docker 镜像、给这个镜像打包,镜像里包含 operator,最后把镜像推到远程仓库。
# Eg.: make docker-build docker-push IMG=<some-registry>/<project-name>:<tag>
$ make docker-build docker-push IMG=kind-registry:5000/cicd-operator:v0.0.1
# 通过创建 deployment 和 RBAC 资源,创建 k8s 中的 operator
$ make install
# E.g: make deploy IMG=<some-registry>/<project-name>:<tag>
$ make deploy IMG=kind-registry:5000/cicd-operator:v0.0.1
# operator 会基于 SSH 克隆 git 仓库,因此需要创建能链接到这些 Scm 仓库、包含 ssh key 的密钥。
$ ssh-keygen -t rsa -N "" -f mykey
# 用 web 控制台导入 mykey.pub 到 GitHub Bitbucket
#Github
$ ssh-keyscan github.com > /tmp/known_hosts
#Bitbucket
$ ssh-keyscan bitbucket.org > /tmp/known_hosts
# 创建密钥
$ kubectl create secret generic git-creds --from-file=ssh=mykey --from-file=known_hosts=/tmp/known_hosts
github
bitbucket
CI 控制器会调用 reconcile 方法,创建一个 job 做编译、推 docker 镜像。job 跑完后 CD 控制器会基于从这个仓库里拉到的镜像创建应用 deployment 资源
# Creating the CRs
# this CR points to a IBM App Connect (ACE) code
$ kubectl apply -f config/samples/cicd_v1alpha1_cibuild_ace.yaml
# this CR points to a Go application
$ kubectl apply -f config/samples/cicd_v1alpha1_cibuild_go.yaml
# this CR points to a Nodejs application
$ kubectl apply -f config/samples/cicd_v1alpha1_cibuild_nodejs.yaml
# this CR points to a Java application
$ kubectl apply -f config/samples/cicd_v1alpha1_cibuild_java.yaml
ace
java
上图中展示的代码是基于 github 的,可以自行修改配置让它基于 bitbucket 等平台。
准入控制器是在对象持久化之前用于对 Kubernetes API Server 的请求进行拦截的 http 回调,可以定义两种类型的准入 webhook,验证性质的准入 Webhook 和 修改性质的准入 Webhook。
验证性质的准入 Webhook 可以执行 OpenAPI 格式检查做不到的检查。例如要保证一个值在创建后不可变,或是做基于用户做 API server 的访问控制。
验证性质的准入 webhook 可以用来拒绝请求,增强自定义认证的控制。
修改性质的准入 Webhook 经常用在给创建的资源的值加默认值的场景里。
istio 就是通过修改性质的准入 Webhook 把 sidecar 代理插入用户 pod 的。
webhook 运行的环境是需要在 kubernetes 上装 Cert Manager,operator 的 dockerfile 中设置ENABLE_WEBHOOKS=true
打开控制器的 webhook 逻辑。
cert-manager 是 k8s 的一个插件,这个插件会自动管理、颁发 TLS 证书、定期检查证书有效性并且在合适的时机尝试更新证书。
$ kubectl apply — validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.16.0/cert-manager.yaml
上面的命令创建了 cert-manager 这个 namespace 下的 3 个 deployment,分别是 cert-manager, ca-injector 和 webhook。
希望这篇文章可以帮到 operator 的新入门选手。你会发现 k8s 上几乎所有东西都依赖 controller,理解了 controller 也能帮你更好理解很多 k8s 生态的项目。
operator 代码在github 上链接是:
https://github.com/victorpaulo/cicd-operator
- EOF -
Go 开发大全
参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。
关注后获取
回复 Go 获取6万star的Go资源库
分享、点赞和在看
支持我们分享更多好文章,谢谢!