天天看點

k8s 入門教程和實戰

在本文中,我們從技術細節上對kubernetes進行簡單運用介紹,利用一些yaml腳本層面上執行個體告訴大家kubernetes基本概念。kubernetes以及它呈現出的程式設計範式值得你去使用和整合到自己的技術棧中。

大家如有興趣,可以關注由達觀資料出品的更多經典好文datagrand達觀資料

kubernetes最初認為是谷歌開源的容器叢集管理系統,是google多年大規模容器管理技術borg或omega的開源版本。準确來說的話,kubernetes更是一個全新的平台,一個全新的

平台管理工具,它是專門為job和service設計。完全開放,2014年6月開始接受公開的commit,任何人都可以提供意見。由于kubernetes簡化了開發、運維和管理負荷,越來越多

的企業開始在生産環境使用,是以kubernetes得到了迅速的發展。

基于容器的應用部署、維護和滾動更新

負載均衡和服務發現

跨機器和跨地區的叢集排程

自動伸縮

無狀态服務和有狀态服務

廣泛的volume支援

插件機制保證擴充性

kubernetes主要由以下幾個核心元件組成:

etcd儲存了整個叢集的狀态;

apiserver提供了資源操作的唯一入口,并提供認證、授權、通路控制、api注冊和發現等機制;

controller manager負責維護叢集的狀态,比如故障檢測、自動擴充、滾動更新等;

scheduler負責資源的排程,按照預定的排程政策将pod排程到相應的機器上;

kubelet負責維護容器的生命周期,同時也負責volume(cvi)和網絡(cni)的管理;

container runtime負責鏡像管理以及pod和容器的真正運作(cri);

kube-proxy負責為service提供cluster内部的服務發現和負載均衡

如果隻是為了了解kubernetes,可以使用minikube的方式進行單機安裝,minikube 實際就是本地建立了一個虛拟機,裡面運作了kubernetes 的一些必要的環境,相當于 k8s 的服務環境,建立 pod,service,deployment等都是在裡面進行建立和管理。

在本文中,我使用kubeadm方式安裝kubernetes 1.10.0,具體kubernetes部署步驟:

使用kubeadm方式安裝kubernetes 1.10.0

kubernetes叢集添加/删除node

kubernetes dashboard1.8.3部署

k8s原生的叢集監控方案(heapster+influxdb+grafana)

請注意:上述環境隻是測試環境,生産環境部署大同小異。

container(容器)是一種便攜式、輕量級的作業系統級虛拟化技術。它使用 namespace 隔離不同的軟體運作環境,并通過鏡像自包含軟體的運作環境,

進而使得容器可以很友善的在任何地方運作。由于容器體積小且啟動快,是以可以在每個容器鏡像中打包一個應用程式。這種一對一的應用鏡像關系擁有很多好處。使用容器,

不需要與外部的基礎架構環境綁定, 因為每一個應用程式都不需要外部依賴,更不需要與外部的基礎架構環境依賴。完美解決了從開發到生産環境的一緻性問題。

容器同樣比虛拟機更加透明,這有助于監測和管理。尤其是容器程序的生命周期由基礎設施管理,而不是由容器内的程序對外隐藏時更是如此。最後,

每個應用程式用容器封裝,管理容器部署就等同于管理應用程式部署。

在 kubernetes 必須要使用 pod 來管理容器,每個 pod 可以包含一個或多個容器。

pod是kubernetes中你可以建立和部署的最小也是最簡的機關。一個pod代表着叢集中運作的一個程序;

在kubrenetes叢集中pod的使用方式;

pod中如何管理多個容器

上面已經說了“pod是kubernetes中你可以建立和部署的最小也是最簡的機關。一個pod代表着叢集中運作的一個程序。”pod中封裝着應用的容器(有的情況下是好幾個容器),

存儲、獨立的網絡ip,管理容器如何運作的政策選項。pod代表着部署的一個機關:kubernetes中應用的一個執行個體,可能由一個或者多個容器組合在一起共享資源。

