- 架构选型
- 业务容器直连
- Node DaemonSet
- sidecar
- 部署模型
- 查看helm的渲染结果
- 部署模型
- 部署ELK stack
- 部署elk & kibana
- 注意点
- 资源限制
- pod反亲和性策略
- 持久化PV
- NodePort端口开放
- 部署NFS Persistent Volume
- 部署filebeat
- 概念验证
- 总结
默认情况下,如果Pod在节点被删除,日志就被删除了。因此,为了保留日志,我们需要调研一个日志集中存储方案。
架构选型根据Kubernetes官方文档:https://kubernetes.io/docs/concepts/cluster-administration/logging/
架构方案大致是分成三种
- 业务容器直连
- Node DaemonSet
- Sidecar
集成日志中间件的SDK,由业务容器直接发送日志到elasticsearch中间件。
- 缺点:
中间件和日志中间件耦合性比较强,还需要处理网络,命名空间等各种kubernetes相关信息。因此是不推荐的。
DaemonSet会在每个Node节点运行一个agent。由filebeat agent监控docker容器日志,发送到elastcsearch.
本文实现使用的是这种方案。
sidecarapp-container将日志写入到文件,streaming container 作为sidecar容器,处理日志内容,打印到stdout和stderr. 当然,相对于filebeat DaemonSet Pod而言,一切都没有变。
这种方案资源占用虽然较多,不过可以提供复杂日志信息过滤能力,更好的租户隔离性。
基于Kubernetes Volume这种方式可以很轻松的实现,Volume和Pod具有相同的声明周期,用于临时处于同一Pod的容器共享存储。
如下声明了一个Pod,有两个容器,分别是count(模拟业务容器)和watcher(模拟sidecar)容器,创建了一个Volume用于容器间共享存储。
count不在将日志写入标准输出流,而是写到日志文件中。
watcher将处理日志文件,输出到stdout和stderr
apiVersion: v1
kind: Pod
metadata:
name: sleeper
spec:
containers:
- name: count
image: busybox
args: [/bin/sh, -c,
'i=0; while true; do echo "$i: $(date) we are good" >> /cache/hello.log ; i=$((i+1)); sleep 1; done']
volumeMounts:
- mountPath: /cache
name: cache-volume
- name: watcher
image: busybox
args: [/bin/sh, -c,
'tail -f /cache/hello.log']
volumeMounts:
- mountPath: /cache
name: cache-volume
# 共享存储Volume
volumes:
- name: cache-volume
emptyDir: {}
部署模型
查看helm的渲染结果
helm chart是一种模板语法,你可以通过
# elasticsearch 为部署名 helm get manifest elasticsearch
查看helm的资源文件清单。
部署模型elk的部署模型为一个3实例的高可用集群。
如下所示:
集群中每个Pod的身份是不一样的,有主从角色,所以要使用到kubernetes的StatefulSet和Headless Service.
helm chart的渲染结果同时有两个Service,一个是正常的,一个是headless类型的。
# Source: elasticsearch/templates/service.yaml
kind: Service
apiVersion: v1
metadata:
name: elasticsearch-master
spec:
type: NodePort
selector:
release: "elk"
chart: "elasticsearch"
app: "elasticsearch-master"
ports:
- name: http
protocol: TCP
port: 9200
nodePort: 32000
- name: transport
protocol: TCP
port: 9300
---
# Source: elasticsearch/templates/service.yaml
kind: Service
apiVersion: v1
metadata:
name: elasticsearch-master-headless
spec:
clusterIP: None # This is needed for statefulset hostnames like elasticsearch-0 to resolve
# Create endpoints also if the related pod isn't ready
publishNotReadyAddresses: true
selector:
app: "elasticsearch-master"
ports:
- name: http
port: 9200
- name: transport
port: 9300
正常的Service通常都有一个ClusterIP, 服务通过DNS名 ${service name} 访问时,会解析为ClusterIP。但是Headless Service被访问时,会被解析为Pod的IP。
因此elk的配置发现节点的配置时,选择的是headless 服务。
- name: discovery.seed_hosts
value: "elasticsearch-master-headless" # 会被解析为多个用","分隔的ip
当filebeat和kibana访问elasticsearch时,使用的是普通Service。
部署ELK stack下面以Node DaemonSet方式实现集中化日志采集。filebeat作为logging agent, elasticsearch作为logging backend.最后通过Kibana查看日志。
基于以下组件实现kubernetes环境的日志采集,做一个快速的概念验证:
| 组件 | version |
|---|---|
| elasticsearch | 7.16.2 |
| kibana | 7.16.2 |
| filebeat | 7.16.2 |
ELK技术栈是典型的有状态应用,本文基于开源项目:https://github.com/elastic/helm-charts.git
部署elk & kibana请安装helm v3
helm repo add elastic https://helm.elastic.co helm install elasticsearch elastic/elasticsearch helm install kibana elastic/kibana注意点
需要helm基础知识
需要修改chart, chart开放了values.yaml用于参数微调。因此下面的配置都在value.yaml下调整。
helm install下载完成之后,chart在用户目录下,以tgz文件格式存在:
# ls /root/.cache/helm/repository/ elastic-charts.txt elastic-index.yaml elasticsearch-7.16.2.tgz kibana-7.16.2.tgz
将tgz文件解压,需要调整values.yaml的参数,重新upgrade一遍。
tar xf elasticsearch-7.16.2.tgz # 修改完values.yaml # elk 部署名 elasticsearch/ tgz解压形成的文件 helm install elk elasticsearch/资源限制
我的机器是8G的,无法运行三实例elasticsearch和kibana。因此可以调整副本数量为1
replicas: 1pod反亲和性策略
默认chart使用了部署了3个elk实例组成高可用集群,使用pod反亲和性策略,elk集群节点不能处于同一kubernetes节点。
可以修改values.yaml:
# Hard means that by default pods will only be scheduled if there are enough nodes for them # and that they will never end up on the same node. Setting this to soft will do this "best effort" antiAffinity: "soft"持久化PV
- 使用NFS进行持久化PV供应,需要调整NFS文件夹权限
# cat /etc/exports /data/share *(rw,sync,insecure,no_subtree_check,no_root_squash)
- 修改共享文件夹的权限:
如果不修改共享文件夹的权限,会碰到elasticsearch无法访问持久化卷的情况,报错如下:
How to fix ElasticSearch docker AccessDeniedException[/usr/share/elasticsearch/data/nodes];”,
解决方案:
sudo chown -R 1000:1000 [directory]
- 原因
根据Kubernetes Pod Security Context:,容器可以设置以某些用户身份运行,比如elk就是以用户ID 1000运行的
securityContext:
fsGroup: 1000
runAsUser: 1000
automountServiceAccountToken: true
所以将pv的用户权限全部改为UID 1000用户所有就没问题了。
NodePort端口开放- elk
service:
enabled: true
labels: {}
labelsHeadless: {}
type: NodePort # 修改为NodePort方式
nodePort: "32000" # 指定具体的NodePort端口
- kibana
service: type: NodePort loadBalancerIP: "" port: 5601 nodePort: "30090"部署NFS Persistent Volume
将以下内容保存为pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv1
spec:
capacity:
storage: 60Gi
accessModes:
- ReadWriteonce
nfs:
server: 192.168.10.2
path: "/data/share"
创建PV
kubectl apply -f pv.yaml部署filebeat
kubectl apply -f https://raw.githubusercontent.com/elastic/beats/7.16/deploy/kubernetes/filebeat-kubernetes.yaml
默认filebeat会部署在kube-system命名空间里,你可以修改filebeat连接elastic的地址为${NODE_IP}:${nodePort}
env:
- name: ELASTICSEARCH_HOST
value: elasticsearch
- name: ELASTICSEARCH_PORT
value: "9200"
概念验证
部署一个busybox Pod,定期输出日志,在kibana里查看日志。
busybox.yaml 在第三种方案的样例里
可以通过namespace, pod名,deployment名字筛选查看日志了。
将elk部署至kubernetes需要考虑高可用性,有状态服务,持久化存储,kubernetes的RBAC权限等问题。部署的最佳实践当然是使用开源的helm chart,然后在开发环境先适用一段时间,根据实际环境进行优化。
日志集中化处理的主要优点是,在kubernetes这种动态环境中,你可以看到消亡的Pod日志,做崩溃原因分析。这是kubectl logs 做不到的。



