Kubernetes:新しいイメージでデプロイできない問題

新しいイメージをK8sにデプロイできない

「新しいイメージをDockerレジストリにpushしたのに、Kubernetesでデプロイできない」 と、同僚のDanさん(仮名)からHELP!のslackメッセージが飛んできました。

あ~自分もぶち当たった問題だなぁ、どれどれ…と思い内容を読むと、色々な問題や、勘違いに出くわしていることに気が付きました。
私自身も「これが原因だったのか!」と改めて知れたことがあったので記録しておこうと思います。

「pullは、毎回リポジトリから取得してるんだよね?」

⇒いいえ、設定によります!

Kuberenetesに触れる前に、Dockerで ①レジストリにPushする方法 ②リポジトリからPullした時の動作 をおさらいしておきます。

1.Docker イメージをレジストリにPushする方法

①操作している環境にあるイメージの一覧を表示する

以下のコマンドを入力します。

$ docker images  
REPOSITORY           TAG          IMAGE ID            CREATED             VIRTUAL SIZE
docker-whale         latest       7d9495d03763        38 minutes ago      273.7 MB

こんな風に出力した、とします。(docs.docker.jpより拝借)
※以下を同じように手元で行う場合は、docker-whaleイメージをPullしてください。  

$ docker image pull maryatdocker/docker-whale

docker-whaleイメージのイメージIDは 7d9495d03763 リポジトリ名は docker-whale であることがわかります。
このイメージをレジストリにアップロード(Push)するには、Pushしたいイメージに
・どのレジストリに ・どんな名前で ・何バージョンとしてアップロードするか
の情報を持たせてあげる必要があります。
言うなればイメージの住所と名前、年齢みたいなものでしょうか?

②Pushしたいイメージのイメージ名をフォーマット

以下のコマンドを入力します。

$ docker tag イメージID Dockerレジストリのホスト名/名前空間(namespace)/リポジトリ:タグ  

DockerHubの場合、フォーマット部分は dockerhub.io/アカウント名/リポジトリ名:タグレジストリのホスト名は省略可能

当方Azure使いなので、ACRの場合は レジストリ名.azurecr.io/リポジトリ名:タグ となります。

名前空間/イメージ名のところは(正確には違いますが)「ディレクトリ・フォルダ」/「ファイル名」みたいな印象を受けました。
Danさんのレジストリは、名前空間部でバージョン分けしているような構成(その代わりタグがどれもv1)になっていました。

レジストリにログイン

DockerHubの場合

$ docker login --username=ユーザ名 --email=メールアドレス

ACRの場合 Azureにログイン

$ az login

ACRに接続

$ acr login --name レジストリ名

④Docker Imageを Push

$ docker push フォーマットしたイメージ名

ここまでで、レジストリにDockerイメージを配置できたかと思います。

2.リポジトリからPullした時の動作

①ローカルマシン上のイメージを確認する

以下のコマンドを入力します。

$ docker images  
REPOSITORY                  TAG       IMAGE ID        CREATED          VIRTUAL SIZE
testregi.azurecr.io/default/whale   v1    7d9495d03763    5 minutes ago    273.7 MB
docker-whale                latest    7d9495d03763    2 hours ago      273.7 MB

先ほどタグ付けしたイメージがあることを確認できます。

②Docker リポジトリからPullする

試しに先ほどPushしたDockerイメージをPullします。
が、いったんローカルホストにあるイメージに影響されないよう、今回使ったイメージを削除しておきます。 docker rmi コマンドで、イメージを削除します。

$ docker rmi -f 7d9495d03763
$ docker rmi -f docker-whale

イメージIDまたはイメージ名を指定し、削除できました。

docker runコマンドで、Dockerコンテナを起動します。

$ docker run testregi.azurecr.io/default/whale
Unable to find image 'testregi.azurecr.io/default/whale' locally
latest: Pulling from testregi.azurecr.io/default/whale
e190868d63f8: Pull complete
8eed3712d2cf: Download complete ...  
_______________________________________
/ What a computer is to me is the most   \
| remarkable tool that we have ever come |
| up with. It's the equivalent of a      |
| bicycle for our minds.                 |
|                                        |
\ -- Steve Jobs (1955-2011)              /
 --------------------------------------
