Running Solr on Kubernetes

2020-02-10 11:13:33 浏览数 (1)

原文地址:https://lucidworks.com/post/running-solr-on-kubernetes-part-1/

包括自己在试验过程的一些测试、解释和注意事项

我们将为搜索工程师介绍在Kubernetes(k8s)上运行Solr的基础知识。 具体来说,我们涵盖以下主题:

  1. Getting started with Google Kubernetes Engine (GKE)。 (GKR入门)
  2. Helm charts.
  3. StatefulSets, initContainers, ConfigMaps, and Persistent Volumes。
  4. Liveness and Readiness Probes
  5. Cross pod synchronization。 (跨pod同步)
  6. Load-balancing services and pod selectors。 (负载平衡服务和Pod选择器)
  7. Upgrading Solr with zero-downtime canary style deployments。 (丝雀零停机的Solr升级)
  8. Performance and Load Testing。 (性能和负载测试)
  9. Monitoring Solr metrics with Prometheus and Grafana。 (使用Prometheus and Grafana进行监控Solr metrics)
  10. Encrypting traffic between Solr instances using TLS。 (使用TLS加密Solr实例之间的流量)

在下一篇文章中,我们将深入探讨有关自动缩放,性能和负载测试以及其他高级操作的问题。 在深入研究细节之前,让我们探讨为什么可能要在Kubernetes上运行Solr的问题。 也就是说,k8s为Solr运算符提供了三个主要优点:

  1. 帮助实施最佳实践和成熟的分布式系统设计模式,
  2. 降低了诸如Solr之类的复杂系统的拥有成本,并且
  3. 在用户希望运行基于微服务的应用程序的相同操作环境中运行Solr。

就最佳实践和设计模式而言,Kubernetes提供了一种通用语言来声明如何在生产环境中安装,配置和维护分布式应用程序。 运营工程师学习如何管理Solr使用Kubernetes native resources like services, StatefulSets, and volume claims,而不必担心内部实现细节。 借助Kubernetes,运维团队可以使用标准工具专注与集群调整,监控,性能测试,日志,报警等。

关于降低拥有成本,Kubernetes使一般运营工程师可以运行Solr,而我们的客户无需投资培训或雇用专家。 这对于Solr尤为重要,因为在Solr中,操作大型Solr集群通常需要非常专业的技能。 在当今的就业市场上,让Solr专家离开以获得更好的机会是一个真正的风险。 当然,k8s并不能消除大规模运行Solr的所有复杂性,但是要走很长一段路。

Kubernetes专为管理基于云的微服务的应用程序而构建。 Solr能够在不到一秒的时间内搜索大量数据集,并通过流表达式提供低延迟的专业分析,因此对于数据密集型应用程序来说,Solr是一个有吸引力的后端。

但是,在几秒钟内将微服务部署到Kubernetes不会产生任何效果,因为随后必须为k8之外的Solr进行复杂的部署过程。 理想情况下,运维团队只需将Solr以及与其相关的微服务应用程序一起部署即可。 Lucidworks提供的Solr helm chart 使这成为现实。

既然您已经知道了为什么在Kubernetes上运行Solr是个好主意,那么让我们振作起来,在云中启动Solr集群。

Prerequisites 先决条件

在本节中,我们将介绍如何使用Kubernetes进行设置以及如何在GKE中启动您的第一个集群。 如果您已经熟悉kubectl,helm,gcloud和GKE,则可以安全地跳到下一部分。

Kubernetes

在整个文档中,我们展示了如何部署到基于Google Kubernetes Engine(GKE)的集群。 建议使用GKE选项,因为您可以快速部署多个节点,GKE是一个学习k8s概念的有趣环境,Google会给您$ 300的免费赠金以开始使用。 在继续之前,请按照以下说明设置Google Cloud访问和SDK: https : //cloud.google.com/sdk/docs/quickstarts 。 您也可以在minikube上本地运行一个单节点Solr集群,但是这里不做介绍。

Kubectl

kubectl是用于与Kubernetes集群进行交互的命令行工具。 它应该已经与minikube或gcloud SDK一起安装了。 要验证kubectl是否可用,请执行:kubectl version。 如果尚未安装,只需执行以下操作:

代码语言:txt复制
gcloud components install kubectl

