在 GKE 上探索 Apache Solr 运算符 v0.3.0

作者:Tim Potter

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

介绍

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

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

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

Solr Operator Components

入门

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

Kubernetes 设置

我鼓励您在家中进行操作,因此请启动一个 GKE 集群并打开您的终端。如果您不熟悉 GKE,请在继续使用本文档之前完成 GKE 快速入门。为了获得更好的高可用性,您应该跨三个区域部署一个区域 GKE 集群(每个区域至少一个 Solr pod)。当然,您可以将区域集群部署到一个区域以用于开发/测试目的,但我展示的示例基于在 us-central1 区域运行的 3 节点 GKE 集群,每个区域有一个节点。

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

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

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

要验证 ingress 控制器是否正常运行,请执行以下操作

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

对于本文档,我们将把运算符和 Solr 部署到名为sop030的命名空间中

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

Solr 运算符设置

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

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 运算符 pod

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

请注意,我使用标签选择器过滤器而不是通过 ID 寻址 pod,这可以避免我必须查找 ID 来获取 pod 详细信息。

您的命名空间中还应该有一个 Zookeeper 运算符 pod 正在运行,请使用以下命令进行验证

kubectl get pod -l component=zookeeper-operator

Solr CRD

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

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

Solr 运算符定义了表示 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 的不同部分。

创建 SolrCloud

要在 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 并运行以下命令来跟踪运算符 pod 日志

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

请注意,我使用标签选择器 (-l ...) 而不是通过其 ID 寻址 pod;这避免了每次我想查看运算符日志时都必须查找 pod ID。

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

kubectl apply -f explore-SolrCloud.yaml

我们将在本文档的其余部分中更新 explore-SolrCloud.yaml 文件。
任何以 "spec:" 开头的代码部分都引用此文件。

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

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

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

kubectl get sts explore -o yaml

我依赖于此初始 SolrCloud 定义的一个略微细微的设置

  updateStrategy:
    method: StatefulSet

我们需要从 StatefulSet 开始,因为 updateStrategy 方法,这样我们才能在现有的 SolrCloud 上启用 TLS。在启用 TLS 后,我们将在此 HA 部分将其切换到 Managed。使用 Managed 需要运算符调用集合 API 来获取 CLUSTERSTATUS,这在集群从 HTTP 转换为 HTTPs 时不起作用。在实际部署中,您应该从一开始就启用 TLS,而不是在现有集群上升级到 TLS。

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

Zookeeper 连接

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

自定义 Log4J 配置

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

安全性

安全性应该始终是您的首要和主要关注点,尤其是在像 GKE 这样的公共云中运行时;您不希望成为那个系统被入侵的运维工程师。在本节中,我们将为 Solr 的 API 端点启用 TLS、基本身份验证和授权控制。有关所有配置选项的更详细说明,请参阅 SolrCloud CRD 文档。

要为 Solr 启用 TLS,您只需要一个包含公共 X.509 证书和私钥的 TLS 机密。Kubernetes 生态系统提供了一个强大的工具来签发和管理证书:cert-manager。如果您的集群中尚未安装,请按照 Solr 运算符提供的基本说明安装最新版本的 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 密钥,其中包含 Solr 使用的 X.509 证书、私钥和符合 PKCS12 标准的密钥库。花点时间使用以下命令检查密钥的内容:

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

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

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://localhost:8983/solr/admin/info/system -k

Let's Encrypt 发行的 TLS 证书

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

这是我为 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 的密钥中。

您可以跟踪 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 密钥中存储的证书和私钥的 PKCS12 密钥库。

spec:
  ...

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

最后一步是创建一个 DNS A 记录,将 Ingress(由 Solr 运算符创建)的 IP 地址映射到 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 运算符将“solr-cloud”标签设置为所有 Pod 上的 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 比集群中可用的节点多,那么您应该使用 preferredDuringSchedulingIgnoredDuringExecution 而不是 requiredDuringSchedulingIgnoredDuringExecution 来代替基于 kubernetes.io/hostname 的规则。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 的服务帐户具有获取节点权限,则可以使用 向下 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 系统属性。

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

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

副本类型

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

滚动重启

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

相比之下,该操作符增强了 StatefulSets 的滚动重启操作,以考虑哪个 Solr pod 主机 Overseer(最后重启)、pod 上的领导者数量等等。结果是 SolrCloud 的优化滚动重启过程,其中多个 pod 可以同时重启。该操作符使用 Solr 的集群状态 API 来确保在决定同时重启哪些 pod 时,每个分片*至少有一个副本在线。更重要的是,这些自定义协调过程遵循 Kubernetes 中非常重要的幂等性理念。即使在相同起始状态下调用 100 次协调,结果也应该与第 1 次和第 100 次相同。

回想一下,我最初使用的是StatefulSet方法,以便我们可以迁移现有集群以使用 TLS。让我们使用以下配置将该方法切换为使用Managed方法

spec:
  ...

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

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

* 如上所示,Managed更新策略是可定制的,可以配置为满足您的安全或速度要求。有关更多信息,请参阅更新文档

性能监控

现在我们已经拥有一个安全的、支持 HA 的 Solr 集群,由 Solr 操作符部署和管理。我想要介绍的最后一个部分是使用Prometheus 堆栈进行性能监控。

Prometheus 堆栈

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

Prometheus 导出器

该操作符的文档介绍了如何为您的 SolrCloud 实例部署 Prometheus 导出器。由于我们启用了基本身份验证和 TLS,您需要确保导出器可以使用以下配置设置与安全的 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 操作符从其抓取指标的服务是正确的

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

如果这显示了一个健康的服务,那么创建一个服务监控器,以触发 Prometheus 通过explore-prom-exporter-solr-metrics服务从导出器 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

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

Grafana 仪表盘

使用 kubectl expose 为 Grafana 创建一个 LoadBalancer(外部 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

总结

此时,您已经拥有一个创建安全、支持 HA、平衡的 Solr 集群的蓝图,该集群通过 Prometheus 和 Grafana 进行性能监控。在推出生产环境之前,您还需要考虑备份/恢复、自动扩展和关键健康指标的警报。希望我能够在以后的文章中介绍这些额外的方面。

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

以下是我在这篇文章中使用的 SolrCloud、Prometheus 导出器和支持对象 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