一个在Kubernetes中运行的容器如何读取自己的日志?

huangapple go评论90阅读模式
英文:

How can a container running in k8s read its own logs?

问题

我有一个设计为在K8s应用程序中运行的应用程序,并且它导入了一些我不拥有的依赖项,这些依赖项运行exec.Cmd。这没问题,但是我想捕获这些日志。但是当我这样做时,会抛出一个错误:

r := bufio.NewReader(os.Stdout)

...

line, err := r.ReadString('\n')

错误显示/dev/stdout是一个bad file descriptor。这是怎么回事?难道它不是用于控制台输出的标准本地目标吗?

kubectl logs似乎能够捕获输出,而且更具体地说,我们的中央日志转发器也能够捕获它。但是尝试从实际生成这些日志的容器内的kube API服务器捕获日志似乎有点愚蠢...有没有更好的方法来做到这一点?

英文:

I have an application designed to run as a K8s application, and it imports some dependencies (that I don't own) that run exec.Cmds. This is fine, except I want to capture those logs. For some reason, when I do:

r := bufio.NewReader(os.Stdout)

...

line, err := r.ReadString('\n')

An error is thrown saying that /dev/stdout is a bad file descriptor. How can this be? Isn't that the standard local destination for console output?

kubectl logs seems to be able to capture the output, and more specifically, our central log forwarder is able to capture it as well. But trying to capture logs from the kube API server inside the container that's actually generating those logs seems kinda silly... Is there a better way to do this?

答案1

得分: 1

通常情况下,stdin是一个只读流,用于获取写入程序的输入,而stdout是一个只写流,用于发送程序写入的输出。换句话说,除了Chuck Norris之外,没有人可以从/dev/stdout读取。

默认情况下,stdout指向你的终端。但是可以将stdout从终端重定向到文件中。这个重定向是在启动程序之前设置的。

通常发生的情况是:容器运行时将容器进程的stdout重定向到运行容器的节点上的一个文件中(例如/var/log/containers/<container-name>-<container-id>.log)。当你使用kubectl logs请求日志时,kubectl连接到kube-apiserver,kube-apiserver连接到运行容器的节点上的kubelet,并要求kubelet将日志文件的内容发送回来。

还可以参考https://kubernetes.io/docs/concepts/cluster-administration/logging/,该文档解释了各种日志记录设计方法。


从安全性和可移植性的角度来看,一个解决方案是在容器中添加一个hostPath挂载,将节点上的/var/log/containers目录挂载到容器中,并直接访问容器日志。


一个正确的解决方案可能是更改镜像的命令,并将输出写入容器的stdout以及容器内的一个本地文件。可以使用tee命令来实现这一点。然后,你的应用程序可以从该文件中读取日志。但要记住,如果没有适当的轮换,日志文件将会增长,直到容器被终止。

apiVersion: v1
kind: Pod
metadata:
  name: log-to-stdout-and-file
spec:
  containers:
  - image: bash:latest
    name: log-to-stdout-and-file
    command: 
    - bash
    - -c
    - '(while true; do date; sleep 10; done) | tee /tmp/test.log';

一个稍微复杂一些的解决方案是,使用mkfifo创建一个命名管道文件,将容器中的日志文件替换为该命名管道文件。这样可以避免文件大小增长的问题(只要你的应用程序不断从命名管道文件中读取日志)。

apiVersion: v1
kind: Pod
metadata:
  name: log-to-stdout-and-file
spec:
  # init容器在一个空目录挂载中创建fifo
  initContainers:
  - image: bash:latest
    name: create-fifo
    command: 
    - bash
    - -c
    - mkfifo /var/log/myapp/log
    volumeMounts:
    - name: ed
      mountPath: /var/log/myapp

  # 实际的应用程序使用tee将日志写入stdout和fifo
  containers:
  - image: bash:latest
    name: log-to-stdout-and-fifo
    command: 
    - bash
    - -c
    - '(while true; do date; sleep 10; done) | tee /var/log/myapp/log'
    volumeMounts:
    - name: ed
      mountPath: /var/log/myapp

  # 这个sidecar容器仅用于测试目的,它读取写入fifo的内容(通常由应用程序自己完成)
  #- image: bash:latest
  #  name: log-reader
  #  command: 
  #  - bash
  #  - -c
  #  - cat /var/log/myapp/log
  #  volumeMounts:
  #  - name: ed
  #    mountPath: /var/log/myapp

  volumes:
  - name: ed
    emptyDir: {}

希望这些信息对你有所帮助!

英文:

Generally, stdin is a read-only stream for retrieving input written to your program, while stdout is a write-only stream for sending output written by your program. In other words, nobody can read from /dev/stdout, except Chuck Norris.

By default, stdout is "pointing" to your terminal. But it is possible to redirect stdout from your terminal to a file. This redirection is set up before your program is started.

What usually happens, is the following: The container runtime redirects stdout of the process of your container to a file on the node where your container is running (e.g., /var/log/containers/&lt;container-name&gt;-&lt;container-id&gt;.log). When you request logs with kubectl logs, kubectl connects to kube-apiserver, which connects to the kubelet on the node running your container and asks it to send back the content from the log file.

Also take a look at https://kubernetes.io/docs/concepts/cluster-administration/logging/ which explains the various logging design approaches.


A solution, which from a security and portability perspective you would definitely NOT implement, is to add a hostPath mount in your container mounting the /var/log/containers directory of your node and to access the container log directly.


A proper solution might be to change the command of your image and to write output to stdout of your container and also to a local file within your container. This can be achieved using the tee command. Your application can then read back the log from this file. But keep in mind, that without proper rotation, the log file will grow until your container is terminated.

apiVersion: v1
kind: Pod
metadata:
  name: log-to-stdout-and-file
spec:
  containers:
  - image: bash:latest
    name: log-to-stdout-and-file
    command: 
    - bash
    - -c
    - &#39;(while true; do date; sleep 10; done) | tee /tmp/test.log&#39;

A little more complex solution would be, to replace the log file in the container with a named pipe file created with mkfifo. This avoids the growing file size problem (as long as your application is continuously reading the log from the named pipe file).

apiVersion: v1
kind: Pod
metadata:
  name: log-to-stdout-and-file
spec:
  # the init container creates the fifo in an empty dir mount
  initContainers:
  - image: bash:latest
    name: create-fifo
    command: 
    - bash
    - -c
    - mkfifo /var/log/myapp/log
    volumeMounts:
    - name: ed
      mountPath: /var/log/myapp

  # the actual app uses tee to write the log to stdout and to the fifo
  containers:
  - image: bash:latest
    name: log-to-stdout-and-fifo
    command: 
    - bash
    - -c
    - &#39;(while true; do date; sleep 10; done) | tee /var/log/myapp/log&#39;
    volumeMounts:
    - name: ed
      mountPath: /var/log/myapp

  # this sidecar container is only for testing purposes, it reads the
  # content written to the fifo (this is usually done by the app itself)
  #- image: bash:latest
  #  name: log-reader
  #  command: 
  #  - bash
  #  - -c
  #  - cat /var/log/myapp/log
  #  volumeMounts:
  #  - name: ed
  #    mountPath: /var/log/myapp

  volumes:
  - name: ed
    emptyDir: {}

答案2

得分: 0

你应该考虑使用多容器 Pod,其中包括一个主要/应用容器和一个作为 sidecar 容器的日志容器,用于读取主要/应用容器的日志。可以参考下面链接中的示例,了解如何从主要容器中获取日志。

https://learnk8s.io/sidecar-containers-patterns

英文:

You should consider multi container pod with main/app container and log container as sidecar container reading the logs from main/app container. conside r the example from the below link showing how to tail logs from main container

https://learnk8s.io/sidecar-containers-patterns

huangapple
  • 本文由 发表于 2022年10月26日 09:33:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/74201777.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定