使用 Stackdriver 生成日志

在阅读这篇文档之前,强烈建议你先熟悉一下 Kubernetes 日志概况

说明: 默认情况下,Stackdriver 日志机制仅收集容器的标准输出和标准错误流。 如果要收集你的应用程序写入一个文件(例如)的任何日志,请参见 Kubernetes 日志概述中的 sidecar 方式

部署

为了接收日志,你必须将 Stackdriver 日志代理部署到集群中的每个节点。 此代理是一个已配置的 fluentd,其配置存在一个 ConfigMap 中,且实例使用 Kubernetes 的 DaemonSet 进行管理。 ConfigMapDaemonSet 的实际部署,取决你的集群设置。

部署到一个新的集群

Google Kubernetes Engine

对于部署在 Google Kubernetes Engine 上的集群,Stackdriver 是默认的日志解决方案。 Stackdriver 日志机制会默认部署到你的新集群上,除非你明确地不选择。

其他平台

为了将 Stackdriver 日志机制部署到你正在使用 kube-up.sh 创建的集群上,执行如下操作:

  1. 设置环境变量 KUBE_LOGGING_DESTINATIONgcp
  2. 如果不是跑在 GCE 上,在 KUBE_NODE_LABELS 变量中包含 beta.kubernetes.io/fluentd-ds-ready=true

集群启动后,每个节点都应该运行 Stackdriver 日志代理。 DaemonSetConfigMap 作为附加组件进行配置。 如果你不是使用 kube-up.sh,可以考虑不使用预先配置的日志方案启动集群,然后部署 Stackdriver 日志代理到正在运行的集群。

警告: 除了 Google Kubernetes Engine,Stackdriver 日志守护进程在其他的平台有已知的问题。 请自行承担风险。

部署到一个已知集群

  1. 在每个节点上打标签(如果尚未存在)

    Stackdriver 日志代理部署使用节点标签来确定应该将其分配到给哪些节点。 引入这些标签是为了区分 Kubernetes 1.6 或更高版本的节点。 如果集群是在配置了 Stackdriver 日志机制的情况下创建的,并且节点的版本为 1.5.X 或更低版本,则它将使用 fluentd 用作静态容器。 节点最多只能有一个 fluentd 实例,因此只能将标签打在未分配过 fluentd pod 的节点上。 你可以通过运行 kubectl describe 来确保你的节点被正确标记,如下所示:

    kubectl describe node $NODE_NAME
    

    输出应类似于如下内容:

    Name:           NODE_NAME
    Role:
    Labels:         beta.kubernetes.io/fluentd-ds-ready=true
    ...
    

    确保输出内容包含 beta.kubernetes.io/fluentd-ds-ready=true 标签。 如果不存在,则可以使用 kubectl label 命令添加,如下所示:

    kubectl label node $NODE_NAME beta.kubernetes.io/fluentd-ds-ready=true
    
    说明: 如果节点发生故障并且必须重新创建,则必须将标签重新打在重新创建了的节点。 为了让此操作更便捷,你可以在节点启动脚本中使用 Kubelet 的命令行参数给节点添加标签。
  1. 通过运行以下命令,部署一个带有日志代理配置的 ConfigMap

    kubectl apply -f https://k8s.io/examples/debug/fluentd-gcp-configmap.yaml
    

    该命令在 default 命名空间中创建 ConfigMap。你可以在创建 ConfigMap 对象之前手动下载文件并进行更改。

  1. 通过运行以下命令,部署日志代理的 DaemonSet

    kubectl apply -f https://k8s.io/examples/debug/fluentd-gcp-ds.yaml
    

    你也可以在使用前下载和编辑此文件。

验证日志代理部署

部署 Stackdriver DaemonSet 之后,你可以通过运行以下命令来查看日志代理的部署状态:

kubectl get ds --all-namespaces

如果你的集群中有 3 个节点,则输出应类似于如下:

NAMESPACE     NAME               DESIRED   CURRENT   READY     NODE-SELECTOR                              AGE
...
default       fluentd-gcp-v2.0   3         3         3         beta.kubernetes.io/fluentd-ds-ready=true   5m
...

