09、基于K8s构建EFK+logstash+kafka日志平台(中)

目录

一、安装存储日志组件 Elasticsearch

1、 1创建名称空间;

1、 2安装elasticsearch组件;

1)创建 headless service 服务

2)通过 statefulset 创建 elasticsearch 集群

二、安装 kibana 可视化 UI 界面


本篇文章所用到的资料文件下载地址:

kibana-v7.2.0-kubernetes文档类资源-CSDN下载

https://download.csdn.net/download/weixin_46560589/87391396

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