【生产问题】K8s退出信号处理和僵尸进程问题

接上一篇容器多进程的内容延伸到僵尸进程,也是一个真实的生产问题

  1. 公司有大量的Python + Selenium爬虫服务,据开发所说一个服务有很多个并行任务
  2. 一天早上告警类似Resource temporarily unavailable的错误,对于这类问题其实只需根据ulimit -a查看各项资源即可
  3. 因为确实部分资源使用率指标,所以只能在宿主机查看缺失的资源利用情况,如果只关心进程数直接ps -aux | wc -l
  4. 僵尸进程对于多进程服务来说是常有的事,但需要通过一些自动化手段帮助k8s清理宿主机僵尸进程

一、什么是僵尸进程

通常来说就是,在 Unix-like 操作系统中已经完成执行(终止)但仍然保留在系统进程表中的进程记录。 这种状态的进程实际上已经停止运行,不占用除进程表外的任何资源,比如CPU和内存, 但它仍然保留了一个PID和终止状态信息,等待父进程通过调用wait()waitpid()函数来进行回收。

1. 生命周期

  • 子进程执行完毕后,会发送一个SIGCHLD信号给父进程,并变为僵尸状态。

  • 父进程通过wait()waitpid()读取子进程的终止状态,此时操作系统会清理僵尸进程的记录,释放其PID供其他进程使用。

Read more...

【生产问题】在容器中运行多进程服务OOMKilled未能被K8s检测识别的解决方案

这是两个月前公司的图片AI训练模型集群出现的一个生产问题,是这样的:

well-known, Python项目因为GIL普遍使用多进程代替多线程,使得container中存在1号进程之外的其他进程。

  1. 算法组的同学曾在群里反馈模型服务并没有问题,但多次跑出来的数据有缺失
  2. 开始运维方任务是算法代码问题,并没有在意,但随手发现相关的Pod内存曲线有断崖下降并且没有再回升
  3. 直觉告诉内部有进程挂了,在算法同学允许下重跑了一边服务,ps aux命令观察了一下果然若干小时候被强退,预计OOMKilled了
  4. 但主要问题是,监控系统并没有抓取到这一事件,无法发出OOMKilled告警

一、container以及Pod的状态

1. container的异常指标

总所周知,这个异常指标可以用过kube-state-metrics获得

kube_pod_container_status_terminated_reason{ container="nginx",  namespace="default", node="xxxx", pod="nginx-dep-123", reason="OOMKilled", service="kube-state-metrics"}

解读一下:意思是pod nginx-dep-123中的某个容器 nginx 的状态是terminated,并且它进入terminated状态的reason原因是因为OOMKilled

值得注意的是,kubectl get展示的status即可能是容器也可能是pod的状态。

具体可以参考这两个官方文档容器状态Pod阶段

容器状态只有三种:

  • Waiting(等待)处于Waiting状态的容器仍在运行它完成启动所需要的操作:例如从某个容器镜像仓库拉取容器镜像,或者向容器应用Secret数据等等
  • Running(运行中) 状态表明容器正在执行状态并且没有问题发生
  • Terminated(已终止) 处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。

kubectl get打印的源码可以在kubernetes\pkg\printers\internalversion\printers.go这里看printPod()方法

2. containerd如何获取容器状态的

我们都知道的Pod状态均来自于CRI,kubelet的pleg会通过cri接口获取containerd的状态信息,pleg是个大坑回头有精力可以讲。

可以直接定位到pod.Status.Reason获取的位置kubernetes\pkg\kubelet\pleg\generic.go

 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
func (g *GenericPLEG) updateCache(ctx context.Context, pod *kubecontainer.Pod, pid types.UID) (error, bool) {
	if pod == nil {
		klog.V(4).InfoS("PLEG: Delete status for pod", "podUID", string(pid))
		g.cache.Delete(pid)
		return nil, true
	}

	g.podCacheMutex.Lock()
	defer g.podCacheMutex.Unlock()
	timestamp := g.clock.Now()

	// 这里是pleg的非常重的逻辑就不展开了
	// 1. 用m.runtimeService.PodSandboxStatus获取sandbox的网络容器状态
	// 2. 再通过m.getPodContainerStatuses(uid, name, namespace)获取业务容器状态
	//    a. 这里回去调用对应的CRI GRPC接口,即(r *remoteRuntimeService) ContainerStatus(containerID string)
	// 3. 最后拼装为&kubecontainer.PodStatus
	status, err := g.runtime.GetPodStatus(ctx, pod.ID, pod.Name, pod.Namespace)
	if err != nil {
	} else {
		// ...
		status.IPs = g.getPodIPs(pid, status)
	}
	// ...

	return err, g.cache.Set(pod.ID, status, err, timestamp)
}
Read more...

