- 介绍
- 准备开发环境
- 创建项目
- 添加业务逻辑
- 安装调试
- 打镜像部署
一、介绍
- Operator介绍
Operator可以看成是CRD和Controller的组合,通过CRD及其控制器扩展Kubernetes API的资源。
业务逻辑在Controller里,Controller代码结构大同小异,每次手动从头写费时费力,于是产生了以下两个控制器代码生成工具。
生成控制器Controller代码的工具:Operator Franmework和kubebuilder。
Opertor Framework是一个快速开发Operator的工具包,该框架主要包括两个部分:
Operator SDK: 无需了解复杂的 Kubernetes API 特性,即可让你根据你自己的专业知识构建一个 Operator 应用。
Operator Lifecycle Manager(OLM): 帮助你安装、更新和管理跨集群的运行中的所有 Operator(以及他们的相关服务) 。
当前示例项目背景
<br />我们平时创建一个简单的应用的时候,一般是编写deployment yaml,然后再创建一个service,通过标签进行关 联,最后通过Ingress或者type=NodePort暴露服务,略显麻烦。
设想一下,有一种CRD,指定镜像、服务名、端口、环境变量等,就能自动创建对应的Deployment和service,是不是方便很多?
二、准备开发环境
| 环境 | 版本 | 节点 |
|---|---|---|
| k8s集群 | 1.18.0 | 虚拟机A、B、C (centos7.5) |
| docker | 17.03+ | 虚拟机A (centos7.5) |
| go | 1.16+ | 虚拟机A (centos7.5) |
| kubectl | 1.16.0+ | 虚拟机A (centos7.5) |
| operator-sdk | 1.12.0+ | 虚拟机A (centos7.5) |
| Goland | 2020+ | 本地windows |
这里只演示operator-sdk在linux(centos7)的安装。其他平台请参考:https://sdk.operatorframework.io/docs/installation/
wget https://github.com/operator-framework/operator-sdk/releases/download/v1.17.0/operator-sdk_linux_amd64mv operator-sdk_linux_amd64 /usr/local/bin/operator-sdkchmod +x /usr/local/bin/operator-sdkoperator-sdk version

三、创建项目
1、预设CRD资源清单
这里创建一个名为AppService的CRD资源对象,其资源清单如下:
apiVersion: app.ydzs.io/v1beta1
kind: DeployService
metadata:
name: DS-app
spec:
size: 2
image: nginx:1.7.9
ports:
- port: 80
targetPort: 80
nodePort: 30002
2、创建项目
$ operator-sdk # 查看operator-sdk命令使用帮助
$ mkdir -p test1 && cd test1 # 创建项目目录
$ export GO111MODULE=on # 使用gomodules包管理工具
$ export GOPROXY="https://goproxy.cn"
# 使用包代理,加速
# 使用 sdk 创建一个名为 test1 的 operator 项目,如果在 GOPATH 之外需要指定 repo 参数
$ go mod init github.com/fuyu89/test1/v2
# 使用下面的命令初始化项目
$ operator-sdk init --domain ydzs.io --license apache2 --owner "fuyu89"
项目结构:
3、添加API
operator-sdk create api --group app --version v1beta1 --kind DeployService --resource=true --controller=true

四、添加业务逻辑
1、main.go中添加日志模块初始化
if err = (&controllers.DeployServiceReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("DeployService"), // 日志模块初始化
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "DeployService")
os.Exit(1)
}
//+kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
2、修改api/v1beta1/deployservice_type.go
1)添加模导入块

2)修改DeployServiceSpec结构体
与 “1、预设CRD资源清单“ 里面的spec部分添加对应的方法,omitempty表示可为空
。
3)在结构体DeployServiceStatus里添加这个CRD的状态说明

