惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
A
About on SuperTechFans
Apple Machine Learning Research
Apple Machine Learning Research
The Register - Security
The Register - Security
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理
MyScale Blog
MyScale Blog
云风的 BLOG
云风的 BLOG
博客园_首页
U
Unit 42
T
Tailwind CSS Blog
G
GRAHAM CLULEY
F
Full Disclosure
V
Vulnerabilities – Threatpost
T
Tenable Blog
月光博客
月光博客
P
Privacy & Cybersecurity Law Blog
P
Privacy International News Feed
K
Kaspersky official blog
Scott Helme
Scott Helme
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
News and Events Feed by Topic
T
The Exploit Database - CXSecurity.com
N
News and Events Feed by Topic
有赞技术团队
有赞技术团队
Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
LINUX DO - 最新话题
Recorded Future
Recorded Future
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Help Net Security
Help Net Security
The GitHub Blog
The GitHub Blog
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
P
Proofpoint News Feed
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
罗磊的独立博客
S
Security Affairs
M
MIT News - Artificial intelligence
L
LINUX DO - 热门话题
美团技术团队
Simon Willison's Weblog
Simon Willison's Weblog
T
Threat Research - Cisco Blogs
Stack Overflow Blog
Stack Overflow Blog
Forbes - Security
Forbes - Security
Hugging Face - Blog
Hugging Face - Blog
博客园 - Franky
V
Visual Studio Blog

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式 Go 内存模型 Kubernetes 核心概念 软件版本语义化
Docker 网络原理概览
2023-04-03 · via 蛮荆

2023-04-03 计算机网络 Docker Cloud Native

概述

Docker 原生网络是基于 Linux 的 网络命名空间(net namespace) 和 虚拟网络设备(veth pair)实现的。 当 Docker 进程启动时,会在宿主机上创建一个名称为 docker0虚拟网桥,在该宿主机上启动的 Docker 容器会连接到这个虚拟网桥上。

$ ifconfig

# 输出如下

docker0: ... mtu 1500
inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
...

虚拟网桥的工作方式和物理交换机类似,宿主机上所有的容器通过虚拟网桥连接在一个二层网络中

docker0 子网中分配一个 IP 给容器使用,并设置 docker0 的 IP 地址为容器的默认网关。在宿主机上创建一对虚拟网卡 veth pair 设备, Dockerveth pair 设备的一端放在新创建的容器中,并命名为 eth0(容器的网卡), 另一端放在宿主机中,以 vethxxx 类似的名字命名, 并将这个网络设备连接到 docker0 网桥中。

Docker 会自动配置 iptables 规则和配置 NAT,便于连通宿主机上的 docker0 网桥,完成这些操作之后,容器就可以使用它的 eth0 虚拟网卡,来连接其他容器和访问外部网络。

Docker 中的网络接口默认都是虚拟的接口,Linux 在内核中通过 数据复制 实现接口之间的数据传输,可以充分发挥数据在不同 Docker 容器或容器与宿主机之间的转发效率, 发送接口发送缓存中的数据包,将直接复制到接收接口的缓存中,无需通过物理网络设备进行交换

# 查询主机上 veth 设备
$ ifconfig | grep veth*

veth06f40aa: 
...
vethfdfd27a:

图片来源: https://www.suse.com/c/rancher_blog/introduction-to-container-networking/

虚拟网桥 docker0 通过 iptables 配置与宿主机器上的网卡相连,符合条件的请求都会通过 iptables 转发到 docker0, 然后分发给对应的容器

# 查看 docker 的 iptables 配置
$ iptables -t nat -L

# 输出如下

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere

网络驱动

Docker 的网络子系统支持插拔式的驱动程序,默认存在多个驱动程序,并提供核心网络功能。

名称 描述
bridge 默认的网络设备,当应用程序所在的容器需要通信时使用
host 移除容器与宿主机之间的网络隔离,直接使用宿主机的网络
overlay 将多个容器连接,并使集群服务能够相互通信
ipvlan 使用户可以完全控制 IPv4 和 IPv6 寻址
macvlan 可以为容器分配 MAC 地址
none 禁用所有网络
Network plugins 通过 Docker 安装和使用第三方网络插件

