在 Kubernetes 上运行 Elastic Stack

1,360 阅读8分钟

这是一个关于在 Kubernetes 上部署 Elastic Stack 的简单指南。在本指南中,我们将使用 ECK 操作器在 Kubernetes 上创建所有与 Elastic 相关的资源。

本指南的成果如下:

  • 一个 Elasticsearch 集群。
  • 一个与 ES 集群集成的 Kibana 仪表盘。
  • Filebeat 代理,用于将主机 K8s 集群的日志传输到 ES 集群。(K8s 集群和 ES 集群是两个不同的集群)
  • 可选的 Ingress,用于接收来自主机集群外部的日志。

先决条件:

  • 一个 Kubernetes 集群,每个工作节点至少具有 2 个 vCPU 和 4GB RAM。(一个 Elasticsearch Pod 至少需要 1 个 vCPU 和 2GB RAM)
  • 集群上安装了 Ingress 控制器。(仅当您需要通过 Ingress 暴露服务时)
  • 在您的计算机上安装了 Helm。

步骤 1 — 安装 ECK 操作器 CRD

我们可以使用 Helm 图表或直接应用 Kubernetes 清单来安装 ECK。要使用 Helm 图表安装 ECK,请执行以下操作:

helm repo add elastic https://helm.elastic.co  
helm repo update  
  
helm install elastic-operator elastic/eck-operator -n elastic-system --create-namespace

查看 Helm 值文件以进行更多自定义。

步骤 2 — 创建优先级类(可选)

为了减少 ES pods 被频繁重新调度的机会,我们可以为它们分配一个优先级类,这样 kube-scheduler 会优先考虑这些 pods。如果节点的资源容量不足以容纳新的 DaemonSet,kube-scheduler 可能会驱逐一些 pods 以腾出空间。因此,我们需要优先级类来防止我们的 ES pods 在每次这种情况发生时被重新调度。根据您的工作负载和节点大小,这可能是可选的。

请查看以下示例优先级类,并根据需要应用它们。

# OPTIONAL #
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: elastic-cluster-high-priority
value: 10000000
globalDefault: false
description: "This priority class should be used for elasticsearch cluster pods only."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: filebeat-high-priority
value: 2000000
globalDefault: false
description: "This priority class should be used for filebeat pods only."
kubectl apply -f elastic-priority-classes.yaml

步骤 3 — 创建 Elasticsearch 集群

在这一步中,我们将使用第一步中安装的 Elasticsearch CRD 来创建实际的 ES 集群。在创建集群之前,请对集群中最初要拥有的节点数量以及所需的配置有一个基本的了解。

以下示例在我们的集群中创建了 2 个主节点、1 个数据节点和 1 个数据冷节点。

apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: elastic-cluster
spec:
  version: 8.6.2
  volumeClaimDeletePolicy: DeleteOnScaledownOnly
  nodeSets:
  - name: masters
    count: 2
    config:
      node.roles: ["master"]
    podTemplate:
      spec:
        priorityClassName: elastic-cluster-high-priority
        containers:
        - name: elasticsearch
          resources:
            limits:
              cpu: 2
              memory: 2.5Gi
            requests:
              cpu: 1
              memory: 2Gi
        initContainers:
        - name: sysctl
          securityContext:
            privileged: true
            runAsUser: 0
          command: ['sh', '-c', 'sysctl -w vm.max_map_count=262144']
    volumeClaimTemplates:
    - metadata:
        name: elasticsearch-data
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi
  - name: data
    count: 1
    config:
      node.roles: ["data", "ingest"]
    podTemplate:
      spec:
        priorityClassName: elastic-cluster-high-priority
        containers:
        - name: elasticsearch
          resources:
            limits:
              cpu: 2
              memory: 4Gi
            requests:
              cpu: 1
              memory: 2Gi
        initContainers:
        - name: sysctl
          securityContext:
            privileged: true
            runAsUser: 0
          command: ['sh', '-c', 'sysctl -w vm.max_map_count=262144']
    volumeClaimTemplates:
    - metadata:
        name: elasticsearch-data
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 100Gi
  - name: data-warm
    count: 1
    config:
      node.roles: ["data", "data_warm"]
    podTemplate:
      spec:
        priorityClassName: elastic-cluster-high-priority
        containers:
        - name: elasticsearch
          resources:
            limits:
              cpu: 1.5
              memory: 3Gi
            requests:
              cpu: 1
              memory: 2Gi
        initContainers:
        - name: sysctl
          securityContext:
            privileged: true
            runAsUser: 0
          command: ['sh', '-c', 'sysctl -w vm.max_map_count=262144']
    volumeClaimTemplates:
    - metadata:
        name: elasticsearch-data
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 150Gi

