目录
本文主要参考以下两个文章,对文章内容进行翻译整合。
https://devopscube.com/kubernetes-logging-tutorial/
https://devopscube.com/setup-efk-stack-on-kubernetes/
在这个 Kubernetes 日志教程中,您将学习 Kubernetes 集群日志中涉及的关键概念和工作流。
当涉及到 Kubernetes 生产调试时,日志起着至关重要的作用。它可以帮助你理解正在发生的事情,哪里出了问题,甚至是哪里可能出问题。作为一名 DevOps 工程师,您应该清楚地了解 Kubernetes 日志以解决集群和应用程序问题。
在 Kubernetes,大多数组件都是以容器方式运行的。在 kubernetes 架构中,一个应用程序 Pod 可以包含多个容器,大多数 Kubernetes 集群组件都是这样,如 api-server、 kube-scheduler、 Etcd、 kube 代理等等,会以容器方式运行。但是,kubelet 组件以本机 systemd 服务运行。
在这一节中,我们将看看日志是如何为 Kubernetes Pod 工作的。它可以是一个 Application Pod 或 Kubernetes component Pod。我们还将研究如何管理 kubelet systemd 日志。
通常,我们在 Kubernetes 上部署的任何 Pod 都会将日志写入 stdout 和 stderr 流,而不是将日志写入专用的日志文件。但是,来自每个容器的对 stdout 和 stderr 的流都以 JSON 格式存储在文件系统中。底层容器引擎完成这项工作,它被设计用来处理日志记录。例如,Docker 容器引擎。
笔者注:这段话的意思是容器应用的日志通过控制台输出时,会被容器引擎收集,这些日志流会被以 Json 文件的形式存储到文件系统中。 容器的日志收集方式后面提到。 注意: 所有 kubernetes 集群组件日志都是像处理其他容器日志一样处理的。
Kubelet 在所有节点上运行,以确保节点上的容器正常运行。它还负责运行的静态 Pod 以及,如果 kubelet 作为一个系统服务运行,它将日志记录到 journald(systemd-journald )。
另外,如果容器没有将日志传输到 stdout 和 stderr,您将不会使用 kubetl logs
命令获得日志,因为 kubelet 无法访问日志文件。
笔者注:例如 Pod 在节点 B 中运行,但是你在 A 节点执行
kubectl logs
命令,Pod 的日志不会凭空飞过去,是通过 kubelet 传输过去的。
这一小节说的是,程序的日志要通过 stdout 和 stderr 输出,才会被 Kubernetes 利用(kubectl logs
),而在节点间传输日志的组件叫 kubelet。因为 kubelet不是以 Pod 而是以 systemd 的形式运行,因此 kubelet 自身的日志要通过 systemd-journald 查看。
您可以在以下每个工作节点的目录中找到 kubernetes 存储的 Pod 日志。
/var/log/pods/<namespace>_<pod_name>_<pod_id>/<container_name>/
.每个 pod 文件夹包含单个容器文件夹及其各自的日志文件。每个文件夹都有一个命名方案;另外,如果您的底层容器工程师是 docker,您将在 /var/lib/docker/containers
文件夹中找到日志。
日志以 Json 的形式记录,还会记录日志的一些属性信息。 {"log":"Shutting down, got signal: Terminated\n","stream":"stderr","time":"2021-11-09T06:14:42.535854831Z"}
如果您登录到任何 Kubernetes 工作者节点并转到 /var/log/containers
目录,您将找到该节点上运行的每个容器的日志文件。日志文件命名方案遵循 /var/log/pods/<namespace>_<pod_name>_<pod_id>/<container_name>/
。下面的图片显示了一个例子。
此外,这些日志文件由 Kubelet 控制,因此当您运行 kubectl logs
命令时,Kubelet 会在终端中显示这些日志。
对于 Kubelet,您可以使用 journalctl 从单个工作者节点访问日志。例如,使用以下命令检查 Kubelet 日志。
journalctl -u kubelet
journalctl -u kubelet -o cat
如果 Kubelet 在没有 systemd 的情况下运行,您可以在 /var/log
目录中找到 Kubelet 日志。
下面是容器日志的其中一行数据示例:
{"log":"Shutting down, got signal: Terminated\n","stream":"stderr","time":"2021-11-09T06:14:42.535854831Z"}
如前所述,所有日志数据都以 JSON 格式存储。因此,如果打开任何一个日志文件,就会发现每个日志条目都有三个键。
log
-实际的日志数据stream
– 写入日志的流time
– Timetamp 时间表说到 Kubernetes,以下是不同类型的日志。
如果我们将 Kubernetes 集群作为一个整体,那么我们将需要统一收集日志。但是 Kubernetes 并不提供任何日志收集功能,因此您需要设置一个集中的日志后端(例如: Elasticsearch) ,并将所有日志发送到日志后端。下面的图像描绘了一个 high-level 的 Kubernetes Logging 架构。
让我们了解一下日志记录的三个关键组件。
本节将研究一些 Kubernetes 日志记录模式,以便将日志流传到日志后端。有三种关键的 Kubernetes 集群日志记录模式
让我们详细研究一下每个方案的特点。
在这种方法中,每个节点运行着一个代理(例如: Fluentd)读取使用容器 STDOUT 和 STDERR 流创建的日志文件,然后将其发送给像 Elasticsearch 这样的日志后端。这是一种常用的日志记录模式,不需要任何开销就可以很好地工作。
而云原生中的 12因素应用程序方法 也建议将日志流传到 STDOUT。
当应用程序不能直接向 STDOUT 和 STDERR 流写入日志时,这种 Sidecar 方法非常有用。
Pod 中的应用程序容器将所有日志写入容器中的一个文件,然后 Pod 中存在一个 sidecar 容器从该日志文件中读取数据并将其传输到 STDOUT 和 STDERR,最后利用 Node Level Logging Agent 的方式收集。
应用程序的日志自定义文件 -> 重新将流输出到 STDOUT -> 容器引擎收集
在这种方法中,日志不会被流送到 STDOUT 和 STDERR。相反,一个带有日志代理的 sidecar 容器将与应用程序容器一起运行。然后,日志代理将直接将日志流传到日志后端。
也就是说,Pod 中的 sidecar 容器,把日志1直接推送到日志存储后端,不需要容器引擎的收集。
这种方法有两个缺点。
kubectl logs
命令获得日志,因为 Kubelet 不会处理日志。Kubernetes 最常用的开源日志堆栈是 EFK (Elasticsearch,Flunentd/Fluent-but,和 Kibana)。
EFK 方案包含以下三大部件:
当涉及到像 Google GKE、 AWS 和 Azure AKS 这样的管理 Kubernetes 服务时,它集成了特定于云的集中式日志记录。因此,当您部署托管 kubernetes 集群时,您将获得在相应的日志记录服务中启用日志监视的选项。比如说:
需要在云上操作。
此外,组织可能会使用企业日志解决方案,比如 Splunk。在这种情况下,日志被转发给 Splunk 监控,并遵守组织的日志保留规范。以下是一些企业日志解决方案。
在 Kubernetes 中,目前其中一个最好的开源日志方案是 EFK ,它包含 Elasticsearch、 Fluentd 和 Kibana 三个部分。
在第二部分中,我们将在 Kubernetes 集群中,部署 EFK 日志方案。
第一部分,其中为初学者介绍了 Kubernetes 日志基本原理和模式。在第二部分中,您将学习如何在 Kubernetes 集群上设置用于日志流、日志分析和日志监视的 EFK。
在 Kubernetes 集群上运行多个应用程序和服务时,将所有应用程序和 Kubernetes 集群日志流到一个集中的日志基础设施中,以便于日志分析,这样做更有意义。第二部分旨在通过 EFK 堆栈向您介绍 Kubernetes 日志的重要技术方面。
EFK 代表 Elasticsearch、 Fluentd 和 Kibana。EFK 是用于 Kubernetes 日志聚合和分析的流行且最佳的开源选择。
此外,Elasticsearch 还可以帮助解决大量非结构化数据分离的问题,目前许多组织都在使用 Elasticsearch,通常跟 Kibana 部署在一起。
注意: 当涉及到 Kubernetes 时,FLuentd 是最好的选择,因为比 logstash 更好,因为 FLuentd 可以解析容器日志而不需要任何额外的配置。此外,这是一个 CNCF 项目。
接下来我们将一步步在 Kubernetes 中部署和配置 EFK,你可以在 Kubernetes EFK Github repo 中找到本博客中使用的所有部署定义文件,每个 EFK 组件的 YAML 定义文件都放在不同的目录中。
首先克隆仓库:
git clone https://github.com/scriptcamp/kubernetes-efk
注意,在本文部署的 EFK 组件,都会在 Kubernetes 默认的 default 命名空间中。
仓库的文件结构如下:
├── kubernetes-efk
│ ├── README.md
│ ├── elasticsearch
│ │ ├── es-sts.yaml
│ │ └── es-svc.yaml
│ ├── fluentd
│ │ ├── fluentd-ds.yaml
│ │ ├── fluentd-rb.yaml
│ │ ├── fluentd-role.yaml
│ │ └── fluentd-sa.yaml
│ ├── kibana
│ │ ├── kibana-deployment.yaml
│ │ └── kibana-svc.yaml
│ └── test-pod.yaml
下图显示了我们将要构建的 EFK 的高级架构。
EKF 组件部署说明如下:
Elasticsearch 是作为 Statefulset 部署的,多个副本通过一个 headless service 彼此连接。Headless svc 在 Pod 的 DNS 域中提供帮助。
打开 elasticsearch/es-svc. yaml
文件,可以看到定义如下:
apiVersion: v1
kind: Service
metadata:
name: elasticsearch
labels:
app: elasticsearch
spec:
selector:
app: elasticsearch
clusterIP: None
ports:
- port: 9200
name: rest
- port: 9300
name: inter-node
让我们现在就创造它。
kubectl create -f es-svc.yaml
在我们开始为弹性搜索创建 statefulset 之前,让我们回想一下,statefulset 需要事先定义的存储类,它可以在需要时创建卷。
注意: 虽然在生产环境中,可能需要 400-500gb SSD 支撑 ES 的存储,由于这里是存储环境,因此定义 YAML 文件中是 3GB 的 PV。 笔者因为要学习 EFK,特意挂载了一个 512GB 的企业级 SSD。
在 elasticsearch/es-sts.yaml
文件中,它会自动创建一个具有 3GB 的存储卷,其定义如下:
spec:
accessModes: [ "ReadWriteOnce" ]
# storageClassName: ""
resources:
requests:
storage: 3Gi
由于笔者有自己的盘,笔者挂载到了 slave2 节点中,所以这里笔者就不用这个配置了,笔者使用 NFS - PV 的方式,当然读者为了避免麻烦,可以继续使用上面的配置。
读者可以首先按照 https://k8s.whuanle.cn/5.volumes/3.nfts.html 部署自己的 NFS 存储,然后利用下面这个模板创建 PV。 apiVersion: v1 kind: PersistentVolume metadata: name: es-pv spec: capacity: storage: 450Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: es-volume mountOptions: - hard - nfsvers=4.1 nfs: path: /data/volumns/es server: 10.0.0.4 需读者自行提前创建 NFS,读者可参考 https://k8s.whuanle.cn/5.volumes/3.nfts.html 创建一个自己的跨节点的存储系统。
将 volumeClaimTemplates 部分改成:
volumeClaimTemplates:
- metadata:
name: data
labels:
app: elasticsearch
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "es-volume"
resources:
requests:
storage: 450Gi
然后我们看一下 es-sts.yaml 的文件中,对于 Eelasticsearch 集群的定义:
- name: discovery.seed_hosts
value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
这里有两个地方要注意,一是你的集群必须要安装 CoreDNS,二是你的从节点数量跟 es-sts.yaml 中定义的 replicas: 3
数量一致,如果你有三个节点(一主二从),那么 replics
要设置为 2,但是一般 Eelasticsearch 实例数量都是单数。因此笔者只设置使用一个 Eelasticsearch 实例。
然后部署 Elasticsearch :
kubectl create -f es-sts.yaml
在 Elasticsearch Pod 进入运行状态之后,让我们尝试验证 Elasticsearch 状态集。最简单的方法是检查集群的状态。为了检查状态,端口前进 Elasticsearch Pod 的 9200 端口。
kubectl port-forward es-cluster-0 19200:9200
port-forward 方式具有临时性,避免 Elasticsearch 暴露到外网;或者使用 Service IP 等方式测试 IP 连通性,你还可以使用 Service NodePort 的方式暴露 Elasticsearch 。
要检查 Elasticsearch 集群的健康状况,请在终端中运行以下命令。
curl http://localhost:19200/_cluster/health/?pretty
输出将显示 Elasticsearch 集群的状态。如果所有的步骤都被正确的执行,访问此地址,会获得 Json 响应。
跟部署 Elasticsearch 一样,可以使用一个简单的 yaml 文件部署 Kibana。如果您检查以下 Kibana 部署清单文件,我们有一个 ELASTICSEARCH_URL
定义来配置 Elasticsearch 集群 Endpoint,Kibana 使用 Endpoint URL 连接 Elasticsearch。
下面是 kibana-deployment.yaml 文件的定义。
apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana
labels:
app: kibana
spec:
replicas: 1
selector:
matchLabels:
app: kibana
template:
metadata:
labels:
app: kibana
spec:
containers:
- name: kibana
image: docker.elastic.co/kibana/kibana:7.5.0
resources:
limits:
cpu: 1000m
requests:
cpu: 100m
env:
- name: ELASTICSEARCH_URL
value: http://elasticsearch:9200
ports:
- containerPort: 5601
"state":"green","message":"Status changed from yellow to green - Ready","prevState":"yellow","prevMsg":"Waiting for Elasticsearch"
现在直接部署 Kibana 即可:
kubectl create -f kibana-deployment.yaml
让我们创建一个 NodePort 类型的 Service,通过节点 IP 地址访问 Kibana UI。我们使用 nodePort 进行演示。然而,理想情况下,kubernetes 与 ClusterIP 服务的接入用于实际的项目实现。
kibana-svc.yaml 文件的定义如下:
apiVersion: v1
kind: Service
metadata:
name: kibana-np
spec:
selector:
app: kibana
type: NodePort
ports:
- port: 8080
targetPort: 5601
nodePort: 30000
创建 Kibana Service:
kubectl create -f kibana-svc.yaml
现在你可以通过 http://<node-ip>:3000
访问 Kibana UI。
Pod 进入运行状态后,让我们尝试验证 Kibana 部署。最简单的方法是通过集群的 UI 访问。
要检查状态,端口转发 Kibana Pod 的 5601端口。如果您已经创建了 nodePort 服务,您也可以使用它(注意防火墙可能会拦截)。
kubectl port-forward <kibana-pod-name> 5601:5601
之后,通过 web 浏览器访问 UI 或使用 curl 发出请求
curl http://localhost:5601/app/kibana
如果 Kibana UI 加载或出现有效的 curl 响应,那么我们可以断定 Kibana 正在正确运行。
Fluentd 被部署为守护进程,因为它必须从集群中的所有节点流日志。除此之外,它还需要特殊的权限来列出和提取所有名称空间中的 Pod 元数据。
Kubernetes 服务帐户用于为 Kubernetes 中的组件提供权限,以及集群角色和集群绑定。让我们继续前进,创建所需的服务帐户和角色。
Kubernetes 中的集群角色包含表示一组权限的规则,对于 Fluentd,我们希望为 Pod 和名称空间授予权限。
fluentd-role.yaml
文件的定义如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: fluentd
labels:
app: fluentd
rules:
- apiGroups:
- ""
resources:
- pods
- namespaces
verbs:
- get
- list
- watch
创建角色:
kubectl create -f fluentd-role.yaml
在 Kubernetes 中,Service Account 是为 Pod 提供身份的实体,在这里,我们希望创建一个 Service Account,用于 Fluentd Pods。
fluentd-sa.yaml
文件的定义如下:
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd
labels:
app: fluentd
创建 Service Account:
kubectl create -f fluentd-sa.yaml
Kubernetes 中的集群角色绑定将集群角色中定义的权限授予服务帐户。我们希望在上面创建的角色和服务帐户之间创建一个角色绑定。
fluentd-rb.yaml
文件的定义如下:
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd
roleRef:
kind: ClusterRole
name: fluentd
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: fluentd
namespace: default
创建角色绑定:
kubectl create -f fluentd-rb.yaml
现在让我们部署 Fluentd :
kubectl create -f fluentd-ds.yaml
为了验证 fluentd 的安装,让我们启动一个连续创建日志的 pod。然后我们将尝试在 Kibana 境内看到这些日志。
kubectl create -f test-pod.yaml
现在,让我们前往 Kibana,看看这个吊舱里的日志是否被 fluentd 拾取并存储在 elasticsearch。遵循以下步骤:
1,点击 explore on my own
进入后台。
2,选择 Kibana 部分下的“ Index Patterns”选项。
3,使用 logstash-*
模式创建一个新的 Index Patten,然后点击 Next step
。
4,在选项找到 @timestamp
,然后点击 Create index pattern
现在已经创建了索引模式,我们可以前往控制台,在控制台中,您将能够看到所有由 Fluentd 导出的日志,如下图所示,这些日志来自我们的 test-pod
。
接下来,读者可以根据官方文档或其他资料,继续深入学习探究图表的制作和日志分析,以便在生产中应用 EFK 三件套。
如果想深入 Kibana 面板,笔者推荐你阅读这篇博客:https://devopscube.com/kibana-dashboard-tutorial/