关闭nginx容器之后,再次启动,原来宿主机映射的端口失效的问题解决

最近用containerd在部署nginx的时候,发生了一个比较诡异的问题,当笔者通过nerdctl stop把原来的nginx容器关闭,然后再通过nerdctl run启动一个新的nginx容器的时候,把原来的宿主机端口映射到这个新容器上,但新启动的容器却无法通过映射的端口收到任何请求,而且新容器启动顺利,没有任何报错。这个问题的原因十分隐蔽,而且一开始让人无从下手。本文介绍了这个问题的解决办法,为被该问题困扰的同学提供一个脱困的思路。

1. 环境

出现问题的服务器环境长这样:

$ sudo uname -a
Linux ubuntu-1 5.4.0-121-generic #137-Ubuntu SMP Wed Jun 15 13:33:07 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
$ sudo nerdctl version
Client:
 Version:       v0.20.0
 OS/Arch:       linux/amd64
 Git commit:    e77e05b5fd252274e3727e0439e9a2d45622ccb9

Server:
 containerd:
  Version:      1.6.6
  GitCommit:    10c12954828e7c7c9b6e0ea9b0c02b01407d3ae1

同时还更新了cni网络插件到v1.1.1版本。
使用最新的nginx镜像:

$ sudo nerdctl exec nginx-4087e nginx -v
nginx version: nginx/1.25.0

2. 问题重现

首先启动一个nginx容器:

$ sudo nerdctl run -d -v /home/linmao/nginx/nginx.conf:/etc/nginx/nginx.conf -v /home/linmao/nginx/cert:/cert/ -p 8000:8000 -p 4443:4443 nginx
15b4bc769d04e6c28ac895c0403085656e866a35f72157edd6b81aec5464cbe6

其中映射了2个端口,8000端口用来提供http端点,4443端口用来提供https端点。
nginx.conf长这样:

events {
    worker_connections 64;
}

