はじめに
自然言語処理とか画像認識の機械学習向けのKubernetes環境にPython(Anacondaから構築した環境)のコンテナを使用する場合があると思います。処理のためのモデルやデータを含めてコンテナ化することがあると思いますが、7GぐらいのコンテナになってしまうとKubernetesのPull時に必ずエラーになるので、AKS(Azure Kubernetes Service)を使って対処法を検証してみました。(エラーメッセージは出ますが最終的にはPull完了しますします)
エラーメッセージの対処法を考える
例えば以下の様なエラーメッセージ。
Failed to pull image "kekekekenta.azurecr.io/nlp-tools": rpc error: code = FailedPrecondition desc = unexpected EOF
kubernetesのgithubやstackoverflowを探しても色々な人が悩みを持っているようですね。それらをまとめると以下のような案がありました。
- Pullのタイムアウトを長くすることができるか?
- Pullの速度(ダウンロードと展開)を速くすることができるか?
- ネットワークは速いので展開時の時間を短くするために圧縮アルゴリズムを変更したい。
Pullのタイムアウトを長くすることができるか?
Pull時のタイムアウトは以下のURLにも書かれていますが、デフォルト設定で2分になっています。
https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/
--runtime-request-timeout duration
Timeout of all runtime requests except long running request - pull, logs, exec and attach. When timeout exceeded, kubelet will cancel the request, throw out an error and retry later. (default 2m0s)
AKSの実際のタイムアウトは何分に設定されているか不明ですが、Azure Container Service EngineのGithubでは以下のように書かれていました。
setKubeletOpts " --container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
どちらにしても、提供されているものを使っている限りはこの設定は変更できないので別の方法を探します。
Pullの速度(ダウンロードと展開)を速くすることができるか?
Pullの速度は、コンテナイメージのダウンロードと、tar.gzされたコンテナイメージのレイヤーを展開する測度が関係してきますが、ACR(Azure Container Registry)からのダウンロードはほぼ気にならないぐらい速いので、展開する方を速くできないか考えてみます。Githubでも色々な案が出ていましたが、現段階での実装でなんとかならないかと思い、gzipの測度を圧縮レベルを変えて測定してみます。
試しに/usrをtarしたファイルを2つ用意します。
$ ls -la
-rw-rw-r-- 1 kenta kenta 1114408960 Jan 5 01:50 usr1.tar
-rw-rw-r-- 1 kenta kenta 1114408960 Jan 5 01:52 usr9.tar
gzipする時の時間は遅くても問題ないのですが、とりあえず計測してみます。 -1は圧縮率が低いけど高速。-9は圧縮率が高いけど低速です。
$ time gzip -1 usr1.tar
real 0m19.791s
user 0m19.091s
sys 0m0.696s
$ time gzip -9 usr9.tar
real 3m47.467s
user 3m46.716s
sys 0m0.747s
圧縮したファイルをtarで展開してみますが、そんなに変りませんね。。
$ time tar xfz usr1.tar.gz
real 0m21.931s
user 0m10.419s
sys 0m4.389s
$ time tar xfz usr9.tar.gz
real 0m20.804s
user 0m9.375s
sys 0m4.292s
別の方法を考えて見ます。
コンテナイメージのレイヤーを複数に分割する
コンテナイメージを作る際はイメージサイズを小さくするためにレイヤーを纏めた方が良いとされていますが、レイヤーを纏めると1つのレイヤーの展開に時間がかかります。また、並列ダウンロードもされません。また、今回はRPCからエラーがでている事もあり、もっとレスポンスの良いコンテナイメージにしてあげる必要があるかもしれません。
そこで、レイヤーを纏めたコンテナイメージと、複数のレイヤーに分けたコンテナイメージを作成して、Pull時の動作を確認することにより、KubernetesのPullに適したコンテナ構造を検証します。
コンテナイメージを用意
レイヤーを纏めたDockerfile(1つのRUNで纏める)を用意しました。色々インストールしています。
FROM continuumio/anaconda3
RUN buildDeps='make libc6-dev gcc g++' \
&& set -x \
&& echo 'Installing Mecab' \
&& apt-get update && apt-get install -y $buildDeps --no-install-recommends \
&& apt-get -y install mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8 \
&& mkdir -p `mecab-config --dicdir` \
&& git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git \
&& /mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -y \
&& export VAL=`mecab-config --dicdir`/mecab-ipadic-neologd; sed -i 's#^\(dicdir\s*=\s*\).*$#\1'$VAL'#' /etc/mecabrc \
&& echo 'Installing Tika' \
&& apt-get -y install openjdk-8-jre-headless tesseract-ocr tesseract-ocr-eng tesseract-ocr-jpn \
&& curl --output /tmp/tika-server.jar http://search.maven.org/remotecontent?filepath=org/apache/tika/tika-server/1.16/tika-server-1.16.jar \
&& curl --output /tmp/tika-server.jar.md5 http://search.maven.org/remotecontent?filepath=org/apache/tika/tika-server/1.16/tika-server-1.16.jar.md5 \
&& echo 'Installing Python' \
&& apt-get install -y swig \
&& conda update -y -n base conda \
&& conda install -y -c anaconda gensim requests nltk \
&& conda install -y -c conda-forge tika \
&& pip install tinysegmenter sumy mecab-python3 langdetect \
&& rm -rf /mecab-ipadic-neologdclone \
&& rm -rf /var/lib/apt/lists/*
複数のレイヤーに分割したDockerfile(複数のRUNで分割する)は以下の通り。
FROM continuumio/anaconda3
RUN buildDeps='make libc6-dev gcc g++' \
&& set -x \
&& echo 'Installing Mecab' \
&& apt-get update && apt-get install -y $buildDeps --no-install-recommends
RUN apt-get -y install mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8 \
&& mkdir -p `mecab-config --dicdir` \
&& git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
RUN /mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -y \
&& export VAL=`mecab-config --dicdir`/mecab-ipadic-neologd; sed -i 's#^\(dicdir\s*=\s*\).*$#\1'$VAL'#' /etc/mecabrc
RUN echo 'Installing Tika' \
&& apt-get -y install openjdk-8-jre-headless tesseract-ocr tesseract-ocr-eng tesseract-ocr-jpn \
&& curl --output /tmp/tika-server.jar http://search.maven.org/remotecontent?filepath=org/apache/tika/tika-server/1.16/tika-server-1.16.jar \
&& curl --output /tmp/tika-server.jar.md5 http://search.maven.org/remotecontent?filepath=org/apache/tika/tika-server/1.16/tika-server-1.16.jar.md5
RUN echo 'Installing Python' \
&& apt-get install -y swig \
&& conda update -y -n base conda \
&& conda install -y -c anaconda gensim requests nltk \
&& conda install -y -c conda-forge tika
RUN pip install tinysegmenter sumy mecab-python3 langdetect \
&& rm -rf /mecab-ipadic-neologdclone \
&& rm -rf /var/lib/apt/lists/*
ビルドします。
$ docker build -t kekekekenta.azurecr.io/nlp-tools nlp-tools
$ docker build -t kekekekenta.azurecr.io/layered-nlp-tools layered-nlp-tools
ビルド後のコンテナイメージサイズは以下の通り。どちらも同じぐらいのサイズ。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
kekekekenta.azurecr.io/nlp-tools latest 370bb5c6837e 4 hours ago 7.17GB
kekekekenta.azurecr.io/layered-nlp-tools latest cff610a7e062 4 hours ago 7.17GB
continuumio/anaconda3 latest 69acfdf1121f 41 hours ago 3.72GB
便利なdiveでレイヤーを確認。Anacondaのレイヤーは3.4G。大丈夫かな。。。
https://github.com/wagoodman/dive
$ dive kekekekenta.azurecr.io/nlp-tools
[● Layers]────────────────────────────────────────────────────────────────────── [Current Layer Contents]────────────────────────────────────────────────────────
Cmp Image ID Size Command Permission UID:GID Size Filetree
sha256:b28ef0b6fef80faa25 101 MB #(nop) ADD file:58d5c21fcabcf1eec94e8676a drwxr-xr-x 0:0 5.2 MB ├── bin
sha256:60b398413f86c0f8ac 231 MB apt-get update --fix-missing && apt-get i -rwxr-xr-x 0:0 1.1 MB │ ├── bash
sha256:b1f721b57648924150 3.4 GB wget --quiet https://repo.anaconda.com/ar -rwxr-xr-x 0:0 36 kB │ ├── cat
sha256:abd2c1804296f74fae 20 MB apt-get update --fix-missing && apt-get -rwxr-xr-x 0:0 64 kB │ ├── chgrp
sha256:aac6d3b7d1450a4434 3.4 GB FROM sha256:aac6d3b7d1450a4434 -rwxr-xr-x 0:0 60 kB │ ├── chmod
....
$ dive kekekekenta.azurecr.io/layered-nlp-tools
[● Layers]────────────────────────────────────────────────────────────────────── [Current Layer Contents]────────────────────────────────────────────────────────
Cmp Image ID Size Command Permission UID:GID Size Filetree
sha256:b28ef0b6fef80faa25 101 MB #(nop) ADD file:58d5c21fcabcf1eec94e8676a drwxr-xr-x 0:0 5.2 MB ├── bin
sha256:60b398413f86c0f8ac 231 MB apt-get update --fix-missing && apt-get i -rwxr-xr-x 0:0 1.1 MB │ ├── bash
sha256:b1f721b57648924150 3.4 GB wget --quiet https://repo.anaconda.com/ar -rwxr-xr-x 0:0 36 kB │ ├── cat
sha256:abd2c1804296f74fae 20 MB apt-get update --fix-missing && apt-get -rwxr-xr-x 0:0 64 kB │ ├── chgrp
sha256:546ef8f4ba85d10b69 154 MB buildDeps='make libc6-dev gcc g++' && -rwxr-xr-x 0:0 60 kB │ ├── chmod
sha256:0cabb03f966e749806 282 MB apt-get -y install mecab libmecab-dev mec -rwxr-xr-x 0:0 64 kB │ ├── chown
sha256:7abb783748c90e7e13 2.5 GB /mecab-ipadic-neologd/bin/install-mecab-i -rwxr-xr-x 0:0 130 kB │ ├── cp
sha256:30242b13e105ca7ba4 293 MB echo 'Installing Tika' && apt-get -y -rwxr-xr-x 0:0 117 kB │ ├── dash
sha256:752f8655a9f8c5b758 245 MB echo 'Installing Python' && apt-get i -rwxr-xr-x 0:0 105 kB │ ├── date
sha256:89e71c7f05b61effb0 5.9 MB FROM sha256:89e71c7f05b61effb0 -rwxr-xr-x 0:0 77 kB │ ├── dd
....
ACRに登録します。
$ az acr login -n kekekekenta
$ docker push kekekekenta.azurecr.io/nlp-tools
$ docker push kekekekenta.azurecr.io/layered-nlp-tools
初回のapply動作
AKSにapplyしてみます。(yamlファイルは省略しますがKubernetesにはJobとして展開しています。また、それぞれのコンテナで同じレイヤーを使っている箇所があるので、キャッシュされたレイヤーが使われないように異なるノードに展開します。)
レイヤーを纏めたコンテナイメージ
nlp-tools(レイヤーを纏めたコンテナイメージ)の初回Apply後の経過は以下の通り。エラーは出ますが最終的に約19分でJob動作完了。
$ kubectl apply -f nlp.yaml
job.batch/nlp created
$ kubectl get pods -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nlp-9thhn 0/1 ContainerCreating 0 14s <none> aks-agentpool-35064155-0 <none> <none>
nlp-9thhn 0/1 ErrImagePull 0 2m6s 10.244.1.12 aks-agentpool-35064155-0 <none> <none>
nlp-9thhn 0/1 ImagePullBackOff 0 2m22s 10.244.1.12 aks-agentpool-35064155-0 <none> <none>
nlp-9thhn 0/1 ContainerCreating 0 10m 10.244.1.12 aks-agentpool-35064155-0 <none> <none>
nlp-9thhn 0/1 Completed 0 19m 10.244.1.12 aks-agentpool-35064155-0 <none> <none>
$ kubectl describe pods nlp-9thhn
....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 33m default-scheduler Successfully assigned default/nlp-9thhn to aks-agentpool-35064155-0
Warning Failed 31m kubelet, aks-agentpool-35064155-0 Failed to pull image "kekekekenta.azurecr.io/nlp-tools": rpc error: code = FailedPrecondition desc = unexpected EOF
Warning Failed 31m kubelet, aks-agentpool-35064155-0 Error: ErrImagePull
Normal BackOff 31m kubelet, aks-agentpool-35064155-0 Back-off pulling image "kekekekenta.azurecr.io/nlp-tools"
Warning Failed 31m kubelet, aks-agentpool-35064155-0 Error: ImagePullBackOff
Normal Pulling 31m (x2 over 33m) kubelet, aks-agentpool-35064155-0 pulling image "kekekekenta.azurecr.io/nlp-tools"
Normal Pulling 22m kubelet, aks-agentpool-35064155-0 pulling image "kekekekenta.azurecr.io/nlp-tools"
Normal Pulled 14m kubelet, aks-agentpool-35064155-0 Successfully pulled image "kekekekenta.azurecr.io/nlp-tools"
Normal Created 14m kubelet, aks-agentpool-35064155-0 Created container
Normal Started 14m kubelet, aks-agentpool-35064155-0 Started container
複数レイヤーに分割したコンテナイメージ
layered-nlp-tools(複数レイヤーに分割したコンテナイメージ)の初回apply後の経過は以下の通り。エラーは出ずに約8分でJob動作完了。
$ kubectl apply -f layerednlp.yaml
job.batch/layerednlp created
$ kubectl get pods -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
layerednlp-ngkng 0/1 ContainerCreating 0 13s <none> aks-agentpool-35064155-2 <none> <none>
layerednlp-ngkng 0/1 Completed 0 8m17s 10.244.0.12 aks-agentpool-35064155-2 <none> <none>
$ kubectl describe pods layerednlp-ngkng
....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 11m default-scheduler Successfully assigned default/layerednlp-ngkng to aks-agentpool-35064155-2
Normal Pulling 11m kubelet, aks-agentpool-35064155-2 pulling image "kekekekenta.azurecr.io/layered-nlp-tools"
Normal Pulled 3m47s kubelet, aks-agentpool-35064155-2 Successfully pulled image "kekekekenta.azurecr.io/layered-nlp-tools"
Normal Created 3m18s kubelet, aks-agentpool-35064155-2 Created container
Normal Started 3m18s kubelet, aks-agentpool-35064155-2 Started container
複数レイヤーに分割したコンテナイメージの方は、レイヤーを纏めたコンテナイメージと比べて、エラーも出ず、素早くPull完了していますね。
2回目以降のapplyの動作
2回目以降のapplyはキャッシュされているコンテナイメージを使うので、それぞれ約2秒で完了しています。
レイヤーを纏めたコンテナイメージ
$ kubectl apply -f nlp.yaml
job.batch/nlp created
$ kubectl get pods -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nlp-zzl4s 0/1 Completed 0 28s 10.244.1.16 aks-agentpool-35064155-0 <none> <none>
$ kubectl describe pods nlp-zzl4s
....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 53s default-scheduler Successfully assigned default/nlp-zzl4s to aks-agentpool-35064155-0
Normal Pulling 52s kubelet, aks-agentpool-35064155-0 pulling image "kekekekenta.azurecr.io/nlp-tools"
Normal Pulled 52s kubelet, aks-agentpool-35064155-0 Successfully pulled image "kekekekenta.azurecr.io/nlp-tools"
Normal Created 51s kubelet, aks-agentpool-35064155-0 Created container
Normal Started 51s kubelet, aks-agentpool-35064155-0 Started container
複数レイヤーに分割したコンテナイメージ
$ kubectl apply -f layerednlp.yaml
job.batch/layerednlp created
$ kubectl get pods -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
layerednlp-9p478 0/1 Completed 0 13s 10.244.0.13 aks-agentpool-35064155-2 <none> <none>
$ kubectl describe pods layerednlp-9p478
....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m23s default-scheduler Successfully assigned default/layerednlp-9p478 to aks-agentpool-35064155-2
Normal Pulling 2m22s kubelet, aks-agentpool-35064155-2 pulling image "kekekekenta.azurecr.io/layerd-nlp-tools"
Normal Pulled 2m21s kubelet, aks-agentpool-35064155-2 Successfully pulled image "kekekekenta.azurecr.io/layerd-nlp-tools"
Normal Created 2m21s kubelet, aks-agentpool-35064155-2 Created container
Normal Started 2m21s kubelet, aks-agentpool-35064155-2 Started container
まとめ
機械学習系のコンテナイメージをKubernetesに展開する際は、なるべく小さいサイズのレイヤーに分けた方が良さそうですね。
もしくは、もっと細かくコンテナを分けた方が良いかも知れません。Pullされた後にセットアップするようなコンテナでも良いですね。
では。メモでしたー。
参照先
https://github.com/kubernetes/kubernetes/blob/39529006f0f47a105f6c08371df11be00726378a/pkg/kubelet/kubelet.go#L315
https://github.com/moby/moby/issues/1266
https://github.com/moby/moby/pull/34610