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
的索引