目录
一、安装存储日志组件 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博客