在 GKE 上探索 Apache Solr Operator v0.3.0

作者:Tim Potter

今年早些时候,彭博慷慨地将 Solr Operator 捐赠给了 Apache 软件基金会。最新的v0.3.0 版本是 Apache 下的第一个版本,代表了整个 Apache Solr 社区的一个重要里程碑。该 Operator 是 Solr 的第一个卫星项目,由 Solr PMC 管理,但独立于 Apache Solr 发布。现在,社区有了一个强大的工具,可以将大规模运行 Solr 的来之不易的经验教训和最佳实践转化为 Kubernetes 上的自动化解决方案。

简介

在这篇文章中,我将从 DevOps 工程师的角度探索 v0.3.0 版本,他们需要在 Kubernetes 上部署配置良好的 Solr 集群。

Solr Operator 使在 Kubernetes 上开始使用 Solr 非常容易。如果您按照本地教程操作,您可以在短时间内在本地启动并运行 Solr 集群。但是,对于部署到生产环境,有三个额外的考虑因素:安全性、高可用性和性能监控。本指南的目的是帮助您规划和实施这些重要的生产问题。

在深入细节之前,请花点时间查看下面的图表,该图表描述了 Operator 部署到 Kubernetes 的 Solr 集群的主要组件、配置和交互。当然,还有许多其他 Kubernetes 对象在起作用(密钥、服务帐户等),但该图表仅显示主要对象。

Solr Operator Components

入门

让我们在 GKE 上运行 Solr Operator、Solr 集群和支持服务的基本部署。我与 Google 没有正式的隶属关系,并且使用 GKE 来撰写这篇文章是因为它易于使用,但相同的基本流程也适用于其他云托管 Kubernetes,如 Amazon 的 EKS 或 AKS。我们将在本文档的各个部分中改进此初始配置。最后,我们将拥有在云中运行生产就绪 Solr 集群所需的 CRD 定义和支持脚本。

Kubernetes 设置

我鼓励您在家中跟随操作,因此请启动 GKE 集群并打开终端。如果您是 GKE 的新手,请在继续本文档之前完成GKE 快速入门。为了获得更好的 HA,您应该跨三个区域部署一个区域性 GKE 集群(每个区域至少一个 Solr pod)。当然,您可以为开发/测试目的将区域性集群部署到一个区域,但我显示的示例基于在美国中部 1 区运行的 3 节点 GKE 集群,每个区域都有一个节点。

首先,我们需要将 nginx 入口控制器安装到 ingress-nginx 命名空间中

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.45.0/deploy/static/provider/cloud/deploy.yaml

有关更多信息,请参阅在 GKE 上部署 Nginx 入口

要验证入口控制器是否正常运行,请执行

kubectl get pods -l app.kubernetes.io/name=ingress-nginx -n ingress-nginx \
  --field-selector status.phase=Running

应看到与以下类似的预期输出

NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-6c94f69c74-fxzp7   1/1     Running   0          6m23s

对于本文档,我们将 Operator 和 Solr 部署到名为 sop030 的命名空间

kubectl create ns sop030
kubectl config set-context --current --namespace=sop030

Solr Operator 设置

如果您安装了以前版本的 Solr Operator,请使用以下说明升级到 Apache Solr 版本:升级到 Apache。否则,添加 Apache Solr Helm 存储库,安装 Solr CRD安装 Solr Operator

helm repo add apache-solr https://solr.apache.org/charts
helm repo update

kubectl create -f https://solr.apache.org/operator/downloads/crds/v0.3.0/all-with-dependencies.yaml

helm upgrade --install solr-operator apache-solr/solr-operator \
  --version 0.3.0

此时,验证您的命名空间中是否有一个 Solr Operator pod 正在运行

kubectl get pod -l control-plane=solr-operator
kubectl describe pod -l control-plane=solr-operator

请注意,我使用的是标签选择器过滤器,而不是通过 ID 寻址 pod,这使我无需查找 ID 即可获取 pod 详细信息。

您的命名空间中还应运行一个 Zookeeper Operator pod,使用以下命令进行验证

kubectl get pod -l component=zookeeper-operator

Solr CRD

自定义资源定义 (CRD) 允许应用程序开发人员在 Kubernetes 中定义一种新型对象。这提供了许多好处

  1. 向人工操作员公开特定于域的配置设置
  2. 减少样板代码并隐藏实现细节
  3. 使用 kubectl 对 CRD 执行 CRUD 操作
  4. 像任何其他 K8s 资源一样存储在 etcd 中并进行管理

