skip to content
Logo 三七の小站

Docker 流量统计:让你的容器流量无处遁形!

/ 13 min read

嘿,各位小伙伴们!你们有没有遇到过这样的灵魂拷问:

  • “我服务器这个月的流量费怎么又超了?到底是谁干的!” 😱
  • “我跑了好几个 Docker 应用,哪个才是真正的‘流量刺客’?”
  • “我想给客户按容器使用流量计费,该怎么算才准?”

别担心!今天,我将带你踏上一场有趣的侦探之旅,我们将使用 Linux 系统自带的神器 iptables,来精确地揪出每一个 Docker 容器的流量小秘密,让它们在你面前“一丝不挂”!而且,完全免费!

第一站:最简单的场景 (端口号一致) 😊

假设我们有一个 Web 应用,启动命令是这样的:

Terminal window
docker run -d --name my-web-app -p 38022:38022 my-image

这里,宿主机的 38022 端口映射到了容器内部的 38022 端口。这是最简单、最直接的情况。

我们的侦探工具iptables!你可以把它想象成是服务器的网络守门人,所有进出的数据包都得经过它的盘问。

我们的侦查地点:Docker 很贴心地为我们准备了一个绝佳的“蹲点”位置,叫做 DOCKER-USER 链。所有准备进入 Docker 容器的流量,都会经过这里。我们只要在这设下关卡,就能轻松记账!

记账咒语 (执行命令)

我们需要设置两条规则,一条记录“进来”的流量,一条记录“出去”的流量。

Terminal window
# 规则1: 记录所有“进入”容器的流量 (目标端口是 38022)
sudo iptables -I DOCKER-USER -p tcp --dport 38022
# 规则2: 记录所有从容器“出来”的流量 (源端口是 38022)
sudo iptables -I DOCKER-USER -p tcp --sport 38022
  • --dport (Destination Port): 就像是收件地址上的门牌号。所有寄往 38022 端口的“包裹”(数据包),都会被这条规则记下一笔。这就是入站流量 (Inbound)。📬
  • --sport (Source Port): 就像是寄件人地址。所有从 38022 端口寄出的“包裹”,也会被记下一笔。这就是出站流量 (Outbound)。📤

查看账本

随时可以运行下面的命令来查看最新的流量统计:

Terminal window
sudo iptables -vnL DOCKER-USER

你会看到类似这样的输出:

Chain DOCKER-USER (1 references)
pkts bytes target prot opt in out source destination
30 10204 tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:38022
17 5791 tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp spt:38022
  • dpt:38022 那一行的 bytes 10204 就是你的入站总流量。
  • spt:38022 那一行的 bytes 5791 就是你的出站总流量。
  • pkts 记录的是数据包的数量。

看到 bytes 在不断增加,恭喜你,你已经成功了!🎉

第二站:剧情反转 - 端口号不一致 🤔

在真实世界里,我们经常会把容器内部的 80 端口映射到宿主机的一个随机端口,比如 38022

Terminal window
docker run -d --name another-app -p 38022:80 nginx

问题来了:我们应该监听宿主机的 38022 端口,还是容器的 80 端口呢?

揭晓谜底:答案是,永远只关心容器内部的端口! 🎯

为什么呢?这里有个小故事:

  1. 一个外部请求(包裹)寄往你服务器的 38022 端口。
  2. 门口的大爷 Docker(门卫)收到了包裹。他查了一下登记表,发现这个是要给“住在 80 号房间的 Nginx 先生”的。
  3. 于是,门卫大爷把包裹上的地址从 38022 撕掉,贴上了一个新地址 80,然后把包裹放进了内部电梯(也就是 DOCKER-USER 链)。
  4. 我们的 iptables 侦探正是在电梯口检查包裹。当他看到这个包裹时,上面的地址已经是 80

所以,正确的咒语应该是:

Terminal window
# 监听进入容器的流量,它的目标端口已经是 80 了
sudo iptables -I DOCKER-USER -p tcp --dport 80
# 监听从容器出来的流量,它的源端口自然也是 80
sudo iptables -I DOCKER-USER -p tcp --sport 80

记住这个黄金法则:忽略宿主机端口,紧盯容器端口!

第三站:高难度挑战 - 多个容器,相同内网端口 🤯

如果我们有两个应用,它们的容器内部端口都是 80 呢?

  • WebApp 1: docker run --name webapp1 -d -p 8080:80 nginx
  • WebApp 2: docker run --name webapp2 -d -p 8081:80 nginx

如果还只用 --dport 80 来统计,那两个应用的流量就会混在一起,像一碗浆糊。这可不行!

我们的超级武器容器的内部 IP 地址! 🔫

每个容器在 Docker 的世界里都有一个独一无二的身份证号——它的内部 IP。我们只要在记账时,不仅看端口号,还核对一下 IP 地址,就能完美区分它们了!

第一步:获取它们的 IP 地址

Terminal window
# 获取 webapp1 的 IP
docker inspect -f '{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' webapp1
# ==> /webapp1 - 172.17.0.2
# 获取 webapp2 的 IP
docker inspect -f '{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' webapp2
# ==> /webapp2 - 172.17.0.3

(你的 IP 地址可能不同,以命令输出为准)

第二步:念出终极咒语(指定 IP 和端口)

现在我们的咒语升级了,加入了 -d (目标 IP) 和 -s (源 IP) 参数。

为 WebApp 1 (172.17.0.2) 记账:

Terminal window
sudo iptables -I DOCKER-USER -p tcp -d 172.17.0.2 --dport 80 # 入站
sudo iptables -I DOCKER-USER -p tcp -s 172.17.0.2 --sport 80 # 出站

为 WebApp 2 (172.17.0.3) 记账:

Terminal window
sudo iptables -I DOCKER-USER -p tcp -d 172.17.0.3 --dport 80 # 入站
sudo iptables -I DOCKER-USER -p tcp -s 172.17.0.3 --sport 80 # 出站

现在再去看账本 sudo iptables -vnL DOCKER-USER,你就会看到四条独立的、清清楚楚的记录,每一条都对应着特定容器的特定方向的流量。完美!🏆

第四站:生产环境终极秘籍 - 稳定可靠的秘诀 🚀

问题:如果容器因为更新或其他原因被删除重建,它的内部 IP 可能会改变,这会导致我们辛辛苦苦设置的 iptables 规则失效!这在生产环境是绝对不能接受的。

解决方案:使用 Docker Compose自定义网络来为容器分配静态内部 IP 地址。这是在生产环境中管理多容器应用的推荐做法,一劳永逸!

下面是一个 docker-compose.yml 的例子,它就像是容器们的“出生证明”和“户口本”:

version: '3.8'
services:
webapp1:
image: nginx
ports:
- "8080:80"
networks:
my_app_net:
ipv4_address: 172.20.0.10 # 分配一个固定的 IP,就像门牌号
webapp2:
image: nginx
ports:
- "8081:80"
networks:
my_app_net:
ipv4_address: 172.20.0.11 # 分配另一个固定的 IP
networks:
my_app_net:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/24

通过这种方式,webapp1 的 IP 将永远是 172.20.0.10webapp2 的 IP 永远是 172.20.0.11,无论它们被重启多少次。你的 iptables 规则也将因此而变得坚如磐石,稳定可靠!

重要!别忘了保存规则!⚠️

iptables 的规则在服务器重启后会消失。为了让我们辛苦设置的规则能够“开机自启”,需要把它们保存下来。

  1. 安装持久化工具:

    Terminal window
    # Debian/Ubuntu
    sudo apt-get install iptables-persistent
    # CentOS/RHEL
    sudo dnf install iptables-services
  2. 保存规则 (就像游戏存档!):

    Terminal window
    # Debian/Ubuntu
    sudo netfilter-persistent save
    # CentOS/RHEL
    sudo service iptables save

另外,如果你想 清空计数器,重新开始统计,只需运行:

Terminal window
sudo iptables -Z DOCKER-USER

侦探收工:抹掉所有痕迹!🕵️‍♀️💨

好了,我们的监控任务圆满完成了,或者你只是想清理一下不再需要的规则。作为一名专业的侦探,撤离时必须不留下一丝痕迹。删除 iptables 规则就像擦掉黑板上的字一样简单,主要有两种方法:

方法一:精准狙击(按规则内容删除)

如果你还记得当初添加规则时念的“咒语”,最直接的方法就是把插入 (-I) 或追加 (-A) 命令换成删除 (-D) 命令。

比如说,我们之前为 WebApp 1 (172.17.0.2) 添加了这条入站规则:

Terminal window
# 这是添加时的命令
sudo iptables -I DOCKER-USER -p tcp -d 172.17.0.2 --dport 80

要删除它,只需把 -I 换成 -D,其他部分一字不差地再执行一遍:

Terminal window
# 这就是删除它的命令
sudo iptables -D DOCKER-USER -p tcp -d 172.17.0.2 --dport 80

搞定!这条规则就从规则链中消失了。这个方法非常精确,但缺点是你需要准确地知道整条规则的内容。

方法二:按编号抓人(按行号删除)

当你有很多规则,或者记不清具体内容时,这个方法简直是救星。它就像在队伍里按编号揪人出来,又快又准。

第一步:给所有规则编上号,列出清单

运行 list 命令,并加上一个神奇的参数 --line-numbers

Terminal window
sudo iptables -L DOCKER-USER --line-numbers

你会看到一个带有编号的列表:

Chain DOCKER-USER (1 references)
num target prot opt source destination
1 ... tcp -- anywhere 172.17.0.3 tcp dpt:80
2 ... tcp -- 172.17.0.3 anywhere tcp spt:80
3 ... tcp -- anywhere 172.17.0.2 tcp dpt:80
4 ... tcp -- 172.17.0.2 anywhere tcp spt:80

(这里的 num 就是行号)

第二步:按编号直接删除

假设你想删除第 3 条规则(WebApp 1 的入站规则),直接告诉 iptables 它的编号就行:

Terminal window
sudo iptables -D DOCKER-USER 3

砰!第 3 条规则就被删除了。

🚨 高能预警:一个重要的小陷阱!

当你删除一条规则后,下面规则的编号会自动向上移动!比如,你删除了第 3 条,那么原来第 4 条规则现在就变成了新的第 3 条。

所以,如果你想删除多条规则,一个万无一失的技巧是:从编号大的开始,从下往上删! 这样就不会因为编号变化而出错了。

最后,别忘了更新你的存档! 在你增删改查,对规则满意之后,记得再次运行保存命令,把最新的规则集固化下来,以防服务器重启后又回到了旧状态。

Terminal window
# Debian/Ubuntu
sudo netfilter-persistent save
# CentOS/RHEL
sudo service iptables save