kind (Kubernetes IN Docker)クラスタでローカルdockerイメージ利用する際にちょっとハマってしまった。 結論からいうと、imagePullPolicyの理解が足りていなかっただけなのだが、同じ様にハマってしまう人も多いのではないかと思うので、設定・確認につかったコマンドを含めて記録として残しておく。
kindクラスタの作成
まずは、1 x control planeノードと2 x workerノードなkindクラスタを作成したいので、次のようなkindクラスタ構成ファイルを作成する
cat << EOF | > kind-cluster.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
EOF
続いて、kind cliコマンドで、k8s v1.19.11のノードイメージと上記で作成したkind構成ファイルを指定してkindクラスタを作成する
K8S_NODE_IMAGE=v1.19.11
kind create cluster --name my-kind-cluster \
--image=kindest/node:${K8S_NODE_IMAGE} \
--config kind-cluster.yaml
作成されたkindクラスタを確認する
# kind クラスタ一覧を表示
kind get clusters
> my-kind-cluster
# kubectlでノード一覧を表示
kubectl get nodes
NAME STATUS ROLES AGE VERSION
my-kind-cluster-control-plane Ready master 3m32s v1.19.11
my-kind-cluster-worker Ready <none> 2m59s v1.19.11
my-kind-cluster-worker2 Ready <none> 2m59s v1.19.11
サンプルアプリ用イメージの作成
サンプルイメージ用のDockerfileを作成
cat << EOF | > Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
EOF
作成したDockerfileでサンプルイメージをビルド
docker build -t sample-app .
sample-app:latest
が作成されていることを確認
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
sample-app latest e31ba19f294d 23 minutes ago 22.9MB
latestタグの付いたdockerイメージをkindクラスタノードにロード
kind load docker-image
コマンドで作成したサンプルイメージをkindクラスタノードにロードする。
kind load docker-image sample-app:latest --name my-kind-cluster
Image: "sample-app:latest" with ID "sha256:e31ba19f294d720f3d5dfeaa421d6ca84a25fa0f324824df7608370a67c8ead6" not yet present on node "my-kind-cluster-worker2", loading...
Image: "sample-app:latest" with ID "sha256:e31ba19f294d720f3d5dfeaa421d6ca84a25fa0f324824df7608370a67c8ead6" not yet present on node "my-kind-cluster-control-plane", loading...
Image: "sample-app:latest" with ID "sha256:e31ba19f294d720f3d5dfeaa421d6ca84a25fa0f324824df7608370a67c8ead6" not yet present on node "my-kind-cluster-worker", loading...
上記出力内容からcontrol planeノードとworkerノード x 2 全てにイメージがロードされたことが分かる。
さらに、各ノードにロードされているイメージ一覧を確認してみる。これは各ノード用に立ち上がったdockerコンテナ中にロードされたイメージ一覧を見ればよい。
まずは、次のコマンドで各ノード用に立ち上がったdockerコンテナ一覧を確認。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
87e9f413cb38 kindest/node:v1.19.11 "/usr/local/bin/entr…" 28 minutes ago Up 28 minutes my-kind-cluster-worker2
3140c46409fc kindest/node:v1.19.11 "/usr/local/bin/entr…" 28 minutes ago Up 28 minutes 127.0.0.1:60149->6443/tcp my-kind-cluster-control-plane
ef0eba3986d9 kindest/node:v1.19.11 "/usr/local/bin/entr…" 28 minutes ago Up 28 minutes my-kind-cluster-worker
それでは、この中のmy-kind-cluster-worker
という名前のworkerノード用コンテナにロードされたイメージ一覧を確認する。
kindのノード内部ではコンテナランタイムにdockerdではなくCRI互換のcontainerdを使っている。 よって、デーモンとのやり取りにはdockerコマンドではなくcrictl (Container Runtime Interface CLI)を利用する。 以下のようにmy-kind-cluster-worker
コンテナ内のイメージ一覧をcrictlコマンドで取得する。
docker exec -it my-kind-cluster-worker crictl images
IMAGE TAG IMAGE ID SIZE
docker.io/kindest/kindnetd v20210326-1e038dc5 6de166512aa22 54MB
docker.io/library/sample-app latest e31ba19f294d7 24.5MB
docker.io/rancher/local-path-provisioner v0.0.14 e422121c9c5f9 13.4MB
k8s.gcr.io/build-image/debian-base v2.1.0 c7c6c86897b63 21.1MB
k8s.gcr.io/coredns 1.7.0 bfe3a36ebd252 14MB
k8s.gcr.io/etcd 3.4.13-0 0369cf4303ffd 86.7MB
k8s.gcr.io/kube-apiserver v1.19.11 2fb26b7ebd23a 120MB
k8s.gcr.io/kube-controller-manager v1.19.11 1af1564e1890d 112MB
k8s.gcr.io/kube-proxy v1.19.11 d2c2a8b355f56 120MB
k8s.gcr.io/kube-scheduler v1.19.11 120d339f7d67a 47.7MB
k8s.gcr.io/pause 3.5 ed210e3e4a5ba 301kB
sample-app:latestがworkerノードにロードされていることが確認できる。
サンプルイメージ(sample-app:latest)のpullに失敗
それではサンプルイメージ(sample-app:latest)を指定したKubernetes deploymentリソースを作成して、kindクラスタにデプロイしてみる。
cat << EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-app
spec:
selector:
matchLabels:
app: sample-app
replicas: 1
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: sample-app
image: sample-app:latest
ports:
- containerPort: 80
EOF
kubectl apply -f deployment.yaml
すると、ImagePullBackOffで、起動に失敗する。
kubectl get pod
NAMESPACE NAME READY STATUS RESTARTS AGE
default sample-app-6cb94bb4b8-8xvsr 0/1 ImagePullBackOff 0 88s
kubectt describeでEvents情報を参照すると、どうやらworkerノードのローカルにsample-app:latest
がロードされているにもかかわらず、リモート(docker.io/library/sample-app:latest
)イメージをpullしようとして失敗していたことが分かる。
kubectl describe pod sample-app-6cb94bb4b8-8xvsr
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 8m50s default-scheduler Successfully assigned default/sample-app-6cb94bb4b8-8xvsr to my-kind-cluster-worker2
Normal Pulling 7m15s (x4 over 8m50s) kubelet Pulling image "sample-app:latest"
Warning Failed 7m13s (x4 over 8m48s) kubelet Failed to pull image "sample-app:latest": rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/library/sample-app:latest": failed to resolve reference "docker.io/library/sample-app:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
Warning Failed 7m13s (x4 over 8m48s) kubelet Error: ErrImagePull
Normal BackOff 6m47s (x7 over 8m47s) kubelet Back-off pulling image "sample-app:latest"
Warning Failed 3m47s (x20 over 8m47s) kubelet Error: ImagePullBackOff
imagePullPolicyについて
行き詰まってしまったのでググってみると、kind公式ドキュメントにしっかりとNOTEが書いてあった。
雑にサマると、これはそもそもimagePullPolicyの動作仕様の話であり、latestイメージタグを使わないか、imagePullPolicyをIfNotPresent
かNever
に設定せよということである。
-
イメージタグがlatestもしくは省略されている時(デフォルトでlatest)の場合はimagePullPolicyは
Alway
として認識される。つまり、毎回kubeletはコンテナレジストリに同じ名前のイメージを問い合わせる(ただ、同一イメージダイジェストがキャッシュされていればそのローカルキャッシュを使用する) -
イメージタグがlatestではない場合はimagePullPolicyは
IfNotPresent
として認識される。つまり、ローカルに無いときにのみコンテナレジストリに同じ名前のイメージを問い合わせる
ということで、ここではlatest
タグを使うことをやめ、すなわちIfNotPresent
なimagePullPolicyとして設定されるようにして、Workerノードのローカルイメージが使われるようにした。
新しいタグのサンプルイメージをデプロイ
まずは、さきほどロードしたsample-app:latestイメージを消しておく(別にこれはやる必要はないけど、不要なものは消しておきたい性格なので)
docker exec -it my-kind-cluster-worker crictl images
IMAGE TAG IMAGE ID SIZE
docker.io/kindest/kindnetd v20210326-1e038dc5 6de166512aa22 54MB
docker.io/library/sample-app latest e31ba19f294d7 24.5MB
docker exec -it my-kind-cluster-worker crictl rmi e31ba19f294d7
Deleted: docker.io/library/sample-app:latest
つぎに、新しいタグ(ここでは0.0.1)を付与したサンプルイメージをkindクラスターにロードする。
# sample-appに0.0.1タグを付与
docker tag sample-app sample-app:0.0.1
# sample-app:0.0.1イメージをkindクラスタにロード
kind load docker-image sample-app:0.0.1 --name my-kind-cluster
さきほどと同じようにmy-kind-cluster-workerコンテナ内のイメージ一覧をcrictlコマンドで取得して、workerノードにsample-app:0.0.1がロードされたことを確認する。
docker exec -it my-kind-cluster-worker crictl images
IMAGE TAG IMAGE ID SIZE
docker.io/kindest/kindnetd v20210326-1e038dc5 6de166512aa22 54MB
docker.io/library/sample-app 0.0.1 d2c2a8b355f56 24.5MB
それではさきほど作成したKubernetes deployment(sample-app)にサンプルイメージ (sample-app:0.0.1
)を指定してみる
kubectl set image deployment sample-app sample-app=sample-app:0.0.1
ところが、 latestタグでないにも関わらずさきほどと同じ用にImagePullBackOffで起動に失敗する。
kubectl describe pod sample-app-856987d994-ws7kh
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 93s default-scheduler Successfully assigned default/sample-app-856987d994-ws7kh to my-kind-cluster-worker
Normal Pulling 45s (x3 over 91s) kubelet Pulling image "sample-app:0.0.1"
Warning Failed 44s (x3 over 89s) kubelet Failed to pull image "sample-app:0.0.1": rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/library/sample-app:0.0.1": failed to resolve reference "docker.io/library/sample-app:0.0.1": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
Warning Failed 44s (x3 over 89s) kubelet Error: ErrImagePull
Normal BackOff 6s (x5 over 89s) kubelet Back-off pulling image "sample-app:0.0.1"
Warning Failed 6s (x5 over 89s) kubelet Error: ImagePullBackOff
どうしたものかと思いdeploymentの内容を確認してみると、最初のデプロイで作成されたdeploymentのpodテンプレートで、imagePullPolicyがAlwaysのままだったという残念なミスだった。
spec:
...
template:
...
spec:
containers:
- image: sample-app:0.0.1
imagePullPolicy: Always # <<<< ここ
そこで、imagePullPolicyをIfNotPresent
にして更新してみたところ無事ローカルイメージが使われ、次のようにサンプルイメージの起動が成功した。
kubectl get pod
NAMESPACE NAME READY STATUS RESTARTS AGE
default sample-app-58fdfdbc4d-4bnwf 1/1 Running 0 1m
なお、これは単に私がkubectl set image deploymentでイメージのみを更新してしまったことが原因であり、次のように新しくdeploymentから作り直せばimagePullPolicyはIfNotPresentとして設定されるのでこのような問題は起こらない。
kubectl delete -f deployment.yaml
cat << EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-app
spec:
selector:
matchLabels:
app: sample-app
replicas: 1
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: sample-app
image: sample-app:0.0.1
ports:
- containerPort: 80
EOF
kubectl apply -f deployment.yaml
結論
ここでは kindクラスタへのデプロイメントを題材としているが、 結局のところkindは関係なく、imagePullPolicyを理解していれば回避できた問題だった。 latestタグなイメージにおけるデフォルトのimagePullPolicyはAlwaysということで。