首先这里要收集的日志是容器的日志,而不是集群状态的日志

要完成的三个点,收集,监控,报警,收集是基础,监控和报警可以基于收集的日志来作,这篇主要实现收集

不想看字的可以直接看代码,一共没几行,尽量用调用本地方法实现,有工夫的可以改写成shell脚本

https://github.com/cclient/kubernetes-filebeat-collector

官方的收集方案

https://kubernetes.io/docs/tasks/debug-application-cluster/logging-elasticsearch-kibana/

一些基础的总结方案,文章时间比较早

https://jimmysong.io/kubernetes-handbook/practice/app-log-collection.html

官方的方案限制较多,因各种原因放弃

文内的原因是

```

Kubernetes官方提供了EFK的日志收集解决方案,但是这种方案并不适合所有的业务场景,它本身就有一些局限性,例如:

  • 所有日志都必须是out前台输出,真实业务场景中无法保证所有日志都在前台输出
  • 只能有一个日志输出文件,而真实业务场景中往往有多个日志输出文件
  • Fluentd并不是常用的日志收集工具,我们更习惯用logstash,现使用filebeat替代
  • 我们已经有自己的ELK集群且有专人维护,没有必要再在kubernetes上做一个日志收集服务

基于以上几个原因,我们决定使用自己的ELK集群。

```

个人不采用官方收集的原因基本类似,既要收集out前台输出,也要收集映射目录的多个输出文件,个人也不喜欢Fluentd,太重,整体较封闭。

基本实现方式

编号 方案 优点 缺点
1 每个app的镜像中都集成日志收集组件 部署方便,kubernetes的yaml文件无须特别配置,可以为每个app自定义日志收集配置 强耦合,不方便应用和日志收集组件升级和维护且会导致镜像过大
2 单独创建一个日志收集组件跟app的容器一起运行在同一个pod中 低耦合,扩展性强,方便维护和升级 需要对kubernetes的yaml文件进行单独配置,略显繁琐
3 将所有的Pod的日志都挂载到宿主机上,每台主机上单独起一个日志收集Pod 完全解耦,性能最高,管理起来最方便 需要统一日志收集规则,目录和输出方式

文内的需求选择的是方案2

文内和我的需有区别,但不管需求如何

方案1,2都引入了更多的复杂度,需要较多的额外工作,区别只是引入时机,1是build image时和2是deploy时

1,2从设计上就可以排除,太不优雅,以开发语言来说,侵入性太高,3是类似aop性质的能减少侵入,只有在3难以实现的情况下才考虑,实际上3并不难实现,早前纯docker集群就部署过类似的方案,稍改改就能适用于kubernetes

而且这些方案的设计,都只考虑到了,收集的是自定义日志文件,映射外部目录下的文件,不论在容器内,容器外读取,都只监听相应目录下的文件,都不收集 docker 默认的json file,即 docker logs --tail 100 -f contain_name 这类日志的数据

docker logs 操作上比较方便,早先设计docker 日志收集时,虽官方支持不少日志插件

因为`The docker logs command is not available for drivers other than json-file and journald.`

https://docs.docker.com/config/containers/logging/configure/#supported-logging-drivers

