前言
kubernetes 的入门阶段是使用,进一步的深入就是基于 kubernetes api 进行开发应用。kubernetes 开发的入门就是了解 CRD 的创建,以及 CRD 资源状态的监听。
pod,deployment,node 等是 kubernetes 自带的资源,CRD 是我们自定义的资源。
kubernetes api 客户端主流的是 client-go, java 也有 kubernetes-client,这些客户端默认都只提供 k8s 原生资源的操作,如果是自定义的 CRD,就需要额外借助一些代码生成的工具来新增相关的客户端类,如 go-generate、kubebuild。
下面介绍 go 进行 k8s 开发的一个代码脚手架。java 相对应的脚手架后续也会安排。
技术栈说明
go-restful
go-restful 是一个 Golang 第三方库,是一个轻量的 RESTful API 框架,基于 Golang Build-in 的 http/net 库。
适用于构建灵活多变的 Web Application,k8s中许多框架都使用了go-restful, 包括 kubernetes 的api server。
kubebuilder
kubebuilder 是kubernetes提供的用于开发自定义CRD的工具集(SDK),通过kubebuilder可以在Go中快速构建和发布Kubernetes API。一般会借助他来生成 CRD 对应的 yaml 文件。
code-generator
跟 kubebuilder 类似,但 code-generate 生成的东西比较多,也比较复杂。 可以用它生成Informers,Clientset,listers,自定义workqueue消费k8s产生的事件,而通过kubebuilder无法生成 Informers信息。
client-go
client-go 是k8s官方提供的用于与api-server交互的客户端,client-go 提供的一系列的util工具,包括informer list-watch
机制,workqueue 机制等,如果自定义CRD 要与k8s交互,client-go是必不可少的一个包。
脚手架说明
目录结构
- hotwhell
-- build //放置 dockerfile
-- cmd //应用的入口函数
-- docs //说明文档
-- examples //实例代码,crd定义文件
-- hack //放置shell脚本等
-- pkg //项目的主要逻辑代码,如controlle等
-- test //放置e2e测试代码
-- tools //swagger生成的代码放置在此目录下
-- vendor //第三方数据包
-- go.mod //go 版本管理
-- go.vendor //go 版本管理
-- Makefile //make 命令集合
启动流程
go 的包依赖模式,vendor 与 mod 本来是 2 种对立的模式,vendor 的包会依赖 gopath,mod 模式下不会。该项目同时拥有 mod 和 vendor,因为 mod 中做了兼容处理,如果项目中有 vendor 会优先检索 vendor中的包。而引入 vendor 的原因是可以将 vendor 纳入 git 依赖,其他人员可以不用重复下载。
// 开启 mod 模式,设置打包成 linux amd64 的包,指定包下载代理
export GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY="https://goproxy.cn,direct"
go mod download
go mod vendor
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/hotwheel-agent cmd/hotwheel/main.go
Makefile 文件说明
生成 clientset,informers,listers
client:
./hack/generate_client.sh
生成自定义 CRD 的 yaml 文件
manifests:
go run ./vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/... rbac:roleName=controller-perms "crd:trivialVersions=true" output:crd:artifacts:config=examples/crds
生成 swagger api
openapi:
go run ./vendor/k8s.io/kube-openapi/cmd/openapi-gen/openapi-gen.go -O openapi_generated -i ./vendor/k8s.io/apimachinery/pkg/apis/meta/v1,./vendor/k8s.io/apimachinery/pkg/runtime,./vendor/k8s.io/api/core/v1,./vendor/k8s.io/api/apps/v1,yummy.tech/hotwheel/pkg/apis/cluster/v1alpha1 -o ./ -p pkg/apis/cluster/v1alpha1 -h ./hack/boilerplate.go.txt --report-filename ./api/api-rules/violation_exceptions.list
go run ./tools/cmd/doc-gen/main.go
生成docker镜像
release: build-go
docker build -t hotwheel-agent:latest -f build/hotwheel/Dockerfile .
编译
build-go: fmt vet
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/hotwheel cmd/hotwheel/main.go
代码说明
1.入口
go build -o bin/hotwheel-agent cmd/hotwheel/main.go
从启动命令我们知道入口方法在 cmd/hotwheel/main.go
package main
import (
"log"
"yummy.tech/hotwheel/cmd/hotwheel/app"
)
func main() {
cmd := app.NewHotWheelCommand()
if err := cmd.Execute(); err != nil {
log.Fatalln(err)
}
}
2. cobra 命令工具
main 里面调用了 app.NewHotWheelCommand()
func NewHotWheelCommand() *cobra.Command {
c := config.NewConfig()
cmd := &cobra.Command{
Use: "hotWheel agent",
Long: `HotWheel is one of the magic weapons of Ne Zha, a well-known legend in Chinese mythology`,
Run: func(cmd *cobra.Command, args []string) {
if errs := c.Validate(); len(errs) != 0 {
klog.Error(errors.NewAggregate(errs))
os.Exit(1)
}
if err := run(c, signals.SetupSignalHandler()); err != nil {
klog.Error(err)
os.Exit(1)
}
},
SilenceUsage: true,
}
fs := cmd.Flags()
namedFlagSets := c.Flags()
for _, f := range namedFlagSets.FlagSets {
fs.AddFlagSet(f)
}
return cmd
}
入口的函数里面用到了 cobra
的一个库,一般应用启动的时候支持用户输入一些指定的参数,而这个库的作用就是提供了丰富的命令参数。这个库在 kubernetes 源码中也有用到,就像如下 kubectl 支持 command,flag 等多种参数。
$ kubectl
kubectl controls the Kubernetes cluster manager.
Find more information at https://github.com/kubernetes/kubernetes.
Usage:
kubectl [flags]
kubectl [command]
Available Commands:
get Display one or many resources
describe Show details of a specific resource or group of resources
3.初始化业务
// 初始化对象,执行apiServer和controllerManager
func run(c *config.Config, stopCh <-chan struct{}) (err error) {
k8sClient, err := k8s.NewKubernetesClient(c.K8sOptions)
if err != nil {
klog.Errorf("failed new kubernetes client with error: %v", err)
return err
}
informerFactory := informers.NewInformerFactories(k8sClient)
apiServer, err := apiserver.NewAPIServer(c, k8sClient, informerFactory, stopCh)
...
cobra 里面触发了 run 函数,run 函数里面初始化了 k8s 客户端,informerfactory,apiserver 一个 http 服务。
4. http 服务
借助 go-restful 启动的 http 服务。
func (a *APIServer) PrepareRun() error {
// 创建一个空的Container
container := restful.NewContainer()
// 设置 过滤器 打印请求过程的 log 日志
container.Filter(logRequestAndResponse)
// 设定路由为CurlyRouter(快速路由)
container.Router(restful.CurlyRouter{})
// 配置panic产生之后的恢复处理函数
container.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
logStackOnRecover(panicReason, httpWriter)
})
// 路由注册
a.container = container
a.registerContainers()
//设置集群是否正常
handler := filter.WithHealthCheck(a.container, a.clusterClient)
//多集群转发策略
clusterDispatcher := dispatch.NewClusterDispatch(a.clusterClient)
handler = filter.WithClusterDispatcher(handler, clusterDispatcher)
//authentication 认证
//jwtAuthenticator := jwttoken.NewJwtAuthenticator()
//handler = filter.WithAuthorization(handler, jwtAuthenticator)
//获取到请求信息
requestInfoFactory := &request.RequestInfoFactory{
APIPrefixes: sets.NewString("api", "apis", "kapis", "kapi"),
GrouplessAPIPrefixes: sets.NewString("api", "kapi"),
}
handler = filter.WithRequestInfo(handler, requestInfoFactory)
a.server.Handler = handler
return nil
}
定义了路由与 handler 处理类
// 添加路由, 类似于MVC中的路由功能,在container里面有register跟handler
func (a *APIServer) registerContainers() {
urlruntime.Must(terminalv1alpha1.AddToContainer(a.container, a.clusterClient))
urlruntime.Must(clusterv1alpha1.AddToContainer(a.container, a.k8sClient.Hotwheel().ClusterV1alpha1()))
urlruntime.Must(resourcev1alpha1.AddToContainer(a.container, a.clusterClient))
}
到此,启动的主流程都已介绍完毕。下面是一些分支流程
CRD 创建
用 kubebuilder 生成 CRD 的定义
kubebuilder create api --group cluster.yummy.tech --version v1alpha1 --kind cluster
会自动生成
- pkg/apis/cluster/v1alpha1 CRD api 相关定义代码
- apk/controller/cluster CRD 对应的 Operator 实现的代码
生成 CRD 的 mainfests,也就是 yaml 文件
步骤 1 生成好了相关代码,对应的文件里面有注释提示语,指明要修改的地方,需要根据自己的需求定义 CRD 相关代码。
使用make manifests
可以快速的在examples/crds
目录下生成资源定义# Generate manifests e.g. CRD, RBAC etc. 生成 crd yaml manifests: go run ./vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/... rbac:roleName=controller-perms "crd:trivialVersions=true" output:crd:artifacts:config=examples/crds
controller-tools 是 kubernetes 的一个子项目
- code-generator 生成相关客户端代码
步骤1中的 kubebuilder 只能生成 controller 对象,可以借助 code-generator 生成其他资源。通过make client
可以在pkg/client
目录下生成clientset等信息
后期都是通过Informer list-watch 机制与k8s交互
Makefile
client:
./hack/generate_client.sh
generate_client.sh
set -o errexit
set -o nounset
set -o pipefail
set -x
SCRIPT_ROOT=$(dirname ${BASH_SOURCE})/..
CODEGEN_PKG=${SCRIPT_ROOT}/vendor/k8s.io/code-generator/generate-groups.sh
chmod +x $CODEGEN_PKG
rm -rf ${SCRIPT_ROOT}/pkg/client
# the group version
GV="cluster:v1alpha1"
# generate the code with:
# --output-base because this script should also be able to run inside the vendor dir of
# k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir
# instead of the $GOPATH directly. For normal projects this can be dropped
$CODEGEN_PKG "deepcopy,client,informer,lister" yummy.tech/hotwheel/pkg/client yummy.tech/hotwheel/pkg/apis "$GV" --output-base=./ --go-header-file=${SCRIPT_ROOT}/hack/boilerplate.go.txt
mv yummy.tech/hotwheel/pkg/client ${SCRIPT_ROOT}/pkg
mv yummy.tech/hotwheel/pkg/apis/cluster/v1alpha1/zz_generated.deepcopy.go ${SCRIPT_ROOT}/pkg/apis/cluster/v1alpha1
rm -rf yummy.tech
发表评论 取消回复