内容目录

容器互联介绍

什么是容器互联

容器互联就是在docker中连接两个或多个容器,使这些容器可以相互通信。
例如:有两个容器,一个叫容器A,一个叫容器B。我们可以在启动时,先启动B,然后启动A,实现A能连接B

互联后的效果:容器A可以直接使用容器B的名称来访问容器B,比如直接ping B,不需要知道Bip地址。
因为互联后会在A容器的hosts文件中自动添加一条记录,如:容器Bip 容器B的名称。

容器互联示例

  1. 先启动容器Bmysql容器
docker run --name db -d -e MYSQL_ALLOW_EMPTY_PASSWORD="true" mysql
91eb4736238dc00799ae88c77527418045773f0e582f437b79cccb142f834090
  1. 然后启动容器Aweb容器,并通过--link连接到mysql上;
    docker run -it --name web --link db centos:7 /bin/bash

  2. 测试直接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
  1. 检查容器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

容器互联实践

场景说明

file

  1. 首先,我们启动一个redis容器,命名为myredis。这个redis容器负责提供redis服务
  2. 然后,我们构建一个python应用,使用dockerfile构建成镜像,并运行成一个容器。
  3. 运行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容器

  1. 启动python容器,而后使用--link指向myredis别名的redis容器。

  2. 设置一个REDIS_HOST的环境变量,值为myredis,这个值解析出来的结果就是redis容器的ip地址。
    docker run -d --name python-web --link myredis -e "REDIS_HOST=myredis" -p 8899:80 python-web:v1.0

  3. 访问对应主机的8899端口

