docker中run的示例分析

這篇文章給大家分享的是有關docker中run的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

環(huán)江ssl適用于網站、小程序/APP、API接口等需要進行數據傳輸應用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯建站的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!

docker源碼相關

通過在/components/cli/command/commands.go里,抽象出各種命令的初始化操作。

使用第三方庫"github.com/spf13/cobra"

  1. docker run 初始化命令行終端解析參數,最終生成 APIclient發(fā)出REQUEST請求給docker daemon.

    • docker daemon的初始化,設置了server的監(jiān)聽地址,初始化化routerSwapper, registryService 以及l(fā)ayStore、imageStore、volumeStore等各種存儲 。

  2. docker run的命令解析為 docker container create 和 container start 兩次請求:

    • 其中container create 不涉及底層containerd的調用,首先將host.config 、networkingConfig和AdjustCPUShares等組裝成一個客戶端請求,發(fā)送到docker daemon注冊該容器。該請求會完成拉取image, 以及初始化 baseContainer的RWlayer, config文件等,之后daemon就可以通過containerid來使用該容器。

    • container start 命令的核心是調用了daemon的containerStart(),它會完成

    • 調用containerd進行create容器,調用libcontainerd模塊 clnt *client 的初始化,

    1. 設置容器文件系統,掛載點: /var/lib/docker/overlay/{container.RWLayer.mountID}/merged

    2. 設置容器的網絡模式,調用libnetwork ,CNM模型(sandbox, endpoint,network)

    3. 創(chuàng)建/proc /dev等spec文件,對容器所特有的屬性進行設置,

    4. 調用containerd進行create容器

container.create
1)獲取libcontainerd模塊中的containers

2)獲取gid和uid

3)創(chuàng)建state目錄,配置文件路徑。

4)創(chuàng)建一個containercommon對象,創(chuàng)建容器目錄,以及配置文件路徑,根據spec創(chuàng)建配置文件。
container.start
1) 讀取spec對象的配置文件

2) 創(chuàng)建一個fifo的pipe

3) 定義containerd的請求對象,grpc調用containerd模塊。

ctr.client.remote.apiClient.CreateContainer(context.Background(), r)

4)啟動成功后,更新容器狀態(tài)。
daemon 啟動libcontainerd ,作為grpc的server。
  1. cmd/dockerd/daemon.go 中存在libcontainerd初始化的流程。

    包括啟動grpc服務器,對套接字進行監(jiān)聽。

    通過grpc.dail 與grpc server建立連接conn, 根據該鏈接建立apiclient對象,發(fā)送json請求。

  2. runContainerdDaemon

    通過docker-containerd二進制與grpc server進行通信,

    docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc

    執(zhí)行結果的輸入輸出流重定向到docker daemon。

     runc把state.json文件保存在容器運行時的狀態(tài)信息,默認存放在/run/runc/{containerID}/state.json。

containerd源碼相關

type Supervisor struct {
	// stateDir is the directory on the system to store container runtime state information.
	stateDir string
	// name of the OCI compatible runtime used to execute containers
	runtime     string
	runtimeArgs []string
	shim        string
	containers  map[string]*containerInfo
	startTasks  chan *startTask //這是containerd到runc的橋梁,由func (w *worker) Start()消費
	// we need a lock around the subscribers map only because additions and deletions from
	// the map are via the API so we cannot really control the concurrency
	subscriberLock sync.RWMutex
	subscribers    map[chan Event]struct{}
	machine        Machine
	tasks          chan Task //所有來自于docker-daemon的request都會轉化為event存放到這,由func (s *Supervisor) Start()消費
	monitor        *Monitor
	eventLog       []Event
	eventLock      sync.Mutex
	timeout        time.Duration
}
type startTask struct {
	Container      runtime.Container
	CheckpointPath string
	Stdin          string
	Stdout         string
	Stderr         string
	Err            chan error
	StartResponse  chan StartResponse
}

我們知道containerd作為docker daemon的grpc server端,通過接收 apiclient request轉化成對應的events,在不同的子系統distribution , bundles , runtime 中進行數據的流轉,包括鏡像上傳下載,鏡像打包和解壓,運行時的創(chuàng)建銷毀等。

其中containerd 核心組件包括 supervisor 和executor, 數據流如下:

docker-daemon
--->tasks chan Task
 --->func (s *Supervisor) Start()消費
   --->存放到startTasks  chan *startTask
      -->func (w *worker) Start()消費

containerd的初始化

docker-containerd初始化包括 新建Supervisor對象:

  1. 該對象會啟動10個worker,負責處理創(chuàng)建新容器的任務(task)。

  2. supervisor的初始化,包括startTask chan初始化,啟動監(jiān)控容器進程的monitor

  3. 一個worker包含一個supervisor和sync.waitgroup,wg用于實現容器啟動。

  4. supervisor的start,消費tasks,把task中的container數據組裝成runtime.container, 封裝到type startTask struct,發(fā)送到startTask chan隊列。

  5. 啟動grpc server(startServer),用來接收dockerd的request請求。

