天天看點

Kubernetes實戰(二)- 一鍵部署神器kubeadm容器的核心在于“容器化”應用kubeadm的工作原理kubeadm init的工作流程kubeadm join的執行流程總結參考

容器的核心在于“容器化”應用

比如,應用既可能是

  • Java Web和MySQL
  • Cassandra這樣的分布式系統

而要使用容器把後者運作起來,單通過Docker把一個Cassandra鏡像run是沒用的。

Cassandra應用容器化的關鍵,在于處理好這些Cassandra容器之間的編排關系。比如

  • 哪些Cassandra容器是主,哪些從?
  • 主從容器如何區分?
  • 它們之間又如何進行自動發現和通信?
  • Cassandra容器的持久化資料又如何保持

這也是

Kubernetes

項目的主要原因:展現出來的容器化“表達能力”,具有獨有的先進性和完備性。

使得它不僅能運作Java Web&MySQL這樣的正常組合,還能夠處理Cassandra容器叢集等複雜編排問題。

是以,對編排能力的剖析、解讀和最佳實踐,才是容器技術研究重點!

作為一個典型的分布式項目,Kubernetes的部署一直以來都是擋在初學者前面的一隻“攔路虎”。尤其是在Kubernetes項目釋出初期,它的部署完全要依靠一堆由社群維護的腳本。

其實,Kubernetes作為一個Go項目,已經免去很多類似Python項目要安裝語言級别依賴的麻煩。但是,除了将各個元件編譯成二進制檔案外,使用者還要負責為這些二進制檔案編寫對應的配置檔案、配置自啟動腳本,以及為kube-apiserver配置授權檔案等等諸多運維工作。

目前,各大雲廠商最常用的部署的方法,是使用SaltStack、Ansible等運維工具自動化地執行這些步驟。

但即使這樣,這個部署過程依然非常繁瑣。因為,SaltStack這類專業運維工具本身的學習成本,就可能比Kubernetes項目還要高。

難道Kubernetes項目就沒有簡單的部署方法了嗎!!!

這個問題,在Kubernetes社群裡一直沒有得到足夠重視。直到2017年,在志願者的推動下,社群才終于發起了一個獨立的部署工具,名叫:kubeadm

Kubernetes實戰(二)- 一鍵部署神器kubeadm容器的核心在于“容器化”應用kubeadm的工作原理kubeadm init的工作流程kubeadm join的執行流程總結參考

該項目就是要讓使用者能夠通過這樣兩條指令完成一個Kubernetes叢集的部署:

# 建立一個Master節點
$ kubeadm init

# 将一個Node節點加入到目前叢集中
$ kubeadm join <Master節點的IP和端口>
      

太友善啦!

可能你會有所顧忌:Kubernetes的功能那麼多,這樣一鍵部署出來的叢集,能用于生産環境?!

是以本文就是讓你信服,是的!可以!

kubeadm的工作原理

在部署時,Kubernetes的每一個元件都是一個需要被執行的、單獨的二進制檔案。

是以SaltStack這樣的運維工具或者由社群維護的腳本的功能,就是要把這些二進制檔案傳輸到指定的機器當中,然後編寫控制腳本來啟停這些元件。

不過,在了解了容器技術之後,你可能已經萌生出了這樣一個想法,為什麼不用容器部署Kubernetes呢?

這樣,隻要給每個Kubernetes元件做一個容器鏡像,然後在每台主控端上用docker run指令啟動這些元件容器,部署不就完成了嗎?

事實上,在Kubernetes早期的部署腳本裡,确實有一個腳本就是用Docker部署Kubernetes項目的,這個腳本相比于SaltStack等的部署方式,也的确簡單了不少。

但是,這樣做會帶來一個很麻煩的問題,即:如何容器化kubelet。

kubelet是Kubernetes項目用來操作Docker等容器運作時的核心元件。可是,除了跟容器運作時打交道外,kubelet在配置容器網絡、管理容器資料卷時,都需要直接操作主控端。

而如果現在kubelet本身就運作在一個容器裡,那麼直接操作主控端就會變得很麻煩。對于網絡配置來說還好,kubelet容器可以通過不開啟Network Namespace(即Docker的host network模式)的方式,直接共享主控端的網絡棧。可是,要讓kubelet隔着容器的Mount Namespace和檔案系統,操作主控端的檔案系統,就有點兒困難了。

