最簡單的 kubernetes 高可用安裝方式

sealos 項目地址:github.com/fanux/sealo…node

本文教你如何用一條命令構建 k8s 高可用集羣且不依賴 haproxy 和 keepalived,也無需 ansible。經過內核 ipvs 對 apiserver 進行負載均衡,而且帶 apiserver 健康檢測。架構以下圖所示:python

本項目名叫 sealos,旨在作一個簡單幹淨輕量級穩定的 kubernetes 安裝工具,能很好的支持高可用安裝。 其實把一個東西作的功能強大並不難,可是作到極簡且靈活可擴展就比較難。 因此在實現時就必需要遵循這些原則。下面介紹一下 sealos 的設計原則:nginx

1. 設計原則

sealos 特性與優點:git

  • 支持離線安裝,工具與資源包(二進制程序 配置文件 鏡像 yaml文件等)分離,這樣不一樣版本替換不一樣離線包便可
  • 證書延期
  • 使用簡單
  • 支持自定義配置
  • 內核負載,極其穩定,由於簡單因此排查問題也極其簡單

爲何不用 ansible?

1.0 版本確實是用 ansible 實現,可是用戶仍是須要先裝 ansile,裝 ansible 又須要裝 python 和一些依賴等,爲了避免讓用戶那麼麻煩把 ansible 放到了容器裏供用戶使用。若是不想配置免密鑰使用用戶名密碼時又須要 ssh-pass 等,總之不能讓我滿意,不是我想的極簡。github

因此我想就來一個二進制文件工具,沒有任何依賴,文件分發與遠程命令都經過調用 sdk 實現因此不依賴其它任何東西,總算讓我這個有潔癖的人滿意了。golang

爲何不用 keepalived haproxy?

haproxy 用 static pod 跑沒有太大問題,還算好管理,keepalived 如今大部分開源 ansible 腳本都用 yum 或者 apt 等裝,這樣很是的不可控,有以下劣勢:docker

  • 源不一致可能致使版本不一致,版本不一直連配置文件都不同,我曾經檢測腳本不生效一直找不到緣由,後來才知道是版本緣由。
  • 系統緣由安裝不上,依賴庫問題某些環境就直接裝不上了。
  • 看了網上不少安裝腳本,不少檢測腳本與權重調節方式都不對,直接去檢測 haproxy 進程在不在,實際上是應該去檢測 apiserver 是否是 healthz 的,若是 apiserver 掛了,即便 haproxy 進程存在,集羣也會不正常了,就是僞高可用了。
  • 管理不方便,經過 prometheus 對集羣進行監控,是能直接監控到 static pod 的可是用 systemd 跑又須要單獨設置監控,且重啓啥的還須要單獨拉起。不如 kubelet 統一管理來的乾淨簡潔。
  • 咱們還出現過 keepalived 把 CPU 佔滿的狀況。

因此爲了解決這個問題,我把 keepalived 跑在了容器中(社區提供的鏡像基本是不可用的) 改造中間也是發生過不少問題,最終好在解決了。shell

總而言之,累覺不愛,因此在想能不能甩開 haproxy 和 keepalived 作出更簡單更可靠的方案出來,還真找到了。。。api

本地負載爲何不使用 envoy 或者 nginx?

咱們經過本地負載解決高可用問題。bash

本地負載:在每一個 node 節點上都啓動一個負載均衡,上游就是三個 master。

若是使用 envoy 之類的負載均衡器,則須要在每一個節點上都跑一個進程,消耗的資源更多,這是我不但願的。ipvs 實際也多跑了一個進程 lvscare,可是 lvscare 只是負責管理 ipvs 規則,和 kube-proxy 相似,真正的流量仍是從很穩定的內核走的,不須要再把包丟到用戶態中去處理。

