专一于大数据及容器云核心技术解密,可提供全栈的大数据+云原平生台咨询方案,请持续关注本套博客。若有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号。 linux
create.go:
setupSpec(context)
utils_linux.go:
startContainer(context, spec, CT_ACT_CREATE, nil)
|- createContainer
|- specconv.CreateLibcontainerConfig
|- loadFactory(context)
|- libcontainer.New(......)
|- factory.Create(id, config)
复制代码
使用 create 命令建立容器
sudo runc create mybusybox
复制代码
/* utils/utils_linux.go */
func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
.....
return libcontainer.New(abs, cgroupManager, intelRdtManager,
libcontainer.CriuPath(context.GlobalString("criu")),
libcontainer.NewuidmapPath(newuidmap),
libcontainer.NewgidmapPath(newgidmap))
}
复制代码
/* libcontainer/factory_linux.go */
func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
.....
l := &LinuxFactory{
.....
InitPath: "/proc/self/exe",
InitArgs: []string{os.Args[0], "init"},
}
......
return l, nil
}
复制代码
func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) {
....
c := &linuxContainer{
id: id,
config: config,
initPath: l.InitPath,
initArgs: l.InitArgs,
}
.....
return c, nil
}
复制代码
|- runner.run(spec.Process)
|- newProcess(*config, r.init)
|- r.container.Start(process)
|- c.createExecFifo()
|- c.start(process)
|- c.newParentProcess(process)
|- parent.start()
复制代码
/* libcontainer/factory_linux.go */
func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
.....
l := &LinuxFactory{
.....
InitPath: "/proc/self/exe",
InitArgs: []string{os.Args[0], "init"},
}
......
return l, nil
}
/* libcontainer/factory_linux.go */
func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
.....
l := &LinuxFactory{
.....
InitPath: "/proc/self/exe",
InitArgs: []string{os.Args[0], "init"},
}
......
return l, nil
}
func (r *runner) run(config *specs.Process) (int, error) {
......
process, err := newProcess(*config, r.init) /* 第1部分 */
......
switch r.action {
case CT_ACT_CREATE:
err = r.container.Start(process) /* runc start */ /* 第2部分 */
case CT_ACT_RESTORE:
err = r.container.Restore(process, r.criuOpts) /* runc restore */
case CT_ACT_RUN:
err = r.container.Run(process) /* runc run */
default:
panic("Unknown action")
}
......
return status, err
}
复制代码
func (c *linuxContainer) Start(process *Process) error {
if process.Init {
if err := c.createExecFifo(); err != nil { /* 1.建立fifo */
return err
}
}
if err := c.start(process); err != nil { /* 2. 调用start() */
if process.Init {
c.deleteExecFifo()
}
return err
}
return nil
}
复制代码
func (c *linuxContainer) start(process *Process) error {
parent, err := c.newParentProcess(process) /* 1. 建立parentProcess */
err := parent.start(); /* 2. 启动这个parentProcess */
......
func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
parentPipe, childPipe, err := utils.NewSockPair("init") /* 1.建立 Socket Pair */
cmd, err := c.commandTemplate(p, childPipe) /* 2. 建立 *exec.Cmd */
if !p.Init {
return c.newSetnsProcess(p, cmd, parentPipe, childPipe)
}
if err := c.includeExecFifo(cmd); err != nil { /* 3.打开以前建立的fifo */
return nil, newSystemErrorWithCause(err, "including execfifo in cmd.Exec setup")
}
return c.newInitProcess(p, cmd, parentPipe, childPipe) /* 4.建立 initProcess */
}
- includeExecFifo() 方法打开以前建立的 fifo,也将其 fd 放到 cmd.ExtraFiles 中,同时将_LIBCONTAINER_FIFOFD=%d记录到 cmd.Env。
- 建立 InitProcess 了,这里首先将_LIBCONTAINER_INITTYPE="standard"加入cmd.Env,而后从 configs 读取须要新的容器建立的 Namespace 的类型,并将其打包到变量 data 中备用,最后再建立 InitProcess 本身,能够看到,这里将以前的一些资源和变量都联系了起来
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) {
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard))
nsMaps := make(map[configs.NamespaceType]string)
for _, ns := range c.config.Namespaces {
if ns.Path != "" {
nsMaps[ns.Type] = ns.Path
}
}
_, sharePidns := nsMaps[configs.NEWPID]
data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps)
if err != nil {
return nil, err
}
return &initProcess{
cmd: cmd,
childPipe: childPipe,
parentPipe: parentPipe,
manager: c.cgroupManager,
intelRdtManager: c.intelRdtManager,
config: c.newInitConfig(p),
container: c,
process: p, /* sleep 5 在这里 */
bootstrapData: data,
sharePidns: sharePidns,
}, nil
}
复制代码
/* libcontainer/process_linux.go */
func (p *initProcess) start() error {
p.cmd.Start()
p.process.ops = p
io.Copy(p.parentPipe, p.bootstrapData)
.....
}
/proc/self/exe 正是runc程序本身,因此这里至关因而执行runc init,也就是说,
咱们输入的是runc create命令,隐含着又去建立了一个新的子进程去执行runc init。
为何要额外从新建立一个进程呢?缘由是咱们建立的容器极可能须要运行
在一些独立的 namespace 中,好比 user namespace,这是经过 setns() 系统调用完成的,
复制代码
libcontainer/process_linux.go:282
func (p *initProcess) start() error {
// 当前执行空间进程称为bootstrap进程
// 启动了 cmd,即启动了 runc init 命令,建立 runc init 子进程
// 同时也激活了C代码nsenter模块的执行(在runc create namespace 中设置 clone 了三个进程parent、child、init))
// C 代码执行后返回 go 代码部分,最后的 init 子进程为了好区分此处命名为" nsInit "(即配置了Namespace的init)
// 后执行go代码(init.go)--->初始化其它部分(网络、rootfs、路由、主机名、console、安全等)
err := p.cmd.Start() // +runc init 命令执行,Namespace应用代码执行空间时机
//...
if p.bootstrapData != nil {
// 将 bootstrapData 写入到 parent pipe 中,此时 runc init 能够从 child pipe 里读取到这个数据
if _, err := io.Copy(p.messageSockPair.parent, p.bootstrapData); err != nil {
return newSystemErrorWithCause(err, "copying bootstrap data to pipe")
}
}
//...
}
复制代码
package nsenter
/*
/* nsenter.go */
#cgo CFLAGS: -Wall
extern void nsexec();
void __attribute__((constructor)) init(void) {
nsexec();
}
*/
import "C"
void nsexec(void)
{
/*
* If we don't have an init pipe, just return to the go routine. * We'll only get an init pipe for start or exec.
*/
pipenum = initpipe();
if (pipenum == -1)
return;
/* Parse all of the netlink configuration. */
nl_parse(pipenum, &config);
update_oom_score_adj(config.oom_score_adj, config.oom_score_adj_len);
....
}
复制代码
init.go
import (
"os"
"runtime"
"github.com/opencontainers/runc/libcontainer"
_ "github.com/opencontainers/runc/libcontainer/nsenter"
"github.com/urfave/cli"
)
factory, _ := libcontainer.New("")
if err := factory.StartInitialization(); err != nil {
}
复制代码
void nsexec(void)
{
.....
/* Pipe so we can tell the child when we've finished setting up. */ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sync_child_pipe) < 0) // sync_child_pipe is an out parameter bail("failed to setup sync pipe between parent and child"); /* * We need a new socketpair to sync with grandchild so we don't have
* race condition with child.
*/
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sync_grandchild_pipe) < 0)
bail("failed to setup sync pipe between parent and grandchild");
}
复制代码
namespaces在runc init 2完成建立
runc init 1和runc init 2最终都会执行exit(0),
但runc init 3不会,它会继续执行runc init命令的后半部分。
所以最终只会剩下runc create进程和runc init 3进程
switch (setjmp(env)) {
case JUMP_PARENT:{
.....
clone_parent(&env, JUMP_CHILD);
.....
}
case JUMP_CHILD:{
......
if (config.namespaces)
join_namespaces(config.namespaces);
clone_parent(&env, JUMP_INIT);
......
}
case JUMP_INIT:{
}
复制代码
详情参考:https://segmentfault.com/a/1190000017576314
复制代码
func (p *initProcess) start() error {
......
p.execSetns()
fds, err := getPipeFds(p.pid())
p.setExternalDescriptors(fds)
p.createNetworkInterfaces()
p.sendConfig()
parseSync(p.parentPipe, func(sync *syncT) error {
switch sync.Type {
case procReady:
.....
writeSync(p.parentPipe, procRun);
sentRun = true
case procHooks:
.....
// Sync with child.
err := writeSync(p.parentPipe, procResume);
sentResume = true
}
return nil
})
......
/* libcontainer/init_linux.go */
func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd int) (initer, error) {
var config *initConfig
/* read config from pipe (from runc process) */
son.NewDecoder(pipe).Decode(&config);
populateProcessEnvironment(config.Env);
switch t {
......
case initStandard:
return &linuxStandardInit{
pipe: pipe,
consoleSocket: consoleSocket,
parentPid: unix.Getppid(),
config: config, // <=== config
fifoFd: fifoFd,
}, nil
}
return nil, fmt.Errorf("unknown init type %q", t)
}
runc create runc init 3
| |
p.sendConfig() --- config --> NewContainerInit()
复制代码
/* init.go */
func (l *LinuxFactory) StartInitialization() (err error) {
......
i, err := newContainerInit(it, pipe, consoleSocket, fifofd)
return i.Init()
}
func (l *linuxStandardInit) Init() error {
......
name, err := exec.LookPath(l.config.Args[0])
syscall.Exec(name, l.config.Args[0:], os.Environ())
}
复制代码
func (l *linuxStandardInit) Init() error {
......
name, err := exec.LookPath(l.config.Args[0])
syscall.Exec(name, l.config.Args[0:], os.Environ())
}
复制代码
namespace建立源码比较深奥,再次总结于此,留记!!!git
专一于大数据及容器云核心技术解密,可提供全栈的大数据+云原平生台咨询方案,请持续关注本套博客。若有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号。 github