图片来源: Docker——容器与容器云

Docker daemon 通过调用 libnetwork 提供的 API 完成网络的创建和管理等功能。libnetwork 中使用了 CNM 来完成网络功能, CNM 中主要有沙盒(sandbox)、端点(endpoint)和网络(network)3 种组件。

  • 沙盒:一个沙盒包含了一个容器网络栈的信息。一个沙盒可以有多个端点和多个网络,沙盒可以对容器的接口、路由和 DNS 设置等进行管理,沙盒的实现可以是 Linux network namespace、FreeBSD Jail或者类似的机制
  • 端点:一个端点可以加入一个沙盒和一个网络。一个端点只属于一个网络和一个沙盒,端点的实现可以是 veth pair、Open vSwitch 内部端口或者相似的设备
  • 网络:一个网络是一组可以直接互相联通的端点。一个网络可以包含多个端点,网络的实现可以是Linux bridge、VLAN 等

bridge 模式

bridge 是默认的网络模式,为容器创建独立的网络命名空间,容器具有独立的网卡等所有的网络栈。使用该模式的所有容器都是连接到 docker0 这个网桥, 作为 虚拟交换机 使容器可以相互通信,但是由于宿主机的 IP 地址与容器 veth pair 的 IP 地址不在同一个网段,所以为了和宿主机以外的网络通信, Docker 采用了端口绑定的方式,也就是通过 iptables 的 NAT,将宿主机上的端口流量转发到容器。

bridge 模式已经可以满足 Docker 容器最基本的使用需求了,但是其与外界通信时使用 NAT,增加了通信的复杂性,在复杂场景下使用会有限制

$ docker network inspect bridge

# 输出如下 (节选部分信息)
[
    {
        "Name": "bridge",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Containers": {
            # 使用 bridge 网络的容器列表
        },
    }
]

通过上面的输出可以看到,虚拟网桥 的 IP 地址就是 bridge 网络类型的网关地址

我们可以从输出的 Containers 容器列表中找一个容器,查看其网络类型和配置:

$ docker inspect 容器ID

