栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

[ docker-ce源码分析系列 ]docker exec残留sh进程的原因

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

[ docker-ce源码分析系列 ]docker exec残留sh进程的原因

1 概述: 1.1 环境

版本信息如下:
a、操作系统: centos 7.6,amd64
b、服务器docker版本:v18.09.2
c、docker的存储驱动: overlay2

2 现象:

执行 [ docker exec -it 容器ID sh ]命令,用户可在容器中执行shell指令进行各种操作,此时用户直接kill掉docker exec命令,或者直接关闭xshell,则该sh进程依然残留在容器中,这种sh进程会消耗虚拟终端的数量,本质上是消耗文件描述符。如果用户退出时不是通过执行exit指令,这种方式会导致操作系统上的虚拟终端数消耗完毕,需要手动杀死残留在容器中的sh进程或增大内核的虚拟终端最大数量。

查看当前系统正在使用的虚拟终端数量的命令:

sysctl kernel.pty.nr

修改虚拟终端数量最大值的命令:

sysctl -w kernel.pty.max=8192

3 源码简析:

通过docker源码,分析目标进程(sh进程)为什么会残留。杀死进程,肯定是通过发送系统信号TERM或信号KILL,个人估计发送系统信号相关的业务逻辑应该是在错误处理代码块中。

3.1 服务端注册路由initRoutes()
func (r *containerRouter) initRoutes() {
	r.routes = []router.Route{
		
		// 本方法主要是返回一个exec ID,客户端会再发起这样的请求:/v1.39/exec/{exec ID}/start
		router.NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
		router.NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
	}
}

3.2 func (s *containerRouter) postContainerExecStart(…)

本方法的核心方法是s.backend.ContainerExecStart(…)。

func (s *containerRouter) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

	
	
	// ContainerExecStart(...)会一直阻塞,直到docker exec命令关闭
	if err := s.backend.ContainerExecStart(context.Background(), execName, stdin, stdout, stderr); err != nil {
		if execStartCheck.Detach {
			return err
		}
		stdout.Write([]byte(err.Error() + "rn"))
		logrus.Errorf("Error running exec %s in container: %v", execName, err)
	}
	return nil
}

3.3 func (d *Daemon) ContainerExecStart(…)

docker exec意外退出(通过直接关闭xshell窗口或者kill命令),或者在容器中执行exit指令,dockerd都不会调用containerd的接口来给目标进程发送系统信号,因此导致目标进程的残留。

func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (err error) {
	

	// attachErr是一个管道,用于后续select语句
	attachErr := ec.StreamConfig.CopyStreams(ctx, &attachConfig)

	// d.containerd.Exec()是通过grpc调用containerd进程的接口,以在目标容器中创建进程(例如sh)
	// systemPid是容器中新建的进程在root pid namespace中的pid
	systemPid, err := d.containerd.Exec(ctx, c.ID, ec.ID, p, cStdin != nil, ec.InitializeStdio)
	
	

	// 一直select语句阻塞
	// docker exec退出,或者在容器中执行exit指令,管道attachErr都会返回nil值
	select {
	case <-ctx.Done():
		// 本方法的入参ctx对象,来自context.Background(),因此分支是没机会进入。
		// 本方法的入参ctx对象,来自context.Background(),因此分支是没机会进入。
		// 本方法的入参ctx对象,来自context.Background(),因此分支是没机会进入。
		
		
		return ctx.Err()
		
	case err := <-attachErr:
		// 此分支没有实际的业务操作,只是日志记录。
		// 因此目标进程(例如sh进程)不会接收到任何系统信号,残留在了容器中。
		if err != nil {
			if _, ok := err.(term.EscapeError); !ok {
				return errdefs.System(errors.Wrap(err, "exec attach failed"))
			}
			attributes := map[string]string{
				"execID": ec.ID,
			}
			d.LogContainerEventWithAttributes(c, "exec_detach", attributes)
		}
	}

	return nil
}

docker exec意外退出(通过直接关闭xshell窗口或者kill命令),或者在容器中执行exit指令,管道attachErr都会返回nil值


4 总结:

用户通过docker exec命令进入容器创建的sh进程,dockerd在代码层面是没有机会往sh进程发送系统信号(虽然containerd进程已经暴露了往进程发送信号的接口),因此sh进程不执行exit指令,是会残留在操作系统中的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/312540.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号