万江区网站仿做,wordpress导出数据库结构,自己做商城网站能卖服装吗,ps扩展插件网站Operator 介绍 Operator 可以看成是 CRD Controller 的一种组合资源。Kubernetes 中的基础资源类型有 Pod、Service、Job、Deployment 等表达能力有限#xff0c;CRD 则提供了创建新的资源类型方式#xff1b;Controller 监听 CRD 对象实例的增、删、改事件#xff0c;然后…Operator 介绍 Operator 可以看成是 CRD Controller 的一种组合资源。Kubernetes 中的基础资源类型有 Pod、Service、Job、Deployment 等表达能力有限CRD 则提供了创建新的资源类型方式Controller 监听 CRD 对象实例的增、删、改事件然后执行相应的业务逻辑。
为解决开发难得问题社区推出了简单易用的 Operator 框架比较主流的是 kubebuilder 和 Operator Framework这两个框架的使用基本上差别不大我们可以根据自己习惯选择一个即可我们这里先使用 Operator Framework 来给大家简要说明下 Operator 的开发。
Operator Framework Operator Framework 是 CoreOS 开源的一个用于快速开发 Operator 的工具包该框架包含两个主要的部分
Operator SDK: 无需了解复杂的 Kubernetes API 特性即可让你根据你自己的专业知识构建一个 Operator 应用。 Operator Lifecycle ManagerOLM: 帮助你安装、更新和管理跨集群的运行中的所有 Operator以及他们的相关服务 Operator SDK 提供了用于开发 Go、Ansible 以及 Helm 中的 Operator 的工作流下面的工作流适用于 Golang 的 Operator 使用 SDK 创建一个新的 Operator 项目 通过添加自定义资源CRD定义新的资源 API 指定使用 SDK API 来 watch 的资源 定义 Operator 的协调reconcile逻辑 使用 Operator SDK 构建并生成 Operator 部署清单文件
每种 Operator 类型都有不同的功能集在选择项目的类型时重要的是要了解每种项目类型的功能和局限性以及 Operator 的用例。
示例
我们平时在部署一个简单的 Webserver 到 Kubernetes 集群中的时候都需要先编写一个 Deployment 的控制器然后创建一个 Service 对象通过 Pod 的 label 标签进行关联最后通过 Ingress 或者 typeNodePort 类型的 Service 来暴露服务每次都需要这样操作是不是略显麻烦我们就可以创建一个自定义的资源对象通过我们的 CRD 来描述我们要部署的应用信息比如镜像、服务端口、环境变量等等然后创建我们的自定义类型的资源对象的时候通过控制器去创建对应的 Deployment 和 Service是不是就方便很多了相当于我们用一个资源清单去描述了 Deployment 和 Service 要做的两件事情。
这里我们将创建一个名为 AppService 的 CRD 资源对象然后定义如下的资源清单进行应用部署
apiVersion: app.example.com/v1
kind: AppService
metadata:name: nginx-app
spec:replicas: 2image: nginx:1.7.9ports:- port: 80targetPort: 80nodePort: 30002
通过这里自定义的 AppService 资源对象去创建副本数为2的 Pod然后通过nodePort30002的端口去暴露服务接下来我们就来实现这个简单的 Operator 应用。
开发环境 依赖服务版本
Kubernetesv1.18.8operator-sdkv1.17.0golang1.17.3docker20.10.12
安装Operator其他服务安装略。
wget -c https://github.com/operator-framework/operator-sdk/releases/download/v1.17.0/operator-sdk_linux_amd64
mv operator-sdk_linux_amd64 /usr/bin/operator-sdk
chmod x /usr/bin/operator-sdk
还需要安装gcc
yum -y install gcc
Operator开发
环境准备好了接下来就可以使用 operator-sdk 直接创建一个新的项目了命令格式为operator-sdk init。
按照上面我们预先定义的 CRD 资源清单我们这里可以这样创建
# 创建项目目录
mkdir -p my-operator cd my-operator
# 使用gomodules包管理工具
export GO111MODULEon
# 使用代理加速
export GOPROXYhttps://goproxy.cn
# 使用 sdk 创建一个名为 my-operator 的 operator 项目
go mod init my-operator# 使用下面的命令初始化项目
operator-sdk init --domain example.com --license apache2 --owner developer
初始化完成后的项目结构如下所示
$ tree -L 2
.
├── config
│ ├── default
│ ├── manager
│ ├── manifests
│ ├── prometheus
│ ├── rbac
│ └── scorecard
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT8 directories, 7 files
到这里一个全新的 Operator 项目就新建完成了。
项目结构
使用 operator-sdk init 命令创建新的 Operator 项目后项目目录就包含了很多生成的文件夹和文件。
go.mod/go.sum - Go Modules 包管理清单用来描述当前 Operator 的依赖包。main.go 文件使用 operator-sdk API 初始化和启动当前 Operator 的入口。deploy - 包含一组用于在 Kubernetes 集群上进行部署的通用的 Kubernetes 资源清单文件。pkg/apis - 包含定义的 API 和自定义资源CRD的目录树这些文件允许 sdk 为 CRD 生成代码并注册对应的类型以便正确解码自定义资源对象。pkg/controller - 用于编写所有的操作业务逻辑的地方version - 版本定义build - Dockerfile 定义目录
我们主要需要编写的是 pkg 目录下面的 api 定义以及对应的 controller 实现。
添加 API
接下来为我们的自定义资源添加一个新的 API按照上面我们预定义的资源清单文件在 Operator 相关根目录下面执行如下命令
$ operator-sdk create api --resourcetrue --controllertrue --group app --version v1 --kind AppService
$ go mod tidy
这里我们添加了一个 group 为 app版本为 v1的 AppService 的资源对象。
自定义 API
打开源文件 api/v1beta1/appservice_types.go需要我们根据我们的需求去自定义结构体 AppServiceSpec我们最上面预定义的资源清单中就有 replicas、image、ports 这些属性所有我们需要用到的属性都需要在这个结构体中进行定义
type AppServiceSpec struct {Replicas *int32 json:replicasImage string json:imageResources corev1.ResourceRequirements json:resources,omitemptyEnvs []corev1.EnvVar json:envs,omitemptyPorts []corev1.ServicePort json:ports,omitempty
}
代码中会涉及到一些包名的导入由于包名较多所以我们会使用一些别名进行区分主要的包含下面几个
import (appsv1 k8s.io/api/apps/v1corev1 k8s.io/api/core/v1metav1 k8s.io/apimachinery/pkg/apis/meta/v1
)
这里的 resources、envs、ports 的定义都是直接引用的 k8s.io/api/core/v1中定义的结构体而且需要注意的是我们这里使用的是 ServicePort而不是像传统的 Pod 中定义的 ContanerPort这是因为我们的资源清单中不仅要描述容器的 Port还要描述 Service 的 Port。
然后一个比较重要的结构体 AppServiceStatus 用来描述资源的状态当然我们可以根据需要去自定义状态的描述我这里就偷懒直接使用 Deployment 的状态了
type AppServiceStatus struct {appsv1.DeploymentStatus json:,inline
}
定义完成后在项目根目录下面执行如下命令
$ make
/Users/ych/devs/projects/go/bin/controller-gen object:headerFilehack/boilerplate.go.txt paths./...
go fmt ./...
go vet ./...
go build -o bin/manager main.go
该命令会使用我们更新后的资源对象结构重新自动生成一些代码这样我们就算完成了对自定义资源对象的 API 的声明。
实现业务逻辑 上面 API 描述声明完成了接下来就需要我们来进行具体的业务逻辑实现了编写具体的 controller 实现打开源文件controllers/appservice_controller.go需要我们去更改的地方也不是很多核心的就是Reconcile 方法该方法就是去不断的 watch 资源的状态然后根据状态的不同去实现各种操作逻辑。
首先 sdk 为我们搭建了一个基本的 reconciler 结构几乎每一个调谐器器都需要记录日志并且能够获取对象所以可以直接使用。
type AppServiceReconciler struct {client.ClientLog logr.LoggerScheme *runtime.Scheme
}
在appservice_controller.go文件中定义一个全局变量用于后面的annotations
var oldSpecAnnotation old/spec
Reconcile 实际上是对单个对象进行调谐我们的 Request 只是有一个名字但我们可以使用 client 从缓存中获取这个对象。我们返回一个空的结果没有错误这就向 controller-runtime 表明我们已经成功地对这个对象进行了调谐在有一些变化之前不需要再尝试调谐。
大多数控制器需要一个日志句柄和一个上下文所以我们在 Reconcile 中将他们初始化。上下文是用来允许取消请求的它是所有 client 方法的第一个参数。
Reconcile协调业务逻辑代码如下。
func (r *AppServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {ctx context.Background()log : r.Log.WithValues(appservice, req.NamespacedName)// 获取appService crd资源appService : appv1.AppService{}if err : r.Client.Get(ctx, req.NamespacedName, appService); err ! nil {if errors.IsNotFound(err) {return ctrl.Result{}, nil}return ctrl.Result{}, err}// crd 资源标记为删除if appService.DeletionTimestamp ! nil {return ctrl.Result{}, nil}log.Info(fetch appservice objects, appservice, appService)// 如果不存在则创建关联资源; 如果存在判断是否需要更新// 如果需要更新则直接更新; 如果不需要更新则正常返回oldDeploy : appsv1.Deployment{}if err : r.Client.Get(ctx, req.NamespacedName, oldDeploy); err ! nil {// deployment 不存在创建if errors.IsNotFound(err) {// 创建deploymentif err : r.Client.Create(ctx, resources.NewDeploy(appService)); err ! nil {return ctrl.Result{}, err}// 创建serviceif err : r.Client.Create(ctx, resources.NewService(appService)); err ! nil {return ctrl.Result{}, err}// 更新 crd 资源的 Annotationsdata, _ : json.Marshal(appService.Spec)if appService.Annotations ! nil {appService.Annotations[spec] string(data)} else {appService.Annotations map[string]string{spec: string(data)}}if err : r.Client.Update(ctx, appService); err ! nil {return ctrl.Result{}, err}} else {return ctrl.Result{}, err}} else {// deployment 存在更新oldSpec : appv1.AppServiceSpec{}if err : json.Unmarshal([]byte(appService.Annotations[spec]), oldSpec); err ! nil {return ctrl.Result{}, err}if !reflect.DeepEqual(appService.Spec, oldSpec) {// 更新deploymentnewDeploy : resources.NewDeploy(appService)oldDeploy.Spec newDeploy.Specif err : r.Client.Update(ctx, oldDeploy); err ! nil {return ctrl.Result{}, err}// 更新servicenewService : resources.NewService(appService)oldService : corev1.Service{}if err : r.Client.Get(ctx, req.NamespacedName, oldService); err ! nil {return ctrl.Result{}, err}// 更新 service 必须设置老的 clusterIPclusterIP : oldService.Spec.ClusterIPoldService.Spec newService.SpecoldService.Spec.ClusterIP clusterIPif err : r.Client.Update(ctx, oldService); err ! nil {return ctrl.Result{}, err}// 更新 crd 资源的 Annotationsdata, _ : json.Marshal(appService.Spec)if appService.Annotations ! nil {appService.Annotations[spec] string(data)} else {appService.Annotations map[string]string{spec: string(data)}}if err : r.Client.Update(ctx, appService); err ! nil {return ctrl.Result{}, err}}}return ctrl.Result{}, nil
}
上面就是业务逻辑实现的核心代码逻辑很简单就是去判断资源是否存在不存在则直接创建新的资源创建新的资源除了需要创建 Deployment 资源外还需要创建 Service 资源对象因为这就是我们的需求当然你还可以自己去扩展比如再创建一个 Ingress 对象。更新也是一样的去对比新旧对象的声明是否一致如果不一致则需要更新同样的两种资源都需要更新的。
另外两个核心的方法就是上面的 resources.NewDeploy(instance) 和 resources.NewService(instance) 方法这两个方法实现逻辑也很简单就是根据 CRD 中的声明去填充 Deployment 和 Service 资源对象的 Spec 对象即可。
在groupversion_info.go文件里增加一个Kind全局变量
var (GroupVersion schema.GroupVersion{Group: app.example.com, Version: v1}Kind AppServiceSchemeBuilder scheme.Builder{GroupVersion: GroupVersion}AddToScheme SchemeBuilder.AddToScheme
)
NewDeploy 方法实现如下
/resources/deployment.goNewService 对应的方法实现如下
/resources/service.go这样我们就实现了 AppService 这种资源对象的业务逻辑。
调试 测试访问 Kubernetes 集群是否可以:
$ kubectl cluster-info
首先需要在集群中安装 CRD 对象
$ make install $ kubectl get crd |grep appservice
appservices.app.example.com 2022-02-27T07:31:10Z
当我们通过 kubectl get crd 命令获取到我们定义的 CRD 资源对象就证明我们定义的 CRD 安装成功了。其实现在只是 CRD 的这个声明安装成功了但是我们这个 CRD 的具体业务逻辑实现方式还在我们本地并没有部署到集群之中我们可以通过下面的命令来在本地项目中启动 Operator 的调试
$ make run ENABLE_WEBHOOKSfalse
上面的命令会在本地运行 Operator 应用通过 ~/.kube/config 去关联集群信息现在我们去添加一个 AppService 类型的资源然后观察本地 Operator 的变化情况资源清单文件就是我们上面预定义的config/samples/app_v1_appservice.yaml:
apiVersion: app.ydzs.io/v1beta1
kind: AppService
metadata:name: nginx
spec:replicas: 2image: nginx:1.7.9ports:- port: 80targetPort: 80nodePort: 30002
直接创建这个资源对象
$ kubectl apply -f config/samples/app_v1_appservice.yaml
我们可以看到我们的应用创建成功了这个时候查看 Operator 的调试窗口会有如下的信息出现
......
1.646208501373661e09 INFO controllers.AppService fetch appservice objects {appservice: default/nginx, appservice: {apiVersion: app.example.com/v1, kind: AppService, namespace: default, name: nginx}}
1.6462085017729385e09 INFO controllers.AppService fetch appservice objects {appservice: default/nginx, appservice: {apiVersion: app.example.com/v1, kind: AppService, namespace: default, name: nginx}}
......
然后我们可以去查看集群中是否有符合我们预期的资源出现
$ kubectl get AppService
NAME AGE
nginx 2m8s$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx p 2/2 2 2 2m20s$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.111.179.0 none 80:30002/TCP 2m23s
看到了吧我们定义了两个副本replicas2这里就出现了两个 Pod还有一个 NodePort30002 的 Service 对象.
如果应用在安装过程中出现了任何问题我们都可以通过本地的 Operator 调试窗口找到有用的信息然后调试修改即可。
清理
$ kubectl delete -f config/samples/app_v1_appservice.yaml
$ make uninstall
部署 自定义的资源对象现在测试通过了但是如果我们将本地的调试控制器终止掉我们可以猜想到就没办法处理 AppService 资源对象的一些操作了所以我们需要将我们的业务逻辑实现部署到集群中去。
执行下面的命令构建 Operator 应用打包成 Docker 镜像
Dockerfile 需要更改为这样
# Build the manager binary
FROM golang:1.17 as builderWORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we dont need to re-download as much
# and so that source changes dont invalidate our downloaded layer
RUN export GOPROXYhttps://goproxy.cn go mod download# Copy the go source
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
COPY resources/ resources/# Build
RUN CGO_ENABLED0 GOOSlinux GOARCHamd64 GO111MODULEon go build -a -o manager main.go# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM golang:1.17
WORKDIR /
COPY --frombuilder /workspace/manager .
#USER 65532:65532ENTRYPOINT [/manager]
执行命令构建
$ make docker-build IMGregistry.cn-hangzhou.aliyuncs.com/test-operator/my-operator:v1.0.0
镜像构建成功后推送到自己的registry
$ make docker-push IMGregistry.cn-hangzhou.aliyuncs.com/test-operator/my-operator:v1.0.0
将config/manager/manager.yaml里的镜像名改为你打包后的镜像名
apiVersion: v1
kind: Namespace
metadata:labels:control-plane: controller-managername: system
---
apiVersion: apps/v1
kind: Deployment
metadata:name: controller-managernamespace: systemlabels:control-plane: controller-manager
spec:selector:matchLabels:control-plane: controller-managerreplicas: 1template:metadata:annotations:kubectl.kubernetes.io/default-container: managerlabels:control-plane: controller-managerspec:securityContext:runAsNonRoot: truecontainers:- command:- /managerargs:- --leader-electimage: registry.cn-hangzhou.aliyuncs.com/test-operator/my-operator:v1.0.0name: managersecurityContext:allowPrivilegeEscalation: falselivenessProbe:httpGet:path: /healthzport: 8081initialDelaySeconds: 15periodSeconds: 20readinessProbe:httpGet:path: /readyzport: 8081initialDelaySeconds: 5periodSeconds: 10# TODO(user): Configure the resources accordingly based on the project requirements.# More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/resources:limits:cpu: 500mmemory: 128Mirequests:cpu: 10mmemory: 64MiserviceAccountName: controller-managerterminationGracePeriodSeconds: 10
修改config/default/manager_auth_proxy_patch.yaml文件将kube-rbac镜像地址改成国内的。
containers:
- name: kube-rbac-proxyimage: kubesphere/kube-rbac-proxy:v0.8.0
部署operator 编辑config/rbac/role_binding.yaml文件绑定controller 到 cluster-admin 集群管理员角色。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: manager-rolebinding
roleRef:apiGroup: rbac.authorization.k8s.iokind: ClusterRole#name: manager-rolename: cluster-admin
subjects:- kind: ServiceAccountname: controller-managernamespace: system现在部署 Operator本质上用的是kustomize工具构建:
$ touch config/rbac/role.yaml # 创建一个空文件否则部署会报错$ make deploy IMGregistry.cn-hangzhou.aliyuncs.com/test-operator/my-operator:v1.0.0
/root/my-operator/bin/controller-gen rbac:roleNamemanager-role crd webhook paths./... output:crd:artifacts:configconfig/crd/bases
cd config/manager /root/my-operator/bin/kustomize edit set image controllerregistry.cn-hangzhou.aliyuncs.com/test-operator/my-operator:v1.0.0
/root/my-operator/bin/kustomize build config/default | kubectl apply -f -
namespace/my-operator-system created
customresourcedefinition.apiextensions.k8s.io/appservices.app.example.com created
serviceaccount/my-operator-controller-manager created
role.rbac.authorization.k8s.io/my-operator-leader-election-role created
clusterrole.rbac.authorization.k8s.io/my-operator-metrics-reader created
clusterrole.rbac.authorization.k8s.io/my-operator-proxy-role created
rolebinding.rbac.authorization.k8s.io/my-operator-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/my-operator-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/my-operator-proxy-rolebinding created
configmap/my-operator-manager-config created
service/my-operator-controller-manager-metrics-service created
deployment.apps/my-operator-controller-manager created查看operator部署是否成功:
# kubectl get po -A | grep my-operator
my-operator-system my-operator-controller-manager-b5f8bcb58-7nqkv 2/2 Running 0 11h# kubectl get svc -A | grep my-operator
my-operator-system my-operator-controller-manager-metrics-service ClusterIP 172.21.2.239 none 8443/TCP 11h部署 crd资源测试。可以看到我们自定义的deployment、service资源已经按照需求创建成功了:
$ kubectl apply -f config/samples/app_v1_appservice.yaml$ kubectl get deploy |grep nginx
nginx 2/2 2 2 46s$ kubectl get pod |grep nginx
nginx-5bf87f5f59-knbdw 1/1 Running 0 51s
nginx-5bf87f5f59-lblx6 1/1 Running 0 51s$ kubectl get svc |grep nginx
nginx NodePort 172.21.0.149 none 80:30002/TCP 57s
其他信息
删除 CRD 自定义资源
$ kubectl delete -f config/samples/app_v1_appservice.yaml
删除 CRD 定义
$ make uninstall
删除 controller
$ make undeploy
到这里我们的 CRD 和 Operator 实现都已经安装成功了。