在架構實現上有個問題會讓使用 envoy 等變得很是尷尬,就是 join 時若是負載均衡沒有創建那是會卡住的,kubelet 就不會起來,因此爲此你須要先啓動 envoy,意味着你又不能用 static pod 去管理它,同上面 keepalived 宿主機部署同樣的問題,用 static pod 就會相互依賴,邏輯死鎖,雞說要先有蛋,蛋說要先有雞,最後誰都沒有。

使用 ipvs 就不同,我能夠在 join 以前先把 ipvs 規則創建好,再去 join 就能夠了,而後對規則進行守護便可。一旦 apiserver 不可訪問了,會自動清理掉全部 node 上對應的 ipvs 規則, 等到 master 恢復正常時添加回來。

爲何要定製 kubeadm?

首先是因爲 kubeadm 把證書過時時間寫死了,因此須要定製把它改爲 99 年,雖然大部分人能夠本身去籤個新證書,可是咱們仍是不想再依賴個別的工具,就直接改源碼了。

其次就是作本地負載時修改 kubeadm 代碼是最方便的,由於在 join 時咱們須要作兩個事,第一是 join 以前先建立好 ipvs 規則,第二是建立 static pod。若是這塊不去定製 kubeadm 就把報靜態 pod 目錄已存在的錯誤,忽略這個錯誤很不優雅。 並且 kubeadm 中已經提供了一些很好用的 sdk 供咱們去實現這個功能。

且這樣作以後最核心的功能都集成到 kubeadm 中了,sealos 就單單變成分發和執行上層命令的輕量級工具了,增長節點時咱們也就能夠直接用 kubeadm 了。

2. 使用教程

安裝依賴

安裝

多 master HA 只需執行如下命令:

$ sealos init --master 192.168.0.2 \
  --master 192.168.0.3 \
  --master 192.168.0.4 \
  --node 192.168.0.5 \
  --user root \
  --passwd your-server-password \
  --version v1.14.1 \
  --pkg-url /root/kube1.14.1.tar.gz 
複製代碼

而後,就沒有而後了。。。沒錯,你的高可用集羣已經裝好了,是否是以爲一臉懵逼?就是這麼簡單快捷!

單 master 多 node:

$ sealos init --master 192.168.0.2 \
  --node 192.168.0.5 \ 
  --user root \
  --passwd your-server-password \
  --version v1.14.1 \
  --pkg-url /root/kube1.14.1.tar.gz 
複製代碼

使用免密鑰或者密鑰對:

$ sealos init --master 172.16.198.83 \
  --node 172.16.198.84 \
  --pkg-url https://sealyun.oss-cn-beijing.aliyuncs.com/free/kube1.15.0.tar.gz \
  --pk /root/kubernetes.pem # this is your ssh private key file \
  --version v1.15.0
複製代碼

參數解釋:

--master   master服務器地址列表
--node     node服務器地址列表
--user     服務器ssh用戶名
--passwd   服務器ssh用戶密碼
--pkg-url  離線包位置,能夠放在本地目錄,也能夠放在一個 http 服務器上,sealos 會 wget 到安裝目標機
--version  kubernetes 版本
--pk       ssh 私鑰地址,配置免密鑰默認就是 /root/.ssh/id_rsa
複製代碼

其餘參數:

--kubeadm-config string   kubeadm-config.yaml kubeadm 配置文件,可自定義 kubeadm 配置文件
--vip string              virtual ip (default "10.103.97.2") 本地負載時虛擬 ip,不推薦修改,集羣外不可訪問
複製代碼

檢查安裝是否正常:

$ kubectl get node
NAME                      STATUS   ROLES    AGE     VERSION
izj6cdqfqw4o4o9tc0q44rz   Ready    master   2m25s   v1.14.1
izj6cdqfqw4o4o9tc0q44sz   Ready    master   119s    v1.14.1
izj6cdqfqw4o4o9tc0q44tz   Ready    master   63s     v1.14.1
izj6cdqfqw4o4o9tc0q44uz   Ready    <none>   38s     v1.14.1

