Kubernetes 部署失敗的 10 個最廣泛緣由

1. 錯誤的容器鏡像/非法的倉庫權限

其中兩個最廣泛的問題是:(a)指定了錯誤的容器鏡像,(b)使用私有鏡像卻不提供倉庫認證信息。這在首次使用 Kubernetes 或者綁定 CI/CD 環境時尤爲棘手。

讓咱們看個例子。首先咱們建立一個名爲 fail 的 deployment,它指向一個不存在的 Docker 鏡像:vue

$ kubectl run fail --image=rosskukulinski/dne:v1.0.0


而後咱們查看 Pods,能夠看到有一個狀態爲 ErrImagePull 或者 ImagePullBackOff 的 Pod:nginx

$ kubectl get pods
NAME                    READY     STATUS             RESTARTS   AGE
fail-1036623984-hxoas   0/1       ImagePullBackOff   0          2m


想查看更多信息,能夠 describe 這個失敗的 Pod:docker

$ kubectl describe pod fail-1036623984-hxoas


查看 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。api

  • 若是這成功了,那麼極可能 Kubernetes 沒有權限去拉取這個鏡像。參考鏡像拉取 Secrets 來解決這個問題。
  • 若是失敗了,那麼我會繼續用不顯式帶 tag 的鏡像測試 - docker pull rosskukulinski/dne - 這會嘗試拉取 tag 爲 latest 的鏡像。若是這樣成功,代表原來指定的 tag 不存在。這多是人爲緣由,拼寫錯誤,或者 CI/CD 的配置錯誤。


若是 docker pull rosskukulinski/dne(不指定 tag)也失敗了,那麼咱們碰到了一個更大的問題:咱們全部的鏡像倉庫中都沒有這個鏡像。默認狀況下,Kubernetes 使用 Dockerhub 鏡像倉庫,若是你在使用 Quay.ioAWS ECR,或者 Google Container Registry,你要在鏡像地址中指定這個倉庫的 URL,好比使用 Quay,鏡像地址就變成 quay.io/rosskukulinski/dne:v1.0.0。

若是你在使用 Dockerhub,那你應該再次確認你發佈鏡像到 Dockerhub 的系統,確保名字和 tag 匹配你的 deployment 正在使用的鏡像。

注意:觀察 Pod 狀態的時候,鏡像缺失和倉庫權限不正確是無法區分的。其它狀況下,Kubernetes 將報告一個 ErrImagePull 狀態。安全

2. 應用啓動以後又掛掉

不管你是在 Kubernetes 上啓動新應用,仍是遷移應用到已存在的平臺,應用在啓動以後就掛掉都是一個比較常見的現象。

咱們建立一個 deployment,它的應用會在1秒後掛掉:網絡

$ kubectl run crasher --image=rosskukulinski/crashing-app


咱們看一下 Pods 的狀態:app

$ kubectl get pods
NAME                       READY     STATUS             RESTARTS   AGE
crasher-2443551393-vuehs   0/1       CrashLoopBackOff   2          54s



CrashLoopBackOff 告訴咱們,Kubernetes 正在盡力啓動這個 Pod,可是一個或多個容器已經掛了,或者正被刪除。

讓咱們 describe 這個 Pod 去獲取更多信息:tcp

$ 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看到應用日誌:

$ kubectl logs crasher-2443551393-vuehs


不幸的是,這個 Pod 沒有任何日誌。這多是由於咱們正在查看一個新起的應用實例,所以咱們應該查看前一個容器:

$ kubectl logs crasher-2443551393-vuehs --previous


什麼!咱們的應用仍然不給咱們任何東西。這個時候咱們應該給應用加點啓動日誌了,以幫助咱們定位這個問題。咱們也能夠本地運行一下這個容器,以肯定是否缺失環境變量或者掛載卷。

3. 缺失 ConfigMap 或者 Secret

Kubernetes 最佳實踐建議經過 ConfigMaps 或者 Secrets 傳遞應用的運行時配置。這些數據能夠包含數據庫認證信息,API endpoints,或者其它配置信息。

一個常見的錯誤是,建立的 deployment 中引用的 ConfigMaps 或者 Secrets 的屬性不存在,有時候甚至引用的 ConfigMaps 或者 Secrets 自己就不存在。

缺失 ConfigMap

第一個例子,咱們將嘗試建立一個 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會發生什麼呢?

缺失 Secrets

下面是一個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 建立完成,容器也將正確啓動。

4. 活躍度/就緒狀態探測失敗

在 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 正在殺這個容器,由於容器的活躍度探測失敗了。

這裏有三種可能性:

  1. 你的探測不正確,健康檢查的 URL 是否改變了?
  2. 你的探測太敏感了, 你的應用是否要過一會才能啓動或者響應?
  3. 你的應用永遠不會對探測作出正確響應,你的數據庫是否配置錯了


查看 Pod 日誌是一個開始調測的好地方。一旦你解決了這個問題,新的 deployment 應該就能成功了。

5. 超出CPU/內存的限制

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個選擇:

  1. 要求你的集羣管理員提高限額
  2. 減小 deployment 的請求或者限額設置
  3. 直接編輯限額

 

查看 Part 2

以上是 Kubernetes 部署失敗的前5個廣泛緣由。點擊這裏查看第二部分的後5個緣由。

相關文章
相關標籤/搜索