想要變成 Docker 的高階玩家,搞懂 Docker 的容器通信是必不可少的。
1、需求
通常一個 Web 項目上線,我們會把開發完成的服務部署在Tomcat 服務器里面,然后需要的持久化數據會存放在數據庫 Mysql,那么在服務運行時,少不了 Tomcat 和 Mysql 的交互。
對應的,應用到 Docker 中,就是 Tomcat 容器和 Mysql 容器間的交互,那么問題來了:
兩個容器之間怎么通信呢?
1.1 准備兩個鏡像
①、Tomcat
FROM tomcat:latest
MAINTAINER itcoke
WORKDIR /usr/local/tomcat/webapps
COPY ./webapps/ /usr/local/tomcat/webapps/
RUN apt update && apt install -y iproute2 && apt install -y iputils-ping && apt install -y vim
這是制作 Tomcat 鏡像的 Dockerfile,因為目前最新版的官方 Tomcat 鏡像沒有一個網絡查看命令,所以需要手動安裝。
構建鏡像命令:
docker build -f Dockerfile -t itcoke/mytomcat8:1.0 .
②、MySQL
FROM mysql:8.0
MAINTAINER itcoke
RUN apt update && apt install -y iproute2 && apt install -y iputils-ping && apt install -y vim
構建MySQL鏡像命令:
docker build -f Dockerfile -t itcoke/mysql8:1.0 .
1.2 啟動容器
①、啟動並進入Tomcat容器
docker run -it -p 8080:8080 --name tomcat1 itcoke/mytomcat8:1.0 /bin/bash
②、啟動並進入MySQL容器
docker run -it -p 3306:3306 --name mysql1 itcoke/mysql8:1.0 /bin/bash
1.3 通過 IP 通信
容器創建好了,想要進行通信,我們第一時間會想到通過 IP,我們通過如下命令查看容器 IP 地址:
ip addr
Tomcat 容器IP:
MySQL 容器IP:
可以看到容器是有 IP的,我們在 Tomcat容器ping MySQL容器:
自此大功告成,我們可以說容器間通信使用 IP 就行。
2、問題
通過 IP 通信,我們看似解決了容器間通信的問題,但在實際生產中,我們容器是會經常重新啟動的,而上面的容器 IP 是Docker 分配的虛擬IP,這個IP是會變得,假設我們每次重新構建一個容器,那就要重新修改服務配置IP,生產環境會有幾十個幾百個容器,都要進行修改,這將是很麻煩的。
那么怎么辦呢?熟悉 IP-域名解析的同學,可能會一下想到,保證域名不變的情況,IP 無論怎么變,通過 DNS 解析都是能正確訪問到網頁的,於是:
有沒有辦法通過容器名來通信呢?
3、容器名通信
前面我們驗證了可以通過容器IP來進行通信,但是容器重新構建IP會發生變化,這給我們造成很大的麻煩,於是我們想到通過容器名來進行通信,下面測試一下:
PS:前面啟動容器時,我們給Tomcat容器命名為 tomcat1,給MySQL容器命名為mysql1。
我們發現直接通過容器名是不能夠通信的。那么應該怎么辦呢?
3.1 容器單向通信
啟動容器的時候通過增加 --link 容器名 參數:
比如:
docker run -it -p 8080:8080 --name tomcat1 --link mysql1 3336fdaf451a /bin/bash
然后,我們在 tomcat1 容器ping mysql1 :
為什么說是單向通信,如果啟動 mysql1 容器的時候沒有增加--link 參數,則 mysql1 訪問不了 tomcat1。
PS:如果你查看tomcat1 容器的 /etc/hosts 文件,發現 --link 就是增加了名字解析:
而mysql1 容器的 /etc/hosts 則沒有名字解析:
4、通信原理
知道了容器之間可以通信,但是為什么能夠通信呢?
我們啟動了一個 Tomcat1容器,啟動了一個 MySQL1容器,下面我們看下宿主機IP:
①、本機回環地址
lo,127.0.0.1,不屬於任何一個有類別地址類。它代表設備的本地虛擬接口,通常在安裝網卡前就可以ping通這個本地回環地址。
一般用來測試本機的網絡配置,能PING通 127.0.0.1 說明本機的IP協議安裝沒有問題。
②、服務器內網地址
ens33,192.168.88.2,這也是我創建docker宿主機的真實IP地址。
注意:我這里是安裝虛擬機,如果是真實物理機,這個名字可能是eth0,eth0表示第一塊網卡,同理eth2表示第二塊網卡。
③、docker0
Docker啟動的時候會在主機上自動創建一個docker0網橋(注意名字一定是docker0,會有docker1,docker2之類),實際上是一個 Linux 網橋,所有容器的啟動如果在docker run的時候沒有指定網絡模式的情況下都會掛載到docker0網橋上。
④、容器地址
在宿主機查看運行ip addr,可以看到
tomcat1 的名稱是:45: veth8eb364e@if44
mysql1的名稱是:49: veth02cb24d@if48
我們進入到容器tomcat1內部,查看ip:
44: eth0@if45
同理,進入到容器 mysql1 內部,查看ip:
48: eth0@if49
不知道大家注沒注意到這一串名稱的數字其實是關聯的,這就是大名鼎鼎的 veth-pair 技術。
4.1 veth-pair
veth-pair 就是一對的虛擬設備接口,它都是成對出現的。一端連着協議棧,一端彼此相連着,因為這個特性,它常常充當着一個橋梁,連接着各種虛擬網絡設備,典型的例子像“兩個 namespace 之間的連接”,“Bridge、OVS 之間的連接”,“Docker 容器之間的連接” 等等,以此構建出非常復雜的虛擬網絡結構,比如 OpenStack Neutron。

多個容器之間通信依賴 veth-pair 技術:
5、容器間雙向通信
其實就是利用網橋鏈接新創建的容器和宿主機,上面圖片的 docker0 就是一個網橋。
docker network ls #查看網橋
①、host:容器將不會虛擬出自己的網卡,配置自己的IP等,而是使用宿主機的IP和端口。
②、none:該模式關閉了容器的網絡功能。
③、bridge:此模式會為每一個容器分配、設置IP等,並將容器連接到一個docker0虛擬網橋,通過docker0網橋以及Iptables nat表配置與宿主機通信。
下面我們就自定義一個網橋,利用自定義bridge模式進行雙向通信。
其實 docker0 就是一個默認網橋,為什么我們還要自定義呢?
使用自定義的網橋可以控制哪些容器可以互相通信,可以通過容器名通信(自動DNS解析名稱到IP地址,這個docker0是不支持的)。
一、創建自定義網橋
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 myBridge
②、啟動容器
docker run -it -p 8080:8080 --name tomcat1 --net myBridge 3336fdaf451a /bin/bash
docker run -it -p 3306:3306 --name mysql1 --net myBridge adaa6a5d739c /bin/bash
大功告成,我們發現通過容器名稱是可以 ping 通了。
5.1 不同bridge 網絡通信
docker0 和 myBridge 里面的容器可以互相ping通嗎?
答案是不行的,那么如何打通呢?我們只需要將容器鏈接到另一個網橋即可。
docker network connect [OPTIONS] network container
比如,我們要把默認網橋 docker0 上面的 tomcat1-docker0 容器能鏈接 myBridge 網橋里面的容器,只需要執行以下命令即可。
docker network connect myBridge tomcat1-docker0
然后進入 tomcat1-docker0 容器,發現可以 ping 通 myBridge 網橋里面的容器了。
並且查看 tomcat1-docker0 容器的ip,你會發現有兩個 ip了,也就是一個容器,多個ip。