请记住,要为索引拥有副本分片,可能需要多个“数据”节点。您可以在 config.node.roles 下为节点分配角色。可以在此处阅读关于节点角色的更多信息。如果您不确定在集群中需要多少节点以及这些节点的角色,建议先从一个主节点和一个数据节点开始。之后,您可以通过重新应用清单来增加节点数量并添加新类型的节点。

为了避免集群节点出现“内存不足”错误,我们创建了一个名为“sysctl”的初始化容器,以将内核设置 vm.max_map_count 增加到 262144。

请确保为 volumeClaims 使用适当的存储类。可以在此处阅读更多关于 Elasticsearch volumeClaims 的信息。建议为“数据”节点使用 SSD 存储。

如果您想了解更多关于 ES 集群配置的信息,请阅读官方文档,链接在此

准备好设置后,在您希望的命名空间中应用清单。

kubectl apply -f elasticsearch-cluster.yaml -n <CLUSTER-NAMESPACE>

应用后,等待几分钟直到 pods 创建完成。然后,您可以通过执行 kubectl get elasticsearchkubectl get es 来检查集群状态。

image.png

默认的用户凭据存储在同一命名空间中的一个名为 elastic-cluster-es-elastic-user 的 secret 中。用户名为 elastic,密码可以通过执行以下命令获取:

kubectl get secret elastic-cluster-es-elastic-user -o go-template='{{.data.elastic | base64decode}}'

步骤 4 — 创建 Kibana 实例

现在我们可以使用 CRD 安装的 Kibana 资源为集群创建我们的 Kibana 仪表盘。以下示例是一个配置较少的简单 Kibana 部署。

apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: kibana
spec:
  version: 8.6.2
  count: 1
  elasticsearchRef:
    name: elastic-cluster
  podTemplate:
    spec:
      priorityClassName: elastic-cluster-high-priority
      containers:
      - name: kibana
        env:
          - name: NODE_OPTIONS
            value: "--max-old-space-size=2048"
          - name: SERVER_PUBLICBASEURL
            value: "https://kibana.yourdomain.com"
        resources:
          requests:
            memory: 1Gi
            cpu: 0.5
          limits:
            memory: 2.5Gi
            cpu: 2

spec.elasticsearchRef.name 应与上一步中创建的 Elasticsearch 资源的名称匹配。在本例中,该名称为 elastic-cluster

请确保将 SERVER_PUBLICBASEURL 设置为您将用于 Kibana 仪表盘实例的基本 URL。在接下来的步骤中,我们将为 Kibana 创建一个具有匹配主机名的 ingress。

您可以在与 ES 集群相同的命名空间中创建一个 Kibana pod,也可以在单独的命名空间中创建。为了便于使用,我将它们都创建在同一个命名空间中。

kubectl apply -f kibana-instance.yaml -n <KIBANA-NAMESPACE>

然后,通过执行 kubectl get kibanakubectl get kb 来检查 Kibana 实例的状态。

image.png

步骤 5 — 安装 Filebeat

在访问 Kibana 仪表盘之前,让我们配置 Filebeat 以发送主机 Kubernetes 集群的日志。

[更新 — 2024 年 3 月]

在撰写本文时,ECK 不支持直接从其 CRD 安装 Beats。我建议您按照这些指南直接从其 CRD 安装 Beats。

下载适用于 Kubernetes 的官方 Filebeat 清单。请注意,命名空间设置为 filebeat

curl -L -O https://raw.githubusercontent.com/elastic/beats/8.6/deploy/kubernetes/filebeat-kubernetes.yaml

在 Filebeat DaemonSet 中相应地设置以下环境变量(ENV)值。

- name: ELASTICSEARCH_HOST  
value: elastic-cluster-es-http.<ELASTIC_NAMESPACE>.SVC  
- name: ELASTICSEARCH_PORT  
value: "9200"  
- name: ELASTICSEARCH_USERNAME  
value: elastic  
- name: ELASTICSEARCH_PASSWORD  
value: <PASSWORD_FROM_THE_SECRET>