Solr Operator 定义了代表 Solr 特定对象的 CRD,例如 SolrCloud 资源、指标导出器资源和备份/恢复资源。SolrCloud CRD 定义了在 Kubernetes 命名空间中部署和管理 Solr 集群所需的配置设置。首先,让我们使用 kubectl 查看 SolrCloud CRD

# get a list of all CRDs in the cluster
kubectl get crds

# get details about the SolrCloud CRD Spec
kubectl explain solrclouds.spec
kubectl explain solrclouds.spec.solrImage

# get details about the SolrCloud CRD Status
kubectl explain solrclouds.status

花点时间查看上面 explain 命令的输出;各种结构和字段应该看起来很熟悉。随意深入研究,探索 SolrCloud CRD Spec 和 Status 的不同部分。

创建 Solr Cloud

要在 Kubernetes 命名空间中部署 SolrCloud 对象的实例,我们编写一些 YAML,如下面的示例所示

apiVersion: solr.apache.org/v1beta1
kind: SolrCloud
metadata:
  name: explore
spec:
  customSolrKubeOptions:
    podOptions:
      resources:
        limits:
          memory: 3Gi
        requests:
          cpu: 700m
          memory: 3Gi
  dataStorage:
    persistent:
      pvcTemplate:
        spec:
          resources:
            requests:
              storage: 2Gi
      reclaimPolicy: Delete
  replicas: 3
  solrImage:
    repository: solr
    tag: 8.8.2
  solrJavaMem: -Xms500M -Xmx500M
  updateStrategy:
    method: StatefulSet
  zookeeperRef:
    provided:
      chroot: /explore
      image:
        pullPolicy: IfNotPresent
        repository: pravega/zookeeper
        tag: 0.2.9
      persistence:
        reclaimPolicy: Delete
        spec:
          accessModes:
          - ReadWriteOnce
          resources:
            requests:
              storage: 2Gi
      replicas: 3
      zookeeperPodPolicy:
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 250m
            memory: 500Mi

密切注意 Solr 和 Zookeeper 的资源请求/限制和磁盘大小;为每个 Solr pod 分配合适的内存、CPU 和磁盘是设计集群时的基本任务。当然,使用 Kubernetes,您可以根据需要添加更多 pod,但您仍然需要在部署 pod 之前估计您的用例的正确资源请求/限制和磁盘大小。生产环境的调整超出了本文档的范围,并且非常特定于用例(通常需要运行一些实际的负载测试进行一些试错)。

关于 SolrCloud YAML,您应该注意的是,如果您过去使用过 Solr,则大多数设置都是非常特定于 Solr 的并且是不言自明的。您的运维团队会将此 YAML 保存在源代码控制中,从而使他们能够自动化在 Kubernetes 中创建 SolrCloud 集群的过程。您甚至可以构建一个 Helm 图表来管理您的 SolrCloud YAML 和相关对象,例如备份/恢复和 Prometheus 导出器 CRD 定义。

打开一个 shell 并运行以下命令来跟踪 Operator pod 日志

kubectl logs -l control-plane=solr-operator -f

请注意,我使用的是标签选择器 (-l ...),而不是通过其 ID 寻址 pod;这省去了每次我想查看 Operator 日志时都必须查找 pod ID 的麻烦。

要将 explore SolrCloud 部署到 K8s,请将上面显示的 YAML 保存到名为 explore-SolrCloud.yaml 的文件中,然后在另一个 shell 选项卡中运行以下命令

kubectl apply -f explore-SolrCloud.yaml

我们将在本文档的其余部分对 explore-SolrCloud.yaml 文件进行更新。
任何以“spec:”开头的代码部分都指的是此文件。

当您将此 SolrCloud 定义提交到 Kubernetes API 服务器时,它会使用类似于观察者的机制通知 Solr Operator(作为 pod 在您的命名空间中运行)。这会启动 Operator 中的协调过程,该过程会创建运行 explore SolrCloud 集群所需的各种 K8s 对象(请参见上面的图表)。在部署 SolrCloud 实例时,简要查看 Operator 的日志。

CRD 的主要好处之一是,您可以使用 kubectl 与它们交互,就像与本机 K8s 对象交互一样

$ kubectl get solrclouds

NAME     VERSION   TARGETVERSION   DESIREDNODES   NODES   READYNODES   AGE
explore  8.8.2                     3              3       3            73s

$ kubectl get solrclouds explore -o yaml

在后台,Operator 创建了一个 StatefulSet 来管理一组 Solr pod。使用以下命令查看 explore StatefulSet

kubectl get sts explore -o yaml

对于此初始 SolrCloud 定义,我依赖一个稍微细致的设置

  updateStrategy:
    method: StatefulSet