請注意:docker是kubernetes中最常用的容器運作時,但是pod也支援其他容器運作時。

(1)一個pod中運作一個容器

“每個pod中一個容器”的模式是最常見的用法;在這種使用方式中,你可以把pod想象成是單個容器的封裝,kuberentes管理的是pod而不是直接管理容器。

(2)在一個pod中同時運作多個容器

說明:在一個pod中同時運作多個容器是一種比較進階的用法。隻有當你的容器需要緊密配合協作的時候才考慮用這種模式。

一個pod中也可以同時封裝幾個需要緊密耦合互相協作的容器,它們之間共享資源。這些在同一個pod中的容器可以互相協作成為一個service機關——一個容器共享檔案,

另一個“sidecar”容器來更新這些檔案。pod将這些容器的存儲資源作為一個實體來管理。

以上是關于pod的簡單介紹,如需了解更多,請參考pod

node 是 pod 真正運作的主機,可以實體機,也可以是虛拟機。為了管理 pod,每個 node 節點上至少要運作 container runtime(比如 docker 或者 rkt)、kubelet 和 kube-proxy 服務。

namespace 是對一組資源和對象的抽象集合,比如可以用來将系統内部的對象劃分為不同的項目組或使用者組。常見的 pods, services, replication controllers 和 deployments 等都是屬于

某一個 namespace 的(預設是 default),而 node, persistentvolumes 等則不屬于任何 namespace。

我們既然有pod了,為什麼還要使用deployment呢?這是因為實際工作中,我們很少會直接在kubernetes中建立單個pod。因為pod的生命周期是短暫的,用後即焚的實體。

deployment 為 pod 和 replicaset 提供了一個聲明式定義(declarative)方法,用來替代以前的replicationcontroller 來友善的管理應用。

你隻需要在 deployment 中描述想要的目标狀态是什麼,deployment controller 就會幫你将 pod 和replicaset 的實際狀态改變到你的目标狀态。你可以定義一個全新的 deployment 來建立

replicaset 或者删除已有的 deployment 并建立一個新的來替換。

rc是k8s叢集中最早的保證pod高可用的api對象。通過監控運作中的pod來保證叢集中運作指定數目的pod副本。指定的數目可以是多個也可以是1個;少于指定數目,rc就會啟動運作新的pod副本;

多于指定數目,rc就會殺死多餘的pod副本。即使在指定數目為1的情況下,通過rc運作pod也比直接運作pod更明智,因為rc也可以發揮它高可用的能力,保證永遠有1個pod在運作。rc是k8s較早期

的技術概念,隻适用于長期伺服型的業務類型,比如控制小機器人提供高可用的web服務。

rs是新一代rc,提供同樣的高可用能力,差別主要在于rs後來居上,能支援更多種類的比對模式。副本集對象一般不單獨使用,而是作為deployment的理想狀态參數使用。

定義deployment來建立pod和replicaset

滾動更新和復原應用;如果目前狀态不穩定,復原到之前的deployment revision。每次復原都會更新deployment的revision

擴容和縮容,擴容deployment以滿足更高的負載

暫停和繼續deployment,暫停deployment來應用podtemplatespec的多個修複,然後恢複上線

比如,我們這裡定義一個簡單的nginx應用:

關于deployment的應用還有很多,如:擴容、縮容、滾動更新、復原應用等,這裡由于篇幅的問題不再一一介紹,詳見deployment的應用

label 是識别 kubernetes 對象的标簽,以 key/value 的方式附加到對象上(key 最長不能超過 63 位元組,value 可以為空,也可以是不超過 253 位元組的字元串)。

label 不提供唯一性,并且實際上經常是很多對象(如 pods)都使用相同的 label 來标志具體的應用。

label 定義好後其他對象可以使用 label selector 來選擇一組相同 label 的對象(比如 replicaset 和 service 用 label 來選擇一組 pod)。label selector 支援以下幾種方式:

