kubernets轻量 contain log 日志收集技巧

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

 

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

 

不想看字的可以直接看代码,一共没几行,尽量用调用本地方法实现,有工夫的可以改写成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
}
View Code

 

如果是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独立部署一个脚本即可。

 

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

posted @ 2018-07-28 07:11  cclient  阅读(1107)  评论(0编辑  收藏  举报