容器互联介绍
什么是容器互联
容器互联就是在docker中连接两个或多个容器,使这些容器可以相互通信。
例如:有两个容器,一个叫容器A,一个叫容器B。我们可以在启动时,先启动B,然后启动A,实现A能连接B。
互联后的效果:容器A可以直接使用容器B的名称来访问容器B,比如直接ping B,不需要知道B的ip地址。
因为互联后会在A容器的hosts文件中自动添加一条记录,如:容器B的ip 容器B的名称。
容器互联示例
- 先启动容器
B,mysql容器
docker run --name db -d -e MYSQL_ALLOW_EMPTY_PASSWORD="true" mysql
91eb4736238dc00799ae88c77527418045773f0e582f437b79cccb142f834090
-
然后启动容器
A,web容器,并通过--link连接到mysql上;
docker run -it --name web --link db centos:7 /bin/bash -
测试直接ping对应的
B容器名称,也就是db
[root@66e12fb403d9 /]# ping db
PING db (172.17.0.3) 56(84) bytes of data.
64 bytes from db (172.17.0.3): icmp_seq=1 ttl=64 time=0.071 ms
64 bytes from db (172.17.0.3): icmp_seq=2 ttl=64 time=0.052 ms
64 bytes from db (172.17.0.3): icmp_seq=3 ttl=64 time=0.056 ms
- 检查容器
A是否存在对应的解析记录
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 db 91eb4736238d
172.17.0.4 66e12fb403d9
容器互联实践
场景说明

- 首先,我们启动一个
redis容器,命名为myredis。这个redis容器负责提供redis服务 - 然后,我们构建一个
python应用,使用dockerfile构建成镜像,并运行成一个容器。 - 运行
python容器时,我们使用--link,将它连接到myredis容器。这个链接会在python容器内创建一个myredis别名,指向redis容器。这样就可以使用myredis这个别名来访问redis服务。
启动redis容器
docker run -d --name myredis redis:latest
准备app应用
- 编写
python代码(会从REDIS_HOST变量中提取redis地址)
cat app.py
from flask import Flask
from redis import Redis
import os
import socket
app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)
@app.route('/')
def hello():
redis.incr('hits')
return 'Hello Container World! website access %s count! and my hostname is %s.\n' % (redis.get('hits'),socket.gethostname())
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80, debug=True)
- 编写
dockerfile
cat Dockerfile
FROM python:2.7
RUN pip install flask redis
ADD app.py /
EXPOSE 80
CMD [ "python", "/app.py" ]
- 构建镜像
docker build -t python-web:v1.0 .
启动app容器
-
启动
python容器,而后使用--link指向myredis别名的redis容器。 -
设置一个
REDIS_HOST的环境变量,值为myredis,这个值解析出来的结果就是redis容器的ip地址。
docker run -d --name python-web --link myredis -e "REDIS_HOST=myredis" -p 8899:80 python-web:v1.0 -
访问对应主机的8899端口
[root@docker-node01 flask_redis]# curl localhost:8899
Hello Container World! website access 3 count! and my hostname is 8863aa88b525.
- 登录
redis容器,检查对应的KEY是否值为5
docker exec -it myredis /bin/bash
root@efde7c8668c7:/data# redis-cli
127.0.0.1:6379> keys *
1) "hits"
127.0.0.1:6379> get hits
"3"
docker网络概述
docker网络功能
docker网络功能使容器之间以及容器与外部环境之间的通信变得容易。
它提供了一个抽象层,帮助我们去组织和管理容器间的网络连接。
以下是一些具体的用例:
- 1.容器与容器之间的通信(
container --> container) - 2.容器访问非
docker环境中的服务(container --> remote process) - 3.非
docker环境中的服务访问容器应用(process --> hostos --> containeer) - 4.跨主机间通信(
docker --> hostos1 --> hostos2 --> container)
- 容器与容器之间的通信(
container --> container)
当多个容器运行在同一台主机上并连接到同一个网络时,docker使用虚拟网桥(也称为虚拟交换机)来管理容器之间的通信。
虚拟网桥是一种二层设备,它根据mac地址进行转发,从而实现容器之间的通信。
在这种情况下,虚拟网桥充当了一个局域网内的交换机,将主机上的容器连接在一起。
容器之间的通信就像它们运行在同一个局域网中一样。
例如,在同一个网络中部署一个数据库容器和一个web容器,web容器可以通过ip地址直接访问数据库容器。
当容器需要与其他容器通信时,它会将数据包发送到虚拟网桥。
虚拟网桥根据目标容器的mac地址查找其所连接的端口,并将数据包转发到目标容器。
这个过程与物理交换机的工作方式类似,只不过这里所有的设备和连接都是虚拟的。

- 容器访问非
docker环境中的服务(container --> remote process)
当docker容器需要访问非docker环境中的服务(例如,远程服务器上的服务)时,docker会使用网络地址转换(SNAT)规则来实现通信。
具体来说,docker会将请求的源地址(容器的内部ip地址)替换为宿主机的ip地址。
这样一来,外部服务将会认为请求来自宿主机而非docker容器。
当外部服务对请求进行响应时,它会将响应发送会宿主机,宿主机送回给对应的容器。