我们需要从 StatefulSet 作为 updateStrategy 方法开始,以便我们可以在现有的 SolrCloud 上启用 TLS。启用 TLS 后,我们将在 HA 部分将其切换为 Managed。使用 Managed 需要 Operator 调用 collections API 以获取 CLUSTERSTATUS,这在集群从 HTTP 转换为 HTTPs 时不起作用。在实际部署中,您应该首先启用 TLS,而不是在现有集群上升级到 TLS。

此外,我们现在不要创建任何集合或加载数据,因为我们想在继续之前锁定集群。

Zookeeper 连接

Solr Cloud 依赖于 Apache Zookeeper。在 explore SolrCloud 定义中,我使用的是 提供的 选项,这意味着 Solr Operator 为 SolrCloud 实例提供一个 Zookeeper 集群。在后台,Solr Operator 定义了一个 ZookeeperCluster CRD 实例,该实例由 Zookeeper Operator 管理。provided 选项对于入门和开发很有用,但不会公开 Zookeeper Operator 支持的所有配置选项。对于生产部署,请考虑在 SolrCloud CRD 定义之外定义您自己的 ZookeeperCluster,然后仅使用 spec.zookeeperRef 下的 connectionInfo 指向 Zookeeper 集群连接字符串。这使您可以完全控制 Zookeeper 集群的部署,允许多个 SolrCloud 实例(和其他应用程序)共享相同的 Zookeeper 服务(当然,具有不同的 chroot),并提供良好的关注点分离。或者,Solr Operator 不需要使用 Zookeeper Operator,因此如果 Zookeeper Operator 不能满足您的需求,您可以使用 Helm 图表 来部署您的 Zookeeper 集群。

自定义 Log4J 配置

在继续之前,我想指出 Operator 中的一个方便功能,它允许您从用户提供的 ConfigMap 加载自定义 Log4j 配置。我提到此功能是因为您可能会遇到需要自定义 Solr 的 Log4j 配置以帮助解决生产环境中的问题的情况。我不会在此处详细介绍,但请使用 自定义日志配置 文档来配置您自己的自定义 Log4J 配置。

安全

安全应该是您始终首先也是最关心的问题,尤其是在 GKE 等公共云中运行时;您不希望成为系统受到威胁的那个运维工程师。在本节中,我们将为 Solr 的 API 端点启用 TLS、基本身份验证和授权控制。有关所有配置选项的更详细说明,请参阅 SolrCloud CRD 文档。

要为 Solr 启用 TLS,您只需要一个包含公共 X.509 证书和私钥的 TLS 密钥。Kubernetes 生态系统提供了一个用于颁发和管理证书的强大工具:cert-manager。如果尚未在您的集群中安装,请按照 Solr Operator 提供的基本说明来安装最新版本的 cert-manager:使用 cert-manager 颁发证书

Cert-manager 和 Let's Encrypt

首先,让我们从自签名证书开始。您需要创建一个自签名颁发者(cert-manager CRD)、证书(cert-manager CRD)和一个包含密钥库密码的密钥。将以下 yaml 保存到一个文件中,并通过 kubectl apply -f 应用它。

---
apiVersion: v1
kind: Secret
metadata:
  name: pkcs12-keystore-password
stringData:
  password-key: Test1234

---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: explore-selfsigned-cert
spec:
  subject:
    organizations: ["self"]
  dnsNames:
    - localhost
  secretName: explore-selfsigned-cert-tls
  issuerRef:
    name: selfsigned-issuer
  keystores:
    pkcs12:
      create: true
      passwordSecretRef:
        key: password-key
        name: pkcs12-keystore-password

请注意,我使用以下命令为我的证书请求生成 PKCS12 密钥库

  keystores:
    pkcs12:
      create: true

当使用基于 Java 的应用程序时,这是 cert-manager 的一个不错的特性,因为 Java 本身支持读取 PKCS12 格式,而如果 cert-manager 没有自动为您执行此操作,您则需要使用 keytool 转换 tls.crt 和 tls.key 文件。

Cert-manager 创建一个 Kubernetes secret,其中包含 X.509 证书、私钥和 Solr 使用的符合 PKCS12 标准的密钥库。花点时间使用以下命令检查 secret 的内容

kubectl get secret explore-selfsigned-cert-tls -o yaml

更新 explore-SolrCloud.yaml 中的 SolrCloud CRD 定义,以启用 TLS 并指向包含密钥库的 secret