除此之外,还需在 filebeat-config ConfigMap 中设置以下配置。

output.elasticsearch:  
## SET BY DEFAULT ##  
hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']  
username: ${ELASTICSEARCH_USERNAME}  
password: ${ELASTICSEARCH_PASSWORD}  
## SET BY DEFAULT ##  
  
## ADD THESE TWO LINES IF FILEBEAT AGENTS FAIL TO SEND LOGS TO THE CLUSTER ##  
protocol: "https"  
ssl.verification_mode: none  
  
## CUSTOM INDEX PATTERN  
index: "filebeat-aks-devops-prod-%{[agent.version]}"  
  
setup.template:  
name: "filebeat-aks-devops-prod"  
pattern: "filebeat-aks-devops-prod-%{[agent.version]}"

应用清单并检查其中一个 pod 的日志,以查看它是否能够成功连接到 ES 集群。

kubectl apply -f filebeat-kubernetes.yaml

等待几秒钟,然后检查其中一个 pod 的日志。

kubectl logs -f filebeat-XXXXX -n filebeat

如果您看到如下日志消息,这意味着 Filebeat 代理现在可以将日志发送到集群。

"message":"Connection to backoff(elasticsearch(https://elastic-cluster-es-http.elastic-prod.svc:9200)) established"

步骤 6 — 暴露 ES 集群和 Kibana(可选)

现在我们已经有了一个可用的 ES 集群和一个 Kibana 仪表盘,我们需要访问 Kibana 仪表盘,并可能连接外部代理将日志发送到 ES 集群。在这种情况下,我们可以使用 Ingress 或任何其他首选方法来暴露这两个服务。

如果您想快速访问 Kibana 仪表盘而不创建 Ingress,可以尝试使用 kubectl port-forward 命令通过端口转发进行访问。

示例:

kubectl port-forward svc/kibana-kb-http 15601:5601

15601 是我的本地端口,5601 是远程端口。现在您可以通过访问浏览器中的 https://localhost:15601 来访问它。使用默认用户凭据登录到 Kibana 仪表盘。

现在让我们使用 Ingress 正式暴露它们。

首先,确保集群中安装了 Ingress 控制器。如果没有,请在继续之前安装一个。这里有一个 Kubernetes Ingress 控制器的列表。Ingress Nginx 控制器是最受欢迎的 Ingress 控制器之一。

现在您有了 Ingress 控制器,继续为 elastic-clusterkibana 服务创建 Ingress。通过执行 kubectl get svc -n <NAMESPACE> 查找这两个服务的名称。Elasticsearch 服务的名称以 -es-http 结尾,而 Kibana 服务的名称以 -kb-http 结尾。在这种情况下,它们分别是 elastic-cluster-es-httpkibana-kb-http

然后为您的域创建一个 TLS secret。如果您已经有一个,可以跳过此步骤,或者按照此指南为您的主机创建一个 TLS secret。

现在您有了 secret,继续创建如下所示的 Ingress 资源。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: elastic-cluster
  namespace: <ELASTIC-NAMESPACE>
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - elastic.yourdomain.com
    secretName: <TLS-SECRET>
  rules:
  - host: elastic.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: elastic-cluster-es-http
            port:
              number: 9200
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kibana
  namespace: <KIBANA-NAMESPACE>
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - kibana.yourdomain.com
    secretName: <TLS-SECRET>
  rules:
  - host: kibana.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kibana-kb-http
            port:
              number: 5601

应用清单并等待几秒钟/分钟,让 Ingress 控制器为您创建一个负载均衡器。

kubectl apply -f elastic-cluster-ingress.yaml
kubectl apply -f kibana-ingress.yaml

当您通过执行 kubectl get ing -A 查看 Ingress 资源时,负载均衡器的 IP/地址应该会出现在 ADDRESS 列中。

如果您希望自动为每个创建的 Ingress 在您的 DNS 区域中添加 DNS 记录,请考虑使用 ExternalDNS

步骤 7 — 在 Kibana 中创建数据视图

使用默认用户凭据登录并搜索 "Discover"。这里有一个简单的指南,帮助您在 Kibana 中创建新的数据视图。创建数据视图时,您应该会看到我们在配置 Filebeat 时创建的索引的数据流可用。如果没有,请再次检查 Filebeat pod 的日志,确保它们能够连接到集群并正确发送日志。