Nginx try_files 在删除文件时成功,然后 nginx 使用 open() 报告错误

Nginx try_files 在删除文件时成功,然后 nginx 使用 open() 报告错误

我遇到了一个奇怪的错误,try_files指令成功找到刚刚删除的文件,然后 nginx 继续打开该文件并返回错误。

我使用带有 php-fpm 的标准 PHP 后端设置。为了减少 PHP 工作量,我生成了静态 HTML 文件,然后直接由 nginx 提供。所有内容都打包在 docker 容器中,一个用于 php,一个用于 nginx。

文件相关部分docker-compose

  nginx:
    image: nginx:latest
    ports:
      - "${WEB_PORT:-8100}:80"
    volumes:
      - ./.docker/nginx:/etc/nginx/conf.d
      - ./:/var/www/html
    restart: always

Nginx 配置文件如下所示:

server {
    ... ...
    set $bypass_url "/cache/${request_uri}/_index.html";

    location / {
        open_file_cache off;
        try_files $bypass_url $uri /index.php?$args;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

这个想法是try_files首先检查静态生成的文件是否存在,如果不存在,它应该将所有内容传递给上游 PHP。

当你访问的时候/会生成一个静态缓存文件/cache/_index.html

当您/下次访问时,将提供生成的文件。这按预期工作。问题是当删除缓存文件时缓存会失效。事情变得有点奇怪。

如果缓存至少被使用过一次,然后被删除,然后/在删除缓存后立即被访问,nginx 将返回默认的 404 错误页面。如果/再次被访问,请求将正常传递给上游,这是第一次请求时应该发生的事情。

当返回404时,nginx也会记录错误:

2023/09/28 14:34:32 [error] 22#22: *4 open() "/var/www/html/cache/_index.html" failed (2: No such file or directory), client: 172.19.0.1, server: server.test, request: "GET / HTTP/1.1", host: "localhost:8100"

所以,我的问题是,为什么try_files不处理这个问题?如果我在删除缓存文件后等待一分钟并尝试访问该 URL,请求将被传递到上游 php,并且不会返回 404。这表明存在某种超时,之后一切正常。

nginxopen_cache_file off不缓存打开的文件描述符。这可能是 Docker 以及 docker 处理打开文件的方式的问题吗?我尝试在调试模式下运行 nginx,但没有得到任何有用的信息。只是 try_files 检查了是否存在丢失的文件并且显然成功了?

Nginx 调试日志:

2023/09/28 14:02:17 [debug] 21#21: *11 try files handler
2023/09/28 14:02:17 [debug] 21#21: *11 http script var: "/cache/_index.html"
2023/09/28 14:02:17 [debug] 21#21: *11 trying to use file: "/cache/_index.html" "/var/www/html/cache/_index.html"
2023/09/28 14:02:17 [debug] 21#21: *11 try file uri: "/cache/_index.html"
2023/09/28 14:02:17 [debug] 21#21: *11 generic phase: 13
2023/09/28 14:02:17 [debug] 21#21: *11 content phase: 14
2023/09/28 14:02:17 [debug] 21#21: *11 content phase: 15
2023/09/28 14:02:17 [debug] 21#21: *11 content phase: 16
2023/09/28 14:02:17 [debug] 21#21: *11 content phase: 17
2023/09/28 14:02:17 [debug] 21#21: *11 content phase: 18
2023/09/28 14:02:17 [debug] 21#21: *11 content phase: 19
2023/09/28 14:02:17 [debug] 21#21: *11 http filename: "/var/www/html/cache/_index.html"
2023/09/28 14:02:17 [debug] 21#21: *11 add cleanup: 0000AAAAE8A4B440
2023/09/28 14:02:17 [error] 21#21: *11 open() "/var/www/html/cache/_index.html" failed (2: No such file or directory), client: 172.19.0.1, server: server.test, request: "GET / HTTP/1.1", host: "localhost:8100"
2023/09/28 14:02:17 [debug] 21#21: *11 http finalize request: 404, "/cache/_index.html?" a:1, c:1
2023/09/28 14:02:17 [debug] 21#21: *11 http special response: 404, "/cache/_index.html?"
2023/09/28 14:02:17 [debug] 21#21: *11 http set discard body
2023/09/28 14:02:17 [debug] 21#21: *11 HTTP/1.1 404 Not Found

我打算在 docker 之外尝试这个,但是这是一种奇怪的行为,它看起来像是 try_files 的一个错误,或者我错过了一些 nginx 配置指令,我可以禁用这个看起来像另一个缓存层的东西。

编辑:我做了一些调试,并在容器内运行的 nginx 上运行了 strace。

这是 try_files 查找 _index.html 时出现的跟踪,如果找不到则返回到上游 php。

[{events=EPOLLIN, data={u32=2234669808, u64=281472916413168}}], 512, 65000, NULL, 8) = 1
recvfrom(12, "GET / HTTP/1.1\r\nHost: localhost:"..., 1024, 0, NULL, NULL) = 1024
ioctl(12, FIONREAD, [439])              = 0
mmap(NULL, 28672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff853d8000
recvfrom(12, "|0|1366; _ym_isad=2; _cb=_OpSiCK"..., 7859, 0, NULL, NULL) = 439
newfstatat(AT_FDCWD, "/var/www/html/cache/_index.html", 0xffffe69f7170, 0) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/var/www/html/", {st_mode=S_IFDIR|0755, st_size=1760, ...}, 0) = 0
newfstatat(AT_FDCWD, "/var/www/html/index.php", {st_mode=S_IFREG|0644, st_size=405, ...}, 0) = 0
epoll_ctl(9, EPOLL_CTL_MOD, 12, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=2234669808, u64=281472916413168}}) = 0
getsockname(12, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("172.19.0.5")}, [112 => 16]) = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 13
... ...