iptables -t nat -S POSTROUTING
-P POSTROUTING ACCEPT
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
- 非
docker环境中的服务访问容器应用(process --> hostos --> containeer)
有时候,我们需要让非docker环境中的应用(例如,主机系统上的应用)访问docker容器内的服务。
例如,一个web应用可能运行在主机系统上,而数据库服务运行在docker容器中。
通过配置docker网络和端口映射,我们可以实现主机系统上的web应用与容器内数据库服务的通信。

iptables -t nat -S PREROUTING
-P PREROUTING ACCEPT
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
[root@docker-node1 ~]# iptables -t nat -S DOCKER
-N DOCKER
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 50000 -j DNAT --to-destination 172.17.0.2:50000
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:8080
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8899 -j DNAT --to-destination 172.17.0.5:80
- 跨主机间通信(
docker --> hostos1 --> hostos2 --> container)
假设有两台主机,host1和host2,它们都运行着docker容器。希望host2上的容器c1能够直接访问host2上的容器c3。

传统方法(端口映射):
- 在
host2上为容器c3配置端口映射,将容器内部的端口映射到hostb的一个端口。 - 容器
c1访问host2的ip地址及端口,数据包经过host1进行snat,再发送到host2,然后由host2进行dnat,送到容器c3。 - 容器
c3处理请求并返回响应;响应后会将源地址修改回host2的ip,然后发送给host1,由host1发送回到容器c1
使用overlay叠加网络:
- 在
host1(10.0.0.1)和host2(10.0.0.2)上创建一个overlay网络。 - 将
host1上的容器c1(192.168.1.1)和host2上的容器c3(192.168.2.1)连接到这个overlay网络 - 容器
c1和容器c3可以直接使用它们各自的ip地址进行通信,就像它们运行在同一个主机上一样。
容器c1与容器c3通信流程:
- 容器
c1向容器c3发送请求(192.168.1.1-->192.168.2.1) - 数据报文先发送给
host1的vxlan隧道端点(vtep),然后host1将数据包封装成一个vxlan报文,包含容器c1和容器c3的虚拟ip地址,并增加一个外部ip首部(源地址为10.0.0.1,目标地址为10.0.0.2) - 封装后的
vxlan报文通过host1的物理网络发送到host2 host2收到vxlan报文后,它的vtep解封装数据包,发现源地址是192.168.1.1,目标是192.168.2.1,然后通过虚拟交换机送给容器c3- 容器
c3返回数据报文时,同样按照以上流程执行。
docker网络模式
docker网络模式是docker容器之间、以及容器和宿主机之间进行通信的方式。
docker提供了以下四种网络模式:
bridge:这是docker的默认网络模式。每个容器都会连接到一个虚拟网络桥,从而可以与其他容器和宿主机进行通信。host:在这种模式下,容器将直接使用宿主机的网络,而不是创建独立的net网络名称container:这种模式允许一个容器共享另一个容器的网络空间。这样,两个容器就像是在同一个网络命名空间中一样。none:在这种模式下,容器不会分配任何网络,也就是说,容器会处于一个独立的网络命名空间,无法与其他容器或宿主机通信。
bridge模式
bridge介绍
bridge模式是docker的默认网络模式。当启动一个容器时,会历经如下几个步骤:
- 1.在主机上创建一个虚拟网桥,默认名为
docker0.这个网桥作用就像一个网络交换机,连接所有容器。 - 2.为容器分配一个虚拟网卡,并将其连接到
docker0网桥上。这就像是在交换机上插上一根网线,将容器接入网络 - 3.为容器的虚拟网卡分配一个私有
ip地址,并设置网关为docker0接口ip。此时,容器已经连接到了docker0,并有了ip地址。
容器和docker0网桥之间通过虚拟网型veth pair进行连接,veth相当于一根虚拟网型,一端插到容器网卡上,一端插到docker0上,实现二者的连接。
因此所有连接在docker0上的容器就可以使用彼此的ip进行直接通信。
所以,bridge网络实际上在主机上创建了一个二层网络,并将所有容器接入到这个网络。
通过veth pair实现了容器网卡与docker0之间的连通,容器就可以在这个虚拟网络中互相发现和通信。