http {
    resolver 8.8.8.8;

    server {
        listen 8000;
        # 这是部署在nginx服务器上层的网络入口防火墙的ip地址,4443端口映射到本机的4443端口。
        rewrite ^(.*) https://xxx.xxx.xxx.xxx:4443$1 permanent;
    }

    server {
        listen 4443 ssl;
        # 这是笔者自签的证书
        ssl_certificate /cert/self_cert.pem;
        ssl_certificate_key /cert/self_key.pem;

        location ~ /(.*)$ {
        	# 这是代理的后端服务的域名
            proxy_pass https://www.xxxx.com/$1;
            proxy_redirect off;
            proxy_set_header Host $proxy_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

此时8000端口和4443端口是正常的。

$ curl localhost:8000
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.25.0</center>
</body>
</html>

$ curl localhost:4443
<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx/1.25.0</center>
</body>
</html>

关闭这个容器之后再用同样的命令run一个新的nginx,新的nginx启动日志是正常的,但是该新nginx无法收到任何网络流量。映射出来的端口并没有被监听。

$ sudo nerdctl stop 15b4bc769d04
15b4bc769d04

$ sudo nerdctl run -d -v /home/linmao/nginx/nginx.conf:/etc/nginx/nginx.conf -v /home/linmao/nginx/cert:/cert/ -p 8000:8000 -p 4443:4443 nginx
b8bf8e71ce1578d2b1f79c5d66cb75d2287a0f0d942d978761124f2dc003b3b8

$ sudo nerdctl logs -f b8bf8e71ce1578d2b1f79c5d66cb75d2287a0f0d942d978761124f2dc003b3b8
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up

$ curl localhost:8000
curl: (7) Failed to connect to localhost port 8000: No route to host
$ curl localhost:4443
curl: (7) Failed to connect to localhost port 4443: No route to host

$ netstat -ano|grep :8000
$
$ netstat -ano|grep :4443
$

3. 问题的原因

遇到这样的问题笔者是一点不慌(才怪),马上打开iptables看一下nat表,一下发现了一些不正常的地方(列表较长,不相关的规则被笔者去掉了,留下的都是相关的规则)。nat表的POSTROUTING链多了2个名为CNI-xxxxxx的规则,并且增加了2个名称对应为CNI-xxxxx的链。另外CNI-HOSTPORT-DNAT的链增加了2个名为CNI-DN-xxx的规则,同时增加了2个对应名称为CNI-DN-xxxxx的规则链。

$ sudo iptables -nL -t nat --line-numbers
...
Chain POSTROUTING (policy ACCEPT)
num  target     prot opt source               destination
...
9    CNI-d7ad1e347f6ac6bffdae5988  all  --  10.4.0.54            0.0.0.0/0            /* name: "bridge" id: "default-15b4bc769d04e6c28ac895c0403085656e866a35f72157edd6b81aec5464cbe6" */
10   CNI-a294464dbb367262e6fa35c6  all  --  10.4.0.55            0.0.0.0/0            /* name: "bridge" id: "default-b8bf8e71ce1578d2b1f79c5d66cb75d2287a0f0d942d978761124f2dc003b3b8" */
...
Chain CNI-HOSTPORT-DNAT (2 references)
num  target     prot opt source               destination
1    CNI-DN-d7ad1e347f6ac6bffdae5  tcp  --  0.0.0.0/0            0.0.0.0/0            /* dnat name: "bridge" id: "default-15b4bc769d04e6c28ac895c0403085656e866a35f72157edd6b81aec5464cbe6" */ multiport dports 8000,4443
2    CNI-DN-a294464dbb367262e6fa3  tcp  --  0.0.0.0/0            0.0.0.0/0            /* dnat name: "bridge" id: "default-b8bf8e71ce1578d2b1f79c5d66cb75d2287a0f0d942d978761124f2dc003b3b8" */ multiport dports 8000,4443
...
Chain CNI-DN-a294464dbb367262e6fa3 (1 references)
num  target     prot opt source               destination
1    CNI-HOSTPORT-SETMARK  tcp  --  10.4.0.0/24          0.0.0.0/0            tcp dpt:8000
2    CNI-HOSTPORT-SETMARK  tcp  --  127.0.0.1            0.0.0.0/0            tcp dpt:8000
3    DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8000 to:10.4.0.55:8000
4    CNI-HOSTPORT-SETMARK  tcp  --  10.4.0.0/24          0.0.0.0/0            tcp dpt:4443
5    CNI-HOSTPORT-SETMARK  tcp  --  127.0.0.1            0.0.0.0/0            tcp dpt:4443
6    DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:4443 to:10.4.0.55:4443
...
Chain CNI-DN-d7ad1e347f6ac6bffdae5 (1 references)
num  target     prot opt source               destination
1    CNI-HOSTPORT-SETMARK  tcp  --  10.4.0.0/24          0.0.0.0/0            tcp dpt:8000
2    CNI-HOSTPORT-SETMARK  tcp  --  127.0.0.1            0.0.0.0/0            tcp dpt:8000
3    DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8000 to:10.4.0.54:8000
4    CNI-HOSTPORT-SETMARK  tcp  --  10.4.0.0/24          0.0.0.0/0            tcp dpt:4443
5    CNI-HOSTPORT-SETMARK  tcp  --  127.0.0.1            0.0.0.0/0            tcp dpt:4443
6    DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:4443 to:10.4.0.54:4443
...
Chain CNI-d7ad1e347f6ac6bffdae5988 (1 references)
num  target     prot opt source               destination
1    ACCEPT     all  --  0.0.0.0/0            10.4.0.0/24          /* name: "bridge" id: "default-15b4bc769d04e6c28ac895c0403085656e866a35f72157edd6b81aec5464cbe6" */
2    MASQUERADE  all  --  0.0.0.0/0           !224.0.0.0/4          /* name: "bridge" id: "default-15b4bc769d04e6c28ac895c0403085656e866a35f72157edd6b81aec5464cbe6" */
...
Chain CNI-a294464dbb367262e6fa35c6 (1 references)
num  target     prot opt source               destination
1    ACCEPT     all  --  0.0.0.0/0            10.4.0.0/24          /* name: "bridge" id: "default-b8bf8e71ce1578d2b1f79c5d66cb75d2287a0f0d942d978761124f2dc003b3b8" */
2    MASQUERADE  all  --  0.0.0.0/0           !224.0.0.0/4          /* name: "bridge" id: "default-b8bf8e71ce1578d2b1f79c5d66cb75d2287a0f0d942d978761124f2dc003b3b8" */
...

再看一下mangle表,mangle表正常(空的):

$ sudo iptables -nL -t mangle --line-numbers
Chain PREROUTING (policy ACCEPT)
num  target     prot opt source               destination

Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
num  target     prot opt source               destination

Chain KUBE-IPTABLES-HINT (0 references)
num  target     prot opt source               destination

Chain KUBE-KUBELET-CANARY (0 references)
num  target     prot opt source               destination

再看一下filter表也正常,不过在笔者的另一台机器里发现了fliter表也被增加了几个规则。不过在笔者尝试重现问题的时候并未发现filter表有异常。建议同学们也查一下这个表。

$ sudo iptables -nL -t filter --line-numbers

这些新增的规则和规则链不会因为容器的停止而删除。后来笔者发现,这些规则的新增,是因为nginx.conf配置文件中,server块的rewrite命令后边写了ip地址。

    server {
        listen 8000;
        # 就是这一句写了ip地址,导致出现了上边那些规则,如果把ip地址改成localhost就不会出现上边的问题。
        rewrite ^(.*) https://xxx.xxx.xxx.xxx:4443$1 permanent;
    }

4. 问题的解决

知道了原因就很好办了,直接使用iptables -F 或者 iptables -D删掉多出来的规则就行了。有同学知道为什么nginx.conf把rewrite写成一个ip地址,就会在iptables增加一些不会自动删除的规则和规则链的原因的吗?快来评论区告诉笔者吧。

你可能感兴趣的:(nginx,nginx,运维,linux)