client 模式
docker命令對應的源文件是docker/docker.go,
docker [options] command [arg...]
其中options參數為flag,任何時候執行一個命令docker命令都需要先解析flag,然后按照用戶生命的command向指定的子命令執行對應的操作
如果子命令為daemom,docker都會創建一個運行在宿主機上的daemom進程,即執行daemom模式。其余子命令都會執行client模式。處於client模式命令工作流程包含幾個步驟
1.解析flag信息
docker命令支持大量的option,或者說flag,列出對於client模式下的docker比較重要的一些flag
Debug,對應-D和--debug參數,他將向系統中添加DEBUG環境變量且賦值為1,並把日志顯示級別調為DEBUG級,這個flag用於啟動調試模式
LogLevel,對應-l和--log-level 參數。默認等級為info,即只輸出普通的操作信息。用戶可以指定的日志等級現在有panic、fatal、error、warn、info、DEBUG這幾種
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():回調方法