目录
一、安装存储日志组件 Elasticsearch
1、 1创建名称空间;
1、 2安装elasticsearch组件;
1)创建 headless service 服务
2)通过 statefulset 创建 elasticsearch 集群
二、安装 kibana 可视化 UI 界面
本篇文章所用到的资料文件下载地址:
一、安装存储日志组件 Elasticsearch
1.1 创建名称空间
在安装Elasticsearch 集群之前,我们先创建一个名称空间,在这个名称空间下安装日志收工具 elasticsearch、fluentd、kibana。我们创建一个 kube-logging 名称空间,将 EFK 组件安装到该名称空间中。
[root@k8s-master1 ~]# mkdir efk
[root@k8s-master1 ~]# cd efk/
[root@k8s-master1 efk]# kubectl create namespace kube-logging
namespace/kube-logging created
# 查看 kube-logging 名称空间是否创建成功
[root@k8s-master1 efk]# kubectl get ns
NAME STATUS AGE
default Active 8d
kube-logging Active 7s
kube-node-lease Active 8d
kube-public Active 8d
kube-system Active 8d
1.2 安装 elasticsearch 组件
通过上面步骤已经创建了一个名称空间 kube-logging,在这个名称空间下去安装日志收集组件 efk。首先,我们需要部署一个有 3 个节点的 Elasticsearch 集群,我们使用 3 个 Elasticsearch Pods 可以避免高可用中的多节点群集中发生的“裂脑”的问题。
Elasticsearch 脑裂参考地址:Node | Elasticsearch Guide [8.6] | Elastic
-
1)创建 headless service 服务
创建一个 headless service 的 Kubernetes 服务,服务名称是 elasticsearch,这个服务将为 3 个 Pod 定义一个 DNS 域。headless service 不具备负载均衡也没有 IP。
要了解有关 headless service 的更多信息,可参考:服务(Service) | Kubernetes
[root@k8s-master1 efk]# vim elasticsearch_svc.yaml
kind: Service
apiVersion: v1
metadata:
name: elasticsearch
namespace: kube-logging
labels:
app: elasticsearch
spec:
selector:
app: elasticsearch
clusterIP: None
ports:
- port: 9200
name: rest
- port: 9300
name: inter-node
在kube-logging 名称空间定义了一个名为 elasticsearch 的 Service服务,带有app=elasticsearch 标签,当我们将 Elasticsearch StatefulSet 与此服务关联时,服务将返回带有标签 app=elasticsearch 的 Elasticsearch Pods 的 DNS 记录,然后设置 clusterIP=None,将该服务设置成无头服务。最后,我们分别定义端口 9200、9300,分别用于与 REST API 交互,以及用于节点间通信。
[root@k8s-master1 efk]# kubectl apply -f elasticsearch_svc.yaml
service/elasticsearch created
[root@k8s-master1 efk]# kubectl get svc -n kube-logging
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
elasticsearch ClusterIP None <none> 9200/TCP,9300/TCP 7s
现在我们已经为 Pod 设置了无头服务和一个稳定的域名 .elasticsearch.kube-logging.svc.cluster.local,接下来我们通过 StatefulSet 来创建具体的 Elasticsearch 的 Pod 应用。
-
2)通过 statefulset 创建 elasticsearch 集群
- 创建 Storageclass,实现存储类动态供给:
1、安装 nfs 服务
# 在各个节点安装 nfs 服务
yum install nfs-utils -y
# 启动 nfs 服务
systemctl enable nfs --now
# 在 master1 上创建一个 nfs 共享目录
[root@k8s-master1 efk]# mkdir -pv /data/v1
# 编辑 /etc/exports 文件
[root@k8s-master1 efk]# vim /etc/exports
/data/v1 192.168.78.0/24(rw,no_root_squash)
# 加载配置,使配置生效
[root@k8s-master1 efk]# exportfs -arv
exporting 192.168.78.0/24:/data/v1
[root@k8s-master1 efk]# systemctl restart nfs
2、创建 nfs 作为存储的供应商
# 创建 sa
[root@k8s-master1 efk]# kubectl create serviceaccount nfs-provisioner
serviceaccount/nfs-provisioner created
[root@k8s-master1 efk]# kubectl get sa
NAME SECRETS AGE
default 1 8d
nfs-provisioner 1 4s
# 对 sa 做 rbac 授权
[root@k8s-master1 efk]# vim rbac.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["nfs-provisioner"]
verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-provisioner
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-provisioner
apiGroup: rbac.authorization.k8s.io
[root@k8s-master1 efk]# kubectl apply -f rbac.yaml
注意:k8s-v1.20+ 版本通过 nfs provisioner 动态生成 pv 会报错,信息如下:
Unexpected error getting claim reference to claim "default/test-claim1": selfLink was empty, can't make reference
报错原因是 1.20 版本启用了 selfLink,解决方法如下:
[root@k8s-master1 efk]# vim /etc/kubernetes/manifests/kube-apiserver.yaml
******
spec:
containers:
- command:
- kube-apiserver
- --feature-gates=RemoveSelfLink=false # 添加这一行内容
******
[root@k8s-master1 efk]# kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
[root@k8s-master1 efk]# kubectl get pods -n kube-system | grep apiserver
kube-apiserver 0/1 CrashLoopBackOff 1 (14s ago) 16s
kube-apiserver-k8s-master1 1/1 Running 0 117s
# 重新更新 apiserver.yaml 会有生成一个新的 pod:kube-apiserver,这个 pod 状态是 CrashLoopBackOff,需要删除
[root@k8s-master1 efk]# kubectl delete pods -n kube-system kube-apiserver
把nfs-client-provisioner.tar.gz 上传到 node1、node2 节点,手动解压
[root@k8s-node1 ~]# docker load -i nfs-client-provisioner.tar.gz
[root@k8s-node2 ~]# docker load -i nfs-client-provisioner.tar.gz
# 通过 deployment 创建 pod 用来运行 nfs-provisioner
[root@k8s-master1 efk]# vim deployment.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-provisioner
spec:
selector:
matchLabels:
app: nfs-provisioner
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-provisioner
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-provisioner
image: registry.cn-hangzhou.aliyuncs.com/open-ali/xianchao/nfs-client-provisioner:v1
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: example.com/nfs
- name: NFS_SERVER
value: 192.168.78.143 # 这个需要写 nfs 服务端所在的 ip 地址,即安装了 nfs 服务的机器 ip
- name: NFS_PATH
value: /data/v1 # 这个是 nfs 服务端共享的目录
volumes:
- name: nfs-client-root
nfs:
server: 192.168.78.143
path: /data/v1
[root@k8s-master1 efk]# kubectl apply -f deployment.yaml
deployment.apps/nfs-provisioner created
[root@k8s-master1 efk]# kubectl get pods | grep nfs
nfs-provisioner-6988f7c774-nk8x5 1/1 Running 0 7s
# 创建 stoorageclass
[root@k8s-master1 efk]# vim class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: do-block-storage
provisioner: example.com/nfs # 该值需要和 nfs provisioner 配置的 PROVISIONER_NAME 的 value 值保持一致
[root@k8s-master1 efk]# kubectl apply -f class.yaml
[root@k8s-master1 efk]# kubectl get storageclasses
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
do-block-storage example.com/nfs Delete Immediate false 65s
- 安装 elasticsearch 集群
把elasticsearch_7_2_0.tar.gz 和 busybox.tar.gz 文件上传到 node1、node2,手动解压:
[root@k8s-node1 ~]# docker load -i elasticsearch_7_2_0.tar.gz
[root@k8s-node1 ~]# docker load -i busybox.tar.gz
[root@k8s-node2 ~]# docker load -i elasticsearch_7_2_0.tar.gz
[root@k8s-node2 ~]# docker load -i busybox.tar.gz
elasticsearch-statefulset.yaml 文件解释说明:
[root@k8s-master1 efk]# vim elasticsearch-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: es-cluster
namespace: kube-logging
spec:
serviceName: elasticsearch
replicas: 3
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
*****
上面内容的解释:在 kube-logging 的名称空间中定义了一个 es-cluste r的 StatefulSet。然后,我们使用 serviceName 字段与我们之前创建的 headless ElasticSearch 服务相关联。这样可以确保可以使用以下 DNS 地址访问 StatefulSet 中的每个 Pod:
es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local,其中 [0,1,2] 与 Pod 分配的序号数相对应。我们指定 3 个 replicas(3 个 Pod 副本),将 selector matchLabels 设置为 app: elasticseach。该 .spec.selector.matchLabels 和 .spec.template.metadata.labels 字段必须匹配。
# statefulset 中定义 pod 模板,内容如下:
*****
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
imagePullPolicy: IfNotPresent
resources:
limits:
cpu: 1000m
requests:
cpu: 100m
ports:
- containerPort: 9200
name: rest
protocol: TCP
- containerPort: 9300
name: inter-node
protocol: TCP
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
env:
- name: cluster.name
value: k8s-logs
- name: node.name
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: discovery.seed_hosts
value: "es-cluster-0.elasticsearch.kube-logging.svc.cluster.local,es-cluster-1.elasticsearch.kube-logging.svc.cluster.local,es-cluster-2.elasticsearch.kube-logging.svc.cluster.local"
- name: cluster.initial_master_nodes
value: "es-cluster-0,es-cluster-1,es-cluster-2"
- name: ES_JAVA_OPTS
value: "-Xms512m -Xmx512m"
******
上面内容解释:在 statefulset 中定义了 pod,容器的名字是 elasticsearch,镜像是docker.elastic.co/elasticsearch/elasticsearch:7.2.0。使用 resources 字段来指定容器至少需要 0.1个 vCPU,并且容器最多可以使用 1 个 vCPU 了解有关资源请求和限制。
可参考Resource Management for Pods and Containers | Kubernetes。
容器暴露了 9200 和 9300 两个端口,名称要和上面定义的 Service 保持一致,通过volumeMount 声明了数据持久化目录,定义了一个 data 数据卷,通过 volumeMount 把它挂载到容器里的 /usr/share/elasticsearch/data 目录。
容器中设置了一些环境变量:
- cluster.name:Elasticsearch 集群的名称,我们这里是 k8s-logs。
- node.name:节点的名称,通过 metadata.name 来获取。这将解析为 es-cluster-[0,1,2],取决于节点的指定顺序。
- discovery.seed_hosts:此字段用于设置在 Elasticsearch 集群中节点相互连接的发现方法,它为我们的集群指定了一个静态主机列表。由于我们之前配置的是无头服务,我们的 Pod 具有唯一的 DNS 地址 es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local,因此我们相应地设置此地址变量即可。由于都在同一个 namespace 下面,所以我们可以将其缩短为 es-cluster-[0,1,2].elasticsearch。要了解有关 Elasticsearch 发现的更多信息,请参阅 Elasticsearch 官方文档:Discovery and cluster formation | Elasticsearch Guide [8.6] | Elastic
- ES_JAVA_OPTS:这里我们设置为-Xms512m -Xmx512m,告诉JVM使用512 MB的最小和最大堆。这个值应该根据群集的资源可用性和需求调整这些参数。要了解更多信息,请参阅设置堆大小的相关文档:Heap size settings | Elasticsearch Guide [8.6] | Elastic。
# initcontainer 内容
******
initContainers:
- name: fix-permissions
image: busybox
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
securityContext:
privileged: true
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
- name: increase-vm-max-map
image: busybox
imagePullPolicy: IfNotPresent
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
- name: increase-fd-ulimit
image: busybox
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "ulimit -n 65536"]
securityContext:
privileged: true
******
这里我们定义了几个在主应用程序之前运行的 Init 容器,这些初始容器按照定义的顺序依次执行,执行完成后才会启动主应用容器。
第一个名为 fix-permissions 的容器用来运行 chown 命令,将 Elasticsearch 数据目录的用户和组更改为1000:1000(Elasticsearch 用户的 UID)。因为默认情况下,Kubernetes 用 root 用户挂载数据目录,这会使得 Elasticsearch 无法访问该数据目录,可以参考 Elasticsearch 生产中的一些默认注意事项相关文档说明:Install Elasticsearch with Docker | Elasticsearch Guide [8.6] | Elastic。
第二个名为 increase-vm-max-map 的容器用来增加操作系统对 mmap 计数的限制,默认情况下该值可能太低,导致内存不足的错误,要了解更多关于该设置的信息,可以查看 Elasticsearch 官方文档说明:Virtual memory | Elasticsearch Guide [8.6] | Elastic。
最后一个初始化容器是用来执行 ulimit 命令增加打开文件描述符的最大数量的。
此外Elastisearch Notes for Production Use 文档还提到了由于性能原因最好禁用 swap,对于 Kubernetes 集群而言,最好也是禁用 swap 分区的。
现在我们已经定义了主应用容器和它之前运行的 Init Containers 来调整一些必要的系统参数,接下来可以添加数据目录的持久化相关的配置。
# 在 StatefulSet 中,使用 volumeClaimTemplates 来定义 volume 模板即可
******
volumeClaimTemplates:
- metadata:
name: data
labels:
app: elasticsearch
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: do-block-storage
resources:
requests:
storage: 10Gi
我们这里使用 volumeClaimTemplates 来定义持久化模板,Kubernetes 会使用它为 Pod 创建 PersistentVolume,设置访问模式为 ReadWriteOnce,这意味着它只能被 mount 到单个节点上进行读写,然后最重要的是使用了一个名为 do-block-storage 的 StorageClass 对象,所以我们需要提前创建该对象,我们这里使用的 NFS 作为存储后端,所以需要安装一个对应的 nfs provisioner 驱动。
注意:上述几段内容代码的解释都是同一个 elasticsearch-statefulset.yaml 文件里的内容!
# 查看 es 的 pod 是否创建成功
[root@k8s-master1 efk]# kubectl get pods -n kube-logging
NAME READY STATUS RESTARTS AGE
es-cluster-0 1/1 Running 0 22s
es-cluster-1 1/1 Running 0 15s
es-cluster-2 1/1 Running 0 8s
[root@k8s-master1 efk]# kubectl get svc -n kube-logging
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
elasticsearch ClusterIP None <none> 9200/TCP,9300/TCP 88m
pod部署完成之后,可以通过 REST API 检查 elasticsearch 集群是否部署成功,使用下面的命令将本地端口 9200 转发到 Elasticsearch 节点(如es-cluster-0)对应的端口:
[root@k8s-master1 efk]# kubectl port-forward es-cluster-0 9200:9200 --namespace=kube-logging
# 新开一个 master1 终端,执行如下请求,可以访问到数据
[root@k8s-master1 efk]# curl http://localhost:9200/_cluster/state?pretty
二、安装 kibana 可视化 UI 界面
把kibana_7_2_0.tar.gz 文件上传到 node1、node2 节点,手动解压
[root@k8s-node1 ~]# docker load -i kibana_7_2_0.tar.gz
[root@k8s-node2 ~]# docker load -i kibana_7_2_0.tar.gz
[root@k8s-master1 efk]# vim kibana.yaml
apiVersion: v1
kind: Service
metadata:
name: kibana
namespace: kube-logging
labels:
app: kibana
spec:
ports:
- port: 5601
selector:
app: kibana
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana
namespace: kube-logging
labels:
app: kibana
spec:
replicas: 1
selector:
matchLabels:
app: kibana
template:
metadata:
labels:
app: kibana
spec:
containers:
- name: kibana
image: docker.elastic.co/kibana/kibana:7.2.0
imagePullPolicy: IfNotPresent
resources:
limits:
cpu: 1000m
requests:
cpu: 100m
env:
- name: ELASTICSEARCH_URL
value: http://elasticsearch.kube-logging.svc.cluster.local:9200
ports:
- containerPort: 5601
[root@k8s-master1 efk]# kubectl apply -f kibana.yaml
[root@k8s-master1 efk]# kubectl get pods -n kube-logging | grep kibana
kibana-57dd8dfbb6-5g44t 1/1 Running 0 5m9s
[root@k8s-master1 efk]# kubectl get svc -n kube-logging | grep kibana
kibana ClusterIP 10.108.22.154 <none> 5601/TCP 5m39s
上面我们定义了两个资源对象,一个 Service 和 Deployment,为了测试方便,我们将 Service 设置为了 NodePort 类型,Kibana Pod 中配置都比较简单,唯一需要注意的是我们使用 ELASTICSEARCH_URL 这个环境变量来设置 Elasticsearch 集群的端点和端口,直接使用 Kubernetes DNS 即可,此端点对应服务名称为 elasticsearch,由于是一个 headless service,所以该域将解析为 3 个 Elasticsearch Pod 的 IP 地址列表。
# 修改 service 的 type 类型为 NodePort
[root@k8s-master1 efk]# kubectl edit svc -n kube-logging kibana
******
selector:
app: kibana
sessionAffinity: None
type: NodePort # 把 ClusterIP 修改为 NodePort
status:
loadBalancer: {}
******
# 随机生成端口
[root@k8s-master1 efk]# kubectl get svc -n kube-logging | grep kibana
kibana NodePort 10.108.22.154 <none> 5601:30948/TCP 9m51s
在浏览器中打开 http://<任意节点IP>:30948 即可,如果看到如下欢迎界面证明 Kibana 已经成功部署到了 Kubernetes 集群之中(需要等待一段时间,等容器初始化完成才可访问):
上一篇文章:【Kubernetes 企业项目实战】04、基于 K8s 构建 EFK+logstash+kafka 日志平台(上)_Stars.Sky的博客-CSDN博客
下一篇文章:【Kubernetes 企业项目实战】04、基于 K8s 构建 EFK+logstash+kafka 日志平台(下)_Stars.Sky的博客-CSDN博客