过去一直采用默认的json-file,收集时,直接监听 /var/lib/docker/containers/*/*-json.log

早期文件日志收集采用过logstash,修改数据比Fluentd方便,日志收集,没有太多的修改过滤需求,可以切到更轻量的filebeat。

但缺点也很明显,这些日志只是容器的日志,日志本身并不含有容器的相关信息,一种实现应用时就把应用信息写到日志里,虽然没有容器信息,应用信息也足够识别。这些实际也引入项目开发的侵入性。

实际上,docker早期就有基本的容器信息,我们只要把这个容器信息,在收集时,附加到原始日志中即可。

很多人想到附加信息,会考虑从kube-api,或是从etcd读取,这样复杂度过高,基本都放弃了。

实际上,docker 包括contain 在内都可以附加 lable 信息,而这些都可直接从本地文件读取

https://docs.docker.com/config/labels-custom-metadata/#key-format-recommendations

kuberetes 不支持为contain打label,但官方自已会加上一部分,这些lable基本够用了。

需求和简介描述完毕,以下是实现

看一眼config文件即知道实现方式,docker版本不同,文件名可能不一样,印象里早期版本是config.json,内容也没现在的全,早期版本不保证适用本文内容

cat /var/lib/docker/containers/caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f/config.v2.json
 
caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f是容器id,了解docker 应该知道 docker ps 第一列就是这个id的首几位,随便输一个本机的即可
{
"StreamConfig": {},
"State": {
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"RemovalInProgress": false,
"Dead": false,
"Pid": 15201,
"ExitCode": 0,
"Error": "",
"StartedAt": "2018-07-23T00:51:31.821704515Z",
"FinishedAt": "2018-07-23T00:51:31.573484432Z",
"Health": null
},
"ID": "caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f",
"Created": "2018-07-20T09:05:21.881001304Z",
"Managed": false,
"Path": "/app/app",
"Args": [],
"Config": {
"Hostname": "consume-577cd986c7-8mk4h",
"Domainname": "",
"User": "0",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443",
"TUICE_PERSONAL_WECHAT_PORT_8883_TCP_PORT=8883",
"KUBERNETES_SERVICE_HOST=10.96.0.1",
"KUBERNETES_PORT=tcp://10.96.0.1:443",
"TUICE_PERSONAL_WECHAT_PORT=tcp://10.109.89.240:8883",
"TUICE_PERSONAL_WECHAT_PORT_8883_TCP=tcp://10.109.89.240:8883",
"KUBERNETES_SERVICE_PORT=443",
"KUBERNETES_SERVICE_PORT_HTTPS=443",
"KUBERNETES_PORT_443_TCP_PORT=443",
"KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1",
"TUICE_PERSONAL_WECHAT_PORT_8883_TCP_ADDR=10.109.89.240",
"KUBERNETES_PORT_443_TCP_PROTO=tcp",
"TUICE_PERSONAL_WECHAT_SERVICE_HOST=10.109.89.240",
"TUICE_PERSONAL_WECHAT_SERVICE_PORT=8883",
"TUICE_PERSONAL_WECHAT_SERVICE_PORT_SERVER=8883",
"TUICE_PERSONAL_WECHAT_PORT_8883_TCP_PROTO=tcp",
"PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOLANG_VERSION=1.8",
"GOLANG_SRC_URL=https://golang.org/dl/go1.8.src.tar.gz",
"GOLANG_SRC_SHA256=406865f587b44be7092f206d73fc1de252600b79b3cacc587b74b5ef5c623596",
"GOPATH=/app",
"RUN=pro"
],
"Cmd": null,
"Healthcheck": {
"Test": [
"NONE"
]
},
"Image": "hub.docker.admaster.co/social_base/consume@sha256:483cd48b4b1e2ca53846f11fe953695bcceea59542b77f09044d30644cf3235f",
"Volumes": null,
"WorkingDir": "/app",
"Entrypoint": [
"/app/app"
],
"OnBuild": null,
"Labels": {
"annotation.io.kubernetes.container.hash": "e1a52ef6",
"annotation.io.kubernetes.container.restartCount": "0",
"annotation.io.kubernetes.container.terminationMessagePath": "/dev/termination-log",
"annotation.io.kubernetes.container.terminationMessagePolicy": "File",
"annotation.io.kubernetes.pod.terminationGracePeriod": "30",
"io.kubernetes.container.logpath": "/var/log/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/consume/0.log",
"io.kubernetes.container.name": "consume",
"io.kubernetes.docker.type": "container",
"io.kubernetes.pod.name": "consume-577cd986c7-8mk4h",
"io.kubernetes.pod.namespace": "default",
"io.kubernetes.pod.uid": "00bea8a1-8bfc-11e8-b709-f01fafd51338",
"io.kubernetes.sandbox.id": "fda1d939f5b41e31ca5c5214b4d78b38691d438eeb52d22d4460d49a2a820c1f"
}
},
"Image": "sha256:85c9ca987b6fd310ce1019c28031b670da4d6705e70f1807f17727d48aa4aef4",
"NetworkSettings": {
"Bridge": "",
"SandboxID": "",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Networks": null,
"Service": null,
"Ports": null,
"SandboxKey": "",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"IsAnonymousEndpoint": false,
"HasSwarmEndpoint": false
},
"LogPath": "/var/lib/docker/containers/caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f/caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f-json.log",
"Name": "/k8s_consume_consume-577cd986c7-8mk4h_default_00bea8a1-8bfc-11e8-b709-f01fafd51338_0",
"Driver": "overlay",
"MountLabel": "",
"ProcessLabel": "",
"RestartCount": 0,
"HasBeenStartedBefore": true,
"HasBeenManuallyStopped": false,
"MountPoints": {
"/app/conf": {
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~configmap/conf",
"Destination": "/app/conf",
"RW": false,
"Name": "",
"Driver": "",
"Type": "bind",
"Relabel": "ro",
"Propagation": "rprivate",
"Spec": {
"Type": "bind",
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~configmap/conf",
"Target": "/app/conf",
"ReadOnly": true
}
},
"/dev/termination-log": {
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/containers/consume/fc3f9ebf",
"Destination": "/dev/termination-log",
"RW": true,
"Name": "",
"Driver": "",
"Type": "bind",
"Propagation": "rprivate",
"Spec": {
"Type": "bind",
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/containers/consume/fc3f9ebf",
"Target": "/dev/termination-log"
}
},
"/etc/hosts": {
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/etc-hosts",
"Destination": "/etc/hosts",
"RW": true,
"Name": "",
"Driver": "",
"Type": "bind",
"Propagation": "rprivate",
"Spec": {
"Type": "bind",
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/etc-hosts",
"Target": "/etc/hosts"
}
},
"/var/log/contain": {
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~local-volume/pv-3",
"Destination": "/var/log/contain",
"RW": true,
"Name": "",
"Driver": "",
"Type": "bind",
"Propagation": "rprivate",
"Spec": {
"Type": "bind",
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~local-volume/pv-3",
"Target": "/var/log/contain"
}
},
"/tmp/nbbsdownload": {
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~local-volume/pv-3-6",
"Destination": "/tmp/nbbsdownload",
"RW": true,
"Name": "",
"Driver": "",
"Type": "bind",
"Propagation": "rprivate",
"Spec": {
"Type": "bind",
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~local-volume/pv-3-6",
"Target": "/tmp/nbbsdownload"
}
},
"/var/run/secrets/kubernetes.io/serviceaccount": {
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~secret/default-token-g6gr9",
"Destination": "/var/run/secrets/kubernetes.io/serviceaccount",
"RW": false,
"Name": "",
"Driver": "",
"Type": "bind",
"Relabel": "ro",
"Propagation": "rprivate",
"Spec": {
"Type": "bind",
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~secret/default-token-g6gr9",
"Target": "/var/run/secrets/kubernetes.io/serviceaccount",
"ReadOnly": true
}
}
},
"SecretReferences": null,
"AppArmorProfile": "",
"HostnamePath": "/var/lib/docker/containers/fda1d939f5b41e31ca5c5214b4d78b38691d438eeb52d22d4460d49a2a820c1f/hostname",
"HostsPath": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/etc-hosts",
"ShmPath": "/var/lib/docker/containers/fda1d939f5b41e31ca5c5214b4d78b38691d438eeb52d22d4460d49a2a820c1f/shm",
"ResolvConfPath": "/var/lib/docker/containers/fda1d939f5b41e31ca5c5214b4d78b38691d438eeb52d22d4460d49a2a820c1f/resolv.conf",
"SeccompProfile": "unconfined",
"NoNewPrivileges": false
}

如果是k8s启动的contain,会有k8s相应的lable,应用这部分数据

{
"annotation.io.kubernetes.container.hash": "e1a52ef6",
"annotation.io.kubernetes.container.restartCount": "0",
"annotation.io.kubernetes.container.terminationMessagePath": "/dev/termination-log",
"annotation.io.kubernetes.container.terminationMessagePolicy": "File",
"annotation.io.kubernetes.pod.terminationGracePeriod": "30",
"io.kubernetes.container.logpath": "/var/log/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/consume/0.log",
"io.kubernetes.container.name": "consume",
"io.kubernetes.docker.type": "container",
"io.kubernetes.pod.name": "consume-577cd986c7-8mk4h",
"io.kubernetes.pod.namespace": "default",
"io.kubernetes.pod.uid": "00bea8a1-8bfc-11e8-b709-f01fafd51338",
"io.kubernetes.sandbox.id": "fda1d939f5b41e31ca5c5214b4d78babcd1d438eeb52d22d4460d49a2a820c1f"
}

docker的原始日志json file 文件 是

/var/lib/docker/containers/caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f/caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f-json.log

收集这个文件,再把label里的相应数据加上,就是k8s的容器日志了,说穿了很简单,只是很多人不清楚这几个文件的存在罢了。

这里收集的是docker out的日志,另一种普遍的作法是,映射一个本地路径,把文件写在这个目录下。

在容器内执行,收集容器内路径下的日志,或在宿主机执行,收集容器外路径下的日志。

这里最好能和docker out的收集方式统一,用两套完全不可接受

实际实现也很简单,以上的信息里还有这一部分

{
"/var/log/contain": {
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~local-volume/pv-3",
"Destination": "/var/log/contain",
"RW": true,
"Name": "",
"Driver": "",
"Type": "bind",
"Propagation": "rprivate",
"Spec": {
"Type": "bind",
"Source": "/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~local-volume/pv-3",
"Target": "/var/log/contain"
}
}
}

这里mount的映射信息,

/var/log/contain是容器内路径,
/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~local-volume/pv-3是宿主机路径

把这个

/var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~local-volume/pv-3目录也加到收集监控里便ok了

思路已经通了

最后我们要的是类似这样的一个配置文件(不论是不是filebeat,需要的数据就是这些)

补充一点,看下方输出就明白

 ls -alh /var/log/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/consume/0.log
lrwxrwxrwx 1 root root 165 Jul 20 17:05 /var/log/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/consume/0.log -> /var/lib/docker/containers/caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f/caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f-json.log
 
filebeat.inputs:
- type: log
paths:
- /var/lib/docker/containers/caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f/caed8938198152778e3715f4bd3c00795f40c812d2cdb87dadfe8b0bb058390f-json.log
- /var/lib/kubelet/pods/00bea8a1-8bfc-11e8-b709-f01fafd51338/volumes/kubernetes.io~local-volume/pv-3/*.log
fields:
namespace: "default"
name: "consume"
pod_name: "consume-577cd986c7-8mk4h"
output.elasticsearch:
hosts: ["localhost:9200"]
setup.kibana:
host: "localhost:5601"

定时扫描/var/lib/docker/containers/,读取每个contain的config.v2.json,构造配置文件,启动收集进程

因为contain id包含hash值,同名冲突的概率几乎0零,因此也不用考虑任何冲突的问题,每个k8s node独立部署一个脚本即可。

目前实现的方式是用脚本加定时器,实时性稍微差了些,够用了,有工夫的可以优化成文件监听。

kubernets轻量 contain log 日志收集技巧的更多相关文章

  1. Log 日志收集

    class Log { private readonly static String DateFormat = "yyyyMMdd"; private readonly stati ...

  2. filebeat-kafka日志收集

    filebeat-kafka日志收集 由于线上的logstash吃掉大量的CPU,占用较多的系统资源,就想找其它的组件替代.我们的日志需要收集并发送到kafka,生成的日志已经是需要的数据,不用过滤. ...

  3. Docker搭建EFK日志收集系统,并自定义es索引名

    EFK架构图 一.EFK简介 EFK不是一个软件,而是一套解决方案,并且都是开源软件,之间互相配合使用,完美衔接,高效的满足了很多场合的应用,是目前主流的一种日志系统. EFK是三个开源软件的缩写,分 ...

  4. 日志收集系统elk

    目录 elk简介 官方帮助 rsyslog rsyslog日志采集介绍与使用 综合实验 案例一: 单机ELK部署 案例二. JAVA环境配置,部署 filebeat+Elasticsearch apa ...

  5. 阿里云 轻量应用服务器(LAMP) 使用日志记录

    phpStudy(PHP运行环境一键安装包) https://www.jb51.net/softs/182860.html 0:PHP开发工具 https://netbeans.org/downloa ...

  6. ELK 6安装配置 nginx日志收集 kabana汉化

    #ELK 6安装配置 nginx日志收集 kabana汉化 #环境 centos 7.4 ,ELK 6 ,单节点 #服务端 Logstash 收集,过滤 Elasticsearch 存储,索引日志 K ...

  7. 基于elk 实现nginx日志收集与数据分析。

    一.背景 前端web服务器为nginx,采用filebeat + logstash + elasticsearch + granfa 进行数据采集与展示,对客户端ip进行地域统计,监控服务器响应时间等 ...

  8. 转: 基于elk 实现nginx日志收集与数据分析

    原文链接:https://www.cnblogs.com/wenchengxiaopenyou/p/9034213.html 一.背景 前端web服务器为nginx,采用filebeat + logs ...

  9. 海量日志收集利器 —— Flume

    Flume 是什么? Flume是一个分布式.可靠.和高可用的海量日志聚合的系统,支持在系统中定制各类数据发送方,用于收集数据:同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的 ...

随机推荐

  1. 1.3 this深度面试题

    var big = "1" var obj = { big: "2", showBig: function() { return this.big }, } o ...

  2. c++ 字母排序

    char a[123] = {'Z', 's', 'p', 'l', 'j', 'r', 'q', 'v', 'n', 'm', 'C', 'F', 'D', 'B', 'A', '2', '0', ...

  3. eclipse上部署到tomcat不能自动部署maven管理的额jar包

  4. 运行xv6

    我们使用Qemu在Ubuntu下运行 1. 安装Qemu sudo apt-get install qemu 执行 qemu-system-i386 ,如果弹出Qemu界面说明安装成功了 2. 编译x ...

  5. 如何用Python统计《论语》中每个字的出现次数?10行代码搞定--用计算机学国学

    编者按: 上学时听过山师王志民先生一场讲座,说每个人不论干什么,都应该学习国学(原谅我学了计算机专业)!王先生讲得很是吸引我这个工科男,可能比我的后来的那些同学听课还要认真些,当然一方面是兴趣.一方面 ...

  6. oracle 向表中插入BLOB类型数据

    提示: 待插入图片必须保存到oracle主机路径上. 步骤: 1.SYSDBA权限用户创建图片所在目录 CREATE OR REPLACE DIRECTORY TEST_DIR AS 'C:\Pict ...

  7. springMVC源码阅读-通过画图理解一个请求生命周期(十二)

  8. VUE 引用公共样式

    首先创建公共样式文件 在main.js中引用样式 浏览器效果图

  9. PAT Advanced 1094 The Largest Generation (25) [BFS,DFS,树的遍历]

    题目 A family hierarchy is usually presented by a pedigree tree where all the nodes on the same level ...

  10. css块级元素

    <CSS权威指南>中文字显示:任何不是块级元素的可见元素都是内联元素.其表现的特性是“行布局”形式,这里的“行布局”的意思就是说其表现形式始终以行进行显示.比如,我们设定一个内联元素bor ...