1.问题背景
docker安装了mysql服务,服务器为Redhat9,我们希望通过防火墙规则直接限制访问的来源ip,只允许特定ip进行访问,其余ip需要被禁止。
2.排查过程
1.首先尝试了通过firewalld方式添加对应的防火墙规则,
bash
sudo firewall-cmd --add-rich-rule='rule family="ipv4" source address="0.0.0.0/0" port protocol="tcp" port="3306" reject'
firewall-cmd --add-rich-rule='rule family="ipv4" source address="10.190.126.111" port protocol="tcp" port="3306" accept'
sudo firewall-cmd --runtime-to-permanent
sudo firewall-cmd --reload
添加后发现其余IP依然可以访问,firewalld并未起作用。
2.查找问题根源
bash
sudo iptables -L -n | grep 3306

发现docker默认添加了3306端口的放行规则,虽然我在 firewalld 设置了规则,但Docker加的iptables规则优先级更高,所以导致直接放行了。
继续查看DOCKER-USER
bash
sudo iptables -L DOCKER-USER --line-numbers

是 DOCKER-USER 链默认的第一条,意思是:
如果没有其他规则匹配,就RETURN返回,继续走INPUT链的逻辑。
RETURN 相当于 退出当前链,不继续匹配 DOCKER-USER 后续规则了。
➡️所以后续我们添加的自定义规则(比如 DROP、ACCEPT)一定要加在 RETURN 之前!
如果加在 RETURN 之后,就永远到不了你的规则了,因为前面已经 RETURN 了,后面的根本不会执行。
举个例子:
现在 DOCKER-USER链是这样的:
bash
Chain DOCKER-USER (1 references)
num target prot opt source destination
1 RETURN all -- 0.0.0.0/0 0.0.0.0/0
如果我们要加规则,比如:
允许 10.161.238.15 访问3306,再全部拒绝其他IP。
我们需要这么加(保证在RETURN前):
bash
iptables -I DOCKER-USER 1 -s 10.161.238.15 -p tcp --dport 3306 -j ACCEPT
iptables -I DOCKER-USER 2 -p tcp --dport 3306 -j DROP
-I 是 插入,第一个参数是位置,1 表示插在最前面。
插完之后,链的顺序就会变成这样:
bash
Chain DOCKER-USER (1 references)
num target prot opt source destination
1 ACCEPT tcp -- 10.161.238.15 0.0.0.0/0 tcp dpt:3306
2 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306
3 RETURN all -- 0.0.0.0/0 0.0.0.0/0
再次查看具体的iptables策略
bash
sudo iptables -L -n

