容器互联介绍
什么是容器互联
容器互联就是在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
已经在同一个网络空间了。
留言