比如,如果使用者想要使用NFS做容器的持久化資料卷,那麼kubelet就需要在容器進行綁定挂載前,在主控端的指定目錄上,先挂載NFS的遠端目錄。

可是,這時候問題來了。由于現在kubelet是運作在容器裡的,這就意味着它要做的這個“mount -F nfs”指令,被隔離在了一個單獨的Mount Namespace中。即,kubelet做的挂載操作,不能被“傳播”到主控端上。

對于這個問題,有人說,可以使用setns()系統調用,在主控端的Mount Namespace中執行這些挂載操作;也有人說,應該讓Docker支援一個–mnt=host的參數。

但是,到目前為止,在容器裡運作kubelet,依然沒有很好的解決辦法,也不推薦你用容器去部署Kubernetes項目。

正因為如此,kubeadm選擇了一種妥協方案:

把kubelet直接運作在主控端上,然後使用容器部署其他的Kubernetes元件。      

是以,你使用kubeadm的第一步,是在機器上手動安裝kubeadm、kubelet和kubectl這三個二進制檔案。當然,kubeadm的作者已經為各個發行版的Linux準備好了安裝包,是以你隻需要執行:

$ apt-get install kubeadm      

接下來,就可以使用“kubeadm init”部署Master節點

kubeadm init的工作流程

當你執行kubeadm init指令後,kubeadm首先要做的,是一系列的檢查工作,以确定這台機器可以用來部署Kubernetes

這一步檢查,稱為“Preflight Checks”, 包括了很多方面,比如:

  • Linux核心的版本必須是否是3.10以上?
  • Linux Cgroups子產品是否可用?
  • 機器的hostname是否标準?在Kubernetes項目裡,機器的名字以及一切存儲在Etcd中的API對象,都必須使用标準的DNS命名(RFC 1123)
  • 使用者安裝的kubeadm和kubelet的版本是否比對?
  • 機器上是不是已經安裝了Kubernetes的二進制檔案?
  • Kubernetes的工作端口10250/10251/10252端口是不是已經被占用?
  • ip、mount等Linux指令是否存在?
  • Docker是否已經安裝?
  • ……

在通過了Preflight Checks之後,kubeadm要為你做的,是生成Kubernetes對外提供服務所需的各種證書和對應的目錄。

Kubernetes對外提供服務時,除非專門開啟“不安全模式”,否則都要通過HTTPS才能通路kube-apiserver。這就需要為Kubernetes叢集配置好證書檔案。

kubeadm為Kubernetes項目生成的證書檔案都放在Master節點的/etc/kubernetes/pki目錄下。

在這個目錄下,最主要的證書檔案是ca.crt和對應的私鑰ca.key。

此外,使用者使用kubectl擷取容器日志等streaming操作時,需要通過kube-apiserver向kubelet發起請求,這個連接配接也必須是安全的。

kubeadm為這一步生成的是apiserver-kubelet-client.crt檔案,對應的私鑰是apiserver-kubelet-client.key。

除此之外,Kubernetes叢集中還有Aggregate APIServer等特性,也需要用到專門的證書,這裡我就不再一一列舉了。需要指出的是,你可以選擇不讓kubeadm為你生成這些證書,而是拷貝現有的證書到如下證書的目錄裡:

/etc/kubernetes/pki/ca.{crt,key}      

這時,kubeadm就會跳過證書生成的步驟,把它完全交給使用者處理。

證書生成後,kubeadm接下來會為其他元件生成通路kube-apiserver所需的配置檔案

這些檔案的路徑是:

/etc/kubernetes/xxx.conf

ls /etc/kubernetes/
admin.conf  controller-manager.conf  kubelet.conf  scheduler.conf      

這些檔案記錄目前Master節點的

  • 伺服器位址
  • 監聽端口
  • 證書目錄等資訊

這樣,對應的用戶端(比如scheduler,kubelet等),可以直接加載相應的檔案,使用裡面的資訊與kube-apiserver建立安全連接配接。

接下來

kubeadm會為Master元件生成Pod配置檔案

上篇介紹過Kubernetes有三個Master元件kube-apiserver、kube-controller-manager、kube-scheduler,都會被使用Pod的方式部署

這時,Kubernetes叢集尚不存在,難道kubeadm會直接執行docker run來啟動這些容器?

當然不是了!

