在Docker容器内向journald进行结构化日志记录

5
什么是从docker容器内向journald写入结构化日志的最佳方法?
例如,我有一个使用sd_journal_send进行写入的应用程序。而不是更改应用程序,我尝试通过-v /var/log/systemd/journal:/var/log/systemd/journal进行传递。
它在我的Ubuntu 16.04桌面上运行良好,但在使用Ubuntu 16.04基础镜像的CoreOS实例上没有运行。 我不太明白为什么。也许有更好的发送到journal的方法?
Docker journald输出日志选项有哪些限制?它似乎不支持应用程序编写除消息字段之外的内容。
所以我发现我需要 -v /dev/log:/dev/log
但还存在另一个问题,即没有与启动docker容器的服务文件关联。手动添加UNIT:servicename.service并未解决问题。因此,在查看和运输服务日志时,它与exe关联而不是容器或服务。谁遇到了这些问题,你是如何解决的?
扩展一下,C程序可以像这样写入systemd日志:
#include <systemd/sd-journal.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
        sd_journal_send("MESSAGE=Hello World!",
                        "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555",
                        "PRIORITY=5",
                        "HOME=%s", getenv("HOME"),
                        "TERM=%s", getenv("TERM"),
                        "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE),
                        "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN),
                        NULL);
        return 0;
}

这将写入日志并添加自定义字段,如HOME、TERM、PAGE_SIZE等。当我使用journalbeat将它们发送到ELK堆栈时,这些字段会很好地出现在elasticsearch中,我可以直接搜索它们。

然而,似乎docker只是简单地获取应用程序的stdout,并将其提供给journald,并仅添加了一些自己的字段,例如CONTAINER_ID。

当在docker容器中使用此类程序,然后从服务文件运行它们时,会产生一些问题。

1)我必须通过一些目录和设备文件来使其使用sd_journal_send进行写入。

2)如果您从systemd .service文件启动容器,并希望使用journalctl -u servicename查看消息,则不会看到这些日志消息,因为它们通过不同的路径进入日志,且与运行它们的服务无关联。

3)您可以使用docker的journald日志记录驱动程序添加一些任意字段/标签,但它们是固定的、一次性的添加,将出现在每个发送的消息中,且不会发生变化。它们不像上面C代码中想要的动态字段。

本质上,journald日志驱动程序在我的情况下是不够的。

有没有建议如何链接服务名称,以便journalctl -u显示来自sd_journal_send的日志消息?因为这样就可以解决问题了。

-- 我已经找到了一个解决方案。如果其他人对我是如何解决它感兴趣,我将在下面提供答案。


我可能没有表达清楚。我需要在同一单元内添加自己的字段并将其绑定在一起。当从systemd服务文件启动docker容器应用程序时,基本上journalctl -u会将其视为另一个单元。Docker是问题所在。我将不得不深入源代码并查看docker中sysyemd日志驱动程序支持的内容。可能需要进行扩展。 - hookenz
这个有帮助吗?https://unix.stackexchange.com/questions/332274/is-systemd-journald-a-syslog-implementation? - Tarun Lalwani
@TarunLalwani - 不是的。 - hookenz
我该如何在Ubuntu 16.04上编译这个程序?我得到了以下错误信息:vagrant@vagrant:~/journald$ gcc test.c /tmp/ccLHPQPH.o: In function main': test.c:(.text+0x8b): undefined reference to sd_journal_send_with_location' collect2: error: ld returned 1 exit status - Tarun Lalwani
我正在探索另一种选择。看起来logstash可以将JSON数据放入任意字段(例如MESSAGE)中,并在提交到elasticsearch之前将其分解为字段。这对我来说是个解决办法。 - hookenz
显示剩余3条评论
2个回答

5

您需要挂载 journald 监听的套接字。在 Ubuntu 的情况下,它位于 /run/systemd/journal/socket。将其映射到 Docker 容器中,它就可以正常工作了。

通过对您的示例代码使用 strace 弄清楚了这一点。

sendmsg(3, {msg_name(29)={sa_family=AF_LOCAL, sun_path="/run/systemd/journal/socket"}, 
msg_iov(23)=[{"CODE_FILE=test.c", 16}, {"\n", 1}, {"CODE_LINE=13", 12}, {"\n", 1}, {"CODE_FUNC=main", 14}, {"\n", 1}, 
{"MESSAGE=Hello World!", 20}, {"\n", 1}, {"MESSAGE_ID=52fb62f99e2c49d89cfbf"..., 43}, {"\n", 1}, {"PRIORITY=5", 10}, {"\n", 1}, 
{"HOME=/home/vagrant", 18}, {"\n", 1}, {"TERM=xterm-256color", 19}, {"\n", 1}, {"PAGE_SIZE=4096", 14}, {"\n", 1}, 
{"N_CPUS=1", 8}, {"\n", 1}, {"SYSLOG_IDENTIFIER=", 18}, {"a.out", 5}, {"\n", 1}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 208

使用以下命令在Ubuntu Docker容器中进行测试:

docker run -v /run/systemd/journal/socket:/run/systemd/journal/socket -v $PWD:/jd -it -w /jd ubuntu:16.04 ./a.out

我在主机上使用 journalctl -f 命令时,可以看到一条记录。

Aug 15 21:40:33 vagrant a.out[11263]: Hello World!

是的,但当作为systemd服务启动时,它会失去与单元文件的关联。因此,journalbeat单元过滤器会丢弃这些消息。无论如何,我已经通过在logstash中使用json过滤器来解决了这个问题,请参见我的答案。 - hookenz

2
最终的解决方案非常简单。我改为将我的消息写成纯json格式,这样journalctl -u现在可以工作并显示MESSAGE字段,其中包含json数据。然后我使用journalbeat将其发送到logstash。在logstash.conf中,我添加了以下内容:
filter {
  json {
    source => "message"
  }
}

这个操作会在将数据发送到Elasticsearch之前,将信息字段中的JSON数据展开为顶级分隔字段。有关Logstash的JSON过滤器的详细信息,请查看此处

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接