func daemon(context *cli.Context) error {
	s := make(chan os.Signal, 2048)
	signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
	/*
		新建一個supervisor,這個是containerd的核心部件
			==>/supervisor/supervisor.go
				==>func New
	*/
	sv, err := supervisor.New(
		context.String("state-dir"),
		context.String("runtime"),
		context.String("shim"),
		context.StringSlice("runtime-args"),
		context.Duration("start-timeout"),
		context.Int("retain-count"))
	if err != nil {
		return err
	}
	wg := &sync.WaitGroup{}
	/*
		supervisor 啟動10個worker
			==>/supervisor/worker.go
	*/
	for i := 0; i < 10; i++ {
		wg.Add(1)
		w := supervisor.NewWorker(sv, wg)
		go w.Start()
	}
	//啟動supervisor
	if err := sv.Start(); err != nil {
		return err
	}
    // Split the listen string of the form proto://addr
	/*
		根據參數獲取監(jiān)聽器
		listenSpec的值為 unix:///var/run/docker/libcontainerd/docker-containerd.sock
	*/
	listenSpec := context.String("listen")
	listenParts := strings.SplitN(listenSpec, "://", 2)
	if len(listenParts) != 2 {
		return fmt.Errorf("bad listen address format %s, expected proto://address", listenSpec)
	}
	/*
		啟動grpc server端
	*/
	server, err := startServer(listenParts[0], listenParts[1], sv)
	if err != nil {
		return err
	}

其中startServer負責啟動grpc server,監(jiān)聽docker-containerd.sock,聲明注冊路由handler。

  1. 當CreateContainer handler接收到一個Request之后,會把其轉化成type startTask struct,將其轉化為一個StartTask 事件,其中存放創(chuàng)建容器的request信息。

  2. 通過s.sv.SendTask(e)將該事件發(fā)送給supervosior 主循環(huán)。

// SendTask sends the provided event to the the supervisors main event loop
/*
	SendTask將evt Task發(fā)送給 the supervisors main event loop
	所有來自于docker-daemon的request都會轉化為event存放到這,生產者
*/
func (s *Supervisor) SendTask(evt Task) {
	TasksCounter.Inc(1) //任務數+1
	s.tasks <- evt
}
  1. 等待woker.Start()消費處理結果后,將StartResponse返回給docker-daemon。

supervisor.start

負責將每一個request轉化成特定的task類型,通過一個goroutine遍歷task中所有的任務并進行處理。消費tasks,把task中的container數據組裝成runtime.container, 封裝到type startTask struct,發(fā)送到startTask chan隊列。

worker.start

負責調用containerd-shim, 監(jiān)控容器中的進程,并把結果返回給StartResponse chan隊列。

其中,

  1. container.Start() 通過containerd-shim 調用runc create {containerID}創(chuàng)建容器。

     process, err := t.Container.Start(t.CheckpointPath, runtime.NewStdio(t.Stdin, t.Stdout, t.Stderr))
    
     其中值得注意的是,container.start 和container.exec均是調用createcmd,exec 命令則是通過process.json中的相關屬性來判斷是Start()還是Exec(),最后組裝成containerd-shim的調用命令。
    
     當具體容器內進程pid生成(由runc生成)后,createCmd會啟動一個go routine來等待shim命令的結束。 shim命令一般不會退出。 當shim發(fā)生退出時,如果容器內的進程仍在運行,則需要把該進程殺死;如果容器內進程已經不存在,則無需清理工作。

  2. process.Start() 通過調用runc start {containerID}命令啟動容器的init進程

root@idc-gz:/var/run/docker/libcontainerd# tree -L 2 eb347b7e27ecbc01f009971a13cb1b24a89baad795f703053de26d9722129039/
eb347b7e27ecbc01f009971a13cb1b24a89baad795f703053de26d9722129039/
├── 95de4070f528e1d68c80142f679013815a2d1a00da7858c390ad4895b8f8991b-stdin
├── 95de4070f528e1d68c80142f679013815a2d1a00da7858c390ad4895b8f8991b-stdout
├── config.json
├── dc172589265f782a476af1ed302d3178887d078c737ff3d18b930cbc143e5fd5-stdin
├── dc172589265f782a476af1ed302d3178887d078c737ff3d18b930cbc143e5fd5-stdout
├── ef00cfa54bf014e3f732af3bda1f667c9b0f79c0d865f099b1bee014f0834844-stdin
├── ef00cfa54bf014e3f732af3bda1f667c9b0f79c0d865f099b1bee014f0834844-stdout
├── init-stdin
└── init-stdout
root@idc-gz:/var/run/docker/libcontainerdcontainerd# tree -L 2 eb347b7e27ecbc01f009971a13cb1b24a89baad795f703053de26d9722129039/
eb347b7e27ecbc01f009971a13cb1b24a89baad795f703053de26d9722129039/
├── dc172589265f782a476af1ed302d3178887d078c737ff3d18b930cbc143e5fd5
│   ├── control
│   ├── exit
│   ├── log.json
│   ├── pid
│   ├── process.json
│   ├── shim-log.json
│   └── starttime
├── ef00cfa54bf014e3f732af3bda1f667c9b0f79c0d865f099b1bee014f0834844
│   ├── control
│   ├── exit
│   ├── log.json
│   ├── pid
│   ├── process.json
│   ├── shim-log.json
│   └── starttime
├── init
│   ├── control
│   ├── exit
│   ├── log.json
│   ├── pid
│   ├── process.json
│   ├── shim-log.json
│   └── starttime
└── state.json

runc源碼相關

runc create

在源碼create.go中,首先會加載config.json的配置,然后調用startContainer函數,其流程包括:

  1. createContainer, 生成libcontainer.Container對象,狀態(tài)處于stopped、destoryed。

    • 調用loadFactory方法, 生成一個libcontainer.Factory對象。

    • 調用factory.Create()方法,生成libcontainer.Container

  2. 把libcontainer.Container封裝到type runner struct對象中。

    • runner.run負責將config.json設置將來在容器中啟動的process,設置iopipe和tty

    • runc create ,調用container.Start(process)

    1. linuxContainer.newParentPorcess組裝要執(zhí)行的parent命令, 組裝出來的命令是/proc/self/exe init, 通過匿名管道讓runc create 和runc init進行通信。

    2. parent.start()會根據parent的類型來選擇對應的start(),自此之后,將進入/proc/self/exe init,也就是runc init

    3. 將容器狀態(tài)持久化到state.json,此時容器狀態(tài)為created.

  3. runc start,調用container.Run(process)

// LinuxFactory implements the default factory interface for linux based systems.
type LinuxFactory struct {
	// Root directory for the factory to store state.
	/*
		factory 存放數據的根目錄  默認是 /run/runc
		而/run/runc/{containerID} 目錄下,會有兩個文件:
          一個是管道exec.fifo
		  一個是state.json
	*/
	Root string

	// InitArgs are arguments for calling the init responsibilities for spawning
	// a container.
	/*
		用于設置 init命令 ,固定是 InitArgs:  []string{"/proc/self/exe", "init"},
	*/
	InitArgs []string

	// CriuPath is the path to the criu binary used for checkpoint and restore of
	// containers.
	// 用于checkpoint and restore
	CriuPath string

	// Validator provides validation to container configurations.
	Validator validate.Validator

	// NewCgroupsManager returns an initialized cgroups manager for a single container.
	// 初始化一個針對單個容器的cgroups manager
	NewCgroupsManager func(config *configs.Cgroup, paths map[string]string) cgroups.Manager
}

// 一個容器負責對應一個runner
type runner struct {
	enableSubreaper bool
	shouldDestroy   bool
	detach          bool
	listenFDs       []*os.File
	pidFile         string
	console         string
	container       libcontainer.Container
	create          bool
}

runc init

runc create clone出一個子進程,namespace與父進程隔離,子進程中調用/proc/self/exe init進行初始化。

runc init的過程如下:

  1. 調用factory.StartInitialization();

    1. 配置容器內部網絡,路由,初始化mount namespace, 調用setupRootfs在新的mount namespaces中配置設備、掛載點以及文件系統。

    2. 配置hostname, apparmor,processLabel,sysctl, readyonlyPath, maskPath.

    3. 獲取父進程的退出信號,通過管道與父進程同步,先發(fā)出procReady再等待procRun

    4. 恢復parent進程的death信號量并檢查當前父進程pid是否為我們原來記錄的不是的話,kill ourself。

    5. 與父進程之間的同步已經完成,關閉pipe。

    6. "只寫" 方式打開fifo管道并寫入0,會一直保持阻塞。等待runc start以只讀的方式打開FIFO管道,阻塞才會消除。之后本進程才會繼續(xù)執(zhí)行。

    7. 調用syscall.Exec,執(zhí)行用戶真正希望執(zhí)行的命令。用來覆蓋掉PID為1的Init進程。至此,在容器內部PID為1的進程才是用戶希望一直在前臺執(zhí)行的進程。

    8. init進程通過匿名管理讀取父進程的信息,initType以及config信息。

    9. 調用func newContainerInit(),生成一個type linuxStandardInit struct對象

    10. 執(zhí)行l(wèi)inuxStandardInit.Init(),Init進程會根據config配置初始化seccomp,并調用syscall.Exec執(zhí)行cmd。

runc start

runc start的邏輯比較簡單,分為兩步:

  1. 從context中獲取libcontainer.container對象。

  2. 通過判斷container 的狀態(tài)為created,執(zhí)行l(wèi)inuxContainer.exec()。

  • 以“只讀”的方式打開FIFO管道,讀取內容。這同時也恢復之前處于阻塞狀態(tài)的`runc Init`進程,Init進程會執(zhí)行最后調用用戶期待的cmd部分。

  • 如果讀取到的data長度大于0,則讀取到Create流程中最后寫入的“0”,則刪除FIFO管道文件。

感謝各位的閱讀!關于“docker中run的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

網站欄目:docker中run的示例分析
分享URL:http://muchs.cn/article38/iepdpp.html

成都網站建設公司_創(chuàng)新互聯,為您提供網站改版域名注冊、商城網站微信小程序、品牌網站設計、軟件開發(fā)

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯

成都定制網站網頁設計