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
本文来自博客园,作者:vx_guanchaoguo0,转载请注明原文链接:https://www.cnblogs.com/guanchaoguo/p/16182604.html