ELK收集Kubernetes平台日志
如果把日志保存在容器内部或通过数据卷挂载在宿主机上还是保持在远程存储上,比如保存在容器内部,也就是说没有经过任何改动,自是在容器里原封不动的启动了,起来之后日志还是和原来一样保持在原来的目录里,但是这个容器是会经常的删除,销毁和创建是常态。因此我们需要一种持久化的保存日志方式。
如果日志还是放在容器内部,会随着容器删除而被删除
容器数量很多,按照传统的查看日志方式变得不太现实
容器本身特性
容器密集,采集目标多:容器日志输出到控制台,docker本身提供了一种能力来采集日志了。如果落地到本地文件目前还没有一种好的采集方式
容器的弹性伸缩:新扩容的pod属性信息(日志文件路径,日志源)可能会发送变化
收集那些日志
K8S系统的组件日志和应用程序日志,组件日志就是打到宿主机的固定文件和传统的日志收集一样,应用程序日志又分为了标准输出和日志文件。这里以nginx和tomcat说明下这两种方式
用docker先运行一个nginx
[root@k8s02 ~]# docker run -d nginx
feed659283b4adb5d24f0bc736ed83be8130ecdbd52d9cc2a7424ede108d0de3
[root@k8s02 ~]#
测试访问容器
[root@k8s02 ~]# curl 172.17.0.2
查看nginx日志
[root@k8s02 ~]# docker logs determined_feynman
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
172.17.0.1 - - [04/Jun/2020:14:38:24 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
[root@k8s02 ~]#
这是docker将这个日志进行了接管,这实际是控制台的日志,进入nginx看看日志
[root@k8s02 ~]# docker exec -it determined_feynman /bin/bash
root@feed659283b4:/# cd /var/log/nginx/
root@feed659283b4:/var/log/nginx# ls -l
total 0
lrwxrwxrwx 1 root root 11 Jun 2 00:35 access.log -> /dev/stdout
lrwxrwxrwx 1 root root 11 Jun 2 00:35 error.log -> /dev/stderr
root@feed659283b4:/var/log/nginx#
官方在写dockerfile时将这两个日志文件给输出到了控制台,就是标准输出和错误输出都输出到控制台中,控制台的日志都可以通过docker logs 访问到。
用docker运行一个tomcat
[root@k8s03 ~]# docker run -d tomcat
40c6b2d99726544c93973bd42297ce14d1e820979a0463fb4a99d1e3a493a579
[root@k8s03 ~]#
进入到tomcat里面
[root@k8s03 ~]# docker exec -it flamboyant_kepler /bin/bash
root@40c6b2d99726:/usr/local/tomcat# cd logs/
root@40c6b2d99726:/usr/local/tomcat/logs# ls -l
total 8
-rw-r----- 1 root root 4790 Jun 4 14:36 catalina.2020-06-04.log
-rw-r----- 1 root root 0 Jun 4 14:36 host-manager.2020-06-04.log
-rw-r----- 1 root root 0 Jun 4 14:36 localhost.2020-06-04.log
-rw-r----- 1 root root 0 Jun 4 14:36 localhost_access_log.2020-06-04.txt
-rw-r----- 1 root root 0 Jun 4 14:36 manager.2020-06-04.log
root@40c6b2d99726:/usr/local/tomcat/logs#
会发现是有日志的,tomcat产生的文件有一部分会产生到控制台,一部分落到本地的文件中,他没有将这些日志做重定向,而是存在于容器内部的,如果容器删除的话,日志也会删除。
总结
根据以上镜像的情况,归出了两种日志体现,一个是标准输出,一个是日志输出
如果使用标准的日志输出,其实docker已经涉及到这一块了,比如容器→docker→文件系统,日志驱动在容器通过控制台日志输出由docker接管了,可以docker logs可以看到,执行docker logs时是调用了守护进程,守护进程读取的这个接管的日志从本地文件系统中读取这个日志,日志路径在一下位置
[root@k8s02 ~]# cd /var/lib/docker/containers/d8ab1b4e6a01d9124ad1d07bf852bf69e2768ecdc5dd0a644e97518537ea2932/
[root@k8s02 d8ab1b4e6a01d9124ad1d07bf852bf69e2768ecdc5dd0a644e97518537ea2932]# ll
total 16
drwx------ 2 root root 6 Jun 4 22:41 checkpoints
-rw------- 1 root root 6801 Jun 4 22:41 config.v2.json
-rw-r----- 1 root root 666 Jun 4 22:41 d8ab1b4e6a01d9124ad1d07bf852bf69e2768ecdc5dd0a644e97518537ea2932-json.log
-rw-r--r-- 1 root root 2153 Jun 4 22:41 hostconfig.json
drwx------ 2 root root 6 Jun 4 22:41 mounts
可以看到有一个为容器ID开头的json文件,实际上它是从这里来读取日志响应到控制台的,docker还支持fluned、syslog等采集工具
如果使用日志文件写到容器中比如tomcat这样的,一般的做法通过 docker bind mount或volumes将他挂载到宿主机上,去采集宿主机的目录。
ELK日志收集的三个方案
大致分为采集阶段→数据存储→分析→展示
方式 | 优点 | 缺点 |
---|---|---|
Node上部署一个日志收集程序 | 每个Nodej仅需部署一个日志收集程序,资源消耗少,对应用无侵入 | 应用程序日志如果写到标准输出和标准错误输出,那就不支持多行日志。 |
Pod中附加专用日志收集的容器 | 低耦合 | 每个Pod启动一个日志收集代理,增加资源消耗,并增加运维维护成本 |
应用程序直接推送日志 | 无需额外收集工具 | 侵入应用,增加应用复杂度 |
Node上部署一个日志收集程序
DaemonSet方式部署日志收集程序,对本节点/var/log/pods/
或/var/lib/docker/containers/
两个目录下的日志进行收集
Pod中附加专用日志收集的容器
每个运行应用程序的Pod中增加一个日志收集容器,使用emtyDir共享日志目录让日志收集程序读取到
应用程序直接推送日志
应用程序直接将日志推送到远程存储上,不经过docker的管理和kubernetes的管理
在K8S中部署ELK
部署elasticsearch
[root@k8s01 yml]# cat elasticsearch.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: elasticsearch
namespace: kube-system
labels:
k8s-app: elasticsearch
spec:
serviceName: elasticsearch
selector:
matchLabels:
k8s-app: elasticsearch
template:
metadata:
labels:
k8s-app: elasticsearch
spec:
containers:
- image: elasticsearch:7.5.0
name: elasticsearch
resources:
limits:
cpu: 1
memory: 2Gi
requests:
cpu: 0.5
memory: 500Mi
env:
- name: "discovery.type"
value: "single-node"
- name: ES_JAVA_OPTS
value: "-Xms512m -Xmx2g"
ports:
- containerPort: 9200
name: db
protocol: TCP
volumeMounts:
- name: elasticsearch-data
mountPath: /usr/share/elasticsearch/data
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
storageClassName: "managed-nfs-storage"
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 20Gi
---
apiVersion: v1
kind: Service
metadata:
name: elasticsearch
namespace: kube-system
spec:
clusterIP: None
ports:
- port: 9200
protocol: TCP
targetPort: db
selector:
k8s-app: elasticsearch
开始创建
[root@k8s01 yml]# kubectl create -f elasticsearch.yaml
statefulset.apps/elasticsearch created
service/elasticsearch created
[root@k8s01 yml]# kubectl get pods -n kube-system elasticsearch-0
NAME READY STATUS RESTARTS AGE
elasticsearch-0 1/1 Running 0 4m50s
[root@k8s01 yml]#
部署kibana
[root@k8s01 yml]# cat kibana.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana
namespace: kube-system
labels:
k8s-app: kibana
spec:
replicas: 1
selector:
matchLabels:
k8s-app: kibana
template:
metadata:
labels:
k8s-app: kibana
spec:
containers:
- name: kibana
image: kibana:7.5.0
resources:
limits:
cpu: 1
memory: 500Mi
requests:
cpu: 0.5
memory: 200Mi
env:
- name: ELASTICSEARCH_HOSTS
value: http://elasticsearch-0.elasticsearch.kube-system:9200
- name: I18N_LOCALE
value: zh-CN
ports:
- containerPort: 5601
name: ui
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: kibana
namespace: kube-system
spec:
type: NodePort
ports:
- port: 5601
protocol: TCP
targetPort: ui
nodePort: 30601
selector:
k8s-app: kibana
开始创建
[root@k8s01 yml]# kubectl create -f kibana.yaml
deployment.apps/kibana created
service/kibana created
[root@k8s01 yml]# kubectl get pods -n kube-system kibana-6cd7b9d48b-jrx79
NAME READY STATUS RESTARTS AGE
kibana-6cd7b9d48b-jrx79 1/1 Running 0 3m3s
[root@k8s01 yml]# kubectl get svc -n kube-system kibana
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kibana NodePort 10.0.0.132 <none> 5601:30601/TCP 4m17s
[root@k8s01 yml]#
filebeat采集标准输出日志
filebeat支持动态的获取容器的日志
[root@k8s01 yml]# cat filebeat-kubernetes.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: kube-system
labels:
k8s-app: filebeat
data:
filebeat.yml: |-
filebeat.config:
inputs:
# Mounted `filebeat-inputs` configmap:
path: ${path.config}/inputs.d/*.yml
# Reload inputs configs as they change:
reload.enabled: false
modules:
path: ${path.config}/modules.d/*.yml
# Reload module configs as they change:
reload.enabled: false
# To enable hints based autodiscover, remove `filebeat.config.inputs` configuration and uncomment this:
#filebeat.autodiscover:
# providers:
# - type: kubernetes
# hints.enabled: true
output.elasticsearch:
hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-inputs
namespace: kube-system
labels:
k8s-app: filebeat
data:
kubernetes.yml: |-
- type: docker
containers.ids:
- "*"
processors:
- add_kubernetes_metadata:
in_cluster: true
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat
namespace: kube-system
labels:
k8s-app: filebeat
spec:
selector:
matchLabels:
k8s-app: filebeat
template:
metadata:
labels:
k8s-app: filebeat
spec:
serviceAccountName: filebeat
terminationGracePeriodSeconds: 30
containers:
- name: filebeat
image: elastic/filebeat:7.5.0
args: [
"-c", "/etc/filebeat.yml",
"-e",
]
env:
- name: ELASTICSEARCH_HOST
value: elasticsearch-0.elasticsearch.kube-system
- name: ELASTICSEARCH_PORT
value: "9200"
securityContext:
runAsUser: 0
# If using Red Hat OpenShift uncomment this:
#privileged: true
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
volumeMounts:
- name: config
mountPath: /etc/filebeat.yml
readOnly: true
subPath: filebeat.yml
- name: inputs
mountPath: /usr/share/filebeat/inputs.d
readOnly: true
- name: data
mountPath: /usr/share/filebeat/data
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: config
configMap:
defaultMode: 0600
name: filebeat-config
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: inputs
configMap:
defaultMode: 0600
name: filebeat-inputs
# data folder stores a registry of read status for all files, so we don't send everything again on a Filebeat pod restart
- name: data
hostPath:
path: /var/lib/filebeat-data
type: DirectoryOrCreate
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: filebeat
subjects:
- kind: ServiceAccount
name: filebeat
namespace: kube-system
roleRef:
kind: ClusterRole
name: filebeat
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: filebeat
labels:
k8s-app: filebeat
rules:
- apiGroups: [""] # "" indicates the core API group
resources:
- namespaces
- pods
verbs:
- get
- watch
- list
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: filebeat
namespace: kube-system
labels:
k8s-app: filebeat
这里指定了es的路径
output.elasticsearch:
hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
这里是一个处理器,他会自动的为日志添加k8s属性。传统配置日志采集工具重要的参数是什么呢?日志路径、日志源、写正则,格式化日志。add_kubernetes_metadata这个处理器可以自动的为k8s添加日志属性信息
data:
kubernetes.yml: |-
- type: docker
containers.ids:
- "*"
processors:
- add_kubernetes_metadata:
in_cluster: true
这里使用了hostpath挂载了docker的工作目录
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
开始创建
[root@k8s01 yml]# kubectl apply -f filebeat-kubernetes.yaml
configmap/filebeat-config created
configmap/filebeat-inputs created
daemonset.apps/filebeat created
clusterrolebinding.rbac.authorization.k8s.io/filebeat created
clusterrole.rbac.authorization.k8s.io/filebeat created
serviceaccount/filebeat created
[root@k8s01 yml]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-6d8cfdd59d-cbhzg 1/1 Running 0 51m
elasticsearch-0 1/1 Running 0 42m
filebeat-bfd9t 1/1 Running 0 3m15s
filebeat-gbf5f 1/1 Running 0 3m15s
filebeat-sw4z8 1/1 Running 0 3m15s
kibana-6cd7b9d48b-jrx79 1/1 Running 0 35m
kube-flannel-ds-amd64-ghlkp 1/1 Running 1 61m
kube-flannel-ds-amd64-lrq8q 1/1 Running 1 61m
kube-flannel-ds-amd64-vtmf4 1/1 Running 1 61m
起来之后就会自动采集日志
收集日志中落盘的日志文件
收集/var/log/message
的日志,在所有node
上部署一个filebeat
,也就是用daemonsets
去部署,挂载宿主机的messages
文件到容器,编写配置文件去读message
文件就可以了撒,所以YAML
文件如下,Configmap
和daemonset
写到一起了
[root@k8s01 yml]# cat k8s-logs.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: k8s-logs-filebeat-config
namespace: kube-system
data:
filebeat.yml: |
filebeat.inputs:
- type: log
paths:
- /var/log/messages
fields:
app: k8s
type: module
fields_under_root: true
setup.ilm.enabled: false
setup.template.name: "k8s-module"
setup.template.pattern: "k8s-module-*"
output.elasticsearch:
hosts: ['elasticsearch-0.elasticsearch.kube-system:9200']
index: "k8s-module-%{+yyyy.MM.dd}"
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: k8s-logs
namespace: kube-system
spec:
selector:
matchLabels:
project: k8s
app: filebeat
template:
metadata:
labels:
project: k8s
app: filebeat
spec:
containers:
- name: filebeat
image: elastic/filebeat:7.5.0
args: [
"-c", "/etc/filebeat.yml",
"-e",
]
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 500m
memory: 500Mi
securityContext:
runAsUser: 0
volumeMounts:
- name: filebeat-config
mountPath: /etc/filebeat.yml
subPath: filebeat.yml
- name: k8s-logs
mountPath: /var/log/messages
volumes:
- name: k8s-logs
hostPath:
path: /var/log/messages
- name: filebeat-config
configMap:
name: k8s-logs-filebeat-config
这里主要将宿主机的目录挂载到容器中直接通过filebeat进行收集
volumeMounts:
- name: filebeat-config
mountPath: /etc/filebeat.yml
subPath: filebeat.yml
- name: k8s-logs
mountPath: /var/log/messages
volumes:
- name: k8s-logs
hostPath:
path: /var/log/messages
- name: filebeat-config
configMap:
name: k8s-logs-filebeat-config
开始创建
[root@k8s01 yml]# kubectl apply -f k8s-logs.yaml
configmap/k8s-logs-filebeat-config created
daemonset.apps/k8s-logs created
[root@k8s01 yml]#
[root@k8s01 yml]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-6d8cfdd59d-cbhzg 1/1 Running 0 65m
elasticsearch-0 1/1 Running 0 55m
filebeat-bfd9t 1/1 Running 0 16m
filebeat-gbf5f 1/1 Running 0 16m
filebeat-sw4z8 1/1 Running 0 16m
k8s-logs-5q9k6 1/1 Running 0 27s
k8s-logs-7t7jr 1/1 Running 0 27s
k8s-logs-kz8hz 1/1 Running 0 27s
kibana-6cd7b9d48b-jrx79 1/1 Running 0 48m
kube-flannel-ds-amd64-ghlkp 1/1 Running 1 74m
kube-flannel-ds-amd64-lrq8q 1/1 Running 1 74m
kube-flannel-ds-amd64-vtmf4 1/1 Running 1 74m
[root@k8s01 yml]#
去kibana
看一眼,没意外的话会有一个名为k8s-module
的索引