要了解使用 Stackdriver 进行日志记录的工作方式,请考虑以下具有日志生成的 pod 定义 counter-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args: [/bin/sh, -c,
            'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

这个 pod 定义里有一个容器,该容器运行一个 bash 脚本,脚本每秒写一次计数器的值和日期时间,并无限期地运行。 让我们在默认命名空间中创建此 pod。

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

你可以观察到正在运行的 pod:

kubectl get pods
NAME                                           READY     STATUS    RESTARTS   AGE
counter                                        1/1       Running   0          5m

在短时间内,你可以观察到 "pending" 的 pod 的状态,因为 kubelet 必须先下载容器镜像。 当 pod 状态变为 Running 时,你可以使用 kubectl logs 命令查看此 counter pod 的输出。

kubectl logs counter
0: Mon Jan  1 00:00:00 UTC 2001
1: Mon Jan  1 00:00:01 UTC 2001
2: Mon Jan  1 00:00:02 UTC 2001
...

正如日志概览所述,此命令从容器日志文件中获取日志项。 如果该容器被 Kubernetes 杀死然后重新启动,你仍然可以访问前一个容器的日志。 但是,如果将 Pod 从节点中驱逐,则日志文件会丢失。让我们通过删除当前运行的 counter 容器来演示这一点:

kubectl delete pod counter
pod "counter" deleted

然后重建它:

kubectl create -f https://k8s.io/examples/debug/counter-pod.yaml
pod/counter created

一段时间后,你可以再次从 counter pod 访问日志:

kubectl logs counter
0: Mon Jan  1 00:01:00 UTC 2001
1: Mon Jan  1 00:01:01 UTC 2001
2: Mon Jan  1 00:01:02 UTC 2001
...

如预期的那样,日志中仅出现最近的日志记录。 但是,对于实际应用程序,你可能希望能够访问所有容器的日志,特别是出于调试的目的。 这就是先前启用的 Stackdriver 日志机制可以提供帮助的地方。

查看日志

Stackdriver 日志代理为每个日志项关联元数据,供你在后续的查询中只选择感兴趣的消息: 例如,来自某个特定 Pod 的消息。

元数据最重要的部分是资源类型和日志名称。 容器日志的资源类型为 container,在用户界面中名为 GKE Containers(即使 Kubernetes 集群不在 Google Kubernetes Engine 上)。 日志名称是容器的名称,因此,如果你有一个包含两个容器的 pod,在 spec 中名称定义为 container_1container_2,则它们的日志的名称分别为 container_1container_2

系统组件的资源类型为 compute,在接口中名为 GCE VM Instance。 系统组件的日志名称是固定的。 对于 Google Kubernetes Engine 节点,系统组件中的每个日志项都具有以下日志名称之一:

  • docker
  • kubelet
  • kube-proxy

你可以在专用 Stackdriver 页面上了解有关查看日志的更多信息。

查看日志的一种可能方法是使用 Google Cloud SDK 中的 gcloud logging 命令行接口。 它使用 Stackdriver 日志机制的过滤语法查询特定日志。 例如,你可以运行以下命令:

gcloud beta logging read 'logName="projects/$YOUR_PROJECT_ID/logs/count"' --format json | jq '.[].textPayload'
...
"2: Mon Jan  1 00:01:02 UTC 2001\n"
"1: Mon Jan  1 00:01:01 UTC 2001\n"
"0: Mon Jan  1 00:01:00 UTC 2001\n"
...
"2: Mon Jan  1 00:00:02 UTC 2001\n"
"1: Mon Jan  1 00:00:01 UTC 2001\n"
"0: Mon Jan  1 00:00:00 UTC 2001\n"

如你所见,尽管 kubelet 已经删除了第一个容器的日志,日志中仍会包含 counter 容器第一次和第二次运行时输出的消息。

导出日志

你可以将日志导出到 Google Cloud StorageBigQuery 进行进一步的分析。 Stackdriver 日志机制提供了接收器(Sink)的概念,你可以在其中指定日志项的存放地。 可在 Stackdriver 导出日志页面上获得更多信息。

配置 Stackdriver 日志代理

有时默认的 Stackdriver 日志机制安装可能无法满足你的需求,例如:

  • 你可能需要添加更多资源,因为默认的行为表现无法满足你的需求。
  • 你可能需要引入额外的解析机制以便从日志消息中提取更多元数据,例如严重性或源代码引用。
  • 你可能想要将日志不仅仅发送到 Stackdriver 或仅将部分日志发送到 Stackdriver。

在这种情况下,你需要更改 DaemonSetConfigMap 的参数。

先决条件

如果使用的是 GKE,并且集群中启用了 Stackdriver 日志机制,则无法更改其配置,因为它是由 GKE 管理和支持的。 但是,你可以禁用默认集成的日志机制并部署自己的。

说明: 你将需要自己支持和维护新部署的配置了:更新映像和配置、调整资源等等。

若要禁用默认的日志记录集成,请使用以下命令:

gcloud beta container clusters update --logging-service=none CLUSTER

你可以在部署部分中找到有关如何将 Stackdriver 日志代理安装到正在运行的集群中的说明​​。

更改 DaemonSet 参数

当集群中有 Stackdriver 日志机制的 DaemonSet 时,你只需修改其 spec 中的 template 字段,daemonset 控制器将为你更新 pod。 例如,假设你按照上面的描述已经安装了 Stackdriver 日志机制。 现在,你想更改内存限制,来给 fluentd 提供的更多内存,从而安全地处理更多日志。

获取集群中运行的 DaemonSet 的 spec:

kubectl get ds fluentd-gcp-v2.0 --namespace kube-system -o yaml > fluentd-gcp-ds.yaml

然后在 spec 文件中编辑资源需求,并使用以下命令更新 apiserver 中的 DaemonSet 对象:

kubectl replace -f fluentd-gcp-ds.yaml

一段时间后,Stackdriver 日志代理的 pod 将使用新配置重新启动。

更改 fluentd 参数

Fluentd 的配置存在 ConfigMap 对象中。 它实际上是一组合并在一起的配置文件。 你可以在官方网站上了解 fluentd 的配置。

假设你要向配置添加新的解析逻辑,以便 fluentd 可以理解默认的 Python 日志记录格式。 一个合适的 fluentd 过滤器类似如下:

<filter reform.**>
  type parser
  format /^(?<severity>\w):(?<logger_name>\w):(?<log>.*)/
  reserve_data true
  suppress_parse_error_log true
  key_name log
</filter>

现在,你需要将其放入配置中,并使 Stackdriver 日志代理感知它。 通过运行以下命令,获取集群中当前版本的 Stackdriver 日志机制的 ConfigMap

kubectl get cm fluentd-gcp-config --namespace kube-system -o yaml > fluentd-gcp-configmap.yaml

然后在 containers.input.conf 键的值中,在 source 部分之后插入一个新的过滤器。

说明: 顺序很重要。

在 apiserver 中更新 ConfigMap 比更新 DaemonSet 更复杂。 最好考虑 ConfigMap 是不可变的。 如果是这样,要更新配置,你应该使用新名称创建 ConfigMap,然后使用 上面的指南DaemonSet 更改为指向它。

添加 fluentd 插件

Fluentd 用 Ruby 编写,并允许使用 plugins 扩展其功能。 如果要使用默认的 Stackdriver 日志机制容器镜像中未包含的插件,则必须构建自定义镜像。 假设你要为来自特定容器添加 Kafka 信息接收器,以进行其他处理。 你可以复用默认的容器镜像源,并仅添加少量更改:

  • 将 Makefile 更改为指向你的容器仓库,例如 PREFIX=gcr.io/<your-project-id>
  • 将你的依赖项添加到 Gemfile 中,例如 gem 'fluent-plugin-kafka'

然后在该目录运行 make build push。在更新 DaemonSet 以使用新镜像后,你就可以使用在 fluentd 配置中安装的插件了。