其中兩個最廣泛的問題是:(a)指定了錯誤的容器鏡像,(b)使用私有鏡像卻不提供倉庫認證信息。這在首次使用 Kubernetes 或者綁定 CI/CD 環境時尤爲棘手。vue
讓咱們看個例子。首先咱們建立一個名爲 fail 的 deployment,它指向一個不存在的 Docker 鏡像:node
python
而後咱們查看 Pods,能夠看到有一個狀態爲 ErrImagePull 或者 ImagePullBackOff 的 Pod:nginx
$ kubectl get podsgit
NAME READY STATUS RESTARTS AGEgithub
fail-1036623984-hxoas 0/1 ImagePullBackOff 0 2mweb
想查看更多信息,能夠 describe 這個失敗的 Pod:docker
數據庫
查看 describe 命令的輸出中 Events 這部分,咱們能夠看到以下內容:後端
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
5m 5m 1 {default-scheduler } Normal Scheduled Successfully assigned fail-1036623984-hxoas to gke-nrhk-1-default-pool-a101b974-wfp7
5m 2m 5 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} spec.containers{fail} Normal Pulling pulling image "rosskukulinski/dne:v1.0.0"
5m 2m 5 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} spec.containers{fail} Warning Failed Failed to pull image "rosskukulinski/dne:v1.0.0": Error: image rosskukulinski/dne not found
5m 2m 5 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} Warning FailedSync Error syncing pod, skipping: failed to "StartContainer" for "fail" with ErrImagePull: "Error: image rosskukulinski/dne not found"
5m 11s 19 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} spec.containers{fail} Normal BackOff Back-off pulling image "rosskukulinski/dne:v1.0.0"
5m 11s 19 {kubelet gke-nrhk-1-default-pool-a101b974-wfp7} Warning FailedSync Error syncing pod, skipping: failed to "StartContainer" for "fail" with ImagePullBackOff: "Back-off pulling image \"rosskukulinski/dne:v1.0.0\""
顯示錯誤的那句話:Failed to pull image "rosskukulinski/dne:v1.0.0": Error: image rosskukulinski/dne not found 告訴咱們 Kubernetes沒法找到鏡像 rosskukulinski/dne:v1.0.0。
所以問題變成:爲何 Kubernetes 拉不下來鏡像?
除了網絡鏈接問題外,還有三個主要元兇:
鏡像 tag 不正確
鏡像不存在(或者是在另外一個倉庫)
Kubernetes 沒有權限去拉那個鏡像
若是你沒有注意到你的鏡像 tag 的拼寫錯誤,那麼最好就用你本地機器測試一下。
一般我會在本地開發機上,用 docker pull 命令,帶上 徹底相同的鏡像 tag,來跑一下。好比上面的狀況,我會運行命令 docker pull rosskukulinski/dne:v1.0.0。
若是這成功了,那麼極可能 Kubernetes 沒有權限去拉取這個鏡像。參考鏡像拉取 Secrets 來解決這個問題。
若是失敗了,那麼我會繼續用不顯式帶 tag 的鏡像測試 - docker pull rosskukulinski/dne - 這會嘗試拉取 tag 爲 latest 的鏡像。若是這樣成功,代表原來指定的 tag 不存在。這多是人爲緣由,拼寫錯誤,或者 CI/CD 的配置錯誤。
若是 docker pull rosskukulinski/dne(不指定 tag)也失敗了,那麼咱們碰到了一個更大的問題:咱們全部的鏡像倉庫中都沒有這個鏡像。默認狀況下,Kubernetes 使用 Dockerhub 鏡像倉庫,若是你在使用 Quay.io,AWS ECR,或者 Google Container Registry,你要在鏡像地址中指定這個倉庫的 URL,好比使用 Quay,鏡像地址就變成 quay.io/rosskukulinski/dne:v1.0.0。
若是你在使用 Dockerhub,那你應該再次確認你發佈鏡像到 Dockerhub 的系統,確保名字和 tag 匹配你的 deployment 正在使用的鏡像。
注意:觀察 Pod 狀態的時候,鏡像缺失和倉庫權限不正確是無法區分的。其它狀況下,Kubernetes 將報告一個 ErrImagePull 狀態。
不管你是在 Kubernetes 上啓動新應用,仍是遷移應用到已存在的平臺,應用在啓動以後就掛掉都是一個比較常見的現象。
咱們建立一個 deployment,它的應用會在1秒後掛掉:
咱們看一下 Pods 的狀態:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
crasher-2443551393-vuehs 0/1 CrashLoopBackOff 2 54s
CrashLoopBackOff 告訴咱們,Kubernetes 正在盡力啓動這個 Pod,可是一個或多個容器已經掛了,或者正被刪除。
讓咱們 describe 這個 Pod 去獲取更多信息:
$ kubectl describe pod crasher-2443551393-vuehs
Name: crasher-2443551393-vuehs
Namespace: fail
Node: gke-nrhk-1-default-pool-a101b974-wfp7/10.142.0.2
Start Time: Fri, 10 Feb 2017 14:20:29 -0500
Labels: pod-template-hash=2443551393
run=crasher
Status: Running
IP: 10.0.0.74
Controllers: ReplicaSet/crasher-2443551393
Containers:
crasher:
Container ID: docker://51c940ab32016e6d6b5ed28075357661fef3282cb3569117b0f815a199d01c60
Image: rosskukulinski/crashing-app
Image ID: docker://sha256:cf7452191b34d7797a07403d47a1ccf5254741d4bb356577b8a5de40864653a5
Port:
State: Terminated
Reason: Error
Exit Code: 1
Started: Fri, 10 Feb 2017 14:22:24 -0500
Finished: Fri, 10 Feb 2017 14:22:26 -0500
Last State: Terminated
Reason: Error
Exit Code: 1
Started: Fri, 10 Feb 2017 14:21:39 -0500
Finished: Fri, 10 Feb 2017 14:21:40 -0500
Ready: False
Restart Count: 4
...
好可怕,Kubernetes 告訴咱們這個 Pod 正被 Terminated,由於容器裏的應用掛了。咱們還能夠看到應用的 Exit Code 是 1。後面咱們可能還會看到一個 OOMKilled 錯誤。
咱們的應用正在掛掉?爲何?
首先咱們查看應用日誌。假定你發送應用日誌到 stdout(事實上你也應該這麼作),你可使用 kubectl logs看到應用日誌:
不幸的是,這個 Pod 沒有任何日誌。這多是由於咱們正在查看一個新起的應用實例,所以咱們應該查看前一個容器:
什麼!咱們的應用仍然不給咱們任何東西。這個時候咱們應該給應用加點啓動日誌了,以幫助咱們定位這個問題。咱們也能夠本地運行一下這個容器,以肯定是否缺失環境變量或者掛載卷。
Kubernetes 最佳實踐建議經過 ConfigMaps 或者 Secrets 傳遞應用的運行時配置。這些數據能夠包含數據庫認證信息,API endpoints,或者其它配置信息。
一個常見的錯誤是,建立的 deployment 中引用的 ConfigMaps 或者 Secrets 的屬性不存在,有時候甚至引用的 ConfigMaps 或者 Secrets 自己就不存在。
第一個例子,咱們將嘗試建立一個 Pod,它加載 ConfigMap 數據做爲環境變量:
# configmap-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: configmap-pod
spec:
containers:
- name: test-container
image: gcr.io/google_containers/busybox
command: [ "/bin/sh", "-c", "env" ]
env:
- name: SPECIAL_LEVEL_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.how
讓咱們建立一個 Pod:kubectl create -f configmap-pod.yaml。在等待幾分鐘以後,咱們能夠查看咱們的 Pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
configmap-pod 0/1 RunContainerError 0 3s
Pod 狀態是 RunContainerError 。咱們可使用 kubectl describe 瞭解更多:
$ kubectl describe pod configmap-pod
[...]
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
20s 20s 1 {default-scheduler } Normal Scheduled Successfully assigned configmap-pod to gke-ctm-1-sysdig2-35e99c16-tgfm
19s 2s 3 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Pulling pulling image "gcr.io/google_containers/busybox"
18s 2s 3 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Pulled Successfully pulled image "gcr.io/google_containers/busybox"
18s 2s 3 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} Warning FailedSync Error syncing pod, skipping: failed to "StartContainer" for "test-container" with RunContainerError: "GenerateRunContainerOptions: configmaps \"special-config\" not found"
Events 章節的最後一條告訴咱們什麼地方錯了。Pod 嘗試訪問名爲 special-config 的 ConfigMap,可是在該 namespace 下找不到。一旦咱們建立這個 ConfigMap,Pod 應該重啓並能成功拉取運行時數據。
在 Pod 規格說明中訪問 Secrets 做爲環境變量會產生類似的錯誤,就像咱們在這裏看到的 ConfigMap錯誤同樣。
可是假如你經過 Volume 來訪問 Secrets 或者 ConfigMap會發生什麼呢?
下面是一個pod規格說明,它引用了名爲 myothersecret 的 Secrets,並嘗試把它掛爲卷:
# missing-secret.yaml
apiVersion: v1
kind: Pod
metadata:
name: secret-pod
spec:
containers:
- name: test-container
image: gcr.io/google_containers/busybox
command: [ "/bin/sh", "-c", "env" ]
volumeMounts:
- mountPath: /etc/secret/
name: myothersecret
restartPolicy: Never
volumes:
- name: myothersecret
secret:
secretName: myothersecret
讓咱們用 kubectl create -f missing-secret.yaml 來建立一個 Pod。
幾分鐘後,咱們 get Pods,能夠看到 Pod 仍處於 ContainerCreating 狀態:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
secret-pod 0/1 ContainerCreating 0 4h
這就奇怪了。咱們 describe 一下,看看到底發生了什麼:
$ kubectl describe pod secret-pod
Name: secret-pod
Namespace: fail
Node: gke-ctm-1-sysdig2-35e99c16-tgfm/10.128.0.2
Start Time: Sat, 11 Feb 2017 14:07:13 -0500
Labels:
Status: Pending
IP:
Controllers:
[...]
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
18s 18s 1 {default-scheduler } Normal Scheduled Successfully assigned secret-pod to gke-ctm-1-sysdig2-35e99c16-tgfm
18s 2s 6 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} Warning FailedMount MountVolume.SetUp failed for volume "kubernetes.io/secret/337281e7-f065-11e6-bd01-42010af0012c-myothersecret" (spec.Name: "myothersecret") pod "337281e7-f065-11e6-bd01-42010af0012c" (UID: "337281e7-f065-11e6-bd01-42010af0012c") with: secrets "myothersecret" not found
Events 章節再次解釋了問題的緣由。它告訴咱們 Kubelet 沒法從名爲 myothersecret 的 Secret 掛卷。爲了解決這個問題,咱們能夠建立 myothersecret ,它包含必要的安全認證信息。一旦 myothersecret 建立完成,容器也將正確啓動。
在 Kubernetes 中處理容器問題時,開發者須要學習的重要一課是,你的容器應用是 running 狀態,不表明它在工做。
Kubernetes 提供了兩個基本特性,稱做活躍度探測和就緒狀態探測。本質上來講,活躍度/就緒狀態探測將按期地執行一個操做(例如發送一個 HTTP 請求,打開一個 tcp 鏈接,或者在你的容器內運行一個命令),以確認你的應用和你預想的同樣在工做。
若是活躍度探測失敗,Kubernetes 將殺掉你的容器並從新建立一個。若是就緒狀態探測失敗,這個 Pod 將不會做爲一個服務的後端 endpoint,也就是說不會流量導到這個 Pod,直到它變成 Ready。
若是你試圖部署變動你的活躍度/就緒狀態探測失敗的應用,滾動部署將一直懸掛,由於它將等待你的全部 Pod 都變成 Ready。
這個實際是怎樣的狀況?如下是一個 Pod 規格說明,它定義了活躍度/就緒狀態探測方法,都是基於8080端口對 /healthy 路由進行健康檢查:
apiVersion: v1
kind: Pod
metadata:
name: liveness-pod
spec:
containers:
- name: test-container
image: rosskukulinski/leaking-app
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 3
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 3
讓咱們建立這個 Pod:kubectl create -f liveness.yaml,過幾分鐘後查看發生了什麼:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
liveness-pod 0/1 Running 4 2m
2分鐘之後,咱們發現 Pod 仍然沒處於 Ready 狀態,而且它已被重啓了4次。讓咱們 describe 一下查看更多信息:
$ kubectl describe pod liveness-pod
Name: liveness-pod
Namespace: fail
Node: gke-ctm-1-sysdig2-35e99c16-tgfm/10.128.0.2
Start Time: Sat, 11 Feb 2017 14:32:36 -0500
Labels:
Status: Running
IP: 10.108.88.40
Controllers:
Containers:
test-container:
Container ID: docker://8fa6f99e6fda6e56221683249bae322ed864d686965dc44acffda6f7cf186c7b
Image: rosskukulinski/leaking-app
Image ID: docker://sha256:7bba8c34dad4ea155420f856cd8de37ba9026048bd81f3a25d222fd1d53da8b7
Port:
State: Running
Started: Sat, 11 Feb 2017 14:40:34 -0500
Last State: Terminated
Reason: Error
Exit Code: 137
Started: Sat, 11 Feb 2017 14:37:10 -0500
Finished: Sat, 11 Feb 2017 14:37:45 -0500
[...]
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
8m 8m 1 {default-scheduler } Normal Scheduled Successfully assigned liveness-pod to gke-ctm-1-sysdig2-35e99c16-tgfm
8m 8m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Created Created container with docker id 0fb5f1a56ea0; Security:[seccomp=unconfined]
8m 8m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Started Started container with docker id 0fb5f1a56ea0
7m 7m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Created Created container with docker id 3f2392e9ead9; Security:[seccomp=unconfined]
7m 7m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Normal Killing Killing container with docker id 0fb5f1a56ea0: pod "liveness-pod_fail(d75469d8-f090-11e6-bd01-42010af0012c)" container "test-container" is unhealthy, it will be killed and re-created.
8m 16s 10 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Warning Unhealthy Liveness probe failed: Get http://10.108.88.40:8080/healthz: dial tcp 10.108.88.40:8080: getsockopt: connection refused
8m 1s 85 {kubelet gke-ctm-1-sysdig2-35e99c16-tgfm} spec.containers{test-container} Warning Unhealthy Readiness probe failed: Get http://10.108.88.40:8080/healthz: dial tcp 10.108.88.40:8080: getsockopt: connection refused
Events 章節再次救了咱們。咱們能夠看到活躍度探測和就緒狀態探測都失敗了。關鍵的一句話是 container "test-container" is unhealthy, it will be killed and re-created。這告訴咱們 Kubernetes 正在殺這個容器,由於容器的活躍度探測失敗了。
這裏有三種可能性:
你的探測不正確,健康檢查的 URL 是否改變了?
你的探測太敏感了, 你的應用是否要過一會才能啓動或者響應?
你的應用永遠不會對探測作出正確響應,你的數據庫是否配置錯了
解決方案:調整健康檢查值
查看 Pod 日誌是一個開始調測的好地方。一旦你解決了這個問題,新的 deployment 應該就能成功了。
Kubernetes 賦予集羣管理員限制 Pod 和容器的 CPU 或內存數量的能力。做爲應用開發者,你可能不清楚這個限制,致使 deployment 失敗的時候一臉困惑。
咱們試圖部署一個未知 CPU/memory 請求限額的 deployment:
# gateway.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: gateway
spec:
template:
metadata:
labels:
app: gateway
spec:
containers:
- name: test-container
image: nginx
resources:
requests:
memory: 5Gi
你會看到咱們設了 5Gi 的資源請求。讓咱們建立這個 deployment:kubectl create -f gateway.yaml。
如今咱們能夠看到咱們的 Pod:
$ kubectl get pods
No resources found.
爲啥,讓咱們用 describe 來觀察一下咱們的 deployment:
$ kubectl describe deployment/gateway
Name: gateway
Namespace: fail
CreationTimestamp: Sat, 11 Feb 2017 15:03:34 -0500
Labels: app=gateway
Selector: app=gateway
Replicas: 0 updated | 1 total | 0 available | 1 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 0 max unavailable, 1 max surge
OldReplicaSets:
NewReplicaSet: gateway-764140025 (0/1 replicas created)
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
4m 4m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set gateway-764140025 to 1
基於最後一行,咱們的 deployment 建立了一個 ReplicaSet(gateway-764140025) 並把它擴展到 1。這個是用來管理 Pod 生命週期的實體。咱們能夠 describe 這個 ReplicaSet:
$ kubectl describe rs/gateway-764140025
Name: gateway-764140025
Namespace: fail
Image(s): nginx
Selector: app=gateway,pod-template-hash=764140025
Labels: app=gateway
pod-template-hash=764140025
Replicas: 0 current / 1 desired
Pods Status: 0 Running / 0 Waiting / 0 Succeeded / 0 Failed
No volumes.
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
6m 28s 15 {replicaset-controller } Warning FailedCreate Error creating: pods "gateway-764140025-" is forbidden: [maximum memory usage per Pod is 100Mi, but request is 5368709120., maximum memory usage per Container is 100Mi, but request is 5Gi.]
哈知道了。集羣管理員設置了每一個 Pod 的最大內存使用量爲 100Mi(好一個小氣鬼!)。你能夠運行 kubectl describe limitrange 來查看當前租戶的限制。
你如今有3個選擇:
要求你的集羣管理員提高限額
減小 deployment 的請求或者限額設置
直接編輯限額
和資源限額相似,Kubernetes 也容許管理員給每一個 namespace 設置資源配額。這些配額能夠在 Pods,Deployments,PersistentVolumes,CPU,內存等資源上設置軟性或者硬性限制。
讓咱們看看超出資源配額後會發生什麼。如下是咱們的 deployment 例子:
# test-quota.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: gateway-quota spec: template: spec: containers: - name: test-container image: nginx |
咱們可用 kubectl create -f test-quota.yaml 建立,而後觀察咱們的 Pods:
$ kubectl get pods NAME READY STATUS RESTARTS AGE gateway-quota-551394438-pix5d 1/1 Running 0 16s |
看起來很好,如今讓咱們擴展到 3 個副本:kubectl scale deploy/gateway-quota --replicas=3,而後再次觀察 Pods:
$ kubectl get pods NAME READY STATUS RESTARTS AGE gateway-quota-551394438-pix5d 1/1 Running 0 9m |
啊,咱們的pod去哪了?讓咱們觀察一下 deployment:
$ kubectl describe deploy/gateway-quota Name: gateway-quota Namespace: fail CreationTimestamp: Sat, 11 Feb 2017 16:33:16 -0500 Labels: app=gateway Selector: app=gateway Replicas: 1 updated | 3 total | 1 available | 2 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 1 max unavailable, 1 max surge OldReplicaSets: NewReplicaSet: gateway-quota-551394438 (1/3 replicas created) Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 9m 9m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set gateway-quota-551394438 to 1 5m 5m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set gateway-quota-551394438 to 3 |
在最後一行,咱們能夠看到 ReplicaSet 被告知擴展到 3 。咱們用 describe 來觀察一下這個 ReplicaSet 以瞭解更多信息:
kubectl describe replicaset gateway-quota-551394438 Name: gateway-quota-551394438 Namespace: fail Image(s): nginx Selector: app=gateway,pod-template-hash=551394438 Labels: app=gateway pod-template-hash=551394438 Replicas: 1 current / 3 desired Pods Status: 1 Running / 0 Waiting / 0 Succeeded / 0 Failed No volumes. Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 11m 11m 1 {replicaset-controller } Normal SuccessfulCreate Created pod: gateway-quota-551394438-pix5d 11m 30s 33 {replicaset-controller } Warning FailedCreate Error creating: pods "gateway-quota-551394438-" is forbidden: exceeded quota: compute-resources, requested: pods=1, used: pods=1, limited: pods=1 |
哦!咱們的 ReplicaSet 沒法建立更多的 pods 了,由於配額限制了:exceeded quota: compute-resources, requested: pods=1, used: pods=1, limited: pods=1。
和資源限額相似,咱們也有 3 個選項:
要求集羣管理員提高該 namespace 的配額
刪除或者收縮該 namespace 下其它的 deployment
直接編輯配額
除非你的集羣開通了集羣自動伸縮功能,不然總有一天你的集羣中 CPU 和內存資源會耗盡。
這不是說 CPU 和內存被徹底使用了,而是指它們被 Kubernetes 調度器徹底使用了。如同咱們在第 5 點看到的,集羣管理員能夠限制開發者可以申請分配給 pod 或者容器的 CPU 或者內存的數量。聰明的管理員也會設置一個默認的 CPU/內存 申請數量,在開發者未提供申請額度時使用。
若是你全部的工做都在 default 這個 namespace 下工做,你極可能有個默認值 100m 的容器 CP U申請額度,對此你甚至可能都不清楚。運行 kubectl describe ns default 檢查一下是否如此。
咱們假定你的 Kubernetes 集羣只有一個包含 CPU 的節點。你的 Kubernetes 集羣有 1000m 的可調度 CPU。
當前忽略其它的系統 pods(kubectl -n kube-system get pods),你的單節點集羣能部署 10 個 pod(每一個 pod 都只有一個包含 100m 的容器)。
10 Pods * (1 Container * 100m) = 1000m == Cluster CPUs
當你擴大到 11 個的時候,會發生什麼?
下面是一個申請 1CPU(1000m)的 deployment 例子:
# cpu-scale.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: cpu-scale spec: template: metadata: labels: app: cpu-scale spec: containers: - name: test-container image: nginx resources: requests: cpu: 1 |
我把這個應用部署到有 2 個可用 CPU 的集羣。除了個人 cpu-scale 應用,Kubernetes 內部服務也在消耗 CPU 和內存。
咱們能夠用 kubectl create -f cpu-scale.yaml 部署這個應用,並觀察 pods:
$ kubectl get pods NAME READY STATUS RESTARTS AGE cpu-scale-908056305-xstti 1/1 Running 0 5m |
第一個 pod 被調度並運行了。咱們看看擴展一個會發生什麼:
$ kubectl scale deploy/cpu-scale --replicas=2 deployment "cpu-scale" scaled $ kubectl get pods NAME READY STATUS RESTARTS AGE cpu-scale-908056305-phb4j 0/1 Pending 0 4m cpu-scale-908056305-xstti 1/1 Running 0 5m |
咱們的第二個pod一直處於 Pending,被阻塞了。咱們能夠 describe 這第二個 pod 查看更多的信息:
$ kubectl describe pod cpu-scale-908056305-phb4j Name: cpu-scale-908056305-phb4j Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-qwds/10.128.0.4 Start Time: Sun, 12 Feb 2017 08:57:51 -0500 Labels: app=cpu-scale pod-template-hash=908056305 Status: Pending IP: Controllers: ReplicaSet/cpu-scale-908056305 [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 3m 3m 1 {default-scheduler } Warning FailedScheduling pod (cpu-scale-908056305-phb4j) failed to fit in any node fit failure on node (gke-ctm-1-sysdig2-35e99c16-wx0s): Insufficient cpu fit failure on node (gke-ctm-1-sysdig2-35e99c16-tgfm): Insufficient cpu fit failure on node (gke-ctm-1-sysdig2-35e99c16-qwds): Insufficient cpu |
好吧,Events 模塊告訴咱們 Kubernetes 調度器(default-scheduler)沒法調度這個 pod 由於它沒法匹配任何節點。它甚至告訴咱們每一個節點哪一個擴展點失敗了(Insufficient cpu)。
那麼咱們如何解決這個問題?若是你太渴望你申請的 CPU/內存 的大小,你能夠減小申請的大小並從新部署。固然,你也能夠請求你的集羣管理員擴展這個集羣(由於極可能你不是惟一一個碰到這個問題的人)。
如今你可能會想:咱們的 Kubernetes 節點是在咱們的雲提供商的自動伸縮羣組裏,爲何他們沒有生效呢?
緣由是,你的雲提供商沒有深刻理解 Kubernetes 調度器是作啥的。利用 Kubernetes 的集羣自動伸縮能力容許你的集羣根據調度器的需求自動伸縮它自身。若是你在使用 GCE,集羣伸縮能力是一個 beta 特性。
另外一個常見錯誤是建立了一個引用不存在的持久化卷(PersistentVolumes)的 deployment。不論你是使用 PersistentVolumeClaims(你應該使用這個!),仍是直接訪問持久化磁盤,最終結果都是相似的。
下面是咱們的測試 deployment,它想使用一個名爲 my-data-disk 的 GCE 持久化卷:
# volume-test.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: volume-test spec: template: metadata: labels: app: volume-test spec: containers: - name: test-container image: nginx volumeMounts: - mountPath: /test name: test-volume volumes: - name: test-volume # This GCE PD must already exist (oops!) gcePersistentDisk: pdName: my-data-disk fsType: ext4 |
讓咱們建立這個 deployment:kubectl create -f volume-test.yaml,過幾分鐘後查看 pod:
kubectl get pods NAME READY STATUS RESTARTS AGE volume-test-3922807804-33nux 0/1 ContainerCreating 0 3m |
3 分鐘的等待容器建立時間是很長了。讓咱們用 describe 來查看這個 pod,看看到底發生了什麼:
$ kubectl describe pod volume-test-3922807804-33nux Name: volume-test-3922807804-33nux Namespace: fail Node: gke-ctm-1-sysdig2-35e99c16-qwds/10.128.0.4 Start Time: Sun, 12 Feb 2017 09:24:50 -0500 Labels: app=volume-test pod-template-hash=3922807804 Status: Pending IP: Controllers: ReplicaSet/volume-test-3922807804 [...] Volumes: test-volume: Type: GCEPersistentDisk (a Persistent Disk resource in Google Compute Engine) PDName: my-data-disk FSType: ext4 Partition: 0 ReadOnly: false [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 4m 4m 1 {default-scheduler } Normal Scheduled Successfully assigned volume-test-3922807804-33nux to gke-ctm-1-sysdig2-35e99c16-qwds 1m 1m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-qwds} Warning FailedMount Unable to mount volumes for pod "volume-test-3922807804-33nux_fail(e2180d94-f12e-11e6-bd01-42010af0012c)": timeout expired waiting for volumes to attach/mount for pod "volume-test-3922807804-33nux"/"fail". list of unattached/unmounted volumes=[test-volume] 1m 1m 1 {kubelet gke-ctm-1-sysdig2-35e99c16-qwds} Warning FailedSync Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod "volume-test-3922807804-33nux"/"fail". list of unattached/unmounted volumes=[test-volume] 3m 50s 3 {controller-manager } Warning FailedMount Failed to attach volume "test-volume" on node "gke-ctm-1-sysdig2-35e99c16-qwds" with: GCE persistent disk not found: diskName="my-data-disk" zone="us-central1-a" |
很神奇! Events 模塊留有咱們一直在尋找的線索。咱們的 pod 被正確調度到了一個節點(Successfully assigned volume-test-3922807804-33nux to gke-ctm-1-sysdig2-35e99c16-qwds),可是那個節點上的 kubelet 沒法掛載指望的卷 test-volume。那個卷本應該在持久化磁盤被關聯到這個節點的時候就被建立了,可是,正如咱們看到的,controller-manager 失敗了:Failed to attach volume "test-volume" on node "gke-ctm-1-sysdig2-35e99c16-qwds" with: GCE persistent disk not found: diskName="my-data-disk" zone="us-central1-a"。
最後一條信息至關清楚了:爲了解決這個問題,咱們須要在 GKE 的 us-central1-a 區中建立一個名爲 my-data-disk 的持久化卷。一旦這個磁盤建立完成,controller-manager 將掛載這塊磁盤,並啓動容器建立過程。
看着整個 build-test-deploy 任務到了 deploy 步驟卻失敗了,緣由竟是 Kubernetes 對象不合法。還有什麼比這更讓人沮喪的!
你可能以前也碰到過這種錯誤:
$ kubectl create -f test-application.deploy.yaml error: error validating "test-application.deploy.yaml": error validating data: found invalid field resources for v1.PodSpec; if you choose to ignore these errors, turn validation off with --validate=false |
在這個例子中,我嘗試建立如下 deployment:
# test-application.deploy.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: test-app spec: template: metadata: labels: app: test-app spec: containers: - image: nginx name: nginx resources: limits: cpu: 100m memory: 200Mi requests: cpu: 100m memory: 100Mi |
一眼望去,這個 YAML 文件是正確的,但錯誤消息會證實是有用的。錯誤說的是 found invalid field resources for v1.PodSpec,再仔細看一下 v1.PodSpec, 咱們能夠看到 resource 對象變成了 v1.PodSpec 的一個子對象。事實上它應該是 v1.Container 的子對象。在把 resource 對象縮進一層後,這個 deployment 對象就能夠正常工做了。
除了查找縮進錯誤,另外一個常見的錯誤是寫錯了對象名(好比 peristentVolumeClaim 寫成了 persistentVolumeClaim)。這個錯誤曾經在咱們時間很趕的時候絆住了我和另外一位高級工程師。
爲了能在早期就發現這些錯誤,我推薦在 pre-commit 鉤子或者構建的測試階段添加一些校驗步驟。
例如,你能夠:
用 python -c 'import yaml,sys;yaml.safe_load(sys.stdin)' < test-application.deployment.yaml 驗證 YAML 格式
使用標識 --dry-run 來驗證 Kubernetes API 對象,好比這樣:kubectl create -f test-application.deploy.yaml --dry-run --validate=true
重要提醒:校驗 Kubernetes 對象的機制是在服務端的校驗,這意味着 kubectl 必須有一個在工做的 Kubernetes 集羣與之通訊。不幸的是,當前 kubectl 尚未客戶端的校驗選項,可是已經有 issue(kubernetes/kubernetes #29410 和 kubernetes/kubernetes #11488)在跟蹤這個缺失的特性了。
我瞭解的在使用 Kubernetes 的大多數人都碰到過這個問題,它也確實是一個難題。
這個場景就像下面這樣:
使用一個鏡像 tag(好比:rosskulinski/myapplication:v1) 建立一個 deployment
注意到 myapplication 鏡像中存在一個 bug
構建了一個新的鏡像,並推送到了相同的 tag(rosskukulinski/myapplication:v1)
刪除了全部 myapplication 的 pods,新的實例被 deployment 建立出了
發現 bug 仍然存在
重複 3-5 步直到你抓狂爲止
這個問題關係到 Kubernetes 在啓動 pod 內的容器時是如何決策是否作 docker pull 動做的。
在 v1.Container 說明中,有一個選項 ImagePullPolicy:
Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
由於咱們把咱們的鏡像 tag 標記爲 :v1,默認的鏡像拉取策略是 IfNotPresent。Kubelet 在本地已經有一份 rosskukulinski/myapplication:v1 的拷貝了,所以它就不會在作 docker pull 動做了。當新的 pod 出現的時候,它仍然使用了老的有問題的鏡像。
有三個方法來解決這個問題:
切成 :latest tag(千萬不要這麼作!)
deployment 中指定 ImagePullPolicy: Always
使用惟一的 tag(好比基於你的代碼版本控制器的 commit id)