今天周五了,还有半小时下班。
周一的时候,刚到公司屁股还没坐热,开发同学就在群里喊,gitlab代码仓库无法pull/push了,我就知道,活来了。
我们的gitlab部署在k8s(阿里云ACK)上的,通过Helm安装的,前不久才升级了一波版本,还没满月,就出问题了。嘈杂声中,依稀听到有人抱怨:“gitlab怎么老坏呀!", 这话在我听来可以简单翻译为:“我们公司运维是不是废物啊”,我也不多做解释,便开始救火。期间被N个人"叮”,都问我啥时候恢复,我根本不敢点开对话框,怕他们看到我已读不回是不是对他们有什么意见,但其实我自己也不知道什么时候能够恢复,索性就不点开。
我先打开gitlab,看看具体是啥症状,发现web还是可以进的,正常登录,但是想进入项目的时候,发现都是空白的,简单明了,仓库服务坏了。打开终端一顿kubectl,发现只有gitlab-gitaly-0服务已经重启N次了,并且还在重启中,其实这个时候最快的恢复方案是直接卸载,然后重装一套,就几分钟时间就可以恢复,但是我们gitlab的存储也是helm配置的,如果用helm卸载会不会把我的pvc释放?这要是释放了的话,那我们代码就没了,说实话,我有点犹豫了。
考虑了一会儿我就放弃了,不能这么玩,风险太大了,于是我看了下这个服务的日志,看看为啥起不来,日志中内容比较少,但是有一条说某某某文件不存在,这着实给我弄蒙了,这不是闹着玩么,之前升级还是好的呀。接着就是好一顿kubectl,各种找问题,终于发现,这个服务的版本号竟然和其他服务的版本不一致,导致的微服务之间的版本冲突,直接修改版本号不太保险,于是我想到了前段时间的升级,我就用helm进行同版本号升级,不一会儿,就恢复了。
gitlab是恢复了,但是本着既治标又治本的方针,一定要根治,以防下次出现同样的问题,我开始排查,这个服务是无限重启状态,一直起不来,所以拉不到代码,那第一次重启是为什么呢,ACK有集群的事件中心(自建的集群可以装开源的事件中心),搜了一下这个pod这两天发生了什么事,发现了这个日志The node was low on resource: ephemeral-storage. Container gitaly was using 894728Ki, which exceeds its request of 0。提示这个pod临时存储搞完了,我去节点看了下,同节点的好几个pod都在容器里存日志,有一个容器本地存了10几个G的日志,这个节点上惊人的跑了别的节点的2倍pod。这个给我也整蒙了,节点资源消耗也是参差不齐,有的节点内存才用30-40%,有的节点用了102%的内存,这不出问题才怪。
问题已经清楚了,通知开发修改配置,不让他们往本地写日志,全部通过日志接口发出去,不然存容器里几天就好几个G了。把系统磁盘都搞满了,谁也别活。而节点资源使用的不均匀就不太好办了,因为需要为集群里的所有资源添加资源限制,简单描述下k8s是如何分配资源的,在pod调度的时候,调度器会经过一轮预选和一轮优选,预选是检测用户有没有特定的规则,比如指定去那个节点,比如亲和反亲和等等,提前过滤一批节点,然后进行优选打分,而打分大部分都是基于资源限制中的Request配置进行的,例如将节点所有配置了资源限制的pod都加起来,减去本次调度的资源限制,结果大的得分就高,当然这只是其中一种算法,大概有20种左右,综合下来那个节点得分高就调度去那个节点。
我们公司测试集群有上千个服务,如果一个一个改的话,估计要累死,于是自己写了个小脚本
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
|
package main
import (
"context"
"encoding/json"
"fmt"
v12 "k8s.io/api/core/v1"
resource2 "k8s.io/apimachinery/pkg/api/resource"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"log"
"os"
)
func main() {
namespace := os.Args[1]
deployName := os.Args[2]
resources := os.Args[3]
// {"limits":{"cpu":"100m","memory":"100Mi"},"requests":{"cpu":"50m","memory":"50Mi"}}
var resource Resource
err := json.Unmarshal([]byte(resources), &resource)
if err != nil{
fmt.Println("解析json失败", err.Error())
return
}
client := GetKubernetesClient()
deploy, err := client.AppsV1().Deployments(namespace).Get(context.TODO(), deployName, v1.GetOptions{})
if err != nil{
fmt.Println(err.Error())
return
}
resourcerequirment := v12.ResourceRequirements{
Limits: v12.ResourceList{
"cpu": resource2.MustParse(resource.Limits.CPU),
"memory": resource2.MustParse(resource.Limits.Memory),
},
Requests: v12.ResourceList{
"cpu": resource2.MustParse(resource.Requests.CPU),
"memory": resource2.MustParse(resource.Requests.Memory),
},
}
deploy.Spec.Template.Spec.Containers[0].Resources = resourcerequirment
_, err = client.AppsV1().Deployments(namespace).Update(context.TODO(), deploy, v1.UpdateOptions{})
if err != nil{
fmt.Println(err.Error())
return
}
fmt.Printf("deploy %s Update Success!\n", deployName)
}
type Resource struct {
Limits struct {
CPU string `json:"cpu"`
Memory string `json:"memory"`
} `json:"limits"`
Requests struct {
CPU string `json:"cpu"`
Memory string `json:"memory"`
} `json:"requests"`
}
func GetKubernetesClient() *kubernetes.Clientset {
kubeconfig := "/root/.kube/config"
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
log.Println(err)
}
Clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Println(err)
}
return Clientset
}
|
编译后传到服务器上,就可以用了,先写了一个模板,我们测试环境分很多环境,每个环境资源都是类似的,所以可以抄配置
1
2
3
4
5
6
7
8
9
|
app1 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
app2 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
app3 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
app4 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
app5 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
app6 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
app7 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
app8 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
app9 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
|
有模板后,开始跟shell结合使用
1
2
3
4
5
6
7
8
9
|
# awk '{print "./main namespace "$1" "$2}' template
./main namespace app1 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
./main namespace app2 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
./main namespace app3 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
./main namespace app4 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
./main namespace app5 \{\"limits\":\{\"cpu\":\"1\",\"memory\":\"1Gi\"\},\"requests\":\{\"cpu\":\"200m\",\"memory\":\"500Mi\"\}\}
./main namespace app6 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
./main namespace app7 \{\"limits\":\{\"cpu\":\"1\",\"memory\":\"500Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"300Mi\"\}\}
./main namespace app8 \{\"limits\":\{\"cpu\":\"100m\",\"memory\":\"100Mi\"\},\"requests\":\{\"cpu\":\"50m\",\"memory\":\"50Mi\"\}\}
|
然后复制出来粘贴一下,直接运行了。
执行完成之后检查一下集群中还有没有没有配置资源限制的服务
1
|
for j in `kubectl get ns|awk 'NR!=1{print $1}'`;do for i in `kubectl get deploy -n $j|grep -v "0/0"|awk 'NR!=1{print $1}'`;do kubectl get deploy $i -n $j -o jsonpath='{range .items[*]}{.metadata.name}{"\t\t"}{.spec.template.spec.containers[0].resources}{"\n"}';done;done
|
到这里就完成了。