3、修改controllers/deployservice_controller.go
1)给模块启别名
主要是appsv1、corev1,其他Goland自动导入。
import (
"context"
appv1beta1 "github.com/fuyu89/test1/v2/api/v1beta1"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1" // 添加
corev1 "k8s.io/api/core/v1" // 添加
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
2)添加日志处理器
在 type DeployServiceReconciler struct 部分添加 Log logr.Logger:
// DeployServiceReconciler reconciles a DeployService object
type DeployServiceReconciler struct {
client.Client
Log logr.Logger // 添加日志处理器
Scheme *runtime.Scheme
}
3)添加代码权限
我们代码对k8s集群的deploy、service资源进行创建、删除、watch等操作的权限
。
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
4)添加业务逻辑
在此文件的 Reconcile 函数里添加基本的逻辑:实时监控我们用这个CRD创建的deploy和service资源,发现异常做什么动作(此处实现基本逻辑:让资源保持期望状态,有则更新,没有则创建)。
func (r *DeployServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
ctx = context.Background()
// log := r.Log.WithValues("deployservice", req.NamespacedName)
// 首先获取DeployService实例
var deployservice appv1beta1.DeployService
if err := r.Client.Get(ctx, req.NamespacedName, &deployservice); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 调谐,获取当前状态,然后和期望状态进行对比
// CreateOrUpdate Deployment
var deploy appsv1.Deployment
deploy.Name = deployservice.Name
deploy.Namespace = deployservice.Namespace
or, err := ctrl.CreateOrUpdate(ctx, r.Client, &deploy, func() error {
// 调谐必须在这个函数中进行
MutateDeployment(&deployservice, &deploy)
return controllerutil.SetControllerReference(&deployservice, &deploy, r.Scheme)
})
if err != nil {
return ctrl.Result{}, err
}
klog.V(2).Info("CreateOrUpdate", "Deployment", or)
// log.Info("CreateOrUpdate", "Deployment", or)
// CreateOrUpdate Service
var svc corev1.Service
svc.Name = deployservice.Name
svc.Namespace = deployservice.Namespace
or, err = ctrl.CreateOrUpdate(ctx, r.Client, &svc, func() error {
// 调谐必须在这个函数中进行
MutateService(&deployservice, &svc)
return controllerutil.SetControllerReference(&deployservice, &svc, r.Scheme)
})
if err != nil {
return ctrl.Result{}, err
}
klog.V(2).Info("CreateOrUpdate", "Service", or)
// log.Info("CreateOrUpdate", "Service", or)
return ctrl.Result{}, nil
}
5)添加实时Watch功能
在最后的函数添加实时监测Deploy和Service的功能,这样如果把自定义的DS资源的deploy或service删了,会自动重建。主要添加Owns(&appsv1.Deployment{}). 和Owns(&corev1.Service{}).
func (r *DeployServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&appv1beta1.DeployService{}).
Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Complete(r)
}
4、添加controllers/resource.go
添加controllers/resource.go,实现controolers/deployservice_controller.go里面的MutateDeployment(&deployservice, &deploy)和MutateService(&deployservice, &svc)方法,定义要创建的deploy和service的模板。
package controllers
import (
"github.com/fuyu89/test1/v2/api/v1beta1"
appsV1 "k8s.io/api/apps/v1"
coreV1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func MutateDeployment(app *v1beta1.DeployService, deploy *appsV1.Deployment) {
labels := map[string]string{"app": app.Name}
selector := &metaV1.LabelSelector{MatchLabels: labels}
deploy.Spec = appsV1.DeploymentSpec{
Replicas: app.Spec.Size,
Template: coreV1.PodTemplateSpec{ // Pod模板
ObjectMeta: metaV1.ObjectMeta{Labels: labels},
Spec: coreV1.PodSpec{Containers: newContainers(app)},
},
Selector: selector,
}
}
func newContainers(app *v1beta1.DeployService) []coreV1.Container {
var containerPorts []coreV1.ContainerPort
for _, svcPort := range app.Spec.Ports {
containerPorts = append(containerPorts, coreV1.ContainerPort{
ContainerPort: svcPort.TargetPort.IntVal,
})
}
return []coreV1.Container{
{
Name: app.Name,
Image: app.Spec.Image,
Resources: app.Spec.Resources,
Env: app.Spec.Envs,
Ports: containerPorts,
},
}
}
func MutateService(app *v1beta1.DeployService, svc *coreV1.Service) {
svc.Spec = coreV1.ServiceSpec{
ClusterIP: svc.Spec.ClusterIP,
Ports: app.Spec.Ports,
Type: coreV1.ServiceTypeNodePort,
Selector: map[string]string{"app": app.Name},
}
}
5、修改api/v1beta1/groupversion_info.go
添加Kind = “DeployService”,即CRD名称。
五、安装调试
1、上传代码
选中Goland中的项目目录test1;工具—>部署 —> 上传到 虚拟机A。
2、在虚拟机A上编译安装
cd /root/test1
go mod tidy
make
make install
kubectl get crd // 显示crd资源
make run ENABLE_WEBHOOKS=false

3、在虚拟机A上部署一个DeployServer资源测试
# vim test-deployservice.yaml
apiVersion: app.ydzs.io/v1beta1
kind: DeployService
metadata:
name: deployservice-test
spec:
size: 2
image: nginx:1.7.9
ports:
- port: 80
targetPort: 80
nodePort: 30002
# kubectl apply -f test-deployservice.yaml
# kubectl get deploy,svc
# kubectl get pods
六、打镜像部署
**1、修改Makefile
**
打开Makefile,搜索docker-build,去掉后面的test,即编译镜像时跳过测试节点,打镜像更快。

2、修改Dockerfile
打开Dockerfile,在 go mod download前面加go代码:GOPROXY=https://goproxy.cn
&&
**3、在虚拟机A打镜像
**
在项目根目录下执行:
make docker-build IMG=fuyu89/test1:v1.0
**4、在虚拟机A打镜像部署
**
注意:默认部署到项目名-system命名空间,可以在config/default/kustomization.yaml里修改。
kubectl create ns test1-system
make deploy IMG=fuyu89/test1:v1.0
kubec get crd
kubectl get pods,deploy,svc -n test1-system