【生产问题】如何将传统运维环境服务优雅地迁移至Kubernetes集群从而实现全量容器化

最近尝试着面试几家公司,偶尔会被问到传统环境如何向Kubernetes迁移的方案。

坦白说,其实这方面并不缺简单可行性高的方案,我就以屈臣氏中国的迁移方案为例,给访问本博客的同行借鉴一下。

环境的迁移,迁移的是什么?

毋庸置疑,只要外网请求全量并正常地访问Kubernetes环境,我们就可以认为实现了容器化。

流量导入可能还不够,有的公司可能想实现全面云原生,持久层也想迁移进来,涉及到数据库如何尽最大可能无缝迁移。

流量迁移

我这里直接按照阿里云传统的ECS环境迁移到自建K8s环境为例

Read more...

【生产问题】时隔大半年,分享一次Nginx反向代理的需求

博客前面分享了一篇《分享一个 Nginx 正向代理的另类应用案例》,时隔不久,身为救火队员、万金油的博主又再一次接到了一个奇葩需求:

场景和上次有些类似,也是部门引进的第三方应用,部署在各个网络区域,从 OA 办公区域无法直接访问。目前,运营人员都需要登陆 Windows 跳板机,才能打开这些应用的 WEB 控制台。既不方便,而且还有一定 Windows 服务器的维护工作量,于是找到我们团队,希望通过运维手段来解决。

拿到这个需求后,我先问了下各个应用的基本情况,得知每个应用的框架基本是一样的,都是通过 IP+端口直接访问,页面 path 也基本一样,没有唯一性。然后拿到了一个应用 WEB 控制台地址看了下,发现 html 引用的地址都是相对路径。

乍一想,这用 Nginx 代理不好弄吧?页面 path 一样,没法根据 location 来反代到不同的后端,只能通过不同 Nginx 端口来区分,那就太麻烦了!每次他们新上一个应用,我们就得多加一个新端口来映射,这种的尾大不掉、绵绵不绝事情坚决不干,Say pass。

再一想,我就想到了上次那个正向代理另类应用方案,感觉可以拿过来改改做动态代理。原理也简单:先和用户约定一个访问形式,比如:

Nginx 代理地址为 myproxy.oa.com,需要代理到 IP 为 192.168.2.100:8080 的控制器,用户需要访问 http://myproxy.oa.com/192.168.2.100:8080/path

Read more...

【生产问题】分享一次Nginx正向代理的需求

最近接到了一个需求:通过 Nginx 代理把现网一个自研代理程序给替换掉,感觉有点意思,也有所收益,简单分享下。

需求背景

部门的生产环境异常复杂,有部分第三方引入的系统位于特殊网络隔离区域,请求这些系统需要通过 2 层网络代理,如图所示:

12

中心源系统请求目标系统 API 的形式各异,我简单收集了下,至少有如下 3 种:

1
2
3
4
5
curl --digest -u admin:xxxxxx 'http://10.xxx.xxx.xxx:8080/foo/boo?Id=123456789&vId=1234' 
 
curl -d '{"eventId": 20171116, "timestamp": 123456, "caller": "XXP", "version": "1.0", "interface": {"interfaceName": "XXPVC", "para": {"detail": {"owner": "xxxxxxx"}}}, "password": "xxxxxx", "callee": "XXPVC"}' http://10.x.x.x:8080/t/api
 
curl -X PUT -H "Content-Type: application/json" -d'{"vp":{"id":"ab27adc8-xxx-xxxx-a732-fbde162ebdd3"}}' "http://10.x.x.x/v1.0/peers/show_connectioninfos"
Read more...