[TOC=3]
## 1.Docker Client配置容器網絡模式
Docker目前支持4種網絡模式,分別是bridge、host、container、none,Docker開發者可以根據自己的需求來確定最適合自己應用場景的網絡模式。
從Docker Container網絡創建流程圖中可以看到,創建流程第一個涉及的Docker模塊即為Docker Client。當然,這也十分好理解,畢竟Docker Container網絡環境的創建需要由用戶發起,用戶根據自身對容器的需求,選擇網絡模式,并將其通過Docker Client傳遞給Docker Daemon。本節,即從Docker Client源碼的角度,分析如何配置Docker Container的網絡模式,以及Docker Client內部如何處理這些網絡模式參數。
需要注意的是:配置Docker Container網絡環境與創建Docker Container網絡環境有一些區別。區別是:配置網絡環境指用戶通過向Docker Client傳遞網絡參數,實現Docker Container網絡環境參數的配置,這部分配置由Docker Client傳遞至Docker Daemon,并由Docker Daemon保存;創建網絡環境指,用戶通過Docker Client向Docker Daemon發送容器啟動命令之后,Docker Daemon根據之前保存的網絡參數,實現Docker Container的啟動,并在啟動過程中完成Docker Container網絡環境的創建。
以上的基本知識,理解下文的Docker Container網絡環境創建流程。
### 1.1 Docker Client使用
Docker架構中,用戶可以通過Docker Client來配置Docker Container的網絡模式。配置過程主要通過docker run命令來完成,實現配置的方式是在docker run命令中添加網絡參數。使用方式如下(其中NETWORKMODE為四種網絡模式之一,ubuntu為鏡像名稱,/bin/bash為執行指令):
~~~
docker run -d --net NETWORKMODE ubuntu /bin/bash
~~~
運行以上命令時,首先創建一個Docker Client,然后Docker Client會解析整條命令的請求內容,接著解析出為run請求,意為運行一個Docker Container,最終通過Docker Client端的API接口,調用CmdRun函數完成run請求執行。(詳情可以查閱[《Docker源碼分析》系列的第二篇——Docker Client篇](http://www.infoq.com/cn/articles/docker-source-code-analysis-part2))。
Docker Client解析出run命令之后,立即調用相應的處理函數CmdRun進行處理關于run請求的具體內容。CmdRun的作用主要可以歸納為三點:
* 解析Docker Client傳入的參數,解析出config、hostconfig和cmd對象等;
* 發送請求至Docker Daemon,創建一個container對象,完成Docker Container啟動前的準備工作;
* 發送請求至Docker Daemon,啟動相應的Docker Container(包含創建Docker Container網絡環境創建)。
### 1.2 runconfig包解析
CmdRun函數的實現位于[./docker/api/client/commands.go](https://github.com/docker/docker/blob/v1.2.0/api/client/commands.go#L1990)。CmdRun執行的第一個步驟為:通過runconfig包中ParseSubcommand函數解析Docker Client傳入的參數,并從中解析出相應的config,hostConfig以及cmd對象,實現代碼如下:
~~~
config, hostConfig, cmd, err := runconfig.ParseSubcommand
(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]",
"Run a command in a new container"), args, nil)
~~~
其中,config的類型為Config結構體,hostConfig的類型為HostConfig結構體,兩種類型的定義均位于runconfig包。Config與HostConfig類型同用以描述Docker Container的配置信息,然而兩者之間又有著本質的區別,最大的區別在于兩者各自的作用范疇:
* Config結構體:描述Docker Container獨立的配置信息。獨立的含義是:Config這部分信息描述的是容器本身,而不會與容器所在host宿主機相關;
* HostConfig結構體:描述Docker Container與宿主機相關的配置信息。
#### 1.2.1 Config結構體
Config結構體描述Docker Container本身的屬性信息,這些信息與容器所在的host宿主機無關。結構體的定義如下:
~~~
type Config struct {
Hostname string
Domainname string
User string
Memory int64 // Memory limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
CpuShares int64 // CPU shares (relative weight vs. other containers)
Cpuset string // Cpuset 0-2, 0,1
AttachStdin bool
AttachStdout bool
AttachStderr bool
PortSpecs []string // Deprecated - Can be in the format of 8080/tcp
ExposedPorts map[nat.Port]struct{}
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
Env []string
Cmd []string
Image string // Name of the image as it was passed by
the operator (eg. could be symbolic)
Volumes map[string]struct{}
WorkingDir string
Entrypoint []string
NetworkDisabled bool
OnBuild []string
}
~~~
Config結構體中各屬性的詳細解釋如下表:
| Config結構體屬性名 | 類型 | 代表含義 |
|---|---|---|
| Hostname | string | 容器主機名 |
| Domainname | string | 域名名稱 |
| User | string | 用戶名 |
| Memory | int64 | 容器內存使用上限(單位:字節) |
| MemorySwap | int64 | 容器所有的內存使用上限(物理內存+交互區),關閉交互區支持置為-1 |
| CpuShares | int64 | 容器CPU使用share值,其他容器的相對值 |
| Cpuset | string | CPU核的使用集合 |
| AttachStdin | bool | 是否附加標準輸入 |
| AttachStdout | bool | 是否附加標準輸出 |
| AttachStderr | bool | 是否附加標準錯誤輸出 |
| PortsSpecs | []string | 目前已被遺棄 |
| ExposedPorts | map[nat.Port]struct{} | 容器內部暴露的端口號 |
| Tty | bool | 是否分配一個偽終端tty |
| OpenStdin | bool | 在沒有附加標準輸入時,是否依然打開標準輸入 |
| StdinOnce | bool | 若為真,表示第一個客戶關閉標準輸入后關閉標準輸入功能 |
| Env | []string | 容器進程運行的環境變量 |
| Cmd | []string | 容器內通過ENTRYPOINT運行的指令 |
| Image | string | 容器rootfs所依賴的鏡像名稱 |
| Volumes | map[string]struct{} | 容器需要從host宿主機上掛載的目錄 |
| WorkingDir | string | 容器內部的指定工作目錄 |
| Entrypoint | []string | 覆蓋鏡像屬性中默認的ENTRYPOINT |
| NetworkDisabled | bool | 是否關閉容器網絡功能 |
| OnBuild | []string | |
#### 1.2.2 HostConfig結構體
HostConfig結構體描述Docker Container與宿主機相關的屬性信息,結構體的定義如下:
~~~
type HostConfig struct {
Binds []string
ContainerIDFile string
LxcConf []utils.KeyValuePair
Privileged bool
PortBindings nat.PortMap
Links []string
PublishAllPorts bool
Dns []string
DnsSearch []string
VolumesFrom []string
Devices []DeviceMapping
NetworkMode NetworkMode
CapAdd []string
CapDrop []string
RestartPolicy RestartPolicy
}
~~~
Config結構體中各屬性的詳細解釋如下表:
| HostConfig結構體屬性名 | 類型 | 代表含義 |
|---|---|---|
| Binds| []string | 從宿主機上綁定到容器的volumes |
| ContainerIDFile | string | 文件名,文件用以寫入容器的ID |
| LxcConf | []utils.KeyValuePair | 添加自定義的lxc選項 |
| Privileged | bool | 是否賦予該容器擴展權限 |
| PortBindings | nat.PortMap | 容器綁定到host宿主機的端口 |
| Links | []string | 添加其他容器的鏈接到該容器 |
| PublishAllPorts | bool | 是否向宿主機暴露所有端口信息 |
| Dns | []string | 自定義的DNS服務器地址 |
| DnsSearch | []string | 自定義的DNS查找服務器地址 |
| VolumesFrom | []string | 從指定的容器中掛載到該容器的volumes |
| Devices | []DeviceMapping | 為容器添加一個或多個宿主機設備 |
| NetworkMode | NetworkMode | 為容器設置的網絡模式 |
| CapAdd | []string | 為容器用戶添加一個或多個Linux Capabilities |
| CapDrop | []string | 為容器用戶禁用一個或多個Linux Capabilities |
| RestartPolicy | RestartPolicy | 當一個容器異常退出時采取的重啟策略 |
#### 1.2.3 runconfig解析網絡模式
講述完Config與HostConfig結構體之后,回到runconfig包中分析如何解析與Docker Container網絡模式相關的配置信息,并將這部分信息傳遞給config實例與hostConfig實例。
runconfig包中的ParseSubcommand函數調用parseRun函數完成命令請求的分析,實現代碼位于[./docker/runconfig/parse.go#L37-L39](https://github.com/docker/docker/blob/v1.2.0/runconfig/parse.go#L37-L39),如下:
~~~
func ParseSubcommand(cmd *flag.FlagSet, args []string,
sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
return parseRun(cmd, args, sysInfo)
}
~~~
進入parseRun函數即可發現:該函數完成了四方面的工作:
* 定義與容器配置信息相關的[flag參數](https://github.com/docker/docker/blob/v1.2.0/runconfig/parse.go#L42-L97);
* 解析docker run命令后緊跟的請求內容,將請求內容全部保存至flag參數中,余下的內容一個為鏡像image名,另一個為需要在容器內執行的cmd命令;
* 通過flag參數驗證參數的有效性,并處理得到Config結構體與HostConfig結構體需要的屬性值;
* 創建并初始化Config類型實例config、HostConfig類型實例hostConfig,最總返回config、hostConfig與cmd。
本文主要分析Docker Container的網絡模式,而parseRun函數中有關容器網絡模式的flag參數有flNetwork與flNetMode,兩者的定義分別位于[./docker/runconfig/parse.go#L62](https://github.com/docker/docker/blob/v1.2.0/runconfig/parse.go#L62)與[./docker/runconfig/parse.go#L75](https://github.com/docker/docker/blob/v1.2.0/runconfig/parse.go#L75),如下:
> flNetwork = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
> flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container\n'bridge': creates a new network stack for the container on the docker bridge\n'none': no networking for this container\n'container:': reuses another container network stack\n'host': use the host network stack inside the container. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.")
可見flag參數flNetwork表示是否開啟容器的網絡模式,若為true則開啟,說明需要給容器創建網絡環境;否則不開啟,說明不給容器賦予網絡功能。該flag參數的默認值為true,另外使用該flag的方式為:在docker run之后設定--networking或者-n,如:
~~~
docker run --networking true ubuntu /bin/bash
~~~
另一個flag參數flNetMode則表示為容器設定的網絡模式,共有四種選項,分別是:bridge、none、container:和host。四種模式的作用上文已經詳細解釋,此處不再贅述。使用該flag的方式為:在docker run之后設定--net,如:
~~~
Docker run --net host ubuntu /bin/bash
~~~
用戶使用docker run啟動容器時設定了以上兩個flag參數(--networking和--net),則runconfig包會解析出這兩個flag的值。最終,通過flag參數flNetwork,得到Config類型實例config的屬性NetworkDisabled;通過flag參數flNetMode,得到HostConfig類型實例hostConfig的屬性NetworkMode。
函數parseRun返回config、hostConfig與cmd,代表著runconfig包解析配置參數工作的完成,CmdRun得到返回內容之后,繼續向下執行。
### 1.3 CmdRun執行
在runconfig包中已經將有關容器網絡模式的配置置于config對象與hostConfig對象,故在CmdRun函數的執行中,更多的是基于config對象與hostConfig參數處理配置信息,而沒有其他的容器網絡處理部分。
CmdRun后續主要工作是:利用Docker Daemon暴露的RESTful API接口,將docker run的請求發送至Docker Daemon。以下是CmdRun執行過程中Docker Client與Docker Daemon的簡易交互圖。
[](http://cdn2.infoqstatic.com/statics_s2_20151111-0209/resource/articles/docker-source-code-analysis-part8/zh/resources/0210001.jpg)
圖1.1 CmdRun中Docker Client與Docker Daemon交互圖
具體分析CmdRun的執行流程可以發現:在解析config、hostConfig與cmd之后,Docker Client首先發起請求create container。若Docker Daemon節點上已經存在該容器所需的鏡像,則立即執行create container操作并返回請求響應;Docker Client收到響應后,再發起請求start container。若容器鏡像還不存在,Docker Daemon返回一個404的錯誤,表示鏡像不存在;Docker Client收到錯誤響應之后,再發起一個請求pull image,使Docker Daemon首先下載鏡像,下載完畢之后Docker Client再次發起請求create container,Docker Daemon創建完畢之后,Docker Client最終發起請求start container。
總結而言,Docker Client負責創建容器請求的發起。關于Docker Container網絡環境的配置參數存儲于config與hostConfig對象之中,在請求create container和start container發起時,隨請求一起發送至Docker Daemon。
## 2.Docker Daemon創建容器網絡流程
Docker Daemon接收到Docker Client的請求大致可以分為兩次,第一次為create container,第二次為start container。這兩次請求的執行過程中,都與Docker Container的網絡相關。以下按照這兩個請求的執行,具體分析Docker Container網絡模式的創建。Docker Daemon如何通過Docker Server解析RESTful請求,并完成分發調度處理,在[《Docker源碼分析》系列的第五篇——Docker Server篇](http://www.infoq.com/cn/articles/docker-source-code-analysis-part5)中已經詳細分析過,本文不再贅述。
### 2.1創建容器并配置網絡參數
Docker Daemon首先接收并處理create container請求。需要注意的是:create container并非創建了一個運行的容器,而是完成了以下三個主要的工作:
* 通過runconfig包解析出create container請求中與Docker Container息息相關的config對象;
* 在Docker Daemon內部創建了與Docker Container對應的container對象;
* 完成Docker Container啟動前的準備化工作,如準備所需鏡像、創建rootfs等。
創建容器過程中,Docker Daemon首先通過runconfig包中ContainerConfigFromJob函數,解析出請求中的config對象,解析過程代碼如下:
config := runconfig.ContainerConfigFromJob(job)
至此,Docker Client處理得到的config對象,已經傳遞至Docker Daemon的config對象,config對象中已經含有屬性NetworkDisabled具體值。
處理得到config對象之后,Docker Daemon緊接著創建container對象,并為Docker Container作相應的準備工作。具體的實現代碼位于[./docker/daemon/create.go#L73-L78](https://github.com/docker/docker/blob/v1.2.0/daemon/create.go#L73-L78),如下:
~~~
if container, err = daemon.newContainer(name, config, img); err != nil {
return nil, nil, err
}
if err := daemon.createRootfs(container, img); err != nil {
return nil, nil, err
}
~~~
與Docker Container網絡模式配置相關的內容主要位于創建container對象中。newContainer函數的定義位于[./docker/daemon/daemon.go#L516-L550](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L516-L550),具體的container對象如下:
~~~
container := &Container{
ID: id,
Created: time.Now().UTC(),
Path: entrypoint,
Args: args, //FIXME: de-duplicate from config
Config: config,
hostConfig: &runconfig.HostConfig{},
Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
Name: name,
Driver: daemon.driver.String(),
ExecDriver: daemon.execDriver.Name(),
State: NewState(),
}
~~~
在container對象中,config對象直接賦值給container對象的Config屬性,另外hostConfig屬性與NetworkSeeetings屬性均為空。其中hostConfig對象將在start container請求執行過程中被賦值,NetworkSettings類型的作用是描述容器的網絡具體信息,定義位于[./docker/daemon/network_settings.go#L11-L18](https://github.com/docker/docker/blob/v1.2.0/daemon/network_settings.go#L11-L18),代碼如下:
~~~
type NetworkSettings struct {
IPAddress string
IPPrefixLen int
Gateway string
Bridge string
PortMapping map[string]PortMapping // Deprecated
Ports nat.PortMap
}
~~~
Networksettings類型的各屬性的詳細解釋如下表:
| NetworkSettings屬性名稱 | 類型 | 含義 |
|---|---|---|
| IPAddress | string | IP網絡地址 |
| IPPrefixLen | int | 網絡標識位長度 |
| Gateway | string | 網關地址 |
| Bridge | string | 網橋地址 |
| PortMapping | map[string]PortMapping | 端口映射 |
| Ports | nat.PortMap | 端口號 |
總結而言,Docker Daemon關于create container請求的執行,先實現了容器配置信息從Docker Client至Docker Daemon的轉移,再完成了啟動容器前所需的準備工作。
### 2.2啟動容器之網絡配置
創建容器階段,Docker Daemon創建了容器對象container,container對象內部的Config屬性含有NetworkDisabled。創建容器完成之后,Docker Daemon還需要接收Docker Client的請求,并執行start container的操作,即啟動容器。
啟動容器過程中,Docker Daemon首先通過runconfig包中ContainerHostConfigFromJob函數,解析出請求中的hostConfig對象,解析過程代碼如下:
~~~
hostConfig := runconfig.ContainerHostConfigFromJob(job)
~~~
至此,Docker Client處理得到的hostConfig對象,已經傳遞至Docker Daemon的hostConfig對象,hostConfig對象中已經含有屬性NetworkMode具體值。
容器啟動的所有工作,均由以下的Start函數來完成,代碼位于[./docker/daemon/start.go#L36-L38](https://github.com/docker/docker/blob/v1.2.0/daemon/start.go#L36-L38),如下:
~~~
if err := container.Start(); err != nil {
return job.Errorf("Cannot start container %s: %s", name, err)
}
~~~
Start函數實現了容器的啟動。更為具體的描述是:Start函數實現了進程的啟動,另外在啟動進程的同時為進程設定了命名空間(namespace),啟動完畢之后為進程完成了資源使用的控制,從而保證進程以及之后進程的子進程都會在同一個命名空間內,且受到相同的資源控制。如此一來,Start函數創建的進程,以及該進程的子進程,形成一個進程組,該進程組處于資源隔離和資源控制的環境,我們習慣將這樣的進程組環境稱為容器,也就是這里的Docker Container。
回到Start函數的執行,位于[./docker/daemon/container.go#L275-L320](https://github.com/docker/docker/blob/v1.2.0/daemon/container.go#L275-L320)。Start函數執行過程中,與Docker Container網絡模式相關的部分主要有三部分:
* initializeNetwork(),初始化container對象中與網絡相關的屬性;
* populateCommand,填充Docker Container內部需要執行的命令,Command中含有進程啟動命令,還含有容器環境的配置信息,也包括網絡配置;
* container.waitForStart(),實現Docker Container內部進程的啟動,進程啟動之后,為進程創建網絡環境等。
#### 2.2.1初始化容器網絡配置
容器對象container中有屬性hostConfig,屬性hostConfig中有屬性NetworkMode,初始化容器網絡配置initializeNetworking()的主要工作就是,通過NetworkMode屬性為Docker Container的網絡作相應的初始化配置工作。
Docker Container的網絡模式有四種,分別為:host、other container、none以及bridge。initializeNetworking函數的執行完全覆蓋了這四種模式。
initializeNetworking()函數的實現位于[./docker/daemon/container.go#L881-L933](https://github.com/docker/docker/blob/v1.2.0/daemon/container.go#L881-L933)。
##### 2.2.1.1 初始化host網絡模式配置
Docker Container網絡的host模式意味著容器使用宿主機的網絡環境。雖然Docker Container使用宿主機的網絡環境,但這并不代表Docker Container可以擁有宿主機文件系統的視角,而host宿主機上有很多信息標識的是網絡信息,故Docker Daemon需要將這部分標識網絡的信息,從host宿主機添加到Docker Container內部的指定位置。這樣的網絡信息,主要有以下兩種:
* host宿主機的主機名(hostname);
* host宿主機上/etc/hosts文件,用于配置IP地址以及主機名。
其中,宿主機的主機名hostname用于創建container.Config中的Hostname與Domainname屬性。
另外,Docker Daemon在Docker Container的rootfs內部創建hostname文件,并在文件中寫入Hostname與Domainname;同時創建hosts文件,并寫入host宿主機上/etc/hosts內的所有內容。
##### 2.2.1.2 初始化other container網絡模式配置
Docker Container的other container網絡模式意味著:容器使用其他已經創建容器的網絡環境。
Docker Daemon首先判斷host網絡模式之后,若不為host網絡模式,則繼續判斷Docker Container網絡模式是否為other container。如果Docker Container的網絡模式為other container(假設使用的-net參數為--net=container:17adef,其中17adef為容器ID)。Docker Daemon所做的執行操作包括兩部分。
第一步,從container對象的hostConfig屬性中找出NetworkMode,并找到相應的容器,即17adef的容器對象container,實現代碼如下:
~~~
nc, err := container.getNetworkedContainer()
~~~
第二步,將17adef容器對象的HostsPath、ResolveConfPath、Hostname和Domainname賦值給當前容器對象container,實現代碼如下:
~~~
container.HostsPath = nc.HostsPath
container.ResolvConfPath = nc.ResolvConfPath
container.Config.Hostname = nc.Config.Hostname
container.Config.Domainname = nc.Config.Domainname
~~~
##### 2.2.1.3 初始化none網絡模式配置
Docker Container的none網絡模式意味著不給該容器創建任何網絡環境,容器只能使用127.0.0.1的本機網絡。
Docker Daemon通過config屬性的DisableNetwork來判斷是否為none網絡模式。實現代碼如下:
~~~
if container.daemon.config.DisableNetwork {
container.Config.NetworkDisabled = true
return container.buildHostnameAndHostsFiles("127.0.1.1")
}
~~~
##### 2.2.1.4 初始化bridge網絡模式配置
Docker Container的bridge網絡模式意味著為容器創建橋接網絡模式。橋接模式使得Docker Container創建獨立的網絡環境,并通過“橋接”的方式實現Docker Container與外界的網絡通信。
初始化bridge網絡模式的配置,實現代碼如下:
~~~
if err := container.allocateNetwork(); err != nil {
return err
}
return container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
~~~
以上代碼完成的內容主要也是兩部分:第一,通過allocateNetwork函數為容器分配網絡接口設備需要的資源信息(包括IP、bridge、Gateway等),并賦值給container對象的NetworkSettings;第二,為容器創建hostname以及創建Hosts等文件。
#### 2.2.2創建容器Command信息
Docker在實現容器時,使用了Command類型。Command在Docker Container概念中是一個非常重要的概念。幾乎可以認為Command是Docker Container生命周期的源頭。Command的概念會貫穿以后的《Docker源碼分析》系列,比如Docker Daemon與dockerinit的關系,dockerinit和entrypoint.sh的關系,entrypoint.sh與CMD的關系,以及namespace在這些內容中扮演的角色。
簡單來說,Command類型包含了兩部分的內容:第一,運行容器內進程的外部命令exec.Cmd;第二,運行容器時啟動進程需要的所有環境基礎信息:包括容器進程組的使用資源、網絡環境、使用設備、工作路徑等。通過這兩部分的內容,我們可以清楚,如何啟動容器內的進程,同時也清楚為容器創建什么樣的環境。
首先,我們先來看Command類型的定義,位于[./docker/daemon/execdriver/driver.go#L84](https://github.com/docker/docker/blob/v1.2.0/daemon/execdriver/driver.go#L84),通過分析Command類型以及其他相關的數據結構類型,可以得到以下簡要類型關系圖:
[](http://cdn2.infoqstatic.com/statics_s2_20151111-0209/resource/articles/docker-source-code-analysis-part8/zh/resources/0210002.jpg)
圖 2.1 Command類型關系圖
從Command類型關系圖中可以看到,Command類型中重新包裝了exec.Cmd類型,即代表需要創建的進程具體的外部命令;同時,關于網絡方面的屬性有Network,Network的類型為指向Network類型的指針;關于Docker Container資源使用方面的屬性為Resources,從Resource的類型來看,Docker目前能做的資源限制有4個維度,分別為內存,內存+Swap,CPU使用,CPU核使用;關于掛載的內容,有屬性Mounts;等等。
簡單介紹Command類型之后,回到Docker Daemon啟動容器網絡的源碼分析。在Start函數的執行流程中,緊接initializeNetworking()之后,與Docker Container網絡相關的是populateCommand環節。populateCommand的函數實現位于[./docker/daemon/container.go#L191-L274](https://github.com/docker/docker/blob/v1.2.0/daemon/container.go#L191-L273)。上文已經提及,populateCommand的作用是創建包execdriver的對象Command,該Command中既有啟動容器進程的外部命令,同時也有眾多為容器環境的配置信息,包括網絡。
本小節,更多的分析populateCommand如何填充Command對象中的網絡信息,其他信息的分析會在《源碼分析系列》的后續進行展開。
Docker總共有四種網絡模式,故populateCommand自然需要判斷容器屬于哪種網絡模式,隨后將具體的網絡模式信息,寫入Command對象的Network屬性中。查驗Docker Container網絡模式的代碼位于[./docker/daemon/container.go#L204-L227](https://github.com/docker/docker/blob/v1.2.0/daemon/container.go#L204-L227),如下:
~~~
parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2)
switch parts[0] {
case "none":
case "host":
en.HostNetworking = true
case "bridge", "": // empty string to support existing containers
if !c.Config.NetworkDisabled {
network := c.NetworkSettings
en.Interface = &execdriver.NetworkInterface{
Gateway: network.Gateway,
Bridge: network.Bridge,
IPAddress: network.IPAddress,
IPPrefixLen: network.IPPrefixLen,
}
}
case "container":
nc, err := c.getNetworkedContainer()
if err != nil {
return err
}
en.ContainerID = nc.ID
default:
return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
}
~~~
populateCommand首先通過hostConfig對象中的NetworkMode判斷容器屬于哪種網絡模式。該部分內容涉及到execdriver包中的Network類型,可參見Command類型關系圖中的Network類型。若為none模式,則對于Network對象(即en,*execdriver.Network)不做任何操作。若為host模式,則將Network對象的HostNetworking置為true;若為bridge橋接模式,則首先創建一個NetworkInterface對象,完善該對象的Gateway、Bridge、IPAddress和IPPrefixLen信息,最后將NetworkInterface對象作為Network對象的Interface屬性的值;若為other container模式,則首先通過getNetworkedContainer()函數獲知被分享網絡命名空間的容器,最后將容器ID,賦值給Network對象的ContainerID。由于bridge模式、host模式、none模式以及other container模式彼此互斥,故Network對象中Interface屬性、ContainerID屬性以及HostNetworking三者之中只有一個被賦值。當Docker Container的網絡查驗之后,populateCommand將en實例Network屬性的值,傳遞給Command對象。
至此,populateCommand關于網絡方面的信息已經完成配置,網絡配置信息已經成功賦值于Command的Network屬性。。
#### 2.2.3啟動容器內部進程
當為容器做好所有的準備與配置之后,Docker Daemon需要真正意義上的啟動容器。根據Docker Daemon啟動容器流程涉及的Docker模塊中可以看到,這樣的請求,會被發送至execdriver,再經過libcontainer,最后實現真正啟動進程,創建完容器。
回到Docker Daemon的啟動容器,daemon包中start函數的最后一步即為執行container.waitForStart()。waitForStart函數的定義位于[./docker/daemon/container.go#L1070-L1082](https://github.com/docker/docker/blob/v1.2.0/daemon/container.go#L1070-L1082),代碼如下:
~~~
func (container *Container) waitForStart() error {
container.monitor = newContainerMonitor(container, container.hostConfig.RestartPolicy)
select {
case <-container.monitor.startSignal:
case err := <-utils.Go(container.monitor.Start):
return err
}
return nil
}
~~~
以上代碼運行過程中首先通過newContainerMonitor返回一個初始化的containerMonitor對象,該對象中帶有容器進程的重啟策略(RestartPolicy)。這里簡單介紹containerMonitor對象。總體而言,containerMonitor對象用以監視容器中第一個進程的執行。如果containerMonitor中指定了一種進程重啟策略,那么一旦容器內部進程沒有啟動成功,Docker Daemon會使用重啟策略來重啟容器。如果在重啟策略下,容器依然沒有成功啟動,那么containerMonitor對象會負責重置以及清除所有已經為容器準備好的資源,例如已經為容器分配好的網絡資源(即IP地址),還有為容器準備的rootfs等。
waitForStart()函數通過container.monitor.Start來實現容器的啟動,進入[./docker/daemon/monitor.go#L100](https://github.com/docker/docker/blob/v1.2.0/daemon/monitor.go#L100),可以發現啟動容器進程位于[./docker/daemon/monitor.go#L136](https://github.com/docker/docker/blob/v1.2.0/daemon/monitor.go#L136),代碼如下:
~~~
exitStatus, err = m.container.daemon.Run(m.container, pipes, m.callback)
~~~
以上代碼實際調用了daemon包中的Run函數,位于[./docker/daemon/daemon.go#L969-L971](https://github.com/docker/docker/blob/v1.2.0/daemon/daemon.go#L969-L971),代碼如下:
~~~
func (daemon *Daemon) Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
return daemon.execDriver.Run(c.command, pipes, startCallback)
}
~~~
最終,Run函數中調用了execdriver中的Run函數來執行Docker Container的啟動命令。
至此,網絡部分在Docker Daemon內部的執行已經結束,緊接著程序運行邏輯陷入execdriver,進一步完成容器啟動的相關步驟。
## 3.execdriver網絡執行流程
Docker架構中execdriver的作用是啟動容器內部進程,最終啟動容器。目前,在Docker中execdriver作為執行驅動,可以有兩種選項:lxc與native。其中,lxc驅動會調用lxc工具實現容器的啟動,而native驅動會使用[Docker官方發布的libcontainer](https://github.com/docker/libcontainer/tree/v1.2.0)來啟動容器。
Docker Daemon啟動過程中,execdriver的類型默認為native,故本文主要分析native驅動在執行啟動容器時,如何處理網絡部分。
在Docker Daemon啟動容器的最后一步,即調用了execdriver的Run函數來執行。通過分析Run函數的具體實現,關于Docker Container的網絡執行流程主要包括兩個環節:
(1) 創建libcontainer的Config對象
(2) 通過libcontainer中的namespaces包執行啟動容器
將execdriver.Run函數的運行流程具體展開,與Docker Container網絡相關的流程,可以得到以下示意圖:
[](http://cdn2.infoqstatic.com/statics_s2_20151111-0209/resource/articles/docker-source-code-analysis-part8/zh/resources/0210003.jpg)
圖3.1 execdriver.Run執行流程圖
### 3.1創建libcontainer的Config對象
Run函數位于[./docker/daemon/execdriver/native/driver.go#L62-L168](https://github.com/docker/docker/blob/v1.2.0/daemon/execdriver/native/driver.go#L62-L128),進入Run函數的實現,立即可以發現該函數通過createContainer創建了一個container對象,代碼如下:
~~~
container, err := d.createContainer(c)
~~~
其中c為Docker Daemon創建的execdriver.Command類型實例。以上代碼的createContainer函數的作用是:使用execdriver.Command來填充libcontainer.Config。
libcontainer.Config的作用是,定義在一個容器化的環境中執行一個進程所需要的所有配置項。createContainer函數的存在,使用Docker Daemon層創建的execdriver.Command,創建更下層libcontainer所需要的Config對象。這個角度來看,execdriver更像是封裝了libcontainer對外的接口,實現了將Docker Daemon認識的容器啟動信息轉換為底層libcontainer能真正使用的容器啟動配置選項。libcontainer.Config類型與其內部對象的關聯圖如下:
[](http://cdn2.infoqstatic.com/statics_s2_20151111-0209/resource/articles/docker-source-code-analysis-part8/zh/resources/0210004.jpg)
圖3.2 libcontainer.Config類型關系圖
進入createContainer的源碼實現部分,位于[./docker/daemon/execdriver/native/create.go#L23-L77](https://github.com/docker/docker/blob/v1.2.0/daemon/execdriver/native/create.go#L23-L77),代碼如下:
~~~
func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, error) {
container := template.New()
……
if err := d.createNetwork(container, c); err != nil {
return nil, err
}
……
return container, nil
}
~~~
#### 3.1.1 libcontainer.Config模板實例
從createContainer函數的實現以及execdriver.Run執行流程圖中都可以看到,createContainer所做的第一個操作就是即為執行template.New(),意為創建一個libcontainer.Config的實例container。其中,template.New()的定義位于[./docker/daemon/execdriver/native/template/default_template.go](https://github.com/docker/docker/blob/v1.2.0/daemon/execdriver/native/template/default_template.go),主要的作用為返回libcontainer關于Docker Container的默認配置選項。
Template.New()的代碼實現如下:
~~~
func New() *libcontainer.Config {
container := &libcontainer.Config{
Capabilities: []string{
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL",
"AUDIT_WRITE",
},
Namespaces: map[string]bool{
"NEWNS": true,
"NEWUTS": true,
"NEWIPC": true,
"NEWPID": true,
"NEWNET": true,
},
Cgroups: &cgroups.Cgroup{
Parent: "docker",
AllowAllDevices: false,
},
MountConfig: &libcontainer.MountConfig{},
}
if apparmor.IsEnabled() {
container.AppArmorProfile = "docker-default"
}
return container
}
~~~
關于該libcontainer.Config默認的模板對象,從源碼實現中可以看到,首先設定了Capabilities的默認項,如CHOWN、DAC_OVERRIDE、FSETID等;其次又將Docker Container所需要的設定的namespaces添加默認值,即需要創建5個NAMESPACE,如NEWNS、NEWUTS、NEWIPC、NEWPID和NEWNET,其中不包括user namespace,另外與網絡相關的namespace為NEWNET;最后設定了一些關于cgroup以及apparmor的默認配置。
Template.New()函數最后返回類型為libcontainer.Config的實例container,該實例中只含有默認配置項,其他的配置項的添加需要createContainer的后續操作來完成。
#### 3.1.2 createNetwork實現
在createContainer的實現流程中,為了完成container對象(類型為libcontainer.Config)的完善,最后有很多步驟,如與網絡相關的createNetwork函數調用,與Linux內核Capabilities相關的setCapabilities函數調用,與cgroups相關的setupCgroups函數調用,以及與掛載目錄相關的setupMounts函數調用等。本小節主要分析createNetwork如何為container對象完善網絡配置項。
createNetwork函數的定義位于[./docker/daemon/execdriver/native/create.go#L79-L124](https://github.com/docker/docker/blob/v1.2.0/daemon/execdriver/native/create.go#L79-L124),該函數主要利用execdriver.Command中Network屬性中的內容,來判斷如何創建libcontainer.Config中Network屬性(關于兩中Network屬性,可以參見圖3.1和圖3.2)。由于Docker Container的4種網絡模式彼此互斥,故以上Network類型中Interface、ContainerID與HostNetworking最多只有一項會被賦值。
由于execdriver.Command中Network的類型定義如下:
~~~
type Network struct {
Interface *NetworkInterface
Mtu int
ContainerID string
HostNetworking bool
}
~~~
分析createNetwork函數,其具體實現可以歸納為4部分內容:
(1) 判斷網絡是否為host模式;
(2) 判斷是否為bridge橋接模式;
(3) 判斷是否為other container模式;
(4) 為Docker Container添加loopback網絡設備。
首先來看execdriver判斷是否為host模式的代碼:
~~~
if c.Network.HostNetworking {
container.Namespaces["NEWNET"] = false
return nil
}
~~~
當execdriver.Command類型實例中Network屬性的HostNetworking為true,則說明需要為Docker Container創建host網絡模式,使得容器與宿主機共享同樣的網絡命名空間。關于host模式的具體介紹中,已經闡明,只須在創建進程進行CLONE系統調用時,不傳入CLONE_NEWNET參數標志即可實現。這里的代碼正好準確的驗證了這一點,將container對象中NEWNET的Namespace設為false,最終在libcontainer中可以達到效果。
再看execdriver判斷是否為bridge橋接模式的代碼:
~~~
if c.Network.Interface != nil {
vethNetwork := libcontainer.Network{
Mtu: c.Network.Mtu,
Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress,
c.Network.Interface.IPPrefixLen),
Gateway: c.Network.Interface.Gateway,
Type: "veth",
Bridge: c.Network.Interface.Bridge,
VethPrefix: "veth",
}
container.Networks = append(container.Networks, &vethNetwork)
}
~~~
當execdriver.Command類型實例中Network屬性的Interface不為nil值,則說明需要為Docker Container創建bridge橋接模式,使得容器使用隔離的網絡環境。于是這里為類型為libcontainer.Config的container對象添加Networks屬性vethNetwork,網絡類型為“veth”,以便libcontainer在執行時,可以為Docker Container創建veth pair。
接著來看execdriver判斷是否為other container模式的代碼:
~~~
if c.Network.ContainerID != "" {
d.Lock()
active := d.activeContainers[c.Network.ContainerID]
d.Unlock()
if active == nil || active.cmd.Process == nil {
return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID)
}
cmd := active.cmd
nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net")
container.Networks = append(container.Networks, &libcontainer.Network{
Type: "netns",
NsPath: nspath,
})
}
~~~
當execdriver.Command類型實例中Network屬性的ContainerID不為空字符串時,則說明需要為Docker Container創建other container模式,使得創建容器使用其他容器的網絡環境。實現過程中,execdriver需要首先在activeContainers中查找需要被共享網絡環境的容器active;并通過active容器的啟動執行命令cmd找到容器第一進程在宿主機上的PID;隨后在proc文件系統中找到該進程PID的關于網絡namespace的路徑nspath;最后為類型為libcontainer.Config的container對象添加Networks屬性,Network的類型為“netns”。
此外,createNetwork函數還實現為Docker Container創建一個loopback回環設備,以便容器可以實現內部通信。實現過程中,同樣為類型libcontainer.Config的container對象添加Networks屬性,Network的類型為“loopback”,代碼如下:
~~~
container.Networks = []*libcontainer.Network{
{
Mtu: c.Network.Mtu,
Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0),
Gateway: "localhost",
Type: "loopback",
},
}
~~~
至此,createNetwork函數已經把與網絡相關的配置,全部創建在類型為libcontainer.Config的container對象中了,就等著啟動容器進程時使用。
### 3.2 調用libcontainer的namespaces啟動容器
回到execdriver.Run函數,創建完libcontainer.Config實例container,經過一系列其他方面的處理之后,最終execdriver執行namespaces.Exec函數實現啟動容器,container對象依然是namespace.Exec函數中一個非常重要的參數。這一環節代表著execdriver把啟動Docker Container的工作交給libcontainer,以后的執行陷入libcontainer。
調用namespaces.Exec的代碼位于[./docker/daemon/execdriver/native/driver.go#L102-L127](https://github.com/docker/docker/blob/v1.2.0/daemon/execdriver/native/driver.go#L102-L127),為了便于理解,簡化的代碼如下:
~~~
namespaces.Exec(container, c.Stdin, c.Stdout, c.Stderr, c.Console,
c.Rootfs, dataPath, args, parameter_1, parameter_2)
~~~
其中parameter_1為定義的函數,如下:
~~~
func(container *libcontainer.Config, console, rootfs, dataPath,
init string, child *os.File, args []string) *exec.Cmd {
c.Path = d.initPath
c.Args = append([]string{
DriverName,
"-console", console,
"-pipe", "3",
"-root", filepath.Join(d.root, c.ID),
"--",
}, args...)
// set this to nil so that when we set the clone flags anything else is reset
c.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)),
}
c.ExtraFiles = []*os.File{child}
c.Env = container.Env
c.Dir = c.Rootfs
return &c.Cmd
}
~~~
同樣的,parameter_2也為定義的函數,如下:
~~~
func() {
if startCallback != nil {
c.ContainerPid = c.Process.Pid
startCallback(c)
}
}
~~~
Parameter_1以及parameter_2這兩個函數均會在libcontainer的namespaces中發揮很大的重要。
至此,execdriver模塊的執行部分已經完結,Docker Daemon的程序運行邏輯陷入libcontainer。
## 4.libcontainer實現內核態網絡配置
libcontainer是一個Linux操作系統上容器的實現包。libcontainer指定了創建一個容器時所需要的配置選項,同時它利用Linux namespace和cgroup等技術為使用者提供了一套Golang原生態的容器實現方案,并且沒有使用任何外部依賴。用戶借助libcontainer,可以感受到眾多操縱namespaces,網絡等資源的便利。
當execdriver調用libcontainer中namespaces包的Exec函數時,libcontainer開始發揮其實現容器功能的作用。Exec函數位于[./libcontainer/namespaces/exec.go#L24-L113](https://github.com/docker/libcontainer/blob/v1.2.0/namespaces/exec.go#L24-L113)。本文更多的關心Docker Container的網絡創建,因此從這個角度來看Exec的實現可以分為三個步驟:
(1) 通過createCommand創建一個Golang語言內的exec.Cmd對象;
(2) 啟動命令exec.Cmd,執行容器內第一個進程;
(3) 通過InitializeNetworking函數為容器進程初始化網絡環境。
以下詳細分析這三個部分,源碼的具體實現。
### 4.1創建exec.Cmd
提到exec.Cmd,就不得不提Go語言標準庫中的包os以及包os/exec。前者提供了與平臺無關的操作系統功能集,后者則提供了功能集里與命令執行相關的部分。
首先來看一下在Go語言中exec.Cmd的定義,如下:
~~~
type Cmd struct {
Path string //所需執行命令在系統中的路徑
Args []string //傳入命令的參數
Env []string //進程運行時的環境變量
Dir string //命令運行的工作目錄
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
ExtraFiles []*os.File //進程所需打開的文件描述符資源
SysProcAttr *syscall.SysProcAttr //可選的操作系統屬性
Process *os.Process //代表Cmd啟動后,操作系統底層的具體進程
ProcessState *os.ProcessState //進程退出后保留的信息
}
~~~
清楚Cmd的定義之后,再來分析namespaces包的Exec函數中,是如何來創建exec.Cmd的。在Exec函數的實現過程中,使用了以下代碼實現Exec.Cmd的創建:
~~~
command := createCommand(container, console, rootfs, dataPath,
os.Args[0], syncPipe.Child(), args)
~~~
其中createCommand為namespace.Exec函數中傳入的倒數第二個參數,類型為CreateCommand。而createCommand只是namespaces.Exec函數的形參,真正的實參則為execdriver調用namespaces.Exec時的參數parameter_1,即如下代碼:
~~~
func(container *libcontainer.Config, console, rootfs, dataPath,
init string, child *os.File, args []string) *exec.Cmd {
c.Path = d.initPath
c.Args = append([]string{
DriverName,
"-console", console,
"-pipe", "3",
"-root", filepath.Join(d.root, c.ID),
"--",
}, args...)
// set this to nil so that when we set the clone flags anything else is reset
c.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)),
}
c.ExtraFiles = []*os.File{child}
c.Env = container.Env
c.Dir = c.Rootfs
return &c.Cmd
}
~~~
熟悉exec.Cmd的定義之后,分析以上代碼的實現就顯得較為簡單。為Cmd賦值的對象有Path,Args,SysProcAttr,ExtraFiles,Env和Dir。其中需要特別注意的是Path的值d.initPath,該路徑下存放的是dockerinit的二進制文件,Docker 1.2.0版本下,路徑一般為“/var/lib/docker/init/dockerinit-1.2.0”。另外SysProcAttr使用以下的代碼來賦值:
~~~
&syscall.SysProcAttr{
Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)),
}
~~~
syscall.SysProAttr對象中的Cloneflags屬性中,即保留了libcontainer.Config類型的實例container中的Namespace屬性。換言之,通過exec.Cmd創建進程時,正是通過Cloneflags實現Clone系統調用中傳入namespace參數標志。
回到函數執行中,在函數的最后返回了c.Cmd,命令創建完畢。
### 4.2啟動exec.Cmd創建進程
創建完exec.Cmd,當然需要將該執行命令運行起來,namespaces.Exec函數中直接使用以下代碼實現進程的啟動:
~~~
if err := command.Start(); err != nil {
return -1, err
}
~~~
這一部分的內容簡單直接, Start()函數用以完成指定命令exec.Cmd的啟動執行,同時并不等待其啟動完畢便返回。Start()函數的定義位于os/exec包。
進入os/exec包,查看Start()函數的實現,可以看到執行過程中,會對command.Process進行賦值,此時command.Process中會含有剛才啟動進程的PID進程號,該PID號屬于在宿主機pid namespace下,而并非是新創建namespace下的PID號。
### 4.3為容器進程初始化網絡環境
上一環節實現了容器進程的啟動,然而卻還沒有為之配置相應的網絡環境。namespaces.Exec在之后的InitializeNetworing中實現了為容器進程初始化網絡環境。初始化網絡環境需要兩個非常重要的參數:container對象以及容器進程的Pid號。類型為libcontainer.Config的實例container中包含用戶對Docker Container的網絡配置需求,另外容器進程的Pid可以使得創建的網絡環境與進程新創建的namespace進行關聯。
namespaces.Exec中為容器進程初始化網絡環境的代碼實現位于[./libcontainer/namespaces/exec.go#L75-L79](https://github.com/docker/libcontainer/blob/v1.2.0/namespaces/exec.go#L75-L79),如下:
~~~
if err := InitializeNetworking(container, command.Process.Pid, syncPipe, &networkState); err != nil {
command.Process.Kill()
command.Wait()
return -1, err
}
~~~
InitializeNetworing的作用很明顯,即為創建的容器進程初始化網絡環境。更為底層的實現包含兩個步驟:
(1) 先在容器進程的namespace外部,創建容器所需的網絡棧;
(2) 將創建的網絡棧遷移進入容器的net namespace。
IntializeNetworking的源代碼實現位于[./libcontainer/namespaces/exec.go#L176-L187](https://github.com/docker/libcontainer/blob/v1.2.0/namespaces/exec.go#L176-L187),如下:
~~~
func InitializeNetworking(container *libcontainer.Config, nspid int, pipe *syncpipe.SyncPipe, networkState *network.NetworkState) error {
for _, config := range container.Networks {
strategy, err := network.GetStrategy(config.Type)
if err != nil {
return err
}
if err := strategy.Create((*network.Network)(config), nspid, networkState); err != nil {
return err
}
}
return pipe.SendToChild(networkState)
}
~~~
以上源碼實現過程中,首先通過一個循環,遍歷libcontainer.Config類型實例container中的網絡屬性Networks;隨后使用GetStrategy函數處理Networks中每一個對象的Type屬性,得出Network的類型,這里的類型有3種,分別為“loopback”、“veth”、“netns”。除host網絡模式之外,loopback對于其他每一種網絡模式的Docker Container都需要使用;veth針對bridge橋接模式,而netns針對other container模式。
得到Network類型的類型之后,libcontainer創建相應的網絡棧,具體實現使用每種網絡棧類型下的Create函數。以下分析三種不同網絡棧各自的創建流程。
#### 4.3.1 loopback網絡棧的創建
Loopback是一種本地環回設備,libcontainer創建loopback網絡設備的實現代碼位于[./libcontainer/network/loopback.go#L13-L15](https://github.com/docker/libcontainer/blob/v1.2.0/network/loopback.go#L13-L15),如下:
~~~
func (l *Loopback) Create(n *Network, nspid int, networkState *NetworkState) error {
return nil
}
~~~
令人費解的是,libcontainer在loopback設備的創建函數Create中,并沒有作實質性的內容,而是直接返回nil。
其實關于loopback設備的創建,要回到Linux內核為進程新建namespace的階段。當libcontainer執行command.Start()時,由于創建了一個新的網絡namespace,故Linux內核會自動為新的net namespace創建loopback設備。當Linux內核創建完loopback設備之后,libcontainer所做的工作即只要保留loopback設備的默認配置,并在Initialize函數中實現啟動該設備。
#### 4.3.2 veth網絡棧的創建
Veth是Docker Container實際使用的網絡策略之一,其使用網橋docker0并創建veth pair虛擬網絡設備對,最終使一個veth配置在宿主機上,而另一個veth安置在容器網絡namespace內部。
libcontainer中實現veth策略的代碼非常通俗易懂,代碼位于[./libcontainer/network/veth.go#L19-L50](https://github.com/docker/libcontainer/blob/v1.2.0/network/veth.go#L19-L50),如下:
~~~
name1, name2, err := createVethPair(prefix)
if err != nil {
return err
}
if err := SetInterfaceMaster(name1, bridge); err != nil {
return err
}
if err := SetMtu(name1, n.Mtu); err != nil {
return err
}
if err := InterfaceUp(name1); err != nil {
return err
}
if err := SetInterfaceInNamespacePid(name2, nspid); err != nil {
return err
}
~~~
主要的流程包含的四個步驟:
(1) 在宿主機上創建veth pair;
(2) 將一個veth附加至docker0網橋上;
(3) 啟動第一個veth;
(4) 將第二個veth附加至libcontainer創建進程的namespace下。
使用Create函數實現veth pair的創建之后,在Initialize函數中實現將網絡namespace中的veth改名為“eth0”,并設置網絡設備的MTU等。
#### 4.3.3 netns網絡棧的創建
netns專門為Docker Container的other container網絡模式服務。netns完成的工作是將其他容器的namespace路徑傳遞給需要創建other container網絡模式的容器使用。
libcontainer中實現netns策略的代碼位于[./libcontainer/network/netns.go#L17-L20](https://github.com/docker/libcontainer/blob/v1.2.0/network/netns.go#L17-L20),如下:
~~~
func (v *NetNS) Create(n *Network, nspid int, networkState *NetworkState) error {
networkState.NsPath = n.NsPath
return nil
}
~~~
使用Create函數先將NsPath賦給新建容器之后,在Initialize函數中實現將網絡namespace的文件描述符交由新創建容器使用,最終實現兩個Docker Container共享同一個網絡棧。
通過Create以及Initialize的實現之后,Docker Container相應的網絡棧環境即已經完成創建,容器內部的應用進程可以使用不同的網絡棧環境與外界或者內部進行通信。關于Initialize函數何時被調用,需要清楚Docker Daemon與dockerinit的關系,以及如何實現Docker Daemon進程與dockerinit進程跨namespace進行通信,這部分內容會在《Docker源碼分析》系列后續專文分析。
## 5.總結
如何使用Docker Container的網絡,一直是工業界倍加關心的問題。本文將從Linux內核原理的角度闡述了什么是Docker Container,并對Docker Container 4種不同的網絡模式進行了初步的介紹,最終貫穿Docker 架構中的多個模塊,如Docker Client、Docker Daemon、execdriver以及libcontainer,深入分析Docker Container網絡的實現步驟。
目前,若只談論Docker,那么它還是只停留在單host宿主機的場景上。如何面對跨host的場景、如何實現分布式Docker Container的管理,目前為止還沒有一個一勞永逸的解決方案。再者,一個解決方案的存在,總是會適應于一個應用場景。Docker這種容器技術的發展,大大改善了傳統模式下使用諸如虛擬機等傳統計算單位存在的多數弊端,卻在網絡方面使得自身的使用過程中存在瑕疵。希望本文是一個引子,介紹Docker Container網絡,以及從源碼的角度分析Docker Container網絡之后,能有更多的愛好者思考Docker Container網絡的前世今生,并為Docker乃至容器技術的發展做出貢獻。
## 6.作者介紹
孫宏亮,[DaoCloud](http://www.daocloud.io/)初創團隊成員,軟件工程師,浙江大學VLIS實驗室應屆研究生。讀研期間活躍在PaaS和Docker開源社區,對Cloud Foundry有深入研究和豐富實踐,擅長底層平臺代碼分析,對分布式平臺的架構有一定經驗,撰寫了大量有深度的技術博客。2014年末以合伙人身份加入DaoCloud團隊,致力于傳播以Docker為主的容器的技術,推動互聯網應用的容器化步伐。郵箱:[allen.sun@daocloud.io](mailto:allen.sun@daocloud.io)
## 7.參考文獻
[http://docs.studygolang.com/pkg/os/exec/#Cmd](http://docs.studygolang.com/pkg/os/exec/#Cmd)
[https://github.com/docker/libcontainer/tree/v1.2.0](https://github.com/docker/libcontainer/tree/v1.2.0)
[https://github.com/docker/libcontainer/issues/323](https://github.com/docker/libcontainer/issues/323)