This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
<audio id="audio" title="48 | Prometheus、Metrics Server与Kubernetes监控体系" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/75/79/75a1f40a2a9f78e948c578d1d954a879.mp3"></audio>
你好我是张磊。今天我和你分享的主题是Prometheus、Metrics Server与Kubernetes监控体系。
通过前面的文章我已经和你分享过了Kubernetes 的核心架构编排概念以及具体的设计与实现。接下来我会用3篇文章为你介绍 Kubernetes 监控相关的一些核心技术。
首先需要明确指出的是Kubernetes 项目的监控体系曾经非常繁杂在社区中也有很多方案。但这套体系发展到今天已经完全演变成了以Prometheus 项目为核心的一套统一的方案。
在这里,可能有一些同学对 Prometheus 项目还太不熟悉。所以,我先来简单为你介绍一下这个项目。
实际上Prometheus 项目是当年 CNCF 基金会起家时的“第二把交椅”。而这个项目发展到今天,已经全面接管了 Kubernetes 项目的整套监控体系。
比较有意思的是Prometheus项目与 Kubernetes 项目一样,也来自于 Google 的 Borg 体系它的原型系统叫作BorgMon是一个几乎与 Borg 同时诞生的内部监控系统。而 Prometheus 项目的发起原因也跟 Kubernetes 很类似,都是希望通过对用户更友好的方式,将 Google 内部系统的设计理念,传递给用户和开发者。
作为一个监控系统Prometheus 项目的作用和工作方式,其实可以用如下所示的一张官方示意图来解释。
<img src="https://static001.geekbang.org/resource/image/2a/d3/2ada1ece66fcc81d704c2ba46f9dd7d3.png" alt=""><br>
可以看到Prometheus 项目工作的核心,是使用 Pull (抓取)的方式去搜集被监控对象的 Metrics 数据(监控指标数据),然后,再把这些数据保存在一个 TSDB (时间序列数据库,比如 OpenTSDB、InfluxDB 等)当中,以便后续可以按照时间进行检索。
有了这套核心监控机制, Prometheus 剩下的组件就是用来配合这套机制的运行。比如 Pushgateway可以允许被监控对象以Push 的方式向 Prometheus 推送 Metrics 数据。而Alertmanager则可以根据 Metrics 信息灵活地设置报警。当然, Prometheus 最受用户欢迎的功能,还是通过 Grafana 对外暴露出的、可以灵活配置的监控数据可视化界面。
有了 Prometheus 之后我们就可以按照Metrics 数据的来源,来对 Kubernetes 的监控体系做一个汇总了。
**第一种 Metrics是宿主机的监控数据。**这部分数据的提供,需要借助一个由 Prometheus 维护的[Node Exporter](https://github.com/prometheus/node_exporter) 工具。一般来说Node Exporter 会以 DaemonSet 的方式运行在宿主机上。其实,所谓的 Exporter就是代替被监控对象来对 Prometheus 暴露出可以被“抓取”的 Metrics 信息的一个辅助进程。
而 Node Exporter 可以暴露给 Prometheus 采集的Metrics 数据, 也不单单是节点的负载Load、CPU 、内存、磁盘以及网络这样的常规信息,它的 Metrics 指标可以说是“包罗万象”,你可以查看[这个列表](https://github.com/prometheus/node_exporter#enabled-by-default)来感受一下。
**第二种 Metrics是来自于 Kubernetes 的 API Server、kubelet 等组件的/metrics API**。除了常规的 CPU、内存的信息外这部分信息还主要包括了各个组件的核心监控指标。比如对于 API Server 来说,它就会在/metrics API 里,暴露出各个 Controller 的工作队列Work Queue的长度、请求的 QPS 和延迟数据等等。这些信息,是检查 Kubernetes 本身工作情况的主要依据。
**第三种 Metrics是 Kubernetes 相关的监控数据。**这部分数据,一般叫作 Kubernetes 核心监控数据core metrics。这其中包括了 Pod、Node、容器、Service 等主要 Kubernetes 核心概念的 Metrics。
其中,容器相关的 Metrics 主要来自于 kubelet 内置的 cAdvisor 服务。在 kubelet 启动后cAdvisor 服务也随之启动而它能够提供的信息可以细化到每一个容器的CPU 、文件系统、内存、网络等资源的使用情况。
需要注意的是,这里提到的 Kubernetes 核心监控数据,其实使用的是 Kubernetes 的一个非常重要的扩展能力,叫作 Metrics Server。
Metrics Server 在 Kubernetes 社区的定位,其实是用来取代 Heapster 这个项目的。在 Kubernetes 项目发展的初期Heapster 是用户获取 Kubernetes 监控数据(比如 Pod 和 Node的资源使用情况 的主要渠道。而后面提出来的 Metrics Server则把这些信息通过标准的 Kubernetes API 暴露了出来。这样Metrics 信息就跟 Heapster 完成了解耦,允许 Heapster 项目慢慢退出舞台。
而有了 Metrics Server 之后,用户就可以通过标准的 Kubernetes API 来访问到这些监控数据了。比如,下面这个 URL
```
http://127.0.0.1:8001/apis/metrics.k8s.io/v1beta1/namespaces/&lt;namespace-name&gt;/pods/&lt;pod-name&gt;
```
当你访问这个 Metrics API时它就会为你返回一个 Pod 的监控数据,而这些数据,其实是从 kubelet 的 Summary API (即&lt;kubelet_ip&gt;:&lt;kubelet_port&gt;/stats/summary采集而来的。Summary API 返回的信息,既包括了 cAdVisor的监控数据也包括了 kubelet 本身汇总的信息。
需要指出的是, Metrics Server 并不是 kube-apiserver 的一部分,而是通过 Aggregator 这种插件机制,在独立部署的情况下同 kube-apiserver 一起统一对外服务的。
这里Aggregator APIServer 的工作原理,可以用如下所示的一幅示意图来表示清楚:
<img src="https://static001.geekbang.org/resource/image/0b/09/0b767b5224ad1906ddc4cce075618809.png" alt="">
>
备注:图片出处[https://blog.jetstack.io/blog/resource-and-custom-metrics-hpa-v2/](https://blog.jetstack.io/blog/resource-and-custom-metrics-hpa-v2/)
可以看到当Kubernetes 的 API Server 开启了 Aggregator 模式之后你再访问apis/metrics.k8s.io/v1beta1的时候实际上访问到的是一个叫作kube-aggregator 的代理。而kube-apiserver正是这个代理的一个后端而 Metrics Server则是另一个后端。
而且,在这个机制下,你还可以添加更多的后端给这个 kube-aggregator。所以**kube-aggregator其实就是一个根据 URL 选择具体的 API 后端的代理服务器。**通过这种方式,我们就可以很方便地扩展 Kubernetes 的 API 了。
而 Aggregator 模式的开启也非常简单:
- 如果你是使用 kubeadm 或者[官方的kube-up.sh 脚本](https://github.com/kubernetes/kubernetes/blob/master/cluster/kube-up.sh)部署Kubernetes集群的话Aggregator 模式就是默认开启的;
- 如果是手动 DIY 搭建的话,你就需要在 kube-apiserver 的启动参数里加上如下所示的配置:
```
--requestheader-client-ca-file=&lt;path to aggregator CA cert&gt;
--requestheader-allowed-names=front-proxy-client
--requestheader-extra-headers-prefix=X-Remote-Extra-
--requestheader-group-headers=X-Remote-Group
--requestheader-username-headers=X-Remote-User
--proxy-client-cert-file=&lt;path to aggregator proxy cert&gt;
--proxy-client-key-file=&lt;path to aggregator proxy key&gt;
```
而这些配置的作用,主要就是为 Aggregator 这一层设置对应的 Key 和 Cert 文件。而这些文件的生成,就需要你自己手动完成了,具体流程请参考这篇[官方文档](https://github.com/kubernetes-incubator/apiserver-builder/blob/master/docs/concepts/auth.md)。
Aggregator 功能开启之后,你只需要将 Metrics Server 的 YAML 文件部署起来,如下所示:
```
$ git clone https://github.com/kubernetes-incubator/metrics-server
$ cd metrics-server
$ kubectl create -f deploy/1.8+/
```
接下来你就会看到metrics.k8s.io这个API 出现在了你的 Kubernetes API 列表当中。
在理解了Prometheus 关心的三种监控数据源,以及 Kubernetes 的核心 Metrics 之后,作为用户,你其实要做的就是将 Prometheus Operator 在 Kubernetes 集群里部署起来。然后按照本篇文章一开始介绍的架构把上述Metrics 源配置起来,让 Prometheus 自己去进行采集即可。
在后续的文章中,我会为你进一步剖析 Kubernetes 监控体系以及自定义Metrics (自定义监控指标)的具体技术点。
## 总结
在本篇文章中,我主要为你介绍了 Kubernetes 当前监控体系的设计,介绍了 Prometheus 项目在这套体系中的地位,讲解了以 Prometheus 为核心的监控系统的架构设计。
然后,我为你详细地解读了 Kubernetes 核心监控数据的来源Metrics Server的具体工作原理以及 Aggregator APIServer 的设计思路。
通过以上讲述,我希望你能够对 Kubernetes 的监控体系形成一个整体的认知,体会到 Kubernetes 社区在监控这个事情上,全面以 Prometheus 项目为核心进行建设的大方向。
最后,在具体的监控指标规划上,我建议你**遵循业界通用的 USE 原则和 RED 原则。**
其中USE 原则指的是,按照如下三个维度来规划资源监控指标:
<li>
利用率Utilization资源被有效利用起来提供服务的平均时间占比
</li>
<li>
饱和度Saturation资源拥挤的程度比如工作队列的长度
</li>
<li>
错误率Errors错误的数量。
</li>
而 RED 原则指的是,按照如下三个维度来规划服务监控指标:
<li>
每秒请求数量Rate
</li>
<li>
每秒错误数量Errors
</li>
<li>
服务响应时间Duration
</li>
不难发现, USE 原则主要关注的是“资源”,比如节点和容器的资源使用情况,而 RED 原则主要关注的是“服务”,比如 kube-apiserver 或者某个应用的工作情况。这两种指标,在我今天为你讲解的 Kubernetes + Prometheus 组成的监控体系中,都是可以完全覆盖到的。
## 思考题
在监控体系中,对于数据的采集,其实既有 Prometheus 这种 Pull 模式,也有 Push 模式。请问,你如何看待这两种模式的异同和优缺点呢?
感谢你的收听,欢迎你给我留言,也欢迎分享给更多的朋友一起阅读。

View File

@@ -0,0 +1,225 @@
<audio id="audio" title="49 | Custom Metrics: 让Auto Scaling不再“食之无味”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a0/2c/a072e72c01298538ce8b7e0d74b0382c.mp3"></audio>
你好我是张磊。今天我和你分享的主题是Custom Metrics让Auto Scaling不再“食之无味”。
在上一篇文章中,我为你详细讲述了 Kubernetes 里的核心监控体系的架构。不难看到Prometheus 项目在其中占据了最为核心的位置。
实际上借助上述监控体系Kubernetes 就可以为你提供一种非常有用的能力,那就是 Custom Metrics自定义监控指标。
在过去的很多 PaaS 项目中,其实都有一种叫作 Auto Scaling即自动水平扩展的功能。只不过这个功能往往只能依据某种指定的资源类型执行水平扩展比如 CPU 或者 Memory 的使用值。
而在真实的场景中用户需要进行Auto Scaling 的依据往往是自定义的监控指标。比如,某个应用的等待队列的长度,或者某种应用相关资源的使用情况。这些复杂多变的需求,在传统 PaaS项目和其他容器编排项目里几乎是不可能轻松支持的。
而凭借强大的 API 扩展机制Custom Metrics已经成为了 Kubernetes 的一项标准能力。并且Kubernetes 的自动扩展器组件 Horizontal Pod Autoscaler HPA 也可以直接使用Custom Metrics来执行用户指定的扩展策略这里的整个过程都是非常灵活和可定制的。
不难想到Kubernetes 里的 Custom Metrics 机制也是借助Aggregator APIServer 扩展机制来实现的。这里的具体原理是,当你把 Custom Metrics APIServer 启动之后Kubernetes 里就会出现一个叫作`custom.metrics.k8s.io`的 API。而当你访问这个 URL 时Aggregator就会把你的请求转发给Custom Metrics APIServer 。
而Custom Metrics APIServer 的实现,其实就是一个 Prometheus 项目的 Adaptor。
比如,现在我们要实现一个根据指定 Pod 收到的 HTTP 请求数量来进行 Auto Scaling 的 Custom Metrics这个 Metrics 就可以通过访问如下所示的自定义监控 URL 获取到:
```
https://&lt;apiserver_ip&gt;/apis/custom-metrics.metrics.k8s.io/v1beta1/namespaces/default/pods/sample-metrics-app/http_requests
```
这里的工作原理是,当你访问这个 URL 的时候Custom Metrics APIServer就会去 Prometheus 里查询名叫sample-metrics-app这个Pod 的http_requests指标的值然后按照固定的格式返回给访问者。
当然http_requests指标的值就需要由 Prometheus 按照我在上一篇文章中讲到的核心监控体系,从目标 Pod 上采集。
这里具体的做法有很多种,最普遍的做法,就是让 Pod 里的应用本身暴露出一个/metrics API然后在这个 API 里返回自己收到的HTTP的请求的数量。所以说接下来 HPA 只需要定时访问前面提到的自定义监控 URL然后根据这些值计算是否要执行 Scaling 即可。
接下来,我通过一个具体的实例,来为你讲解一下 Custom Metrics 具体的使用方式。这个实例的 GitHub 库[在这里](https://github.com/resouer/kubeadm-workshop),你可以点击链接查看。在这个例子中,我依然会假设你的集群是 kubeadm 部署出来的,所以 Aggregator 功能已经默认开启了。
>
备注我们这里使用的实例fork 自 Lucas 在上高中时做的一系列Kubernetes 指南。
**首先**,我们当然是先部署 Prometheus 项目。这一步,我当然会使用 Prometheus Operator来完成如下所示
```
$ kubectl apply -f demos/monitoring/prometheus-operator.yaml
clusterrole &quot;prometheus-operator&quot; created
serviceaccount &quot;prometheus-operator&quot; created
clusterrolebinding &quot;prometheus-operator&quot; created
deployment &quot;prometheus-operator&quot; created
$ kubectl apply -f demos/monitoring/sample-prometheus-instance.yaml
clusterrole &quot;prometheus&quot; created
serviceaccount &quot;prometheus&quot; created
clusterrolebinding &quot;prometheus&quot; created
prometheus &quot;sample-metrics-prom&quot; created
service &quot;sample-metrics-prom&quot; created
```
**第二步**,我们需要把 Custom Metrics APIServer 部署起来,如下所示:
```
$ kubectl apply -f demos/monitoring/custom-metrics.yaml
namespace &quot;custom-metrics&quot; created
serviceaccount &quot;custom-metrics-apiserver&quot; created
clusterrolebinding &quot;custom-metrics:system:auth-delegator&quot; created
rolebinding &quot;custom-metrics-auth-reader&quot; created
clusterrole &quot;custom-metrics-read&quot; created
clusterrolebinding &quot;custom-metrics-read&quot; created
deployment &quot;custom-metrics-apiserver&quot; created
service &quot;api&quot; created
apiservice &quot;v1beta1.custom-metrics.metrics.k8s.io&quot; created
clusterrole &quot;custom-metrics-server-resources&quot; created
clusterrolebinding &quot;hpa-controller-custom-metrics&quot; created
```
**第三步**,我们需要为 Custom Metrics APIServer 创建对应的 ClusterRoleBinding以便能够使用curl来直接访问 Custom Metrics 的 API
```
$ kubectl create clusterrolebinding allowall-cm --clusterrole custom-metrics-server-resources --user system:anonymous
clusterrolebinding &quot;allowall-cm&quot; created
```
**第四步**,我们就可以把待监控的应用和 HPA 部署起来了,如下所示:
```
$ kubectl apply -f demos/monitoring/sample-metrics-app.yaml
deployment &quot;sample-metrics-app&quot; created
service &quot;sample-metrics-app&quot; created
servicemonitor &quot;sample-metrics-app&quot; created
horizontalpodautoscaler &quot;sample-metrics-app-hpa&quot; created
ingress &quot;sample-metrics-app&quot; created
```
这里,我们需要关注一下 HPA 的配置,如下所示:
```
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta1
metadata:
name: sample-metrics-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: sample-metrics-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Object
object:
target:
kind: Service
name: sample-metrics-app
metricName: http_requests
targetValue: 100
```
可以看到,**HPA 的配置,就是你设置 Auto Scaling 规则的地方。**
比如scaleTargetRef字段就指定了被监控的对象是名叫sample-metrics-app的 Deployment也就是我们上面部署的被监控应用。并且它最小的实例数目是2最大是10。
在metrics字段我们指定了这个 HPA 进行 Scale 的依据是名叫http_requests的 Metrics。而获取这个 Metrics 的途径则是访问名叫sample-metrics-app的 Service。
有了这些字段里的定义, HPA 就可以向如下所示的 URL 发起请求来获取 Custom Metrics 的值了:
```
https://&lt;apiserver_ip&gt;/apis/custom-metrics.metrics.k8s.io/v1beta1/namespaces/default/services/sample-metrics-app/http_requests
```
需要注意的是,上述这个 URL 对应的被监控对象,是我们的应用对应的 Service。这跟本文一开始举例用到的 Pod 对应的 Custom Metrics URL 是不一样的。当然,**对于一个多实例应用来说,通过 Service 来采集 Pod 的 Custom Metrics 其实才是合理的做法。**
这时候我们可以通过一个名叫hey的测试工具来为我们的应用增加一些访问压力具体做法如下所示
```
$ # Install hey
$ docker run -it -v /usr/local/bin:/go/bin golang:1.8 go get github.com/rakyll/hey
$ export APP_ENDPOINT=$(kubectl get svc sample-metrics-app -o template --template {{.spec.clusterIP}}); echo ${APP_ENDPOINT}
$ hey -n 50000 -c 1000 http://${APP_ENDPOINT}
```
与此同时,如果你去访问应用 Service 的 Custom Metircs URL就会看到这个 URL 已经可以为你返回应用收到的 HTTP 请求数量了,如下所示:
```
$ curl -sSLk https://&lt;apiserver_ip&gt;/apis/custom-metrics.metrics.k8s.io/v1beta1/namespaces/default/services/sample-metrics-app/http_requests
{
&quot;kind&quot;: &quot;MetricValueList&quot;,
&quot;apiVersion&quot;: &quot;custom-metrics.metrics.k8s.io/v1beta1&quot;,
&quot;metadata&quot;: {
&quot;selfLink&quot;: &quot;/apis/custom-metrics.metrics.k8s.io/v1beta1/namespaces/default/services/sample-metrics-app/http_requests&quot;
},
&quot;items&quot;: [
{
&quot;describedObject&quot;: {
&quot;kind&quot;: &quot;Service&quot;,
&quot;name&quot;: &quot;sample-metrics-app&quot;,
&quot;apiVersion&quot;: &quot;/__internal&quot;
},
&quot;metricName&quot;: &quot;http_requests&quot;,
&quot;timestamp&quot;: &quot;2018-11-30T20:56:34Z&quot;,
&quot;value&quot;: &quot;501484m&quot;
}
]
}
```
**这里需要注意的是Custom Metrics API 为你返回的 Value 的格式。**
在为被监控应用编写/metrics API 的返回值时,我们其实比较容易计算的,是该 Pod 收到的 HTTP request 的总数。所以,我们这个[应用的代码](https://github.com/resouer/kubeadm-workshop/blob/master/images/autoscaling/server.js)其实是如下所示的样子:
```
if (request.url == &quot;/metrics&quot;) {
response.end(&quot;# HELP http_requests_total The amount of requests served by the server in total\n# TYPE http_requests_total counter\nhttp_requests_total &quot; + totalrequests + &quot;\n&quot;);
return;
}
```
可以看到,我们的应用在/metrics 对应的 HTTP response 里返回的其实是http_requests_total的值。这也就是 Prometheus 收集到的值。
而 Custom Metrics APIServer 在收到对http_requests指标的访问请求之后它会从Prometheus 里查询http_requests_total的值然后把它折算成一个以时间为单位的请求率最后把这个结果作为http_requests指标对应的值返回回去。
所以说,我们在对前面的 Custom Metircs URL 进行访问时会看到值是501484m这里的格式其实就是milli-requests相当于是在过去两分钟内每秒有501个请求。这样应用的开发者就无需关心如何计算每秒的请求个数了。而这样的“请求率”的格式是可以直接被 HPA 拿来使用的。
这时候,如果你同时查看 Pod 的个数的话,就会看到 HPA 开始增加 Pod 的数目了。
不过在这里你可能会有一个疑问Prometheus 项目,又是如何知道采集哪些 Pod 的 /metrics API 作为监控指标的来源呢。
实际上如果仔细观察一下我们前面创建应用的输出你会看到有一个类型是ServiceMonitor的对象也被创建了出来。它的 YAML 文件如下所示:
```
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: sample-metrics-app
labels:
service-monitor: sample-metrics-app
spec:
selector:
matchLabels:
app: sample-metrics-app
endpoints:
- port: web
```
这个ServiceMonitor对象正是 Prometheus Operator 项目用来指定被监控 Pod 的一个配置文件。可以看到我其实是通过Label Selector 为Prometheus 来指定被监控应用的。
# 总结
在本篇文章中,我为你详细讲解了 Kubernetes 里对自定义监控指标,即 Custom Metrics 的设计与实现机制。这套机制的可扩展性非常强也终于使得Auto Scaling 在 Kubernetes 里面不再是一个“食之无味”的鸡肋功能了。
另外可以看到Kubernetes 的 Aggregator APIServer是一个非常行之有效的 API 扩展机制。而且Kubernetes 社区已经为你提供了一套叫作 [KubeBuilder](https://github.com/kubernetes-sigs/kubebuilder) 的工具库,帮助你生成一个 API Server 的完整代码框架,你只需要在里面添加自定义 API以及对应的业务逻辑即可。
# 思考题
在你的业务场景中,你希望使用什么样的指标作为 Custom Metrics ,以便对 Pod 进行 Auto Scaling 呢?怎么获取到这个指标呢?
感谢你的收听,欢迎你给我留言,也欢迎分享给更多的朋友一起阅读。
<img src="https://static001.geekbang.org/resource/image/96/25/96ef8576a26f5e6266c422c0d6519725.jpg" alt="">

View File

@@ -0,0 +1,240 @@
<audio id="audio" title="50 | 让日志无处可逃:容器日志收集与管理" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f0/c0/f0c936846d55e32127539d90523342c0.mp3"></audio>
你好,我是张磊。今天我和你分享的主题是:让日志无处可逃之容器日志收集与管理。
在前面的文章中,我为你详细讲解了 Kubernetes 的核心监控体系和自定义监控体系的设计与实现思路。而在本篇文章里我就来为你详细介绍一下Kubernetes 里关于容器日志的处理方式。
首先需要明确的是Kubernetes 里面对容器日志的处理方式都叫作cluster-level-logging这个日志处理系统与容器、Pod以及Node的生命周期都是完全无关的。这种设计当然是为了保证无论是容器挂了、Pod 被删除,甚至节点宕机的时候,应用的日志依然可以被正常获取到。
而对于一个容器来说,当应用把日志输出到 stdout 和 stderr 之后,容器项目在默认情况下就会把这些日志输出到宿主机上的一个 JSON 文件里。这样,你通过 kubectl logs 命令就可以看到这些容器的日志了。
上述机制,就是我们今天要讲解的容器日志收集的基础假设。而如果你的应用是把文件输出到其他地方,比如直接输出到了容器里的某个文件里,或者输出到了远程存储里,那就属于特殊情况了。当然,我在文章里也会对这些特殊情况的处理方法进行讲述。
而 Kubernetes 本身实际上是不会为你做容器日志收集工作的所以为了实现上述cluster-level-logging你需要在部署集群的时候提前对具体的日志方案进行规划。而 Kubernetes 项目本身,主要为你推荐了三种日志方案。
**第一种,在 Node 上部署 logging agent将日志文件转发到后端存储里保存起来。**这个方案的架构图如下所示。
<img src="https://static001.geekbang.org/resource/image/b5/43/b5515aed076aa6af63ace55b62d36243.jpg" alt="">
不难看到,这里的核心就在于 logging agent ,它一般都会以 DaemonSet 的方式运行在节点上,然后将宿主机上的容器日志目录挂载进去,最后由 logging-agent 把日志转发出去。
举个例子,我们可以通过 Fluentd 项目作为宿主机上的 logging-agent然后把日志转发到远端的 ElasticSearch 里保存起来供将来进行检索。具体的操作过程,你可以通过阅读[这篇文档](https://kubernetes.io/docs/user-guide/logging/elasticsearch)来了解。另外,在很多 Kubernetes 的部署里,会自动为你启用 [logrotate](https://linux.die.net/man/8/logrotate)在日志文件超过10MB 的时候自动对日志文件进行 rotate 操作。
可以看到,在 Node 上部署 logging agent最大的优点在于一个节点只需要部署一个 agent并且不会对应用和 Pod 有任何侵入性。所以,这个方案,在社区里是最常用的一种。
但是也不难看到,这种方案的不足之处就在于,它要求应用输出的日志,都必须是直接输出到容器的 stdout 和 stderr 里。
所以,**Kubernetes 容器日志方案的第二种,就是对这种特殊情况的一个处理,即:当容器的日志只能输出到某些文件里的时候,我们可以通过一个 sidecar 容器把这些日志文件重新输出到 sidecar 的 stdout 和 stderr 上,这样就能够继续使用第一种方案了。**这个方案的具体工作原理,如下所示。
<img src="https://static001.geekbang.org/resource/image/48/20/4863e3d7d1ef02a5a44e431369ac4120.jpg" alt="">
比如,现在我的应用 Pod 只有一个容器,它会把日志输出到容器里的/var/log/1.log 和2.log 这两个文件里。这个 Pod 的 YAML 文件如下所示:
```
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox
args:
- /bin/sh
- -c
- &gt;
i=0;
while true;
do
echo &quot;$i: $(date)&quot; &gt;&gt; /var/log/1.log;
echo &quot;$(date) INFO $i&quot; &gt;&gt; /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
```
在这种情况下,你用 kubectl logs 命令是看不到应用的任何日志的。而且我们前面讲解的、最常用的方案一,也是没办法使用的。
那么这个时候,我们就可以为这个 Pod 添加两个 sidecar容器分别将上述两个日志文件里的内容重新以 stdout 和 stderr 的方式输出出来,这个 YAML 文件的写法如下所示:
```
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox
args:
- /bin/sh
- -c
- &gt;
i=0;
while true;
do
echo &quot;$i: $(date)&quot; &gt;&gt; /var/log/1.log;
echo &quot;$(date) INFO $i&quot; &gt;&gt; /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-1
image: busybox
args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-log-2
image: busybox
args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log']
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
emptyDir: {}
```
这时候,你就可以通过 kubectl logs 命令查看这两个 sidecar 容器的日志,间接看到应用的日志内容了,如下所示:
```
$ kubectl logs counter count-log-1
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
...
$ kubectl logs counter count-log-2
Mon Jan 1 00:00:00 UTC 2001 INFO 0
Mon Jan 1 00:00:01 UTC 2001 INFO 1
Mon Jan 1 00:00:02 UTC 2001 INFO 2
...
```
由于 sidecar 跟主容器之间是共享 Volume 的,所以这里的 sidecar 方案的额外性能损耗并不高,也就是多占用一点 CPU 和内存罢了。
但需要注意的是,这时候,宿主机上实际上会存在两份相同的日志文件:一份是应用自己写入的;另一份则是 sidecar 的 stdout 和 stderr 对应的 JSON 文件。这对磁盘是很大的浪费。所以说,除非万不得已或者应用容器完全不可能被修改,否则我还是建议你直接使用方案一,或者直接使用下面的第三种方案。
**第三种方案,就是通过一个 sidecar 容器,直接把应用的日志文件发送到远程存储里面去。**也就是相当于把方案一里的 logging agent放在了应用 Pod 里。这种方案的架构如下所示:
<img src="https://static001.geekbang.org/resource/image/d4/c7/d464401baec60c11f96dfeea3ae3a9c7.jpg" alt="">
在这种方案里,你的应用还可以直接把日志输出到固定的文件里而不是 stdout你的 logging-agent 还可以使用 fluentd后端存储还可以是 ElasticSearch。只不过 fluentd 的输入源,变成了应用的日志文件。一般来说,我们会把 fluentd 的输入源配置保存在一个 ConfigMap 里,如下所示:
```
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
data:
fluentd.conf: |
&lt;source&gt;
type tail
format none
path /var/log/1.log
pos_file /var/log/1.log.pos
tag count.format1
&lt;/source&gt;
&lt;source&gt;
type tail
format none
path /var/log/2.log
pos_file /var/log/2.log.pos
tag count.format2
&lt;/source&gt;
&lt;match **&gt;
type google_cloud
&lt;/match&gt;
```
然后,我们在应用 Pod 的定义里就可以声明一个Fluentd容器作为 sidecar专门负责将应用生成的1.log 和2.log转发到 ElasticSearch 当中。这个配置,如下所示:
```
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox
args:
- /bin/sh
- -c
- &gt;
i=0;
while true;
do
echo &quot;$i: $(date)&quot; &gt;&gt; /var/log/1.log;
echo &quot;$(date) INFO $i&quot; &gt;&gt; /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: count-agent
image: k8s.gcr.io/fluentd-gcp:1.30
env:
- name: FLUENTD_ARGS
value: -c /etc/fluentd-config/fluentd.conf
volumeMounts:
- name: varlog
mountPath: /var/log
- name: config-volume
mountPath: /etc/fluentd-config
volumes:
- name: varlog
emptyDir: {}
- name: config-volume
configMap:
name: fluentd-config
```
可以看到,这个 Fluentd 容器使用的输入源,就是通过引用我们前面编写的 ConfigMap来指定的。这里我用到了 Projected Volume 来把 ConfigMap 挂载到 Pod 里。如果你对这个用法不熟悉的话可以再回顾下第15篇文章[《 深入解析Pod对象使用进阶》](https://time.geekbang.org/column/article/40466)中的相关内容。
需要注意的是,这种方案虽然部署简单,并且对宿主机非常友好,但是这个 sidecar 容器很可能会消耗较多的资源,甚至拖垮应用容器。并且,由于日志还是没有输出到 stdout上所以你通过 kubectl logs 是看不到任何日志输出的。
以上,就是 Kubernetes 项目对容器应用日志进行管理最常用的三种手段了。
# 总结
在本篇文章中我为你详细讲解了Kubernetes 项目对容器应用日志的收集方式。综合对比以上三种方案,我比较建议你将应用日志输出到 stdout 和 stderr然后通过在宿主机上部署 logging-agent 的方式来集中处理日志。
这种方案不仅管理简单kubectl logs 也可以用,而且可靠性高,并且宿主机本身,很可能就自带了 rsyslogd 等非常成熟的日志收集组件来供你使用。
除此之外,还有一种方式就是在编写应用的时候,就直接指定好日志的存储后端,如下所示:
<img src="https://static001.geekbang.org/resource/image/13/99/13e8439d9945fea58c9672fc4ca30799.jpg" alt="">
在这种方案下Kubernetes 就完全不必操心容器日志的收集了,这对于本身已经有完善的日志处理系统的公司来说,是一个非常好的选择。
最后需要指出的是,无论是哪种方案,你都必须要及时将这些日志文件从宿主机上清理掉,或者给日志目录专门挂载一些容量巨大的远程盘。否则,一旦主磁盘分区被打满,整个系统就可能会陷入崩溃状态,这是非常麻烦的。
# 思考题
<li>
请问,当日志量很大的时候,直接将日志输出到容器 stdout 和 stderr上有没有什么隐患呢有没有解决办法呢
</li>
<li>
你还有哪些容器收集的方案,是否可以分享一下?
</li>
感谢你的收听,欢迎你给我留言,也欢迎分享给更多的朋友一起阅读。
<img src="https://static001.geekbang.org/resource/image/96/25/96ef8576a26f5e6266c422c0d6519725.jpg" alt="">