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 領域のことスッカリ忘れてた。
おしまい