spec:
  ...

  solrAddressability:
    commonServicePort: 443
    external:
      domainName: YOUR_DOMAIN_NAME_HERE
      method: Ingress
      nodePortOverride: 443
      useExternalAddress: false
    podPort: 8983

  solrTLS:
    restartOnTLSSecretUpdate: true
    pkcs12Secret:
      name: explore-selfsigned-cert-tls
      key: keystore.p12
    keyStorePasswordSecret:
      name: pkcs12-keystore-password
      key: password-key

请注意,我还通过 Ingress 将 Solr 暴露在外部,并将常用服务端口切换为 443,这在使用启用 TLS 的服务时更直观。使用以下命令将更改应用到 SolrCloud CRD

kubectl apply -f explore-SolrCloud.yaml

这将触发 Solr Pod 的滚动重启,以使用您的自签名证书启用 TLS。通过打开一个到其中一个 Solr Pod(端口 8983)的端口转发,然后执行以下操作,验证 Solr 是否通过 HTTPS 提供流量

curl https://127.0.0.1:8983/solr/admin/info/system -k

Let’s Encrypt 颁发的 TLS 证书

自签名证书非常适合本地测试,但为了在 Web 上公开服务,我们需要由受信任的 CA 颁发的证书。我将使用 Let’s Encrypt 为我拥有的域的 Solr 集群颁发证书。如果您的 Solr 集群没有域名,您可以跳过此部分,并在需要时返回参考。我在此处使用的过程基于以下文档:适用于 Google 的 ACME DNS01 解析器

这是我为我的 GKE 环境创建的 Let’s Encrypt 颁发者

---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: acme-letsencrypt-issuer
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: *** REDACTED ***
    privateKeySecretRef:
      name: acme-letsencrypt-issuer-pk
    solvers:
    - dns01:
        cloudDNS:
          project: GCP_PROJECT
          serviceAccountSecretRef:
            name: clouddns-dns01-solver-svc-acct
            key: key.json

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: explore-solr-tls-cert
spec:
  dnsNames:
  - YOUR_DOMAIN_NAME_HERE
  issuerRef:
    kind: Issuer
    name: acme-letsencrypt-issuer
  keystores:
    pkcs12:
      create: true
      passwordSecretRef:
        key: password-key
        name: pkcs12-keystore-password
  secretName: explore-solr-tls-letsencrypt
  subject:
    countries:
    - USA
    organizationalUnits:
    - k8s
    organizations:
    - solr

创建证书颁发者通常涉及一些特定于平台的配置。对于 GKE,请注意我使用的是 DNS01 解析器,它需要具有 DNS 管理员权限的服务帐户的凭据,您需要在 GCP 控制台中或使用 gcloud CLI 进行配置。在我的环境中,我将凭据存储在一个名为 clouddns-dns01-solver-svc-acct 的 secret 中。

您可以 tail cert-manager Pod(在 cert-manager 命名空间中)上的日志,以跟踪颁发过程的进度。

kubectl logs -l app.kubernetes.io/name=cert-manager -n cert-manager

一旦 Let's Encrypt 颁发了 TLS 证书,请重新配置(假设您完成了上述自签名过程)您的 SolrCloud 实例,以便通过 Ingress 公开 Solr,并使用 cert-manager 创建的 TLS secret 中存储的包含证书和私钥的 PKCS12 密钥库

spec:
  ...

  solrTLS:
    pkcs12Secret:
      name: explore-solr-tls-letsencrypt
      key: keystore.p12

最后一步是创建 DNS A 记录,将 Ingress 的 IP 地址(由 Solr 运算符创建)映射到 Ingress 的主机名。

mTLS

Solr 运算符支持启用 mTLS 的 Solr 集群,但这超出了本文档的范围。有关配置 mTLS,请参阅 Solr 运算符文档。

身份验证和授权

如果您按照上一节中的过程操作,那么 Solr Pod 之间的线路流量将被加密,但我们还需要确保传入的请求具有用户身份(身份验证),并且请求用户被授权执行该请求。从 v0.3.0 开始,Solr 运算符支持基本身份验证和 Solr 基于规则的授权控制。

最简单的入门方法是让运算符引导基本身份验证和授权控制。有关详细说明,请参阅:身份验证和授权

spec:
  ...

  solrSecurity:
    authenticationType: Basic

运算符为三个 Solr 用户配置凭据:admink8s-opersolr

通过执行以下操作以管理员用户身份登录 Solr 管理 Web UI

kubectl get secret explore-solrcloud-security-bootstrap \
  -o jsonpath='{.data.admin}' | base64 --decode

