做微商有哪些网站可以免费宣传,网站怎么做用什么软件,wordpress 置顶图标,运营管理八个模块简介#xff1a;本文是一篇 kubernetes#xff08;下文用 k8s 代替#xff09;的入门文章#xff0c;将会涉及 k8s 的架构、集群搭建、一个 Redis 的例子#xff0c;以及如何使用 operator-sdk 开发 operator 的教程。在文章过程中#xff0c;会穿插引出 Pod、Deployment…简介本文是一篇 kubernetes下文用 k8s 代替的入门文章将会涉及 k8s 的架构、集群搭建、一个 Redis 的例子以及如何使用 operator-sdk 开发 operator 的教程。在文章过程中会穿插引出 Pod、Deployment、StatefulSet 等 k8s 的概念这些概念通过例子引出来更容易理解和实践。 作者 | 凡澈 来源 | 阿里技术公众号
前言
本文是一篇 kubernetes下文用 k8s 代替的入门文章将会涉及 k8s 的架构、集群搭建、一个 Redis 的例子以及如何使用 operator-sdk 开发 operator 的教程。在文章过程中会穿插引出 Pod、Deployment、StatefulSet 等 k8s 的概念这些概念通过例子引出来更容易理解和实践。文章参考了很多博客以及资料放在最后参考资料部分。
一 k8s架构 我们看下 k8s 集群的架构从左到右分为两部分第一部分是 Master 节点也就是图中的 Control Plane第二部分是 Node 节点。
Master 节点一般包括四个组件apiserver、scheduler、controller-manager、etcd他们分别的作用是什么
Apiserver上知天文下知地理上连其余组件下接ETCD提供各类 api 处理、鉴权和 Node 上的 kubelet 通信等只有 apiserver 会连接 ETCD。Controller-manager控制各类 controller通过控制器模式致力于将当前状态转变为期望的状态。Scheduler调度打分分配资源。Etcd整个集群的数据库也可以不部署在 Master 节点单独搭建。
Node 节点一般也包括三个组件dockerkube-proxykubelet
Docker具体跑应用的载体。Kube-proxy主要负责网络的打通早期利用 iptables现在使用 ipvs技术。Kubeletagent负责管理容器的生命周期。
总结一下就是 k8s 集群是一个由两部分组件 Master 和 Node 节点组成的架构其中 Master 节点是整个集群的大脑Node 节点来运行 Master 节点调度的应用我们后续会以一个具体的调度例子来解释这些组件的交互过程。
二 搭建 k8s 集群
上面说完了 k8s 集群中有哪些组件接下来我们先看下如何搭建一个 k8s 集群有以下几种方法参考文末链接
当我们安装了 Docker Desktop APP 之后勾选 k8s 支持就能搭建起来。使用 MiniKube 来搭建社区提供的一键安装脚本。直接在云平台购买例如阿里云 ack。使用 kubeadmin这是 k8s 社区推荐的可以部署生产级别 k8s 的工具。使用二进制下载各组件安装此教程需要注意下载的各组件版本要和博客中保持一致就可以成功。
本文后面的例子均采用本地 Docker Desktop APP 搭建的 k8s。
➜ ~ kubectl version
Client Version: version.Info{Major:1, Minor:21, GitVersion:v1.21.4, GitCommit:3cce4a82b44f032d0cd1a1790e6d2f5a55d20aae, GitTreeState:clean, BuildDate:2021-08-11T18:16:05Z, GoVersion:go1.16.7, Compiler:gc, Platform:darwin/amd64}
Server Version: version.Info{Major:1, Minor:21, GitVersion:v1.21.4, GitCommit:3cce4a82b44f032d0cd1a1790e6d2f5a55d20aae, GitTreeState:clean, BuildDate:2021-08-11T18:10:22Z, GoVersion:go1.16.7, Compiler:gc, Platform:linux/amd64}
三 从需求出发
下面我们从一个实际的需求出发来看看如何在 k8s 上部署 Redis 服务。
部署一个Redis服务支持高可用提供统一的 EndPoint 访问地址
1 部署单机版
如果我们想在 k8s 上部署一个单机版本 Redis我们执行下面的命令即可
➜ ~ kubectl run redis --imageredis
pod/redis created
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 5s
可以用 kubectl exec 来进入到 Pod 内部连接 Redis 执行命令
➜ ~ kubectl exec -it redis -- bash
rootredis:/data# redis-cli
127.0.0.1:6379 ping
PONG
127.0.0.1:6379
那么 Pod 和 Redis 是什么关系呢这里的 Redis 其实是一个 Docker 进程启动的服务但是在 k8s 中它叫 Pod。
2 Pod 与 Deployment
我们来讲下第一个 k8s 的概念 PodPod 是 k8s 中最小的调度单元一个 Pod 中可以包含多个 Docker这些 Docker 都会被调度到同一台 Node 上这些 Docker 共享 NetWork Namespace并且可以声明共享同一个 Volume 来共享磁盘空间。
这样的好处是什么呢其实在真实的世界中很多应用是有部署在同一台机器的需求的比如 Redis 日志采集插件要采集日志肯定需要和 Redis 部署在同一台机器上才能读到 Redis 的日志我们前面讲述背景的时候说到了 Docker Swarm 存在一些问题其中之一就是它只是基于 Docker 调度虽然也可以设置亲和度让两台 Docker 调度在同一个机器上但是因为不能一起调度所以会存在一个Docker 提前被调度到了一个资源少的机器上从而导致第二个 Docker 调度失败。
例如我们一共有 2 台容器A和B分别为 Redis 和 日志采集组件各需要 2g 内存现在有两台 nodenode1 3.5 内存node2 4g内存在 Docker Swarm 的调度策略下先调度 Redis有可能被调度到了 node1 上接下来再来调度日志采集组件发现 node1 只有 1.5g 内存了调度失败。但是在 k8s 中调度是按照 pod 来调度的两个组件在一个 pod 中调度就不会考虑 node1。
虽然 Pod 已经可以运行 Redis 服务了但是他不具备高可用性因为一旦一个 Pod 与一个节点Node绑定除非这个绑定发生了变化pod.spec.node 字段被修改否则它永远都不会离开这个节点这也就意味着如果这个宿主机宕机了这个 Pod 也不会主动迁移到其他节点上去。为了让服务可以一直在需要使用 Deployment 这样的控制器。
➜ ~ kubectl create deployment redis-deployment --imageredis
deployment.apps/redis-deployment created
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 32m
redis-deployment-866c4c6cf9-8z8k5 1/1 Running 0 8s
➜ ~
redis-deployment-866c4c6cf9-8z8k5就是刚才通过 kubectl create 创建的新的 Deployment为了验证高可用我们把用 kubectl delete pod 把 redis 和 redis-deployment-866c4c6cf9-8z8k5都删掉看会发生什么。
➜ ~ kubectl delete pod redis redis-deployment-866c4c6cf9-8z8k5
pod redis deleted
pod redis-deployment-866c4c6cf9-8z8k5 deleted
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-deployment-866c4c6cf9-zskkb 1/1 Running 0 10s
➜ ~
redis已经消失了但是redis-deployment-866c4c6cf9-zskkb换了个名字又出现了
Deployment 可以定义多副本个 Pod从而为应用提供迁移能力如果单纯使用 Pod实际上当应用被调度到某台机器之后机器宕机应用也无法自动迁移但是使用 Deployment则会调用 ReplicaSet一种控制器 来保证当前集群中的应用副本数和指定的一致。
3 k8s 使用 yaml 来描述命令
k8s 中可以使用 kubectl 来创建简单的服务但是还有一种方式是对应创建复杂的服务的就是提供 yaml 文件。例如上面的创建 Pod 的命令我们可以用下面的 yaml 文件替换执行 kubectl create 之后可以看到 redis Pod 又被创建了出来。
➜ ~ cat pod.yaml
apiVersion: v1
kind: Pod
metadata:name: redis
spec:containers:- name: redisimage: redis
➜ ~ kubectl create -f pod.yaml
pod/redis created
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 6s
redis-deployment-866c4c6cf9-zskkb 1/1 Running 0 6m32s
四 k8s 组件调用流程 下面我们看下kubectl create deployment redis-deployment --imageredis下发之后k8s 集群做了什么。
首先 controller-manager, scheduler, kubelet 都会和 apiserver 开始进行 List-Watch 模型List 是拿到当前的状态Watch 是拿到期望状态然后 k8s 集群会致力于将当前状态达到达期望状态。kubectl 下发命令到 apiserver鉴权处理之后将创建信息存入 etcdDeployment 的实现是使用 ReplicaSet 控制器当 controller-manager 提前拿到当前的状态pod0接着接收到期望状态需要创建 ReplicaSetpod1就会开始创建 Pod。然后 scheduler 会进行调度确认 Pod 被创建在哪一台 Node 上。之后 Node 上的 kubelet 真正拉起一个 docker。
这些步骤中apiserver 的作用是不言而喻的所以说上接其余组件下连 ETCD但是 apiserver 是可以横向扩容的然后通过负载均衡倒是 ETCD 在 k8s 架构中成了瓶颈。
最开始看这架构的时候会想着为啥 apiserver, scheduler, controller-manager 不合成一个组件其实在 Google Borg 中borgmaster 就是这样的功能也是这些功能但是合在了一起最后他们也发现集群大了之后 borgmaster 会有些性能上的问题包括 kubelet 的心跳就是很大一块所以 k8s 从一开始开源设计中有三个组件也是更好维护代码吧。
五 部署主从版本
上面我们已经部署了 Redis 的单机版并通过 Deployment 实现了服务持续运行接下来来看下主从版本如何部署其中一个比较困难的地方就是如何确定主从的同步关系。
1 StatefulSet
k8s 为有状态应用设计了 StatefulSet 这种控制器它主要通过下面两个特性来服务有状态应用
拓扑状态实例的创建顺序和编号是顺序的会按照 name-index 来编号比如 redis-0redis-1 等。存储状态可以通过声明使用外部存储例如云盘等将数据保存从而 Pod 重启重新调度等都能读到云盘中的数据。
下面我们看下 Redis 的 StatefulSet 的例子
apiVersion: apps/v1
kind: StatefulSet # 类型为 statefulset
metadata:name: redis-sfs # app 名称
spec:serviceName: redis-sfs # 这里的 service 下面解释replicas: 2 # 定义了两个副本selector:matchLabels:app: redis-sfstemplate:metadata:labels:app: redis-sfsspec:containers:- name: redis-sfs image: redis # 镜像版本command:- bash- -c- |set -exordinalhostname | awk -F - {print $NF} # 使用 hostname 获取序列if [[ $ordinal -eq 0 ]]; then # 如果是 0作为主echo /tmp/redis.confelseecho slaveof redis-sfs-0.redis-sfs 6379 /tmp/redis.conf # 如果是 1作为备firedis-server /tmp/redis.conf
接着启动这个 StatefulSet发现出现了 redis-sfs-0 和 redis-sfs-1 两个 pod他们正式按照 name-index 的规则来编号的
➜ ~ kubectl create -f server.yaml
statefulset.apps/redis-sfs created
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 65m
redis-deployment-866c4c6cf9-zskkb 1/1 Running 0 71m
redis-sfs-0 1/1 Running 0 33s # 按照
redis-sfs-1 1/1 Running 0 28s
接着我们继续看下主从关系生效了没查看 redis-sfs-1 的日志却发现
➜ ~ kubectl logs -f redis-sfs-1
1:S 05 Nov 2021 08:02:44.243 * Connecting to MASTER redis-sfs-0.redis-sfs:6379
1:S 05 Nov 2021 08:02:50.287 # Unable to connect to MASTER: Resource temporarily unavailable
...
2 Headless Service
似乎 redis-sfs-1 不认识 redis-sfs-0原因就在于我们还没有让它们互相认识这个互相认识需要使用 k8s 一个服务叫 Headless ServiceService 是 k8s 项目中用来将一组 Pod 暴露给外界访问的一种机制。比如一个 Deployment 有 3 个 Pod那么我就可以定义一个 Service。然后用户只要能访问到这个 Service它就能访问到某个具体的 Pod一般有两种方式
VIP访问 VIP 随机返回一个后端的 PodDNS通过 DNS 解析到后端某个 Pod 上
Headless Service 就是通过 DNS 的方式可以解析到某个 Pod 的地址这个 DNS 地址的规则就是 下面我们创建集群对应的 Headless Service
apiVersion: v1
kind: Service
metadata:name: redis-sfslabels:app: redis-sfs
spec:clusterIP: None # 这里的 None 就是 Headless 的意思表示会主动由 k8s 分配ports:- port: 6379name: redis-sfsselector:app: redis-sfs
再次查看发现 redis-sfs-1 已经主备同步成功了因为创建 Headless Service 之后redis-sfs-0.redis-sfs.default.svc.cluster.local 在集群中就是唯一可访问的了。
➜ ~ kubectl create -f service.yaml
service/redis-sfs created
➜ ~ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 none 443/TCP 24d
redis-sfs ClusterIP None none 6379/TCP 33s
➜ ~ kubectl logs -f redis-sfs-1
...
1:S 05 Nov 2021 08:23:31.341 * Connecting to MASTER redis-sfs-0.redis-sfs:6379
1:S 05 Nov 2021 08:23:31.345 * MASTER - REPLICA sync started
1:S 05 Nov 2021 08:23:31.345 * Non blocking connect for SYNC fired the event.
1:S 05 Nov 2021 08:23:31.346 * Master replied to PING, replication can continue...
1:S 05 Nov 2021 08:23:31.346 * Partial resynchronization not possible (no cached master)
1:S 05 Nov 2021 08:23:31.348 * Full resync from master: 29d1c03da6ee2af173b8dffbb85b6ad504ccc28f:0
1:S 05 Nov 2021 08:23:31.425 * MASTER - REPLICA sync: receiving 175 bytes from master to disk
1:S 05 Nov 2021 08:23:31.426 * MASTER - REPLICA sync: Flushing old data
1:S 05 Nov 2021 08:23:31.426 * MASTER - REPLICA sync: Loading DB in memory
1:S 05 Nov 2021 08:23:31.431 * Loading RDB produced by version 6.2.6
1:S 05 Nov 2021 08:23:31.431 * RDB age 0 seconds
1:S 05 Nov 2021 08:23:31.431 * RDB memory usage when created 1.83 Mb
1:S 05 Nov 2021 08:23:31.431 # Done loading RDB, keys loaded: 0, keys expired: 0.
1:S 05 Nov 2021 08:23:31.431 * MASTER - REPLICA sync: Finished with success
^C
➜ ~ kubectl exec -it redis-sfs-1 -- bash
rootredis-sfs-1:/data# redis-cli -h redis-sfs-0.redis-sfs.default.svc.cluster.local
redis-sfs-0.redis-sfs.default.svc.cluster.local:6379 ping
PONG
redis-sfs-0.redis-sfs.default.svc.cluster.local:6379
此时无论我们删除哪个 Pod它都会按照原来的名称被拉起来从而可以保证准备关系这个例子只是一个 StatefulSet 的示例分析下来可以发现虽然它可以维护主备关系但是当主挂了的时候此时备无法切换上来因为没有组件可以帮我们做这个切换操作一个办法是用 Redis Sentinel可以参考这个项目的配置k8s-redis-ha-master如果你的 k8s 较新需要 merge 此 PR.
六 Operator
虽然有了 StatefulSet但是这只能对基础版有用如果想自己定制更加复杂的操作k8s 的解法是 operator简而言之operator 就是定制自己 k8s 对象及对象所对应操作的解法。
那什么是对象呢一个 Redis 集群一个 etcd 集群zk 集群都可以是一个对象现实中我们想描述什么就来定义什么实际上我们定一个是k8s yaml 中的 kind之前的例子中我们使用过 PodDeploymentStatefulSet它们是 k8s 默认实现现在如果要定义自己的对象有两个流程
定义对象比如你的集群默认有几个节点都有啥组件定义对象触发的操作当创建对象时候要做什么流程HA 时候要做什么流程等
operator 的方式是基于编程实现的可以用多种语言用的最多的就是 go 语言通常大家会借助 operator-sdk 来完成因为有很多代码会自动生成。相当于 operator 会生成框架然后我们实现对应的业务逻辑。
1 准备工作
安装好 go 环境安装 operator-sdk
2 初始化项目
然后我们按照官网的 sdk 例子来一步一步实现一个 memcached 的 operator这里也可以换成 Redis但是为了保证和官网一致我们就按照官网来创建 memcached operator。
➜ ~ cd $GOPATH/src
➜ src mkdir memcached-operator
➜ src cd memcached-operator
➜ memcached-operator operator-sdk init --domain yangbodong22011 --repo github.com/yangbodong22011/memcached-operator --skip-go-version-check // 这里需要注意 domain 最好是和你在 https://hub.docker.com 的注册名称相同因为后续会发布 docker 镜像
Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtimev0.9.2
Update dependencies:
$ go mod tidy
Next: define a resource with:
$ operator-sdk create api
3 创建 API 和 Controller
➜ memcached-operator operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource --controller
Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
api/v1alpha1/memcached_types.go
controllers/memcached_controller.go
Update dependencies:
$ go mod tidy
Running make:
$ make generate
go: creating new go.mod: module tmp
Downloading sigs.k8s.io/controller-tools/cmd/controller-genv0.6.1
go get: installing executables with go get in module mode is deprecated.To adjust and download dependencies of the current module, use go get -d.To install using requirements of the current module, use go install.To install ignoring the current module, use go install with a version,like go install example.com/cmdlatest.For more information, see https://golang.org/doc/go-get-install-deprecationor run go help get or go help install.
...
go get: added sigs.k8s.io/yaml v1.2.0
/Users/yangbodong/go/src/memcached-operator/bin/controller-gen object:headerFilehack/boilerplate.go.txt paths./...
➜ memcached-operator
上面的步骤实际上生成了一个 operator 的框架接下来我们首先来定义 memcached 集群都包括啥将默认实现修改为 Size表示一个 Memcached 集群中 Memcached 的数量最后调用 make generate 和 make manifests 来自动生成 deepcopy 和 CRD 资源。
➜ memcached-operator vim api/v1alpha1/memcached_types.go // 修改下面 Memcached 集群的定义
// MemcachedSpec defines the desired state of Memcached
type MemcachedSpec struct {//kubebuilder:validation:Minimum0// Size is the size of the memcached deploymentSize int32 json:size
}// MemcachedStatus defines the observed state of Memcached
type MemcachedStatus struct {// Nodes are the names of the memcached podsNodes []string json:nodes
}➜ memcached-operator make generate
/Users/yangbodong/go/src/memcached-operator/bin/controller-gen object:headerFilehack/boilerplate.go.txt paths./...
➜ memcached-operator make manifests
/Users/yangbodong/go/src/memcached-operator/bin/controller-gen crd:trivialVersionstrue,preserveUnknownFieldsfalse rbac:roleNamemanager-role webhook paths./... output:crd:artifacts:configconfig/crd/bases
➜ memcached-operator
4 实现 Controller
接下来是第二步定义当创建一个 Memcached 集群时候具体要干啥。
➜ memcached-operator vim controllers/memcached_controller.gohttps://raw.githubusercontent.com/operator-framework/operator-sdk/latest/testdata/go/v3/memcached-operator/controllers/memcached_controller.go //
将 example 换成 yangbodong22011注意// 注释中的也要换实际不是注释而是一种格式➜ memcached-operator go mod tidy; make manifests
/Users/yangbodong/go/src/memcached-operator/bin/controller-gen crd:trivialVersionstrue,preserveUnknownFieldsfalse rbac:roleNamemanager-role webhook paths./... output:crd:artifacts:configconfig/crd/bases
5 发布 operator 镜像
➜ memcached-operator vim Makefile
将 -IMG ? controller:latest 改为 IMG ? $(IMAGE_TAG_BASE):$(VERSION)➜ memcached-operator docker login // 提前登录下 docker
Login with your Docker ID to push and pull images from Docker Hub. If you dont have a Docker ID, head over to https://hub.docker.com to create one.
Username: yangbodong22011
Password:
WARNING! Your password will be stored unencrypted in /Users/yangbodong/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-storeLogin Succeeded
➜ memcached-operator sudo make docker-build docker-push
...writing image sha256:a7313209e321c84368c5cb7ec820fffcec2d6fcb510219d2b41e3b92a2d5545a 0.0s naming to docker.io/yangbodong22011/memcached-operator:0.0.1 0.0s
fac03a24e25a: Pushed
6d75f23be3dd: Pushed
0.0.1: digest: sha256:242380214f997d98186df8acb9c13db12f61e8d0f921ed507d7087ca4b67ce59 size: 739
6 修改镜像和部署
➜ memcached-operator vim config/manager/manager.yaml
image: controller:latest 修改为 yangbodong22011/memcached-operator:0.0.1➜ memcached-operator vim config/default/manager_auth_proxy_patch.yaml
因为国内访问不了 gcr.io
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 修改为 kubesphere/kube-rbac-proxy:v0.8.0 ➜ memcached-operator make deploy
...
configmap/memcached-operator-manager-config created
service/memcached-operator-controller-manager-metrics-service created
deployment.apps/memcached-operator-controller-manager created➜ memcached-operator kubectl get deployment -n memcached-operator-system // ready 说明 operator 已经部署了
NAME READY UP-TO-DATE AVAILABLE AGE
memcached-operator-controller-manager 1/1 1 1 31s
➜ memcached-operator
7 创建 Memcached 集群
➜ memcached-operator cat config/samples/cache_v1alpha1_memcached.yaml
apiVersion: cache.yangbodong22011/v1alpha1
kind: Memcached
metadata:name: memcached-sample
spec:size: 1
➜ memcached-operator kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
memcached.cache.yangbodong22011/memcached-sample created
➜ memcached-operator kubectl get pods
NAME READY STATUS RESTARTS AGE
memcached-sample-6c765df685-xhhjc 1/1 Running 0 104s
redis 1/1 Running 0 177m
redis-deployment-866c4c6cf9-zskkb 1/1 Running 0 3h4m
redis-sfs-0 1/1 Running 0 112m
redis-sfs-1 1/1 Running 0 112m
➜ memcached-operator
可以通过 kubectl logs 来查看 operator 的日志
➜ ~ kubectl logs -f deployment/memcached-operator-controller-manager -n memcached-operator-system
2021-11-05T09:50:46.042Z INFO controller-runtime.manager.controller.memcached Creating a new Deployment {reconciler group: cache.yangbodong22011, reconciler kind: Memcached, name: memcached-sample, namespace: default, Deployment.Namespace: default, Deployment.Name: memcached-sample}
至此我们的 operator-sdk 的任务暂时告一段落。
七 总结
本文介绍了 k8s 的架构各组件的功能以及通过一个循序渐进的 Redis 例子介绍了 k8s 中 Pod, Deployment, StatefulSet 的概念并通过 operator-sdk 演示了一个完整的 operator制作的例子。
八 参考资料
[1] 《深入剖析Kubernetes》张磊CNCF TOC 成员at 阿里巴巴。 [2] 《Kubernetes 权威指南》第五版 [3] 《Large-scale cluster management at Google with Borg》https://research.google/pubs/pub43438/ [4] 什么是Kubernetes (Kube) ? 一文了解K8s是什么_红帽 [5] How Docker broke in half | InfoWorld [6] CNCF Cloud Native Interactive Landscape [7] Deploy on Kubernetes | Docker Documentation [8] minikube start | minikube [9] 容器服务ACK_容器服务Kubernetes版_容器_云原生应用平台-阿里云 [10] GitHub - kubernetes/kubeadm: Aggregator for issues filed against kubeadm [11] Kubernetes 深入学习一 —— 入门和集群安装部署 - bojiangzhou - 博客园 [12] GitHub - tarosky/k8s-redis-ha: Kubernetes Redis with High Availability [13] Installation | Operator SDK
原文链接 本文为阿里云原创内容未经允许不得转载。