它调用newfstatat()get's a no such file 然后继续。现在,这是 nginx 因错误而失败并返回 404 时的 strace:

[{events=EPOLLIN, data={u32=2234669808, u64=281472916413168}}], 512, 65000, NULL, 8) = 1
recvfrom(12, "GET / HTTP/1.1\r\nHost: localhost:"..., 1024, 0, NULL, NULL) = 1024
ioctl(12, FIONREAD, [439])              = 0
mmap(NULL, 28672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff853d8000
recvfrom(12, "|0|1366; _ym_isad=2; _cb=_OpSiCK"..., 7859, 0, NULL, NULL) = 439
newfstatat(AT_FDCWD, "/var/www/html/cache/_index.html", {st_mode=S_IFREG|0644, st_size=6, ...}, 0) = 0
openat(AT_FDCWD, "/var/www/html/cache/_index.html", O_RDONLY|O_NONBLOCK|O_LARGEFILE) = -1 ENOENT (No such file or directory)
gettid()                                = 21
write(6, "2023/09/28 18:03:16 [error] 21#2"..., 236) = 236
... ...

newfstatat()如果文件存在,第二次则返回 0。

那么,nginx 是否可能仍保留文件描述符,或者这是由 Docker 造成的?

最后一条信息:如果我连接到容器docker exec -it sh并手动删除容器内的文件,nginx 将按预期工作。

那么这看起来像是一个 docker/container/hypervisor 问题?

答案1

这只是猜测,但当 Linux 上的文件被删除时,打开的句柄仍然有效。是否有可能 NGINX 进程仍将旧的已删除文件视为有效,但其他进程无法打开它?

尽管我没有在文献中找到任何可以支持我的猜测的东西?

fstat 手册页中可能会出现一个提示:

Note:  the order of fields in the stat structure varies somewhat across architectures.  In
       addition, the definition above does not show the padding bytes that may be present between
       some  fields  on  various  architectures.  Consult the glibc and kernel source code if you
       need to know the details.

第二个是 StackOverflow 上的问题,有人问为什么 NGINX 保留已删除文件的句柄:

https://stackoverflow.com/questions/75249922/nginx-keeps-file-descriptors-open-for-deleted-files

答案2

事实证明这是一个与 docker 相关的问题,似乎在主机上删除文件和容器注册该文件不再存在之间存在一定的延迟。这可能是由于 sshfs 造成的。

相关内容