前言
在“使用 helm 将复杂应用打包并部署到 k8s 集群中”这篇文章中我们用 helm
将应用打包为图表,而后通过其简化了部署流程,然而,helm对于基础安装(Basic Install)在行,虽支持无状态应用的无缝升级(Seamless Upgrades):替换镜像版本以利用 K8S RC 控制器的滚动升级特性,但其对于有状态应用却无能为力。
如若升级 MySQL 数据库,其是有状态的,故没法简单的替换程序版本来完成数据库升级,需执行一系列复杂的操作:使用 mysqldump
导出导入到新版本数据库,或采用原地升级方式执行脚本以更新数据库字典信息,这些复杂的逻辑对于 helm
来说无能为力,鉴于此,我们可将复杂的逻辑操作包装到 operator 中。
Operator提供了如上 5 个维度能力,其提供了”第一天 “应用安装能力,也支持” 第二天 “应用升级维护、备份等全生命周期、深度分析、自巡航等特性。利用operator-sdk 我们可将 helm 图表制成 operator,使用ansible 制作 operator,亦或者用go 语言开发operator。
为 Helm 图表创建Operator
当将 helm 图表制作为 operator 后,其并没有具备超出 helm 图表的能力,换言之,若 helm 图表支持基础安装与无缝升级,那么制作成 operator 后不会多出备份~~~~ 等全生命周期等特性,但 operator 具有一些额外的能力。
首先安装 SDK 客户端,可参考文档 Install the Operator SDK CLI。
$ RELEASE_VERSION=v0.18.1
$ curl -LO https://github.com/operator-framework/operator-sdk/releases/download/${RELEASE_VERSION}/operator-sdk-${RELEASE_VERSION}-x86_64-linux-gnu
$ chmod +x operator-sdk-${RELEASE_VERSION}-x86_64-linux-gnu && \
sudo mkdir -p /usr/local/bin/ && \
sudo cp operator-sdk-${RELEASE_VERSION}-x86_64-linux-gnu \
/usr/local/bin/operator-sdk && \
rm operator-sdk-${RELEASE_VERSION}-x86_64-linux-gnu
$ operator-sdk version
operator-sdk version: "v0.18.1"
若读者按照“使用 helm 将复杂应用打包并部署到 k8s 集群中”这篇文章配置并创建了 helm 图表与仓库,则执行如下命令对图表 hello 创建operator:
$ operator-sdk new hello-operator \
--api-version=charts.helm.k8s.io/v1alpha1 \
--kind=Hello --type=helm \
--helm-chart-repo=http://chartmuseum.app.zyl.io \
--helm-chart=hello
否则可将图表克隆到本地,而后执行 operator-sdk new
命令以基于本地目录创建operator,如下所示。
$ git clone https://github.com/zylpsrs/helm-example.git
$ operator-sdk new hello-operator \
--api-version=charts.helm.k8s.io/v1alpha1 \
--kind=Hello --type=helm \
--helm-chart=helm-example/helm/hello
上述命令生成了 hello-operator 目录,其结构如下所示:
$ tree hello-operator/
hello-operator/
├── build # 构建镜像目录,含 Dockerfile 文件
│ └── Dockerfile
├── deploy # operator 部署目录
│ ├── crds
│ │ ├── hello.helm.k8s.io_hellos_crd.yaml
│ │ └── hello.helm.k8s.io_v1alpha1_hello_cr.yaml
│ ├── operator.yaml # operator 部署清单
│ ├── role_binding.yaml # 角色绑定,及将 role 与 sa 绑定
│ ├── role.yaml # 角色文件
│ └── service_account.yaml # sa
├── helm-charts # helm charts 目录
│ └── hello # hello chart
└── watches.yaml # operator 需监视哪些资源
operator监视哪些资源由 watches.yaml 文件所定义,其内容是执行 operator-sdk new
命令时传递的参数。对于本例,其在 API:charts.helm.k8s.io/v1alpha1 上监视 Hello 类型的资源,对于此类型的请求,其执行的 helm 图表为helm-charts/hello。
---
- group: charts.helm.k8s.io
version: v1alpha1
kind: Hello
chart: helm-charts/hello
因为基于现有 helm 图表构建 operator,我们无需调整helm-charts 目录下的图表,执行如下命令为 operator 生成镜像,而后推送到前文搭建的镜像仓库中。如下所示:
$ operator-sdk build registry.zyl.io:5000/hello-operator \
--image-builder podman
$ podman push registry.zyl.io:5000/hello-operator
镜像构建成功后,执行如下命令以实际镜像名替换 deploy/operator.yaml 文件中的 REPLACE_IMAGE 字符串。
$ cat deploy/operator.yaml
...
containers:
- name: hello-operator
# Replace this with the built image name
image: REPLACE_IMAGE
...
$ perl -i -ne 's#REPLACE_IMAGE#registry.zyl.io:5000/hello-operator#;print' \
deploy/operator.yaml
将 Operator 部署到集群中
K8S集群允许通过自定义资源定义(CRD)向其注册我们的 API,对于operator 控制器来说,其监听在特定的 API 上并响应请求,如下为 SDK 为此 operator 生成的 CRD 定义:
$ cat deploy/crds/charts.helm.k8s.io_hellos_crd.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition # 使用此类型来注册自定义资源
metadata:
name: hellos.charts.helm.k8s.io # 自定义资源名称
spec:
group: charts.helm.k8s.io # 自定义资源 API 组
names:
kind: Hello # 自定义资源类型
listKind: HelloList
plural: hellos
singular: hello
scope: Namespaced
...
本例我们选择以 *.k8s.io 作为 api 组,但其为 k8s 保留组,若向集群注册时将报错,解决方法则是参照所提示的 github pr 添加对应的注释。
# crd 没有命名空间概念,无需通过 -n <namespace> 指定命名空间
$ kubectl create -f deploy/crds/charts.helm.k8s.io_hellos_crd.yaml
The CustomResourceDefinition "hellos.charts.helm.k8s.io" is invalid: metadata.annotations[api-approved.kubernetes.io]: Required value: protected groups must have approval annotation "api-approved.kubernetes.io", see https://github.com/kubernetes/enhancements/pull/1111
# 编辑 deploy/crds/charts.helm.k8s.io_hellos_crd.yaml 文件添加如下注释
$ vi deploy/crds/charts.helm.k8s.io_hellos_crd.yaml
...
metadata:
annotations:
"api-approved.kubernetes.io": "https://github.com/kubernetes/kubernetes/pull/78458"
...
# 然后执行如下命令注册:$ kubectl create -f deploy/crds/charts.helm.k8s.io_hellos_crd.yaml
customresourcedefinition.apiextensions.k8s.io/hellos.charts.helm.k8s.io created
$ kubectl get crd | grep hello
hellos.charts.helm.k8s.io 2020-06-19T10:24:13Z
如下我们将 operator 部署到 demo 命名空间中,执行命令应用 deploy 目录下的清单文件。
$ kubectl -n demo apply -f deploy/
deployment.apps/hello-operator unchanged
role.rbac.authorization.k8s.io/hello-operator configured
rolebinding.rbac.authorization.k8s.io/hello-operator unchanged
serviceaccount/hello-operator unchanged
上面的命令将在 demo 命名空间中生成如下对象:
$ kubectl get pod,svc,deployment
NAME READY STATUS RESTARTS AGE
pod/hello-operator-756bb58dc5-4g88j 1/1 Running 1 152m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/hello-operator-metrics ClusterIP 10.106.213.245 <none> 8383/TCP,8686/TCP
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello-operator 1/1 1 1 152m
通过 CR 向Operator请求服务
SDK默认在 deploy 目录生成了一个自定义资源(CR)实例请求文件,其内容如下所示,其显示默认参数取至 hello 图表的 values.yaml 文件,也就是说,我们可配置的参数同 hello 图表一样。
$ cat deploy/crds/charts.helm.k8s.io_v1alpha1_hello_cr.yaml
apiVersion: charts.helm.k8s.io/v1alpha1
kind: Hello
metadata:
name: example-hello
spec:
# Default values copied from <project_dir>/helm-charts/hello/values.yaml
affinity: {}
autoscaling:
...
执行如下命令通过 CR 请求一名为 example 的实例,operator将调用 helm 图表并响应此请求后,命名空间 demo 中可见如下对象:
$ kubectl -n demo apply -f - <<'EOF'
apiVersion: charts.helm.k8s.io/v1alpha1
kind: Hello
metadata:
name: example
spec:
greeter:
replicaCount: 1
EOF
$ kubectl -n demo get pod
NAME READY STATUS RESTARTS AGE
example-greeter-76745b98b8-nswcq 1/1 Running 0 3m28s
example-hello-7bc5d74c5b-5tstd 1/1 Running 0 3m28s
hello-operator-756bb58dc5-4g88j 1/1 Running 1 173m
$ kubectl -n demo get hello
NAME AGE
example 5m59s
我们再执行如下命令更新 CR,此时请求为hello 应用配置 ingress,查看operator 日志可发现有如下报错,其显示权限问题。
$ kubectl -n demo apply -f - <<'EOF'
apiVersion: charts.helm.k8s.io/v1alpha1
kind: Hello
metadata:
name: example
spec:
greeter:
replicaCount: 1
ingress:
enabled: true
EOF
$ kubectl -n demo logs hello-operator-756bb58dc5-4g88j
Unable to continue with install: could not get information about the resource: ingresses.networking.k8s.io "example-hello" is forbidden: User "system:serviceaccount:demo:hello-operator" cannot get resource "ingresses" in API group "networking.k8s.io" in the namespace "demo""
为了解决此问题,我们可更新 roles.yaml 文件添加所需的权限,而后删除 operator 当前 pod,待新pod 启动后,其拥有正确的权限将能生成ingress,如下所示:
$ cat > deploy/role.yaml <<'EOF'
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- '*'
EOF
$ kubectl -n demo apply -f deploy/role.yaml
$ kubectl -n demo delete pod hello-operator-756bb58dc5-4g88j
$ kubectl -n demo get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
example-hello <none> hello.app.zyl.io 192.168.120.6 80 41s
执行如下命令删除 operator 所创建的 deploy/ingress 对象,一段时间后再次查看,可发现对象被重建了,原因是 operator 监视所创建的 CR 对象,其根据 CR 所求情的配置,确保所管理的后端对象如 deploy/ingress 一致性。
$ kubectl -n demo delete deploy,ingress -l app.kubernetes.io/instance=example
deployment.apps "example-greeter" deleted
deployment.apps "example-hello" deleted
ingress.extensions "example-hello" deleted
$ kubectl -n demo get deploy,ingress -l app.kubernetes.io/instance=example
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/example-greeter 1/1 1 1 4m30s
deployment.apps/example-hello 1/1 1 1 4m30s
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.extensions/example-hello <none> hello.app.zyl.io 192.168.120.6 80 4m30s
同上面一样,operator如同管家一样确保 CR 所定义镜像数,故我们无法手动调整此后端镜像数。
$ kubectl -n demo scale deploy example-greeter --replicas=5
deployment.apps/example-greeter scaled
$ kubectl -n demo get deploy example-greeter
NAME READY UP-TO-DATE AVAILABLE AGE
example-greeter 1/1 1 1 7m23s
结束语
本章我们通过 operator-sdk 将helm图表制作成 operator,将其安装到集群后,后续无需部署helm 客户端工具,通过标准的集群命令行工具 kubectl
即可部署应用;helm无法确保配置一致性,当通过 helm 部署应用后,若我们调整了 deploy 等配置,其将造成现有运行中的配置与 helm 保存的版本不一致,而 operator 确能很好的解决此问题。
本章我们将 helm 打包成 operator,但对其能力却提升不是很大,但若通过ansible 来实施 operator,则其支持第一章中图示的所有5 个特性,而后续本人将予以讲解。