【Kubernetes 企业项目实战】04、基于 K8s 构建 EFK+logstash+kafka 日志平台(中)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

一、安装存储日志组件 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 会有生成一个新的 podkube-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 个 replicas3 个 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 个 vC​​PU 了解有关资源请求和限制。

可参考Resource Management for Pods and Containers | Kubernetes

        容器暴露了 9200 和 9300 两个端口名称要和上面定义的 Service 保持一致通过volumeMount 声明了数据持久化目录定义了一个 data 数据卷通过 volumeMount 把它挂载到容器里的 /usr/share/elasticsearch/data 目录。

容器中设置了一些环境变量

  • cluster.nameElasticsearch 集群的名称我们这里是 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:1000Elasticsearch 用户的 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博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: k8s

“【Kubernetes 企业项目实战】04、基于 K8s 构建 EFK+logstash+kafka 日志平台(中)” 的相关文章