等式,如 app=nginx 和 env!=production

集合,如 env in (production, qa)

多個 label(它們之間是 and 關系),如 app=nginx,env=test

service account是為了友善pod裡面的程序調用kubernetes api或其他外部服務。

運作在pod裡的程序需要調用kubernetes api以及非kubernetes api的其它服務。service account它并不是給kubernetes叢集的使用者使用的,而是給pod裡面的程序使用的,它為pod提供必要的身份認證。

user account是為人設計的,而service account則是為了pod中的程序

user account是跨namespace的,而service account則是僅局限它所在的namespace

service account為服務提供了一種友善的認知機制,但它不關心授權的問題。可以配合rbac來為service account鑒權:

配置--authorization-mode=rbac和--runtime-config=rbac.authorization.k

8s.io/v1alpha1

配置--authorization-rbac-super-user=admin

定義role、clusterrole、rolebinding或clusterrolebinding

我們在kubernetes dashboard1.8.3部署中,碰到首次登入出現通路權限報錯的問題,原因就是serviceaccount的建立問題。

service account介紹請參考博文

secret解決了密碼、token、密鑰等敏感資料的配置問題,而不需要把這些敏感資料暴露到鏡像或者pod spec中。secret可以以volume或者環境變量的方式使用。

opaque(default):任意字元串,base64編碼格式的secret,用來存儲密碼、密鑰等

kubernetes.io/service-account-token:作用于serviceaccount,就是kubernetes的service account中所說的。 即用來通路kubernetes api,由kubernetes自動建立,并且會自動挂載到pod的/run/secrets/kubernetes.io/serviceaccount目錄中

kubernetes.io/dockercfg: 作用于docker registry,使用者下載下傳docker鏡像認證使用。用來存儲私有docker registry的認證資訊

以volume方式

以環境變量方式

說明:這樣就可以通過檔案的方式挂載到容器内,在/etc/secrets目錄下回

生成這個檔案。

想要了解更多,請詳見kubernetes的secret

configmaps允許你将配置檔案、指令行參數或環境變量中讀取的配置資訊與docker image分離,以保持集裝箱化應用程式的便攜性。即configmap api給我們提供了向容器中注入配置資訊的機制。

configmap api資源用來儲存key-value pair配置資料,這個資料可以在pods裡使用,或者被用來為像controller一樣的系統元件存儲配置資料。雖然configmap跟secrets類似,但是configmap更友善的處理不含敏感資訊的字元串。

注意:configmaps不是屬性配置檔案的替代品。configmaps隻是作為多個properties檔案的引用。你可以把它了解為linux系統中的/etc目錄,專門用來存儲配置檔案的目錄。

data一欄包括了配置資料,configmap可以被用來儲存單個屬性,也可以用來儲存一個配置檔案。 配置資料可以通過很多種方式在pods裡被使用。configmaps可以被用來:

設定環境變量的值

在容器裡設定指令行參數

在資料卷裡面建立config檔案

更多configmap實戰,詳見configmap - kubernetes

容器磁盤上檔案的生命周期是短暫的,這就使得在容器中運作重要應用時出現一些問題。比如,當容器崩潰時,kubelet會重新開機它,但是容器中的檔案将丢失--容器以幹淨的狀态

(鏡像最初的狀态)重新啟動。其次,在 pod 中同時運作多個容器時,這些容器之間通常需要共享檔案。kubernetes 中的 volume 抽象就很好的解決了這些問題。

docker 中也有一個 volume 的概念,盡管它稍微寬松一些,管理也很少。在 docker 中,卷就像是磁盤或是另一個容器中的一個目錄。它的生命周期不受管理,直到最近才有了 local-disk-backed 卷。docker 現在提供了卷驅動程式,但是功能還非常有限(例如docker1.7隻允許每個容器使用一個卷驅動,并且無法給卷傳遞參數)。

