專一於大數據及容器雲核心技術解密,可提供全棧的大數據+雲原平生臺諮詢方案,請持續關注本套博客。若有任何學術交流,可隨時聯繫。更多內容請關注《數據雲技術社區》公衆號。 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