`docker compose up -d` 后日志采不到怎么办

结论先说: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 查标签”逐段验收;哪一段没有证据,就先检查并修复哪一段。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容