LoginSignup
7
6

More than 5 years have passed since last update.

GKE で Pod 起動時に no space left on device エラー

Last updated at Posted at 2016-09-05

GKE が便利すぎて初歩的なミスにハマった件のメモ書きです。

リリース作業でいつも通りローリングアップデートをしていたら特定のノードに割り当てられた Pod が立ち上がらなくなりました。問題が生じた Pod を調べてみると

$ kubectl describe pod app-rc-20160824103923-yagec
Name:        app-rc-20160824103923-yagec
Namespace:   default
Node:        gke-app-f-default-pool-a5959866-l50l/10.240.0.21
Start Time:  Wed, 24 Aug 2016 10:40:18 +0900
Labels:      name=app-pods,version=20160824103923
Status:      Pending
IP:          10.244.1.5
Controllers: ReplicationController/app-rc-20160824103923
Containers:
  node-server:
    Container ID:
    Image:         asia.gcr.io/<project-id>/<image-name>:5.3.7
    Image ID:
    Ports:         3000/TCP, 3001/TCP
    QoS Tier:
      cpu:         Burstable
      memory:      BestEffort
    Requests:
      cpu:         100m
    State:         Waiting
      Reason:      ImagePullBackOff
    Ready:         False
    Restart Count: 0

  ...SKIP...

Conditions:
  Type        Status
  Ready       False
Volumes:
  default-token-2c258:
    Type:    Secret (a volume populated by a Secret)
    SecretName:    default-token-2c258
Events:
  Type        Reason        Message
  --------    ------        -------
  Normal      Scheduled     Successfully assigned app-rc-20160824103923-yagec to gke-app-f-default-pool-a5959866-l50l
  Warning     Failed        Failed to pull image "asia.gcr.io/<project-id>/<image-name>:5.3.7": Error pulling image (5.3.7) from asia.gcr.io/<project-id>/<image-name>, Untar re-exec error: exit status 1: output: open /hoge/fuga/sample.js: no space left on device
  Normal      Created       Created container with docker id 254072a475ee
  Normal      Started       Started container with docker id 254072a475ee
  Warning     FailedSync    Error syncing pod, skipping: failed to "StartContainer" for "node-server" with ErrImagePull: "Error pulling image (5.3.7) from asia.gcr.io/<project-id>/<image-name>, Untar re-exec error: exit status 1: output: open /hoge/fuga/sample.js: no space left on device"

のように「no space left on device」エラーが生じていました。該当ノードのディスク状況を見てみると

user@gke-app-f-default-pool-a5959866-l50l:~$ sudo df -h
Filesystem      Size  Used Avail Use% Mounted on
rootfs           99G   41G   54G  44% /
udev             10M     0   10M   0% /dev
tmpfs           356M  328K  356M   1% /run
/dev/sda1        99G   41G   54G  44% /
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           712M  1.6M  711M   1% /run/shm
cgroup          1.8G     0  1.8G   0% /sys/fs/cgroup
/dev/sda1        99G   41G   54G  44% /var/lib/docker/aufs

まだまだ余裕な感じ... と思ったら inode 領域を見てみると

user@gke-app-f-default-pool-a5959866-l50l:~$ sudo df -i
Filesystem      Inodes   IUsed  IFree IUse% Mounted on
rootfs         6553600 6493651  59949  100% /
udev            455023     280 454743    1% /dev
tmpfs           455630     226 455404    1% /run
/dev/sda1      6553600 6493651  59949  100% /
tmpfs           455630       3 455627    1% /run/lock
tmpfs           455630      14 455616    1% /run/shm
cgroup          455630      11 455619    1% /sys/fs/cgroup
/dev/sda1      6553600 6493651  59949  100% /var/lib/docker/aufs

でたー、inode 領域フルー。どこが使用しているのか調べてみると

user@gke-app-f-default-pool-a5959866-l50l:/$ for DIR in $(sudo ls -lat | grep "^d" | grep -v "\." | awk '{print $9}'); do echo "$(sudo find ./${DIR} -true | wc -l) $(pwd)/${DIR}"; done | sort -n
1 //lost+found
1 //media
1 //mnt
1 //selinux
1 //tmp
2 //lib64
4 //opt
44 //home
97 //bin
145 //sbin
213 //srv
231 //boot
267 //run
286 //dev
1334 //etc
4271 //lib
8775 //root
13668 //sys
26493 //usr
87879 //proc
6940816 //var

