结论先说:docker compose up -d 只说明 Compose 已把容器创建并启动,不能证明日志链路已经跑通。真正要验收的是四段:Agent 能否读到文件、Agent 能否推送到 Loki、Grafana 的数据源能否访问 Loki、查询标签和时间范围是否正确。哪一段断了,Grafana 最终都只会显示“没有日志”。
如果现在只做一件事,按下面的顺序检查,不要反复重启容器:
docker compose version
docker compose config
docker compose ps -a
docker compose logs --since=10m --tail=200 promtail loki grafana
其中 promtail 要替换成 Compose 文件里的实际服务名;使用 Alloy 时通常写成 alloy。
docker compose up -d 跑完了,第一步该看什么?
排查时,先把问题拆成“容器存活、文件可读、推送成功、查询正确”四个独立检查点。最短路径通常如下:
| 现象 | 更可能断在哪一段 | 先看什么 |
|---|---|---|
| Compose 提示服务无法创建 | YAML、镜像、端口或项目配置 | docker compose config、完整错误信息 |
容器显示 Exited |
配置文件、存储目录、启动参数 | docker compose logs <服务名> |
容器一直 Restarting |
进程启动后立即崩溃 | 退出码、最近日志、挂载路径 |
Agent 报 permission denied |
日志目录、UID/GID、SELinux | id、stat、容器挂载关系 |
| Agent 报连接失败或超时 | Loki 地址、DNS、监听地址、防火墙 | 从 Agent 网络测试 Loki /ready |
Loki 已经收到数据,Grafana 仍显示 No data |
数据源、标签、时间范围、时钟 | Loki API、数据源 URL、实际标签 |
先确认 Compose 插件版本,并查看合并后的配置:
docker compose version
docker compose config
docker compose config --services
docker compose config 只能说明 Compose 文件本身能够被解析,并显示服务、镜像、环境变量和挂载的合并结果。它不能证明容器里的 Loki YAML、Promtail YAML 或 Alloy River 配置正确。
旧环境只有 docker-compose 命令时,不要把两个命令混着执行。先确认实际安装的是哪一代 Compose,再统一使用对应命令;社区旧文章里的命令形式不能直接当成当前环境的事实。
启动后用 ps -a 看所有容器,包括已经退出的容器:
docker compose ps -a
再按服务读取最近日志:
docker compose logs --since=10m --tail=200 promtail loki grafana
如果 up -d 本身失败,优先区分三种情况:YAML 语法错误会在 docker compose config 阶段暴露;镜像拉取失败通常带有明确的拉取错误;端口冲突则多发生在容器创建或启动时,可以检查目标端口:
sudo ss -ltnp | grep ':3100'
这里的 3100 是社区示例常用的 Loki HTTP 端口,实际值仍以自己的部署配置为准。
单机跑通后,多台服务器是不是都要装 Loki 和 Grafana?
不是。单机验证时可以把 Loki、Grafana 和采集 Agent 放在同一个 Compose 项目里;正式集中采集时,通常应当拆成中心端和业务端两个项目:
中心服务器:
compose.yaml + Loki + Grafana + 持久化目录
业务服务器 web-01:
compose.yaml + Promtail 或 Alloy
业务服务器 web-02:
compose.yaml + Promtail 或 Alloy
Docker 官方资料将 Compose 定位为通过一个配置文件定义和运行多容器应用,因此单文件和多文件都可以使用,边界取决于部署目标。
中心服务器负责保存并查询日志,业务服务器只运行采集 Agent。Agent 把日志推送到中心 Loki,Grafana 再从中心 Loki 查询。若每台机器都运行一套完整 Loki,各机器上的 Loki 实例彼此独立,Grafana 查询其中一个实例,不会自动看到其他实例的数据;这既不能完成集中采集,也会重复占用存储和计算资源。
配置地址时,要区分 Compose 内部通信和跨主机通信。Grafana 与 Loki 在同一个 Compose 网络时,Grafana 数据源通常填写 http://loki:3100;远端 Agent 推送时,客户端地址必须是中心服务器的可达 IP 或 DNS 名,不能写 localhost。localhost 在 Agent 容器里指向 Agent 自己,而不是另一台机器上的 Loki。
容器为什么启动后又退出?
容器启动后退出,先找退出码和对应服务的最近日志,不要先猜配置。下面的命令适用于服务名为 loki 的情况,其他服务需要替换:
docker compose ps -a
docker compose logs --since=10m --tail=300 loki
cid="$(docker compose ps -a -q loki)"
docker inspect "$cid" \
--format 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}'
如果容器根本没有创建,重点检查 Compose 文件、镜像名、挂载源目录和端口。如果容器创建后立即退出,重点检查配置文件是否真的挂载进去、启动参数指向的文件是否存在,以及 Loki 的存储目录是否可写。容器正常存活但没有日志,则是另一类问题,需要继续检查采集目标。
Promtail 常见挂载方式类似下面这样:
services:
promtail:
volumes:
- ./promtail.yaml:/etc/promtail/promtail.yaml:ro
command: ["-config.file=/etc/promtail/promtail.yaml"]
这里要区分两个路径:宿主机上的 ./promtail.yaml,以及容器内的 /etc/promtail/promtail.yaml。-config.file 指向的是容器内路径。文件虽然在宿主机上存在,但没有挂载到该位置,Agent 仍然会启动失败。
Loki 也需要检查类似问题:配置文件的容器内路径、Loki 配置中的实际存储路径、Compose 中数据目录的挂载目标,三者必须对应。目录映射和容器权限是社区部署资料反复强调的坑点。
不要直接使用来源不明的 latest,应选择发布时官方兼容的固定标签,条件允许时进一步固定镜像 digest。旧资料中的 v2.8.0 可以作为历史配置来源,但不应直接用于当前部署;这些旧资料也经常使用 latest,这会导致升级后配置或行为发生漂移。新部署前应核对 Loki、Agent 和 Grafana 的兼容说明。
端口冲突、配置语法错误和存储目录不可写,错误位置并不相同。端口冲突通常出现在监听或绑定阶段,配置语法错误会出现在进程加载配置时,而存储权限错误通常出现在 Loki 初始化目录或写入数据时。反复修改 restart 参数不能替代这三项检查。
Promtail 说找不到日志文件,路径该填哪一层?
采集配置里的路径通常指容器内路径。先用挂载关系证明宿主机目录被映射到了哪里:
docker compose config
cid="$(docker compose ps -a -q promtail)"
docker inspect "$cid" \
--format '{{range .Mounts}}{{println .Source "->" .Destination "RW=" .RW}}{{end}}'
假设输出表示:
/srv/app/logs -> /srv/app/logs RW=false
那么 Promtail 的文件采集路径应指向 /srv/app/logs。如果宿主机目录是 /data/logs,容器内挂载点是 /var/log/app,配置中就应使用 /var/log/app/*.log,而不是宿主机路径 /data/logs/*.log。
可以在 Agent 容器内确认路径是否存在,并查看目录和文件的权限:
docker compose exec promtail ls -ld /srv/app/logs
docker compose exec promtail ls -ln /srv/app/logs
如果镜像没有 ls 等工具,就通过容器元数据和镜像文档确认默认用户及工具集,不要为了执行一条诊断命令就给生产 Agent 增加不必要的 shell 或权限。
还要区分两类日志源。普通文件日志通常是把宿主机日志目录以只读方式挂载进容器,然后由 __path__ 或对应 glob 匹配文件。Docker JSON 日志则涉及 Docker 的日志发现机制、容器元数据以及实际日志文件位置,不能假设把宿主机 /var/log 挂进容器就一定能够读取。
目录映射错误是高频故障点,因为“宿主机能看到文件”不等于“容器内同一路径能看到文件”。若挂载后发现宿主机对应位置是一个空目录,Agent 同样不会产生日志;应回到 docker compose config 和 docker inspect 核对源、目标与读写属性。
日志文件能列出来,Agent 为什么还是 permission denied?
先比较宿主机文件的 UID、GID、权限模式和 Agent 的运行身份:
docker compose exec promtail id
stat -c '%u:%g %a %n' \
/srv/app/logs \
/srv/app/logs/app.log
cid="$(docker compose ps -a -q promtail)"
docker inspect "$cid" --format 'imageUser={{.Config.User}}'
如果容器内用户不属于文件属组,且文件没有给其他用户读取权限,Agent 能列出目录也不代表能打开文件。目录需要执行权限,普通日志文件需要读取权限。
优先选择影响范围较小的修复方式:让专用日志目录属于 Agent 的 UID/GID,或者让日志属组与 Agent 的附加组一致,再补齐组权限。例如 Compose 可以加入日志属组:
services:
promtail:
group_add:
- "<LOG_GID>"
然后在宿主机上把 <LOG_GID> 替换为实际组 ID:
sudo chgrp -R <LOG_GID> /srv/app/logs
sudo find /srv/app/logs -type d -exec chmod g+x {} \;
sudo find /srv/app/logs -type f -exec chmod g+r {} \;
也可以统一调整专用日志目录的属主,但要先从镜像元数据或镜像文档确认默认 UID/GID。不要把日志目录随意改成容器用户属主,因为这可能影响其他服务。
只读挂载与可写目录要求也不能混淆。日志源通常应只读挂载:
volumes:
- /srv/app/logs:/srv/app/logs:ro
但 Agent 的位置文件、缓存或其他运行状态仍可能需要写入独立目录。Promtail 的 positions.filename 如果放在只读目录中,同样会失败。日志源只读,不代表整个 Agent 进程都不需要可写状态目录。
社区文章中确实出现过通过 chmod -R 777 恢复读取的做法,但它会把目录和文件对所有本地用户开放,扩大暴露面,也会掩盖真实的属组配置问题,不适合作为首选修复。
在启用了 SELinux 的系统上,即使 DAC 权限看起来正确,仍可能因为安全标签不匹配而拒绝访问。此时先确认系统确实启用了 SELinux,再核对 Compose 挂载标签:
volumes:
- type: bind
source: /srv/app/logs
target: /srv/app/logs
read_only: true
bind:
selinux: Z
普通文件采集与 Docker 容器发现是两类不同需求,不能给所有 Promtail 容器统一增加特权。读取 Docker socket、发现容器日志或读取 cgroup 时,资料提到的 privileged 或 SYS_ADMIN 方案只适用于这些特定场景。挂载 Docker socket 本身就会向容器暴露高权限 Docker API,而 privileged 的能力范围更大;普通日志采集不应照搬这些设置。
Loki 报 permission denied on /tmp/loki,怎么修数据目录权限?
Loki 必须能够写入配置指定的存储路径。先检查 Loki 配置中的实际路径,再检查它被挂载到了哪里:
docker compose logs --since=10m --tail=300 loki
docker compose exec loki id
cid="$(docker compose ps -a -q loki)"
docker inspect "$cid" \
--format '{{range .Mounts}}{{println .Source "->" .Destination "RW=" .RW}}{{end}}'
社区资料给出的一个具体方案,是让 Loki 以 1001:1001 运行,并把宿主目录挂载到 /tmp/loki:
services:
loki:
user: "1001:1001"
volumes:
- ./loki-data:/tmp/loki
然后修改这个专用目录的属主:
sudo chown -R 1001:1001 ./loki-data
但 1001:1001 不是通用 UID。使用前必须核对所选镜像的默认用户、Compose 中的 user 和 Loki 配置中的存储路径,三者要一致。如果实际配置使用 /data,就应当把专用目录挂载到 /data:
services:
loki:
user: "1001:1001"
volumes:
- ./loki-data:/data
不要把 /data 和 /tmp/loki 混着挂,也不要为了修复一个 Compose 数据目录而递归修改系统 /data 的权限。真正要修改的应该是 Loki 配置所指向、且由该 Compose 项目管理的专用宿主目录。
如果属主调整后仍然报权限错误,再检查磁盘空间、inode 和只读挂载:
df -h
df -i
docker compose logs --since=10m --tail=300 loki
磁盘写满、inode 耗尽和根文件系统只读,都可能让 Loki 初始化失败。权限错误不只意味着“用户 ID 不对”,反复改权限也解决不了这些问题。
Agent 为什么连不上另一台机器上的 Loki?
远端 Agent 眼中的 localhost 是 Agent 所在容器,不是中心服务器。先从当前执行环境测试中心 Loki:
curl -fsS http://<LOKI_HOST_IP>:3100/ready
这个命令只能证明“执行 curl 的机器”能访问 Loki。如果要验证 Agent 容器的网络,应进入 Agent 容器测试:
docker compose exec promtail \
sh -c 'command -v curl || command -v wget'
docker compose exec promtail \
sh -c 'wget -qO- http://<LOKI_HOST_IP>:3100/ready || curl -fsS http://<LOKI_HOST_IP>:3100/ready'
镜像内没有网络工具时,可以临时使用一个包含相应工具的诊断容器,并把它接入 Agent 所在的 Compose 网络。诊断镜像也应使用固定版本,不要为了临时排障引入不可追踪的 latest。
连接失败可以按错误类型继续缩小范围:
- DNS 解析失败:检查中心地址、主机的 DNS 配置和 Compose 网络。
Connection refused:检查 Loki 是否启动、是否只监听回环地址,以及端口映射是否正确。- 连接超时:检查主机防火墙、安全组和容器网络隔离。
- 返回
401或403:网络已经通了,应检查反向代理认证、客户端凭据和权限配置。 - TLS 证书错误:检查客户端信任链、域名和 HTTPS 配置是否一致。
Loki 只监听 127.0.0.1 时,远端 Agent 无法连接;容器发布了端口,也不代表容器内进程真的监听在 0.0.0.0。使用 HTTPS 反向代理时,Agent 的写入地址、代理转发路径、证书和认证配置必须成套一致。
3100 是旧社区 Compose 示例中的常用端口,但应以实际 Loki 配置为准。即使需要远程访问,也不建议把没有认证和访问控制的 Loki 直接暴露到公网;优先限制在受控内网或安全组范围内。
Agent 明明在运行,为什么一个 target 都没有?
进程存活只说明 Agent 已启动,不等于它已经发现日志目标。首先确认 Agent 加载的是哪份配置:
docker compose config
docker compose logs --since=30m --tail=300 promtail
然后核对配置中的日志路径、glob 范围、标签和挂载关系。Promtail 的 __path__ 指向容器内路径;文件存在但不符合 glob,同样不会产生 target。文件为空或停止追加时,也不会产生新的日志流。
可以在容器内检查文件是否确实存在且可读:
docker compose exec promtail ls -ln /srv/app/logs
对于 Docker 容器日志发现,还要单独确认 Docker socket、容器目录和元数据来源是否都已提供。普通目录挂载不能替代 Docker socket,也不能自动获得容器发现能力。
已有日志文件从文件末尾还是历史位置开始读取,与所选 Promtail 版本、位置文件状态及启动行为有关。不要用一条所谓通用参数覆盖所有版本,也不要在排障时随意删除位置文件;删除状态文件可能改变重新采集的起点。应按发布时官方文档核对对应配置项。
Agent 的 HTTP 服务显示 Ready,也不等于已经发现 target。目标数量为零时,应继续看日志发现、路径匹配和文件读取,而不是把“进程未退出”当作采集成功。
Promtail 使用 YAML,Alloy 使用 River 配置。把 promtail.yaml 挂到 Alloy 容器里,或者把 Alloy 配置交给 Promtail,都不会得到等价行为。两者的路径、标签、位置状态和写入 Loki 的配置项需要分别核对。
Loki 已经有数据,Grafana 为什么仍显示 No data?
这时采集和推送大概率已经成功,问题通常集中在 Grafana 数据源、查询标签、时间范围和时钟。
同一 Compose 网络中,Grafana 数据源地址应使用 Loki 的服务名:
http://loki:3100
分机部署时,则填写 Grafana 所在环境能够访问的中心地址。无论哪种情况,都不要把数据源写成 localhost 后期待它跨主机访问。
先确认 Loki API 能返回标签:
curl -fsS http://<LOKI_HOST>:3100/loki/api/v1/labels
再从实际存在的标签构造选择器,例如:
{server="web-01", job="nginx"}
查询中的标签名和值必须与实际配置一致。不要用不存在的标签做查询,也不要把日志正文里的任意关键词直接当作标签选择器。Loki 主要通过标签建立索引,查询时应先用标签缩小范围,再增加行过滤条件。例如:
{server="web-01", job="nginx"} |= "connection refused"
时间范围也必须覆盖日志写入时间。Grafana、Loki、Agent 和业务主机如果时钟不一致,日志可能已经进入 Loki,却落在查询范围之外。可以分别执行:
date -Is
timedatectl status
如果 Loki API 能查到数据、Grafana 仍然没有结果,就优先核对数据源测试结果、实际标签、Grafana 当前时间范围和浏览器时区,而不是重新部署采集 Agent。
为什么只有部分服务器有日志,或者同一条日志重复?
多机采集要同时保证“一台主机一个明确采集来源”和“每条日志带稳定且可区分的标签”。
部分主机缺日志,通常是 Agent 没有部署、配置仍指向旧中心地址、该主机出站网络被拦截,或者文件路径与挂载关系不一致。先在故障主机本地验证 Agent 到 Loki 的连接,再检查中心 Loki 是否收到该主机的标签。
重复日志常见于两种情况。第一类是多个 Agent 采集了同一个目录,例如迁移时 Promtail 和 Alloy 同时运行;更隐蔽的情况是多个 Agent 使用完全相同的标签,Grafana 查询时无法区分来源。第二类是多个实例都向中心 Loki 推送,但没有稳定的主机标签。
建议每台主机至少提供稳定的 server,并用 job 表达采集类型:
{server="web-01", job="nginx"}
server 应表示固定主机或逻辑节点,job 应表示日志流水线。不要把容器 ID、完整时间戳、请求 ID 等动态值全部塞进标签;这类高基数字段会持续增加索引和查询负担。日志正文里的主机名可以作为行内容补充,但不能代替结构化标签。
多个 Agent 不要挂载并采集同一个绑定目录。若确实需要集中挂载共享存储,也要明确唯一采集责任,避免每台机器都读取相同文件。Promtail 切换到 Alloy 时尤其要控制重叠运行窗口,否则迁移期间很容易出现重复。
Promtail 和 Alloy 能不能直接换配置继续用?
不能直接换。两者可以承担相同的采集角色,但配置模型不同:
| 能力 | Promtail | Alloy |
|---|---|---|
| 配置格式 | YAML | River |
| 文件读取 | scrape_configs、静态目标 |
promtail.scrape |
| 写入 Loki | clients |
loki.write |
| 健康状态 | 启动后核对其 HTTP 健康或就绪端点 | 启动后核对其 HTTP 健康或就绪端点 |
| 运行状态 | 位置文件及状态目录 | 对应组件的状态目录 |
Promtail 的配置结构大致如下:
server:
http_listen_port: 9080
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://loki-central.example:3100/loki/api/v1/push
scrape_configs:
- job_name: app
static_configs:
- targets:
- app
labels:
server: web-01
job: app
__path__: /srv/app/logs/*.log
Alloy 完成同等流程时使用 River 配置,例如:
promtail.scrape "app" {
targets = [
{
__path__ = "/srv/app/logs/*.log"
server = "web-01"
job = "app"
},
]
forward_to = [promtail.relabel.app.receiver]
}
promtail.relabel "app" {
forward_to = [loki.write.default.receiver]
}
loki.write "default" {
endpoint {
url = "http://loki-central.example:3100/loki/api/v1/push"
}
}
这些片段用于说明配置职责和格式差异,实际字段、默认值和参数仍要结合所选版本核对。Promtail 的配置文件通常通过 -config.file 指向容器内路径;Alloy 则运行自己的配置文件,不能只改扩展名。
新部署应先查看发布时官方支持状态。如果官方已经把 Promtail 标为退役或仅维护,就应优先评估 Alloy,而不是因为旧教程大量使用 Promtail 就直接沿用。存量 Promtail 环境是否迁移,则要看兼容版本、停采窗口、历史日志是否需要回填,以及两边位置状态不能直接互换这一点。
迁移时不要让 Promtail 和 Alloy 长时间同时采集同一目录。更稳妥的顺序是停止旧 Agent、部署并启动新 Agent、写入唯一测试标记、确认标签和查询结果,再决定是否结束旧流程。替换配置不会自动完成历史日志迁移,是否回填需要按具体方案验证。
修好后怎样确认恢复,重启后数据还在不在?
先写入一条带唯一标记的日志,确保文件名符合当前 glob:
marker="loki-check-$(date +%s)-$$"
printf '%s %s\n' "$(date -Is)" "$marker" \
| tee -a /srv/app/logs/loki-check.log
printf 'marker=%s\n' "$marker"
先确认 Agent 容器能列出这条文件:
docker compose exec promtail ls -ln /srv/app/logs/loki-check.log
然后直接向 Loki 查询,验证的是“采集并推送”而不是 Grafana 界面:
curl -G 'http://<LOIKI_HOST>:3100/loki/api/v1/query_range' \
--data-urlencode 'query={server="web-01",job="app"} |= "loki-check-实际标记"' \
--data-urlencode "start=$(date -d '10 minutes ago' +%s)000" \
--data-urlencode "end=$(date +%s)000" \
--data-urlencode 'limit=100'
上面命令中的 Loki 地址、标签和标记都要替换为实际值。Loki API 能查到后,再到 Grafana 使用相同标签和时间范围查询,才能把“采集端正常”和“查询端正常”分别确认。
配置修改后先检查 Compose:
docker compose config
如果只修改了挂载进容器的配置文件,原进程需要重新启动才会重新读取配置;docker compose restart <服务名> 可以重启现有容器。如果修改了镜像、启动命令、环境变量或挂载定义,应执行:
docker compose up -d
Compose 会按新的服务定义重建需要变化的容器。需要排除缓存或强制使用新容器时,再使用:
docker compose up -d --force-recreate
restart 不会完整应用已经变更的 Compose 服务定义,所以它不能替代镜像、挂载和启动参数变更后的重新创建。
Loki 的实际存储目录和 Grafana 数据目录都应使用持久化挂载,例如:
volumes:
- ./loki-data:/tmp/loki
- ./grafana-data:/var/lib/grafana
其中 Loki 路径仍要与配置一致。持久化目录是否保留数据与容器是否重建是两回事;升级镜像前还要核对配置兼容性,并检查磁盘水位、备份和回滚方式。
反复重启不能替代逐段排查。日志链路必须按“Agent 读文件、Agent 推 Loki、Grafana 查标签”逐段验收;哪一段没有证据,就先检查并修复哪一段。










暂无评论内容