Docker:Docker网络管理(宿主机和容器互相访问,容器间互相访问,跨机器访问)

摘要:Docker

整理Docker网络管理知识,包括Docker网络基础,宿主机和容器互相访问,容器间网络通信,跨机器访问容器服务

(1)Docker网络类型

Docker提供了5种网络类型,其中常见的两种:bridge及host

Bridge

Bridge(网桥)是Docker默认使用的网络类型,用于同一主机上的docker容器相互通信,网络中的所有容器可以通过IP互相访问,连接到同一个网桥的docker容器可以相互通信。Bridge网络通过网络接口docker0 与主机桥接,启动docke时就会自动创建,新创建的容器默认都会自动连接到这个网络,可以在主机上通过ifconfig docker0查看到该网络接口的信息

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模式下,容器的网络接口不与宿主机网络隔离。在容器中监听相应端口的应用能够直接被从宿主机访问,即不需要做端口映射。host网络仅支持Linux,host网络没有与宿主机网络隔离,可能引发安全隐患或端口冲突


(2)宿主机访问容器应用

这种是最常见的场景,使用容器部署应用,给其他机器提供服务,此时容器和宿主机IP是通的,可以直接使用容器的虚拟IP+端口进行访问容器服务,如果想使用宿主机的IP访问到容器服务,需要将宿主机的某端口映射到容器的服务端口,即对外暴露端口供宿主机和其他机器访问,如果不发布端口,外界将无法访问这些容器

使用容器的虚拟IP访问

服务准备,现有一个flask api,允许其他IP远程链接,服务端口为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)

对这个Python api服务使用docker部署成服务,此时可以使用docker inspect来查看容器的虚拟IP为172.17.0.2

root@ubuntu:~/docker_test/p_test# docker inspect -f {{".NetworkSettings.IPAddress"}} 9ed6d53f5999
172.17.0.2

测试在宿主机请求容器的服务api是否能够返回数据

>>> 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'}

结果是可以正常访问,但是实际场景中不使用虚拟IP直接访问,原因是

  • 虚拟IP不固定:容器启动分配的虚拟IP是不固定的,每次启动容器都需要重新查看一次容器IP
  • 跨机器无法访问:容器值和所在的宿主机和其他容器IP互通,跨机器无法ping通在这个机器上生成的容器虚拟IP
bridge模式

在bridge网络模式下,连接到同一bridge网络的容器可以相互访问彼此任意一个端口,,在docker run中设定参数指定端口,有-p-P两种方式

  • -p:指定端口映射-p 宿主机端口:容器端口,指定宿主机的端口映射到容器端口,会让容器暴露出一个指定端口供宿主机访问,直接在docker run中指定即可
  • -P:对于已经暴露的容器端口,随机给出一个宿主机端口进行映射,随机端口范围是32769-60999,需要配合Dockerfile中的EXPOSE一起使用,在启动容器后查看容器的port信息获得随机值
    给flask api构建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"]

构建镜像

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

启动镜像指定-p将宿主机端口映射到容器端口实现宿主机访问容器应用进行数据交互,并查看容器的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

此时访问0.0.0.0:5000即可在外部网络访问容器应用api实现数据交互

>>> 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则不能达到任何效果,因为容器内没有暴露任何端口,api应用的默认5000端口也不能自动暴露出来

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

可见port信息为空,外网无法调用容器api应用,正确的使用方式是在Dockerfile中指定EXPOSE暴露一个5000端口出来,然后-P随机映射一个宿主机端口

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"]

重新构建镜像和启动容器,可以查看到ports信息0.0.0.0:49153->5000/tcp,49153是随机分配的宿主机端口

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

调用容器api测试,可以调用成功

>>> 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强制指定宿主机的端口,还不是-P随机分配,-p相当于先指定容器暴露某个端口,再指定某个宿主机端口和容器端口进行映射

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的端口外部网络还是无法访问,但是-p则可以自由地设置宿主机端口和容器端口

