K8s中的Pod和容器設(shè)計(jì)模式
作者 | 中國(guó)移動(dòng)云能力中心PaaS產(chǎn)品部 仇明
?近些年來(lái),容器技術(shù)迅速席卷全球,顛覆了應(yīng)用的開(kāi)發(fā)、交付和運(yùn)行模式。容器技術(shù)作為云原生技術(shù)領(lǐng)域的技術(shù)基石,也是現(xiàn)今最熱門(mén)的一種服務(wù)器端技術(shù)。容器以及容器編排技術(shù)成為基礎(chǔ)設(shè)施領(lǐng)域最炙手可熱的關(guān)鍵詞,隨著容器及周邊生態(tài)技術(shù)的蓬勃發(fā)展,容器社區(qū)當(dāng)仁不讓成為開(kāi)源社區(qū)最活躍的生態(tài)圈之一。同時(shí),以容器技術(shù)為核心的容器生態(tài)圈在云計(jì)算、互聯(lián)網(wǎng)等領(lǐng)域得到了廣泛應(yīng)用。
01容器的概念
早在2000年,容器技術(shù)的概念就已經(jīng)出現(xiàn),當(dāng)初是用在chroot環(huán)境中做進(jìn)程隔離。2013年,Docker項(xiàng)目正式發(fā)布,讓Linux容器技術(shù)逐步席卷天下。2014 年,Kubernetes項(xiàng)目正式發(fā)布,容器技術(shù)開(kāi)始和編排系統(tǒng)齊頭并進(jìn)。
容器的本質(zhì),是一個(gè)試圖被隔離,資源受限制的進(jìn)程。所使用的隔離技術(shù)都是基于Linux內(nèi)核提供,其中namespace用來(lái)做資源隔離,cgroups用來(lái)做資源限制(CPU、內(nèi)存、存儲(chǔ)、網(wǎng)絡(luò)的使用限制)。隔離技術(shù)中不得不提的是虛擬機(jī)。通過(guò)如下的虛擬機(jī)和容器的對(duì)比圖,可以看出,虛擬機(jī)工作原理中最重要的是Hypervisor層——進(jìn)行硬件虛擬化功能,模擬出運(yùn)行操作系統(tǒng)需要的各種硬件(如 CPU、內(nèi)存、I/O 設(shè)備等),然后,在這些虛擬硬件上安裝操作系統(tǒng)Guest OS。而容器沒(méi)有自己的OS,通過(guò)Container Daemon直接共享宿主機(jī)內(nèi)核,所有對(duì)容器進(jìn)程的限制都是基于宿主機(jī)操作系統(tǒng)本身能力進(jìn)行。故而,容器也顯得很輕量化。
02Pod概念
2.1 操作系統(tǒng)中的例子
首先,在虛擬機(jī)中運(yùn)行一個(gè)簡(jiǎn)單的java程序,然后通過(guò)pstree指令,將會(huì)看到這個(gè)java程序?qū)嶋H上是由一組(linux中)線程組成,共享資源,共同協(xié)作完成java程序的業(yè)務(wù)工作。
2.2 Pod實(shí)現(xiàn)原理
在被譽(yù)為云時(shí)代的操作系統(tǒng)Kubernetes 中,容器被類(lèi)比為了進(jìn)程,那么Pod呢?其實(shí) Pod 只是一個(gè)抽象的邏輯概念,它可以類(lèi)比為前面例子中的java程序一樣,是一組(一個(gè)或者多個(gè))容器的集合,這些容器之間共享同一份網(wǎng)絡(luò)(Network Namespace)和存儲(chǔ)(Volume)等資源。
2.2.1 網(wǎng)絡(luò)共享
Pod中首先會(huì)創(chuàng)建一個(gè)基礎(chǔ)容器(Infra container),然后以join network namespace的方式,將其它容器都與基礎(chǔ)容器的網(wǎng)絡(luò)關(guān)聯(lián)起來(lái),這樣就實(shí)現(xiàn)了pod內(nèi)容器間共享網(wǎng)絡(luò)資源的功能。所以基礎(chǔ)容器永遠(yuǎn)都是Pod 里面第一個(gè)被創(chuàng)建的容器,等待其他容器的加入。Pod的生命周期只跟基礎(chǔ)容器一致。基礎(chǔ)容器是一個(gè)非常小的鏡像,叫做 k8s.gcr.io/pause,大概 100~200KB 左右,是一個(gè)匯編語(yǔ)言寫(xiě)的、永遠(yuǎn)處于“暫停”狀態(tài)的容器。
2.2.2 存儲(chǔ)共享
Pod是通過(guò)Volume實(shí)現(xiàn)共享存儲(chǔ)的。Pod先綁定好Volume,然后Pod里的容器只要聲明掛載這個(gè)Volume,就可以共享這個(gè)Volume對(duì)應(yīng)的宿主機(jī)目錄,所有容器看到的這個(gè)Volume目錄內(nèi)容都是一樣的。以下yaml提供了單Pod內(nèi)啟兩個(gè)容器的示例。
apiVersion: v1
kind: Pod
metadata:
name: one-pod-two-container-demo
spec:
volumes: #Pod先綁定好Volume
- name: shared-logs
emptyDir: {}
containers:
- name: web-nginx
image: nginx
volumeMounts: #容器再掛載這個(gè)volume
- name: shared-logs
mountPath: /var/log/nginx
- name: sidecar-container
image: busybox
command: ["sh","-c","while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
volumeMounts: #容器再掛載這個(gè)volume
- name: shared-logs
mountPath: /var/log/nginx
03容器設(shè)計(jì)模式
Kubernetes 社區(qū)結(jié)合了Kubernetes 集群的微服務(wù)模型提出了一系列解決典型分布式系統(tǒng)問(wèn)題的容器設(shè)計(jì)模式,主要可以分三大類(lèi):
- 單容器管理模式;
- 單節(jié)點(diǎn)多容器模式;
- 多節(jié)點(diǎn)多容器模式。
3.1 單容器管理模式
Kubernetes 容器設(shè)計(jì)模式中,單容器管理模式是最簡(jiǎn)單的,單單啟動(dòng)一個(gè)單容器微服務(wù)實(shí)例命令與Docker原生命令類(lèi)似。
$ kubectl run nginx --image=nginx:latest -n qiuqiu
pod/nginx created
$ kubectl get all -n qiuqiu
NAME READY STATUS RESTARTS AGE
pod/nginx 1/1 Running 0 9s
3.2 單節(jié)點(diǎn)多容器模式
在秉持關(guān)注點(diǎn)分離原則(SOC)下,容器化應(yīng)用程序是容器之間的職責(zé)分離,每個(gè)容器都應(yīng)該只承擔(dān)它最擅長(zhǎng)業(yè)務(wù),其他額外的任務(wù),應(yīng)該交給其他容器(Sidecar 容器),單節(jié)點(diǎn)多容器模式便是利用了Kubernetes 中Pod內(nèi)所有容器可以共享同一個(gè)存儲(chǔ)空間(Volume)和網(wǎng)絡(luò)地址空間(Network Namespace)特性,來(lái)充分體現(xiàn)SOC原則。
3.2.1 挎斗模式(Sidecar Pattern)
挎斗模式主要是利用在同一個(gè)Pod中的容器可以共享存儲(chǔ)的能力。如下圖應(yīng)用日志收集服務(wù)場(chǎng)景:對(duì)于應(yīng)用服務(wù)A(如:Nginx)來(lái)說(shuō),重心并非日志的持久化存儲(chǔ)與管理(access.log等),而運(yùn)維人員有時(shí)會(huì)需要通過(guò)日志排查服務(wù)異常問(wèn)題。因此,日志收集器服務(wù)是一個(gè)很有必要的輔助工具。秉持關(guān)注點(diǎn)分離原則(SOC),可以為應(yīng)用服務(wù)的Pod中加入一個(gè)日志收集器服務(wù)的Sidecar 容器,應(yīng)用服務(wù)容器作為主容器只需關(guān)注業(yè)務(wù),日志信息由Sidecar 容器進(jìn)行收集處理。生產(chǎn)環(huán)境中可以使用Filebeat 或者 Logstash 等工具收集日志并分發(fā)給ElasticSearch集群。挎斗模式下,通常作為輔助工具的Sidecar 容器鏡像是標(biāo)準(zhǔn)化可重用的,應(yīng)用服務(wù)不需要每次都帶著輔助工具的執(zhí)行文件一起打包迭代升級(jí)。
以下yaml文件定義了一個(gè)Pod內(nèi),啟動(dòng)2個(gè)容器(不包含基礎(chǔ)容器),并使用emptyDir卷作為共享存儲(chǔ)。Sidecar 容器通常在定義中排在第二位,因此在執(zhí)行 kubectl 命令時(shí),默認(rèn)目標(biāo)是主容器。Nginx 服務(wù)作為主容器,產(chǎn)生的日志寫(xiě)入共享存儲(chǔ)卷掛載的/var/log/nginx目錄。這樣可以阻止原本將Nginx 服務(wù)日志以容器標(biāo)準(zhǔn)化輸出形式輸出(即阻止被軟連接到/dev/stdout和/dev/stderr,輸出到了容器的前臺(tái)日志),并寫(xiě)入access.log 與 error.log 文件。最后由Sidecar容器(busybox)進(jìn)行相關(guān)日志處理。
apiVersion: v1
kind: Pod
metadata:
name: app-log-aggregation-server
spec:
volumes:
- name: shared-logs
emptyDir: {}
containers:
- name: nginx
image: nginx
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
- name: sidecar-container
image: busybox
command: ["sh","-c","while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
3.2.2 外交官模式(Ambassador Pattern)
外交官模式主要是利用同一個(gè)Pod中的容器可以共享網(wǎng)絡(luò)地址空間的特性。外交官模式中存在一個(gè)負(fù)責(zé)代理從應(yīng)用程序容器到其他服務(wù)的連接的邊車(chē)容器(SideCar container ),也稱(chēng)為外交官容器。例如,幾乎所有應(yīng)用程序在某個(gè)階段都需要數(shù)據(jù)庫(kù)連接。針對(duì)應(yīng)用開(kāi)發(fā)迭代中會(huì)存在多種環(huán)境數(shù)據(jù)庫(kù)——一個(gè)開(kāi)發(fā)數(shù)據(jù)庫(kù)、一個(gè)測(cè)試數(shù)據(jù)庫(kù)和一個(gè)生產(chǎn)數(shù)據(jù)庫(kù)。在編寫(xiě)應(yīng)用程序的 Pod 時(shí),開(kāi)發(fā)/實(shí)施人員需要可以通過(guò)環(huán)境變量或 configMap 更改數(shù)據(jù)庫(kù)連接配置項(xiàng)達(dá)到數(shù)據(jù)庫(kù)的切換。而這種情況下還可以通過(guò)外交官模式,根據(jù)運(yùn)行的環(huán)境將數(shù)據(jù)庫(kù)連接代理到相應(yīng)的數(shù)據(jù)庫(kù)。開(kāi)發(fā)/實(shí)施人員無(wú)需修改配置項(xiàng)(和使用開(kāi)發(fā)環(huán)境數(shù)據(jù)庫(kù)配置項(xiàng)一樣),當(dāng)部署到測(cè)試或生產(chǎn)環(huán)境時(shí),外交官容器會(huì)檢測(cè)它在哪個(gè)環(huán)境上運(yùn)行(例如反射方式),并連接到正確的數(shù)據(jù)庫(kù)上。
以下yaml給出一個(gè)Redis客戶端訪問(wèn)不同環(huán)境Redis庫(kù)樣例。主容器是由redis客戶端服務(wù),外交官容器是基于malexer/twemproxy 鏡像啟動(dòng)的容器,提供中繼鏈接Redis數(shù)據(jù)服務(wù)。需要說(shuō)明的malexer/twemproxy 鏡像需要將Redis 實(shí)例以環(huán)境變量傳遞(13-15行),格式為address:port:weight。默認(rèn)情況下,該容器偵聽(tīng)端口 6380(其他高級(jí)配置可查閱github:https://github.com/malexer/docker-twemproxy)。
# Redis client yaml
apiVersion: v1
kind: Pod
metadata:
namespace: qiuqiu
name: ambassador-example
spec:
containers:
- name: redis-client
image: redis
- name: ambassador
image: malexer/twemproxy
env:
- name: REDIS_SERVERS
value: redis-st-0.redis-svc.qiuqiu.svc.cluster.local:6379:1
ports:
- containerPort: 6380
以下圖,演示了應(yīng)用程序(由 Redis 客戶端模擬)向 localhost:6380 發(fā)起請(qǐng)求,外交官容器接收請(qǐng)求并將其中轉(zhuǎn)到其配置中定義的 Redis 服務(wù)器。
3.2.3 適配器模式(Adapter Pattern)
適配器模式主要是以標(biāo)準(zhǔn)化和規(guī)范化應(yīng)用對(duì)外暴露服務(wù)接口。每個(gè)應(yīng)用程序可能會(huì)以各種形式(如JSON、XML、StatsD等),對(duì)外輸出監(jiān)控指標(biāo)數(shù)據(jù)。然而,監(jiān)控系統(tǒng)卻希望收到的是統(tǒng)一數(shù)據(jù)模型的數(shù)據(jù)。這時(shí)就可以通過(guò)使用適配器模式,將應(yīng)用程序容器,同相應(yīng)的監(jiān)控指標(biāo)數(shù)據(jù)轉(zhuǎn)換的適配器容器創(chuàng)建在同一個(gè)Pod內(nèi),就可做到將不同應(yīng)用的異構(gòu)監(jiān)控指標(biāo)數(shù)據(jù)轉(zhuǎn)換為單個(gè)統(tǒng)一模型數(shù)據(jù)。
以下yaml是以Prometheus 監(jiān)控Nginx 服務(wù)狀態(tài)指標(biāo)為例。Nginx 有一個(gè)用于查詢(xún) Web 服務(wù)器狀態(tài)的接口,只要更改 default.conf 文件配置項(xiàng)即可啟用。接著添加一個(gè)適配器容器將此接口的輸出轉(zhuǎn)換為 Prometheus 所需的數(shù)據(jù)格式即可。通過(guò)configMap 配置 default.conf 文件內(nèi)容,其中(20-24行)定義了一個(gè)接口(/nginx_status),它利用 stub_status 模塊來(lái)顯示 Nginx 狀態(tài)信息;接著是創(chuàng)建包含Nginx 容器和適配器容器的Pod。適配器容器使用 nginx/nginx-prometheus-exporter鏡像創(chuàng)建,它可以按照 Prometheus 格式轉(zhuǎn)換 Nginx 在 /nginx_status 接口暴露的指標(biāo)數(shù)據(jù)格式。
# Nginx config
apiVersion: v1
kind: ConfigMap
metadata:
namespace: qiuqiu
name: nginx-conf
data:
default.conf: |
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location /nginx_status {
stub_status;
allow 127.0.0.1; #only allow requests from localhost
deny all; #deny all other hosts
}
}
# Nginx Pod and the adapter container
apiVersion: v1
kind: Pod
metadata:
namespace: qiuqiu
name: webserver
spec:
volumes:
- name: nginx-conf
configMap:
name: nginx-conf
items:
- key: default.conf
path: default.conf
containers:
- name: webserver
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/conf.d
name: nginx-conf
readOnly: true
- name: adapter
image: nginx/nginx-prometheus-exporter:0.4.2
args: ["-nginx.scrape-uri","http://localhost/nginx_status"]
ports:
- containerPort: 9113
以下圖是驗(yàn)證操作:執(zhí)行yaml創(chuàng)建服務(wù)后,首先登錄到 webserver Pod,安裝 curl 指令(便于發(fā)起 HTTP 請(qǐng)求),接著,檢查 /nginx_status 接口和適配器容器的轉(zhuǎn)換接口(9113/metrics )。
3.3 多節(jié)點(diǎn)多容器模式
3.3.1 選舉模式(Election Pattern)
選舉模式是一種多節(jié)點(diǎn)多容器組合模式,在分布式系統(tǒng)中尤為重要。在分布式系統(tǒng)中,有狀態(tài)應(yīng)用服務(wù)來(lái)要解決高可用和水平伸縮問(wèn)題時(shí),需要依賴(lài)外部系統(tǒng)存儲(chǔ)每個(gè)實(shí)例與狀態(tài)分片數(shù)據(jù)的對(duì)應(yīng)關(guān)系(全局信息)。而引入選舉主控節(jié)點(diǎn)機(jī)制,就不需要依賴(lài)外部的系統(tǒng)來(lái)維護(hù)自己的狀態(tài)了,由主控節(jié)點(diǎn)完成保存和分發(fā)全局信息任務(wù)——將應(yīng)用服務(wù)容器與可復(fù)用的選舉器容器組合起來(lái)使用——即選舉模式。
以下yaml提供了一個(gè)用來(lái)作為應(yīng)用容器檢測(cè)選舉結(jié)果的nodejs服務(wù)(具體參考Kubernetes社區(qū)contrib項(xiàng)目election(https://github.com/kubernetes/contrib/tree/master/election))
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-elector-nodejs
namespace: qiuqiu
spec:
replicas: 3
selector:
matchLabels:
name: leader-elector
template:
metadata:
labels:
name: leader-elector
spec:
containers:
- image: k8s.gcr.io/nodejs-election-client:0.1
imagePullPolicy: IfNotPresent
name: nodejs
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 100m
- image: k8s.gcr.io/leader-elector:0.5
imagePullPolicy: Always
name: elector
args:
- --election=elect-master
- --http=localhost:4040
ports:
- containerPort: 4040
protocol: TCP
resources:
requests:
cpu: 100m
04綜述
以上簡(jiǎn)單介紹了容器和Pod的概念,并基于容器和Pod的關(guān)系,引入容器設(shè)計(jì)模式。并結(jié)合實(shí)例介紹了跨斗模式、外交官模式、適配器模式、選舉模式。以下列表是對(duì)以上介紹模式的一個(gè)簡(jiǎn)單綜述。
鑒于篇幅限制,諸如工作隊(duì)列模式(Work queue pattern)、分散收集模式(Scatter/gather pattern)等其他優(yōu)秀的設(shè)計(jì)模式,讀者可以按需查詢(xún)社區(qū)文檔(https://kubernetes.io/blog)。