此时可以看到Chain DOCKER (3 references)和Chain DOCKER-USER (1 references)两部分,关于这两块内容下面是相关解释:
🔥Chain DOCKER
🔥Chain DOCKER-USER
这两个都是 Docker 启动时自己加的链,它们有不同的功能:
链名 | 作用 |
---|---|
DOCKER | Docker自动管理的链:用来处理 Docker 容器的端口映射,比如docker run -p 3306:3306。你不要直接改这里,Docker会自动覆盖。 |
DOCKER-USER | 用户自定义规则链:这是Docker专门留给用户自己加规则的地方,Docker不会动这里。适合你加自己的防火墙规则,比如只让某些IP访问容器。 |
如果你想限制Docker的3306端口访问,应该:
✅ 加在 DOCKER-USER 链里!
🧠 为什么不能加在 DOCKER 链?
DOCKER 链是Docker内部维护的。
每次你启动、停止容器,Docker会重新生成这里的规则。
你手动加进去的,过一会儿就被覆盖掉了!!
所以官方推荐,如果你有自己的访问控制需求,去动 DOCKER-USER。
目的 | 加在哪 |
---|---|
控制容器访问流量(Docker的端口映射) | 加在 DOCKER-USER |
控制本机服务(非Docker容器)访问流量 | 加在 INPUT |
3.关于删除规则
用 iptables -L DOCKER-USER --line-numbers 能看到每条规则的编号,比如:
bash
Chain DOCKER-USER (2 references)
num target prot opt source destination
1 ACCEPT tcp -- 10.161.238.15 anywhere tcp dpt:3306
2 ACCEPT tcp -- 10.161.238.16 anywhere tcp dpt:3306
3 ACCEPT tcp -- 10.161.238.17 anywhere tcp dpt:3306
4 DROP all -- anywhere anywhere
🛠️ 你要删除 1-3号规则,方法如下:
因为每删一条,后面的编号都会往前移动,所以需要倒着删!
✅ 正确操作顺序:
bash
iptables -D DOCKER-USER 3
iptables -D DOCKER-USER 2
iptables -D DOCKER-USER 1
先删3,再删2,再删1。
保证每次删除的都是你想删的那条!
为什么要倒着删?
如果你从1开始删,比如删了1,原来的2号规则就变成了新的1号,这样会乱掉,会误删!
✨如果想一口气删掉(比如脚本里),可以写:
bash
for i in 3 2 1; do
iptables -D DOCKER-USER $i
done
4.关于直接在文件中编辑policy的顺序 **1. 先导出iptables规则到文件**
bash
sudo iptables-save > /tmp/iptables.rules
这样你就得到了一个标准文本文件 /tmp/iptables.rules,可以直接用文本编辑器打开编辑。
2. 打开并编辑
用你喜欢的编辑器,比如 vim 或 nano:
bash
sudo vim /tmp/iptables.rules
文件内容大概长这样:
bash
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -p tcp --dport 22 -j ACCEPT
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 3306 -j DROP
COMMIT
-A INPUT ... 表示添加一条规则到 INPUT 链。
你可以直接剪切、粘贴这些 -A 开头的行,调整它们的上下顺序。
比如想让 3306 的 DROP 规则排到最前面:
修改成:
bash
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -p tcp --dport 3306 -j DROP
-A INPUT -p tcp --dport 22 -j ACCEPT
-A INPUT -p tcp --dport 80 -j ACCEPT
COMMIT
3. 保存退出
4. 清空iptables并重新导入新规则
⚠️ 注意:清空后如果规则不对可能断掉SSH,要小心,可以开一个额外的console测试!
bash
#清空规则表
sudo iptables -F
#先测试是否有语法错误
sudo iptables-restore --test < /tmp/iptables.rules
#正式导入
sudo iptables-restore < /tmp/iptables.rules
5. 关于如何确定mysql的访问ip是多少
1. 通过sql查询最近的访问记录,但这个方式不太全,如果某些库没有在请求那么会被漏掉,所以最好自己整理一下所有库的应用来源IP。
SELECT distinct host FROM information_schema.processlist;
2. 抓包查看具体是哪个IP在访问3306端口
bash
sudo yum install -y tcpdump # centos/redhat
# 或
sudo apt install -y tcpdump # ubuntu/debian
抓3306端口的流量
bash
sudo tcpdump -i any port 3306
6. 关于ip段放行的建议
bash
iptables -I DOCKER-USER 1 -s 10.197.216.x -p tcp --dport 3306 -j ACCEPT && \
iptables -I DOCKER-USER 2 -s 10.197.216.x -p tcp --dport 3306 -j ACCEPT && \
iptables -I DOCKER-USER 3 -s 172.17.0.0/16 -p tcp --dport 3306 -j ACCEPT && \
iptables -I DOCKER-USER 4 -p tcp --dport 3306 -j DROP
后续追加新的policy需要用-I来添加到最前面,否则不会生效
iptables -I DOCKER-USER -s 10.161.238.15 -p tcp --dport 3306 -j ACCEPT
也可以直接指定插入的index,即插入到哪个位置,index=2即插入到目前的第一条和第二条中间
iptables -I DOCKER-USER 2 -s 10.161.238.15 -p tcp --dport 3306 -j ACCEPT
其中上面的第三条是比较特殊的,由于我在抓包时发现部分来源IP为172.17.0.0/16开头,而这个网段是来自于docker
Docker默认bridge网络(默认网络模式)一般是:
bash
172.17.0.0/16
容器起的时候,默认分配的IP就是 172.17.x.x 这种。
所以如果防火墙放行整个 172.17.0.0/16,就能统一解决问题。
有的系统或复杂网络环境,Docker可能用其他网段(比如 172.18.0.0/16),这取决于 Docker 网络配置,但默认就是172.17.0.0/16。
🛠 怎么确认自己机器的Docker网段?
可以在宿主机上执行:
bash
docker network inspect bridge
输出例子:
bash
[
{
"Name": "bridge",
"Id": "...",
"IPAM": {
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
}
}
]
✅ 这里 "Subnet": "172.17.0.0/16" 就是默认的 Docker 网络段。