此时,所有进出 Solr Pod 的流量都使用 TLS 加密,并且 API 端点通过 Solr 基于规则的授权控制和基本身份验证进行锁定。现在 Solr 已正确锁定,让我们继续为我们的集群配置高可用性 (HA)。

高可用性

在本节中,我们将介绍在 Kubernetes 中实现 Solr Pod 高可用性的几个关键主题。确保节点可用性只是等式的一部分。您还需要确保需要高可用性的每个集合的每个分片的副本都正确分布在各个 Pod 中,这样,即使丢失一个节点甚至整个 AZ 也不会导致服务丢失。但是,在发生中断时,确保某些副本保持在线只能起到一定作用。在某个时刻,健康的副本可能会因请求而过载,因此您制定的任何可用性策略也需要计划健康副本上的负载突然增加的情况。

Pod 反亲和性

为了开始探索 Solr 运算符的高可用性,让我们首先使用 Pod 反亲和性来确保 Solr Pod 均匀分布在集群周围。

确定所需的 Solr Pod 数量后,您还需要使用Pod 反亲和性规则,以平衡的方式将 Pod 分布在 Kubernetes 集群中,以便承受随机节点故障以及区域级中断(对于多区域集群)。

要查看集群中每个节点的区域,请执行

kubectl get nodes -L topology.kubernetes.io/zone

在以下 podAntiAffinity 示例中,与 solr-cloud=explore 标签选择器匹配的 Pod 将分布在集群中不同的节点和区域中。

提示:Solr 运算符在所有 Pod 上将“solr-cloud”标签设置为您的 SolrCloud 实例的名称。

spec:
  ...

  customSolrKubeOptions:
    podOptions:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: "technology"
                  operator: In
                  values:
                  - solr-cloud
                - key: "solr-cloud"
                  operator: In
                  values:
                  - explore
              topologyKey: topology.kubernetes.io/zone
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                - key: "technology"
                  operator: In
                  values:
                  - solr-cloud
                - key: "solr-cloud"
                  operator: In
                  values:
                  - explore
              topologyKey: kubernetes.io/hostname

显然,当您在 3 个区域的 3 个节点上拥有 3 个 Solr Pod 时,这并不重要,您只需使用主机名反亲和性规则即可获得均衡的分布;对于大型集群,同时拥有主机名和区域的规则非常重要。

如果您没有运行多区域集群,则可以删除基于 topology.kubernetes.io/zone 的规则。此外,我认为此规则应该是一种偏好,而不是硬性要求,以便 Kubernetes 可以在一个区域关闭时在其他健康区域中启动替换节点和 Pod。

此外,当为现有 SolrCloud 应用这些反亲和性规则时,您可能会遇到 Pod 调度问题,因为用于 Solr 磁盘的底层持久卷声明 (PVC) 已固定到一个区域。根据新的反亲和性规则移动到另一个区域的任何 Solr Pod 都会使 Pod 处于 Pending 状态,因为需要重新附加的 PVC 仅存在于原始区域中。因此,最好在推出 SolrCloud 集群之前计划好 Pod 亲和性规则。

如果您的 Solr Pod 多于集群中可用的节点,则应该为基于 kubernetes.io/hostname 的规则使用 preferredDuringSchedulingIgnoredDuringExecution,而不是 requiredDuringSchedulingIgnoredDuringExecution。Kubernetes 会尽最大努力将 Pod 均匀分布在各个节点上,但多个 Pod 将在某个时间点被调度到同一节点上(显然)。

假设您为“explore”SolrCloud 请求了 3 个副本,您应该在三个区域中均匀分布 Pod。运行以下命令以获取运行 Solr Pod 的唯一节点数,并计算有多少个。

kubectl get po -l solr-cloud=explore,technology=solr-cloud \
  -o json | jq -r '.items | sort_by(.spec.nodeName)[] | [.spec.nodeName] | @tsv' | uniq | wc -l

输出应为:3

您应该为 Zookeeper Pod 采用类似的反亲和性配置,以将其也分布在各个区域中。

区域感知副本放置

一旦集群的 Pod 经过适当调整大小并分布在集群周围以促进 HA,您仍然需要确保需要 HA 的集合的所有副本都放置好,以便利用集群布局。换句话说,如果同一分片的所有副本都最终位于同一节点或区域上,那么将 Pod 分布在集群周围以支持 HA 没有任何好处。在 Solr 方面,一个好的起始规则是让同一分片的副本优先选择其他主机,方法是使用

{"node": "#ANY", "shard": "#EACH", "replica":"<2"},

有关此规则和其他类型规则的更多信息,请参阅Solr 自动缩放