$ kubectl get pod --all-namespaces
NAMESPACE     NAME                                              READY   STATUS    RESTARTS   AGE
kube-system   calico-kube-controllers-5cbcccc885-9n2p8          1/1     Running   0          3m1s
kube-system   calico-node-656zn                                 1/1     Running   0          93s
kube-system   calico-node-bv5hn                                 1/1     Running   0          2m54s
kube-system   calico-node-f2vmd                                 1/1     Running   0          3m1s
kube-system   calico-node-tbd5l                                 1/1     Running   0          118s
kube-system   coredns-fb8b8dccf-8bnkv                           1/1     Running   0          3m1s
kube-system   coredns-fb8b8dccf-spq7r                           1/1     Running   0          3m1s
kube-system   etcd-izj6cdqfqw4o4o9tc0q44rz                      1/1     Running   0          2m25s
kube-system   etcd-izj6cdqfqw4o4o9tc0q44sz                      1/1     Running   0          2m53s
kube-system   etcd-izj6cdqfqw4o4o9tc0q44tz                      1/1     Running   0          118s
kube-system   kube-apiserver-izj6cdqfqw4o4o9tc0q44rz            1/1     Running   0          2m15s
kube-system   kube-apiserver-izj6cdqfqw4o4o9tc0q44sz            1/1     Running   0          2m54s
kube-system   kube-apiserver-izj6cdqfqw4o4o9tc0q44tz            1/1     Running   1          47s
kube-system   kube-controller-manager-izj6cdqfqw4o4o9tc0q44rz   1/1     Running   1          2m43s
kube-system   kube-controller-manager-izj6cdqfqw4o4o9tc0q44sz   1/1     Running   0          2m54s
kube-system   kube-controller-manager-izj6cdqfqw4o4o9tc0q44tz   1/1     Running   0          63s
kube-system   kube-proxy-b9b9z                                  1/1     Running   0          2m54s
kube-system   kube-proxy-nf66n                                  1/1     Running   0          3m1s
kube-system   kube-proxy-q2bqp                                  1/1     Running   0          118s
kube-system   kube-proxy-s5g2k                                  1/1     Running   0          93s
kube-system   kube-scheduler-izj6cdqfqw4o4o9tc0q44rz            1/1     Running   1          2m43s
kube-system   kube-scheduler-izj6cdqfqw4o4o9tc0q44sz            1/1     Running   0          2m54s
kube-system   kube-scheduler-izj6cdqfqw4o4o9tc0q44tz            1/1     Running   0          61s
kube-system   kube-sealyun-lvscare-izj6cdqfqw4o4o9tc0q44uz      1/1     Running   0          86s
複製代碼

增長節點

先獲取 join command,在 master 上執行:

$ kubeadm token create --print-join-command
複製代碼

可使用超級 kubeadm,可是 join 時須要增長一個 --master 參數:

$ cd kube/shell && init.sh
$ echo "10.103.97.2 apiserver.cluster.local" >> /etc/hosts   # using vip
$ kubeadm join 10.103.97.2:6443 --token 9vr73a.a8uxyaju799qwdjv \
  --master 10.103.97.100:6443 \
  --master 10.103.97.101:6443 \
  --master 10.103.97.102:6443 \
  --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866
複製代碼

也能夠用 sealos join 命令:

$ sealos join --master 192.168.0.2 \
  --master 192.168.0.3 \
  --master 192.168.0.4 \
  --vip 10.103.97.2 \
  --node 192.168.0.5 \
  --user root \
  --passwd your-server-password \
  --pkg-url /root/kube1.15.0.tar.gz
複製代碼

使用自定義 kubeadm 配置文件

有時你可能須要自定義 kubeadm 的配置文件,好比要在證書里加入域名 sealyun.com

首先須要獲取配置文件模板:

$ sealos config -t kubeadm >>  kubeadm-config.yaml.tmpl
複製代碼