在Kubernetes中,有一種特殊的容器啟動方法叫做“Static Pod”

它允許把要部署的Pod的YAML檔案放在一個指定的目錄裡。

這樣,當這台機器上的kubelet啟動時,它會自動檢查該目錄,加載所有的Pod YAML,然後在該節點啟動它們。

可以看出,kubelet在Kubernetes項目中的地位非常高。

在設計上它就是一個完全獨立的元件,而其他Master元件,則更像是輔助性的系統容器。

在kubeadm中,Master元件的YAML檔案會被生成在/etc/kubernetes/manifests路徑下

比如,kube-apiserver.yaml

apiVersion: v1
kind: Pod
metadata:
  annotations:
    scheduler.alpha.kubernetes.io/critical-pod: ""
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --authorization-mode=Node,RBAC
    - --runtime-config=api/all=true
    - --advertise-address=10.168.0.2
    ...
    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
    image: k8s.gcr.io/kube-apiserver-amd64:v1.11.1
    imagePullPolicy: IfNotPresent
    livenessProbe:
      ...
    name: kube-apiserver
    resources:
      requests:
        cpu: 250m
    volumeMounts:
    - mountPath: /usr/share/ca-certificates
      name: usr-share-ca-certificates
      readOnly: true
    ...
  hostNetwork: true
  priorityClassName: system-cluster-critical
  volumes:
  - hostPath:
      path: /etc/ca-certificates
      type: DirectoryOrCreate
    name: etc-ca-certificates
  ...
      

解析:

  • Pod裡隻定義了一個容器,它使用的鏡像是:k8s.gcr.io/kube-apiserver-amd64:v1.11.1

    該鏡像是Kubernetes官方維護的一個元件鏡像。

  • 這個容器的啟動指令(commands)是

    kube-apiserver --authorization-mode=Node,RBAC …

    即:容器裡kube-apiserver二進制檔案 + 指定的配置參數
  • 如果要修改一個已有叢集的

    kube-apiserver

    的配置,需要修改該YAML
  • kube-apiserver

在這一步完成後,kubeadm還會再生成一個

Etcd

的Pod YAML,用來通過同樣的Static Pod啟動Etcd

是以,最後Master元件的Pod YAML檔案如下所示:

$ ls /etc/kubernetes/manifests/
etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml      

一旦這些YAML出現在被kubelet監視的

/etc/kubernetes/manifests

目錄

kubelet就會自動建立這些YAML中定義的Pod(Master元件的容器)

Master容器啟動後,kubeadm會通過檢查

localhost:6443/healthz

(Master元件的健康檢查URL),等待Master元件完全運作

然後

kubeadm就會為叢集生成一個bootstrap token

隻要持有該token,任何一個安裝了kubelet和kubadm的節點,都可以通過kubeadm join加入到該叢集

該token的值和使用方法會在kubeadm init結束後被列印

在token生成之後

kubeadm會将ca.crt等Master節點的重要資訊,通過ConfigMap的方式儲存在Etcd當中,供後續部署Node節點使用。

這個ConfigMap的名字是cluster-info。

kubeadm init的最後一步,就是

安裝預設插件

Kubernetes預設

  • kube-proxy

    提供整個叢集的服務發現

  • DNS

    提供整個叢集的DNS功能

這兩個插件必裝。這兩個插件也隻是兩個容器鏡像,是以kubeadm隻要用Kubernetes用戶端建立兩個Pod

kubeadm join的執行流程

kubeadm init生成bootstrap token之後,就可以在任一台安裝了kubelet和kubeadm的機器上執行kubeadm join

可為什麼執行kubeadm join需要這樣一個token呢?

任何一台機器想要成為Kubernetes叢集中的一個節點,就必須在叢集的kube-apiserver上注冊

可是,要想跟apiserver打交道,這台機器就必須要擷取到相應的證書檔案(CA檔案)。

可是,為了能夠一鍵安裝,就不能讓使用者去Master節點上手動拷貝這些檔案。

是以,kubeadm至少需要發起一次“不安全模式”的通路到kube-apiserver,進而拿到儲存在ConfigMap中的cluster-info(它儲存了APIServer的授權資訊)。

而bootstrap token,扮演的就是這個過程中的安全驗證的角色。

隻要有了cluster-info裡的kube-apiserver的位址、端口、證書,kubelet就可以以“安全模式”連接配接到apiserver上,這樣一個新的節點就部署完成了。

