• 业务发展越来越庞大,服务器越来越多
• 各种访问日志、应用日志、错误日志量越来越多
• 开发人员排查问题,需要到服务器上查日志,效率低、权限不好控制
• 运维需实时关注业务访问情况
• K8s弹性伸缩性:导致不能预先确定采集的目标 • 容器隔离性:容器的文件系统与宿主机是隔离,导致日志采集器读取日志文件受阻
• 标准输出:输出到控制台,使用kubectl logs可以看到 (比如Nginx 的日志就是输出到控制台)
• 日志文件:写到容器的文件系统的文件(比如Tomcat的日志就是写入到容器中的文件系统的文件)
针对标准输出:以DaemonSet方式在每个Node上部署一个日志收集程序,采集/var/lib/Docker/containers/目录下所有容器日志
针对容器中日志文件:在Pod中增加一个容器运行日志采集器,使用emtyDir共享日志目录让日志采集器读取到日志文件
EFK 是三个开源软件的缩写,提供一套完整的企业级日 志平台解决方案。 分别是:
• Elasticsearch:搜索、分析和存储数据
• Filebeat :是本地文件的日志数据采集器,可监控日志目录或特定日志文件,并将它们转发给Elasticsearch或Logstatsh进行索引、kafka等
• Kibana:数据可视化
主机名 |
IP地址 |
节点信息 |
Master |
192.168.31.61 |
master 节点 8核8G |
Node-1 |
192.168.31.63 |
node 节点 8核12G |
Node-2 |
192.168.31.66 |
node 节点 8核12G |
Node-3 |
192.168.31.67 |
node 节点 8核12G |
NFS |
192.168.31.100 |
nfs 存储节点 8核12G |
软件名 |
版本 |
备注 |
kubernetes |
v1.18.6 |
|
Elasticsearch |
v7.9.2 |
单点 |
Filebeat |
v7.9.2 |
|
Kibana |
v7.9.2 |
|
Nfs-client-provisioner |
v1.2.8 |
动态PV |
# 创建 NFS 存储目录
mkdir -p /home/elk
# 安装nfs服务
yum -y install nfs-utils rpcbind
# 修改配置文件
echo "/home/elk *(rw,sync,no_root_squash,no_subtree_check)" >> /etc/exports
# 启动服务
systemctl start nfs && systemctl start rpcbind
# 设置开机启动
systemctl enable nfs-server && systemctl enable rpcbind
yum -y install nfs-utils
#记住,所有节点都要安装nfs-utils,否则无法使用pv
[root@master-1 ~]# kubectl create ns nfs
namespace/nfs created
注意事项:
(1)、nfs-client-provisioner部署到刚刚创建的nfs命名空间下
(2)、storageClass.name #指定storageClassName名称,用于 PVC 自动绑定专属动态 PV 上
(3)、需要指定NFS服务器的IP 地址(192.168.31.100),以及共享名称路径(/home/elk)
#添加helm charts repo
[root@master-1 es-single-node]# helm repo add helm-stable https://charts.helm.sh/stable
[root@master-1 es-single-node]# helm repo update
cat > elastic-client-nfs.yaml << EOF
# NFS 设置
nfs:
server: 192.168.31.100
path: /home/elk
storageClass:
# 此配置用于绑定 PVC 和 PV
name: elastic-nfs-client
# 资源回收策略
#主要用于绑定的PVC删除后,资源释放后如何处理该PVC在存储设备上写入的数据。
#Retain:保留,删除PVC后,PV保留数据;
#Recycle:回收空间,删除PVC后,简单的清除文件;(NFS和HostPath存储支持)
#Delete:删除,删除PVC后,与PV相连接的后端存储会删除数据;(AWS EBS、Azure Disk、Cinder volumes、GCE PD支持)
reclaimPolicy: Retain
# 使用镜像
image:
repository: kubesphere/nfs-client-provisioner
# 副本数量
replicaCount: 3
EOF
#helm 部署 nfs-client-provisioner
[root@master-1 es-single-node]# helm install elastic-nfs-storage -n nfs --values elastic-client.yaml helm-stable/nfs-client-provisioner --version 1.2.8
[root@master-1 es-single-node]# kubectl get pods -n nfs
NAME READY STATUS RESTARTS AGE
elastic-nfs-storage-nfs-client-provisioner-78c7754777-8kvlg 1/1 Running 0 28m
elastic-nfs-storage-nfs-client-provisioner-78c7754777-vtpn8 1/1 Running 0 28m
elastic-nfs-storage-nfs-client-provisioner-78c7754777-zbx8s 1/1 Running 0 28m
[root@master-1 es-single-node]# kubectl create ns ops
namespace/ops created
cat > elasticsearch.yaml << EOF
apiVersion: Apps/v1
kind: Deployment
metadata:
name: elasticsearch
namespace: ops
labels:
k8s-app: elasticsearch
spec:
replicas: 1
selector:
matchLabels:
k8s-app: elasticsearch
template:
metadata:
labels:
k8s-app: elasticsearch
spec:
containers:
- image: elasticsearch:7.9.2
name: elasticsearch
resources:
limits:
cpu: 2
memory: 3Gi
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
volumes:
- name: elasticsearch-data
persistentVolumeClaim:
claimName: es-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: es-pvc
namespace: ops
spec:
#指定动态PV 名称
storageClassName: "elastic-nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: elasticsearch
namespace: ops
spec:
ports:
- port: 9200
protocol: TCP
targetPort: 9200
selector:
k8s-app: elasticsearch
EOF
[root@master-1 es-single-node]# kubectl apply -f elasticsearch.yaml
deployment.apps/elasticsearch create
persistentvolumeclaim/es-pvc create
service/elasticsearch create
[root@master-1 es-single-node]# kubectl get pod -n ops -l k8s-app=elasticsearch
NAME READY STATUS RESTARTS AGE
elasticsearch-97f7d74f5-qr6d4 1/1 Running 0 2m41s
[root@master-1 es-single-node]# kubectl get service -n ops
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
elasticsearch ClusterIP 10.0.0.126 <none> 9200/TCP 2m41s
cat > kibana.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana
namespace: ops
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.9.2
resources:
limits:
cpu: 2
memory: 4Gi
requests:
cpu: 0.5
memory: 500Mi
env:
- name: ELASTICSEARCH_HOSTS
#指定elasticsearch的servicesname,记得加上命名空间.ops
value: http://elasticsearch.ops:9200
- name: I18N_LOCALE
value: zh-CN
ports:
- containerPort: 5601
name: ui
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: kibana
namespace: ops
spec:
type: NodePort
ports:
- port: 5601
protocol: TCP
targetPort: ui
nodePort: 30601
selector:
k8s-app: kibana
EOF
[root@master-1 es-single-node]# kubectl apply -f kibana.yaml
deployment.apps/kibana create
service/kibana create
[root@master-1 es-single-node]# kubectl get pod -n ops -l k8s-app=kibana
NAME READY STATUS RESTARTS AGE
kibana-5c96d89b65-zgphp 1/1 Running 0 7m
[root@master-1 es-single-node]# kubectl get service -n ops
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kibana NodePort 10.0.0.164 <none> 5601:30601/TCP 7m
输入kibana 地址: http://nodeIP:30601
大致思路:
以DaemonSet方式在每个Node上部署一个Filebeat 的日志收集程序的Pod,采用hostPath 方式把 /var/lib/docker/containers 挂载到Filebeat 容器中,/var/lib/docker/containers 目录下的就是每个容器标准输出的日志
cat > filebeat-kubernetes.yaml << EOF
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: ops
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
output.elasticsearch:
hosts: ['elasticsearch.ops:9200']
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-inputs
namespace: ops
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: ops
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.9.2
args: [
"-c", "/etc/filebeat.yml",
"-e",
]
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/v1
kind: ClusterRoleBinding
metadata:
name: filebeat
subjects:
- kind: ServiceAccount
name: filebeat
namespace: ops
roleRef:
kind: ClusterRole
name: filebeat
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
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: ops
labels:
k8s-app: filebeat
EOF
[root@master-1 es-single-node]# kubectl apply -f filebeat-kubernetes.yaml
configmap/filebeat-config create
configmap/filebeat-inputs create
daemonset.apps/filebeat create
clusterrolebinding.rbac.authorization.k8s.io/filebeat create
clusterrole.rbac.authorization.k8s.io/filebeat create
serviceaccount/filebeat create
[root@master-1 es-single-node]# kubectl get pods -n ops -l k8s-app=node-exporter
NAME READY STATUS RESTARTS AGE
node-exporter-j72cb 1/1 Running 10 13d
node-exporter-k6d7v 1/1 Running 10 13d
node-exporter-vhgns 1/1 Running 10 13d
索引管理:
(一般只要有数据入到ES中就会有索引出现 ,如果没有出现可以试着访问下业务使其产生日志输出到ES中)
点击 左边的 Stack Management 中的 索引管理 可以看到一个名词为filebeat-7.9.2-2021.03.01-000001的索引,状态为open
添加索引模式:
点击 左边的 Stack Management 中的索引模式,创建索引模式
输入索引模式名称: filebeat-7.9.2-*
表示可以匹配到上面的索引 filebeat-7.9.2-2021.03.01-000001
选择@timestamp 时间字段
cat > app-log-stdout.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-log-stdout
spec:
replicas: 3
selector:
matchLabels:
project: stdout-test
app: nginx-stdout
template:
metadata:
labels:
project: stdout-test
app: nginx-stdout
spec:
containers:
- name: nginx
image: nginx
---
apiVersion: v1
kind: Service
metadata:
name: app-log-stdout
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
project: stdout-test
app: nginx-stdout
EOF
[root@master-1 es-single-node]# kubectl apply -f app-log-stdout.yaml
deployment.apps/app-log-stdout created
service/app-log-stdout created
[root@master-1 es-single-node]# kubectl get pods -l app=nginx-stdout
NAME READY STATUS RESTARTS AGE
app-log-stdout-76fb86fcf6-cjch4 1/1 Running 0 2m34s
app-log-stdout-76fb86fcf6-wcfqm 1/1 Running 0 2m34s
app-log-stdout-76fb86fcf6-zgzcc 1/1 Running 0 2m34s
[root@master-1 es-single-node]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
app-log-stdout ClusterIP 10.0.0.167 <none> 80/TCP 2m41s
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 63d
[root@node-1 ~]# curl 10.0.0.167
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
检索的语句: kubernetes.namespace : "default" and message : "curl"
可以看到有1个 日志被命中了
本期的分享先分享到这里,下期接着分享 EFK 日志系统收集K8s日志之 容器中日志文件