而後修改 kubeadm-config.yaml.tmpl 便可,將 sealyun.com 添加到配置中:

apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: {{.Version}}
controlPlaneEndpoint: "apiserver.cluster.local:6443"
networking:
 podSubnet: 100.64.0.0/10
apiServer:
 certSANs:
 - sealyun.com # 這是新增的域名
 - 127.0.0.1
 - apiserver.cluster.local
        {{range .Masters -}}
 - {{.}}
        {{end -}}
 - {{.VIP}}
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
 excludeCIDRs: 
 - "{{.VIP}}/32"
複製代碼

注意:其它部分不用修改,sealos 會自動填充模板裏面的內容。

最後在部署時使用 --kubeadm-config 指定配置文件模板便可:

$ sealos init --kubeadm-config kubeadm-config.yaml.tmpl \
  --master 192.168.0.2 \
  --master 192.168.0.3 \
  --master 192.168.0.4 \
  --node 192.168.0.5 \
  --user root \
  --passwd your-server-password \
  --version v1.14.1 \
  --pkg-url /root/kube1.14.1.tar.gz 
複製代碼

版本升級

本教程以 1.14 版本升級到 1.15 爲例,其它版本原理相似,懂了這個其它的參考官方教程便可。

升級過程

  1. 升級 kubeadm,全部節點導入鏡像
  2. 升級控制節點
  3. 升級 master(控制節點)上的 kubelet
  4. 升級其它 master(控制節點)
  5. 升級 node
  6. 驗證集羣狀態

升級 kubeadm

把離線包拷貝到全部節點執行 cd kube/shell && sh init.sh。這裏會把 kubeadm、kubectl、kubelet 的二進制文件都更新掉,並且會導入高版本鏡像。

升級控制節點

$ kubeadm upgrade plan
$ kubeadm upgrade apply v1.15.0
複製代碼

重啓 kubelet:

$ systemctl restart kubelet
複製代碼

其實 kubelet 升級很簡單粗暴,咱們只須要把新版本的 kubelet 拷貝到 /usr/bin 下面,重啓 kubelet service 便可,若是程序正在使用不讓覆蓋那麼就停一下 kubelet 再進行拷貝,kubelet bin 文件在 conf/bin 目錄下。

升級其它控制節點

$ kubeadm upgrade apply
複製代碼

升級 node

驅逐節點(要不要驅逐看狀況, 喜歡粗暴的直接來也沒啥):

$ kubectl drain $NODE --ignore-daemonsets
複製代碼

更新 kubelet 配置:

$ kubeadm upgrade node config --kubelet-version v1.15.0
複製代碼

而後升級 kubelet。一樣是替換二進制再重啓 kubelet service。

$ systemctl restart kubelet
複製代碼

召回失去的愛情:

$ kubectl uncordon $NODE
複製代碼

驗證

$ kubectl get nodes
複製代碼

若是版本信息都對的話基本就升級成功了。

kubeadm upgrade apply 幹了啥?

  1. 檢查集羣是否可升級
  2. 執行版本升級策略 哪些版本之間能夠升級
  3. 確認鏡像是否存在
  4. 執行控制組件升級,若是失敗就回滾,其實就是 apiserver、controller manager、scheduler 等這些容器
  5. 升級 kube-dns 和 kube-proxy
  6. 建立新的證書文件,備份老的若是其超過 180 天

源碼編譯

由於使用了 netlink 庫,因此推薦在容器內進行編譯,只需一條命令:

$ docker run --rm -v $GOPATH/src/github.com/fanux/sealos:/go/src/github.com/fanux/sealos -w /go/src/github.com/fanux/sealos -it golang:1.12.7  go build
複製代碼

若是你使用的是 go mod,則須要指定經過 vendor 編譯:

$ go build -mod vendor
複製代碼

卸載

$ sealos clean \
  --master 192.168.0.2 \
  --master 192.168.0.3 \
  --master 192.168.0.4 \
  --node 192.168.0.5 \
  --user root \
  --passwd your-server-password
