日志架构

    但是,由容器引擎或运行时提供的原生功能通常不足以满足完整的日志记录方案。 例如,如果发生容器崩溃、Pod 被逐出或节点宕机等情况,你仍然想访问到应用日志。 因此,日志应该具有独立的存储和生命周期,与节点、Pod 或容器的生命周期相独立。 这个概念叫 集群级的日志 。集群级日志方案需要一个独立的后台来存储、分析和查询日志。 Kubernetes 没有为日志数据提供原生存储方案,但是你可以集成许多现有的日志解决方案到 Kubernetes 集群中。

    集群级日志架构假定在集群内部或者外部有一个日志后台。 如果你对集群级日志不感兴趣,你仍会发现关于如何在节点上存储和处理日志的描述对你是有用的。

    本节,你会看到一个kubernetes 中生成基本日志的例子,该例子中数据被写入到标准输出。 这里的示例为包含一个容器的 Pod 规约,该容器每秒钟向标准输出写入数据。

    用下面的命令运行 Pod:

    1. kubectl apply -f https://k8s.io/examples/debug/counter-pod.yaml

    输出结果为:

    1. pod/counter created

    使用 kubectl logs 命令获取日志:

    1. kubectl logs counter

    输出结果为:

    一旦发生容器崩溃,你可以使用命令 kubectl logs 和参数 --previous 检索之前的容器日志。 如果 pod 中有多个容器,你应该向该命令附加一个容器名以访问对应容器的日志。 详见 。

    节点级别的日志记录

    容器化应用写入 stdoutstderr 的任何数据,都会被容器引擎捕获并被重定向到某个位置。 例如,Docker 容器引擎将这两个输出流重定向到某个 日志驱动 , 该日志驱动在 Kubernetes 中配置为以 JSON 格式写入文件。

    默认情况下,如果容器重启,kubelet 会保留被终止的容器日志。 如果 Pod 在工作节点被驱逐,该 Pod 中所有的容器也会被驱逐,包括容器日志。

    节点级日志记录中,需要重点考虑实现日志的轮转,以此来保证日志不会消耗节点上所有的可用空间。 Kubernetes 当前并不负责轮转日志,而是通过部署工具建立一个解决问题的方案。 例如,在 Kubernetes 集群中,用 kube-up.sh 部署一个每小时运行的工具 。 你也可以设置容器 runtime 来自动地轮转应用日志,比如使用 Docker 的 log-opt 选项。 在 kube-up.sh 脚本中,使用后一种方式来处理 GCP 上的 COS 镜像,而使用前一种方式来处理其他环境。 这两种方式,默认日志超过 10MB 大小时都会触发日志轮转。

    例如,你可以找到关于 kube-up.sh 为 GCP 环境的 COS 镜像设置日志的详细信息, 相应的脚本在 这里

    当运行 时, 节点上的 kubelet 处理该请求并直接读取日志文件,同时在响应中返回日志文件内容。

    系统组件有两种类型:在容器中运行的和不在容器中运行的。例如:

    • 在容器中运行的 kube-scheduler 和 kube-proxy。
    • 不在容器中运行的 kubelet 和容器运行时(例如 Docker)。

    在使用 systemd 机制的服务器上,kubelet 和容器 runtime 写入日志到 journald。 如果没有 systemd,他们写入日志到 /var/log 目录的 .log 文件。 容器中的系统组件通常将日志写到 /var/log 目录,绕过了默认的日志机制。他们使用 klog 日志库。 你可以在找到这些组件的日志告警级别协议。

    和容器日志类似,/var/log 目录中的系统组件日志也应该被轮转。 通过脚本 kube-up.sh 启动的 Kubernetes 集群中,日志被工具 logrotate 执行每日轮转, 或者日志大小超过 100MB 时触发轮转。

    虽然Kubernetes没有为集群级日志记录提供原生的解决方案,但你可以考虑几种常见的方法。以下是一些选项:

    • 使用在每个节点上运行的节点级日志记录代理。
    • 在应用程序的 pod 中,包含专门记录日志的 sidecar 容器。
    • 将日志直接从应用程序中推送到日志记录后端。

    你可以通过在每个节点上使用 节点级的日志记录代理 来实现群集级日志记录。 日志记录代理是一种用于暴露日志或将日志推送到后端的专用工具。 通常,日志记录代理程序是一个容器,它可以访问包含该节点上所有应用程序容器的日志文件的目录。

    由于日志记录代理必须在每个节点上运行,它可以用 DaemonSet 副本,Pod 或 本机进程来实现。 然而,后两种方法被弃用并且非常不别推荐。

    对于 Kubernetes 集群来说,使用节点级的日志代理是最常用和被推荐的方式, 因为在每个节点上仅创建一个代理,并且不需要对节点上的应用做修改。 但是,节点级的日志 仅适用于应用程序的标准输出和标准错误输出

    Kubernetes 并不指定日志代理,但是有两个可选的日志代理与 Kubernetes 发行版一起发布。 Stackdriver 日志 适用于 Google Cloud Platform,和 。 你可以在专门的文档中找到更多的信息和说明。 两者都使用 fluentd 与自定义配置作为节点上的代理。

    你可以通过以下方式之一使用 sidecar 容器:

    • sidecar 容器将应用程序日志传送到自己的标准输出。
    • sidecar 容器运行一个日志代理,配置该日志代理以便从应用容器收集日志。

    传输数据流的 sidecar 容器

    数据流容器的 Sidecar 容器

    利用 sidecar 容器向自己的 stdoutstderr 传输流的方式, 你就可以利用每个节点上的 kubelet 和日志代理来处理日志。 sidecar 容器从文件、套接字或 journald 读取日志。 每个 sidecar 容器打印其自己的 stdoutstderr 流。

    这种方法允许你将日志流从应用程序的不同部分分离开,其中一些可能缺乏对写入 stdoutstderr 的支持。重定向日志背后的逻辑是最小的,因此它的开销几乎可以忽略不计。 另外,因为 stdoutstderr 由 kubelet 处理,你可以使用内置的工具 kubectl logs

    考虑接下来的例子。pod 的容器向两个文件写不同格式的日志,下面是这个 pod 的配置文件:

    admin/logging/two-files-counter-pod.yaml

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: counter
    5. spec:
    6. containers:
    7. - name: count
    8. image: busybox
    9. args:
    10. - /bin/sh
    11. - -c
    12. i=0;
    13. while true;
    14. do
    15. echo "$i: $(date)" >> /var/log/1.log;
    16. echo "$(date) INFO $i" >> /var/log/2.log;
    17. i=$((i+1));
    18. done
    19. volumeMounts:
    20. - name: varlog
    21. mountPath: /var/log
    22. volumes:
    23. - name: varlog
    24. emptyDir: {}

    在同一个日志流中有两种不同格式的日志条目,这有点混乱,即使你试图重定向它们到容器的 stdout 流。 取而代之的是,你可以引入两个 sidecar 容器。 每一个 sidecar 容器可以从共享卷跟踪特定的日志文件,并重定向文件内容到各自的 stdout 流。

    日志架构 - 图6

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: counter
    5. spec:
    6. containers:
    7. - name: count
    8. image: busybox
    9. args:
    10. - /bin/sh
    11. - -c
    12. - >
    13. i=0;
    14. while true;
    15. do
    16. echo "$i: $(date)" >> /var/log/1.log;
    17. echo "$(date) INFO $i" >> /var/log/2.log;
    18. i=$((i+1));
    19. sleep 1;
    20. done
    21. volumeMounts:
    22. - name: varlog
    23. mountPath: /var/log
    24. - name: count-log-1
    25. image: busybox
    26. args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
    27. volumeMounts:
    28. - name: varlog
    29. - name: count-log-2
    30. args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log']
    31. volumeMounts:
    32. - name: varlog
    33. mountPath: /var/log
    34. volumes:
    35. - name: varlog
    36. emptyDir: {}

    现在当你运行这个 Pod 时,你可以分别地访问每一个日志流,运行如下命令:

    1. kubectl logs counter count-log-1
    1. kubectl logs counter count-log-2
    1. Mon Jan 1 00:00:00 UTC 2001 INFO 0
    2. Mon Jan 1 00:00:01 UTC 2001 INFO 1
    3. Mon Jan 1 00:00:02 UTC 2001 INFO 2
    4. ...

    集群中安装的节点级代理会自动获取这些日志流,而无需进一步配置。如果你愿意,你可以配置代理程序来解析源容器的日志行。

    注意,尽管 CPU 和内存使用率都很低(以多个 cpu millicores 指标排序或者按内存的兆字节排序), 向文件写日志然后输出到 stdout 流仍然会成倍地增加磁盘使用率。 如果你的应用向单一文件写日志,通常最好设置 /dev/stdout 作为目标路径,而不是使用流式的 sidecar 容器方式。

    应用本身如果不具备轮转日志文件的功能,可以通过 sidecar 容器实现。 该方式的一个例子是运行一个定期轮转日志的容器。 然而,还是推荐直接使用 stdoutstderr,将日志的轮转和保留策略交给 kubelet。

    如果节点级日志记录代理程序对于你的场景来说不够灵活,你可以创建一个带有单独日志记录代理程序的 sidecar 容器,将代理程序专门配置为与你的应用程序一起运行。

    例如,你可以使用 Stackdriver, 它使用 fluentd 作为日志记录代理。 以下是两个可用于实现此方法的配置文件。 第一个文件包含配置 fluentd 的 。

    admin/logging/fluentd-sidecar-config.yaml 日志架构 - 图8

    1. apiVersion: v1
    2. kind: ConfigMap
    3. metadata:
    4. name: fluentd-config
    5. data:
    6. fluentd.conf: |
    7. <source>
    8. type tail
    9. format none
    10. path /var/log/1.log
    11. pos_file /var/log/1.log.pos
    12. tag count.format1
    13. </source>
    14. <source>
    15. type tail
    16. format none
    17. path /var/log/2.log
    18. pos_file /var/log/2.log.pos
    19. tag count.format2
    20. </source>
    21. <match **>
    22. type google_cloud

    第二个文件描述了运行 fluentd sidecar 容器的 Pod 。flutend 通过 Pod 的挂载卷获取它的配置数据。

    一段时间后,你可以在 Stackdriver 界面看到日志消息。

    记住,这只是一个例子,事实上你可以用任何一个日志代理替换 fluentd ,并从应用容器中读取任何资源。

    直接从应用程序暴露日志