如果您过度分片您的集合,即总副本数 > Pod 数,那么您可能需要放宽节点级自动缩放规则中的计数阈值。

注意:Solr 自动缩放框架已在 8.x 中弃用,并在 9.x 中删除。但是,我们在本文档中用于副本放置的规则将被 9.x 中提供的 AffinityPlacementPlugin 替换,有关详细信息,请参阅:solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactory.java

对于多 AZ 集群,StatefulSet 中的每个 Solr Pod 都需要设置 availability_zone Java 系统属性,这是一个唯一的标签,用于标识该 Pod 的区域。availability_zone 属性可以在自动缩放规则中使用,以在 SolrCloud 集群的所有可用区域中分布副本。

{"replica":"#EQUAL", "shard":"#EACH", "sysprop.availability_zone":"#EACH"},

如果您的 Solr Pod 的服务帐户具有获取节点权限,您可以使用 Downward API 从节点元数据中获取区域。但是,许多管理员不愿意授予此权限。下面显示了一个特定于 GCP 的解决方案,我们在其中 curl http://metadata.google.internal/computeMetadata/v1/instance/zone API

spec:
  ...

  customSolrKubeOptions:
    podOptions:
      initContainers: # additional init containers for the Solr pods
        - name: set-zone # GKE specific, avoids giving get nodes permission to the service account
          image: curlimages/curl:latest
          command:
            - '/bin/sh'
            - '-c'
            - |
              zone=$(curl -sS http://metadata.google.internal/computeMetadata/v1/instance/zone -H 'Metadata-Flavor: Google')
              zone=${zone##*/}
              if [ "${zone}" != "" ]; then
                echo "export SOLR_OPTS=\"\${SOLR_OPTS} -Davailability_zone=${zone}\"" > /docker-entrypoint-initdb.d/set-zone.sh
              fi
          volumeMounts:
            - name: initdb
              mountPath: /docker-entrypoint-initdb.d
      volumes:
      - defaultContainerMount:
          mountPath: /docker-entrypoint-initdb.d
          name: initdb
        name: initdb
        source:
          emptyDir: {}

请注意,initContainer 将 set-zone.sh 脚本添加到 /docker-entrypoint-initdb.d。Docker Solr 框架在启动 Solr 之前会获取此目录中的任何脚本。类似的方法可以应用于 EKS(请参阅 http://169.254.169.254/latest/dynamic/instance-identity/document 的输出)。当然,使用特定于平台的方法并不理想,但授予获取节点权限也不是理想的。关键是使用适用于您系统的任何方法设置 availability_zone 系统属性。

您还需要确保分布式查询使用 Solr 8.2 中添加的 node.sysprop shardPreference,从而优先选择同一区域中的其他副本。当两个区域都健康时,此查询路由偏好也有助于减少跨区域的查询。有关更多详细信息,请查阅 Solr 参考指南 - 分片偏好

我将把应用使用 availability_zone 系统属性影响副本放置的自动缩放策略作为练习留给读者。

副本类型

如果您使用运算符部署多个 SolrCloud 实例,但它们都使用相同的 Zookeeper 连接字符串(和 chroot),那么从 Solr 的角度来看,它的行为就像一个单一的 Solr Cloud 集群。您可以使用此方法将 Solr Pod 分配给 Kubernetes 集群中的不同节点。例如,您可能希望在一组节点上运行 TLOG 副本,而在另一组节点上运行 PULL 副本,以隔离写入和读取流量(请参阅:副本类型)。通过副本类型隔离流量超出了本文档的范围,但您可以使用运算符部署多个 SolrCloud 实例来实现隔离。每个实例都需要设置 Java 系统属性,例如 solr_node_type,以区分彼此的 Solr Pod;Solr 的自动缩放策略引擎支持使用系统属性按类型分配副本。

滚动重启

运算符的主要好处之一是,我们可以扩展 Kubernetes 的默认行为,以考虑特定于应用程序的状态。例如,在执行 StatefulSet 的滚动重启时,K8s 将从具有最高序号值的 Pod 开始,并向下运行到零,并在其间等待重启的 Pod 达到 Running 状态。虽然此方法有效,但对于大型集群来说通常太慢,并且在不知道该节点上的副本是否正在恢复的情况下,可能会造成损害。

相比之下,该 Operator 增强了 StatefulSet 的滚动重启操作,会考虑哪个 Solr Pod 托管了 Overseer(最后重启)、Pod 上的 Leader 数量等等。 这样做的结果是,为 SolrCloud 优化了滚动重启过程,可以同时重启多个 Pod。该 Operator 使用 Solr 的集群状态 API 来确保在决定同时重启哪些 Pod 时,每个分片至少有一个副本处于在线状态。更重要的是,这些自定义的协调过程遵循了 Kubernetes 中非常重要的幂等性思想。在相同的初始状态下调用协调 100 次,得到的结果应该与第一次和第 100 次相同。

回想一下,我最初使用 StatefulSet 方法是为了将现有集群迁移到使用 TLS。 让我们切换到使用 Managed 方法,配置如下:

spec:
  ...

  updateStrategy:
    managed:
      maxPodsUnavailable: 2
      maxShardReplicasUnavailable: 2
    method: Managed

将此添加到您的 explore-SolrCloud.yaml 文件并应用更改。

* 如上所示,Managed 更新策略是可自定义的,可以根据您的需要配置为尽可能安全或尽可能快速。 有关更多信息,请参阅更新文档

性能监控

现在我们有了一个安全、具备高可用性的 Solr 集群,由 Solr Operator 部署和管理。 我想介绍的最后一部分是使用 Prometheus Stack 进行性能监控。

Prometheus Stack

您可能已经在使用 Prometheus 进行监控,但如果您的集群中尚未安装,请使用安装说明来安装包含 Grafana 的 Prometheus Stack。

Prometheus Exporter

该 Operator 的文档介绍了如何为您的 SolrCloud 实例部署 Prometheus Exporter。 由于我们启用了基本身份验证和 TLS,您需要确保 Exporter 可以使用以下配置设置与安全的 Solr Pod 进行通信。

 solrReference:
    cloud:
      name: "explore"
    basicAuthSecret: explore-solrcloud-basic-auth
 solrTLS:
    restartOnTLSSecretUpdate: true
    pkcs12Secret:
      name: explore-selfsigned-cert-tls
      key: keystore.p12
    keyStorePasswordSecret:
      name: pkcs12-keystore-password
      key: password-key

请确保 pkcs12Secret.name 是正确的,具体取决于您使用的是自签名证书还是由其他 CA(如 Let's Encrypt)颁发的证书。

确保 Prometheus Operator 从中抓取指标的服务是正确的

kubectl get svc -l solr-prometheus-exporter=explore-prom-exporter

如果这显示服务运行正常,那么创建一个服务监视器以触发 Prometheus 通过 explore-prom-exporter-solr-metrics 服务开始从 Exporter Pod 抓取指标。

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: solr-metrics
  labels:
    release: prometheus-stack
spec:
  selector:
    matchLabels:
      solr-prometheus-exporter: explore-prom-exporter
  namespaceSelector:
    matchNames:
    - sop030
  endpoints:
  - port: solr-metrics
    interval: 15s

在 Exporter 开始生成有用的指标之前,您需要在集群中至少创建一个集合。

Grafana 仪表盘

使用 kubectl expose 为 Grafana 创建一个负载均衡器(外部 IP)

kubectl expose deployment prometheus-stack-grafana --type=LoadBalancer \
   --name=grafana -n monitoring

等待一段时间后,通过执行以下操作获取 grafana 服务的外部 IP 地址:

kubectl -n monitoring get service grafana \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

或者,您可以直接打开一个端口转发到监听 3000 端口的 Grafana Pod。

使用 adminprom-operator 登录 Grafana

从源发行版下载默认的 Solr 仪表盘

wget -q -O grafana-solr-dashboard.json \
  "https://raw.githubusercontent.com/apache/lucene-solr/branch_8x/solr/contrib/prometheus-exporter/conf/grafana-solr-dashboard.json"

手动将 grafana-solr-dashboard.json 文件导入到 Grafana 中。

此时,您应该加载一些数据并运行查询性能测试。如果您运行的是多区域集群,请务必将以下查询参数添加到您的查询请求中,以优先选择同一区域中的副本(这有助于减少当所有区域都有健康副本时,每个请求的跨区域流量)。 如果您没有查询负载测试工具,那么我建议您查看 Gatling (gatling.io)。

shards.preference=node.sysprop:sysprop.availability_zone,replica.location:local

总结

至此,您现在拥有了一个创建安全、具备高可用性、负载均衡的 Solr 集群的蓝图,并通过 Prometheus 和 Grafana 进行性能监控。 在部署到生产环境之前,您还需要考虑备份/恢复、自动扩展以及关键健康指标的告警。 希望我能在以后的文章中介绍这些额外的方面。

还有其他您想了解更多信息的问题吗?请告诉我们,我们在 slack #solr-operator 或通过 GitHub Issues 上联系我们。

这是我在这篇文章中使用的 SolrCloud、Prometheus Exporter 和支持对象的 YAML 的最终列表。 祝您使用愉快!

---
apiVersion: v1
kind: Secret
metadata:
  name: pkcs12-keystore-password
stringData:
  password-key: Test1234

---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: explore-selfsigned-cert
spec:
  subject:
    organizations: ["self"]
  dnsNames:
    - localhost
  secretName: explore-selfsigned-cert-tls
  issuerRef:
    name: selfsigned-issuer
  keystores:
    pkcs12:
      create: true
      passwordSecretRef:
        key: password-key
        name: pkcs12-keystore-password

---
apiVersion: solr.apache.org/v1beta1
kind: SolrCloud
metadata:
  name: explore
spec:
  customSolrKubeOptions:
    podOptions:
      resources:
        limits:
          memory: 3Gi
        requests:
          cpu: 700m
          memory: 3Gi
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: "technology"
                  operator: In
                  values:
                  - solr-cloud
                - key: "solr-cloud"
                  operator: In
                  values:
                  - explore
              topologyKey: topology.kubernetes.io/zone
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                - key: "technology"
                  operator: In
                  values:
                  - solr-cloud
                - key: "solr-cloud"
                  operator: In
                  values:
                  - explore
              topologyKey: kubernetes.io/hostname
      initContainers: # additional init containers for the Solr pods
        - name: set-zone # GKE specific, avoids giving get nodes permission to the service account
          image: curlimages/curl:latest
          command:
            - '/bin/sh'
            - '-c'
            - |
              zone=$(curl -sS http://metadata.google.internal/computeMetadata/v1/instance/zone -H 'Metadata-Flavor: Google')
              zone=${zone##*/}
              if [ "${zone}" != "" ]; then
                echo "export SOLR_OPTS=\"\${SOLR_OPTS} -Davailability_zone=${zone}\"" > /docker-entrypoint-initdb.d/set-zone.sh
              fi
          volumeMounts:
            - name: initdb
              mountPath: /docker-entrypoint-initdb.d
      volumes:
      - defaultContainerMount:
          mountPath: /docker-entrypoint-initdb.d
          name: initdb
        name: initdb
        source:
          emptyDir: {}

  dataStorage:
    persistent:
      pvcTemplate:
        spec:
          resources:
            requests:
              storage: 2Gi
      reclaimPolicy: Delete
  replicas: 3
  solrImage:
    repository: solr
    tag: 8.8.2
  solrJavaMem: -Xms500M -Xmx510M
  updateStrategy:
    managed:
      maxPodsUnavailable: 2
      maxShardReplicasUnavailable: 2
    method: Managed

  solrAddressability:
    commonServicePort: 443
    external:
      domainName: YOUR_DOMAIN_NAME_HERE
      method: Ingress
      nodePortOverride: 443
      useExternalAddress: false
    podPort: 8983

  solrTLS:
    restartOnTLSSecretUpdate: true
    pkcs12Secret:
      name: explore-selfsigned-cert-tls
      key: keystore.p12
    keyStorePasswordSecret:
      name: pkcs12-keystore-password
      key: password-key

  solrSecurity:
    authenticationType: Basic

  zookeeperRef:
    provided:
      chroot: /explore
      image:
        pullPolicy: IfNotPresent
        repository: pravega/zookeeper
        tag: 0.2.9
      persistence:
        reclaimPolicy: Delete
        spec:
          accessModes:
          - ReadWriteOnce
          resources:
            requests:
              storage: 2Gi
      replicas: 3
      zookeeperPodPolicy:
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 250m
            memory: 500Mi

---
apiVersion: solr.apache.org/v1beta1
kind: SolrPrometheusExporter
metadata:
  labels:
    controller-tools.k8s.io: "1.0"
  name: explore-prom-exporter
spec:
  customKubeOptions:
    podOptions:
      resources:
        requests:
          cpu: 300m
          memory: 800Mi
  solrReference:
    cloud:
      name: "explore"
    basicAuthSecret: explore-solrcloud-basic-auth
    solrTLS:
      restartOnTLSSecretUpdate: true
      pkcs12Secret:
        name: explore-selfsigned-cert-tls
        key: keystore.p12
      keyStorePasswordSecret:
        name: pkcs12-keystore-password
        key: password-key
  numThreads: 6
  image:
    repository: solr
    tag: 8.8.2

---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: solr-metrics
  labels:
    release: prometheus-stack
spec:
  selector:
    matchLabels:
      solr-prometheus-exporter: explore-prom-exporter
  namespaceSelector:
    matchNames:
    - sop030
  endpoints:
  - port: solr-metrics
    interval: 15s