文档意义
入门级基础文档,帮助未入门的同学轻松入门, 初识crd的作用,了解kubebuilder 开发流程以及编写controller的语法。
此文档更多注重于引导在开发中使用的方法和在开发中避免基础的问题,讲解概念以及问题更多从个人理解的角度来进行讲解,避免官方化的对白。
文档内容概括
- 文档意义
- 文档内容概括
- 开发前介绍
- kubebuilder 安装
- 初始kubebuilder项目创建
- 定制自己的crd项目
- 调试运行crd
- 正式使用crd
- 遇到过问题
- 参考文档
开发前介绍
概念介绍
kubernetes
kubernetes是当前热门的容器编排系统,可以实现自动化部署程序,自动化负载,自动化调度等功能。简单的来说,就是现在我们要运行一个应用,比如tomcat web应用,我们只需要告诉 kubernetes 我们想要一个tomcat,内容在tomcat1的镜像里面, 我们想要的端口是8080,然后 kubernetes 会自动创建一个tomcat web应用,然后给我们一个访问地址(ip:port),我们直接使用访问地址来连接tomcat web应用,而不用自己在tomcat服务器手动上传war包,手动配置。详情请查看kubernetes 中文文档crd
crd 全称”CustomResourceDefinition“,意思为自定义kubernetes资源,用来满足使用特定功能的需求。用自己的话来说,就是现有的kubernetes没办法满足我的场景,所以自己给自己开发个kubernetes资源来使用。例如本文中举的crd例子 -- 创建一个outLoadBalancer 资源,可以将集群外的内网服务映射到集群中,比如我在自己电脑上启动了一个mysql web工具,但是在公司外的人无法连接进来,所以需要做一个公网地址的转换,我们创建一个outLoadBalancer资源,让kubernetes集群帮我们创建这样一个地址转换。详情参考crd官方文档kubebuilder
kubebuilder 能帮助我们快速开发一个crd资源,让我们注重逻辑的开发。详情查看kubebuilder 快速开始golang
golang 是一门高级编程语言,kubernetes 编排软件就是由golang开发的。同学们想学习的话可以免费观看李文周老师的golang 教学视频make介绍
kubebuilder 调试部署等都会用到make工具, make是linux中用来构建编译软件的工具,我们主要关注Makefile文件的内容
开发环境介绍
本地有kubernetes集群,集群外部开发可以查看kubebuilder官网设置
编辑器选择vscode, 选择理由: 可以安装golang模块,可以查看源代码,免费,使用简单。安装方法可以查看下方连接
golang 教学视频
kubebuilder 安装(linux)
1. 查看本地是否已经正常安装kubebuilder, kustomize
whereis kubebuilder # 如果没有具体的目录文件,则表示没有安装,如果有,则需要查看是否kubebuilder结构和下方所示一致
kubebuilder
└── bin
├── etcd
├── kube-apiserver
├── kubebuilder
└── kubectl
2. 如果不正确,则需要先卸载旧的kubebuilder
rm -r /usr/local/kubebuilder # 这里一定要注意是否是正确的路径
3. 如果没有安装,则安装kubebuilder
os=$(go env GOOS)
arch=$(go env GOARCH)
curl -L https://go.kubebuilder.io/dl/2.3.1/${os}/${arch} | tar -xz -C /tmp/
sudo mv /tmp/kubebuilder_2.3.1_${os}_${arch} /usr/local/kubebuilder
export PATH=$PATH:/usr/local/kubebuilder/bin
4. 检查是否安装kustomize
whereis kustomize
5. 如果没有安装, 则手动安装
wget https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv3.8.6/kustomize_v3.8.6_linux_amd64.tar.gz-P /tmp/
tar xvf /tmp/kustomize_v3.8.6_linux_amd64.tar.gz -C /usr/local/kubebuilder/bin
kubebuilder 项目创建
mkdir -p /home/ly/KubernetesCrdSimple/outLoadBalancer (GOPATH为/home/ly/go)
cd /home/ly/KubernetesCrdSimple/outLoadBalancer
go mod init longyi.com # 创建go.mod
kubebuilder init --domain longyi.com # 初始化kubebuilder项目
kubebuilder create api --namespaced=false --group longyi --version v1 --kind OutLoadBalancer # 创建需要的api,包含role等
# 测试项目
make install
make run
# 新开一个终端
new <>: cd /home/ly/KubernetesCrdSimple/outLoadBalancer
new <>: kubectl apply -f config/samples/
# 原来的终端可以看到资源的注册则表示项目初始化已经成功
new <>: kubectl delete -f config/samples/
# 原来的终端
# Ctrl + C 停止make run的运行
# 卸载刚刚创建的crd
make uninstall
# make 支持的命令可以在Makefile中查看
cat ./Makefile
# 格式如下
# Install CRDs into a cluster
install: manifests
kustomize build config/crd | kubectl apply -f - # make install 执行的命令
定制自己的crd项目
现在的项目结构
├── api
│ └── v1
├── groupversion_info.go
├── outloadbalancer_types.go # 这里定义我们crd的字段,比如我们需要集群外机器的ip,端口等都在这面定义
└── zz_generated.deepcopy.go
├── bin
│ └── manager
├── config
│ ├── certmanager
│ ├── crd
│ ├── default
│ ├── manager
│ ├── prometheus
│ ├── rbac
│ ├── samples
│ └── webhook
├── controllers
│ ├── outloadbalancer_controller.go # 这里主要是写我们需要实现的逻辑,例如创建外网的地址转换逻辑.
│ └── suite_test.go
├── cover.out
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT
outloadbalancer_types.go 自定义字段
// OutLoadBalancerSpec defines the desired state of OutLoadBalancer
// 定义Spec的结构体,这里是我们最终会用到的变量,定义时需要满足json序列化
// 即 变量名 变量类型 `json:"json中显示的变量名,也是我们在kubernetes中要使用的变量名"`
type OutLoadBalancerSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of OutLoadBalancer. Edit OutLoadBalancer_types.go to remove/update
OutHost string `json:"outHost"` // 定义ingress中的host字段
OutPort int32 `json:"outPort"` // 定义集群外的端口
OutIP string `json:"outIP"` // 定义集群外的ip
LoadName string `json:"loadName"` // 定义此映射的名字
}
outloadbalancer_controller.go 自定义逻辑
// 新导入package,后续创建endpoint svc ingress 会用到
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// Reconcile controller 这里定义我们要实现的逻辑,参看[https://book.kubebuilder.io/cronjob-tutorial/controller-implementation.html]
func (r *OutLoadBalancerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background() // 获取上下文
log := r.Log.WithValues("outloadbalancer", req.NamespacedName) // 获取日志模块
// your logic 这里写我们的逻辑
load := &longyiv1.OutLoadBalancer{} // 这里是我们上一步定义的crd 结构体
if err := r.Get(ctx, req.NamespacedName, load); err != nil { // 获取值注入到我们的load中,之后可以直接通过load来进行获取
log.Info("unable to fetch load: %v", err)
return ctrl.Result{}, nil
}
// 如果正常获取到数据,则创建我们的网络地址转换
// 这里有 3 个技巧
// 1. 可以 ctrl+左键 查看我们需要创建的结构体内容,根据内容填充即可
// 2. 创建的时候,先创建结构体变量,比如:
// loadEndpoing := corev1.Endpoints{
// } // 鼠标放到左侧,出现黄色小灯的时候,点击小灯可以自动填充结构体内容
// 3. 如果不知道具体类型,可以查看kubernetes中的集群来进行筛选,例如我们不清楚ingress在哪一个package中
// 在kubernetes中查看任意一个ingress,获取到apiversion,形如 apiVersion: extensions/v1beta1
// 由此得知,ingress在extensions/v1beta1中,在vscode中键入v1beta1,
// 在编辑器提示的import选项中选择extensions/v1beta1即可导入正确的package
var labels map[string]string // 定义 labels 标签
labels = make(map[string]string, 1) // 申请地址
labels["outLoad"] = "true" // 赋值
var annotations map[string]string // 同label一样,定义annotation注解
annotations = make(map[string]string, 1)
annotations["outLoad"] = "true"
loadMetadata := metav1.ObjectMeta{ // 定义Metadata中的名称
Name: load.Spec.LoadName,
Namespace: req.Namespace,
Labels: labels,
Annotations: annotations,
}
// create Sevcice
loadServiceSpec := corev1.ServiceSpec{ // 定义service中的Spec字段
// ServicePort 中的TargetPort 是IntOrString类型, 即从int或者string中解析出值,我们定义了outPort 为int32, 所以这里直接写 IntVal即可,
// IntOrString 参考 https://docs.lvrui.io/2019/03/06/go%E8%AF%AD%E8%A8%80%E5%B7%A7%E7%94%A8intstr%E6%9D%A5%E5%A4%84%E7%90%86%E6%95%B0%E5%AD%97%E4%B8%8E%E5%AD%97%E7%AC%A6%E7%B1%BB%E5%9E%8B/
Ports: []corev1.ServicePort{{Name: "http", Protocol: corev1.ProtocolTCP, Port: 80, TargetPort: intstr.IntOrString{IntVal: load.Spec.OutPort}, NodePort: 0}},
Selector: nil, // 由于是外部地址,不需要selector,设置为nil
ClusterIP: "",
Type: corev1.ServiceTypeClusterIP,
ExternalIPs: []string{},
SessionAffinity: "",
LoadBalancerIP: "",
LoadBalancerSourceRanges: []string{},
ExternalName: "",
ExternalTrafficPolicy: "",
HealthCheckNodePort: 0,
PublishNotReadyAddresses: false,
SessionAffinityConfig: &corev1.SessionAffinityConfig{},
}
loadService := corev1.Service{ // 将metadata和spec注入到service中
Spec: loadServiceSpec,
ObjectMeta: loadMetadata,
}
r.Create(ctx, &loadService) // 调用Create创建service
// 使用相同方法创建 Endpoint Ingress
// 代码查看 github仓库
// https://github.com/ajax-2/KubernetesCrdSimple
return ctrl.Result{}, nil
}
Dockerifle 定义控制器镜像
由于原生的镜像在国内拉不下来,我们需要改动Dockerfile
# Build the manager binary
FROM golang:1.13.0 as builder # 我开发的go版本是golang:1.13.0, 所以改成 golang:1.13.0
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
RUN export GOPROXY="https://goproxy.cn" && go mod download # 增加GOPROXY的国内配置,不然会导致 go mod download失败
# Copy the go source
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
# 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 bubaoxiaoyu/distroless-static:v1 # 更改static镜像, 原生的gcr.io 下载不下来
WORKDIR /
COPY --from=builder /workspace/manager .
USER nonroot:nonroot
ENTRYPOINT ["/manager"]
rbac role
由于我们的需要跨namespace, 所以改动role.yaml, 为了快速调试,直接给最大权限
cat > config/rbac/role.yaml < EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: manager-role
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '*'
verbs:
- '*'
EOF
controller auth-proxy
controller中的auth-proxy 默认是gcr.io的,我们需要改动,查看Makefile
deploy: manifests
cd config/manager && kustomize edit set image controller=${IMG}
kustomize build config/default | kubectl apply -f -
可知我们需要改动的文件在 config/default中
改动 config/default/manager_auth_proxy_patch.yaml 中的image:
image: bubaoxiaoyu/kube-rbac-proxy:v0.5.0
调试部署
make run # 进行本地调试,调试通过后,执行下列步骤进行部署
make docker-build docker-push IMG=bubaoxiaoyu/outloadcrd:v1 # 进行controller镜像构建, IMG换成自己的hub仓库
make deploy IMG=bubaoxiaoyu/outloadcrd:v1 # 构建到本地集群
至此已经完成了crd的开发
遇到的问题
1. 构建的时候提示无法序列化
定义struct 字段的时候,需要实现json序列化, Demo String `json:"demo"` // 这里是反引号,不是单引号
2. controller没有创建相应的资源
rbac role权限不正确,添加正确的权限,可暂时使用cluster-admin 快速查看crd开发结果。
3. 不知道go-client的结构,直接文档少
借用vscode, 直接查看结构定义
4. kube-rabc-proxy 提示image pull 失败
更改镜像为 bubaoxiaoyu/kube-rbac-proxy:v0.5.0
5. kubebuilder提示没有etcd等命令
按照前面的步骤重新安装kubebuilder
总结
1. kubebuilder kustomize 需要安装正确
2. crd Strunct 需要序列化
3. Controller 开发使用client-go来进行开发
4. role 权限需要设置对
5. kube-rbac-porxy 镜像需要更改
6. Dockerfile需要更改
总结: 此文档更过注重于方法以及开发心得的分享,如果有不恰当的地方,各位同学多多包涵。
任何一门知识入门并不难,我们知识缺少好的文档,我愿意分享自己的学习历程,帮助同学们快速入门。
希望为入门的同学们能尽快找到进门的方法,祝好!
参考文档
kubernetes 中文文档
crd 官方文档
kubebuilder 快速开始
IntOrString用法参考
golang 教学视频
kubernetes-simple-controller
kubernetes-client-go-example
kubernetes api
github 代码