Docker基础之containerd的shim
一、shimcontainerd下的shim是充当containerd和runc之间的中间件,用来组装runc命令的参数,负责容器中进程的启动。containerd中调用shim时执行的命令如下:/root/lib-containerd/containerd/bin/containerd-shim {containerID} {bundleDirPath} runc其中三个参数如下:1、Arg0:
·
一、shim
containerd下的shim是充当containerd和runc之间的中间件,用来组装runc命令的参数,负责容器中进程的启动。
containerd中调用shim时执行的命令如下:
/root/lib-containerd/containerd/bin/containerd-shim {containerID} {bundleDirPath} runc
其中三个参数如下:
1、Arg0:容器Id
2、Arg1:路径
3、Arg2:执行时间
二、主函数main()
func main() {
flag.Parse()
cwd, err := os.Getwd() //获取当前进程目录cwd, /run/docker/libcontainerd/containerd/{containerID}/init
if err != nil {
panic(err)
}
//打开shim的日志文件:/var/run/docker/libcontainerd/containerd/{containerID}/init/shim-log.json
f, err := os.OpenFile(filepath.Join(cwd, "shim-log.json"), os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666)
if err != nil {
panic(err)
}
//启动容器
if err := start(f); err != nil {
//启动失败时,把错误信息err记录到shim的log文件f中
if err == errRuntime {
f.Close()
return
}
//记录错误,而不是写入到stderr,因为垫片将有/dev/null,因为它是stdio,因为它应该被重新创建到系统init,并没有任何人从它读取
writeMessage(f, "error", err)
f.Close()
os.Exit(1)
}
}
三、start()函数
核心步骤是基于containerID,bundle,runtimeName 构建一个进程对象,即type process struct。
然后p.create()
调用runc命令启动进程。
func start(log *os.File) error {
// 尽快开始处理信号,以便正确地获取信息,或者在我们执行处理程序之前运行时退出
signals := make(chan os.Signal, 2048)
signal.Notify(signals)
// 将shim设置为容器创建的所有孤立进程的子进程回收
if err := osutils.SetSubreaper(1); err != nil {
return err
}
// 打开exit管道(p),用来发送退出信号
f, err := os.OpenFile("exit", syscall.O_WRONLY, 0)
if err != nil {
return err
}
defer f.Close()
//打开control管道,可发送和接收容器控制信息:/var/run/docker/libcontainerd/containerd/redis/init/control
control, err := os.OpenFile("control", syscall.O_RDWR, 0)
if err != nil {
return err
}
defer control.Close()
//基于containerID,bundle,runtimeName 构建一个进程对象,即process构造函数
p, err := newProcess(flag.Arg(0), flag.Arg(1), flag.Arg(2))
if err != nil {
return err
}
defer func() {
if err := p.Close(); err != nil {
writeMessage(log, "warn", err)
}
}()
//创建进程,调用`runc`命令
if err := p.create(); err != nil {
p.delete()
return err
}
//创建控制信息通道
msgC := make(chan controlMessage, 32)
go func() {
//一个死循环,用于接收信号:将control管道的信息格式化写入channel controlMessage
for {
var m controlMessage
if _, err := fmt.Fscanf(control, "%d %d %d\n", &m.Type, &m.Width, &m.Height); err != nil {
continue
}
msgC <- m
}
}()
var exitShim bool
for {
select {
case s := <-signals: //监听信号通道signals
switch s {
case syscall.SIGCHLD:
exits, _ := osutils.Reap()
for _, e := range exits {
// check to see if runtime is one of the processes that has exited
if e.Pid == p.pid() {
exitShim = true
writeInt("exitStatus", e.Status)
}
}
}
// 运行时已经退出,所以垫片也可以退出
if exitShim {
f.Close()
p.Wait()
return nil
}
case msg := <-msgC:
//监听控制信息通道 controlMessage:0为关闭进程输入,1为调整窗口大小
switch msg.Type {
case 0:
// 关闭标准输入输出
if p.stdinCloser != nil {
p.stdinCloser.Close()
}
case 1:
if p.console == nil {
continue
}
ws := term.Winsize{
Width: uint16(msg.Width),
Height: uint16(msg.Height),
}
term.SetWinsize(p.console.Fd(), &ws)
}
}
}
return nil
}
四、process构造方法
type process struct {
sync.WaitGroup
id string //进程编号
bundle string
stdio *stdio //标准输入输出
exec bool
containerPid int //容器进程ID
checkpoint *checkpoint
checkpointPath string
shimIO *IO
stdinCloser io.Closer
console *os.File
consolePath string
state *processState
runtime string
}
1、newProcess()函数
func newProcess(id, bundle, runtimeName string) (*process, error) {
p := &process{
id: id,
bundle: bundle,
runtime: runtimeName,
}
//读取该进程的process.json文件,得到一个type processState struct对象
s, err := loadProcess()
if err != nil {
return nil, err
}
p.state = s
if s.CheckpointPath != "" {
cpt, err := loadCheckpoint(s.CheckpointPath)
if err != nil {
return nil, err
}
p.checkpoint = cpt
p.checkpointPath = s.CheckpointPath
}
//打开进程的输入输出端
if err := p.openIO(); err != nil {
return nil, err
}
return p, nil
}
2、create()函数
func (p *process) create() error {
cwd, err := os.Getwd()
if err != nil {
return err
}
//设置runc日志路径
logPath := filepath.Join(cwd, "log.json")
//开始根据不同情况组装`runc`的参数
args := append([]string{
"--log", logPath,
"--log-format", "json",
}, p.state.RuntimeArgs...)
//`docker exec`时标记为true了
if p.state.Exec {
args = append(args, "exec",
"-d",
"--process", filepath.Join(cwd, "process.json"),
"--console", p.consolePath,
)
} else if p.checkpoint != nil {
args = append(args, "restore",
"--image-path", p.checkpointPath,
"--work-path", filepath.Join(p.checkpointPath, "criu.work", "restore-"+time.Now().Format(time.RFC3339)),
)
add := func(flags ...string) {
args = append(args, flags...)
}
if p.checkpoint.Shell {
add("--shell-job")
}
if p.checkpoint.TCP {
add("--tcp-established")
}
if p.checkpoint.UnixSockets {
add("--ext-unix-sk")
}
if p.state.NoPivotRoot {
add("--no-pivot")
}
for _, ns := range p.checkpoint.EmptyNS {
add("--empty-ns", ns)
}
} else {
//初始化进程走这通道
args = append(args, "create",
"--bundle", p.bundle,
"--console", p.consolePath,
)
if p.state.NoPivotRoot {
args = append(args, "--no-pivot")
}
}
args = append(args,
"--pid-file", filepath.Join(cwd, "pid"),
p.id,
)
//构造`runc` 命令
cmd := exec.Command(p.runtime, args...)
cmd.Dir = p.bundle
cmd.Stdin = p.stdio.stdin
cmd.Stdout = p.stdio.stdout
cmd.Stderr = p.stdio.stderr
//调用setPDeathSig来设置SysProcAttr作为特定平台的元素
cmd.SysProcAttr = setPDeathSig()
//开始`runc` 命令
if err := cmd.Start(); err != nil {
if exErr, ok := err.(*exec.Error); ok {
if exErr.Err == exec.ErrNotFound || exErr.Err == os.ErrNotExist {
return fmt.Errorf("%s not installed on system", p.runtime)
}
}
return err
}
p.stdio.stdout.Close()
p.stdio.stderr.Close()
//等待命令执行完毕
if err := cmd.Wait(); err != nil {
if _, ok := err.(*exec.ExitError); ok {
return errRuntime
}
return err
}
//读取pid文件信息,得到进程pid数据,pid文件中信息由runc写入
data, err := ioutil.ReadFile("pid")
if err != nil {
return err
}
//string类型转int类型
pid, err := strconv.Atoi(string(data))
if err != nil {
return err
}
//回填容器的属性pid
p.containerPid = pid
return nil
}
更多推荐
已为社区贡献3条内容
所有评论(0)