bridge实践
- 两个容器互
ping。启动两个容器,分别在容器内执行ping对方容器的ip,来验证bridge网络的连通性。
# 启动容器1
docker run -it --rm --name container1 centos:7 /bin/bash
# 启动容器2
docker run -it --rm --name container2 centos:7 /bin/bash
# 在容器1中ping容器2的IP
ping 172.17.0.4
# 在容器2中ping容器1的IP
ping 172.17.0.3
- 容器访问宿主机
- 容器 -->
docker0网关 -->eth0网卡 --> 宿主机 - 宿主机 -->
eth0网卡 -->docker0网关 --> 容器
- 容器 -->
# 在容器中curl访问服务
[root@e0307c707f5c /]# curl 192.168.99.31:32768
<!DOCTYPE html>
<html lang="zh-CN">
<head>
- 自定义
docker网络
# 创建⽹络并指定⽹段
[root@docker-node1 ~]# docker network create my-net --subnet=172.25.0.0/16 --gateway=172.25.0.1
[root@docker-node1 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
007e7d737183 my-net bridge local
# 启动容器1连接到my-net
[root@docker-node1 ~]# docker run -it --rm --name container1 --network my-net --ip=172.25.0.5 centos:7 /bin/bash
# 启动容器2连接到my-net
[root@docker-node1 ~]# docker run -it --rm --name container2 --network my-net --ip=172.25.0.6 centos:7 /bin/bash
# 在容器中通过IP进⾏通信,除此外还可以通过名称进⾏通信,docker从1.10开始内嵌了DNS服务,但dns服务必须在⾃定义⽹络中运⾏
# ping 172.25.0.5
# ping 172.25.0.6
# ping container1
# ping container2
host模式
host介绍
如果在启动容器时指定网络模式为host模式,容器将不会获得独立的网络namespace,而是和宿主机共用一个网络namespace。因此,容器也不会有自己的网卡、ip等网络资源。
host模式能提高容器的网络性能,但也会造成容器和宿主机端口冲突等问题,其次网络隔离性能较差。当然除了网络以外,容器的其他方面(如文件系统mount、进程pid、用户user、主机名称uts)依然和宿主机隔离。只是网络方面和宿主机共享。

host网络模式在kubernetes中有被大量使用到,主要应用场景:
- 1.
K8S的集群级服务(如:apiserver、controllermanager)需要较高的网络性能。host网络可以减少网络栈的开销,提高性能 - 2.
ingress容器需要监听宿主机端口,并根据用户请求的域名/路径进行请求转发,所有该pod需要运行在host网络模式下。 - 3.
kube-proxy容器负责为service组件提供负责均衡与pod映射。它需要监听宿主机端口和iptables规则,所以会使用到host网络
host实践
- 在主机上使用
host网络模式运行nginx、redis容器,会使这些容器表现得就像在主机上直接运行对应的进程一样。
docker run -itd --net=host --name nginx nginx
docker run -itd --net=host --name redis redis
-
检查本机进程,这两个进程就好像是在本机运行的进程一样。
netstat -lntp | egrep "80|6379" -
通过宿主机
ip+对应端口可以直接访问到对应的进程。
curl -I 192.168.99.31
redis-cli -h 192.168.99.31
container模式
container介绍
container网络模式是指新创建的容器,与一个已存在的容器共享同一个network namespace,而不是与宿主机共享。
这表示,新创建的容器不会创建自己的网络设备或配置自己的ip地址。
而是和另一个指定的容器共享ip、端口等。
除网络以外,两个容器在其他方面(如文件系统、进程)仍然保持隔离。
它们之间的进程可以通过环回网络接口(lo)相互通信。
简而言之,在网络方面,这两个容器表现为一个整体,但在其他方面有时相互隔离的。

在kubernetes中,每个pod都运行一个基础容器(如pause),其他应用容器共享这个基础容器的network namespace。
这是kubernetes网络模型的一大特色。
以一个nginx+php-fpm的pod为例,它包含3个容器:
- 1.
pause容器:kubernetes基础容器,提供共享network namespace - 2.
nginx容器:web服务对外入口,请求通过localhost地址代理给php-fpm - 3.
php-fpm容器:处理php请求
nginx和php-fpm容器共享pause容器提供的network namespace。
- 1.它们三个容器共用一个
ip地址,可以通过localhost互相直接访问。简化了网络配置。 - 2.
nginx容器可以轻松地代理请求给php-fpm容器。这实现了服务发现。 - 3.
nginx与php应用无需对外暴露端口,外部用户通过pause容器暴露的对应端口进行访问。这提高了资源利用率。
如果没有pause这样的基础容器:
- 1.
pod内各容器需要分配独立的ip地址和端口,网络配置会更加复杂 - 2.容器间无法通过
localhost直接通信,无法实现简单的服务发现。 - 3.外部访问也需要指定各容器的端口,资源利用率降低。
container实践
为了让mysql和adminer管理工具一起使用,我们要让两个容器部署在同一个网络上,这样它们就可以通过localhost进行通信。下面是如何实现的步骤:
-
运行一个
mysql容器,作为基础容器。需要设置root密码,并暴露8080端口,以便后续adminer使用。
docker run -itd --name mysql -e MYSQL_ROOT_PASSWORD=123456 -p 8080:8080 mysql -
然后运行一个
adminer容器,让它和mysql容器用同一个网络空间。
docker run -itd --name adminer --network=container:mysql adminer -
访问
adminier,然后填写数据库地址(127.0.0.1)、用户名(root)、密码(123456),然后登录

- 当
adminer能用本地地址127.0.0.1顺利访问数据库容器时,就说明adminier和mysql已经在同一个网络空间了。

留言