kubernetes 中的卷有明确的壽命——與封裝它的 pod 相同。是以,卷的生命比 pod 中的所有容器都長,當這個容器重新開機時資料仍然得以儲存。當然,當 pod 不再存在時,卷也将不複存在。也許更重要的是,kubernetes 支援多種類型的卷,pod 可以同時使用任意數量的卷。

要使用卷,需要為 pod 指定為卷(spec.volumes 字段)以及将它挂載到容器的位置(spec.containers.volumemounts 字段)。

kubernetes 支援以下類型的卷:

k8s的存儲系統從基礎到進階大緻分為三個層次:普通volume,persistent volume 和動态存儲供應。

最簡單的普通volume是單節點volume。它和docker的存儲卷類似,使用的是pod所在k8s節點的本地目錄。

它和普通volume的差別是什麼呢?

普通volume和使用它的pod之間是一種靜态綁定關系,在定義pod的檔案裡,同時定義了它使用的volume。volume 是pod的附屬品,我們無法單獨建立一個volume,因為它不是一個獨立的k8s資源對象。

而persistent volume 簡稱pv是一個k8s資源對象,是以我們可以單獨建立一個pv。它不和pod直接發生關系,而是通過persistent volume claim,簡稱pvc來實作動态綁定。pod定義裡指定的是pvc,然後pvc會根據pod的要求去自動綁定合适的pv給pod使用。

pv的通路模式有三種:

readwriteonce:是最基本的方式,可讀可寫,但隻支援被單個pod挂載

readonlymany:可以以隻讀的方式被多個pod挂載

readwritemany:這種存儲可以以讀寫的方式被多個pod共享。比較常用的是nfs

一般來說,pv和pvc的生命周期分為5個階段:

provisioning,即pv的建立,可以直接建立pv(靜态方式),也可以使用storageclass動态建立

binding,将pv配置設定給pvc

using,pod通過pvc使用該volume

releasing,pod釋放volume并删除pvc

reclaiming,回收pv,可以保留pv以便下次使用,也可以直接從雲存儲中删除

根據這5個階段,volume的狀态有以下4種:

available:可用

bound:已經配置設定給pvc

released:pvc解綁但還未執行回收政策

failed:發生錯誤

變成released的pv會根據定義的回收政策做相應的回收工作。有三種回收政策:

retain 就是保留現場,k8s什麼也不做,等待使用者手動去處理pv裡的資料,處理完後,再手動删除pv

delete k8s會自動删除該pv及裡面的資料

recycle k8s會将pv裡的資料删除,然後把pv的狀态變成available,又可以被新的pvc綁定使用

在實際使用場景裡,pv的建立和使用通常不是同一個人。這裡有一個典型的應用場景:管理者建立一個pv池,開發人員建立pod和pvc,pvc裡定義了pod所需存儲的大小和通路模式,然後pvc會到pv池裡自動比對最合适的pv給pod使用。

前面在介紹pv的生命周期時,提到pv的供給有兩種方式,靜态和動态。其中動态方式是通過storageclass來完成的,這是一種新的存儲供應方式。

使用storageclass有什麼好處呢?除了由存儲系統動态建立,節省了管理者的時間,還有一個好處是可以封裝不同類型的存儲供pvc選用。在storageclass出現以前,pvc綁定一個pv隻能根據兩個條件,一個是存儲的大小,另一個是通路模式。在storageclass出現後,等于增加了一個綁定次元。

比如這裡就有兩個storageclass,它們都是用谷歌的存儲系統,但是一個使用的是普通磁盤,我們把這個storageclass命名為slow。另一個使用的是ssd,我們把它命名為fast。

在pvc裡除了正常的大小、通路模式的要求外,還通過annotation指定了storage class的名字為fast,這樣這個pvc就會綁定一個ssd,而不會綁定一個普通的磁盤。

限于篇幅問題,我這裡簡單說一下emptydir、nfs、pv和pvc。

