我部署对外公开的服务通常都会开启Cloudflare橙云代理,避免外部直接访问源站。
但是对于Minio这类对象存储服务,BucketExists和StatObject这类HEAD请求却时不时出现403的错误(主要表现在初始化Minio组件有判断存储桶是否存在的逻辑时会直接报错,或者上传资源前需要获取资源信息时时不时出现403 Access denied的报错,且每次出现的路径objectKey不固定),如果关闭橙云改成DNS Only直连Minio则没有这个问题。
在这里记录下我对这个问题的排查和解决的方式。
处理过程
起初是因为我的项目增加了一个对象存储初始化的功能,安装或升级版本时需要比对已有的静态资源,缺失或不一致则补传到OSS,期间会执行StatObject用来获取文件的元信息判断文件是否存在、是否一致等。如果返回404 NOT FOUND则进行补传,但是时不时会出现403 ACCESS DENIED的情况,会导致异常退出。
而出现了403,一开始我想到的可能是安全规则的问题,我对Minio的endpoint配置的安全规则,用于限制IP白名单访问,而我访问Minio的IP可能是变化的,会导致进入“Sorry, you have been blocked”的页面,拦截页面的状态码也是403,我首先配置了规则对endpoint做直连访问。

但是问题依然没有解决,通过对minio-go sdk executeRequest方法下断点,先确认请求方式为HEAD且Path的存储桶名称和对象路径正确,然后又检查了响应的Header,主要检查了这几个键
- Cf-Cache-Status — 描述CF的缓存行为,BYPASS表示明确绕过缓存,但是其实这里有个坑,后面再说
- Content-Type — 内容类型,这里已经明确是xml格式了,所以基本上可以确认为是Minio返回的403内容

然后就可以使用mc admin trace -v <alias>Minio客户端的查看实时请求跟踪日志命令,这里的-v表示verbose,用来显示更详细的信息,来观察Minio实际收到的请求和发出的响应,包含url、method、header、body等
这是正常的请求和响应,通过HEAD请求来获取某个对象的信息,返回200正常的状态码,或者404表示文件不存在

而403的请求这里使用的是GET请求,提示了SignatureDoesNotMatch的报错,这和我StatObject所需要执行的请求类型是不一致的,所以导致了这个报错

这里关于Cloudflare缓存行为的文档有提及,缓存不存在时会将HEAD请求转换为GET请求,然后仅返回响应头的信息,但是这个转换对于Minio的StatObject等HEAD请求,以及一些验签包含请求方式的服务是致命的
Head Requests and Set-Cookie Headers

因此我们需要关闭这种转换,这里我是配置的域名包含时的排除规则,也可以改成针对HEAD请求方法及URL路径的排除规则

至此,Minio StatObject 403的问题就解决了
后续再查看trace日志的时候,发现Header没有Cf-Cache-Status字段了
一些回顾
CF-Cache-Status: BYPASS不等于请求从一开始就绕过Cloudflare的Cache Pipeline,只是说明响应不会被缓存,但不意味着请求一开始就没进入缓存逻辑,增加Cache rule能明确让请求开始就不具备缓存的条件
这里跟踪请求是否在Cloudflare层就被拦截,还是在服务层拦截的问题,也可以通过Nginx的AccessLog日志来查看,确认请求是否到达服务
对于Cloudflare代理Minio的配置,我再Nginx层上还做了一些额外的location配置
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_cache_convert_head off;
proxy_buffering off:可选。主要影响响应缓冲。对象下载、大文件或流式场景下可以关,但不是 MinIO 正常工作的必要条件。proxy_request_buffering off:比较推荐,尤其有大文件上传时。这样Nginx不会先把整个请求体缓存完再转发给 MinIO,能减少磁盘临时文件和延迟。proxy_cache off:推荐用于MinIO API/签名请求。S3 API本身不适合让 Nginx 随便做反向代理缓存,尤其是带AWS SigV4的请求proxy_cache_convert_head off:如果遇到过HEAD被转换成GET,那对这个场景来说就很有价值,否则不是通用必需项