ログファイルでも溜まっているのか...?

root@gke-app-f-default-pool-a5959866-l50l:/var/lib/docker/aufs# for DIR in $(ls -lat | grep "^d" | grep -v "\." | awk '{print $9}'); do echo "$(find ./${DIR} -true | wc -l) $(pwd)/${DIR}"; done | sort -n
671 /var/lib/docker/aufs/layers
598975 /var/lib/docker/aufs/mnt
6335092 /var/lib/docker/aufs/diff

どうやら /var/lib/docker/aufs/diff 配下が食べ尽くしている。このディレクトリには Docker イメージの差分ファイルが蓄積されている。確かにローリングアップデートを繰り返していた環境で不要となったイメージを消してなかった... これは GKE の管轄外で利用者側が気にしてやらないといけない。

原因判明したけど GKE の各ホストに一括でコマンドを投げるためには Ansible...? ノード台数の変動やクラスタの再構築でいちいち変わるのも面倒だなー、と考えた結果、泥臭く「現在稼働しているノードを取得して、それぞれで不要となったイメージを削除する」シェルスクリプトになりました。

#!/bin/sh

#----------------------------
# config
#----------------------------

QUIET=true
MAX_IMAGES_NUM=20

GCR_DOMAIN=xxxxxxxxxx
PROJECT_ID=yyyyyyyyyy
IMAGE_NAME=zzzzzzzzzz

#----------------------------
# cleanup old dcoker images
#----------------------------

#
function log() {
  /bin/echo "$@"
}

#
function log_n() {
  /bin/echo -n "$@"
}

#
log "Cleanup to old docker images into GKE nodes"
for GKE_NODE in $(kubectl get node | grep '^gke' | awk '{print $1}');
do

  #
  CURRENT_IMAGES=$(gcloud compute ssh ${GKE_NODE} "sudo docker images | grep ^${GCR_DOMAIN}/${PROJECT_ID}/${IMAGE_NAME}")
  CURRENT_IMAGES_NUM=$(log "${CURRENT_IMAGES}" | wc -l | tr -d ' ')
  REMOVE_IMAGES_NUM=$(expr ${CURRENT_IMAGES_NUM} - ${MAX_IMAGES_NUM})

  # status
  log "=================================================================="
  log "Cleanup old docker images into GKE node"
  log ""
  log "GKE_NODE=${GKE_NODE}"
  log "MAX_IMAGES_NUM=${MAX_IMAGES_NUM}"
  log "CURRENT_IMAGES_NUM=${CURRENT_IMAGES_NUM}"
  log "=================================================================="
  log ""

  # current
  log "Current docker images:"
  if [ ${CURRENT_IMAGES_NUM} -gt 0 ] ; then
    for CURRENT_IMAGE_NAME in $(log "${CURRENT_IMAGES}" | awk '{print sprintf("%s:%s", $1, $2)}') ;
    do
      log ${CURRENT_IMAGE_NAME}
    done
  fi
  log ""

  # cleanup
  log "Remove old docker images:"
  if [ ${REMOVE_IMAGES_NUM} -gt 0 ] ; then
    for REMOVE_IMAGE_NAME in $(log "${CURRENT_IMAGES}" | tail -n ${REMOVE_IMAGES_NUM} | awk '{print sprintf("%s:%s", $1, $2)}') ;
    do
      log "==> remove : ${REMOVE_IMAGE_NAME}"
      if [ x"${QUIET}" != x"true" ] ; then
        log_n "are you ok [y/N] ? "
        read ANSWER
        if [ "${ANSWER}" != "y" ] ; then
          log "cannceled"
          exit 1
        fi
      fi
      gcloud compute ssh ${GKE_NODE} "sudo docker rmi ${REMOVE_IMAGE_NAME}"
  done
  fi
  log ""

done

こんな感じのスクリプトをローリングアップデート時に実行するように仕込んで、各ノードの inode 領域を解放することにしました。

まとめると inode 領域を喰い尽くさないように不要となった Docker イメージを削除しよう。GKE 各ノードのディスク容量監視に加えて inode 領域監視も入れようって話でした。GKE が悪いというより便利すぎて inode 領域のことスッカリ忘れてた。

おしまい

7
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6