emptydir類型的volume建立于pod被排程到某個主控端上的時候,而同一個pod内的容器都能讀寫emptydir中的同一個檔案。一旦這個pod離開了這個主控端,emptydir中的資料就會被永久删除。是以目前emptydir類型的volume主要用作臨時空間,比如web伺服器寫日志或者tmp檔案需要的臨時目錄。

簡單解釋下上面的内容:

在這個例子中,我們定義了一個名為html的卷。它的類型是emptydir,這意味着當一個pod被配置設定到一個節點時,卷先被建立,并隻要pod在節點上運作時,這個卷仍存在。正如名字所說,它最初是空的。第一容器運作nginx的

伺服器并将共享卷挂載到目錄/ usr /share/ nginx /html。第二容器使用centos的鏡像,并将共享卷挂載到目錄/html。每一秒,第二容器添加目前日期和時間到index.html檔案中,它位于共享卷。當使用者發出一個http請求到pod,

nginx的伺服器讀取該檔案并将其傳遞給響應請求的使用者。

更多volume實戰,詳見kubernetes部分volume類型介紹及yaml示例--emptydir

nfs 卷允許将現有的 nfs(網絡檔案系統)共享挂載到你的容器中。不像 emptydir,當删除 pod 時,nfs 卷的内容被保留,卷僅僅是被解除安裝。這意味着 nfs 卷可以預填充資料,并且可以在 pod 之間“切換”資料。 nfs 可以被多個寫入者同時挂載。

請先部署好自己的nfs服務

在使用共享之前,必須運作自己的nfs伺服器并運作共享

更多volume實戰,詳見kubernetes部分volume類型介紹及yaml示例--nfs(網絡資料卷)

nfs 作為 k8s 的網絡存儲驅動,可以滿足持久存儲業務的需求,支援多節點讀寫。下面是兩個pod同時使用一個持久性volume執行個體。

更多pv和pvc實戰,詳見實戰kubernetes持久性卷使用

kubernetes pod 是有生命周期的,它們可以被建立,也可以被銷毀,然而一旦被銷毀生命就永遠結束。 通過 replicasets 能夠動态地建立和銷毀 pod(例如,需要進行擴縮容,或者執行 滾動更新)。 每個 pod 都會擷取它自己的 ip 位址,即使這些 ip 位址不總是穩定可依賴的。 這會導緻一個問題:在 kubernetes 叢集中,如果一組 pod(稱為 backend)為其它 pod (稱為 frontend)提供服務,那麼那些 frontend 該如何發現,并連接配接到這組 pod 中的哪些 backend 呢?

kubernetes service 定義了這樣一種抽象:一個 pod 的邏輯分組,一種可以通路它們的政策 —— 通常稱為微服務。 這一組 pod 能夠被 service 通路到,通常是通過 label selector(檢視下面了解,為什麼可能需要沒有 selector 的 service)實作的。

舉個例子,考慮一個圖檔處理 backend,它運作了3個副本。這些副本是可互換的 —— frontend 不需要關心它們調用了哪個 backend 副本。 然而組成這一組 backend 程式的 pod 實際上可能會發生變化,frontend 用戶端不應該也沒必要知道,而且也不需要跟蹤這一組 backend 的狀态。 service 定義的抽象能夠解耦這種關聯。

對 kubernetes 叢集中的應用,kubernetes 提供了簡單的 endpoints api,隻要 service 中的一組 pod 發生變更,應用程式就會被更新。 對非 kubernetes 叢集中的應用,kubernetes 提供了基于 vip 的網橋的方式通路 service,再由 service 重定向到 backend pod。

一個 service 在 kubernetes 中是一個 rest 對象,和 pod 類似。 像所有的 rest 對象一樣, service 定義可以基于 post 方式,請求 apiserver 建立新的執行個體。 例如,假定有一組 pod,它們對外暴露了 9376 端口,同時還被打上 "app=myapp" 标簽。

