摘要:Docker
整理Docker網(wǎng)絡(luò)管理知識(shí)贿肩,包括Docker網(wǎng)絡(luò)基礎(chǔ),宿主機(jī)和容器互相訪(fǎng)問(wèn)龄寞,容器間網(wǎng)絡(luò)通信汰规,跨機(jī)器訪(fǎng)問(wèn)容器服務(wù)
(1)Docker網(wǎng)絡(luò)類(lèi)型
Docker提供了5種網(wǎng)絡(luò)類(lèi)型,其中常見(jiàn)的兩種:bridge及host
Bridge
Bridge(網(wǎng)橋)是Docker默認(rèn)使用的網(wǎng)絡(luò)類(lèi)型物邑,用于同一主機(jī)上的docker容器相互通信溜哮,網(wǎng)絡(luò)中的所有容器可以通過(guò)IP互相訪(fǎng)問(wèn),連接到同一個(gè)網(wǎng)橋的docker容器可以相互通信拂封。Bridge網(wǎng)絡(luò)通過(guò)網(wǎng)絡(luò)接口docker0
與主機(jī)橋接茬射,啟動(dòng)docke時(shí)就會(huì)自動(dòng)創(chuàng)建,新創(chuàng)建的容器默認(rèn)都會(huì)自動(dòng)連接到這個(gè)網(wǎng)絡(luò)冒签,可以在主機(jī)上通過(guò)ifconfig docker0
查看到該網(wǎng)絡(luò)接口的信息
root@ubuntu:~# ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
Host
Host模式下在抛,容器的網(wǎng)絡(luò)接口不與宿主機(jī)網(wǎng)絡(luò)隔離。在容器中監(jiān)聽(tīng)相應(yīng)端口的應(yīng)用能夠直接被從宿主機(jī)訪(fǎng)問(wèn)萧恕,即不需要做端口映射刚梭。host網(wǎng)絡(luò)僅支持Linux,host網(wǎng)絡(luò)沒(méi)有與宿主機(jī)網(wǎng)絡(luò)隔離票唆,可能引發(fā)安全隱患或端口沖突
(2)宿主機(jī)訪(fǎng)問(wèn)容器應(yīng)用
這種是最常見(jiàn)的場(chǎng)景朴读,使用容器部署應(yīng)用,給其他機(jī)器提供服務(wù)走趋,此時(shí)容器和宿主機(jī)IP是通的衅金,可以直接使用容器的虛擬IP+端口進(jìn)行訪(fǎng)問(wèn)容器服務(wù),如果想使用宿主機(jī)的IP訪(fǎng)問(wèn)到容器服務(wù),需要將宿主機(jī)的某端口映射到容器的服務(wù)端口氮唯,即對(duì)外暴露端口供宿主機(jī)和其他機(jī)器訪(fǎng)問(wèn)鉴吹,如果不發(fā)布端口,外界將無(wú)法訪(fǎng)問(wèn)這些容器
使用容器的虛擬IP訪(fǎng)問(wèn)
服務(wù)準(zhǔn)備惩琉,現(xiàn)有一個(gè)flask api豆励,允許其他IP遠(yuǎn)程鏈接,服務(wù)端口為5000
import json
import datetime
import traceback
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route("/search", methods=["POST"])
def api():
res = {}
try:
data = request.get_json(force=True)
ent = data["ent"]
dt = data.get("dt", datetime.datetime.today().strftime("%Y-%m-%d"))
res = {"msg": "success", "code": 200, "data": json.dumps({"ent": ent, "dt": dt}, ensure_ascii=False)}
code = 200
except Exception as e:
res = {"meg": "fail", "code": 400, "trace": traceback.format_exc()}
code = 400
app.logger.info(res)
return jsonify(res), code
if __name__ == '__main__':
app.run("0.0.0.0", 5000, debug=False)
對(duì)這個(gè)Python api服務(wù)使用docker部署成服務(wù)瞒渠,此時(shí)可以使用docker inspect
來(lái)查看容器的虛擬IP為172.17.0.2
root@ubuntu:~/docker_test/p_test# docker inspect -f {{".NetworkSettings.IPAddress"}} 9ed6d53f5999
172.17.0.2
測(cè)試在宿主機(jī)請(qǐng)求容器的服務(wù)api是否能夠返回?cái)?shù)據(jù)
>>> import requests
>>> res = requests.post("http://172.17.0.2:5000/search", json={"ent": "a"})
>>> res.json()
{'code': 200, 'data': '{"ent": "a", "dt": "2021-07-08"}', 'msg': 'success'}
結(jié)果是可以正常訪(fǎng)問(wèn)良蒸,但是實(shí)際場(chǎng)景中不使用虛擬IP直接訪(fǎng)問(wèn),原因是
-
虛擬IP不固定
:容器啟動(dòng)分配的虛擬IP是不固定的伍玖,每次啟動(dòng)容器都需要重新查看一次容器IP -
跨機(jī)器無(wú)法訪(fǎng)問(wèn)
:容器值和所在的宿主機(jī)和其他容器IP互通嫩痰,跨機(jī)器無(wú)法ping通在這個(gè)機(jī)器上生成的容器虛擬IP
bridge模式
在bridge網(wǎng)絡(luò)模式下,連接到同一bridge網(wǎng)絡(luò)的容器可以相互訪(fǎng)問(wèn)彼此任意一個(gè)端口私沮,始赎,在docker run中設(shè)定參數(shù)指定端口,有-p
仔燕,-P
兩種方式
-
-p
:指定端口映射-p 宿主機(jī)端口:容器端口
造垛,指定宿主機(jī)的端口映射到容器端口,會(huì)讓容器暴露出一個(gè)指定端口供宿主機(jī)訪(fǎng)問(wèn)晰搀,直接在docker run中指定即可 -
-P
:對(duì)于已經(jīng)暴露的容器端口五辽,隨機(jī)
給出一個(gè)宿主機(jī)端口進(jìn)行映射,隨機(jī)端口范圍是32769-60999外恕,需要配合Dockerfile中的EXPOSE
一起使用杆逗,在啟動(dòng)容器后查看容器的port信息獲得隨機(jī)值
給flask api構(gòu)建Dockerfile,其中不指定EXPOSE暴露端口
FROM python:3.6
MAINTAINER xiaogp
ENV PIPURL=https://mirrors.aliyun.com/pypi/simple/
COPY . /home
RUN pip install -i ${PIPURL} flask --default-timeout=1000
WORKDIR /home
CMD ["python", "api.py"]
構(gòu)建鏡像
root@ubuntu:~/docker_test/Pp_test# docker build -t xiaogp/p_test:v1 .
root@ubuntu:~/docker_test/Pp_test# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
xiaogp/p_test v1 98b4d1d5d440 6 minutes ago 886MB
啟動(dòng)鏡像指定-p
將宿主機(jī)端口映射到容器端口實(shí)現(xiàn)宿主機(jī)訪(fǎng)問(wèn)容器應(yīng)用進(jìn)行數(shù)據(jù)交互鳞疲,并查看容器的port詳情
root@ubuntu:~/docker_test/Pp_test# docker run -p 5000:5000 --rm -d 98b4d1d5d440
root@ubuntu:~/docker_test/Pp_test# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
12f2d9124300 98b4d1d5d440 "python api.py" 6 seconds ago Up 4 seconds 0.0.0.0:5000->5000/tcp upbeat_jemison
此時(shí)訪(fǎng)問(wèn)0.0.0.0:5000即可在外部網(wǎng)絡(luò)訪(fǎng)問(wèn)容器應(yīng)用api實(shí)現(xiàn)數(shù)據(jù)交互
>>> import requests
>>> res = requests.post("http://127.0.0.1:5000/search", json={"ent": "123"})
>>> res.json()
{'code': 200, 'data': '{"ent": "123", "dt": "2021-07-04"}', 'msg': 'success'}
如果使用-P
則不能達(dá)到任何效果罪郊,因?yàn)槿萜鲀?nèi)沒(méi)有暴露任何端口,api應(yīng)用的默認(rèn)5000端口也不能自動(dòng)暴露出來(lái)
root@ubuntu:~/docker_test/Pp_test# docker run -P --rm -d 98b4d1d5d440
7267db6adb6cf5a3645e4bd55d9c7e6d57d50498d3ba35b4f3efb596f095e3fb
root@ubuntu:~/docker_test/Pp_test# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7267db6adb6c 98b4d1d5d440 "python api.py" 10 seconds ago Up 8 seconds funny_mcclintock
可見(jiàn)port信息為空尚洽,外網(wǎng)無(wú)法調(diào)用容器api應(yīng)用悔橄,正確的使用方式是在Dockerfile中指定EXPOSE暴露一個(gè)5000端口出來(lái),然后-P隨機(jī)映射一個(gè)宿主機(jī)端口
FROM python:3.6
MAINTAINER xiaogp
ENV PIPURL=https://mirrors.aliyun.com/pypi/simple/
COPY . /home
RUN pip install -i ${PIPURL} flask --default-timeout=1000
WORKDIR /home
EXPOSE 5000
CMD ["python", "api.py"]
重新構(gòu)建鏡像和啟動(dòng)容器腺毫,可以查看到ports信息0.0.0.0:49153->5000/tcp癣疟,49153是隨機(jī)分配的宿主機(jī)端口
root@ubuntu:~/docker_test/Pp_test# docker build -t xiaogp/p_test:v2 .
root@ubuntu:~/docker_test/Pp_test# docker run -P --rm -d 82bbc6bd3778
8b127ae48ef94ee736ae1183f6862b6af5a1fcfc08253d100a8c2d4eac6ab6a5
root@ubuntu:~/docker_test/Pp_test# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8b127ae48ef9 82bbc6bd3778 "python api.py" 3 seconds ago Up 2 seconds 0.0.0.0:49153->5000/tcp sharp_vaughan
調(diào)用容器api測(cè)試,可以調(diào)用成功
>>> res = requests.post("http://127.0.0.1:49153/search", json={"ent": "123"})
>>> res.json()
{'code': 200, 'data': '{"ent": "123", "dt": "2021-07-04"}', 'msg': 'success'}
在指定了EXPOSE之后依然可以使用-p
強(qiáng)制指定宿主機(jī)的端口潮酒,還不是-P
隨機(jī)分配睛挚,-p相當(dāng)于先指定容器暴露某個(gè)端口,再指定某個(gè)宿主機(jī)端口和容器端口進(jìn)行映射
root@ubuntu:~/docker_test/Pp_test# docker run -p 5000:5000 --rm -d 82bbc6bd3778
418599b4cb619de3bfc2ba5f049093a97a0cfc8d01cb8075fdcee39d91722a2a
root@ubuntu:~/docker_test/Pp_test# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
418599b4cb61 82bbc6bd3778 "python api.py" 5 minutes ago Up 5 minutes 0.0.0.0:5000->5000/tcp eloquent_lehmann
另外如果使用EXPOSE則暴露的端口必須和api的端口一致急黎,否則api的端口外部網(wǎng)絡(luò)還是無(wú)法訪(fǎng)問(wèn)扎狱,但是-p則可以自由地設(shè)置宿主機(jī)端口和容器端口
Host模式
此模式下可以直接在宿主機(jī)訪(fǎng)問(wèn)容器中監(jiān)聽(tīng)的端口侧到,不需要做端口映射,比如在Dockerfile中不指定EXPOSE委乌,不顯式的暴露端口
FROM python:3.6
MAINTAINER xiaogp
ENV PIPURL=https://mirrors.aliyun.com/pypi/simple/
COPY . /home
RUN pip install -i ${PIPURL} flask --default-timeout=1000
WORKDIR /home
CMD ["python", "api.py"]
在docker run中設(shè)置--net=host
來(lái)設(shè)置
root@ubuntu:~/docker_test/Pp_test# docker build -t xiaogp/p_test:v3 .
root@ubuntu:~/docker_test/Pp_test# docker run --net=host --rm -d ca47c8bec4d1
792912903b80e877159bdfc54315cba16f355b38fec8a37e41345ad67c8f96cf
root@ubuntu:~/docker_test/Pp_test# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
792912903b80 ca47c8bec4d1 "python api.py" 6 seconds ago Up 1 second elastic_wiles
此時(shí)查看不到端口信息床牧,因?yàn)闆](méi)有申明端口映射也沒(méi)有暴露端口荣回,但是依舊可以直接在宿主機(jī)使用容器中api的默認(rèn)5000端口訪(fǎng)問(wèn)
>>> res = requests.post("http://127.0.0.1:5000/search", json={"ent": "123"})
>>> res.json()
{'code': 200, 'data': '{"ent": "123", "dt": "2021-07-04"}', 'msg': 'success'}
>>> res = requests.post("http://192.168.1.28:5000/search", json={"ent": "123"})
>>> res.json()
{'code': 200, 'data': '{"ent": "123", "dt": "2021-07-04"}', 'msg': 'success'}
(3)容器訪(fǎng)問(wèn)外部網(wǎng)絡(luò)
Bridge
在網(wǎng)橋bridge模式下docker0網(wǎng)絡(luò)的默認(rèn)網(wǎng)關(guān)就是宿主機(jī)IP遭贸,linux下docker0的網(wǎng)關(guān)地址為172.17.0.1,在容器中使用該IP地址即可訪(fǎng)問(wèn)宿主機(jī)上的各種服務(wù)
測(cè)試游一下在容器中部署一個(gè)一個(gè)flask API服務(wù)心软,API需要訪(fǎng)問(wèn)宿主機(jī)的MySQL數(shù)據(jù)庫(kù),先創(chuàng)建數(shù)據(jù)庫(kù)表,注釋掉MySQL的bind-address或者修改為0.0.0.0允許遠(yuǎn)程鏈接的IP連入
mysql> select * from student;
+----+------+-------------+
| id | name | phone |
+----+------+-------------+
| 1 | ? | 13852517263 |
| 2 | a | 13874530293 |
| 3 | b | 13874530293 |
| 4 | e | 13879930293 |
+----+------+-------------+
# vim /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 0.0.0.0
創(chuàng)建一個(gè)API實(shí)現(xiàn)查詢(xún)MySQL返回?cái)?shù)據(jù)的功能
import json
from flask import Flask, jsonify, request
import pymysql
import yaml
CONFIG = yaml.load(open("./etc/config.yml"), Loader=yaml.FullLoader)
print(CONFIG)
app = Flask(__name__)
def get_mysql_data(name: str):
res = []
conn = pymysql.connect(**CONFIG)
cursor = conn.cursor()
cursor.execute("SELECT phone FROM student WHERE name='{}'".format(name))
data = cursor.fetchall()
for line in data:
res.append({"phone": line[0]})
return res
@app.route("/student", methods=["POST"])
def get_data_api():
res = {}
try:
data = request.get_json(force=True)
name = data["name"]
data = get_mysql_data(name)
res = jsonify({"code": 200, "msg": "success", "data": json.dumps(data, ensure_ascii=False)})
except Exception as e:
print(e)
res = jsonify({"code": 400, "msg": "fail", "trace": e.args})
return res
if __name__ == '__main__':
app.run("0.0.0.0", 5001, debug=False)
構(gòu)建鏡像
root@ubuntu:~/docker_test/bridge_test# cat Dockerfile
FROM python:3.7
MAINTAINER xiaogp
ENV PIPURL=https://mirrors.aliyun.com/pypi/simple/
COPY . /home
WORKDIR /home
RUN pip install -r requirements.txt -i ${PIPURL}
CMD ["python", "api.py"]
root@ubuntu:~/docker_test/bridge_test# tree
.
├── api.py
├── Dockerfile
├── etc
│ └── config.yml
└── requirements.txt
root@ubuntu:~/docker_test/bridge_test#docker build -t xiaogp/bridge_test .
在/etc/config.yml下存儲(chǔ)了MySQL的鏈接信息恼布,其中host是docker0的網(wǎng)關(guān)地址172.17.0.1攒射,不能是127.0.0.1,在容器中127.0.0.1指向容器自己猎唁,最后把config和相關(guān)的目錄一起掛載到容器下運(yùn)行
root@ubuntu:~/docker_test/bridge_test/etc# cat config.yml
host: 172.17.0.1
port: 3306
database: test
user: xiaogp
password: gp123456
root@ubuntu:~/docker_test/bridge_test# docker run --rm -p 5001:5001 -v `pwd`:/home a80d59e81ee8
測(cè)試接口返回?cái)?shù)據(jù)正常
>>> res = requests.post("http://127.0.0.1:5001/student", json={"name": "b"})
>>> res.json()
{'code': 200, 'data': '[{"phone": "13874530293"}]', 'msg': 'success'}
如果將配置中的host改為本地回環(huán)地址127.0.0.1則報(bào)錯(cuò)MySQL拒絕鏈接
# 修改host為127.0.0.1
>>> res = requests.post("http://127.0.0.1:5001/student", json={"name": "b"})
>>> res.json()
{'code': 400, 'msg': 'fail', 'trace': [2003, "Can't connect to MySQL server on '127.0.0.1' ([Errno 111] Connection refused)"]}
Host
在Host模式下咒劲,容器直接使用Host宿主機(jī)網(wǎng)絡(luò),此時(shí)容器的localhost就是宿主機(jī)的localhost诫隅,可以直接使用本地調(diào)用服務(wù)的IP和端口腐魂,在啟動(dòng)時(shí)設(shè)置--net=host
來(lái)進(jìn)行設(shè)定,此時(shí)config可以直接指定本地回環(huán)地址127.0.0.1
# 修改config的host
host: 127.0.0.1
port: 3306
database: test
user: xiaogp
password: gp123456
root@ubuntu:~/docker_test/bridge_test# docker run --rm -v `pwd`:/home --net=host a80d59e81ee8
>>> res = requests.post("http://127.0.0.1:5001/student", json={"name": "b"})
>>> res.json()
{'code': 200, 'data': '[{"phone": "13874530293"}]', 'msg': 'success'}
(3)容器間互相訪(fǎng)問(wèn)
使用容器虛擬IP直接訪(fǎng)問(wèn)
在同一個(gè)網(wǎng)橋下的所有容器IP是相通的逐纬,在獲得容器IP之后蛔屹,可以在某個(gè)容器中指定訪(fǎng)問(wèn)其他容器的IP和端口進(jìn)行訪(fǎng)問(wèn),現(xiàn)將宿主機(jī)的MySQL搬到容器中豁生,在另一個(gè)容器中啟動(dòng)一個(gè)API訪(fǎng)問(wèn)MySQL兔毒,先下載一個(gè)MySQL鏡像然后啟動(dòng)容器MySQL服務(wù)
root@ubuntu:~# docker pull mysql:5.7
容器啟動(dòng)腳本,端口映射到本機(jī)的3307
root@ubuntu:~/docker/mysql# cat run.sh
#!/bin/bash
cd /home/docker/mysql
docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=gp123456 --name mysql mysql:5.7
MySQL遠(yuǎn)程鏈接腳本
root@ubuntu:~/docker/mysql# cat connect.sh
#!/bin/bash
mysql -h127.0.0.1 -P3307 -uroot -pgp123456
測(cè)試在宿主機(jī)遠(yuǎn)程鏈接容器的MySQL服務(wù)甸箱,并在其中插入幾條測(cè)試數(shù)據(jù)
root@ubuntu:~/docker/mysql# bash connect.sh
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 7
mysql>
下一步查看MySQL的容器IP為172.17.0.3
root@ubuntu:~/docker/mysql# docker inspect -f {{".NetworkSettings.IPAddress"}} mysql
172.17.0.3
修改api config中的host為MySQL容器的IP為172.17.0.3
root@ubuntu:~/docker_test/bridge_test/etc# cat config.yml
host: 172.17.0.3
port: 3306
database: test
user: root
password: gp123456
重新啟動(dòng)api容器
root@ubuntu:~/docker_test/bridge_test# docker run --rm -p 5001:5001 -v `pwd`:/home a80d59e81ee8
測(cè)試接口可以正常且正確的返回?cái)?shù)據(jù)
>>> res = requests.post("http://127.0.0.1:5001/student", json={"name": "a"})
>>> res.json()
{'code': 200, 'data': '[{"phone": "13876545372"}]', 'msg': 'success'}
使用--link
如果是以虛擬IP進(jìn)行訪(fǎng)問(wèn)育叁,IP是經(jīng)常會(huì)變化的,因此另一種方式是采用link
來(lái)為容器起個(gè)名字芍殖,link就是類(lèi)似使用了容器的IP地址豪嗽,無(wú)需知道其真實(shí)IP,使用link后的別名围小,這樣解決了IP常發(fā)生變化而導(dǎo)致的問(wèn)題
link的格式如下昵骤,別名alias可選
--link <name or id>:alias
將需要被連接的容器叫做源容器,另一個(gè)連接源容器的叫做接受容器肯适,接受容器中可以直接ping通link指定的name和alias变秦,相當(dāng)于此時(shí)name和alias就是MySQL容器的虛擬IP
修改flask api的config的mysql host為mysql_host
root@ubuntu:~/docker_test/bridge_test/etc# cat config.yml
host: mysql_host
port: 3306
database: test
user: root
password: gp123456
此時(shí)再次掛載配置文件啟動(dòng)容器,指定--link將MySQL容器的虛擬IP以容器名并配以別名綁定到api容器一起啟動(dòng)
root@ubuntu:~/docker_test/bridge_test# docker run --rm -d -p 5001:5001 -v `pwd`:/home --link mysql:mysql_host a80d59e81ee8
810a6c982a5b709ce104fbdf89268e2243760e70b9eeae26b117255e1a267c6d
測(cè)試api是否能夠正常訪(fǎng)問(wèn)ok
>>> res = requests.post("http://127.0.0.1:5001/student", json={"name": "a"})
>>> res.json()
{'code': 200, 'data': '[{"phone": "13345434986"}]', 'msg': 'success'}
此時(shí)進(jìn)入接受容器框舔,發(fā)現(xiàn)在/etc/hosts下已經(jīng)增加了MySQL容器name蹦玫,容器id赎婚,link的alias的IP地址映射172.17.0.3 mysql_host 5fc7595d085e mysql
root@ubuntu:~/docker_test/bridge_test/etc# docker exec -it 810a6c982a5b /bin/bash
root@810a6c982a5b:/home# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 mysql_host 5fc7595d085e mysql
172.17.0.4 810a6c982a5b