複製代碼

3. sealos 實現原理

執行流程

  • 經過 sftp或者 wget 把離線安裝包拷貝到目標機器上(masters 和 nodes)。
  • 在 master0 上執行 kubeadm init
  • 在其它 master 上執行 kubeadm join 並設置控制面,這個過程會在其它 master 上起動 etcd 並與 master0 的 etcd 組成集羣,並啓動控制平面的組件(apiserver、controller 等)。
  • join node 節點,會在 node 上配置 ipvs 規則,配置 /etc/hosts 等。

全部對 apiserver 的請求都是經過域名進行訪問,由於 node 須要經過虛擬 ip 鏈接多個 master,每一個節點的 kubelet 與 kube-proxy 訪問 apiserver 的虛擬地址是不同的,而 kubeadm 又只能在配置文件中指定一個地址,因此使用一個域名可是每一個節點解析的 IP 不一樣。當 IP 地址發生變化時僅須要修改解析地址便可。

本地內核負載

經過這樣的方式實現每一個 node 上經過本地內核負載均衡訪問 masters:

+----------+                       +---------------+  virturl server: 127.0.0.1:6443
  | mater0   |<----------------------| ipvs nodes    |    real servers:
  +----------+                      |+---------------+            10.103.97.200:6443
                                    |                             10.103.97.201:6443
  +----------+                      |                             10.103.97.202:6443
  | mater1   |<---------------------+
  +----------+                      |
                                    |
  +----------+                      |
  | mater2   |<---------------------+
  +----------+
複製代碼

在 node 上起了一個 lvscare 的 static pod 去守護這個 ipvs,一旦 apiserver 不可訪問了,會自動清理掉全部 node 上對應的 ipvs 規則, master 恢復正常時添加回來。

因此在你的 node 上加了三個東西,能夠直觀的看到:

$ cat /etc/kubernetes/manifests   # 這下面增長了 lvscare 的 static pod
$ ipvsadm -Ln                     # 能夠看到建立的ipvs規則
$ cat /etc/hosts                  # 增長了虛擬IP的地址解析
複製代碼

定製 kubeadm

sealos 對 kubeadm 改動很是少,主要是延長了證書過時時間和擴展了 join 命令。下面主要講講對 join 命令的改造。

首先 join 命令增長 --master 參數用於指定 master 地址列表:

lagSet.StringSliceVar(
	&locallb.LVScare.Masters, "master", []string{},
	"A list of ha masters, --master 192.168.0.2:6443 --master 192.168.0.2:6443 --master 192.168.0.2:6443",
)
複製代碼

這樣就能夠拿到 master 地址列表去作 ipvs 負載均衡了。

若是不是控制節點且不是單 master,那麼就只建立一條 ipvs 規則,控制節點上不須要建立,連本身的 apiserver 便可:

if data.cfg.ControlPlane == nil {
			fmt.Println("This is not a control plan")
			if len(locallb.LVScare.Masters) != 0 {
				locallb.CreateLocalLB(args[0])
			}
		} 
複製代碼

而後再去建立 lvscare static pod 來守護 ipvs:

if len(locallb.LVScare.Masters) != 0 {
				locallb.LVScareStaticPodToDisk("/etc/kubernetes/manifests")
			}
複製代碼

**因此哪怕你不使用 sealos,也能夠直接用定製過的 kubeadm 去部署集羣,只是麻煩一些。**下面給出安裝步驟。

kubeadm 配置文件:

apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: v1.14.0
controlPlaneEndpoint: "apiserver.cluster.local:6443" # apiserver DNS name
apiServer:
 certSANs:
 - 127.0.0.1
 - apiserver.cluster.local
 - 172.20.241.205
 - 172.20.241.206
 - 172.20.241.207
 - 172.20.241.208
 - 10.103.97.1          # virturl ip
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
 excludeCIDRs: 
 - "10.103.97.1/32" # 注意:若是不加這個,kube-proxy 就會清理你的規則