上述配置将建立一個名稱為 “my-service” 的 service 對象,它會将請求代理到使用 tcp 端口 9376,并且具有标簽 "app=myapp" 的 pod 上。 這個 service 将被指派一個 ip 位址(通常稱為 “cluster ip”),它會被服務的代理使用(見下面)。 該 service 的 selector 将會持續評估,處理結果将被 post 到一個名稱為 “my-service” 的 endpoints 對象上。

需要注意的是, service 能夠将一個接收端口映射到任意的 targetport。 預設情況下,targetport 将被設定為與 port 字段相同的值。 可能更有趣的是,targetport 可以是一個字元串,引用了 backend pod 的一個端口的名稱。 但是,實際指派給該端口名稱的端口号,在每個 backend pod 中可能并不相同。 對于部署和設計 service ,這種方式會提供更大的靈活性。 例如,可以在 backend 軟體下一個版本中,修改 pod 暴露的端口,并不會中斷用戶端的調用。

kubernetes service 能夠支援 tcp 和 udp 協定,預設 tcp 協定。

很多 service 需要暴露多個端口。對于這種情況,kubernetes 支援在 service 對象中定義多個端口。 當使用多個端口時,必須給出所有的端口的名稱,這樣 endpoint 就不會産生歧義,例如:

service 抽象了該如何通路 kubernetes pod,但也能夠抽象其它類型的 backend,例如:

希望在生産環境中使用外部的資料庫叢集,但測試環境使用自己的資料庫。

希望服務指向另一個 namespace 中或其它叢集中的服務。

正在将工作負載轉移到 kubernetes 叢集,和運作在 kubernetes 叢集之外的 backend。

根據以上的應用場景,我們都能夠定義沒有selector的service,如下:

由于這個 service 沒有 selector,就不會建立相關的 endpoints 對象。可以手動将 service 映射到指定的 endpoints:

注意:endpoint ip 位址不能是 loopback(127.0.0.0/8)、 link-local(169.254.0.0/16)、或者 link-local 多點傳播(224.0.0.0/24)。

通路沒有 selector 的 service,與有 selector 的 service 的原理相同。請求将被路由到使用者定義的 endpoint(該示例中為 10.0.0.3:9376)。

對一些應用(如 frontend)的某些部分,可能希望通過外部(kubernetes 叢集外部)ip 位址暴露 service。

kubernetes servicetypes 允許指定一個需要的類型的 service,預設是 clusterip 類型。

type 的取值以及行為如下:

clusterip 通過叢集的内部 ip 暴露服務,選擇該值,服務隻能夠在叢集内部可以通路,這也是預設的 servicetype。

nodeport 通過每個 node 上的 ip 和靜态端口(nodeport)暴露服務。nodeport 服務會路由到 clusterip 服務,這個 clusterip 服務會自動建立。通過請求 <nodeip>:<nodeport>,可以從叢集的外部通路一個 nodeport 服務。

loadbalancer 使用雲提供商的負載均衡器,可以向外部暴露服務。外部的負載均衡器可以路由到 nodeport 服務和 clusterip 服務。

externalname 通過傳回 cname 和它的值,可以将服務映射到 externalname 字段的内容(例如, foo.bar.example.com)。 沒有任何類型代理被建立,這隻有 kubernetes 1.7 或更高版本的 kube-dns 才支援。

如果設定 type 的值為 "nodeport",kubernetes master 将從給定的配置範圍内(預設:30000-32767)配置設定端口,每個 node 将從該端口(每個 node 上的同一端口)代理到 service。該端口将通過 service 的 spec.ports[*].nodeport 字段被指定。

如果需要指定的端口号,可以配置 nodeport 的值,系統将配置設定這個端口,否則調用 api 将會失敗(比如,需要關心端口沖突的可能性)。

kubernetes概念--service

kubernetes 官網教程

kubernetes中的persistent volume解析

繼續閱讀