k8s operater 开发示例

预制条件
  • goang version v1.17
  • Docker engine version v20.10.14
  • k8s version v1.22.5
  • opreater-sdk version v1.22.5
  • 开发环境 wsl2 ubuntu 20.04
依赖对应关系
  • github opreater-sdk release change log查看
  • chang log 一般会说明 变更事项 如 k8s golang docker engine
  • breaking change 一定要注意可能不兼容之前版本
准备
  • 注意这个一定要修改 goproxy 否则这个过程会失败 如果有梯子 请无视
  • golang 二进制安装 需要安装gcc 因为依赖cgo
  • 确认安装make 整个脚手架基于 makefile
  • 徐亚开启 go mod ,如果项目 不在gopath 需要go mod init
  • k8s集群一定要runing 并且保证 ~/.kube/config 可以访问集群
目标需求
  • 定义一个 crd ,spec 包含以下信息:
Replicas	# 副本数
Image		# 镜像
Resources	# 资源限制
Envs		# 环境变量
Ports		# 服务端口
create app
 mkdir -p $GOPATH/src/github.com/guanchaoguos/app &&  cd $GOPATH/src/github.com/guanchaoguos/app
 operator-sdk init --domain=example.com --repo=github.com/guanchaoguos/app
create api
operator-sdk create api --group app --version v1 --kind App --resource=true --controller=true

自定义字段

  • 在整个脚手架已经帮我们做好了 很多工作 我们只要完成自定义数据结构的定义
import (
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type AppSpec struct {
  Replicas *int32               `json:"replicas"`		// 副本数
  Image    string               `json:"image"`		// 镜像
  Resources corev1.ResourceRequirements  `json:"resources,omitempty"`	// 资源限制
  Envs     []corev1.EnvVar      `json:"envs,omitempty"`	// 环境变量
  Ports    []corev1.ServicePort `json:"ports,omitempty"`	// 服务端口
}

调谐

  • 调谐: 整个k8s的逻辑 就是期望值 spec 【yaml template 】定义的值 和实际k8s 运行的状态 在一个循环不断做对比然后 进项操作【curd】
  • 这个循环被称为事件循环 每个资源的变更叫做事件 期望值时间的周期叫做时间周期 就是基于时间周期的事件调谐
  • 脚手架帮帮我们做好了逻辑 我们只要在 Reconcile 函数中实现业务逻辑即可
  • 修改 controller 代码 controllers/app_controller.go
func (r *AppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	_ = r.Log.WithValues("app", req.NamespacedName)
	// your logic here

	// 获取 crd 资源
	instance := &appv1.App{}
	if err := r.Client.Get(ctx, req.NamespacedName, instance); err != nil {
		if errors.IsNotFound(err) {
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}

	// crd 资源已经标记为删除
	if instance.DeletionTimestamp != nil {
		return ctrl.Result{}, nil
	}

	oldDeploy := &appsv1.Deployment{}
	if err := r.Client.Get(ctx, req.NamespacedName, oldDeploy); err != nil {
		// deployment 不存在,创建
		if errors.IsNotFound(err) {
			// 创建deployment
			if err := r.Client.Create(ctx, deployment.New(instance)); err != nil {
				return ctrl.Result{}, err
			}

			// 创建service
			if err := r.Client.Create(ctx, service.New(instance)); err != nil {
				return ctrl.Result{}, err
			}

			// 更新 crd 资源的 Annotations
			data, _ := json.Marshal(instance.Spec)
			if instance.Annotations != nil {
				instance.Annotations["spec"] = string(data)
			} else {
				instance.Annotations = map[string]string{"spec": string(data)}
			}
			if err := r.Client.Update(ctx, instance); err != nil {
				return ctrl.Result{}, err
			}
		} else {
			return  ctrl.Result{}, err
		}
	} else {
		// deployment 存在,更新
		oldSpec := appv1.AppSpec{}
		if err := json.Unmarshal([]byte(instance.Annotations["spec"]), &oldSpec); err != nil {
			return ctrl.Result{}, err
		}

		if !reflect.DeepEqual(instance.Spec, oldSpec) {
			// 更新deployment
			newDeploy := deployment.New(instance)
			oldDeploy.Spec = newDeploy.Spec
			if err := r.Client.Update(ctx, oldDeploy); err != nil {
				return ctrl.Result{}, err
			}

			// 更新service
			newService := service.New(instance)
			oldService := &corev1.Service{}
			if err := r.Client.Get(ctx, req.NamespacedName, oldService); err != nil {
				return ctrl.Result{}, err
			}
			clusterIP := oldService.Spec.ClusterIP	// 更新 service 必须设置老的 clusterIP
			oldService.Spec = newService.Spec
			oldService.Spec.ClusterIP = clusterIP
			if err := r.Client.Update(ctx, oldService); err != nil {
				return ctrl.Result{}, err
			}

			// 更新 crd 资源的 Annotations
			data, _ := json.Marshal(instance.Spec)
			if instance.Annotations != nil {
				instance.Annotations["spec"] = string(data)
			} else {
				instance.Annotations = map[string]string{"spec": string(data)}
			}
			if err := r.Client.Update(ctx, instance); err != nil {
				return ctrl.Result{}, err
			}
		}
	}
	return ctrl.Result{}, nil
}
修改自定资源的 类似deployment
apiVersion: app.example.com/v1
kind: App
metadata:
  name: app-sample
  namespace: default
spec:
  # Add fields here
  replicas: 2
  image: nginx:1.16.1
  ports:
  - targetPort: 80
    port: 8080
  envs:
  - name: DEMO
    value: app
  - name: GOPATH
    value: gopath
  resources:
    limits:
      cpu: 500m
      memory: 500Mi
    requests:
      cpu: 100m
      memory: 100Mi

修改dockerfile

# Build the manager binary
FROM golang:1.15 as builder

WORKDIR /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 don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer

ENV GOPROXY https://goproxy.cn,direct

RUN go mod download

# Copy the go source
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
COPY resource/ resource/

# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on 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 gcr.io/distroless/static:nonroot
FROM kubeimages/distroless-static:latest
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532

ENTRYPOINT ["/manager"]

####### 本调试发布到本地集群中

make generate  # 生成 api/v1/app_types.go 的深拷贝文件  zz_generated.deepcopy.go
make manifests  # 指令生成 CRD 文件 config/crd/bases/app.example.com_apps.yaml
make install   # 真个发布逻辑 生成对应的 ymal
make run  #  apply 到 k8s 
posted @ 2022-04-23 15:46  vx_guanchaoguo0  阅读(452)  评论(0编辑  收藏  举报