原创 吴就业 167 0 2024-04-03
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/c9f6946877044771b0686eb3b4470e51
作者:吴就业
链接:https://wujiuye.com/article/c9f6946877044771b0686eb3b4470e51
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
apiserver是k8s的另一种扩展机制,相比CRD,它更为灵活。 Metrics API就是基于apiserver扩展的,关于Metrics API和apiserver的理解可以看这篇文章:Kubernetes可观测之Metrics API,什么是Metrics API?。
本篇以实战为主,介绍如何实现一个简单的apiserver。
先定义我们这个apiserver提供什么服务。
假设,我需要实现一个预测算法,预测Pod需要的cpu和内存。自定义应用部署服务(自动生成deployment资源部署应用)可以调用这个apiserver获取预测的pod的cpu和内存的requests值。自定义应用部署服务在生成deployment资源的时候为pod的容器指定cpu和内存的requests就使用这个apiserver预测的值。
相当于我们平时写业务api一样,写http接口,但是会有一些要求。
首先是路径的要求,必须是“/apis/{your group}/{your version}”开头。假设我们定义的group是:k8s.wujiuye.com,然后version是:v1beta1。那么我们的apiserver的路径必须是以”/apis/k8s.wujiuye.com/v1beta1”开头。
其次,我们必须要支持https请求,所以要生成ca证书。
先看代码:
func main() {
// (1)
r := gin.New()
group := r.Group("/apis/k8s.wujiuye.com/v1beta1")
// kubectl get --raw "/apis/k8s.wujiuye.com/v1beta1/namespaces/default/requests/hello-word"
group.GET("/namespaces/:namespaces/requests/:name", func(context *gin.Context) {
paths := strings.Split(r.URL.Path, "/")
namespace := paths[5]
name := paths[7]
data := &v1.DeploymentRequests{
Kind: "Requests",
ApiVersion: "k8s.wujiuye.com/v1beta1",
Metadata: v1.Metadata{
Name: name,
Namespace: namespace,
},
Resources: v1.Resources{
Cpu: "100m",
Memory: "100Mi",
},
}
jsonData, _ := json.Marshal(data)
w.WriteHeader(200)
w.Write(jsonData)
})
// (2)
s := &http.Server{
Addr: ":443",
Handler: r.Handler(),
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
},
}
curPath, _ := os.Getwd()
if err := s.ListenAndServeTLS(curPath+"/apiservice.pem",curPath+"/apiservice-key.pem");
err != nil && !errors.Is(err, http.ErrServerClosed) {
//....
}
}
解释下这段代码:
curPath+"/apiservice.pem"
和curPath+"/apiservice-key.pem"
是我们生成的ca证书文件的路径。后面介绍怎么生成。DeploymentRequests是我们自定义的Requests资源,不是严格意义上的CRD,只要求包含ApiVersion、Kind、Metadata即可。
type DeploymentRequests struct {
Kind string `json:"kind"`
ApiVersion string `json:"apiVersion"`
Metadata Metadata `json:"metadata"`
Resources Resources `json:"resources"`
}
type Metadata struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
}
type Resources struct {
Cpu string `json:"cpu"`
Memory string `json:"memory"`
}
怎么构建docker镜像就不举例了。
只是要注意,我们需要生成ca证书,将证书同编译后的二进制执行文件一同放到镜像中。
怎么生成证书呢?
可以参考这篇文章:Operator实战2:实现webhook修改Job的最大重试次数
我们使用cfssl工具生成ca证书。
1.安装cfssl工具,mac下使用brew安装。
brew install cfssl
2.创建一个目录,用于作为自签证书的workspace。
mkdir /tmp/ca
3.在workspace下添加以下几个配置文件。
ca-config.json:
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"server": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "87600h"
}
}
}
}
ca-csr.json:
{
"CN": "Kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "Portland",
"O": "Kubernetes",
"OU": "CA",
"ST": "Oregon"
}
]
}
server-csr.json:
{
"CN": "admission",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "Portland",
"O": "Kubernetes",
"OU": "Kubernetes",
"ST": "Oregon"
}
]
}
4.编写脚本(create_cert.sh)
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-hostname=requests-apiserver-service.default.svc \
-profile=server \
server-csr.json | cfssljson -bare apiservice
其中hostname值为“{service-name}.{service-namespace}.svc”,就是此apiserver的Service资源的名称和部署的namespace。
5.执行create_cert.sh脚本,我们将在workspace得到apiservice.pem、apiservice-key.pem,然后将这两个文件拷贝到项目中,然后在Dockerfile中把这两个文件拷贝到镜像中即可。
现在我们开始写Deployment来部署这个apiserver。
apiVersion: apps/v1
kind: Deployment
metadata:
name: requests-apiserver
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: requests-apiserver
template:
metadata:
name: requests-apiserver
namespace: default
labels:
app: requests-apiserver
spec:
containers:
- name: requests-apiserver
image: requests-apiserver:v1.0.0
imagePullPolicy: Always
ports:
- name: web-https
containerPort: 443
protocol: TCP
resources:
limits:
cpu: 200m
memory: 100Mi
requests:
cpu: 200m
memory: 100Mi
restartPolicy: Always
terminationGracePeriodSeconds: 30
其中镜像替换为我们自己打包的镜像即可。
前面第二步生成ca证书时,我们指定的hostname(requests-apiserver-service.default.svc
)就是这一步定义的Service资源。
apiVersion: v1
kind: Service
metadata:
name: requests-apiserver-service
namespace: default
spec:
type: ClusterIP
selector:
app: requests-apiserver
ports:
- name: api-service
port: 443
targetPort: 443
protocol: TCP
当前面四个步骤做完后,我们的apiserver就可以部署起来了。
但是还需要通过APIService资源将我们的apiserver注册到kube-apiserver。
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1beta1.k8s.wujiuye.com
spec:
group: k8s.wujiuye.com
groupPriorityMinimum: 20000
version: v1beta1
versionPriority: 1000
insecureSkipTLSVerify: true
service:
name: requests-apiserver-service
namespace: default
port: 443
意思是,将group为k8s.wujiuye.com,version为v1beta1的请求,转发给namespace为default、name为requests-apiserver-service的Service,并且端口号为443。
当我们将第五步生成的APIService资源apply到k8s之后,我们就可以通过kubectl命令访问接口了。
例如:
kubectl get --raw "/apis/k8s.wujiuye.com/v1beta1/namespaces/default/requests/hello-pod"
不出意外,我们会看到这个错误:
Error from server (ServiceUnavailable): the server is currently unable to handle the request
说是服务不可用。
接着我们可以通过kubectl get apiservice v1beta1.k8s.wujiuye.com -o yaml
命令查看具体原因。
status:
conditions:
- lastTransitionTime: "2024-04-03T03:44:10Z"
message: 'failing or missing response from https://10.112.1.120:443/apis/k8s.wujiuye.com/v1beta1:
bad status from https://10.112.1.120:443/apis/k8s.wujiuye.com/v1beta1:
404'
reason: FailedDiscoveryCheck
status: "False"
type: Available
意思是请求/apis/k8s.wujiuye.com/v1beta1
这个接口报404,找不到接口。
原因是,当我们apply一个APIService资源的时候,kube-apiserver会校验这个APIService是否可用,通过调用/apis/{group}/{version}
接口来验证,如果接口响应200,则说明apiserver可用,才是注册成功。
所以我们还需要简单的实现/apis/k8s.wujiuye.com/v1beta1
这个接口。
在main方法中加入:
group.GET("", func(context *gin.Context) {
context.Writer.WriteHeader(200)
context.Writer.Write([]byte("{}"))
})
自定义apiserver要求必须支持TLS,也就是实现https协议。kube-apiserver请求我们的自定义apiserver是使用https请求的。如果我们只实现http,那么就会遇到这个错误:
status:
conditions:
- lastTransitionTime: "2024-04-03T03:44:10Z"
message: 'failing or missing response from https://10.112.1.119:8080/apis/k8s.wujiuye.com/v1beta1:
Get "https://10.112.1.119:8080/apis/k8s.wujiuye.com/v1beta1":
http: server gave HTTP response to HTTPS client'
reason: FailedDiscoveryCheck
status: "False"
type: Available
如果我们使用lens工具执行kubectl get --raw
命令,那么可能会看到这个错误:
<!doctype html><html lang="en"><head><meta charset="UTF-8"><script defer="defer" src="/build/runtime.js?18572a9df0905f6f78c2"></script><script defer="defer" src="/build/lens.js?18572a9df0905f6f78c2"></script><link href="/build/lens.css?18572a9df0905f6f78c2" rel="stylesheet"></head><body><div id="app"></div><div id="terminal-init"></div></body></html>%
所以我们不能使用Lens工具执行kubectl get --raw
命令。
如果状态码是403,那就是没有权限访问资源,需要给ServiceAccount添加此apiserver提供的自定义资源的访问权限。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: xxx-role
rules:
- apiGroups:
- "k8s.wujiuye.com"
resources:
- requests
verbs:
- get
- list
- watch
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。
自定义资源的Controller创建出来的子资源,子资源创建的子资源(子子资源),如何Watch子子资源的事件?我们以MyDeployment->创建Pod->创建Event,想要watch Pod创建的Event的Create事件为例。
我们在CreateFunc、DeleteFunc、UpdateFunc方法中添加日记,发现这些方法被调用了,但却没有触发控制器的Reconcile方法执行。
我们使用自定义的调度器来调度pod,有自定义的Filter插件。Autoscaler在执行扩容之前,会调用Filter插件,尝试是不是真的没有node满足调度这个pod再去扩容。而默认情况下,Autoscaler拿的是默认的Filter插件,拿不到我们自定义的Filter插件,所以没有走我们的Filter逻辑,所以不会扩容。
本篇简单描述(Autopilot: workload autoscaling at Google)论文中描述的资源request预测算法,不需要理解论文中那复杂的数学公式。
前面《如何获取Pod的CPU和内存指标,使用Grafana Agent收集指标,上传到Prometheus》这篇介绍的指标获取只拿到了cpu使用率,怎么转成cpu使用量呢?
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。