什么是Pod
一個Pod(就像一群鯨魚,或者一個豌豆夾)相當於一個共享context的配置組,在同一個context下,應用可能還會有獨立的cgroup隔離機制,一個Pod是一個容器環境下的“邏輯主機”,它可能包含一個或者多個緊密相連的應用,這些應用可能是在同一個物理主機或虛擬機上。
Pod 的context可以理解成多個linux命名空間的聯合
- PID 命名空間(同一個Pod中應用可以看到其它進程)
- 網絡 命名空間(同一個Pod的中的應用對相同的IP地址和端口有權限)
- IPC 命名空間(同一個Pod中的應用可以通過VPC或者POSIX進行通信)
- UTS 命名空間(同一個Pod中的應用共享一個主機名稱)
同一個Pod中的應用可以共享磁盤,磁盤是Pod級的,應用可以通過文件系統調用,額外的,一個Pod可能會定義頂級的cgroup隔離,這樣的話綁定到任何一個應用(好吧,這句是在沒怎么看懂,就是說Pod,應用,隔離)
由於docker的架構,一個Pod是由多個相關的並且共享磁盤的容器組成,Pid的命名空間共享還沒有應用到Docker中
和相互獨立的容器一樣,Pod是一種相對短暫的存在,而不是持久存在的,正如我們在Pod的生命周期中提到的,Pod被安排到結點上,並且保持在這個節點上直到被終止(根據重啟的設定)或者被刪除,當一個節點死掉之后,上面的所有Pod均會被刪除。特殊的Pod永遠不會被轉移到的其他的節點,作為替代,他們必須被replace.
Pod的發展
資源的共享及通信
Pod使Pod內的數據共享及通信變得容易
Pod的中的應用均使用相同的網絡命名空間及端口,並且可以通過localhost發現並溝通其他應用,每個Pod都有一個扁平化的網絡命名空間下IP地址,它是Pod可以和其他的物理機及其他的容器進行無障礙通信,(The hostname is set to the pod’s Name for the application containers within the pod)主機名被設置為Pod的名稱(這個沒翻譯出來…)
除了定義了在Pod中運行的應用之外,Pod還定義了一系列的共享的磁盤,磁盤讓這些數據在容器重啟的時候不回丟失並且可以將這些數據在Pod中的應用進行共享
管理
Pod通過提供一個高層次抽象而不是底層的接口簡化了應用的部署及管理,Pod 作為最小的部署及管理單位,位置管理,拷貝復制,資源共享,依賴關系都是自動處理的。(fate sharing估計就說什么時候該死了,什么時候該新增一個了…)
Pod的使用
Pod可以作為垂直應用整合的載體,但是它的主要特點是支持同地協作,同地管理程序,例如:
- 內容管理系統,文件和數據加載,本地緩存等等
- 日志和檢查點備份,壓縮,循環,快照等等
- 數據交換監控,日志追蹤,日志記錄和監控適配器,以及事件發布等等
- 代理,網橋,適配器
- 控制,管理,配置,更新
總體來說,獨立的Pod不會去加載多個相同的應用實例
考慮過的其他方案
為什么不直接在一個容器上運行所有的應用?
- 透明,Pod中的容器對基礎設施可見使的基礎設施可以給容器提供服務,例如線程管理和資源監控,這為用戶提供很多便利
- 解耦軟件依賴關系,獨立的容器可以獨立的進行重建和重新發布,Kubernetes 甚至會在將來支持獨立容器的實時更新
- 易用,用戶不需要運行自己的線程管理器,也不需要關心程序的信號以及異常結束碼等
- 高效,因為基礎設施承載了更多的責任,所以容器可以更加高效
為什么不支持容器的協同調度
容器的協同調度可以提供,但是它不具備Pod的大多數優點,比如資源共享,IPC,選擇機制,簡單管理等
Pod的持久性
Pod並不是被設計成一個持久化的資源,它不會在調度失敗,節點崩潰,或者其他回收中(比如因為資源的缺乏,或者其他的維護中)幸存下來
總體來說,用戶因該直接去創建Pod,並且一直使用controller(replication controller),即使是一個節點的情況,這是因為controller提供了集群范圍內的自我修復,以及復制還有展示管理
集群API的使用是用戶的主要使用方式,這是相對普遍的在如下雲管理平台中( Borg, Marathon, Aurora, and Tupperware.)
Pod的直接暴露是如下操作變得更容器
- 調度和管理的易用性
- 在沒有代理的情況下通過API可以對Pod進行操作
- Pod的生命周期與管理器的生命周期的分離
- 解偶控制器和服務,后段管理器僅僅監控Pod
- 划分清楚了Kubelet級別的功能與雲平台級別的功能,kubelet 實際上是一個Pod管理器
- 高可用,當發生一些刪除或者維護的過程時,Pod會自動的在他們被終止之前創建新的替代
目前對於寵物的最佳實踐是,創建一個副本等於1和有對應service的一個replication控制器。如果你覺得這太麻煩,請在這里留下你的意見。
容器的終止
因為pod代表着一個集群中節點上運行的進程,讓這些進程不再被需要,優雅的退出是很重要的(與粗暴的用一個KILL信號去結束,讓應用沒有機會進行清理操作)。用戶應該能請求刪除,並且在室進程終止的情況下能知道,而且也能保證刪除最終完成。當一個用戶請求刪除pod,系統記錄想要的優雅退出時間段,在這之前Pod不允許被強制的殺死,TERM信號會發送給容器主要的進程。一旦優雅退出的期限過了,KILL信號會送到這些進程,pod會從API服務器其中被刪除。如果在等待進程結束的時候,Kubelet或者容器管理器重啟了,結束的過程會帶着完整的優雅退出時間段進行重試。
一個示例流程:
1. 用戶發送一個命令來刪除Pod,默認的優雅退出時間是30秒
2. API服務器中的Pod更新時間,超過該時間Pod被認為死亡
3. 在客戶端命令的的里面,Pod顯示為”Terminating(退出中)”的狀態
4. (與第3同時)當Kubelet看到Pod標記為退出中的時候,因為第2步中時間已經設置了,它開始pod關閉的流程
i. 如果該Pod定義了一個停止前的鈎子,其會在pod內部被調用。如果鈎子在優雅退出時間段超時仍然在運行,第二步會意一個很小的優雅時間斷被調用
ii. 進程被發送TERM的信號
5. (與第三步同時進行)Pod從service的列表中被刪除,不在被認為是運行着的pod的一部分。緩慢關閉的pod可以繼續對外服務,當負載均衡器將他們輪流移除。
6. 當優雅退出時間超時了,任何pod中正在運行的進程會被發送SIGKILL信號被殺死。
7. Kubelet會完成pod的刪除,將優雅退出的時間設置為0(表示立即刪除)。pod從API中刪除,不在對客戶端可見。
默認情況下,所有的刪除操作的優雅退出時間都在30秒以內。kubectl delete命令支持–graceperiod=的選項,以運行用戶來修改默認值。0表示刪除立即執行,並且立即從API中刪除pod這樣一個新的pod會在同時被創建。在節點上,被設置了立即結束的的pod,仍然會給一個很短的優雅退出時間段,才會開始被強制殺死。
使用Volume
Volume可以為容器提供持久化存儲,比如
apiVersion: v1 kind: Pod metadata: name: redis spec: containers: - name: redis image: redis volumeMounts: - name: redis-storage mountPath: /data/redis volumes: - name: redis-storage emptyDir: {}
私有鏡像
在使用私有鏡像時,需要創建一個docker registry secret,並在容器中引用。
創建docker registry secret:
kubectl create secret docker-registry regsecret --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>
容器中引用該secret:
apiVersion: v1 kind: Pod metadata: name: private-reg spec: containers: - name: private-reg-container image: <your-private-image> imagePullSecrets: - name: regsecret
RestartPoliy
支持三種RestartPolicy
- Always:只要退出就重啟
- OnFailure:失敗退出(exit code不等於0)時重啟
- Never:只要退出就不再重啟
注意,這里的重啟是指在Pod所在Node上面本地重啟,並不會調度到其他Node上去。
環境變量
環境變量為容器提供了一些重要的資源,包括容器和Pod的基本信息以及集群中服務的信息等:
(1) hostname
HOSTNAME
環境變量保存了該Pod的hostname。
(2)容器和Pod的基本信息
Pod的名字、命名空間、IP以及容器的計算資源限制等可以以Downward API的方式獲取並存儲到環境變量中。
apiVersion: v1 kind: Pod metadata: name: test spec: containers: - name: test-container image: gcr.io/google_containers/busybox command: [ "sh", "-c"] args: - env resources: requests: memory: "32Mi" cpu: "125m" limits: memory: "64Mi" cpu: "250m" env: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: MY_POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: MY_POD_IP valueFrom: fieldRef: fieldPath: status.podIP - name: MY_POD_SERVICE_ACCOUNT valueFrom: fieldRef: fieldPath: spec.serviceAccountName - name: MY_CPU_REQUEST valueFrom: resourceFieldRef: containerName: test-container resource: requests.cpu - name: MY_CPU_LIMIT valueFrom: resourceFieldRef: containerName: test-container resource: limits.cpu - name: MY_MEM_REQUEST valueFrom: resourceFieldRef: containerName: test-container resource: requests.memory - name: MY_MEM_LIMIT valueFrom: resourceFieldRef: containerName: test-container resource: limits.memory restartPolicy: Never
(3) 集群中服務的信息
容器的環境變量中還包括了容器運行前創建的所有服務的信息,比如默認的kubernetes服務對應了環境變量
KUBERNETES_PORT_443_TCP_ADDR=10.0.0.1 KUBERNETES_SERVICE_HOST=10.0.0.1 KUBERNETES_SERVICE_PORT=443 KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_PORT=tcp://10.0.0.1:443 KUBERNETES_PORT_443_TCP=tcp://10.0.0.1:443 KUBERNETES_PORT_443_TCP_PROTO=tcp KUBERNETES_PORT_443_TCP_PORT=443
由於環境變量存在創建順序的局限性(環境變量中不包含后來創建的服務),推薦使用DNS來解析服務。
ImagePullPolicy
支持三種ImagePullPolicy
- Always:不管鏡像是否存在都會進行一次拉取。
- Never:不管鏡像是否存在都不會進行拉取
- IfNotPresent:只有鏡像不存在時,才會進行鏡像拉取。
注意:
- 默認為
IfNotPresent
,但:latest
標簽的鏡像默認為Always
。 - 拉取鏡像時docker會進行校驗,如果鏡像中的MD5碼沒有變,則不會拉取鏡像數據。
- 生產環境中應該盡量避免使用
:latest
標簽,而開發環境中可以借助:latest
標簽自動拉取最新的鏡像。
資源限制
Kubernetes通過cgroups限制容器的CPU和內存等計算資源,包括requests(請求,調度器保證調度到資源充足的Node上)和limits(上限)等:
spec.containers[].resources.limits.cpu
:CPU上限,可以短暫超過,容器也不會被停止spec.containers[].resources.limits.memory
:內存上限,不可以超過;如果超過,容器可能會被停止或調度到其他資源充足的機器上spec.containers[].resources.requests.cpu
:CPU請求,可以超過spec.containers[].resources.requests.memory
:內存請求,可以超過;但如果超過,容器可能會在Node內存不足時清理
比如nginx容器請求30%的CPU和56MB的內存,但限制最多只用50%的CPU和128MB的內存:
apiVersion: v1 kind: Pod metadata: labels: app: nginx name: nginx spec: containers: - image: nginx name: nginx resources: requests: cpu: "300m" memory: "56Mi" limits: cpu: "500m" memory: "128Mi"
注意,CPU的單位是milicpu,500mcpu=0.5cpu;而內存的單位則包括E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki等。
健康檢查
為了確保容器在部署后確實處在正常運行狀態,Kubernetes提供了兩種探針(Probe,支持exec、tcp和httpGet方式)來探測容器的狀態:
- LivenessProbe:探測應用是否處於健康狀態,如果不健康則刪除重建改容器
- ReadinessProbe:探測應用是否啟動完成並且處於正常服務狀態,如果不正常則更新容器的狀態
apiVersion: v1 kind: Pod metadata: labels: app: nginx name: nginx spec: containers: - image: nginx imagePullPolicy: Always name: http livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 15 timeoutSeconds: 1 readinessProbe: httpGet: path: /ping port: 80 initialDelaySeconds: 5 timeoutSeconds: 1
Init Container
Init Container在所有容器運行之前執行(run-to-completion),常用來初始化配置。
apiVersion: v1 kind: Pod metadata: name: init-demo spec: containers: - name: nginx image: nginx ports: - containerPort: 80 volumeMounts: - name: workdir mountPath: /usr/share/nginx/html # These containers are run during pod initialization initContainers: - name: install image: busybox command: - wget - "-O" - "/work-dir/index.html" - http://kubernetes.io volumeMounts: - name: workdir mountPath: "/work-dir" dnsPolicy: Default volumes: - name: workdir emptyDir: {}
容器生命周期鈎子
容器生命周期鈎子(Container Lifecycle Hooks)監聽容器生命周期的特定事件,並在事件發生時執行已注冊的回調函數。支持兩種鈎子:
- postStart: 容器啟動后執行,注意由於是異步執行,它無法保證一定在ENTRYPOINT之后運行。如果失敗,容器會被殺死,並根據RestartPolicy決定是否重啟
- preStop:容器停止前執行,常用於資源清理。如果失敗,容器同樣也會被殺死
而鈎子的回調函數支持兩種方式:
- exec:在容器內執行命令
- httpGet:向指定URL發起GET請求
postStart和preStop鈎子示例:
apiVersion: v1 kind: Pod metadata: name: lifecycle-demo spec: containers: - name: lifecycle-demo-container image: nginx lifecycle: postStart: exec: command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"] preStop: exec: command: ["/usr/sbin/nginx","-s","quit"]
指定Node
通過nodeSelector,一個Pod可以指定它所想要運行的Node節點。
首先給Node加上標簽:
kubectl label nodes <your-node-name> disktype=ssd
接着,指定該Pod只想運行在帶有disktype=ssd
標簽的Node上:
apiVersion: v1 kind: Pod metadata: name: nginx labels: env: test spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent nodeSelector: disktype: ssd
使用Capabilities
默認情況下,容器都是以非特權容器的方式運行。比如,不能在容器中創建虛擬網卡、配置虛擬網絡。
Kubernetes提供了修改Capabilities的機制,可以按需要給給容器增加或刪除。比如下面的配置給容器增加了CAP_NET_ADMIN
並刪除了CAP_KILL
。
apiVersion: v1 kind: Pod metadata: name: hello-world spec: containers: - name: friendly-container image: "alpine:3.4" command: ["/bin/echo", "hello", "world"] securityContext: capabilities: add: - NET_ADMIN drop: - KILL
限制網絡帶寬
可以通過給Pod增加kubernetes.io/ingress-bandwidth
和kubernetes.io/egress-bandwidth
這兩個annotation來限制Pod的網絡帶寬
apiVersion: v1 kind: Pod metadata: name: qos annotations: kubernetes.io/ingress-bandwidth: 3M kubernetes.io/egress-bandwidth: 4M spec: containers: - name: iperf3 image: networkstatic/iperf3 command: - iperf3 - -s
僅kubenet支持限制帶寬
目前只有kubenet網絡插件支持限制網絡帶寬,其他CNI網絡插件暫不支持這個功能。
kubenet的網絡帶寬限制其實是通過tc來實現的
# setup qdisc (only once) tc qdisc add dev cbr0 root handle 1: htb default 30 # download rate tc class add dev cbr0 parent 1: classid 1:2 htb rate 3Mbit tc filter add dev cbr0 protocol ip parent 1:0 prio 1 u32 match ip dst 10.1.0.3/32 flowid 1:2 # upload rate tc class add dev cbr0 parent 1: classid 1:3 htb rate 4Mbit tc filter add dev cbr0 protocol ip parent 1:0 prio 1 u32 match ip src 10.1.0.3/32 flowid 1:3
調度到指定的Node上
可以通過nodeSelector、nodeAffinity、podAffinity以及Taints和tolerations等來將Pod調度到需要的Node上。