\
 \
  \
                ##        .
          ## ## ##       ==
       ## ## ## ##      ===
    /""""""""""""""""___/ ===
~~~/~~ ~~~~ ~~~ ~~~~ ~~ ~___/ ====- ~~~
   \______ o          __/
     \    \        __/
      \____\______/

Jobsのお言葉が現れました。

Unable to find image ~ locally:イメージがローカルホスト上にないため、latest: Pulling from ~ イメージが~からダウンロードされたことがわかります。

では試しに、もう一度同じコマンドを実行してみます。

$ docker run testregi.azurecr.io/default/whale
_______________________________________
/ Well, we'll really have a party, but  \
| we've gotta post a guard outside.     |
|                                       |
\ -- Eddie Cochran, "Come On Everybody" /
 ---------------------------------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ /~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
       \______ o          __/
         \    \        __/
          \____\______/

今回はダウンロードする動作や処理の宣言がなく、1回目よりも高速に実行されました(吹き出しの言葉が毎回変わることに今更気づいた)。 つまりローカルホスト上にあるDockerイメージを使って実行したのです。

Dockerはコンテナを実行する際、以下の順で動作します。

​ ①ローカルホスト上のリポジトリに指定したDockerイメージがあるか確認し、あればそれを使う ​ ②なければ、指定したコンテナレジストリからDockerイメージをダウンロードし実行する

そう!これが言いたくて、ここまでDockerについて書き続けてきたんです! 道のりが長い…

3.KubernetesでPodをデプロイしたときの動作

始めのDanさんの疑問に戻ります。 「KubernetesでPodデプロイした時、Dockerコンテナは毎回必ずリポジトリから取得してるんだよね?」

答: Kubernetesでも、上記と同様の動作をします(設定によりますが)。

「新しいイメージをレジストリにpushしたのに、Kubernetesでデプロイできない」という問題、実はKubernetesのNodeが持つDockerイメージが影響していました。

Nodeとは(Kubernetesチュートリアルより): Kubernetesにおけるワーカーマシンのこと。Nodeはマスターによって管理され、複数のPodを持つことができる。 そして少なくとも以下が動作します。

  • Kubelet: KubernetesマスターとNode間の通信を担当するプロセス。マシン上で実行されているPodとコンテナを管理します。

  • レジストリからコンテナイメージを取得し、コンテナを解凍し、アプリケーションを実行することを担当する、Docker、rktのようなコンテナランタイム。

つまりKubernetesのNodeでもNode上にあるDockerイメージを利用するため、一見リポジトリのDockerイメージが使えないような動作をしていたんです。

①ノードの一覧を表示する

以下のコマンドを実行します。

$ kubectl get nodes
NAME      LABELS                          STATUS
node01    kubernetes.io/hostname=node01   Ready

②指定したノードの詳細情報をYAML形式で表示する

以下のコマンドを実行します。

$ kubectl get nodes node1 -o yaml

指定したノードに関する大量の情報が出力されます。 今回はノードに保持されているイメージの存在を確認したいので、status.images部分を確認してみます。

status:
    images:
    - names:
        - nginx@sha256:afdgsj452cja92jjgd7dksfjcasadkslawfoasvsak52dsamho
        - nginx:1.13
        sizeBytes: 108958610

いました。※省略しています

先ほどの(長ったらしい)Dockerイメージの取得について振り返ります。

①ローカルホスト上のリポジトリに指定したDockerイメージがあるか確認し、あればそれを使う ②なければ、指定したコンテナレジストリからDockerイメージをダウンロードし実行する

Kubernetesのノードも、どうやらこれと同じことをしてくれるみたいなのです。

https://kubernetes.io/docs/concepts/containers/images/

Using a Private Registryの説明でこのような記載がありました。

Pre-pulling Images

  • all pods can use any images cached on a node

またUpdating Imagesの説明で、デフォルトのPullに関するポリシーはIfNotPresentであり、「既にイメージが存在する場合KubeletはPullしない」とのことです。

何が何でもいつもリポジトリからPullしてほしい場合は

以下のいずれかを満たせばいつも・強制的にPullしてくれます。

  • set the imagePullPolicy of the container to Always.
  • omit the imagePullPolicy and use :latest as the tag for the image to use.
  • omit the imagePullPolicy and the tag for the image to use.
  • enable the AlwaysPullImages admission controller.

・spec.containers[n].imagePullPolicyをAlwaysにする

・imagePullPolicyを省略し、latestタグを使用する

・imagePullPolicyとタグを省略する

・アドミッションコントローラでAlwaysPullImagesを有効にする

おわりに

基本的にイメージは、保守性の観点からもバージョン管理(タグ管理)するべきものだと考えています(latest極力使わない、とフォロワーさんからも教わりました)。

今回のケースは、そもそもタグ付けの記載方法が誤っていたことと、Kubernetesの仕様が相まって起こった現象でした。

同僚のDanさんには、現象と原因についてお伝えしました。

…あー長くなってしまったーおしまい!