目录

记一次故障恢复

目录

今天周五了,还有半小时下班。

周一的时候,刚到公司屁股还没坐热,开发同学就在群里喊,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

到这里就完成了。