最终,您将厌倦了键入“ kubectl”,因此现在为将来的自己提供帮助,并在您的shell初始化脚本中添加以下别名(例如〜/ .bash_profile):

代码语言:txt复制
alias k=kubectl

Launch GKE Cluster

启动一个kubernetes实例。

可以使用docker desktop:

https://github.com/AliyunContainerService/k8s-for-docker-desktop

Helm

Helm是k8s生态系统中流行的用于部署应用程序的工具。 我们在下面使用Helm来部署Solr,因此请按照此处的说明进行Helm的设置: https : //github.com/helm/helm 。 安装Tiller是使用Helm的最常见方法,但并不需要按照本文进行操作。 在Mac上简短地尝试:

代码语言:txt复制
安装helm v2版本
brew install kubernetes-helm@2

添加环境变量:
echo 'export PATH="/usr/local/opt/helm@2/bin:$PATH"' >> ~/.bash_profile

添加tiller到 [k8s] service account
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller

安装tiller
helm init --service-account tiller

helm version

Deploy Solr to GKE

首先,将带有Zookeeper的3节点Solr集群部署到GKE。克隆仓库或从以下地址下载zip文件: https : //github.com/lucidworks/solr-helm-chart 。 我们已将Helm图表提交到https://github.com/helm/charts,但仍在等待批准。

Helm的一个不错的功能是chart可以动态链接到其他charts。 例如Solr chart依赖于Zookeeper chart。 让我们通过以下操作将Zookeeper chart拖入Solr chat:

代码语言:txt复制
# 先移除原先的仓库
helm repo remove stable

# 添加新的仓库地址
helm repo add incubator https://storage.googleapis.com/kubernetes-charts-incubator

# 添加新的仓库地址(阿里云)
helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

# 更新charts列表
helm repo update

# 查询仓库列表
helm search incubator

cd solr-helm-chart/solr

# 下载requirements.yaml文件中定义的其他Chart,这些Chart会存放在charts目录
helm dependency update

在部署之前,请花一点时间查看values.yaml中定义的配置变量。 该文件允许您为Solr部署自定义最常见的变量,例如资源分配,传递给Solr的JVM args和Solr版本(当前为7.6.0)。

对于生产来说,通常向在k8s中运行的Helm Tiller服务提交helm charts,但是对于本练习让我们跳过Tiller并使用helm template命令从Solr和Zookeeper helm charts中呈现Kubernetes清单。 让我们还将Solr版本更改为7.5.0,以便稍后在练习中可以升级到7.6.0:

代码语言:txt复制
# 生成solr.yaml模板文件
helm template . --name solr > solr.yaml

helm template . --set image.tag=7.5.0 --name solr > solr.yaml

现在,使用以下命令将Solr清单(solr.yaml)部署到Kubernetes:

代码语言:txt复制
kubectl apply -f solr.yaml

在Zookeeper和Solr初始化时要耐心等待。Kubernetes可能需要从Docker Hub提取Docker映像以及设置持久卷。 此外,在Pod初始化时,您也不必担心在GCloud控制台UI中看到的任何警告。 根据我们的经验,在配置Pod时,集群工作负载UI的警告有点过于激进,可能会给人错误的感觉。 如果首次执行此操作后3到4分钟内Solr和Zookeeper并没有全部运行,则可以开始故障排除。

查看pods状态,:

代码语言:txt复制
kubectl get pods

当它们准备就绪时,您将看到类似以下的输出:

代码语言:txt复制
NAME               READY     STATUS    RESTARTS   AGE
solr-0             1/1       Running   0          38m
solr-1             1/1       Running   0          35m
solr-2             1/1       Running   0          34m
solr-zookeeper-0   1/1       Running   0          38m
solr-zookeeper-1   1/1       Running   0          37m
solr-zookeeper-2   1/1       Running   0          36m

如果Pod无法进入“Running”状态或上线速度较慢,请使用describe命令查看Pod的特定活动,例如“ kubectl describe pod solr-0”。describe命令输出包括Kubernetes启动Pod所发生的事件。花一点时间查看为solr-0 pod报告的事件。

看起来一切正常,那么现在呢? 大多数将Solr用作后端的应用程序都不会将其公开给互联网,而是使用无状态微服务搜索应用程序(例如Lucidworks Fusion)作为前端。因此,我们使用以下kubectl port-forward solr-0 28983:8983将本地端口转发到集群: kubectl port-forward solr-0 28983:8983

