為了提升k3s的使用體驗,我們將推出由k3s開發(fā)人員撰寫的“k3s黑魔法”系列文章來詳細介紹k3s功能、原理等方面的內容。本篇文章是該系列的第二篇,文章詳細介紹k3s的HA部署實踐并探討其原理。
同時,歡迎大家添加k3s助手(微信號:k3s2019),加入官方微信群和大家一起交流。
前 言
上一篇我們探討了k3s單進程如何實現(xiàn)了對k8s各個服務的管理,我們通過單點部署的方式給大家展現(xiàn)了k3s運行時的效果。而在面向生產環(huán)境的實踐中,高可用HA是我們無法避免的問題,k3s本身也歷經(jīng)多個版本的迭代,HA方案也進行了不斷優(yōu)化,形成了目前的比較穩(wěn)定的HA方案。k3s的HA方案中,主要關注以下幾點:
k3s 中的datastore如何選型?
k3s worker節(jié)點如何能夠均衡訪問master服務?
除了datastore本身外,是否還需要依賴其他第三方組件?
帶著這些疑問,我們先進行HA部署實踐,然后進行原理上的探討。
HA部署實踐
目前,k3s支持SQLite/etcd/MySQL/PostgreSQL/DQLite等datastore,不同的datastore面向不同的使用場景。大部分用戶使用默認參數(shù)安裝時,都會使用到SQlite,而對于etcd,大家都已經(jīng)再熟悉不過。所以我們這里選擇大家熟知的MySQL來做HA的實踐,PostgreSQL與MySQL類似,我們就不再重復。
對于MySQL本身的HA,由于我們并不是在DBA范疇討論,所以我們直接使用公有云的RDS創(chuàng)建MySQL服務,并通過k3s來使用它。使用AWS創(chuàng)建MySQL服務非常簡單,很容易我們就能得到一個服務實例,如下:
創(chuàng)建兩個節(jié)點,以k3s v1.0版本為例,分別執(zhí)行以下命令:
$ curl -sfL https://get.k3s.io | sh -s - server --datastore-endpoint='mysql://admin:Rancher2019k3s@tcp(k3s-mysql.csrskwupj33i.ca-central-1.rds.amazonaws.com:3306)/k3sdb'
# 注意database name不要加特殊字符
在任意一個節(jié)點中,查看node情況:
$ kubectl get no
NAME STATUS ROLES AGE VERSION
ip-172-31-4-119 Ready master 7s v1.16.3-k3s.2
ip-172-31-7-200 Ready master 2m32s v1.16.3-k3s.2
在我們添加一個worker節(jié)點之前,我們需要給多個k3s server提供一個統(tǒng)一的訪問入口,這可以使用以下方式實現(xiàn):
L4層負載均衡器
Round-robin DNS
VIP或者彈性IP
本文采用比較簡單的DNS方案,我們針對k3s server的IP,添加了對應的A記錄,并得到了一個域名,使用這個域名我們就可以添加worker:
$ curl -sfL https://get.k3s.io | K3S_URL=https://k3s-t2.niusmallnan.club:6443 K3S_TOKEN=${K3S_TOKEN} sh -
$ kubectl get no
NAME STATUS ROLES AGE VERSION
ip-172-31-7-200 Ready master 15m v1.16.3-k3s.2
ip-172-31-4-119 Ready master 12m v1.16.3-k3s.2
ip-172-31-7-217 Ready <none> 25s v1.16.3-k3s.2
這樣兩臺k3s server同樣具備worker屬性,且我們又新增了一個純粹的woker。而這臺worker節(jié)點是如何連接k3s server的呢?之前的文章,我們探討過worker節(jié)點依然會通過thread方式運行kubelet/kubeproxy,而這些組件也需要連接api-server獲取資源狀態(tài),比如我們可以看kubelet的kubeconfig文件:
$ cat /var/lib/rancher/k3s/agent/kubelet.kubeconfig
apiVersion: v1
clusters:
- cluster:
server: https://127.0.0.1:42545
certificate-authority: /var/lib/rancher/k3s/agent/server-ca.crt
name: local
contexts:
- context:
cluster: local
namespace: default
user: user
name: Default
current-context: Default
kind: Config
preferences: {}
users:
- name: user
user:
client-certificate: /var/lib/rancher/k3s/agent/client-kubelet.crt
client-key: /var/lib/rancher/k3s/agent/client-kubelet.key
我們可以看到kubelet訪問的是本地的服務端口,所以worker節(jié)點中一定存在一個proxy來反向代理api-server,查看下面的配置,我們就能找到答案:
cat /var/lib/rancher/k3s/agent/etc/k3s-agent-load-balancer.json
{
"ServerURL": "https://k3s-t2.niusmallnan.club:6443",
"ServerAddresses": [
"172.31.4.119:6443",
"172.31.7.200:6443"
]
}
進入數(shù)據(jù)庫中,我們可以看到k3s存儲的表結構,相當于把etcd那種kv模式改為關系型數(shù)據(jù)庫模式,比如:
$ select * from kine limit 3\G;
…
…
…
*************************** 3. row ***************************
id: 3
name: /registry/apiregistration.k8s.io/apiservices/v1.apiextensions.k8s.io
created: 1
deleted: 0
create_revision: 0
prev_revision: 0
lease: 0
value: {"kind":"APIService","apiVersion":"apiregistration.k8s.io/v1beta1","metadata":{"name":"v1.apiextensions.k8s.io","uid":"c9109c3a-3475-42cb-a1e3-73ddb6161c55","creationTimestamp":"2019-11-19T09:18:06Z","labels":{"kube-aggregator.kubernetes.io/automanaged":"onstart"}},"spec":{"service":null,"group":"apiextensions.k8s.io","version":"v1","groupPriorityMinimum":16700,"versionPriority":15},"status":{"conditions":[{"type":"Available","status":"True","lastTransitionTime":"2019-11-19T09:18:06Z","reason":"Local","message":"Local APIServices are always available"}]}}
old_value: NULL
3 rows in set (0.00 sec)
DQLite(https://github.com/canonical/dqlite)的方案更佳輕量級,可以理解為高可用版本的SQLite。k3s已經(jīng)內置了DQLite,所以啟用它是很方便的,由于DQLite的數(shù)據(jù)備份基于raft協(xié)議,所以基數(shù)個節(jié)點是比較穩(wěn)妥的方式,部署過程參考官方文檔即可:
https://rancher.com/docs/k3s/latest/en/installation/ha-embedded/
那么,我們可以總結一下,各個datastore類型的使用場景,參考如下:
總的來說,k3s HA的基本模式,如下圖所示:
HA實現(xiàn)原理
我們都知道k8s的默認datastore是etcd,而k3s是如何將針對etcd的相關操作,轉化到其他類型的datastore上的呢?這里Rancher開發(fā)了一個新的組件叫kine(https://github.com/rancher/kine),由它來完成這項工作。k8s(k3s)的api-server默認使用etcd v3的存儲接口,etcd v3中被k8s使用的接口大致如下(https://github.com/etcd-io/etcd/blob/master/etcdserver/etcdserverpb/rpc.proto):
在kine中,這些接口都依次被實現(xiàn)(Lease相關還不支持),可以參考這里:
https://github.com/rancher/kine/tree/master/pkg/server 。
kine將這些接口暴露給k3s的api-server。當我們給k3s設置相應的datastore-endpoint時,k3s的api-server還是配置連接etcd v3,但是etcd-servers的地址則配置為kine的服務地址,相當于所有的api讀寫操作都通過kine來處理,kine來把讀寫操作轉給MySQL/PostgreSQL/SQLite等。如果我們設置真正的etcd backend,kine會略過并直接暴露真實的etcd-servers給k3s api-server。
worker節(jié)點中的api-server代理通過一個tcpproxy(http://github.com/google/tcpproxy)來實現(xiàn),參考:
https://github.com/rancher/k3s/blob/master/pkg/agent/loadbalancer/loadbalancer.go 。
tcpproxy需要獲取真實的api-server的服務地址,這里主要靠worker與master建立的tunnel,也就是websocket數(shù)據(jù)通道獲取,這個tunnel是k3s獨有的。當api-server地址有更新,worker依靠websocket獲取新的地址,同時更新worker中的tcpproxy的配置。
后 記
高可用是軟件投產的基本標準,k3s目前已經(jīng)GA,并提供了HA的方案。無論是在邊緣場景中,還是開發(fā)測試中,都可以根據(jù)自身的需求選擇合適的datastore以及對應的HA方式。DQLite的方案目前還是實驗性質的,相信隨著整個邊緣計算生態(tài)的發(fā)展,這部分也會很快穩(wěn)定可用。