k8s的核心概念
一. Pod
pod,中文翻譯過來叫豆莢,如下圖。我們都知道豆莢,一個豆莢裡面有很多豆子。豆莢就可以理解為pod,一個個的豆子就可以理解為容器。pod和容器的關係是一個pod裡面可以有一個或者多個容器。Pod是k8s部署的最小單位。
那麼pod中容器和容器之間有什麼關係呢?
當server api將指令下發給kubelet的時候,kubelet會建立第一個容器,不管建立pod的目標是什麼,第一個容器總是不變的,他就是pause。pause是第一個被建立的容器,它的作用有兩個:
-
初始化網路棧
建立對應的veth,網路名稱空間,網橋,這些都是初始化網路棧的時候完成的。
-
掛載網路卷
當我們需要 pod有一定的儲存能力,這時就需要給pod掛載到對應的卷,這個卷是被誰掛載的呢?不是被應用容器掛載的,而是被pause掛載的。
初始化好pause,後續可以在pod中安裝一個或多個容器。這些容器跟pause共享網路棧,共享資料卷。
比如,我們在pod中新增了3個容器,nginx,mysql,java應用程式。還可以更多,但最少要有一個container。這些容器共享pause的網路和資料卷。所以,在pod中的容器,容器名、埠號都不能重複,否則會報錯。
提示:通常容器關聯比較大的應用,放在一個pod中,在同一個pod中可以使用localhost進行訪問
1.Pod的型別
1) 自主式Pod
自主式Pod是不被控制器管理的Pod. 這種Pod死亡以後, 不會被重新啟動. 這個Pod死了以後, 副本數就達不到期望值了, 也不會有人去建立一個新的Pod來達到副本數的期望值.
在傳統情況下, 我們執行一個容器, 每一個容器都是獨立存在的, 每個容器都有自己的ip地址, 每個容器都有自己的掛載卷. 但在k8s移植的時候, 就不太容易了. 我們把一個沒有在容器裡執行的環境轉移到或遷移到k8s的環境裡, 就比較難遷移.比如:LAMP, 那麼A和php之間有聯絡,我們把A和php分開了, 他倆個是不同的地址, 還要去配置反向代理, 比較費勁.
說的是什麼意思呢? 有些元件應該在一起, 並且能互相見面, 也就是通過localhost能訪問到. 但是, 使用標準的容器, 你沒辦法這樣做, 除非你把兩個程式封裝在一個容器內部. K8S給我們建立了一個Pod, Pod是怎樣實現的呢?
首先, 要定義一個Pod, 他會先啟動第一個容器, 需要注意的是, 只要執行了Pod, 這個容器就會被啟動. 這個容器叫pause
然後, 在Pod裡定義了兩個容器, 這兩個容器不是固定的,幾個都可以,至少是一個。 然後這兩個容器會共用PAUSE的網路棧和儲存卷.
也就是說, 這兩個容器沒有自己獨立的ip地址和儲存卷, 或者說, 他們有的是PAUSE或者pod的ip地址. 這兩個容器他們尾根隔離, 但是程式不隔離. 也就是說, 如果容器1執行的是php, 容器2執行的是nginx, nginx想要反向代理訪問php, 只需要要寫localhost:9000即可. 不需要寫IP地址+埠對映. 能夠直接使用localhost的原因是這兩個容器共享的是PAUSE的網路棧.
這樣就說明了, 在同一個Pod裡, 容器之間的埠不能衝突. 一個pod裡不能有兩個容器的埠都是80
下面一個要說明的是: 共享儲存. 這裡兩個容器除了共享網路, 同時也共享儲存卷.
假設PAUSE掛載了一個儲存,容器1會共享PAUSE的儲存卷,容器2也共享PAUSE的儲存卷,也就是說一個pod中的容器共享PAUSE的儲存卷
2) 控制器管理的Pod
控制器管理的Pod有三種:ReplicationController & ReplicaSet & Deployment , 這三種控制器有很多相似的地方
i.ReplicationController
ReplicationController簡稱rc. 它的作用是確保容器應用的副本數始終保持在使用者定義的副本數, 這是使用者的期望。即如果有容器異常退出, 會自動建立新的Pod來代替; 而如果異常多出來的容器也會自動回收. 在新版本的k8s中, 建議使用ReplicaSet來取代ReplicationController.
ii.ReplicaSet:
ReplicaSet簡稱rs. 跟ReplicationController沒有本質上的區別, 除了名字不同, ReplicaSet支援集合式的selector.
這個集合式的選擇器是什麼呢?就是在我們建立Pod的時候, 可以給他打標籤. 比如: app = http, version = v1版本等等. 我們會打一堆的標籤. 當我們想刪除容器的時候, 我們可以這樣說: 當app=http, version = v1的時候, 執行什麼操作. rs支援這種集合方案, 但是rc不支援. 所以在大型專案中, rs比rc會更簡單, 更有效率. 所以, 在新版本中, 官方拋棄rc, 全部轉用rs.
在小的叢集下,有沒有標籤都沒所謂,但當叢集越來越大,pod越來越多的時候,標籤就很有用了。我們可以通過標籤定位某一個pod。
所以,rc適合小叢集使用,rs適合多叢集,pod量很大的時候使用。rs包含了rc的功能,所以官方都建議使用rs。
iii.Deployment:
雖然replicaSet可以獨立使用, 但一般還是建議使用Deployment來自動管理ReplicaSet, 這樣就無需擔心跟其他機制的不相容問題(比如ReplicaSet不支援 rolling-update滾動更新, 但Deployment支援) 。Deployment為何要和RS一起使用呢?是因為Deployment本身不能建立Pod。
滾動更新還是很有意義的, 尤其是在生成環境中
比如:我們現在有兩個容器, 我們要將現在容器的版本從v1版本升級到v2版本. 這時候, 怎麼辦呢? 我們可以進行滾動更新.
首先, 先生成一個新的pod. 然刪除一箇舊的pod, 如下如所示. 先生成一個v2版本的pod, 然後刪除一個v1版本的pod .再把v2版本的pod掛載到叢集上,然後以此類推,所有伺服器以此替換。最後,所有伺服器就都是v2版本的,這就是滾動更新.
那麼, Deployment是如何管理rs並滾動更新的呢?
- 首次部署的時候,要做那些事呢?
第一步:會建立一個Deployment控制器,在Deployment控制器中定義了pod的模板和副本數量。
第二步,Deployment會主動建立一個RS。也就是說rs不是我們自己定義的, 是Deployment自動生成的. RS會建立多個pod
第三步,RS主動幫我們建立Pod,並維持pod副本數的穩定。
Deployment定義出來以後, 他會定義一個rs, RS會建立多個pod. 如下圖
- 當需要更新版本的時候. 怎麼做呢?
官方開發出了v2版本,這時我們要進行滾動更新了。如何滾動更新呢?
第一步:Deployment會更改pod模板為V2。
第二步:Deployment控制器再建立一個新的RS。我們的期望是從左邊的RS遷移到右邊的RS。
`
第三步:RS-2再建立一個新的 Pod, 將其升級到v2版本. 然後下掉一個v1版本的Pod
第四步:在建立一個Pod, 將其版本升級到v2, 在下掉一個v1版本的Pod
第五步:直至全部下完.
這就是Deployment管理的滾動rolling-update滾動升級。
如果升級的過程中, 發現新版本有一些小bug, 我們還可以回滾. 如何回滾, 執行undo即可. 回滾的邏輯和版本升級的原理一樣. 恢復一個v1, 下掉一個v2. 直至全部恢復.
為什麼RS能夠恢復呢?
因為, 下掉的RS沒有被刪掉. 只是停用了. 當回滾的時候, 老舊的RS就會被啟動.
iv. HPA控制器
Horizontal Pod Autoscaling 簡稱HPA控制器,僅適用於Deployment和ReplicaSet,在V1版本中僅支援根據Pod的CPU利用率擴縮容,在vlalpha版本中,可以根據記憶體和使用者自定義的metric擴縮容。
舉個例子:
首先,我們建立出一個Deployment控制器或者RS控制器,這個控制器會建立pod副本,在Deployment或者RS之上可以增加一個HPA控制器。HPA也是一個物件, 他是基於RS建立的。HPA控制器可以定義一個閾值,比如CPU使用率大於80%的時候,進行擴容;CPU使用率小於20%的時候進行縮容。pod副本數最小2個,最大20個。
於是,當cpu使用率超過80%的時候,RS會自動進行擴容,最大擴容到20個副本。
如果建立了20個副本以後,CPU依然大於80%,那麼就愛莫能助了。
縮容也是一樣的,當cpu使用率很小的時候,就縮容,但最小是2個。也即是減到只剩2個pod, 不能再減了.
這樣就達到了一個水平擴充套件的目的. 這也是HPA幫我們實現的.
v. statefulSet
statefulSet主要解決的是有狀態服務的問題. docker主要面對的是無狀態服務。
服務的分類:
1. 無狀態服務: 踢出去過段時間放回來, 依然能正常工作. 比如LVS排程器, APACHE(http服務)
為什麼apache是無狀態服務呢? 因為apache中的資料可以通過共享服務來完成. 對於元件本身他不需要資料, 也沒有資料的更新. 所以, apache被定義到無狀態服務裡面.
docker: 對於docker來說, 他更適合執行的是無狀態服務.
2. 有狀態服務: 踢出叢集后過段時間再放回來, 就不能正常工作了, 這樣的服務就是有狀態服務. 比如: 資料庫DBMS, 因為有很大一部分資料缺失了.
Kubernetes的一個難點就是必須要攻克有狀態服務. 那麼, 有狀態服務, 有些資料需要持久化, 需要儲存起來, 這時,我們就會引入儲存的概念.
主要解決的是有狀態服務的問題. docker主要面對的是無狀態服務, 無狀態服務的含義時, 沒有對應的儲存需要實時的保留. 或者是把他摘出來, 經過一段時間以後, 放回去依然能夠工作. 典型的無狀態服務有哪些呢? 比如: apache服務, LVS服務(負載均衡排程器) . 典型的有狀態服務有哪些呢?mysql, mongodb, 他們需要實時的對資料進行更新和儲存. 把他抽離出叢集,再放回來就沒辦法工作了. statefulSet就是為了解決有狀態服務而誕生的. Deployment 和 ReplicaSet是無狀態服務
statefulset的應用場景包括:
1>穩定的持久化儲存.
即Pod重新排程後還是能訪問到相同的持久化資料. 基於PVC來實現.
Pod重新排程指的是Pod死亡以後, 我們會在排程回來。也就是建立一個新的Pod,建立的這個新的Pod取代原來的Pod的時候, 他的儲存依然是之前的儲存, 並不會變, 並且裡面的資料也不會丟失.
2> 穩定的網路標識:
即Pod重新排程後,其PodName和HostName是不變的,也就是說之前的Pod叫什麼, 現在Pod就叫什麼. 之前的主機名是什麼,現在的主機名還是哪個。 基於Headless Service(即沒有Cluster IP 的Service)來實現。
3> 有序部署.
有序部署分為擴充套件和回收兩個階段.
- 有序擴充套件. 即Pod是有順序的, 再部署或擴充套件的時候, 要依據定義的順序一次進行(即從0到n-1, 在下一個pod執行之前, 所有之前的Pod必須都是Running和Ready的標誌), 基於init Containars實現
只有當前一個Pod處於running和ready的狀態, 第二個才可以被建立. 為什麼需要這樣部署呢? 原因是, 我們構建一個叢集化, 比如叢集裡有nginx, apache, mysql. 我們的啟動順序是先啟mysql, 再啟apache, 再啟nginx, 因為他們之間是有依賴關係的. nginx依賴apache, apache依賴mysql. 這就是有序部署.
4> 有序收縮, 有序刪除
回收也是一樣的是有序的, 不同的是, 他是逆序回收. 從n-1開始, 一直到0.
vi. **DaemonSet: **
確保全部(或一些)Node上執行一個Pod的副本. 當有Pod加入叢集時, 也會為他們增加一個Pod副本, 當有Pod從叢集移除時, Pod副本也會被回收,刪除DeamonSet會刪除對應的所有的Pod.
- 這裡說的是確保全部或者一些,為什麼會是一些呢?這是因為,我們可以在node上打汙點,打上汙點的node是不被排程的。所以,在DaemonSet執行的時候,打了汙點的node是不會被建立的。預設情況下,所有node都會被執行。每個pod只會建立一個副本。
使用DaemonSet的典型用法:
- 執行叢集儲存daemon, 例如在每個Node上執行glusterd, ceph.
- 在每個節點上執行日誌收集daemon, 例如fluentd, logstash.
- 在每個節點上執行監控daemon, 例如Prometheus, Node Exporter.
只要有需求,每個node上都可以執行一個守護程式,去幫我們做一些事情,這個時候就可以使用DaemonSet
vii. Job, CronJob
-
Job是負責批處理的任務. 僅執行一次的任務, 它保證批處理任務的一個或多個Pod成功結束
比如: 我想備份資料庫, 備份程式碼可以放在Pod裡, 我們將其放到Job裡去執行,指令碼是可以正常執行正常工作。直接在linux操作, 到時間就可以把指令碼執行, 執行出來. linux作業系統執行不也是一樣的麼?
一方面封裝成pod, 我們可以重複利用;
另一方面,如果指令碼執行意外退出,是沒辦法重複執行的,job如果判斷當前指令碼不是正常退出,她會重新執行一遍指令碼.直到正常退出為止,並且還可以設定政策退出的次數.
-
CronJob管理基於時間的Pob, 在特定的時間可以執行
即
》在給定的時間點只執行一次
》週期性的再交給時間點執行
3. 服務發現
k8s是如何實現服務間的呼叫的呢?這就是接下來要說的服務發現
客戶端想要訪問一組pod, 如果這些pod是無相干的話,是不能通過Service統一代理的. pod需要具有相關性, 比如由同一個rs//rc/deployment建立的, 或者擁有同一組標籤, 這樣的話可以被service收集到. 即: service去搜集Pod是通過標籤去選擇到的. 這一點很重要.
選擇到以後, service會有自己的ip+port, 客戶端就可以訪問service的ip+埠. 間接訪問pod. 並且這裡有一個RR的演算法存在.
假設我們現在有一個簡單的叢集環境:
有一個myqsl, 三臺apache-fpm, 三臺快取伺服器SQUID, 有一個負載均衡器LVS. 我們來分析一下, 如果把這個叢集放到k8s中應該如何部署.
1> mysql需要執行在一個Pod中
在k8s上建立一個pod,裡面在建立一個mysql容器
2> apache-fpm, 有三個, 其實他們都是類似, 所以我們可以把它放到Deployment控制器中建立, Deployment可以配置apache-fpm的副本數有3個副本
3> SQUID,快取伺服器也有三個, 我們也可以把它放到Deployment控制器中建立.
4> LVS, 可以用叢集本身的功能, 進行負載排程.
現在這種結構, 我們發現, 如果快取伺服器SQUID想要訪問apapche-fpm, 寫反向代理的話, 需要寫三臺伺服器. 並且, pod如果退出重新建立, 那麼pod的ip地址會變換. 除非採用的是statefulSet, 但是在apache-fpm中使用statefulSet是沒有意義, 因為他是一個無狀態服務 . 那怎麼辦呢? 我麼可以在前面加一個service, 這個service就是Service-php-fpm的. 他會繫結我們的標籤.
SQUID去進行反向代理設定的時候, 不需要寫php-fpm的三個ip地址了, 而且, pod死亡以後, 控制器會把他維持到三個副本, 會在自動建立一個, 新建立的ip地址和原來的是不一樣的. SQUID如果在裡面填寫的是目標ip, 就有問題. 所以, SQUID裡面寫的是server-php-fpm的地址. 這樣SQUID只要執行到Service-php-fpm上面即可.
mysql也是一個pod, 我們要求mysql這個pod如果死了,重啟, 他的ip地址和主機名是不能變的, 因此我們把它放到statefulSet中.
Kubernetes內部是一個扁平化的網路, 相互之間可以通過localhost請求訪問, 所以, 關聯關係如下:
SQUID需要被外網訪問, 因此, 我們在SQUID上也可以建立一個Service-SQUID
這樣,我們就可以把這個架構完整的部署在k8s叢集中了.