複製代碼

master0(假設 vip 地址爲 10.103.97.100)上執行如下命令:

$ echo "10.103.97.100 apiserver.cluster.local" >> /etc/hosts # 解析的是 master0 的地址
$ kubeadm init --config=kubeadm-config.yaml --experimental-upload-certs  
$ mkdir ~/.kube && cp /etc/kubernetes/admin.conf ~/.kube/config
$ kubectl apply -f https://docs.projectcalico.org/v3.6/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
複製代碼

master1(假設 vip 地址爲 10.103.97.101)上執行如下命令:

$ echo "10.103.97.100 apiserver.cluster.local" >> /etc/hosts #解析的是 master0 的地址,爲了能正常 join 進去
$ kubeadm join 10.103.97.100:6443 --token 9vr73a.a8uxyaju799qwdjv \
    --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 \
    --experimental-control-plane \
    --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07 

$ sed "s/10.103.97.100/10.103.97.101/g" -i /etc/hosts  # 解析再換成本身的地址,不然就都依賴 master0 的僞高可用了
複製代碼

master2(假設 vip 地址爲 10.103.97.102)上執行如下命令:

$ echo "10.103.97.100 apiserver.cluster.local" >> /etc/hosts
$ kubeadm join 10.103.97.100:6443 --token 9vr73a.a8uxyaju799qwdjv \
    --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 \
    --experimental-control-plane \
    --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07  

$ sed "s/10.103.97.100/10.103.97.102/g" -i /etc/hosts
複製代碼

在 node 上 join 時加上 --master 參數指定 master 地址列表:

$ echo "10.103.97.1 apiserver.cluster.local" >> /etc/hosts   # 須要解析成虛擬 ip
$ kubeadm join 10.103.97.1:6443 --token 9vr73a.a8uxyaju799qwdjv \
    --master 10.103.97.100:6443 \
    --master 10.103.97.101:6443 \
    --master 10.103.97.102:6443 \
    --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866
複製代碼

離線包結構分析

.
├── bin  # 指定版本的二進制文件,只須要這三個,其它組件跑在容器裏
│   ├── kubeadm
│   ├── kubectl
│   └── kubelet
├── conf
│   ├── 10-kubeadm.conf  # 這個文件新版本沒用到,我在 shell 裏直接生成,這樣能夠檢測 cgroup driver
│   ├── dashboard
│   │   ├── dashboard-admin.yaml
│   │   └── kubernetes-dashboard.yaml
│   ├── heapster
│   │   ├── grafana.yaml
│   │   ├── heapster.yaml
│   │   ├── influxdb.yaml
│   │   └── rbac
│   │       └── heapster-rbac.yaml
│   ├── kubeadm.yaml # kubeadm 的配置文件
│   ├── kubelet.service  # kubelet systemd 配置文件
│   ├── net
│   │   └── calico.yaml
│   └── promethus
├── images  # 全部鏡像包
│   └── images.tar
└── shell
    ├── init.sh  # 初始化腳本
    └── master.sh # 運行master腳本
複製代碼
  • init.sh 腳本會將 bin 目錄下的二進制文件拷貝到 $PATH 下面,並配置好 systemd,關閉 swap 和防火牆等等,而後導入集羣所須要的鏡像。
  • master.sh 主要執行了 kubeadm init。
  • conf 目錄下面包含了 kubeadm 的配置文件,calico yaml 文件等等。
  • sealos 會調用上面的兩個腳本,因此大部分兼容。不一樣版本均可以經過微調腳原本保持兼容。

微信公衆號

掃一掃下面的二維碼關注微信公衆號,在公衆號中回覆◉加羣◉便可加入咱們的雲原生交流羣,羣裏高手如雲,孫宏亮、張館長、陽明大佬、kubesphere 核心貢獻者都在哦😏

相關文章
相關標籤/搜索