Now, point your browser to: http://localhost:28983/solr/#/~cloud?view=nodes

You should see something like:

avataravatar

创建一个集合:

代码语言:txt复制
curl -v "http://localhost:28983/solr/admin/collections?action=CREATE&name=perf3x1&numShards=3&replicationFactor=1&maxShardsPerNode=1&collection.configName=_default"

增加一些测试数据:

代码语言:txt复制
wget https://raw.githubusercontent.com/apache/lucene-solr/master/solr/example/exampledocs/books.json
curl "http://localhost:28983/solr/perf3x1/update/json/docs?commit=true" -H "Content-Type: application/json" --data-binary @books.json

此时,您将在Kubernetes中运行一个3节点的Solr集群。现在,我们将详细介绍部署的工作方式,并介绍一些基本操作,例如在Solr实例之间启用TLS。

Kubernetes Nuts & Bolts

在本节中,我们介绍了Solr部署的一些有趣方面。 为了节省时间,我们将不介绍Zookeeper,而是为您提供有关Zookeeper如何在Kubernetes中工作的以下指南: https://kubernetes.io/docs/tutorials/stateful-application/zookeeper/

而且,这里没有涵盖许多重要的Kubernetes概念。 有关k8s概念的更深入介绍,请参见:https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/

Pod

Pod是一组共享网络和存储的一个或多个容器(通常是Docker)。简单的说,可以将pod视为在安装了特定应用程序的逻辑主机上的一组相关的进程。Pod中的容器共享相同的IP地址和端口空间,因此它们可以通过localhost进行通信,但不能绑定到相同的端口。

由于k8s是一个容器编排框架,您可能想知道为什么他们发明了一个新术语而不是仅仅使用“容器”? 事实证明,尽管许多部署在Pod中只有一个容器,而我们的Solr部署就是这种情况,但部署具有多个容器的Pod并不少见。

一个很好的例子是Istio部署的sidecar Envoy代理。 具有多个相关容器的pod的经典示例是在同一pod中运行Apache httpd和memcached。 互联网上有很多关于Pod的丰富资源,因此让我们继续研究更有趣的概念,并根据需要介绍Solr Pod的重要方面。

StatefulSet启动solr

如果您是Kubernetes的新手,那么您需要了解的第一件事是Pod在集群中移动,您对此没有太多控制权!实际上,您不必在乎Pod是否在集群中移动,因为该过程对于Kubernetes的设计至关重要。

Kubernetes执行的主要任务之一是平衡群集资源的利用率。 作为此过程的一部分,k8可能会决定将Pod移动到另一个节点。 或者,一个节点可能由于各种原因而发生故障,而k8则需要替换集群中另一个运行正常的节点上的那些发生故障的Pod。

因此,请稍等一会,如果k8将Solr pod移至另一个节点会发生什么情况。 如果Solr使用的磁盘没有附带,则在新节点上初始化Solr时,它将没有任何可用的cores(Lucene索引),并且必须从磁盘中的另一个副本执行可能昂贵的快照复制。 又由于该信息也存储在磁盘上,它将如何知道需要复制哪些cores? 对于使用一个replication因子的集合,情况将更加糟糕,因为没有其他副本可以与之同步。

这个问题并非Solr独有。 值得庆幸的是,Kubernetes为Solr等系统提供了一种出色的解决方案,该系统需要在磁盘上保持状态并在Pod移动(或崩溃并重新启动)时恢复状态,即StatefulSets。

我们可以花整个blog来研究StatefulSet的详细信息,但是从https://cloud.google.com/kubernetes-engine/docs/concepts/statefulset上,已经有大量资源可以做到这一点。

我们确实想消除一个误解,即在讨论在Kubernetes上运行Solr时听到过的喃喃自语,即k8s不适合有状态应用程序。 的确,k8s与运行有状态应用程序的历史混杂在一起,但这是个老新闻。 StatefulSet是k8中的一流功能,并且有许多成功的有状态应用程序的示例。 在helm github站点上搜索带StatefulSet的charts有110个: https://github.com/helm/charts/search?l=YAML&q=StatefulSet.

现在,让我们来看一个StatefulSet的运行情况。如果列出了pod( kubectl get pods ),您将看到以下输出:

