client 模式算法
docker命令對應的源文件是docker/docker.go,docker
docker [options] command [arg...]
其中options參數爲flag,任什麼時候候執行一個命令docker命令都須要先解析flag,而後按照用戶生命的command向指定的子命令執行對應的操做數據庫
若是子命令爲daemom,docker都會建立一個運行在宿主機上的daemom進程,即執行daemom模式。其他子命令都會執行client模式。處於client模式命令工做流程包含幾個步驟json
1.解析flag信息api
docker命令支持大量的option,或者說flag,列出對於client模式下的docker比較重要的一些flag數組
Debug,對應-D和--debug參數,他將向系統中添加DEBUG環境變量且賦值爲1,並把日誌顯示級別調爲DEBUG級,這個flag用於啓動調試模式安全
LogLevel,對應-l和--log-level 參數。默認等級爲info,即只輸出普通的操做信息。用戶能夠指定的日誌等級如今有panic、fatal、error、warn、info、DEBUG這幾種bash
hosts,對應-h和--hosts=[]參數,對於client模式,就是指本次操做須要鏈接的docker daemom位置,而對於daemom模式,則提供所要監聽的地址,若host變量或者系統環境變量DOCKER_HOST不爲空,說明用戶指定了host對象;不然使用默認設置,默認狀況下Linux系統設置爲unix:///var/run/docker.sock服務器
protAddrParts,這個參數來自-H參數中://先後的兩部分的組合,即與docker daemom創建通訊的協議方式與socke地址網絡
2建立client實例
client的建立就是在已有配置參數信息的基礎上,調用api/client/cli.go#NewDockerCli,須要設置好proto(傳輸協議)、addr(host的目標地址)和tlsConfig(安全傳輸層協議的配置),另外還會配置標準輸入輸出及錯誤輸出
3執行具體的命令
Docker client 對象建立成功後,剩下的執行具體命令的過程就交給cli/cli.go來處理
從命令到映射的方法
cli主要經過反射機制,從用戶輸入的命令(如run)獲得匹配的執行方法(CmdRun),這就是所謂「約定大於配置」的方法命名規範。
同時,cli會根據參數列表的長度判斷是否用於多級docker命令支持,而後根據找到的執行方法,把剩下的參數傳入並執行。若參數傳入的方法不正確或者錯誤,則返回docker的幫助並退出
每個相似api/client/commnds.go#CmdRun 的方法都剝離出來做爲一個單獨的文件存在。docker run 這個命令的執行過程,就須要尋找api/client/run.go這個文件
執行對應的方法,發起請求
1.解析傳入的參數,並針對參數進行配置處理
2.獲取與Docker daemon通訊所須要的認證配置信息
3.根據命令業務類型,給Docker daemon發送POST、GET等請求
4.讀取來自Docker daemon
daemom 模式
一旦進入daemom模式,剩下的初始化工做都由docker的docker/daemon.go#CmdDaemon來完成;docker daemon經過一個server模塊(api/server/server.go)接收來自client的請求,而後根據請求的類型,交由具體方法執行,所以daemom首先要啓動並初始化這個server,另外一方面啓動server後。docker 進程須要初始化一個daemon對象(daemon/daemon.go)來負責處理server的請求。
docker daemon 初始化啓動過程
API server的配置和初始化過程
啓動過程
(1)整理解析用戶指定的各項參數
(2)建立PID文件
(3)加載所需的server輔助配置,包括日誌、是否容許遠程訪問、版本以及TLS認證信等。
(4)根據上述server配置,加上以前解析出來的用戶指定的server配置(好比Host),經過goro-utine的方式啓動API server。這個server監聽的socket位置就是Host的值
(5)建立一個負責處理業務的daemon對象(對應daemon/daemon.go)做爲負責處理用戶請求的邏輯實體
(6)對APIserver中的路由表進行初始化,即將用戶的請求和對應的處理函數相對應起來。
(7)設置一個channel,保證上述goroutine只是在server出錯的狀況纔會退出
(8)設置信號捕獲,docker daemon進程收到INT、TERM、QUIT信號時,關閉API server,用shutdowndaemon中止這個daemon
(9)若是上面流程完成後,API server就會與daemon綁定,並接受client的鏈接。
(10)最後,docker daemon進程向宿主機的init守護進程發送「READY=1」信號,表示docker daemon已經開始工做
關閉過程
(1)建立並設置一個channel,使用select監聽數據。在正確完成關閉daemon工做後將channel關閉,標識該工做的完成;不然在超時15秒後報錯
(2)調用daemon/daemon.go#Shoutdown方法執行以下工做
遍歷全部運行中的容器,先用SIGTERM軟殺死容器進程,若是10秒不能完成,則使用SIGKILL強制殺死
若是netController被初始化過,調用#libnetwork/controler.go#GC 方法進行垃圾回收
結束運行中的鏡像驅動程序
在docker1.6版本之前的早期和之前全部版本,server的啓動和初始化使用了一種複雜的job機制(API server即被看做一種job),而且依賴於一個專門的docker Engine來管理和運行這些job。1.7版本,這個設計在整個社區的推進下唄重構,上述說的是新的server初始化過程,該server會經過與daemon對象綁定來接受並處理完成具體的請求(相似於一個API接受器綁定了一個業務邏輯處理器)
daemon對象的建立與初始化
對象建立過程至少包括功能有:docker容器配置信息、檢測系統支持及用戶權限、配置工做路徑、加載並配置graphdriver、建立docker網絡環境、建立並初始化鏡像數據庫、建立容器管理驅動、檢測DNS配置和加載已有Docker容器等。
docker 容器配置信息
容器配置信息的主要功能有:提供用戶自由配置的docker容器的可選功能,使得docker容器運行更貼近用戶期待的運行場景;設置默認的網絡最大傳輸單元:當用戶沒有對-mut參數進行指定是,將其設置爲1500.不然,沿用用戶指定參數值 ;檢測網橋配置信息:此部分配置爲進一步配置docker網絡提供鋪墊
檢測系統支持及用戶權限
初步處理完docker的配置信息後,docker自身運行的環境進行一系列檢測,主要包括3個方面
* 操做系統類型對docker daemon的支持,目前docker daemon只能運行在Linux上
* 用戶權限的級別,必須是root權限
* 內核版本與處理器支持,只支持amd64架構的處理,且內核版本必須升至3.10.0及以是上。
配置daemon工做路徑
配置docker daemon的工做路徑,主要是建立Docker daemon 運行中所在的工做目錄,默認爲/var/lib/docker.若該目錄不存在,則會建立,並賦予0700權限
配置docker容器所需的文件環境
這一步docker daemon會在docker工做目錄/var/lib/docker 下面初始化一些重要的目錄文件,來構建docker容器工做所需的文件系統環境
這一,建立容器配置文件目錄。docker daemon在出建立docker容器以後,須要將容器內的配置文件放到這個目錄下統一管理。目錄默認位置:/var/lib/docker/containers,它下面會爲每一個具體容器保存以下幾個配置文件,其中xxx爲容器ID
[root@mast ~]# ls /var/lib/docker/containers/4d5464672680c97ed061b73e7d8336741b2971c2fb5a81fa5ac2ec8fac096cf9/ 4d5464672680c97ed061b73e7d8336741b2971c2fb5a81fa5ac2ec8fac096cf9-json.log checkpoints config.v2.json hostconfig.json hostname hosts mounts resolv.conf resolv.conf.hash
第二,配置graphdriver目錄。它用於完成docker容器鏡像管理所需的底層存儲驅動層,因此在這一步的配置工做就是加載並配置鏡像存儲驅動graphdriver,建立存在驅動鏡像管理層文件系統所需的目錄和環境,初始化鏡像層元數據存儲。建立graphdriver時,首先會從環境變量DOCKER_DRIVER中讀用戶指定的驅動,若爲空,則開始遍歷優先級數組選擇一個graphdriver,在Linux環境下,優先級從高到低依次爲aufs、btrfs、zfs、devicemapper、overlay和vfs。 不一樣操做系統下,優先級列表的內容和順序都會不一樣,並且隨着內核的發展以及驅動的完善,會繼續發生變化。
須要注意,目前vfs在docker中時用來管理volume的,並不做爲鏡像存儲使用。另外,因爲目前在overlay文件系統上運行的docker容器不兼容SELinux,所以當config中配置信息須要啓動SELinux而且driver的類型爲overlay時,該過程就會報錯
當識別出對應的driver後,docker執行這個driver對應的初始化方法(位於daemon/graphdriver/aufs/aufs,go),這個初始化的主要工做包括:嘗試加載內核aufs模塊來肯定docker主機支持aufs,發送statfs系統調用獲取當前docker主目錄(/var/lib/docker)的文件系統信息,肯定aufs是否支持該文件系統;建立aufs驅動根目錄(默認:/var/lib/docker/aufs)並將該目錄配置爲私有掛載,在根目錄下建立mnt、diff和layers目錄做aufs驅動的工做環境,工做完成後,graphdriver的配置工做就完成。
第三,配置鏡像目錄。主要工做是在docker主目錄下建立一個image目錄,來存儲全部鏡像和鏡像層管理數據,默認目錄「/var/lib/docker/image」.在image目錄下,每一graphdriver都有一個具體的目錄用於存儲使用該graphdriver存儲的鏡像相關的元數據
根據上一步graphdriver的選擇狀況(以aufs爲例)建立image/aufs/layerdb/目錄做爲鏡像層元數據存儲目錄,並建立MetadataStore用來管理元數據。根據graphdriver與元數據存儲結構建立layerStore,用來管理全部的鏡像和容器層,將邏輯鏡像層的操做映射到物理存儲驅動層graphdriver的操做,建立用於registry的鏡像上傳下載的uploadManager和downloadMannger
建立image/aufs/imagedb/目錄用於存儲鏡像的元數據,根據layerStore建立imageStore,用來管理鏡像的元數據。
第四,調用volume/local/local.go#New建立volume驅動目錄(默認爲/var/lib/docker/volumes),docker中volume是宿主機上掛載到docker容器內的特定目錄。volume目錄下有一個metadata.db 數據庫文件用於存儲volume相關的元數據,其他以volume ID 命名的文件夾用於存儲具體的volume內容。默認的volume驅動是local,用戶也能夠經過插件的形式使用其餘volume驅動來存儲
第五,準備「可信鏡像」所需的工做目錄。docker工做根目錄下建立trust目錄。這個存儲目錄能夠根據用戶給出的可信URL加載受權文件,用來處理可信鏡像的受權和驗證過程。
第六,建立distributionMetadataStore和referenceStore。referenceStore用於存儲鏡像倉庫列表。記錄鏡像倉庫的持久化文件位於docker根目錄下的image/[graphdriver]/repositories.json中,主要記錄鏡像ID與鏡像倉庫之間的映射。distributionMetadataStore存儲與第二版鏡像倉庫registry有關的元數據,主要用於作鏡像層的diff_id與registry中鏡像層元數據之間的映射
第七,將持久化在Docker根目錄中的鏡像、鏡像層以及鏡像倉庫等的元數據內容恢復到daemon的imageStore、layerStore和reference中
第八,執行鏡像遷移,docker1.10版本之後,鏡像管理部分使用了基於內容尋址存儲。在第一次啓動daemon時,爲了將老版本的graph鏡像管理遷移到新的鏡像管理體系中,這裏會根據docker根目錄中是否存在graph文件夾,若是存在就會讀取graph中的老版本鏡像信息,計算校驗和並將鏡像數據寫入到新版本的imageStore和layerStore中,注意的是,遷移鏡像中計算校驗和是一項很是佔CPU的工做,而且在未完成鏡像遷移時,docker daemon是不會響應任何請求的,全部若是你本地的老版本鏡像和容器比較多時,或者是在對服務器負載和響應比較敏感的線上環境嘗試,docker版本升級,那就要注意妥善安排時間,docker提供了遷移工具讓用戶在老版本daemon運行的時候進行鏡像遷移
這裏docker daemon須要在docker根目錄(/var/lib/docker)下建立並初始化一系列容器文件系統密切相關的目錄和文件。
建立docker network
建立docker daemon運行環境的時候,建立網絡環境是極爲重要的一部分。這不只關係着容器對外通訊,一樣也關乎着容器之間的通訊。網絡部分早已被抽離出來做爲一個單獨的模塊,稱爲libnetwork,libnetwork經過插件的形式爲docker提供網絡功能,使得用戶能夠根據本身需求實現本身的dirver來提供不一樣的網絡功能。截止docker1.10版本,libnetwork實現了host、null、birdge和overlay的驅動。其中,birdge driver 爲默認驅動,和以前版本中的docker網絡功能是基本等價的,須要注意的是,同以前的docker網絡同樣,bridge driver並不提供跨主機通訊的能力,overlay driver則是用於多主機環境
初始化execdriver
execdriver是docker中用來管理容器的驅動,docker會調用execdrivers中NewDriver()函數來建立新的execdriver
在建立execdriver的時候,須要注意一下5部分信息
運行時中指定使用的驅動類型,在默認配置文件中默認使用native,即其對應的容器運行時爲libcontainer;
用戶定義的execdirver選項,即-exec-opt參數值
用戶定義的-exec-root參數值,docker execdriver運行的root路徑,默認爲/var/run/docker;
docker 運行時的root路徑,默認爲/var/lib//docker
系統功能的信息,包括容器的內存限制功能,交換分區內存限制功能、數據轉發功能以及AppArel安全功能等;AppArel經過host主機是否存在/sys/kernel/security/apparmor來判斷是否加入AppArel配置
最後,若是選擇netive做爲這個execdriver的驅動實現,上述driver的建立過程就會新建一個libcontainer,這個libcontainer會在後面建立和啓動Linux容器時發揮做用
daemon對象誕生
docker daemon進程在通過以上諸多設置以及建立對象以後,最終建立出了daemon對象實例
ID | 根據傳入的證書生成的容器ID,若沒有傳入則自動使用ECDSA算法生成 |
repository | 部署全部docker容器的路徑 |
containers | 用於存儲具體的docker容器信息的對象 |
execCommands | docker容器所執行的命令 |
referenceStore | 存儲docker鏡像倉庫名和鏡像ID的映射 |
distributionMetadataStore | v2版registry相關的元數據存儲 |
trustkey | 可信任證書 |
IDInfo | 用於經過簡短有效的字符串前綴定位惟一的鏡像 |
sysInfo | docker所在宿主機的系統信息 |
configStore | docker所需配置信息 |
execDriver | docker 容器執行驅動,默認native類型 |
statsCollector | 收集容器網絡以及cgroups的信息 |
dafaultLogConfig | 提供日誌的默認配置信息 |
registryService | 鏡像存儲服務相關信息 |
EvenetsServer | 事件服務相關信息 |
volume | volume所使用的驅動,默認爲local |
root | docker運行的工做根目錄 |
uidMaps | uid的對應圖 |
gidMaps | gid的對應圖 |
seccompEnabled | 是否使用seccompute |
nameIndex | 記錄建和其名字的對應關係 |
linkIndex | 容器的link目錄,記錄容器的link關係 |
恢復已有的docker容器
當docker daemon啓動時,會去查看在daemon.repository也就是在/var/lib/docker/containers中的內容。如有已經存在的docker容器,則將相應信息收集並進行維護,同時重啓restart policy 爲always的容器
docker daemon的啓動看起來很是複雜,這是docker在演進的過程當中不斷增長功能點形成的,但無論從此docker的功能點增長多少,docker daemon進程的啓動都將遵循3步
(1)首先建立一個API server,它工做在用戶經過-H指定socket
(2)而後docker使用NewDaemon方法建立一個daemon對象來保存信息和處理業務邏輯
(3)最後將上述API server和daemon對象綁定起來,接受並處理client的請求
只不過,NewDaemon方法的長度會不斷增長而已
從client到daemon
發起請求
(1)docker run命令開始運行,用戶端的docker進入client模式
(2)通過初始化,新建出了一個client
(3)上述client經過反射機制找到了CmdRun方法
CmdRun在解析過程用戶提供的容器參數等一系列操做後,最終發出了這樣兩個請求:
「POST」,「/containers/create?」+containerValues //建立容器
「POST」 ,「/containers/」+createResponse.ID+"/start" //啓動容器
至此,client 任務結束
建立容器
在這一步docker daemon並不須要建立一個真正的Linux容器,它只須要理解用戶經過client提交的POST表單,而後使用這些參數在daemon中新建一個container對象出來便可,這個container實體就是container/container_unix.go,其中的commonContainer字段定義在平臺爲主。
啓動容器
這個時候daemon這邊的重點來了。API server接受到start請求後告訴docker daemon進行container啓動容器操做,這個過程daemon/start.go
此時,因爲container所需的各項參數,如NetworkSetings、ImageID等,都已經在容器過程當中賦好了值,docker daemon會在start.go 中直接執行daemon.ContainerStart,就可以宿主機上建立對應的容器了;建立容器過程是docker daemon,containerMonitor將daemon設置爲本身的supervisor。因此通過一系列調用後。daemon.ContainerStart 實際上執行的操做是
即告訴daemon進程,請使用container相關的信息做參數,執行對應的execdriver的Run方法
最後一步
「萬事俱備,只欠東風」。在docker daemon已經完成全部的準備工做,最後下達了執行Run操做的命令後,跟系統打交道的任務都交給ExecDriver.Run來完成;execdriver是docker的重要組成部分,它封裝了對namespace、cgroups等全部對OS資源操做的方法,而在docker中。execdriver的默認實現(native)就是libcontainer了,到這一步。docker daemon只須要提供三大參數,接下來等着返回結果
* commandv:該容器須要的全部配置信息集合
* pipes:用於將容器stdin、stdout、stderr重定向到daemon
* startCallback():回調方法