Running Solr on Kubernetes
原文地址:https://lucidworks.com/post/running-solr-on-kubernetes-part-1/
包括自己在试验过程的一些测试、解释和注意事项
我们将为搜索工程师介绍在Kubernetes(k8s)上运行Solr的基础知识。 具体来说,我们涵盖以下主题:
- Getting started with Google Kubernetes Engine (GKE)。
(GKR入门) - Helm charts.
- StatefulSets, initContainers, ConfigMaps, and Persistent Volumes。
- Liveness and Readiness Probes
- Cross pod synchronization。
(跨pod同步) - Load-balancing services and pod selectors。
(负载平衡服务和Pod选择器) - Upgrading Solr with zero-downtime canary style deployments。
(丝雀零停机的Solr升级) - Performance and Load Testing。
(性能和负载测试) - Monitoring Solr metrics with Prometheus and Grafana。
(使用Prometheus and Grafana进行监控Solr metrics) - Encrypting traffic between Solr instances using TLS。
(使用TLS加密Solr实例之间的流量)
在下一篇文章中,我们将深入探讨有关自动缩放,性能和负载测试以及其他高级操作的问题。 在深入研究细节之前,让我们探讨为什么可能要在Kubernetes上运行Solr的问题。 也就是说,k8s为Solr运算符提供了三个主要优点:
- 帮助实施最佳实践和成熟的分布式系统设计模式,
- 降低了诸如Solr之类的复杂系统的拥有成本,并且
- 在用户希望运行基于微服务的应用程序的相同操作环境中运行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
。 如果尚未安装,只需执行以下操作:
gcloud components install kubectl
最终,您将厌倦了键入“ kubectl”,因此现在为将来的自己提供帮助,并在您的shell初始化脚本中添加以下别名(例如〜/ .bash_profile):
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上简短地尝试:
安装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:
# 先移除原先的仓库
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:
# 生成solr.yaml模板文件
helm template . --name solr > solr.yaml
helm template . --set image.tag=7.5.0 --name solr > solr.yaml
现在,使用以下命令将Solr清单(solr.yaml)部署到Kubernetes:
kubectl apply -f solr.yaml
在Zookeeper和Solr初始化时要耐心等待。Kubernetes可能需要从Docker Hub提取Docker映像以及设置持久卷。 此外,在Pod初始化时,您也不必担心在GCloud控制台UI中看到的任何警告。 根据我们的经验,在配置Pod时,集群工作负载UI的警告有点过于激进,可能会给人错误的感觉。 如果首次执行此操作后3到4分钟内Solr和Zookeeper并没有全部运行,则可以开始故障排除。
查看pods状态,:
kubectl get pods
当它们准备就绪时,您将看到类似以下的输出:
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:
avatar
创建一个集合:
curl -v "http://localhost:28983/solr/admin/collections?action=CREATE&name=perf3x1&numShards=3&replicationFactor=1&maxShardsPerNode=1&collection.configName=_default"
增加一些测试数据:
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 ),您将看到以下输出:
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定义的变量,然后进行一次发布即可。
# 修改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
添加副本:
avatar
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 主要字段解释:
- replicas 副本数
- selector 那个pod是由自己管理的
- serviceName 必须关联到一个无头服务商
- template 定义pod模板(其中定义关联那个存储卷)
- volumeClaimTemplates 生成PVC
Statefulset优点
- 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
- 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
- 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
- 有序、平滑的收缩、删除 既Pod是有顺序的,在收缩或者删除的时候要依据定义的顺序依次进行(既从N-1到0,既倒序)。
- 有序的滚动更新,或金丝雀发布。
Persistent Volumes
为了证明StatefulSet中的副本返回了相同的hostname和附加的存储,我们需要杀死Pod。 在开始杀死集群中的Pod之前,让我们介绍一下Solr StatefulSets的重要方面,即PersistentVolumes。 如果查看Solr helm chart,您会注意到StatefulSet具有以下volumeMount:
volumeMounts:
- name: solr-pvc
mountPath: /opt/solr/server/home
让我们登录solr-0,看看它是什么:
kubectl exec -it solr-0 --container solr -- /bin/bash
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):
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获取有关持久卷附加的存储的详细信息,如下所示:
或者通过命令:
kubectl describe PersistentVolumeClaim solr-pvc-solr-0
得到的结果:
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:
- name: "SOLR_HOME"
value: "/opt/solr/server/home"
Solr 7.x要求SOLR_HOME目录包含solr.xml文件。当k8s挂载solr-pvc
卷时,它最初是一个空目录。 因此,我们利用另一个有用的Kubernetes工具initContainer将solr.xml引导到我们的持久卷目录中。
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进行定义:
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:
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上。
> 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看会发生什么:
kubectl delete po solr-2 --force --grace-period 0
查看现在solr pod状态:
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:
# 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睡眠几秒钟,然后一分钟重试。
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的状态:
kubectl describe pod <pod name>
Upgrading Solr
还记得我们说过Kubernetes帮助实施最佳实践和经过验证的设计模式吗? 在不停机的情况下执行滚动升级是StatefulSet中内置的最佳实践之一。 要查看实际效果,只需重新运行helm template命令,而无需使用–set image.tag参数:
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的才会进行更新。类似于金丝雀部署方式。
# 修改分区更新必须大于等于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集群定义的服务:
> 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:
# 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数量。
有序扩展,有序收缩。
# 将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复制的情况下在更大的集合上运行更长的性能和负载测试。