代码语言:txt复制
NAME                             READY   STATUS    RESTARTS   AGE
solr-0                           1/1     Running   0          120m
solr-1                           1/1     Running   0          113m
solr-2                           1/1     Running   0          112m
solr-exporter-58dbb665db-46wfx   1/1     Running   0          120m
solr-zookeeper-0                 1/1     Running   0          120m
solr-zookeeper-1                 1/1     Running   0          120m
solr-zookeeper-2                 1/1     Running   0          119m

这些是StatefulSet中名为“solr”的pods。 注意,每个都获得一个稳定的hostname,其主机索引以0开头; 如果Pod销毁,它将返回相同的主机名但具有不同的IP地址。 尽管对于Solr而言并不重要,但是由于它使用Zookeeper来协调集群活动,因此集合中的副本将以升序初始化,并以降序删除。

所以如果需要修改pods数量,则修改values.yaml定义的变量,然后进行一次发布即可。

代码语言:txt复制
# 修改values.yaml文件:
replicaCount: 5

# 重新发布:
> helm template . --name solr > solr.yaml
> kubectl apply -f solr.yaml

poddisruptionbudget.policy/solr-zookeeper configured
service/solr-zookeeper-headless configured
service/solr-zookeeper configured
statefulset.apps/solr-zookeeper configured
statefulset.apps/solr configured
configmap/solr-config-map configured
poddisruptionbudget.policy/solr configured
service/solr-exporter configured
deployment.apps/solr-exporter configured
service/solr-headless configured
service/solr-svc configured

# 查看节点:
> kubectl get pods
NAME                             READY   STATUS     RESTARTS   AGE
solr-0                           1/1     Running    0          21m
solr-1                           1/1     Running    0          19m
solr-2                           1/1     Running    0          18m
solr-3                           1/1     Running    0          34s
solr-4                           0/1     Init:1/2   0          9s

添加副本:

avataravatar

Statefulset介绍

StatefulSet 是Kubernetes中的一种控制器,他解决的什么问题呢?我们知道Deployment是对应用做了一个简化设置,Deployment认为一个应用的所有的pod都是一样的,他们之间没有顺序,也无所谓在那台宿主机上。需要扩容的时候就可以通过pod模板加入一个,需要缩容的时候就可以任意杀掉一个。但是实际的场景中,并不是所有的应用都能做到没有顺序等这种状态,尤其是分布式应用,他们各个实例之间往往会有对应的关系,例如:主从、主备。还有数据存储类应用,它的多个实例,往往会在本地磁盘存一份数据,而这些实例一旦被杀掉,即使从建起来,实例与数据之间关系也会丢失,而这些实例有不对等的关系,实例与外部存储有依赖的关系的应用,被称作“有状态应用”。StatefulSet与Deployment相比,相同于他们管理相同容器规范的Pod,不同的时候,StatefulSet为pod创建一个持久的标识符,他可以在任何编排的时候得到相同的标识符。

StatefulSet由以下几个部分组成:

  • Headless Service(无头服务)用于为Pod资源标识符生成可解析的DNS记录。
  • volumeClaimTemplates (存储卷申请模板)基于静态或动态PV供给方式为Pod资源提供专有的固定存储。
  • StatefulSet,用于管控Pod资源。

kubectl explain sts.spec 主要字段解释:

  1. replicas 副本数
  2. selector 那个pod是由自己管理的
  3. serviceName 必须关联到一个无头服务商
  4. template 定义pod模板(其中定义关联那个存储卷)
  5. volumeClaimTemplates 生成PVC

Statefulset优点

  1. 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  2. 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
  3. 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
  4. 有序、平滑的收缩、删除 既Pod是有顺序的,在收缩或者删除的时候要依据定义的顺序依次进行(既从N-1到0,既倒序)。
  5. 有序的滚动更新,或金丝雀发布。

Persistent Volumes

为了证明StatefulSet中的副本返回了相同的hostname和附加的存储,我们需要杀死Pod。 在开始杀死集群中的Pod之前,让我们介绍一下Solr StatefulSets的重要方面,即PersistentVolumes。 如果查看Solr helm chart,您会注意到StatefulSet具有以下volumeMount:

代码语言:txt复制
volumeMounts:
  - name: solr-pvc
    mountPath: /opt/solr/server/home

让我们登录solr-0,看看它是什么:

代码语言:txt复制
kubectl exec -it solr-0 --container solr -- /bin/bash
代码语言:txt复制
solr@solr-1:/opt/solr-8.4.0$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0  59.6G  0 disk
└─sda1   8:1    0  59.6G  0 part /etc/hosts
sr0     11:0    1 470.7M  0 rom
sr1     11:1    1   148K  0 rom
sr2     11:2    1 824.6M  0 rom

这表明我们在/opt/solr/server/home安装了20G磁盘。那是怎么发生的? 为了使永久卷附加到集中的每个副本,您需要一个卷声明模板,该模板设置组标识(对于Solr,gid = 8983和所需的大小(20 GB):

代码语言:txt复制
statefulset.yaml生成到solr.yaml文件

volumeClaimTemplates:
  - metadata:
      name: solr-pvc
      annotations:
        pv.beta.kubernetes.io/gid: "8983"
    spec:
      accessModes:
        - ReadWriteOnce
        
      resources:
        requests:
          storage: 20Gi

显然,真正的Solr部署需要更多磁盘空间,可以通过更改values.yaml文件中的volumeClaimTemplates.storageSize参数来增加磁盘空间。 在后台,GKE从Google计算引擎分配磁盘。 您可以使用UI从UI获取有关持久卷附加的存储的详细信息,如下所示:

avataravatar

或者通过命令:

代码语言:txt复制
kubectl describe PersistentVolumeClaim solr-pvc-solr-0

得到的结果:

代码语言:txt复制
Name:          solr-pvc-solr-0
Namespace:     default
StorageClass:  hostpath
Status:        Bound
Volume:        pvc-a0b488d5-90e6-4ad2-918c-d36f0aa2ee9a
Labels:        app=solr
               component=server
               release=solr
Annotations:   control-plane.alpha.kubernetes.io/leader:
                 {"holderIdentity":"32e0bba2-4724-11ea-bfa0-3c15c2dd97b4","leaseDurationSeconds":15,"acquireTime":"2020-02-04T13:28:09Z","renewTime":"2020-...
               pv.beta.kubernetes.io/gid: 8983
               pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
               volume.beta.kubernetes.io/storage-provisioner: docker.io/hostpath
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      20Gi
Access Modes:  RWO
VolumeMode:    Filesystem
Mounted By:    solr-0
Events:        <none>

Using an initContainer to Bootstrap Solr Home

如果查看/opt/solr/server/home目录,则会看到solr.xml文件。这里发生了一些有趣的事情。 首先,StatefulSet的pod规范,使用环境变量将以下内容传递给Solr:

代码语言:txt复制
- name: "SOLR_HOME"
  value: "/opt/solr/server/home"

Solr 7.x要求SOLR_HOME目录包含solr.xml文件。当k8s挂载solr-pvc卷时,它最初是一个空目录。 因此,我们利用另一个有用的Kubernetes工具initContainer将solr.xml引导到我们的持久卷目录中。

代码语言:txt复制
statefulset.yaml生成到solr.yaml文件

initContainers:
  - name: check-zk
    image: busybox:latest
    command:
      - 'sh'
      - '-c'
      - |
        COUNTER=0;
        while [  $COUNTER -lt 120 ]; do
          for i in "solr-zookeeper-0.solr-zookeeper-headless" "solr-zookeeper-1.solr-zookeeper-headless" "solr-zookeeper-2.solr-zookeeper-headless" ;
            do mode=$(echo srvr | nc $i 2181 | grep "Mode");
              if [ "$mode" == "Mode: leader" ] || [ "$mode" == "Mode: standalone" ]; then
                exit 0;
              fi;
            done;
          let COUNTER=COUNTER 1;
          sleep 2;
        done;
        echo "Did NOT see a ZK leader after 240 secs!";
        exit 1;
  - name: "cp-solr-xml"
    image: busybox:latest
    command: ['sh', '-c', 'cp /tmp/solr.xml /tmp-config/solr.xml']
    volumeMounts:
    - name: "solr-xml"
      mountPath: "/tmp"
    - name: "solr-pvc"
      mountPath: "/tmp-config"

cp-solr-xml initContainer只是将solr.xml文件从/tmp 复制到/tmp-config,该文件恰好与Solr容器在/opt/solr/server/home看到的永久卷(solr-pvc)相同。 但是,等等,solr.xml是如何进入initContainer的/tmp的呢?使用Kubernetes ConfigMap和StatefulSet的volume进行定义:

代码语言:txt复制
solr-xml-configmap.yaml生成到solr.yaml

apiVersion: "v1"
kind: "ConfigMap"
metadata:
  name: "solr-config-map"
  labels:
    app: solr
    chart: solr-1.0.0
    release: solr
    heritage: Tiller
data:
  solr.xml: |
    <?xml version="1.0" encoding="UTF-8" ?>
    <solr>
      <solrcloud>
        <str name="host">${host:}</str>
        <int name="hostPort">${jetty.port:8983}</int>
        <str name="hostContext">${hostContext:solr}</str>
        <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>
        <int name="zkClientTimeout">${zkClientTimeout:30000}</int>
        <int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:600000}</int>
        <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
        <str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>
        <str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>
      </solrcloud>
      <shardHandlerFactory name="shardHandlerFactory"
        class="HttpShardHandlerFactory">
        <int name="socketTimeout">${socketTimeout:600000}</int>
        <int name="connTimeout">${connTimeout:60000}</int>
      </shardHandlerFactory>
    </solr>

现在,在ConfigMap的solr.xmlkey中包含一个solr.xml文件内容。 为了使其可用于StatefulSet中的pod,我们使用以下命令将ConfigMap挂载为volume:

代码语言:txt复制
statefulset.yaml生成到solr.yaml

volumes:
  - name: solr-xml
    configMap:
      name: solr-config-map
      items:
      - key: solr.xml
        path: solr.xml

使用initContainers和ConfigMaps将solr.xml引导到Solr的主目录中非常麻烦。 它确实是使用initContainers在启动主容器之前使pod处于良好状态的一个很好的例子。 将来,Solr应该对此内置的问题有更好的解决方案,请参阅: https : //issues.apache.org/jira/browse/SOLR-13035 。

概括地说,Solr StatefulSet已根据集合名称和副本序号为集群中的每个节点分配了主机名,例如solr-0,solr-1等,并为每个pod分配了20G永久volume在/opt/solr/server/home目录。

Replacing Lost Stateful Replicas

首先查看solr pod运行在哪个node上。

代码语言:txt复制
> kubectl get pod -o=custom-columns=NODE:.spec.nodeName,NAME:.metadata.name
NODE             NAME
docker-desktop   solr-0
docker-desktop   solr-1
docker-desktop   solr-2
docker-desktop   solr-exporter-58dbb665db-46wfx
docker-desktop   solr-zookeeper-0
docker-desktop   solr-zookeeper-1
docker-desktop   solr-zookeeper-2

现在kill调一个solr pod看会发生什么:

代码语言:txt复制
kubectl delete po solr-2 --force --grace-period 0

查看现在solr pod状态:

代码语言:txt复制
kubectl get pods
NAME                             READY   STATUS     RESTARTS   AGE
apple-app                        1/1     Running    0          17h
banana-app                       1/1     Running    0          17h
solr-0                           1/1     Running    0          4h19m
solr-1                           1/1     Running    0          4h12m
solr-2                           0/1     Init:0/2   0          1s

等待片刻后,请注意丢失的solr-2 pod已重新添加到集群中。 如果您重新运行get nodes,您将看到solr-2 pod已经在之前相同的nodes上重新创建。 这是因为k8s在努力维持平衡集群。

Liveness and Readiness Probes

Kubernetes使用liveness和readiness探针时刻监控你的pods的状态。 目前,Solr helm chart使用以下命令/solr/admin/info/system:

代码语言:txt复制
# solr.yaml文件 由statefulset生成

livenessProbe:
  initialDelaySeconds: 20
  periodSeconds: 10
  httpGet:
    scheme: "HTTP"
    path: /solr/admin/info/system
    port: 8983
readinessProbe:
  initialDelaySeconds: 15
  periodSeconds: 5
  httpGet:
    scheme: "HTTP"
    path: /solr/admin/info/system
    port: 8983

Coordinating Pod Initialization

在继续下一节之前,让我们看一下k8s如何协调Solr和Zookeeper pods之间的时序性。 具体来说,Solr要求Zookeeper在完全初始化并处理请求之前可用。 但是,对于k8s,我们希望能够在无需协调顺序的情况下部署pods。 实际上,在Kubernetes中没有在StatefulSets之间命令pod初始化的概念。 为此,我们依靠initContainer在k8s调用主Solr容器之前测试ZK运行状况。 如果ZK不健康,则initContainer睡眠几秒钟,然后一分钟重试。

代码语言:txt复制
statefulset.yaml生成到solr.yaml文件

initContainers:
  - name: check-zk
    image: busybox:latest
    command:
      - 'sh'
      - '-c'
      - |
        COUNTER=0;
        while [  $COUNTER -lt 120 ]; do
          for i in "solr-zookeeper-0.solr-zookeeper-headless" "solr-zookeeper-1.solr-zookeeper-headless" "solr-zookeeper-2.solr-zookeeper-headless" ;
            do mode=$(echo srvr | nc $i 2181 | grep "Mode");
              if [ "$mode" == "Mode: leader" ] || [ "$mode" == "Mode: standalone" ]; then
                exit 0;
              fi;
            done;
          let COUNTER=COUNTER 1;
          sleep 2;
        done;
        echo "Did NOT see a ZK leader after 240 secs!";
        exit 1;

如果Solr不在线,请使用以下命令检查initContainers的状态:

代码语言:txt复制
kubectl describe pod <pod name>

Upgrading Solr

还记得我们说过Kubernetes帮助实施最佳实践和经过验证的设计模式吗? 在不停机的情况下执行滚动升级是StatefulSet中内置的最佳实践之一。 要查看实际效果,只需重新运行helm template命令,而无需使用–set image.tag参数:

代码语言:txt复制
helm template . --name solr > solr.yaml

kubectl apply -f solr.yaml

请注意,它如何检测到Solr StatefulSet发生了变化,但其他所有资源均保持不变。 从solr-2开始,k8s进行从Solr 7.5.0容器到7.6.0容器的滚动升级。 solr-2初始化之后,查看一下日志,您会看到它现在正在运行Solr 7.6.0:

一切都很好,只是它没有考虑到要升级的节点上所有leader的重新选举。 在这种情况下,Kube也支持我们,因为它向solr进程发送了SIGTERM,这触发了solr开始卸载内核并正常关闭。 k8s将等待30秒以使Solr正常关闭,这对于大多数用例来说已经足够了。 如果需要,您可以使用Pod规范上的terminationGracePeriodSeconds增加超时时间。

Kubernetes升级策略

在Kubernetes 1.7及更高版本中,通过.spec.updateStrategy字段允许配置或禁用Pod、labels、source request/limits、annotations自动滚动更新功能。

  • OnDelete:通过.spec.updateStrategy.type 字段设置为OnDelete,StatefulSet控制器不会自动更新StatefulSet中的Pod。用户必须手动删除Pod,以使控制器创建新的Pod。
  • RollingUpdate 滚动更新:通过.spec.updateStrategy.type 字段设置为RollingUpdate,实现了Pod的自动滚动更新,如果.spec.updateStrategy未指定,则此为默认策略。StatefulSet控制器将删除并重新创建StatefulSet中的每个Pod。它将以Pod终止(从最大序数到最小序数)的顺序进行更新每个Pod。在更新下一个Pod之前,必须等待这个Pod Running and Ready。
  • Partitions 滚动更新的分区更新:通过指定 .spec.updateStrategy.rollingUpdate.partition 来对 RollingUpdate 更新策略进行分区,如果指定了分区,则当 StatefulSet 的 .spec.template 更新时,具有大于或等于分区序数的所有 Pod 将被更新。 具有小于分区的序数的所有 Pod 将不会被更新,即使删除它们也将被重新创建。如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition 大于其 .spec.replicas,则其 .spec.template 的更新将不会更新Pod。默认partition的值是0,简单来说就是当partition等N,N 的都会更新。

示例:修改更新策略,以partition方式进行更新,更新值为2,只有myapp编号大于等于2的才会进行更新。类似于金丝雀部署方式。

代码语言:txt复制
# 修改分区更新必须大于等于2的pods
> kubectl patch sts solr -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'

# 进行升级,将solr版本修改为8.3
> kubectl set image sts/solr solr=solr:8.3.0

# 查看状态
> kubectl get pods solr-2 -o yaml |grep image

    image: solr:8.3.0
    imagePullPolicy: Always
    image: busybox:latest

则只会将solr-2升级为8.3.0版本。

多StatefulSet的金丝雀发布

在StatefulSet上滚动更新升级所有Pod,但是如果要在整个集群上滚动发布Solr更新之前进行试验,即要执行所谓的“canary release”,那该怎么办。

例如,假设我们想尝试Solr 8.0.0,但是仅升级一部分,以防万一我们的实验出错了。 或者,可以尝试一些不同的Solr配置参数组合。 关键是您的canary pod有了一些更改,需要在跨群集推出之前进行验证。

对于本实验,我们只想将发布单个canary pod。 在实施该解决方案之前,让我们介绍一下Kubernetes服务如何与一组Pod一起工作。 首先,使用以下命令查看为Solr集群定义的服务:

代码语言:txt复制
> kubectl get svc -o wide -l app=solr

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE   SELECTOR
solr-exporter   ClusterIP   10.105.249.59    <none>        9983/TCP   10m   app=solr,component=exporter,release=solr
solr-headless   ClusterIP   None             <none>        8983/TCP   10m   app=solr,component=server,release=solr
solr-svc        ClusterIP   10.103.229.114   <none>        8983/TCP   10m   app=solr,component=server,release=solr

Kubernetes使用Pod标签选择器为一组Pod提供上层请求的负载均衡服务。 例如,solr-svc服务选择带有以下标签的容器:app=solr,release=solr和component=server:

代码语言:txt复制
# solr.yaml文件内容:

---
# Source: solr/templates/service-headless.yaml
---

apiVersion: "v1"
kind: "Service"
metadata:
  name: "solr-headless"
  labels:
    app: solr
    chart: solr-1.0.0
    release: solr
    heritage: Tiller
spec:
  clusterIP: "None"
  ports:
  - port: 8983
    name: "solr-headless"
  selector:
    app: "solr"
    release: "solr"
    component: "server"

因此,只要Pod的标签与服务的选择器匹配,Pod来自哪个StatefulSet(或Deployment)都无关紧要。 这意味着我们可以在集群中部署多个StatefulSet,每个StatefulSet指向不同版本的Solr,并且该服务将流量路由到这些SstatefulSet。

我们将其作为练习,供读者使用不同的Solr版本使用单个副本部署另一个StatefulSet。canary pod上线后,您需要使用Solr集合API将集合中的副本添加到canary Solr实例上。

扩展伸缩pods

使用kubectl的scale命名,设置pod数量,则可以扩展或减少pods数量。

有序扩展,有序收缩。

代码语言:txt复制
# 将solr应用扩容到6个pod
> kubectl scale sts solr --replicas=6

# 查看pods状态
> kubectl get pods
NAME                             READY   STATUS            RESTARTS   AGE
solr-0                           1/1     Running           2          4h41m
solr-1                           1/1     Running           2          4h40m
solr-2                           1/1     Running           2          4h39m
solr-3                           1/1     Running           2          4h21m
solr-4                           1/1     Running           2          4h20m
solr-5                           0/1     PodInitializing   0          8s

Performance Smoke Test

我们现在不会花很多时间在性能和负载测试上,因为我们将在下一篇文章中更详细地介绍它。 目前,我们要回答的问题之一是Solr在Kubernetes中是否较慢。

首先,我们需要大数据的索引,因此我们选择使用在Dataproc中运行的Spark和Lucidworks提供的spark-solr库。以下Scala脚本从存储在Google Cloud Storage(GCS)中的Spark索引导出750万个文档:

该脚本允许我们根据需要使用Spark将其扩展到尽可能多的并发索引核心,因此我们可以测试存储在GCS中的海量Solr集群和任意大小的数据集。 索引到以“ n1-standard-4”实例类型运行的3节点群集导致了16,800个文档/秒(3个分片/每个分片1个副本)。 我们在Spark端使用了12个并发执行程序核心。

相比之下,我们对在GCE(虚拟机而非容器)上运行的Solr进行了相同的测试,并获得了约15,000个文档/秒。 因此,在这种情况下,在Kube上运行速度更快,但这是一个相当小的数据集,并且云VM的性能可能会略有不同。 重要的是,Kube在使用相同的n1-standard-4实例类型的GCE中具有与基于VM的性能相当的性能。 在下一篇文章中,我们将在启用Solr复制的情况下在更大的集合上运行更长的性能和负载测试。

Solr Metrics in Prometheus

Encrypting Traffic Between Solr Instances Using TLS

Wrap Up

0 人点赞