Docker的本地網絡實現其實就是利用了Linux上的網絡命名空間和虛擬網絡設備(特別是veth pair)。 git
直觀上看,要實現網絡通訊,機器須要至少一個網絡接口(物理接口或虛擬接口)與外界相通,並能夠收發數據包;此外,若是不一樣子網之間要進行通訊,須要額外的路由機制。 github
Docker中的網絡接口默認都是虛擬的接口。虛擬接口的最大優點就是轉發效率極高。這是由於Linux經過在內核中進行數據複製來實現虛擬接口之間的數據轉發,即發送接口的發送緩存中的數據包將被直接複製到接收接口的接收緩存中,而無需經過外部物理網絡設備進行交換。對於本地系統和容器內系統來看,虛擬接口跟一個正常的以太網卡相比並沒有區別,只是它速度要快得多。 web
Docker容器網絡就很好地利用了Linux虛擬網絡技術,在本地主機和容器內分別建立一個虛擬接口,並讓它們彼此連通(這樣的一對接口叫作veth pair)。 docker
通常狀況下,Docker建立一個容器的時候,會具體執行以下操做: centos
1.建立一對虛擬接口,分別放到本地主機和新容器的命名空間中; 緩存
2.本地主機一端的虛擬接口鏈接到默認的docker0網橋或指定網橋上,並具備一個以veth開頭的惟一名字,如veth1234; 服務器
3.容器一端的虛擬接口將放到新建立的容器中,並修更名字做爲eth0。這個接口只在容器的命名空間可見; 網絡
4.從網橋可用地址段中獲取一個空閒地址分配給容器的eth0(例如172.17.0.2/16),並配置默認路由網關爲docker0網卡的內部接口docker0的IP地址(例如172.17.42.1/16)。 app
完成這些以後,容器就可使用它所能看到的eth0虛擬網卡來鏈接其餘容器和訪問外部網絡。用戶也能夠經過docker network命令來手動管理網絡。 工具
安裝Docker時,它會自動建立三個網絡,bridge(建立容器默認鏈接到此網絡,也就是在不使用--network參數時)、 none 、host。還有之後一種自定義模式,自定義模式有三種:bridge、overlay、macvlan。
host:容器將不會虛擬出本身的網卡,配置本身的IP等,而是使用宿主機的IP和端口。
Container:建立的容器不會建立本身的網卡,配置本身的IP,而是和一個指定的容器共享IP、端口範圍。
None:該模式關閉了容器的網絡功能。
Bridge:此模式會爲每個容器分配、設置IP等,並將容器鏈接到一個docker0虛擬網橋,經過docker0網橋以及Iptables nat表配置與宿主機通訊。
經過docker network ls能夠查看docker網絡:
當容器運行分別以none、host、bridge這三種模式的時候以下:
bridge用法也就是容器默認使用,也就是上面基本原理所講到的。
建立network
# docker network create -d bridge --ip-range=192.168.1.0/24 --gateway=192.168.1.1 --subnet=192.168.1.0/24 bridge2
# docker network ls
建立兩個容器指定ip並指定network
# docker run -it --network=bridge2 --ip=192.168.1.3 busybox
# docker run -it --network=bridge2 --ip=192.168.1.4 busybox
在使用docker run命令啓動容器的時候,能夠經過--net參數來指定容器的網絡配置。
有5個可選值bridge、none、container、host和用戶定義的網絡:
--net=bridge:默認值,在Docker網橋docker0上爲容器建立新的網絡棧。
--net=none:讓Docker將新容器放到隔離的網絡棧中,可是不進行網絡配置。以後,用戶能夠自行進行配置。
--net=container:NAME_or_ID:讓Docker將新建容器的進程放到一個已存在容器的網絡棧中,新容器進程有本身的文件系統、進程列表和資源限制,但會和已存在的容器共享IP地址和端口等網絡資源,二者進程能夠直接經過lo環回接口通訊。
--net=host:告訴Docker不要將容器網絡放到隔離的命名空間中,即不要容器化容器內的網絡。此時容器使用本地主機的網絡,它擁有徹底的本地主機接口訪問權限。容器進程能夠跟主機其餘root進程同樣打開低範圍的端口,能夠訪問本地網絡服務,好比D-bus,還可讓容器作一些影響整個主機系統的事情,好比重啓主機。所以使用這個選項的時候要很是當心。若是進一步的使用--privileged=true參數,容器甚至會被容許直接配置主機的網絡棧。
--net=user_defined_network:用戶自行用network相關命令建立一個網絡,經過這種方式將容器鏈接到指定的已建立網絡上去。
有些命令選項只有在Docker服務啓動的時候才能配置,並且不能立刻生效:
-b BRIDGE or--bridge=BRIDGE——指定容器掛載的網橋;
--bip=CIDR——定製docker0的掩碼;
-H SOCKET...or--host=SOCKET...——Docker服務端接收命令的通道;
--icc=true|false——是否支持容器之間進行通訊;
--ip-forward=true|false——啓用net.ipv4.ip_forward,即打開轉發功能;
--iptables=true|false——禁止Docker添加iptables規則;
--mtu=BYTES——容器網絡中的MTU。
下面2個命令選項既能夠在啓動服務時指定,也能夠docker run時候指定。在Docker服務啓動的時候指定則會成爲默認值,後續執行docker run時能夠覆蓋設置的默認值。
--dns=IP_ADDRESS...——使用指定的DNS服務器;
--dns-search=DOMAIN...——指定DNS搜索域。
最後這些選項只能在docker run執行時使用,由於它是針對容器的特性內容:
-h HOSTNAME or--hostname=HOSTNAME——配置容器主機名;
--link=CONTAINER_NAME:ALIAS——添加到另外一個容器的鏈接;
--net=bridge|none|container:NAME_or_ID|host|user_defined_network——配置容器的橋接模式;
-p SPEC or--publish=SPEC——映射容器端口到宿主主機;
-P or--publish-all=true|false——映射容器全部端口到宿主主機。
Docker支持自定義容器的主機名和DNS配置。
1.相關配置文件
容器中主機名和DNS配置信息都是經過三個系統配置文件來維護的:/etc/resolv.conf、/etc/hostname和/etc/hosts。
啓動一個容器,在容器中使用mount命令能夠看到這三個文件掛載信息:
/etc/resolv.conf文件在建立容器時候,默認會與宿主機/etc/resolv.conf文件內容保持一致
/etc/hosts文件中默認只記錄了容器自身的一些地址和名稱:
/etc/hostname文件則記錄了容器的主機名。
2.容器內修改配置文件
Docker 1.2.0開始支持在運行中的容器裏直接編輯/etc/hosts,/etc/hostname和/etc/resolve.conf文件。可是這些修改是臨時的,只在運行的容器中保留,容器終止或重啓後並不會被保存下來。也不會被docker commit提交。
3.經過參數指定
若是用戶想要自定義容器的配置,能夠在建立或啓動容器時利用下面的參數指定:
1)指定主機名-h HOSTNAME或者--hostname=HOSTNAME。設定容器的主機名,它會被寫到容器內的/etc/hostname和/etc/hosts。但這個主機名只有容器內能看到,在容器外部則看不到,既不會在docker ps中顯示,也不會在其餘的容器的/etc/hosts看到。
2)記錄其餘容器主機名--link=CONTAINER_NAME:ALIAS。選項會在建立容器的時候,添加一個所鏈接容器的主機名到容器內/etc/hosts文件中。這樣,新建立容器能夠直接使用主機名來與所鏈接容器通訊。
3)指定DNS服務器--dns=IP_ADDRESS。添加DNS服務器到容器的/etc/resolv.conf中,容器會用指定的服務器來解析全部不在/etc/hosts中的主機名。
4)指定DNS搜索域--dns-search=DOMAIN。設定容器的搜索域,當設定搜索域爲.example.com時,在搜索一個名爲host的主機時,DNS不只搜索host,還會搜索host.example.com。
容器之間可經過 IP,Docker DNS Server 和joined 容器三種方式通訊。
IP 通訊
兩個容器要能通訊,必需要有屬於同一個網絡的網卡。知足這個條件後,容器就能夠經過 IP 交互了。具體作法是在容器建立時經過 --network 指定相應的網絡,或者經過 docker network connect 將現有容器加入到指定網絡。
Docker DNS Server
經過 IP 訪問容器雖然知足了通訊的需求,但仍是不夠靈活。由於咱們在部署應用以前可能沒法肯定 IP,部署以後再指定要訪問的 IP 會比較麻煩。對於這個問題,能夠經過 docker 自帶的 DNS 服務解決。
從 Docker 1.10 版本開始,docker daemon 實現了一個內嵌的 DNS server,使容器能夠直接經過"容器名"通訊。方法很簡單,只要在啓動時用 --name 爲容器命名就能夠了。
下面啓動兩個容器 box1 和 box2,而且在上面定義的網絡模式bridge2中:
docker run -it --network=bridge2 --name box1 busybox
docker run -it --network=bridge2 --name box2 busybox
box2和 box1能夠互ping通
使用 docker DNS 有個限制:只能在 user-defined 網絡中使用。也就是說,默認的 bridge 網絡是沒法使用 DNS 的。
joined 容器
joined 容器是另外一種實現容器間通訊的方式。它可使兩個或多個容器共享一個網絡棧,共享網卡和配置信息,joined 容器之間能夠經過 127.0.0.1 直接通訊。例:
先建立一個http容器,名字爲 box1
docker run -it --name box1 http
而後建立 busybox 容器並經過 --network=container:box1 指定 jointed 容器爲 box1:
docker run -it --network=container:box1 busybox
box1 的網絡:
busybox 和 box1 的網卡 mac 地址與 IP 徹底同樣,它們共享了相同的網絡棧。busybox 能夠直接用 127.0.0.1 訪問 box1 的 http 服務。
joined 容器很是適合如下場景:
不一樣容器中的程序但願經過 loopback 高效快速地通訊,好比 web server 與 app server。
但願監控其餘容器的網絡流量,好比運行在獨立容器中的網絡監控程序。
容器的訪問控制主要經過Linux上的iptables防火牆軟件來進行管理和實現。iptables是Linux系統流行的防火牆軟件,在大部分發行版中都自帶。
咱們知道容器默認指定了網關爲docker0網橋上的docker0內部接口。docker0內部接口同時也是宿主機的一個本地接口。所以,容器默認狀況下是能夠訪問到宿主機本地的。更進一步,容器要想經過宿主機訪問到外部網絡,須要宿主機進行轉發。
若是爲0,則沒有開啓轉發,則須要手動打開:
# sysctl -w net.ipv4.ip_forward=1
更簡單的,在啓動Docker服務的時候設定--ip-forward=true,Docker服務會自動打開宿主機系統的轉發服務。
容器容許外部訪問,能夠在docker run時候經過-p或-P參數來啓用。
無論用那種辦法,其實也是在本地的iptable的nat表中添加相應的規則,將訪問外部IP地址的網包進行目標地址DNAT,將目標地址修改成容器的IP地址。
以一個開放80端口的Web容器爲例,使用-P時,會自動映射本地49000~49900範文內的端口隨機端口到容器的80端口:
能夠看到,nat表中涉及兩條鏈,PREROUTING鏈負責包到達網絡接口時,改寫其目的地址。其中規則將全部流量都扔到DOCKER鏈。而DOCKER鏈中將全部不是從docker0進來的網包(意味着不是本地主機產生),將目標端口爲49153的,修改目標地址爲172.17.0.2,目標端口修改成80。
使用-p 80:80時,與上面相似,只是本地端口也爲80:
有兩點須要注意:
這裏的規則映射了0.0.0.0,意味着將接受主機來自全部網絡接口上的流量。用戶能夠經過-p IP:host_port:container_port或-p IP::port來指定綁定的外部網絡接口,以制定更嚴格的訪問規則;
若是但願映射永久綁定到某個固定的IP地址,能夠在Docker配置文件/etc/default/docker中指定DOCKER_OPTS="--ip=IP_ADDRESS",以後重啓Docker服務便可生效。
Docker服務默認會建立一個名稱爲docker0的Linux網橋(其上有一個docker0內部接口),它在內核層連通了其餘的物理或虛擬網卡,這就將全部容器和本地主機都放到同一個物理網絡。用戶使用Docker建立多個自定義網絡時可能會出現多個容器網橋。
Docker默認指定了docker0接口的IP地址和子網掩碼,讓主機和容器之間能夠經過網橋相互通訊,它還給出了MTU(接口容許接收的最大傳輸單元),一般是1500字節,或宿主主機網絡路由上支持的默認值。這些值均可以在服務啓動的時候進行配置:
--bip=CIDR——IP地址加掩碼格式,例如192.168.1.5/24;
--mtu=BYTES——覆蓋默認的Docker mtu配置。
也能夠在配置文件中配置OPTIONS,而後重啓服務。
因爲目前Docker網橋是Linux網橋,用戶可使用brctl show來查看網橋和端口鏈接信息。
brctl命令若是系統中沒有自帶,可使用 yum install bridge-utils安裝(centos)
每次建立一個新容器的時候,Docker從可用的地址段中選擇一個空閒的IP地址分配給容器的eth0端口。而且使用本地主機上docker0接口的IP做爲容器的默認網關。
目前,Docker不支持在啓動容器時候指定IP地址。實際上,Linux網橋自身功能已經十分完備,也能夠替換爲OpenvSwitch等功能更強大的網橋實現。
在上述docker網絡模式已經講到,使用docker network便可實現。
Docker默認使用的是Linux自帶的網橋實現,實際上,OpenvSwitch項目做爲一個成熟的虛擬交換機實現,具有更豐富的功能。未來會有愈來愈多的容器支持OpenvSwitch做爲底層網橋實現。
1.安裝Docker(centos 7)
安裝並啓動服務。默認Docker服務會建立一個名爲docker0的Linux網橋,做爲鏈接容器的本地網橋。
2.安裝OpenvSwitch
# yum install -y openvswitch
測試添加一個網橋br0並查看:
# ovs-vsctl add-br br0
# ovs-vsctl show
3.配置容器鏈接到OpenvSwitch網橋
目前OpenvSwitch網橋還不能直接支持掛載容器,須要手動在OpenvSwitch網橋上建立虛擬網口並掛載到容器中。
(1)建立無網口容器
啓動一個容器,並指定不建立網絡,後面咱們手動添加網絡。較新版本的Docker默認不容許在容器內修改網絡配置,須要在run的時候指定參數--privileged=true:
記住這裏容器的id。
(2)手動爲容器添加網絡
下載OpenvSwitch項目提供的支持Docker容器的輔助腳本ovs-docker:
# wget https://github.com/openvswitch/ovs/raw/master/utilities/ovs-docker
# chmod a+x ovs-docker
爲容器添加網卡,並掛載到br0上,命令爲:
#./ovs-docker add-port br0 eth0 00b9028e14aa --ipaddress=10.0.0.2/16
添加成功後,在容器內查看網絡信息,多了一個新添加的網卡eth0,對應添加的IP地址。
在容器外,配置OpenvSwitch的網橋br0內部接口地址爲10.0.1.2/16(只要與所掛載容器IP在同一個子網內便可):
# ifconfig br0 10.0.1.2/16
(3)測試連通
通過上面步驟,容器已經鏈接到了網橋br0上了,拓撲以下所示:
容器(10.0.7.2/16)<-->br0網橋<-->br0內部端口(10.0.1.2/16)
此時,在容器內就能夠測試是否連通到網橋br0上了:
在容器內也能夠配置默認網關爲br0接口地址:
route add default gw GWIP
刪除該接口的命令爲:
# ./ovs-docker del-port br0 eth0<container_id>
刪除名爲br0的網橋:ovs-vsctl del-br br0
實際上,Docker社區也已經討論對OpenvSwitch進行原生支持了。在Docker原生支持OpenvSwitch以前,用戶能夠經過編寫腳本或更高級的工具來讓這一過程自動化。