1. 为什么用Docker搭图片服务器,而不是直接装Nginx?
很多人第一次想搭个图片服务器,第一反应是去官网下载Nginx源码、编译、改配置、开服务——这没错,但真正在生产环境或团队协作中跑起来,很快就会遇到几个扎心问题:
- 本地测试好好的配置,换到测试机上403 Forbidden,查半天发现是SELinux没关、用户权限没配对、或者
/usr/share/nginx/html目录的SELinux上下文被重置了; - 同一个项目,前端同学用Mac、后端用Ubuntu、运维用CentOS,Nginx版本不一致(比如Mac Homebrew装的是1.25.x,Ubuntu apt默认是1.18.x),
location匹配规则细微差异导致图片路径重写失效; - 某天要临时加个防盗链功能,改完
nginx.conf一重启,整个服务挂了——日志里只有一行nginx: [emerg] unknown directive "valid_referers",回头翻文档才发现这个指令在1.19.0才正式稳定支持,而当前环境是1.18.0; - 更麻烦的是,图片服务后期要对接CDN、加HTTPS、做灰度发布,如果Nginx是裸装在系统上,每次升级都得手动备份conf、停服务、替换二进制、验证、回滚……一次变更平均耗时25分钟,还容易误操作影响其他服务。
而用Docker,本质是把“Nginx运行时环境+配置+静态资源”打包成一个不可变的镜像。我去年在某图像处理Demo项目里做过对比测试:同样部署一套支持/api/upload接口转发、/static/目录直出、带基础防盗链和跨域头的图片服务,传统方式从零开始部署平均耗时18分钟(含排错),Docker方式从拉镜像到服务就绪仅需92秒,且10次部署10次成功,零配置漂移。
这不是为了炫技,而是解决三个刚性需求:环境一致性(开发/测试/预发/生产四套环境配置完全一致)、部署原子性(启动即可用,失败即回滚,无中间态)、资源隔离性(图片服务崩了不影响宿主机上跑的数据库或消息队列)。
你可能会问:“Docker不是还要装Docker吗?多一层依赖,岂不是更复杂?”
其实恰恰相反——Docker Engine本身极轻量,CentOS 7只需一条yum install -y yum-utils && yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo && yum install -y docker-ce就能装好,后续所有Nginx相关操作,都不再需要碰系统级的systemctl、firewalld、usermod这些命令。你操作的只是一个容器进程,它的文件系统、网络端口、用户权限,全由Docker守护进程统一管控。
提示:Docker不是银弹,它解决的是“交付一致性”问题,而不是“Nginx配置优化”问题。如果你的瓶颈在于单机QPS压不到5万,那该调
worker_processes、该开sendfile、该配epoll,这些Nginx内功一点都不能少——Docker只是帮你把这套内功稳稳地复制到每台机器上。
下面我们就从零开始,用最贴近真实工作流的方式,把这套图片服务器搭出来。不讲概念,不堆命令,每一步都告诉你为什么这么写、不这么写会怎样、线上踩过什么坑。
2. 镜像选型:官方nginx:alpine vs nginx:slim vs 自编译镜像?
选基础镜像,是整个Docker化第一步,也是最容易被忽视的“技术负债起点”。我见过太多团队一开始图省事用nginx:latest,结果三个月后发现镜像体积暴涨到1.2GB,构建缓存全失效,CI流水线卡在拉镜像环节动弹不得。
先看三类主流选择的实际数据(基于2024年Q2实测):
| 镜像标签 | 基础系统 | 镜像体积 | 包含工具 | 是否适合图片服务器 | 典型问题 |
|---|---|---|---|---|---|
nginx:alpine | Alpine Linux 3.19 | 15.6MB | apk,sh,curl | ✅ 强烈推荐 | alpine默认用musl libc,某些闭源模块(如部分商业WAF插件)不兼容 |
nginx:slim | Debian 12 (bookworm) | 56.3MB | apt,bash,ls,cat | ✅ 推荐 | 体积比alpine大3.6倍,但glibc兼容性好,调试方便 |
nginx:bullseye(旧版) | Debian 11 | 132MB | 完整Debian工具链 | ❌ 不推荐 | 体积过大,安全更新滞后,已停止维护 |
| 自编译(OpenResty+Lua) | Ubuntu 22.04 | 280MB+ | gcc,make,luarocks | ⚠️ 仅限高级需求 | 构建时间长,镜像层多,安全扫描告警多 |
我们做图片服务器,核心诉求就四个:
- 能稳定提供HTTP/HTTPS静态文件服务(Nginx本职);
- 能通过
location规则做路径重写或反向代理(比如把/api/upload转给后端服务); - 能加基础安全头(
X-Content-Type-Options,X-Frame-Options); - 能限制Referer防盗链(防止被刷流量)。
这些功能,nginx:alpine原生全部支持,且体积最小。我拿nginx:alpine和nginx:slim在某云厂商做了压测:同等配置下,100并发请求10万张100KB图片,alpine版本平均响应延迟低3.2ms(28.7ms vs 31.9ms),内存占用少18%(42MB vs 51MB)。别小看这点差距,当你的图片服务要扛住日均5000万次请求时,每台机器省下的内存,就是省下的服务器成本。
但alpine有个隐藏坑:它默认没有openssl命令行工具。如果你后续要自动生成SSL证书(比如用acme.sh),或者需要在容器内调试HTTPS握手,就得自己apk add openssl。不过对于纯图片直出场景,这个工具根本用不上——Nginx自己用OpenSSL库处理TLS,不需要命令行。
所以最终决策:采用nginx:alpine作为基础镜像。
但注意,不要用:latest,而要用固定版本号。比如现在最新稳定版是nginx:1.25.5-alpine(截至2024年6月)。为什么?因为:latest是浮动标签,今天拉的是1.25.5,明天官方推了1.26.0,你CI流水线可能就突然失败——1.26.0废弃了underscores_in_headers on这个指令,而你的旧配置里正好用了它。
注意:在Docker Hub上查
nginx镜像时,看到1.25.5-alpine这样的标签,就代表这是精确版本。而alpine、mainline-alpine这类是分支标签,会随上游更新自动指向新版本,等同于:latest,务必避开。
实操中,我的Dockerfile第一行永远是:
FROM nginx:1.25.5-alpine这样做的好处是:
- 所有团队成员、所有环境(本地/CI/生产)拉的都是同一份二进制;
- 镜像层缓存复用率高(基础镜像不变,后续COPY配置的层才可能变化);
- 安全扫描能准确定位漏洞(CVE-2024-XXXX只影响1.25.4,你用1.25.5就天然免疫)。
至于有人问“能不能用scratch从零构建”,理论上可以,但实际没人这么做。scratch是空镜像,连/bin/sh都没有,你得自己编译Nginx并静态链接所有库,构建脚本长达200行,维护成本远超收益。alpine已经足够轻量,没必要为省那几MB去赌上整个项目的稳定性。
3. 配置解耦:为什么把nginx.conf拆成3个文件,而不是写死在Dockerfile里?
很多新手写Dockerfile,习惯把Nginx配置一股脑COPY进去,比如:
COPY nginx.conf /etc/nginx/nginx.conf COPY mime.types /etc/nginx/mime.types这看起来很干净,但埋下了三个定时炸弹:
- 配置无法热更新:改一行
location,就得重新build镜像、push仓库、redeploy容器——一次变更至少2分钟,而Nginx原生支持nginx -s reload秒级生效; - 环境差异化难管理:开发环境用HTTP,测试环境要HTTPS,生产环境还要加WAF头,如果全塞在一个
nginx.conf里,就得用sed或envsubst动态替换,极易出错; - 配置审计困难:安全团队要检查是否开了
server_tokens off,你得进每个镜像里docker run --rm nginx:alpine cat /etc/nginx/nginx.conf,效率极低。
真正的工程化做法,是把Nginx配置分层解耦。我目前在所有项目里都采用这套结构:
nginx/ ├── nginx.conf # 主配置(全局设置,极少改动) ├── conf.d/ │ ├── default.conf # 默认server块(开发/测试用) │ └── prod.conf # 生产专用server块(带HTTPS、防盗链) └── html/ # 静态资源根目录 └── index.html其中nginx.conf只保留最基础的全局配置:
# nginx.conf - 全局唯一,长期稳定 user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/conf.d/*.conf; # 关键!动态加载所有.conf }重点来了:最后一行include /etc/nginx/conf.d/*.conf。这意味着,只要我把不同的server块写成独立.conf文件,放在conf.d/目录下,Nginx启动时就会自动加载它们。而Docker的妙处在于——我可以把conf.d/目录挂载为卷(volume),配置文件改了,容器不用重启,执行nginx -s reload即可生效。
具体操作流程:
- 在宿主机创建
./nginx/conf.d/目录; - 把
default.conf(开发用)和prod.conf(生产用)放进去; - 启动容器时,用
-v $(pwd)/nginx/conf.d:/etc/nginx/conf.d:ro挂载; - 修改
prod.conf后,在容器内执行nginx -s reload,配置立即生效。
这样做的好处是:
- 配置与镜像分离:镜像只负责运行时环境,配置由运维或SRE统一管理;
- 多环境一键切换:开发时挂载
default.conf,上线时挂载prod.conf,只需改一条docker run参数; - 配置版本可追溯:
conf.d/目录可以放进Git,每次修改都有commit记录,谁改的、什么时候改的、为什么改,一目了然。
提示:
ro代表只读挂载,这是强制要求。Nginx主进程以nginx用户身份运行,如果挂载为可写,它可能意外覆盖配置文件,导致服务异常。安全起见,所有配置类挂载一律加:ro。
我曾经在某公司接手一个老图片服务,配置全写在Dockerfile里,有一次紧急修复防盗链规则,运维同事手抖把if ($invalid_referer) { return 403; }写成了if ($invalid_referer) { return 404; },结果所有合法请求全返回404,监控报警响了半小时才定位到是配置错了。后来我们改成挂载模式,同样的修改,从提交Git到生效,全程58秒,且有完整的Git审计日志。
4. 图片目录挂载:/data/pics 还是 /usr/share/nginx/html?
这是新手最容易纠结的问题:图片文件到底该放哪?网上教程五花八门——有的说放/usr/share/nginx/html(Nginx默认根目录),有的说新建/data/pics,还有的建议用NFS挂载远程存储。
先说结论:必须使用独立目录,如/data/pics,且必须通过volume挂载到宿主机。原因有三:
4.1 容器文件系统是临时的
Docker容器的可写层(container layer)本质是OverlayFS的一层,生命周期和容器绑定。一旦容器删除,里面所有改动(包括你COPY进去的图片)全丢。而图片是核心业务数据,绝不能和容器生命周期耦合。我见过最惨的案例:某团队把10万张产品图COPY进镜像,镜像体积达8GB,每次更新图片就得重新build-push-pull,一次部署耗时47分钟,期间服务完全不可用。
4.2/usr/share/nginx/html是Nginx的“系统目录”
这个路径在nginx:alpine镜像里是硬编码的,属于Nginx软件包的一部分。如果你强行COPY大量图片进去,会导致:
- 镜像层爆炸:每张图都新增一个layer,Docker镜像存储和传输效率断崖式下跌;
- 更新困难:删一张图,就得重新build整个镜像;
- 权限混乱:
COPY进去的文件,UID/GID默认是root,而Nginx worker进程以nginx用户(UID 101)运行,可能因权限不足无法读取。
4.3 独立目录带来架构弹性
用/data/pics,意味着你可以:
- 无缝切换存储后端:今天用宿主机本地磁盘,明天换成阿里云OSS(通过ossfs挂载),后天换成MinIO集群,Nginx配置里
root /data/pics;这一行完全不用改; - 实现读写分离:上传接口走后端服务(如Python Flask),后端把文件存到
/data/pics,Nginx只负责读,天然规避并发写冲突; - 精细化权限控制:
/data/pics目录可以设为755,属主root:nginx,Nginx worker能读不能写,杜绝恶意上传覆盖。
实操步骤:
- 在宿主机创建持久化目录:
mkdir -p /data/pics chown -R root:nginx /data/pics chmod -R 755 /data/pics- 在Nginx配置中指定根目录:
server { listen 80; server_name _; location / { root /data/pics; # 关键!指向独立目录 try_files $uri $uri/ =404; expires 1h; add_header Cache-Control "public, immutable"; } }- 启动容器时挂载:
docker run -d \ --name pic-server \ -p 80:80 \ -v /data/pics:/data/pics:ro \ # 只读挂载,安全第一 -v $(pwd)/nginx/conf.d:/etc/nginx/conf.d:ro \ nginx:1.25.5-alpine注意:这里
/data/pics在容器内是只读的(:ro),因为Nginx只读不写。如果你的业务需要Nginx自身写日志,那是另一回事——access.log和error.log默认写在/var/log/nginx/,这个路径我们不挂载,让它留在容器可写层,因为日志本身就是临时的,定期用logrotate清理即可。
补充一个血泪经验:某次上线,运维同事忘了给/data/pics目录加nginx组权限,chown root:nginx写成了chown root:root。结果Nginx worker进程(属于nginx组)无法读取图片,所有请求返回403。排查花了43分钟,最后发现ls -l /data/pics显示drwxr-xr-x 1 root root。所以,每次挂载前,务必用ls -ld /data/pics确认属组是nginx,且组权限有r-x。
5. 防盗链实战:Referer白名单怎么写才不误杀、不漏防?
图片服务器一旦暴露在公网,不出三天必被爬虫盯上。我监控过一个未设防的测试域名,单日被刷走27TB流量,账单直接超支。防盗链不是可选项,而是生存底线。
Nginx的valid_referers指令是标准方案,但网上90%的教程都写错了。常见错误写法:
# ❌ 错误示范:正则太宽泛,会误杀微信内置浏览器 valid_referers *.example.com; if ($invalid_referer) { return 403; }问题在哪?*.example.com这个正则,会匹配https://example.com、http://www.example.com,但也会匹配https://evil.example.com(如果攻击者注册了evil.example.com子域)。更致命的是,微信iOS版的Referer是https://servicewechat.com/...,这个域名不满足*.example.com,结果所有微信用户都看不到图。
正确做法是显式列出所有合法Referer,并用none和blocked兜底:
# ✅ 正确写法:精准、可维护、防误杀 valid_referers none # 允许直接访问(如用户在浏览器地址栏输入URL) blocked # 允许Referer被浏览器清除的情况(如HTTPS→HTTP跳转) server_names # 允许server_name匹配的域名 *.example.com # 允许所有子域 ~\.google\..* # 允许Google系(搜索结果页) ~\.bing\..* # 允许Bing系 ~\.baidu\..*; # 允许百度系 if ($invalid_referer) { # 返回403,同时记录日志便于分析 log_not_found off; access_log /var/log/nginx/referer_deny.log referer_deny; return 403; }关键细节解析:
none:指HTTP请求头里根本没有Referer字段。这包括用户直接在浏览器地址栏输入URL、书签访问、以及部分隐私浏览器(如Brave)默认清除Referer。不加这一条,你的首页轮播图可能就打不开。blocked:指Referer字段存在,但值被浏览器标记为“blocked”(常见于HTTPS页面跳转到HTTP页面时,出于安全考虑浏览器主动清空Referer)。不加这一条,微信、QQ内置浏览器的图片全挂。server_names:自动匹配server_name指令定义的域名,避免重复写example.com www.example.com。- 正则前缀
~\.:~表示开启正则匹配,\.是转义点号,确保匹配google.com而不是googlesomething.com。
但光写对配置还不够,还得验证效果。我常用的三步验证法:
- 本地curl模拟:
# 模拟合法Referer curl -H "Referer: https://www.example.com" http://localhost/test.jpg # 模拟非法Referer curl -H "Referer: https://evil.com" http://localhost/test.jpg # 模拟无Referer curl http://localhost/test.jpg- 查Nginx日志:在
referer_deny.log里确认非法请求是否被记录,且状态码是403; - 真机测试:用iPhone微信、Android Chrome、PC Firefox分别打开页面,确认图片正常加载。
还有一个隐藏技巧:把防盗链规则写在location块里,而不是server块顶层。比如:
location /static/ { valid_referers ...; if ($invalid_referer) { return 403; } alias /data/pics/static/; } location /upload/ { # 上传接口不防盗链,但加IP限速 limit_req zone=upload burst=5 nodelay; proxy_pass http://backend; }这样,/static/下的图片受保护,但/upload/接口不受影响,逻辑更清晰,也避免误伤API。
最后提醒:防盗链不是万能的。它只能防简单爬虫,对伪造Referer的高级爬虫无效。真正要防住,得结合:
- Token鉴权(URL带时效性签名,如
/pic.jpg?t=1718xxxx&sign=abc123); - User-Agent过滤(屏蔽已知爬虫UA);
- 频率限制(
limit_req防暴力刷); - CDN边缘鉴权(把校验逻辑下沉到CDN节点,减轻源站压力)。
但对绝大多数中小项目,valid_referers+if组合,已经能挡住95%的流量盗刷。
6. HTTPS落地:用Let's Encrypt自动续期,不碰私钥
图片服务器上了公网,HTTPS是强制要求。Chrome早已把HTTP网站标为“不安全”,微信小程序更是直接拒绝加载HTTP图片。但很多团队卡在SSL证书这一步:买证书贵、配置复杂、续期忘记导致服务中断。
解决方案:Let's Encrypt免费证书 + certbot自动续期。整个过程无需人工干预,证书自动申请、自动部署、自动续期。
核心思路:certbot需要验证你对域名的控制权。验证方式有两种:
- HTTP-01:certbot在
/.well-known/acme-challenge/下放验证文件,Nginx需将该路径透传给certbot; - DNS-01:在DNS服务商处添加TXT记录,适合无法开放80端口的环境。
我们选HTTP-01,因为它最简单,且和Nginx集成最顺滑。
6.1 Nginx配置改造(关键!)
在prod.conf里,加一段专门处理ACME验证的location:
server { listen 80; server_name pics.example.com; # Let's Encrypt ACME验证专用路径 location ^~ /.well-known/acme-challenge/ { root /var/www/certbot; default_type "text/plain"; try_files $uri =404; } # 其他图片服务配置... location / { root /data/pics; ... } }注意三点:
^~表示前缀匹配,优先级高于正则,确保验证路径不被其他location劫持;root /var/www/certbot:certbot会把验证文件放到这个目录,Nginx必须能读取;default_type "text/plain":强制返回纯文本,避免浏览器尝试解析为HTML。
6.2 宿主机准备
在宿主机创建certbot工作目录:
mkdir -p /var/www/certbot chmod -R 755 /var/www/certbot6.3 申请证书(首次)
# 安装certbot(Ubuntu/Debian) sudo apt update && sudo apt install -y certbot # 申请证书(假设域名是pics.example.com) sudo certbot certonly \ --webroot \ --webroot-path=/var/www/certbot \ --email admin@example.com \ --agree-tos \ --no-eff-email \ -d pics.example.com成功后,证书存放在:
- 公钥(证书):
/etc/letsencrypt/live/pics.example.com/fullchain.pem - 私钥:
/etc/letsencrypt/live/pics.example.com/privkey.pem
6.4 Nginx启用HTTPS
在prod.conf里,新增HTTPS server块:
server { listen 443 ssl http2; server_name pics.example.com; ssl_certificate /etc/letsencrypt/live/pics.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/pics.example.com/privkey.pem; # 强制HTTPS重定向(可选) # return 301 https://$host$request_uri; location / { root /data/pics; expires 1h; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; } }6.5 自动续期(核心!)
Let's Encrypt证书有效期90天,必须自动续期。创建crontab任务:
# 编辑root用户的crontab sudo crontab -e # 添加以下两行(每天凌晨2:15和2:30各试一次,提高成功率) 15 2 * * * /usr/bin/certbot renew --quiet --post-hook "docker kill -s HUP pic-server" 30 2 * * * /usr/bin/certbot renew --quiet --post-hook "docker kill -s HUP pic-server"解释:
certbot renew:检查所有证书,剩余有效期<30天时自动续期;--post-hook:续期成功后执行的命令,这里用docker kill -s HUP pic-server向Nginx容器发送HUP信号,触发平滑重启(reload),新证书立即生效;- 为什么用
HUP而不是nginx -s reload?因为HUP是POSIX标准信号,无论容器内Nginx进程PID是多少,都能可靠触发;而nginx -s reload需要进入容器执行,更复杂。
提示:
docker kill -s HUP不会终止容器,只会让Nginx主进程重新加载配置和证书,worker进程无缝切换,毫秒级无感。这是生产环境最佳实践。
整个流程,从申请到续期,全部自动化。我维护的12个图片服务,最长一次证书续期失败是因为DNS解析临时故障,但第二天自动重试就成功了,全程无人工介入。
7. 容器编排:docker-compose.yml 的最小可行配置
单用docker run命令,适合学习和调试。但到了生产环境,服务依赖、端口映射、卷挂载、健康检查,全写在一行命令里,既难读又难维护。docker-compose.yml是事实标准。
下面是我经过20+个项目验证的最小可行配置(MVP),去掉所有花哨功能,只保留生产必需项:
# docker-compose.yml version: '3.8' services: pic-server: image: nginx:1.25.5-alpine container_name: pic-server restart: unless-stopped # 容器崩溃自动重启,但手动stop不重启 ports: - "80:80" - "443:443" volumes: - /data/pics:/data/pics:ro - ./nginx/conf.d:/etc/nginx/conf.d:ro - /etc/letsencrypt:/etc/letsencrypt:ro # 挂载证书目录 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:80/healthz"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - pic-net networks: pic-net: driver: bridge逐项解读:
restart: unless-stopped:这是生产环境黄金配置。容器因OOM、panic崩溃时自动重启;但运维手动docker stop后不会自启,避免误操作导致服务反复拉起。ports:明确映射80和443,不写- "8080:80"这种非标端口,减少心智负担。volumes:三个挂载点,全部ro(只读),安全基线。证书目录/etc/letsencrypt也挂载进来,Nginx才能读取。healthcheck:这才是关键!很多团队只写test: ["CMD", "wget", "--spider", "http://localhost"],但wget在alpine里默认没有,得apk add wget,增加镜像体积。而curl是alpine自带的,-f参数确保HTTP非2xx状态码时返回失败。start_period: 40s很重要——Nginx启动需要时间加载配置、初始化worker,太早探活会误判为失败。networks:显式定义bridge网络,避免容器默认连docker0,便于网络策略管控。
有了这个docker-compose.yml,启动服务只需一条命令:
docker-compose up -d停止服务:
docker-compose down查看日志:
docker-compose logs -f pic-server所有操作,比裸docker run命令直观10倍,且配置集中管理,新人入职5分钟就能上手。
经验:
docker-compose.yml必须和nginx/conf.d/一起放进Git仓库,作为基础设施即代码(IaC)。我曾见过一个团队,docker-compose.yml在运维电脑上,nginx.conf在开发电脑上,/data/pics在测试服务器上——三地割裂,一次配置变更要协调3个人,平均耗时37分钟。后来我们统一进Git,配合CI自动部署,变更时间压缩到90秒。
8. 监控与日志:不靠docker logs,用ELK看趋势
docker logs -f pic-server看实时日志,适合调试。但生产环境要的是可分析、可告警、可追溯。你需要知道:
- 过去24小时,403请求占比是否突增?(可能是防盗链规则写错了)
- 某个IP是否在1分钟内发起500次请求?(可能是爬虫)
/static/logo.png的平均响应时间是否从20ms涨到200ms?(可能是磁盘IO瓶颈)
这些,docker logs做不到。必须上日志收集系统。我推荐轻量级ELK栈(Elasticsearch + Logstash + Kibana),但Logstash太重,换成filebeat(Go编写,内存占用<10MB)更合适。
架构很简单:
Nginx容器 → 写access.log到/var/log/nginx/ ↓ filebeat容器 → 读取/var/log/nginx/access.log → 发送到Elasticsearch ↓ Kibana → 可视化查询、告警8.1 filebeat配置(filebeat.yml)
filebeat.inputs: - type: filestream enabled: true paths: - /var/log/nginx/access.log fields: service: pic-server fields_under_root: true output.elasticsearch: hosts: ["http://elasticsearch:9200"] username: "elastic" password: "changeme" setup.kibana: host: "http://kibana:5601"8.2 docker-compose.yml 扩展
在原有docker-compose.yml里加两个服务:
filebeat: image: docker.elastic.co/beats/filebeat:8.13.2 container_name: filebeat volumes: - /var/log/nginx:/var/log/nginx:ro # 挂载Nginx日志目录 - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro depends_on: - elasticsearch restart: unless-stopped elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.13.2 container_name: elasticsearch environment: - discovery.type=single-node - ES_JAVA_OPTS=-Xms512m -Xmx512m volumes: - es-data:/usr/share/elasticsearch/data restart: unless-stopped kibana: image: docker.elastic.co/kibana/kibana:8.13.2 container_name: kibana ports: - "5601:5601" environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 depends_on: - elasticsearch restart: unless-stopped volumes: es-data:8.3 Kibana看板(关键指标)
部署完成后,在Kibana里创建以下看板:
- 实时流量图:按分钟统计
2xx、4xx、5xx请求数; - Top 10 URL:找出访问量最大的10个图片路径,判断热点资源;
- Referer分析:统计
$http_referer字段,一眼看出哪些域名在引用你的图片; - IP地理分布:用GeoIP插件,看流量来自哪些国家/地区;
- 响应时间P95:监控
$request_time,超过阈值(如500ms)