# 输出如下 (节选部分信息)
[
  ...
  
  "NetworkSettings": {
    "Bridge": "",
    "Gateway": "172.17.0.1",
    "IPAddress": "172.17.0.4",
    "Networks": {
        "bridge": {
            "Gateway": "172.17.0.1",
            "IPAddress": "172.17.0.4",
        }
    }

  ...
]

通过上面的输出可以看到,虚拟网桥 的 IP 地址就是 bridge 网络类型的容器的网关地址

实现机制

在 iptables 做了 DNAT 规则,实现端口转发功能:

# iptables 配置查看
$ iptables -t nat -vnL

# 输出如下
Chain PREROUTING (policy ACCEPT 37M packets, 2210M bytes)

...

0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.4:80

当容器需要将端口映射到宿主机时,Docker 会自动为该容器分配一个 IP 地址,同时新增一个 iptables 规则

host 模式

容器不会获得一个独立的网络命名空间,而是和宿主机共用一个。 容器不会虚拟出自己的网卡,配置自己的IP等,而是直接使用宿宿主机的。 但是容器的其他方面,如文件系统、进程列表等还是和宿宿主机隔离的,容器对外界是完全开放的,能够访问到宿主机,就能访问到容器。

host 模式降低了容器与容器之间、容器与宿主机之间网络层面的隔离性,虽然有性能上的优势,但是引发了网络资源的竞争与冲突,因此适用于容器集群规模较小的场景

启动一个网络类型为 hostNginx 容器:

$ docker run -d --net host nginx

Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
...
f202870092fc40bc08a607dddbb2770df9bb4534475b066f45ea35252d6e76e2

查看网络类型为 host 的容器列表:

$ docker network inspect host

# 输出如下 (节选部分信息)
[
    {
        "Name": "host",
        "Scope": "local",
        "Driver": "host",
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "Containers": {
           # 使用 host 网络的容器列表  
           "f202870092fc40bc08a607dddbb2770df9bb4534475b066f45ea35252d6e76e2": {
                "Name": "frosty_napier",
                "EndpointID": "7306a8e4103faf4edd081182f015fa9aa985baf3560f4a49b9045c00dc603190",
                "MacAddress": "",
                "IPv4Address": "",
                "IPv6Address": ""
            }        
        },
    }
]

查看 Nginx 容器网络类型和配置:

$ docker inspect f202870092fc4

# 输出如下 (节选部分信息)
[
  ...
  
  "NetworkSettings": {
    "Bridge": "",
    "Gateway": "",
    "IPAddress": "",
    "Networks": {
        "host": {
            "Gateway": "",
            "IPAddress": "",
        }
    }
  ...
]

通过上面的输出可以看到,Nginx 容器使用的网络类型是 host,没有独立的 IP。

查看 Nginx 容器 IP 地址:

# 进入容器内部 shell

$ docker exec -it f202870092fc4 /bin/bash

# 安装 ip 命令

$ apt update && apt install -y iproute2

# 查看 IP 地址
$ ip a

# 输出如下
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    ...
2: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    ...
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    ...
    
# 退出容器,查看宿主机 IP 地址
$ exit
$ ip a

# 输出如下
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    ...
2: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    ...
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    ...

通过上面的输出可以看到,Nginx 容器内部并没有独立的 IP,而是使用了宿主机的 IP。

查看宿主机的端口监听状态:

$ sudo netstat -ntpl

# 输出如下
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1378/nginx: master
tcp6       0      0 :::80                   :::*                    LISTEN      1378/nginx: master

通过上面的输出可以看到,监听 80 端口的进程为 nginx, 而非 docker-proxy

none 模式

容器拥有自己的 Network Namespace,但是并不进行任何网络配置。也就意味着该容器没有网卡、IP、路由等信息,需要手动为容器添加网卡、配置 IP 等, none 模式下的容器会完全隔离,容器中只有 lo 这个 loopback(回环网络)网卡用于进程通信。

none 模式为容器做了最少的网络设置,在没有网络配置的情况下,通过自定义配置容器的网络,提供了最高的灵活性。

启动一个网络类型为 hostNginx 容器:

$ docker run -d --net none nginx

Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
...
d2d0606b7d2429c224e61e06c348019b74cd47f0b8c85347a7cdb8f1e30dcf86

查看网络类型为 none 的容器列表:

$ docker network inspect none

# 输出如下 (节选部分信息)
[
    {
        "Name": "none",
        "Scope": "local",
        "Driver": "null",
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "Containers": {
           # 使用 none 网络的容器列表  
           "d2d0606b7d2429c224e61e06c348019b74cd47f0b8c85347a7cdb8f1e30dcf86": {
                "Name": "hardcore_chebyshev",
                "EndpointID": "b8ff645671518e608f403818a31b1db34d7fce66af60373346ea3ab673a4c6b2",
                "MacAddress": "",
                "IPv4Address": "",
                "IPv6Address": ""
            }        
        },
    }
]

查看 Nginx 容器网络类型和配置:

$ docker inspect d2d0606b7d242

# 输出如下 (节选部分信息)
[
  ...
  
  "NetworkSettings": {
    "Bridge": "",
    "Gateway": "",
    "IPAddress": "",
    "Networks": {
        "none": {
            "Gateway": "",
            "IPAddress": "",
        }
    }
  ...
]

通过上面的输出可以看到,Nginx 容器使用的网络类型是 none,没有独立的 IP。

查看 Nginx 容器 IP 地址:

# 进入容器内部 shell

$ docker exec -it d2d0606b7d242 /bin/bash

# 访问公网链接

$ curl -I "https://www.docker.com"

curl: (6) Could not resolve host: www.docker.com

# 为什么会报错呢? 这是因为当前容器没有网卡、IP、路由等信息,是完全独立的运行环境,所以没有办法访问公网链接。

# 查看 IP 地址
$ hostname -I

# 没有任何输出,该容器没有 IP 地址

查看宿主机的端口监听状态:

$ docker port d2d0606b7d242

或者

$ sudo netstat -ntpl | grep :80

# 没有任何输出,Nginx 进程运行在容器中,端口没有映射到宿主机

container 模式

host 模式类似,容器与指定的容器共享网络命名空间。这两个容器之间不存在网络隔离,但它们与宿主机以及其他的容器存在网络隔离。 该模式下的容器可以通过 localhost 来访问同一网络命名空间下的其他容器,传输效率较高,且节约了一定的网络资源。在一些特殊的场景中非常有用,例如 k8s 的 Pod。

其他模式

出于篇幅考虑,这里不再赘述其他网络模式,感兴趣的读者可以根据文章末尾的引用连接自行阅读。

网络驱动概述

  • 当需要多个容器在同一台宿主机上进行通信时,使用 bridge
  • 当网络栈不应该与宿主机隔离,但是希望容器的其他方面被隔离时,使用 host
  • 当需要在不同宿主机上运行的容器进行通信时,使用 overlay
  • 当从虚拟机迁移或需要使容器看起来像物理宿主机时,使用 Macvlan, 每个容器都有一个唯一的 MAC 地址
  • 当需要将 Docker 与专门的网络栈集成,使用 Third-party

Docker 和 iptables

如果在公网可以访问的服务器运行 Docker,需要对应的 iptables 规则来限制访问主机上的容器或其他服务。

在 Docker 规则之前添加 iptables 规则

Docker 安装了两个名为 DOCKER-USERDOCKER 的自定义 iptables 链,确保传入的数据包始终先由这两个链进行检查。

# 可以通过该命令查看

$ iptables -L -n -v | grep -i docker

Docker 的所有 iptables 规则都被添加到 Docker 链中,不要手动修改此链 (可能会引发问题)。 如果需要添加在一些在 Docker 之前加载的规则,将它们添加到 DOCKER-USER 链中,这些规则应用于 Docker 自动创建的所有规则之前。

添加到 FORWARD 链中的规则在这些链之后进行检测,这意味着如果通过 Docker 公开一个端口,那么无论防火墙配置了什么规则,该端口都会被公开。 如果想让这些规则在通过 Docker 暴露端口时仍然适用,必须将这些规则添加到 DOCKER-USER 链中。

限制到 Docker 主机的连接

默认情况下,允许所有 外部 IP 连接 Docker 主机,为了只允许特定的 IP 或网络访问容器,在 DOCKER-USER 过滤器链的顶部插入一个规则。

例如,只允许 192.168.1.1 访问:

# 假设输入接口为 eth0
$ iptables -I DOCKER-USER -i eth0 ! -s 192.168.1.1 -j DROP

也可以允许来自源子网的连接,例如,允许 192.168.1.0/24 子网的用户访问:

# 假设输入接口为 eth0
$ iptables -I DOCKER-USER -i eth0 ! -s 192.168.1.0/24 -j DROP

阻止 Docker 操作 iptables

Docker 引擎的配置文件 /etc/docker/daemon.json 设置 iptables 的值为 false,但是最好不要修改,因为这很可能破坏 Docker 引擎的容器网络。

为容器设置默认绑定地址

默认情况下,Docker 守护进程将公开 0.0.0.0 地址上的端口,即主机上的任何地址。如果希望将该行为更改为仅公开内部 IP 地址上的端口,则可以使用 --ip 选项指定不同的IP地址。

集成到防火墙

如果运行的是 Docker 20.10.0 或更高版本,在系统上启用了 iptables, Docker 会自动创建一个名为 docker 的防火墙区域, 并将它创建的所有网络接口 (例如 docker0 ) 加入到 docker 区域,以允许无缝组网。

运行命令将 docker 接口从防火墙区域中移除:

firewall-cmd --zone=trusted --remove-interface=docker0 --permanent
firewall-cmd --reload

Reference

扩展阅读