原文地址: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:
創建一個集合:
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
添加副本:
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復制的情況下在更大的集合上運行更長的性能和負載測試。