接下來,你隻要在其他節點上重複這個指令就可以了。

配置kubeadm的部署參數

我在前面講解了kubeadm部署Kubernetes叢集最關鍵的兩個步驟,kubeadm init和kubeadm join。相信你一定會有這樣的疑問:kubeadm确實簡單易用,可是我又該如何定制我的叢集元件參數呢?

比如,我要指定kube-apiserver的啟動參數,該怎麼辦?

在這裡,我強烈推薦你在使用kubeadm init部署Master節點時,使用下面這條指令:

$ kubeadm init --config kubeadm.yaml      

這時,你就可以給kubeadm提供一個YAML檔案(比如,kubeadm.yaml),它的内容如下所示(我僅列舉了主要部分):

apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
api:
  advertiseAddress: 192.168.0.102
  bindPort: 6443
  ...
etcd:
  local:
    dataDir: /var/lib/etcd
    image: ""
imageRepository: k8s.gcr.io
kubeProxy:
  config:
    bindAddress: 0.0.0.0
    ...
kubeletConfiguration:
  baseConfig:
    address: 0.0.0.0
    ...
networking:
  dnsDomain: cluster.local
  podSubnet: ""
  serviceSubnet: 10.96.0.0/12
nodeRegistration:
  criSocket: /var/run/dockershim.sock
  ...      

通過制定這樣一個部署參數配置檔案,你就可以很友善地在這個檔案裡填寫各種自定義的部署參數了。比如,我現在要指定kube-apiserver的參數,那麼我隻要在這個檔案裡加上這樣一段資訊:

...
apiServerExtraArgs:
  advertise-address: 192.168.0.103
  anonymous-auth: false
  enable-admission-plugins: AlwaysPullImages,DefaultStorageClass
  audit-log-path: /home/johndoe/audit.log      

然後,kubeadm就會使用上面這些資訊替換/etc/kubernetes/manifests/kube-apiserver.yaml裡的command字段裡的參數了。

總結

kubeadm的設計非常簡潔。并且,它在實作每一步部署功能時,都在最大程度地重用Kubernetes已有的功能,這也就使得我們在使用kubeadm部署Kubernetes項目時,非常有“原生”的感覺,一點都不會感到突兀。

而kubeadm的源代碼,直接就在kubernetes/cmd/kubeadm目錄下,是Kubernetes項目的一部分。

其中,app/phases檔案夾下的代碼,對應的就是這篇文章中詳細介紹的每一個具體步驟。

kubeadm幾乎完全是一位高中生的作品。他叫Lucas Käldström,芬蘭人,今年隻有19歲。

Kubernetes實戰(二)- 一鍵部署神器kubeadm容器的核心在于“容器化”應用kubeadm的工作原理kubeadm init的工作流程kubeadm join的執行流程總結參考
Kubernetes實戰(二)- 一鍵部署神器kubeadm容器的核心在于“容器化”應用kubeadm的工作原理kubeadm init的工作流程kubeadm join的執行流程總結參考

kubeadm,是他17歲時用業餘時間完成的一個社群項目。

最後,kubeadm能夠用于生産環境嗎?

至今(2019年12月),:不能。

因為kubeadm目前最欠缺的是,一鍵部署一個高可用的Kubernetes叢集

即:Etcd、Master元件都應該是多節點叢集,而不是現在這樣的單點。

這也正是kubeadm接下來發展的主要方向。

另一方面,Lucas也正在積極地把kubeadm phases開放給使用者

即:使用者可以更加自由地定制kubeadm的每一個部署步驟。

這些舉措,都可以讓這個項目更加完善,對它的發展走向也充滿了信心。

如果有部署規模化生産環境的需求,推薦使用kops或者SaltStack這樣更複雜的部署工具

  • 作為Kubernetes項目的原生部署工具,kubeadm對Kubernetes項目特性的使用和內建,确實要比其他項目“技高一籌”,值得參考
  • kubeadm的部署方法,不會涉及到太多運維,也不需要我們額外學習複雜的部署工具。而它部署的Kubernetes叢集,跟一個完全使用二進制檔案搭建起來的叢集幾乎沒有任何差別。

是以,使用kubeadm去部署一個Kubernetes叢集,對于你了解Kubernetes元件的工作方式和架構,最好不過了。

參考

  • 深入剖析Kubernetes