[root@docker-node01 flask_redis]# curl localhost:8899
Hello Container World! website access 3 count! and my hostname is 8863aa88b525.
  1. 登录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
  1. 容器与容器之间的通信(container --> container
    当多个容器运行在同一台主机上并连接到同一个网络时,docker使用虚拟网桥(也称为虚拟交换机)来管理容器之间的通信。
    虚拟网桥是一种二层设备,它根据mac地址进行转发,从而实现容器之间的通信。
    在这种情况下,虚拟网桥充当了一个局域网内的交换机,将主机上的容器连接在一起。
    容器之间的通信就像它们运行在同一个局域网中一样。
    例如,在同一个网络中部署一个数据库容器和一个web容器,web容器可以通过ip地址直接访问数据库容器。
    当容器需要与其他容器通信时,它会将数据包发送到虚拟网桥。
    虚拟网桥根据目标容器的mac地址查找其所连接的端口,并将数据包转发到目标容器。
    这个过程与物理交换机的工作方式类似,只不过这里所有的设备和连接都是虚拟的。

file

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

file

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

file

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
  1. 跨主机间通信(docker --> hostos1 --> hostos2 --> container
    假设有两台主机,host1host2,它们都运行着docker容器。希望host2上的容器c1能够直接访问host2上的容器c3

file

传统方法(端口映射):

  1. host2上为容器c3配置端口映射,将容器内部的端口映射到hostb的一个端口。
  2. 容器c1访问host2ip地址及端口,数据包经过host1进行snat,再发送到host2,然后由host2进行dnat,送到容器c3
  3. 容器c3处理请求并返回响应;响应后会将源地址修改回host2ip,然后发送给host1,由host1发送回到容器c1

使用overlay叠加网络:

  1. host1(10.0.0.1)host2(10.0.0.2)上创建一个overlay网络。
  2. host1上的容器c1(192.168.1.1)host2上的容器c3(192.168.2.1)连接到这个overlay网络
  3. 容器c1和容器c3可以直接使用它们各自的ip地址进行通信,就像它们运行在同一个主机上一样。

容器c1与容器c3通信流程:

  1. 容器c1向容器c3发送请求(192.168.1.1-->192.168.2.1)
  2. 数据报文先发送给host1vxlan隧道端点(vtep),然后host1将数据包封装成一个vxlan报文,包含容器c1和容器c3的虚拟ip地址,并增加一个外部ip首部(源地址为10.0.0.1,目标地址为10.0.0.2)
  3. 封装后的vxlan报文通过host1的物理网络发送到host2
  4. host2收到vxlan报文后,它的vtep解封装数据包,发现源地址是192.168.1.1,目标是192.168.2.1,然后通过虚拟交换机送给容器c3
  5. 容器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之间的连通,容器就可以在这个虚拟网络中互相发现和通信。

file

bridge实践

  1. 两个容器互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
  1. 容器访问宿主机
    • 容器 --> docker0网关 --> eth0网卡 --> 宿主机
    • 宿主机 --> eth0网卡 --> docker0网关 --> 容器
# 在容器中curl访问服务
[root@e0307c707f5c /]# curl 192.168.99.31:32768
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  1. 自定义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)依然和宿主机隔离。只是网络方面和宿主机共享。

file

host网络模式在kubernetes中有被大量使用到,主要应用场景:

  • 1.K8S的集群级服务(如:apiserver、controllermanager)需要较高的网络性能。host网络可以减少网络栈的开销,提高性能
  • 2.ingress容器需要监听宿主机端口,并根据用户请求的域名/路径进行请求转发,所有该pod需要运行在host网络模式下。
  • 3.kube-proxy容器负责为service组件提供负责均衡与pod映射。它需要监听宿主机端口和iptables规则,所以会使用到host网络

host实践

  1. 在主机上使用host网络模式运行nginx、redis容器,会使这些容器表现得就像在主机上直接运行对应的进程一样。
docker run -itd --net=host --name nginx nginx
docker run -itd --net=host --name redis redis
  1. 检查本机进程,这两个进程就好像是在本机运行的进程一样。
    netstat -lntp | egrep "80|6379"

  2. 通过宿主机ip+对应端口可以直接访问到对应的进程。

curl -I 192.168.99.31
redis-cli -h 192.168.99.31

container模式

container介绍

container网络模式是指新创建的容器,与一个已存在的容器共享同一个network namespace,而不是与宿主机共享。
这表示,新创建的容器不会创建自己的网络设备或配置自己的ip地址。
而是和另一个指定的容器共享ip、端口等。
除网络以外,两个容器在其他方面(如文件系统、进程)仍然保持隔离。
它们之间的进程可以通过环回网络接口(lo)相互通信。
简而言之,在网络方面,这两个容器表现为一个整体,但在其他方面有时相互隔离的。

file

kubernetes中,每个pod都运行一个基础容器(如pause),其他应用容器共享这个基础容器的network namespace
这是kubernetes网络模型的一大特色。
以一个nginx+php-fpmpod为例,它包含3个容器:

  • 1.pause容器:kubernetes基础容器,提供共享network namespace
  • 2.nginx容器:web服务对外入口,请求通过localhost地址代理给php-fpm
  • 3.php-fpm容器:处理php请求

nginxphp-fpm容器共享pause容器提供的network namespace

  • 1.它们三个容器共用一个ip地址,可以通过localhost互相直接访问。简化了网络配置。
  • 2.nginx容器可以轻松地代理请求给php-fpm容器。这实现了服务发现。
  • 3.nginxphp应用无需对外暴露端口,外部用户通过pause容器暴露的对应端口进行访问。这提高了资源利用率。

如果没有pause这样的基础容器:

  • 1.pod内各容器需要分配独立的ip地址和端口,网络配置会更加复杂
  • 2.容器间无法通过localhost直接通信,无法实现简单的服务发现。
  • 3.外部访问也需要指定各容器的端口,资源利用率降低。

container实践

为了让mysqladminer管理工具一起使用,我们要让两个容器部署在同一个网络上,这样它们就可以通过localhost进行通信。下面是如何实现的步骤:

  1. 运行一个mysql容器,作为基础容器。需要设置root密码,并暴露8080端口,以便后续adminer使用。
    docker run -itd --name mysql -e MYSQL_ROOT_PASSWORD=123456 -p 8080:8080 mysql

  2. 然后运行一个adminer容器,让它和mysql容器用同一个网络空间。
    docker run -itd --name adminer --network=container:mysql adminer

  3. 访问adminier,然后填写数据库地址(127.0.0.1)、用户名(root)、密码(123456),然后登录

file

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

file

最后修改日期: 2024年9月15日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。