推薦手機閱讀原文:https://mp.weixin.qq.com/s/nR6P6eidE1r5A2viLCFHWA
推薦手機閱讀閱讀:https://mp.weixin.qq.com/s/nR6P6eidE1r5A2viLCFHWA
推薦手機閱讀閱讀:https://mp.weixin.qq.com/s/nR6P6eidE1r5A2viLCFHWA
如下圖,在K8S中資源排程的基本單位是Pod
Pod其實是一個抽象的概念,Pod裡是我們的業務容器(docker/containerd)。像大家聽過的Deployment、StatefulSet、CronJob等資源排程物件所排程的資源都是Pod。
為了更好的理解Pod的概念,大家可以將Pod理解成VM 虛擬機器器,將Pod中的容器理解成VM中的程序。既然這樣理解,就意味著Pod中的容器程序可以直接通過localhost+埠號實現網路互通,也意味著Pod中的容器可以實現類似直接讀取彼此產出到磁碟上的檔案的效果。
如上圖:容器1存取:127.0.0.1:8082
可以存取到容器2。
在實際用應用中,比如我們有兩個服務:服務A和服務B,並且他倆之間只能通過本地迴環網路卡通訊,那我們在就應該將它們分配進同一個pod中。
那什麼是資源排程? 簡單來說就是將:為Pod挑選一個合適的物理節點,然後將Pod中的容器啟動好對外提供服務。
簡單的,只要將我們想啟動的Docker容器填入Pod資源物件的containers欄位中,再通過kubectl命令建立Pod,K8S會為Pod選擇一個合適的Node,並在該Node上啟動使用者指定的容器。
如下是一個Pod的Yaml描述檔案,Pod中定義了兩個容器分別是:nginx、shell
apiVersion: v1 # 必選,API的版本號
kind: Pod # 必選,型別Pod
metadata: # 必選,後設資料
name: daemon-pod # 必選,符合RFC 1035規範的Pod名稱
spec:
containers:
- name: nginx
image: nginx:latest # 必選,容器所用的映象的地址
- name: shell
image: busybox
stdin: true
tty: true
使用kubectl命名建立Pod,可以看到Ready數為2、Node欄位說明該Pod執行在叫node02的宿主機上
驗證,找到node02
登入node02,檢視node02上是否有相應的docker容器
可以看到K8S不光啟動了nginx、busybox容器,還多啟動一個叫pause的容器,大家也叫它infra容器。
Infra容器的作用:Pod中的所有容器會共用一個NetworkNamespace,因為在建立pod中的業務容器前,會先建立pause容器佔用NetworkNamespace,後續建立的業務容器都加入到pause的網路中,相當於在Docker run命令中新增引數:--net=container:pause
,這也是為什麼Pod中的所有容器的ip其實都是pod的ip。
如下圖,進入shell容器中,看到它的ip其實就是上圖中的pod ip。
在nginx容器中,也能看到容器的ip就是pod的ip
如下圖是K8S建立Pod時,Pod的網路協定棧的初始化過程
簡單解讀,理解pause容器是K8S網路模型中的精髓~
--net=node
意為不初始化網路協定棧,說白了就是除了自帶的lo迴環網路卡外,不新增其他的網路卡。疑問:為什麼pause容器的網路協定棧不由容器執行時建立它時立即分配好呢?
答:這是個好問題,這麼做也是呼應了K8S網路的核心目標思想:
IP分配,換句話說K8S要保證在整個叢集中每個POD要獨立不重複的IP地址
IP路由,換句話說K8S要保證在整個叢集中各個POD的IP是要互通的
這也是它為什麼設計這個流程的原因
預設情況下Pod中的各容器是不會共用同一個統一個PID Namespace的,需要手動新增引數shareProcessNamespace: true
apiVersion: v1
kind: Pod
metadata:
name: daemon-pod
spec:
# 共用pid namespace
shareProcessNamespace: true
containers:
- name: nginx
image: nginx:latest
- name: shell
image: busybox
stdin: true
tty: true
驗證:如下圖,在shell容器中可以看到nginx程序(通過這更好的將pod理解成虛擬機器器),同理登陸pod重點任意容器也能看到pid=1的程序是pause程序。
此時pause容器為Pod提供1號程序,在Uninx中,程序為1的程序被稱作init程序。
這個init程序很特殊,因為它會維護一張程序表並不斷的檢測其他程序的狀態,當出現:子程序因父程序的異常退出而變成「孤兒程序」或者是叫「殭屍程序」時,init程序(pause)會收養這個遊離的程序,然後在它退出時,釋放它佔用的資源,否則會可能會出現大量的殭屍程序佔用作業系統的程序表項。
在k8s1.8之前,預設是啟用共用pid namespace
在k8s1.8之後,則需要像本小節一樣,通過引數shareProcessNamespace
顯示的開啟
問:既然共用了pause的pid有這麼多好處,為啥後續版本的k8s不再預設開啟了呢?
答:一方面:k8s推薦的做法是,單個pod裡面放盡量少的容器,如只放你的業務容器,這樣殭屍程序帶來的影響幾乎可以忽略不計,共用與不共用,意義不大。
另一方面:像一些特殊的如systemd映象,啟動需要獲取pid1,否則無法啟動
可以通過lifecycle
欄位在容器建立完成後以及關閉前執行指定的動作,如建立使用者/建立目錄,啟動指令碼等
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:latest
lifecycle:
postStart: # 容器建立完成後執行的指令, 可以是exec httpGet TCPSocket
exec:
command:
- sh
- -c
- 'mkdir /data/ '
preStop: # 關閉前的操作
httpGet:
path: /
port: 80
# exec:
# command:
# - sh
# - -c
# - sleep 9
restartPolicy: Always
spec.containers.lifecycle.postStart
引數可以指定容器在建立完成後執行一段指令
回憶一下,容器還有個command引數即:spec.containers.command
也可以指定一段指令。注意點如下:
spec.containers.lifecycle.postStart
的執行依賴於容器建立後的環境而spec.initContainers.command
的不會依賴業務容器的環境,執行時間也會先於如上兩個command。
業務容器的啟動依賴很多環境設定,如:
如果有攻擊性的程式獲得了使用這些命令的許可權,就會有很大的安全隱患,為了安全,我們是不希望業務容器中包含這些危險的命令。
這時可以使用initcontainer完成這種工作,因為initcontainer執行結束後會退出,沒有後續的安全隱患。
準備yaml,initContainer和業務容器共用掛載volume的方式,讓業務容器共用initContainer的初始化檔案
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: test-init-container
name: test-init-container
spec:
replicas: 1
selector:
matchLabels:
app: test-init-container
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: test-init-container
spec:
volumes:
- name: data
emptyDir: {}
initContainers:
- name: init01
image: busybox
volumeMounts:
- name: data
mountPath: /tmp
command:
- sh
- -c
- touch /tmp/test-init-container.txt
containers:
- image: nginx
name: nginx
volumeMounts:
- name: data
mountPath: /tmp
檢視Pod執行的Event可以看到先執行了初始化容器的相關操作
[root@master01 initContainer]# kubectl describe pod test-init-container-79d689d7d8-fgz2s
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 6m47s default-scheduler Successfully assigned default/test-init-container-79d689d7d8-fgz2s to master01
Normal Pulling 6m47s kubelet Pulling image "busybox"
Normal Pulled 6m43s kubelet Successfully pulled image "busybox" in 3.781619189s
Normal Created 6m43s kubelet Created container init01
Normal Started 6m43s kubelet Started container init01
Normal Pulling 6m42s kubelet Pulling image "nginx"
Normal Pulled 6m11s kubelet Successfully pulled image "nginx" in 31.093619016s
Normal Created 6m11s kubelet Created container nginx
Normal Started 6m11s kubelet Started container nginx
驗收
[root@master01 initContainer]# kubectl exec -ti test-init-container-79d689d7d8-fgz2s -- sh
Defaulted container "nginx" out of: nginx, init01 (init)
# ls /tmp
test-init-container.txt
名稱 | 作用 | 引入版本 |
---|---|---|
startupProbe | 1. 用於判斷容器內的應用程序是否成功啟動。 2. 若設定了startupProbe。直到它檢測通過前,會禁用其他探針 3. startupProbe檢測未通過,會使用restartPolicy重啟策略重啟 4. startupProbe探測成功後將不再探測。 |
1.16 |
readinessProbe | 1. 使用者探測容器內的程式是否健康,是否可以接收流量 2. 探測成功表示該容器已經完全啟動,可接收流量。 3. 若未設定,預設返回success |
|
livenessProbe | 1. 用於判斷容器是否執行 2. 若探測失敗kubelet根據重啟策略重啟容器 3. 若未設定,預設返回success |
檢測方式一:ExecAction
原理如下:執行一個指令碼,返回0表示成功,返回非0表示異常
[root@master01 yamls]# touch 1
[root@master01 yamls]# cat 1
[root@master01 yamls]# echo $?
0
[root@master01 yamls]# cat 123123.txt
cat: 123123.txt: 沒有那個檔案或目錄
[root@master01 yamls]# echo $?
1
#!/bin/bash
#K8S 的存活探針
function liveness()
{
result=`nmap 127.0.0.1 -p $Target_PORT | grep $Target_PORT/tcp | awk '{print $2}'`
if [ "$result" != "open" ];then
echo 'port not open'
return 1
fi
}
liveness
檢測方式二:TcpSocketAction
通過Tcp連線檢查容器內的埠是否是連通的,如果連通,認為容器健康。原理如下:
# 連通的情況
[root@master01 yamls]# telnet 10.10.10.101 2380
Trying 10.10.10.101...
Connected to 10.10.10.101.
Escape character is '^]'.
^CConnection closed by foreign host.
# 非連通的情況
[root@master01 yamls]# telnet 10.10.10.101 2381
Trying 10.10.10.101...
telnet: connect to address 10.10.10.101: Connection refused
檢測方式三:HttpGetAction
通過應用程式暴露的Http介面,來檢查應用程式是否健康,若返回的狀態碼在[200,400)之間,認為程式健康。
目的:判斷容器是否啟動了,檢測失敗後會重啟容器
參考
livenessProbe:
failureThreshold: 5
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
目的:探測容器中的應用是否是正常的
檢測通過:表示應用可以接收流量,READY狀態變成1/1
[root@master01 yamls]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 54s
檢測失敗,READY狀態變成0/1,且RESTARTS且0,表示不會重啟容器
[root@master01 yamls]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 0/1 Running 0 54s
參考
readinessProbe:
failureThreshold: 3
httpGet:
path: /ready
port: 8181
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
在有了livenessProbe和readinessProbe之後,為啥還整一個startupProbe出來呢?
可用來應對極端情況:應用啟動時各種載入設定,導致啟動的特別慢。 最終導致livenessProbe檢查失敗,當livenessProbe檢查失敗時,k8s會重啟容器。 重啟之後應用啟動還是慢,livenessProbe還是失敗,就進入了死迴圈
startupProbe其實就是將等待探測應用正常啟動的步驟從livenessProbe中提取出來,放在livenessProbe步驟前。若設定了startupProbe,livenessProbe和readinessProbe會先被禁用,等startupProbe通過後,livenessProbe和readinessProbe才會生效。
實驗:
apiVersion: v1 # 必選,API的版本號
kind: Pod # 必選,型別Pod
metadata: # 必選,後設資料
name: nginx # 必選,符合RFC 1035規範的Pod名稱
spec: # 必選,用於定義容器的詳細資訊
containers: # 必選,容器列表
- name: nginx # 必選,符合RFC 1035規範的容器名稱
image: nginx:latest # 必選,容器所用的映象的地址
ports: # 可選,容器需要暴露的埠號列表
- name: http # 埠名稱,如果需要暴露多個埠,需要保證每個port的name不能重複
containerPort: 80 # 埠號
protocol: TCP # 埠協定,預設TCP
startupProbe:
httpGet: # httpGet檢測方式,生產環境建議使用httpGet實現介面級健康檢查,健康檢查由應用程式提供。
path: /api/successStart # 檢查路徑
port: 80
restartPolicy: Always
因為沒有/api/successStart
介面,所以startupProbe檢測不通過
如下:pod的status為running,但是Ready為0
[root@master01 yamls]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 0/1 Running 0 19s
檢視詳情:
startupProbe會按照繼續按策略探測,當失敗的次數達到預期後,會重啟,如下重啟了4次了
[root@master01 yamls]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 0/1 CrashLoopBackOff 4 (27s ago) 2m57s
可以將HttpGet檢測方式換成TcpSocket去檢測80埠,startupProbe校驗即可通過
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:latest
startupProbe:
tcpSocket:
port: 80
restartPolicy: Always
[root@master01 yamls]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 14s
當我們關閉或者刪除pod時,Pod的狀態會變成:Terninating
[root@master01 yamls]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 0/1 Terninating 0 2m16s
另外你會發現,執行delete命令時,會等待一段時間
[root@master01 yamls]# kubectl delete pod nginx
pod "nginx" deleted
這個等待的時間是k8s給pod留出來的一段寬限期,可以通過kubectl edit pod xxx
檢視pod的設定,預設情況下有一個叫terminationGracePeriodSeconds: 30
的引數,值為30秒,表示在pod被delete之後,有30秒的寬限期。
在這個寬限期中會做收尾的工作如:
上文說過lifecycle,重新貼出來,如下:
lifecycle:
postStart: # 容器建立完成後執行的指令, 可以是exec httpGet TCPSocket
exec:
command:
- sh
- -c
- 'mkdir /data/ '
preStop: # 容器關閉前的操作
httpGet:
path: /
port: 80
exec:
command:
- sh
- -c
- sleep 9
全稱:Horizontal Pod Autoscaler
可以根據CPU、記憶體使用率或者是自定義的指標完成對Pod的自動擴縮容
檢視K8S叢集的HPA相關API
使用限制:
準備測試環境
# 建立模版yaml
kubectl create deployment nginx-dp --image=nginx --dry-run=client -oyaml > nginx-dp.yaml
# 更新resources
containers:
- image: nginx
name: nginx
resources:
requests:
cpu: 10m
# 建立deployment
kubectl apply -f nginx-dp.yaml
# 檢視pod的CPU指標
[root@master01 yamls]# kubectl top pod
NAME CPU(cores) MEMORY(bytes)
busybox 0m 0Mi
nginx-dp-84c4fd8fc6-s7mnx 0m 6Mi
# 為dp暴露一個service
kubectl expose deployment nginx-dp --port=80
可以通過如下命令使用HPA
# 當CPU使用率超過10%就擴容,擴容最大數Pod數為10,最小數為1
kubectl autoscale deployment nginx-dp --cpu-percent=10 --min=1 --max=3
壓測,可以觀察到pod會被自動擴容
while true; do wget -q -O- http://192.168.217.66 > /dev/null;done
注意點:如果CPU或者是Memory的飆升的源頭是資料庫壓力,那麼我們對pod進行擴容不僅沒有好轉,返回適得其反。
靜態Pod是由kubelet直接管理的,且只能在該kubelet所在的Node上執行。
靜態Pod不受ApiServer管理,無法與ReplicationController、Deployment、DaemonSet進行關聯。
kubelet也無法對靜態Pod進行健康檢查。
有兩種方式建立靜態Pod,分別是使用:靜態檔案/Http,若使用靜態組態檔建立pod,需要在kubelet的啟動引數statucPodPath
中指定靜態Pod的yaml描述檔案的位置。
只要將pod的yaml檔案放入指定的目錄中,過一會便能通過kubectl檢視到pod。
嘗試通過kubectl刪除Pod,會一直處於pending狀態,這是因為kubectl會通過apiserver下發刪除的命令,而apiserver無法管理靜態pod。故,若想刪除靜態Pod,需要將對應的pod的yaml檔案移出statucPodPath
。
像Docker公司推出的叢集排程工具:Docker Compose或是Docker Swarm它們排程的基本單位都是Docker容器。
點選檢視白日夢的筆記:玩轉Docker容器排程-DockerCompose、DockerSwarm
而在K8S中叢集排程的基本單位是上文中長篇介紹的Pod,他們兩者維度是不同的。Pod顯然是站在更高的維度上。
因為在容器編排領域中,難點不是為容器選擇一個合適的節點然後將容器啟動好。難點是解決應用間複雜的相互依賴關係。
比如下:
前文說了,大家可以把Pod理解成傳統意義上的虛擬機器器,Pod中的容器相當於虛擬機器器中的不同應用,Pod中有哪些容器由開發人員說了算。
這樣其實就是變相的將容器編排最為複雜環節的皮球重新踢給了開發人員,由他們自己描述好,也就實現了天然的解決了應用的複雜依賴關係編排這一難點。K8S排程時也要以Pod為基本單位去選擇一個相對合適宿主機,批次的啟動好Pod中的容器就行。
Kubernetes官網
《Kubernetes權威指南》
《Kubernetes網路原理與實踐》