Host模式

此模式下可以直接在宿主机访问容器中监听的端口,不需要做端口映射,比如在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中设置--net=host来设置

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

此时查看不到端口信息,因为没有申明端口映射也没有暴露端口,但是依旧可以直接在宿主机使用容器中api的默认5000端口访问

>>> 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)容器访问外部网络

Bridge

在网桥bridge模式下docker0网络的默认网关就是宿主机IP,linux下docker0的网关地址为172.17.0.1,在容器中使用该IP地址即可访问宿主机上的各种服务
测试游一下在容器中部署一个一个flask API服务,API需要访问宿主机的MySQL数据库,先创建数据库表,注释掉MySQL的bind-address或者修改为0.0.0.0允许远程链接的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

创建一个API实现查询MySQL返回数据的功能

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)

构建镜像

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下存储了MySQL的链接信息,其中host是docker0的网关地址172.17.0.1,不能是127.0.0.1,在容器中127.0.0.1指向容器自己,最后把config和相关的目录一起挂载到容器下运行

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

测试接口返回数据正常

>>> res = requests.post("http://127.0.0.1:5001/student", json={"name": "b"})
>>> res.json()
{'code': 200, 'data': '[{"phone": "13874530293"}]', 'msg': 'success'}

如果将配置中的host改为本地回环地址127.0.0.1则报错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宿主机网络,此时容器的localhost就是宿主机的localhost,可以直接使用本地调用服务的IP和端口,在启动时设置--net=host来进行设定,此时config可以直接指定本地回环地址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)容器间互相访问

使用容器虚拟IP直接访问

在同一个网桥下的所有容器IP是相通的,在获得容器IP之后,可以在某个容器中指定访问其他容器的IP和端口进行访问,现将宿主机的MySQL搬到容器中,在另一个容器中启动一个API访问MySQL,先下载一个MySQL镜像然后启动容器MySQL服务

root@ubuntu:~# docker pull mysql:5.7

容器启动脚本,端口映射到本机的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远程链接脚本

root@ubuntu:~/docker/mysql# cat connect.sh 
#!/bin/bash
mysql -h127.0.0.1 -P3307 -uroot -pgp123456

测试在宿主机远程链接容器的MySQL服务,并在其中插入几条测试数据

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

重新启动api容器

root@ubuntu:~/docker_test/bridge_test# docker run --rm  -p 5001:5001 -v `pwd`:/home a80d59e81ee8

测试接口可以正常且正确的返回数据

>>> res = requests.post("http://127.0.0.1:5001/student", json={"name": "a"})
>>> res.json()
{'code': 200, 'data': '[{"phone": "13876545372"}]', 'msg': 'success'}
使用--link

如果是以虚拟IP进行访问,IP是经常会变化的,因此另一种方式是采用link来为容器起个名字,link就是类似使用了容器的IP地址,无需知道其真实IP,使用link后的别名,这样解决了IP常发生变化而导致的问题
link的格式如下,别名alias可选

--link <name or id>:alias

将需要被连接的容器叫做源容器,另一个连接源容器的叫做接受容器,接受容器中可以直接ping通link指定的name和alias,相当于此时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

此时再次挂载配置文件启动容器,指定--link将MySQL容器的虚拟IP以容器名并配以别名绑定到api容器一起启动

root@ubuntu:~/docker_test/bridge_test# docker run --rm -d -p 5001:5001 -v `pwd`:/home --link mysql:mysql_host a80d59e81ee8
810a6c982a5b709ce104fbdf89268e2243760e70b9eeae26b117255e1a267c6d

测试api是否能够正常访问ok

>>> res = requests.post("http://127.0.0.1:5001/student", json={"name": "a"})
>>> res.json()
{'code': 200, 'data': '[{"phone": "13345434986"}]', 'msg': 'success'}

此时进入接受容器,发现在/etc/hosts下已经增加了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

跨机器访问容器服务

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容