繼續來完成昨天未解決的 Kubernetes 部署 Elasticsearch Cluster 的相關問題,例如在部署多個 Elasticsearch Master 角色時會出現腦裂問題以及如何整合永久性儲存、角色與Production mode要設定的東西。今天會一次性的將這些東西講解完並且實際部署到 Kubernetes 環境上,我自己認為 EFK 最困難的點是在於 Elasticsearch Cluster 的部署,過了這個坑後就沒什麼太大的痛點。
要先按照Elasticsearch Production mode的建議將 Elasticsearch Pod Linux Kernel 中的 max_map_count 參數調整到262144,此一部分我採用的是 sidecar 模式,透過 sidecar 幫我處理 max_map_count 參數。
...
initContainers:
- image: alpine:3.6
command: ["/sbin/sysctl", "-w", "vm.max_map_count=262144"]
name: elasticsearch-logging-init
securityContext:
privileged: true
...
設定 Elasticsearch 角色,由於 Elasticsearch 系統節點可以分為兩個角色( Master Node 與 Worker Node ),這邊我會分成兩個 Deployment 來部署,其中一個 Deployment 專門用來部署 Elasticsearch Master Node ,另外一個 Deployment 用來部署 Elasticsearch Data Node。
env:
- name: "cluster.name"
value: "elasticsearch-cluster"
- name: "node.master"
value: "true"
- name: "node.data"
value: "false"
- name: "node.ingest"
value: "false"
[color=#31ce36]主要是修改環境中的node.master要改成false,因為這個環境變數代表該節點的是什麼角色。[name=jason ]
env:
- name: "cluster.name"
value: "elasticsearch-cluster"
- name: "node.master"
value: "false"
- name: "node.data"
value: "true"
- name: "node.ingest"
value: "false"
這邊稍微簡單的解釋一下為什麼會有腦裂的問題(由於不是本文的重點,就輕鬆的帶過xD),一般來說正常的 Elasticsearch 叢集系統都是由統一一台 Master Node 進行管理的,一但 Master Node 死掉之後會進行選舉再推舉出一台新的 Master Node ,但是有一種情況同時有兩個 Master Node ,這時候有一部分的 Data Node 由一號 Master Node 管理,另外一部分由二號 Master Node管理,那麼問題來了....資料到底要傳到哪裡去?誰才是真正的 Master Node?
OK,這些 Elasticsearch 其實都有幫我們想好,只要修改配置參數就可以大幅度的預防腦裂的問題。
discovery.zen.minimum_master_nodes
這個參數用來決定 Elasticsearch 叢集在選舉過程中需要候選節點除來選舉(有點類似選總統時候至少要多少候選人:"P),關分野給出了一個原則這個參數基本上是設定成 N/2 +1
( N 是叢集節點的數量,例如五個節點的 Elasticsearch 叢集中, discovery.zen.minimum_master_nodes 原則上應該要設定成 5/2 + 1 = 3 (除法的部分四捨五入)
。
...
- name: "discovery.zen.minimum_master_nodes"
value: "3"
- name: "discovery.zen.ping_timeout"
value: "5s"
...
因為 Kubernetes 系統架構中 Pod 元件是具有臨時且不可回復的特性一旦因外在因素或是不預期情況服務中斷,儲存於 Pod 上的資料也會跟著 Pod 一起被刪除。若重新啟動新的 Pod 以取代r舊的 Pod ,新啟動的 Pod 仍然無法得知舊的 Pod 儲存了什麼資料,所以也無法回覆舊 Pod 的已儲存的資訊。
為了避免 Elasticsearch Pod 故障(搬遷、實際節點損壞、污染...現實生活中有很多可怕的情況)導致資料遺失,我在這邊採用 NFS 將 Elasticsearch 的資料儲存於 NFV 上, 因為我們要可以隨時擴展 Elasticsearch 節點,所以要生成很多的 (Persistent Volumes,PV) 與 (Persistent Volumes Claim,PVC),這個需求我們可以依賴Storage Classes的幫忙可以減少很多麻煩,Yaml 設定檔如下所示。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: log
provisioner: fuseim.pri/ifs
結合上述要做的事情後,在這個地方做一個總整理。
首先我先處理 Elasticsearch Master Node
的Deployment檔
,設定如下方yaml所示。
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
app: elasticsearch
role: master
name: elasticsearch-master
namespace: log
spec:
replicas: 3
selector:
matchLabels:
app: elasticsearch
role: master
template:
metadata:
labels:
app: elasticsearch
role: master
spec:
containers:
- name: elasticsearch-master
image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3
ports:
- containerPort: 9200
protocol: TCP
- containerPort: 9300
protocol: TCP
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
env:
- name: "cluster.name"
value: "elasticsearch-cluster"
- name: "discovery.zen.ping.unicast.hosts"
value: "elasticsearch-discovery"
- name: "discovery.zen.minimum_master_nodes"
value: "3"
- name: "discovery.zen.ping_timeout"
value: "5s"
- name: "node.master"
value: "true"
- name: "node.data"
value: "false"
- name: "node.ingest"
value: "false"
- name: "ES_JAVA_OPTS"
value: "-Xms256m -Xmx256m"
initContainers:
- name: increase-vm-max-map
image: busybox
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
volumes:
- emptyDir: {}
name: "data"
---
kind: Service
apiVersion: v1
metadata:
labels:
app: elasticsearch
name: elasticsearch-discovery
namespace: log
spec:
ports:
- port: 9300
targetPort: 9300
selector:
app: elasticsearch
role: master
部署完成後我們可以透過 kubectl 指令確認部署狀況,如此一來就得到三個 Master 的 elasticsearch 叢集了。
$kubectl get pod -n log
NAME READY STATUS RESTARTS AGE
elasticsearch-master-65c699ddfd-fsrc5 1/1 Running 0 75m
elasticsearch-master-65c699ddfd-lhfff 1/1 Running 0 75m
elasticsearch-master-65c699ddfd-qx8j6 1/1 Running 0 75m
[color=#31ce36]從 yaml 檔我們可以觀察到,使用 init container 作為 sidecar 處理 max_map_count 。設置環境變數代表此 Deployment 所建立出來的 Pod 都是 Master ,另外也設定好預防腦裂的參數。由於 Master Node 有問題砍掉時不會影響資料,所以不用特別使用 StorageClass 去處理 Persistent Volume 的問題。
[name=jason ]
接著要來處理的是Elasticsearch Data Node
的 statefulsets 檔
,由於 statefulsets 處理 volume 的時候會需要 StoageClass,所以我們這邊要先把 StroageClass 的相關元件部署起來,這邊使用的是 NFS StoageClass,部署方式跟流程可以參考超哥的 Kubernetes handbook 其中的利用 NFS 动态提供 Kubernetes 后端存储卷。
完成 NFS StoageClass 相關元件部署後,接著建立 StroageClass , Yaml檔如下所示。我們可以透過kubectl指令看到 nfs-client-provisioner 與 nfs StorageClass 正在運行,
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs
parameters:
archiveOnDelete: "false"
provisioner: fuseim.pri/ifs
reclaimPolicy: Delete
volumeBindingMode: Immediate
$kubectl get pod,sc
NAME PROVISIONER AGE
storageclass.storage.k8s.io/elasticsearch fuseim.pri/ifs 23h
NAME READY STATUS RESTARTS AGE
pod/nfs-client-provisioner-77577d595f-ft7qv 1/1 Running 0 94m
看到這邊表示我們已經完成了Volume的設定,可以繼續設定Elasticsearch Data Node
的 statefulsets 檔
,Elasticsearch Data Node 主要跟 Master Node 不一樣的地方是環境變數,需要將 node.master
改為 false
,代表該節點不為 Master 角色, yaml 檔設定如下。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: elasticsearch-data
namespace: log
spec:
serviceName: elasticsearch
replicas: 3
selector:
matchLabels:
app: elasticsearch
role: data
template:
metadata:
labels:
app: elasticsearch
role: data
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3
ports:
- containerPort: 9200
protocol: TCP
- containerPort: 9300
protocol: TCP
volumeMounts:
- name: data
mountPath: /usr/share/elasticsearch/data
env:
- name: "cluster.name"
value: "elasticsearch-cluster"
- name: "discovery.zen.ping.unicast.hosts"
value: "elasticsearch-discovery"
- name: "node.master"
value: "false"
- name: "node.data"
value: "true"
- name: "ES_JAVA_OPTS"
value: "-Xms256m -Xmx256m"
initContainers:
- name: increase-vm-max-map
image: busybox
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
volumeClaimTemplates:
- metadata:
name: data
namespace: log
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: nfs
resources:
requests:
storage: 10Gi
部署完 Elasticsearch Data Node 後一樣我們透過 kubectl 去檢查部署情況,現在我們得到了一個完整的 Elasticsearch 叢集其中包含三台 Master Node 以及三台 Data Node。
$kubectl get pod -n log
NAME READY STATUS RESTARTS AGE
elasticsearch-data-0 1/1 Running 0 30m
elasticsearch-data-1 1/1 Running 0 30m
elasticsearch-data-2 1/1 Running 0 29m
elasticsearch-master-65c699ddfd-fsrc5 1/1 Running 0 78m
elasticsearch-master-65c699ddfd-lhfff 1/1 Running 0 78m
elasticsearch-master-65c699ddfd-qx8j6 1/1 Running 0 78m
完成了以上非常麻煩的過程部署與設定之後,就要來驗證剛剛部署的 Elasticsearch 叢集是否能正常運作,這邊坐兩個非常簡單的測試。
我們可以透過 kubectl 指令獲取 Elasticsearch Pod 的 IP 資料,如 elasticsearch-master-65c699ddfd-fsrc5 這過 Pod 的 IP 是 10.233.92.33 ,知道他 IP 之後可以透過 Elasticsearch 提供的叢集環境檢查 API 檢查目前叢集的狀態。
$kubectl get pod elasticsearch-master-65c699ddfd-fsrc5 -o wide -n log
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
elasticsearch-master-65c699ddfd-fsrc5 1/1 Running 0 89m 10.233.92.33 node3 <none> <none>
$curl 10.233.92.33:9200/_cluster/state\?pretty
{
"cluster_name" : "elasticsearch-cluster",
"compressed_size_in_bytes" : 517,
"cluster_uuid" : "DijI_fcMRoGnjTkbeduF6g",
"version" : 6,
"state_uuid" : "aDM23u9XRy-MfYW5ef_Wtg",
"master_node" : "9WHQl6_BRC-dO3rfhOEMrg",
"blocks" : { },
"nodes" : {
"9WHQl6_BRC-dO3rfhOEMrg" : {
"name" : "9WHQl6_",
"ephemeral_id" : "V7JKhexdTtSiJtiDZXLCuA",
"transport_address" : "10.233.92.33:9300",
"attributes" : { }
},
"9WHQl6_BRC-dO3rfhOEMrg" : {
"name" : "9WHQl6_",
"ephemeral_id" : "V7JKhexdTtSiJtiDZXLCuA",
"transport_address" : "10.233.92.33:9300",
"attributes" : { }
},
...
從上面這一坨資料可以看到現在 Master Node是誰,也就是 9WHQl6_BRC-dO3rfhOEMrg 這個傢伙...說實話我這邊沒設定好,沒有辦法一眼清楚的看出來Master Node 是哪個 Pod ,不過我們用那個醜醜的代號到下面 nodes 那一坨裡面找出他的 IP ,找到他的 IP 就可以知道他是哪個 Pod 囉。
接著我們可以試著刪除看看 Master Node ,按照上一小點所觀察到的目前 Elasticsearch 叢集 Master Node 是 10.233.92.33 這個傢伙,從往上找可以發現這一組 IP 是對應到 elasticsearch-master-65c699ddfd-fsrc5
這個Pod。
接著透過 kubectl 指令刪除這個 Pod 觀察 Elasticsearch 叢集環境的變化。
$kubectl get pod -n log
pod/elasticsearch-master-65c699ddfd-2bmr7 1/1 Running 0 20s
pod/elasticsearch-master-65c699ddfd-lhfff 1/1 Running 0 101m
pod/elasticsearch-master-65c699ddfd-qx8j6 1/1 Running 0 101m
...
$curl 10.233.96.35:9200/_cluster/state\?pretty
{
"cluster_name" : "elasticsearch-cluster",
"compressed_size_in_bytes" : 519,
"cluster_uuid" : "DijI_fcMRoGnjTkbeduF6g",
"version" : 9,
"state_uuid" : "cG3xYSvNRAKGtosXULdzlg",
"master_node" : "Kw10bQaHR7WGbySRhDetfQ",
"blocks" : { },
"nodes" : {
"ZogCGxDORXaTOMMKXHFHeg" : {
"name" : "ZogCGxD",
"ephemeral_id" : "aTuSX0YWSCGaPqpLgrGZjA",
"transport_address" : "10.233.92.37:9300",
"attributes" : { }
},
"MJw5gT-VSCmyGrsEjDpd3g" : {
"name" : "MJw5gT-",
"ephemeral_id" : "UN_ltE_pQ2qdUy9jRm_9Mw",
"transport_address" : "10.233.96.38:9300",
"attributes" : { }
},
"Kw10bQaHR7WGbySRhDetfQ" : {
"name" : "Kw10bQa",
"ephemeral_id" : "drmY5KhNS7q8_eNFy0YI_Q",
"transport_address" : "10.233.90.38:9300",
"attributes" : { }
...
可以觀察到刪除前 Elasticsearch 叢集環境的 Master Node 是 9WHQl6_BRC-dO3rfhOEMrg
這個傢伙,由於我們看他不順眼惡意地把他幹掉,目前透過選舉選出來新的 Master 是 Kw10bQaHR7WGbySRhDetfQ
這個傢伙,細心一點的朋友應該可以發現...Kw10bQaHR7WGbySRhDetfQ5k
這個傢伙也是 Elasticsearch Master Deployment 部署出來的 Pod。也就是說 Master Node 的選舉是由 Elasticsearch Master Deployment 部署出來的 Pod 去選出來的。
呼終於把最麻煩的 Elasticsearch Cluster 部署完了,前面有點偷懶都在介紹架構跟部屬時會發生什麼問題 xD 。(不過這邊部署的東西其實是不能上 production 的,因為還有很多細節沒有處理,要上 production 這邊可以參考 David 大大寫的鐵人文章ELK Stack 1 - Install a Self-host ELK stack on GCP)
明天會很快地將Fluentd 和 Kibana 部署到我們環境上,讓大家看一下UI比較舒服(X)。
感謝大大分享
但是大大是不是少給一個elasticsearch-data的 Service
大概會長這樣
kind: Service
apiVersion: v1
metadata:
labels:
app: elasticsearch
name: elasticsearch-data
namespace: log
spec:
ports:
- port: 9200
targetPort: 9200
selector:
app: elasticsearch
role: data