背景
runc create 创建一个容器
本文针对runc v1.1.11源码
create源码
前置
main.go中
程序入口
func main() {
...
app := cli.NewApp()
...
添加子命令
app.Commands = []cli.Command{
...
createCommand,
...
}
...
if err := app.Run(os.Args); err != nil {
fatal(err)
}
}
create.go中
var createCommand = cli.Command{
...
启动容器
status, err := startContainer(context, CT_ACT_CREATE, nil)
...
}
utils_linux.go中
func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
...
加载spec
spec, err := setupSpec(context)
if err != nil {
return -1, err
}
...
创建容器
container, err := createContainer(context, id, spec)
if err != nil {
return -1, err
}
...
启动
r := &runner{
enableSubreaper: !context.Bool("no-subreaper"),
shouldDestroy: !context.Bool("keep"),
container: container,
listenFDs: listenFDs,
notifySocket: notifySocket,
consoleSocket: context.String("console-socket"),
detach: context.Bool("detach"),
pidFile: context.String("pid-file"),
preserveFDs: context.Int("preserve-fds"),
action: action,
criuOpts: criuOpts,
init: true,
}
return r.run(spec.Process)
...
}
createContainer相关
utils_linux.go中
func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
...
配置转换
config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
CgroupName: id,
UseSystemdCgroup: context.GlobalBool("systemd-cgroup"),
NoPivotRoot: context.Bool("no-pivot"),
NoNewKeyring: context.Bool("no-new-keyring"),
Spec: spec,
RootlessEUID: os.Geteuid() != 0,
RootlessCgroups: rootlessCg,
})
...
加载工厂对象
factory, err := loadFactory(context)
if err != nil {
return nil, err
}
创建容器
return factory.Create(id, config)
...
}
加载工厂对象
func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
...
return libcontainer.New(abs,
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) {
if root != "" {
if err := os.MkdirAll(root, 0o700); err != nil {
return nil, err
}
}
l := &LinuxFactory{
Root: root,
InitPath: "/proc/self/exe",
InitArgs: []string{os.Args[0], "init"},
Validator: validate.New(),
CriuPath: "criu",
}
...
return l, nil
}
func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) {
...
创建cgroup manager
cm, err := manager.New(config.Cgroups)
if err != nil {
return nil, err
}
...
构建容器对象,状态为stopped
c := &linuxContainer{
id: id,
root: containerRoot,
config: config,
initPath: l.InitPath,
initArgs: l.InitArgs,
criuPath: l.CriuPath,
newuidmapPath: l.NewuidmapPath,
newgidmapPath: l.NewgidmapPath,
cgroupManager: cm,
intelRdtManager: intelrdt.NewManager(config, id, ""),
}
c.state = &stoppedState{c: c}
...
}
runner.run相关
utils_linux.go中
func (r *runner) run(config *specs.Process) (int, error) {
...
创建process
process, err := newProcess(*config)
if err != nil {
return -1, err
}
...
switch r.action {
err = r.container.Start(process)
...
}
libcontainer/container_linux.go中
func (c *linuxContainer) Start(process *Process) error {
...
创建execfifo
if process.Init {
if err := c.createExecFifo(); err != nil {
return err
}
}
if err := c.start(process); err != nil {
if process.Init {
c.deleteExecFifo()
}
return err
}
...
}
func (c *linuxContainer) createExecFifo() error {
...
创建fifo文件,用于进程间通信
if err := unix.Mkfifo(fifoName, 0o622); err != nil {
unix.Umask(oldMask)
return err
}
...
}
func (c *linuxContainer) start(process *Process) (retErr error) {
parent, err := c.newParentProcess(process)
if err != nil {
return fmt.Errorf("unable to create new parent process: %w", err)
}
...
if err := parent.start(); err != nil {
return fmt.Errorf("unable to start container process: %w", err)
}
...
}
创建父进程
func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
parentInitPipe, childInitPipe, err := utils.NewSockPair("init")
if err != nil {
return nil, fmt.Errorf("unable to create init pipe: %w", err)
}
...
...
if err := c.includeExecFifo(cmd); err != nil {
return nil, fmt.Errorf("unable to setup exec fifo: %w", err)
}
return c.newInitProcess(p, cmd, messageSockPair, logFilePair)
}
func (c *linuxContainer) commandTemplate(p *Process, childInitPipe *os.File, childLogPipe *os.File) *exec.Cmd {
initPath为/proc/self/exec,也就是runc
cmd := exec.Command(c.initPath, c.initArgs[1:]...)
...
}
创建init进程
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*initProcess, error) {
...
init := &initProcess{
cmd: cmd,
messageSockPair: messageSockPair,
logFilePair: logFilePair,
manager: c.cgroupManager,
intelRdtManager: c.intelRdtManager,
config: c.newInitConfig(p),
container: c,
process: p,
bootstrapData: data,
sharePidns: sharePidns,
}
c.initProcess = init
return init, nil
}
func (p *initProcess) start() (retErr error) {
...
启动
err := p.cmd.Start()
...
ierr := parseSync(p.messageSockPair.parent, func(sync *syncT) error {
switch sync.Type {
case procReady:
...
通知子进程启动程序
if err := writeSync(p.messageSockPair.parent, procRun); err != nil {
return err
}
...
}
...
}
init 相关
init.go中
func init() {
if len(os.Args) > 1 && os.Args[1] == "init" {
...
factory, _ := libcontainer.New("")
if err := factory.StartInitialization(); err != nil {
// as the error is sent back to the parent there is no need to log
// or write it to stderr because the parent process will handle this
os.Exit(1)
}
...
}
libcontainer/factory_linux.go中
构建工厂对象
func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
if root != "" {
if err := os.MkdirAll(root, 0o700); err != nil {
return nil, err
}
}
l := &LinuxFactory{
Root: root,
InitPath: "/proc/self/exe",
InitArgs: []string{os.Args[0], "init"},
Validator: validate.New(),
CriuPath: "criu",
}
for _, opt := range options {
if opt == nil {
continue
}
if err := opt(l); err != nil {
return nil, err
}
}
return l, nil
}
开启初始化
func (l *LinuxFactory) StartInitialization() (err error) {
这个文件就是socketpair
envInitPipe := os.Getenv("_LIBCONTAINER_INITPIPE")
pipefd, err := strconv.Atoi(envInitPipe)
if err != nil {
err = fmt.Errorf("unable to convert _LIBCONTAINER_INITPIPE: %w", err)
logrus.Error(err)
return err
}
pipe := os.NewFile(uintptr(pipefd), "pipe")
defer pipe.Close()
...
这个就是exec.fifo
fifofd := -1
envInitType := os.Getenv("_LIBCONTAINER_INITTYPE")
it := initType(envInitType)
if it == initStandard {
envFifoFd := os.Getenv("_LIBCONTAINER_FIFOFD")
if fifofd, err = strconv.Atoi(envFifoFd); err != nil {
return fmt.Errorf("unable to convert _LIBCONTAINER_FIFOFD: %w", err)
}
}
...
i, err := newContainerInit(it, pipe, consoleSocket, fifofd, logPipeFd, mountFds)
if err != nil {
return err
}
return i.Init()
}
libcontainer/init_linux.go中
创建容器 init
func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd, logFd int, mountFds []int) (initer, error) {
var config *initConfig
if err := json.NewDecoder(pipe).Decode(&config); err != nil {
return nil, err
}
if err := populateProcessEnvironment(config.Env); err != nil {
return nil, err
}
switch t {
...
case initStandard:
return &linuxStandardInit{
pipe: pipe,
consoleSocket: consoleSocket,
parentPid: unix.Getppid(),
config: config,
fifoFd: fifoFd,
logFd: logFd,
mountFds: mountFds,
}, nil
}
return nil, fmt.Errorf("unknown init type %q", t)
}
libcontainer/standard_init_linux.go中
func (l *linuxStandardInit) Init() error {
...
通过socketpair通知parent就绪
if err := syncParentReady(l.pipe); err != nil {
return fmt.Errorf("sync ready: %w", err)
}
...
_ = l.pipe.Close()
...
fifo写入0
fifoPath := "/proc/self/fd/" + strconv.Itoa(l.fifoFd)
fd, err := unix.Open(fifoPath, unix.O_WRONLY|unix.O_CLOEXEC, 0)
if err != nil {
return &os.PathError{Op: "open exec fifo", Path: fifoPath, Err: err}
}
此处会阻塞,由于另一端没有读取
if _, err := unix.Write(fd, []byte("0")); err != nil {
return &os.PathError{Op: "write exec fifo", Path: fifoPath, Err: err}
}
...
启动用户程序
return system.Exec(name, l.config.Args[0:], os.Environ())
}
libcontainer/process_linux.go中
通知父进程就绪等待父进程通知可运行程序
func syncParentReady(pipe io.ReadWriter) error {
// Tell parent.
if err := writeSync(pipe, procReady); err != nil {
return err
}
// Wait for parent to give the all-clear.
return readSync(pipe, procRun)
}
至此runc容器为created状态
start源码
start实际就是读取exec.fifo内容,然后删除exec.fifo
start.go中
命令
var startCommand = cli.Command{
...
switch status {
case libcontainer.Created:
...
if err := container.Exec(); err != nil {
return err
}
...
return nil
...
}
libcontainer/container_linux.go中
start执行Exec
func (c *linuxContainer) Exec() error {
c.m.Lock()
defer c.m.Unlock()
return c.exec()
}
内部exec
func (c *linuxContainer) exec() error {
path := filepath.Join(c.root, execFifoFilename)
pid := c.initProcess.pid()
打开fifo文件
blockingFifoOpenCh := awaitFifoOpen(path)
for {
select {
case result := <-blockingFifoOpenCh:
处理fifo结果
return handleFifoResult(result)
...
}
}
func handleFifoResult(result openResult) error {
if result.err != nil {
return result.err
}
f := result.file
defer f.Close()
读取fifo内容
if err := readFromExecFifo(f); err != nil {
return err
}
删除fifo文件
return os.Remove(f.Name())
}
至此runc 容器为running状态
网友评论