這篇文章給大家分享的是有關(guān)docker中run的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。
環(huán)江ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)建站的ssl證書銷售渠道,可以享受市場價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
通過在/components/cli/command/commands.go里,抽象出各種命令的初始化操作。
使用第三方庫"github.com/spf13/cobra"
docker run 初始化命令行終端解析參數(shù),最終生成 APIclient發(fā)出REQUEST請(qǐng)求給docker daemon.
docker daemon的初始化,設(shè)置了server的監(jiān)聽地址,初始化化routerSwapper, registryService 以及l(fā)ayStore、imageStore、volumeStore等各種存儲(chǔ) 。
docker run的命令解析為 docker container create 和 container start 兩次請(qǐng)求:
其中container create 不涉及底層containerd的調(diào)用,首先將host.config 、networkingConfig和AdjustCPUShares等組裝成一個(gè)客戶端請(qǐng)求,發(fā)送到docker daemon注冊(cè)該容器。該請(qǐng)求會(huì)完成拉取image, 以及初始化 baseContainer的RWlayer, config文件等,之后daemon就可以通過containerid來使用該容器。
container start 命令的核心是調(diào)用了daemon的containerStart(),它會(huì)完成
調(diào)用containerd進(jìn)行create容器,調(diào)用libcontainerd模塊 clnt *client 的初始化,
設(shè)置容器文件系統(tǒng),掛載點(diǎn): /var/lib/docker/overlay/{container.RWLayer.mountID}/merged
設(shè)置容器的網(wǎng)絡(luò)模式,調(diào)用libnetwork ,CNM模型(sandbox, endpoint,network)
創(chuàng)建/proc /dev等spec文件,對(duì)容器所特有的屬性進(jìn)行設(shè)置,
調(diào)用containerd進(jìn)行create容器
1)獲取libcontainerd模塊中的containers 2)獲取gid和uid 3)創(chuàng)建state目錄,配置文件路徑。 4)創(chuàng)建一個(gè)containercommon對(duì)象,創(chuàng)建容器目錄,以及配置文件路徑,根據(jù)spec創(chuàng)建配置文件。
1) 讀取spec對(duì)象的配置文件 2) 創(chuàng)建一個(gè)fifo的pipe 3) 定義containerd的請(qǐng)求對(duì)象,grpc調(diào)用containerd模塊。 ctr.client.remote.apiClient.CreateContainer(context.Background(), r) 4)啟動(dòng)成功后,更新容器狀態(tài)。
cmd/dockerd/daemon.go 中存在libcontainerd初始化的流程。
包括啟動(dòng)grpc服務(wù)器,對(duì)套接字進(jìn)行監(jiān)聽。
通過grpc.dail 與grpc server建立連接conn, 根據(jù)該鏈接建立apiclient對(duì)象,發(fā)送json請(qǐng)求。
runContainerdDaemon
通過docker-containerd二進(jìn)制與grpc server進(jìn)行通信,
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í)行結(jié)果的輸入輸出流重定向到docker daemon。
runc把state.json文件保存在容器運(yùn)行時(shí)的狀態(tài)信息,默認(rèn)存放在/run/runc/{containerID}/state.json。
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()消費(fèi) // 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都會(huì)轉(zhuǎn)化為event存放到這,由func (s *Supervisor) Start()消費(fèi) 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轉(zhuǎn)化成對(duì)應(yīng)的events,在不同的子系統(tǒng)distribution , bundles , runtime 中進(jìn)行數(shù)據(jù)的流轉(zhuǎn),包括鏡像上傳下載,鏡像打包和解壓,運(yùn)行時(shí)的創(chuàng)建銷毀等。
其中containerd 核心組件包括 supervisor 和executor, 數(shù)據(jù)流如下:
docker-daemon --->tasks chan Task --->func (s *Supervisor) Start()消費(fèi) --->存放到startTasks chan *startTask -->func (w *worker) Start()消費(fèi)
docker-containerd初始化包括 新建Supervisor對(duì)象:
該對(duì)象會(huì)啟動(dòng)10個(gè)worker,負(fù)責(zé)處理創(chuàng)建新容器的任務(wù)(task)。
supervisor的初始化,包括startTask chan初始化,啟動(dòng)監(jiān)控容器進(jìn)程的monitor
一個(gè)worker包含一個(gè)supervisor和sync.waitgroup,wg用于實(shí)現(xiàn)容器啟動(dòng)。
supervisor的start,消費(fèi)tasks,把task中的container數(shù)據(jù)組裝成runtime.container, 封裝到type startTask struct,發(fā)送到startTask chan隊(duì)列。
啟動(dòng)grpc server(startServer),用來接收dockerd的request請(qǐng)求。
func daemon(context *cli.Context) error { s := make(chan os.Signal, 2048) signal.Notify(s, syscall.SIGTERM, syscall.SIGINT) /* 新建一個(gè)supervisor,這個(gè)是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 啟動(dòng)10個(gè)worker ==>/supervisor/worker.go */ for i := 0; i < 10; i++ { wg.Add(1) w := supervisor.NewWorker(sv, wg) go w.Start() } //啟動(dòng)supervisor if err := sv.Start(); err != nil { return err } // Split the listen string of the form proto://addr /* 根據(jù)參數(shù)獲取監(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) } /* 啟動(dòng)grpc server端 */ server, err := startServer(listenParts[0], listenParts[1], sv) if err != nil { return err }
其中startServer負(fù)責(zé)啟動(dòng)grpc server,監(jiān)聽docker-containerd.sock,聲明注冊(cè)路由handler。
當(dāng)CreateContainer handler接收到一個(gè)Request之后,會(huì)把其轉(zhuǎn)化成type startTask struct,將其轉(zhuǎn)化為一個(gè)StartTask 事件,其中存放創(chuàng)建容器的request信息。
通過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都會(huì)轉(zhuǎn)化為event存放到這,生產(chǎn)者 */ func (s *Supervisor) SendTask(evt Task) { TasksCounter.Inc(1) //任務(wù)數(shù)+1 s.tasks <- evt }
等待woker.Start()消費(fèi)處理結(jié)果后,將StartResponse返回給docker-daemon。
負(fù)責(zé)將每一個(gè)request轉(zhuǎn)化成特定的task類型,通過一個(gè)goroutine遍歷task中所有的任務(wù)并進(jìn)行處理。消費(fèi)tasks,把task中的container數(shù)據(jù)組裝成runtime.container, 封裝到type startTask struct,發(fā)送到startTask chan隊(duì)列。
負(fù)責(zé)調(diào)用containerd-shim, 監(jiān)控容器中的進(jìn)程,并把結(jié)果返回給StartResponse chan隊(duì)列。
其中,
container.Start() 通過containerd-shim 調(diào)用runc create {containerID}
創(chuàng)建容器。
process, err := t.Container.Start(t.CheckpointPath, runtime.NewStdio(t.Stdin, t.Stdout, t.Stderr)) 其中值得注意的是,container.start 和container.exec均是調(diào)用createcmd,exec 命令則是通過process.json中的相關(guān)屬性來判斷是Start()還是Exec(),最后組裝成containerd-shim的調(diào)用命令。 當(dāng)具體容器內(nèi)進(jìn)程pid生成(由runc生成)后,createCmd會(huì)啟動(dòng)一個(gè)go routine來等待shim命令的結(jié)束。 shim命令一般不會(huì)退出。 當(dāng)shim發(fā)生退出時(shí),如果容器內(nèi)的進(jìn)程仍在運(yùn)行,則需要把該進(jìn)程殺死;如果容器內(nèi)進(jìn)程已經(jīng)不存在,則無需清理工作。
process.Start() 通過調(diào)用runc start {containerID}
命令啟動(dòng)容器的init進(jìn)程
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
在源碼create.go中,首先會(huì)加載config.json的配置,然后調(diào)用startContainer函數(shù),其流程包括:
createContainer, 生成libcontainer.Container對(duì)象,狀態(tài)處于stopped、destoryed。
調(diào)用loadFactory方法, 生成一個(gè)libcontainer.Factory對(duì)象。
調(diào)用factory.Create()方法,生成libcontainer.Container
把libcontainer.Container封裝到type runner struct對(duì)象中。
runner.run負(fù)責(zé)將config.json設(shè)置將來在容器中啟動(dòng)的process,設(shè)置iopipe和tty
runc create ,調(diào)用container.Start(process)
linuxContainer.newParentPorcess組裝要執(zhí)行的parent命令, 組裝出來的命令是/proc/self/exe init, 通過匿名管道讓runc create 和runc init進(jìn)行通信。
parent.start()會(huì)根據(jù)parent的類型來選擇對(duì)應(yīng)的start(),自此之后,將進(jìn)入/proc/self/exe init,也就是runc init
將容器狀態(tài)持久化到state.json,此時(shí)容器狀態(tài)為created.
runc start,調(diào)用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 存放數(shù)據(jù)的根目錄 默認(rèn)是 /run/runc 而/run/runc/{containerID} 目錄下,會(huì)有兩個(gè)文件: 一個(gè)是管道exec.fifo 一個(gè)是state.json */ Root string // InitArgs are arguments for calling the init responsibilities for spawning // a container. /* 用于設(shè)置 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. // 初始化一個(gè)針對(duì)單個(gè)容器的cgroups manager NewCgroupsManager func(config *configs.Cgroup, paths map[string]string) cgroups.Manager } // 一個(gè)容器負(fù)責(zé)對(duì)應(yīng)一個(gè)runner type runner struct { enableSubreaper bool shouldDestroy bool detach bool listenFDs []*os.File pidFile string console string container libcontainer.Container create bool }
runc create clone出一個(gè)子進(jìn)程,namespace與父進(jìn)程隔離,子進(jìn)程中調(diào)用/proc/self/exe init進(jìn)行初始化。
runc init的過程如下:
調(diào)用factory.StartInitialization();
配置容器內(nèi)部網(wǎng)絡(luò),路由,初始化mount namespace, 調(diào)用setupRootfs在新的mount namespaces中配置設(shè)備、掛載點(diǎn)以及文件系統(tǒng)。
配置hostname, apparmor,processLabel,sysctl, readyonlyPath, maskPath.
獲取父進(jìn)程的退出信號(hào),通過管道與父進(jìn)程同步,先發(fā)出procReady再等待procRun
恢復(fù)parent進(jìn)程的death信號(hào)量并檢查當(dāng)前父進(jìn)程pid是否為我們?cè)瓉碛涗浀牟皇堑脑挘琸ill ourself。
與父進(jìn)程之間的同步已經(jīng)完成,關(guān)閉pipe。
"只寫" 方式打開fifo管道并寫入0,會(huì)一直保持阻塞。等待runc start
以只讀的方式打開FIFO管道,阻塞才會(huì)消除。之后本進(jìn)程才會(huì)繼續(xù)執(zhí)行。
調(diào)用syscall.Exec,執(zhí)行用戶真正希望執(zhí)行的命令。用來覆蓋掉PID為1的Init進(jìn)程。至此,在容器內(nèi)部PID為1的進(jìn)程才是用戶希望一直在前臺(tái)執(zhí)行的進(jìn)程。
init進(jìn)程通過匿名管理讀取父進(jìn)程的信息,initType以及config信息。
調(diào)用func newContainerInit(),生成一個(gè)type linuxStandardInit struct對(duì)象
執(zhí)行l(wèi)inuxStandardInit.Init(),Init進(jìn)程會(huì)根據(jù)config配置初始化seccomp,并調(diào)用syscall.Exec執(zhí)行cmd。
runc start的邏輯比較簡單,分為兩步:
從context中獲取libcontainer.container對(duì)象。
通過判斷container 的狀態(tài)為created,執(zhí)行l(wèi)inuxContainer.exec()。
以“只讀”的方式打開FIFO管道,讀取內(nèi)容。這同時(shí)也恢復(fù)之前處于阻塞狀態(tài)的`runc Init`進(jìn)程,Init進(jìn)程會(huì)執(zhí)行最后調(diào)用用戶期待的cmd部分。
如果讀取到的data長度大于0,則讀取到Create流程中最后寫入的“0”,則刪除FIFO管道文件。
感謝各位的閱讀!關(guān)于“docker中run的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
網(wǎng)站欄目:docker中run的示例分析
分享URL:http://www.chinadenli.net/article38/iepdpp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版、域名注冊(cè)、商城網(wǎng)站、微信小程序、品牌網(wǎng)站設(shè)計(jì)、軟件開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)