利用 DynamicClient 来做一个演示.
需求背景
kubernetes
集群里面很多服务处于不健康状态,如镜像拉取失败,pod
一直重复拉取,业务侧也无反馈服务不可用问题,说明这些服务是可以干掉的,因此开发本应用来自动消除这些不健康的服务,以 cronjob
的形式跑在集群内,定时运行,减少人力维护成本。
需求简述
-
并不是所有命名空间和 pod 都需要,pod 状态也不止镜像拉取错误,因此配置文件提供了:命名空间,持续时间,pod 失败状态的选择。
-
持续时间是 pod 创建开始到当前,目前没有去获取异常开始到当前的时间(后面研究有没有方法)。
-
cronjob 的形式运行,后期需求如有变动可以用其他形式。
需求实现
1. 项目目录结构
目录结构在 ClientSet 实战 那里有说过,基本就是复制粘贴过来的,这里不做其他赘述。
1
2
3
4
5
6
|
auto-setzero
├─conf
├─pkg
│ ├─clientset
│ └─utils
└─service
|
2. 初始化 ClientSet
详情翻阅 ClientSet 实战
信息
初始化 clientset 实例的时候,首先使用 inCluster 模式(需要去配置对应的 RBAC 权限,默认的 sa 是 default-> 是没有获取 nodes 的 List 权限)
3. utils 小工具
pkg/utils 目录下新建 utils.go , 里面存放的函数主要用来处理时间对比和对 pod 各种状态的解析的,贴上部分代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
package utils
import (
"time"
"fmt"
)
// 时间比较
func CompareTime(t1, t2 string, duration string) bool {
time1, err := time.Parse("2006-01-02 15:04:05", t1)
if err != nil {
fmt.Println("时间转化出错了.")
}
time2, err := time.Parse("2006-01-02 15:04:05", t2)
if err != nil {
fmt.Println("时间转化出错了.")
}
HourAfter, _ := time.ParseDuration(duration)
time1 = time1.Add(HourAfter)
// true 表示在 HourAfter 时间内,false 表示过了 HourAfter 小时
return time2.Before(time1)
}
// 解析pod状态
func podStatusPhase(po *v1.Pod) string {
status := string(po.Status.Phase)
if po.Status.Reason != "" {
if po.DeletionTimestamp != nil && po.Status.Reason == "NodeLost" {
return "Unknown"
}
status = po.Status.Reason
}
status, ok := initContainerPhase(po.Status, len(po.Spec.InitContainers), status)
if ok {
return status
}
status, ok = containerPhase(po.Status, status)
if ok && status == Completed {
status = Running
}
if po.DeletionTimestamp == nil {
return status
}
return Terminating
}
// 解析容器状态
func containerPhase(st v1.PodStatus, status string) (string, bool) {
var running bool
for i := len(st.ContainerStatuses) - 1; i >= 0; i-- {
cs := st.ContainerStatuses[i]
switch {
case State.Waiting != nil && State.Waiting.Reason != "":
status = State.Waiting.Reason
case State.Terminated != nil && State.Terminated.Reason != "":
status = State.Terminated.Reason
case State.Terminated != nil:
if State.Terminated.Signal != 0 {
status = "Signal:" + strconv.Itoa(int(State.Terminated.Signal))
} else {
status = "ExitCode:" + strconv.Itoa(int(State.Terminated.ExitCode))
}
case Ready && State.Running != nil:
running = true
}
}
return status, running
}
// 解析初始化容器状态
func initContainerPhase(st v1.PodStatus, initCount int, status string) (string, bool) {
for i, cs := range st.InitContainerStatuses {
s := checkContainerStatus(cs, i, initCount)
if s == "" {
continue
}
return s, true
}
return status, false
}
// 检查初始化容器有没有错误
func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string {
switch {
case State.Terminated != nil:
if State.Terminated.ExitCode == 0 {
return ""
}
if State.Terminated.Reason != "" {
return "Init:" + State.Terminated.Reason
}
if State.Terminated.Signal != 0 {
return "Init:Signal:" + strconv.Itoa(int(State.Terminated.Signal))
}
return "Init:ExitCode:" + strconv.Itoa(int(State.Terminated.ExitCode))
case State.Waiting != nil && State.Waiting.Reason != "" && State.Waiting.Reason != "PodInitializing":
return "Init:" + State.Waiting.Reason
default:
return "Init:" + strconv.Itoa(i) + "/" + strconv.Itoa(initCount)
}
}
// 序列化pod的ready, terminating, restartcount次数
func Statuses(ss []v1.ContainerStatus) (cr, ct, rc int) {
for _, c := range ss {
if c.State.Terminated != nil {
ct++
}
if c.Ready {
cr = cr + 1
}
rc += int(c.RestartCount)
}
return
}
|
4. 配置主程序
在 service 目录下新建 auto_setzero_service.go ,这里主要讲下代码的思路:
-
先收集 podList 的信息,遍历出每个 pod
-
判断 pod 当前状态和创建时间距离现在有多久,时间过长肯定是没人理的
-
判断服务是 deployment 类型还是 rollouts 类型
-
根据不通服务类型执行不同的消除 pod 函数
注意
这里设置 merge
patch 类型 types.MergePatchType
如果要 SSA
patch 类型,需要改为 types.ApplyPatchType
这里主要展示 rollouts 类型的处理函数,因为它需要用到动态客户端去执行
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 设置 rollouts pod 数量设置为0
func (c *clients) setRolloutZero(namespace, rolloutsName string) error {
gvr := schema.GroupVersionResource{Group: "argoproj.io", Version: "v1alpha1", Resource: "rollouts"}
patchData := []byte(`{"spec": {"replicas": 0}}`)
_, err := c.dynamicClient.
Resource(gvr).
Namespace(namespace).
Patch(context.TODO(), rolloutsName, types.MergePatchType, patchData, metav1.PatchOptions{})
if err != nil {
return err
}
return nil
}
|
5. main 函数启动
写好逻辑代码后,我们在 main 入口函数出引入,具体步骤如下:
-
先初始化配置文件
-
再执行 AutoSetZero 程序
1
2
3
4
5
6
7
8
9
10
11
12
|
func main() {
// 初始化配置文件
cf.InitConfig()
InitKubeConfig()
clientset := GetClientSet()
dynamicClient := GetDynamicClient()
// 执行程序
client := service.NewClients(clientset, dynamicClient)
client.AutoSetZero()
}
|
代码编译
假设你本地已经装好了golang
,然后设置为 go module
模式
1. 环境配置
如果你是 windows
系统,需要交叉编译得先设置 go env -w GOOS=linux
1
2
3
4
5
6
7
|
go env -w CGO_ENABLED=0
go env -w GO111MODULE=on
go env -w GOARCH=amd64
go env -w GOOS=linux
go mod init auto-setzero
go mod tidy
go build -o auto_setzero --tags netgo -ldflags="-w -s"
|
注意
编译完后打成 Docker
镜像,推送到你的镜像仓,以供后面步骤使用(cicd 自行设计)。
2. 配置文件格式
根据 node
节点 pod
数量自动对节点设置污点,使其不再调度 pod
进入,pod
数量 max
,min
可以在 config.yml
里面设置,当 pod
大于 max
时,打污点,小于 min
时解除污点,例如:
1
2
3
4
5
|
errImagePull:
namespaces: default
hours: 168h
status: ErrImagePull,ImagePullBackOff
enable: true
|
部署到集群
在具有 kubernetes config
配置文件的机器上部署,默认读取当前用户 .kube/config
文件,如文件不在该路径,可以通过 --kubeconfig filepath
指定,如不想这么调度,可以创建具有权限的 sa
账号给程序使用
新建 yaml
文件 auto-setzero.yml
,cronjob
时间和 pod
数量自行根据场景修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: auto-setzero
namespace: kube-system
spec:
concurrencyPolicy: Allow
failedJobsHistoryLimit: 1
jobTemplate:
metadata:
creationTimestamp: null
labels:
app: auto-setzero
spec:
template:
metadata:
creationTimestamp: null
labels:
app: auto-setzero
spec:
containers:
- command:
- /opt/auto_setzero
image: your-docker-image-repositry/auto_setzero:v1.0
workingDir: /opt
imagePullPolicy: Always
name: auto-setzero
resources:
requests:
cpu: 1
memory: 500Mi
limits:
cpu: 1
memory: 500Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /root/.kube
name: kubeconfig
readOnly: true
- mountPath: /etc/localtime
name: time-name
- mountPath: /opt/config.yml
name: config
subPath: config.yml
dnsPolicy: ClusterFirst
nodeSelector:
node-role.kubernetes.io/master: ""
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
restartPolicy: OnFailure
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- hostPath:
path: /root/.kube
type: DirectoryOrCreate
name: kubeconfig
- hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
type: ""
name: time-name
- name: config
configMap:
name: auto-setzero-cm
items:
- key: config.yml
path: config.yml
schedule: "*/5 * * * *"
successfulJobsHistoryLimit: 3
suspend: false
|
配置 configmap:
1
2
3
4
5
6
7
8
9
10
11
12
|
apiVersion: v1
data:
config.yml: |
errImagePull:
namespaces: default
hours: 168h
status: ErrImagePull,ImagePullBackOff
enable: true
kind: ConfigMap
metadata:
name: auto-setzero-cm
namespace: kube-system
|
最后执行:kubectl apply -f 配置文件
应用到集群。
至此,DynamicClient 实战到此分享结束,有兴趣的朋友可以自行尝试。