FF7リメイク、クリアしました。いまSwitchで原作のFF7をやり直しています。もーすけです。
今日はkubernetes-image-pullerというツールを発見し試してみたので、背景や課題感含めてご紹介していきます。
課題感
Kubernetesを利用していて、イメージのプル(ダウンロード)を効率化したいと考えたことはないでしょうか。 たとえば、イメージのプルの時間を短縮してデプロイを早くしたいや、ネットワーク帯域などの都合上で事前にノードにイメージをプルしておきたいといったことなどがあります。
対策
このような問題にどう立ち向かっていったらいいでしょうか。
イメージサイズを小さくする
一つは当然ながら、イメージのサイズを可能な限り小さくすることが考えられます。 イメージサイズを小さくすることで、イメージプルにかかる時間の削減やネットワークの有効活用が可能になります。 イメージのサイズを小さくするプラクティスについては、いろんなところで紹介されていますので、本記事では詳しくは割愛しますが、 マルチステージビルドやレイヤー数を少なくするなどがあります。
こちらのDockerfileのベストプラクティスをご参照ください。
Best practices for writing Dockerfiles | Docker Documentation
レイヤーを意識してイメージを作成する
コンテナイメージはレイヤー構造で構成されています。
簡単な例でいうとdocker pull
したときに表示されるハッシュ値のリストですが、これがレイヤーです。
% docker pull nginxinc/nginx-unprivileged:1.18
1.18: Pulling from nginxinc/nginx-unprivileged
54fec2fa59d0: Already exists
3a4d0e4b78ef: Downloading [=============> ] 7.153MB/26.2MB
83b11c8c3a6e: Download complete
56e6a727dfef: Download complete
e575c5ee613a: Download complete
5073a53c326e: Download complete
このレイヤーを意識してコンテナイメージを作成することで、イメージの一部に変更があったとしても最小限のレイヤーのプルのみで済むなどの工夫ができます。プルする対象を減らすことができるので、結果的にイメージプルの効率が上がります。
よくある例としては、アプリケーションのコードに変更があった場合です。
以下はRailsを例にしていますが、アプリケーションを実行するために必要な依存関係のライブラリのインストール(RUN bundle install部分
)をアプリケーションのコードを置く前に実施しています。
アプリケーションの変更の多くはソースコードであるため、変わらない部分を前に持ってくることで、そこまでのイメージレイヤーを使い回すことができるためです。
# Dockerfile
FROM ruby:2.6
RUN gem install bundler -v "1.15.4"
RUN mkdir /usr/local/src/app
# ----- 依存関係ライブラリのインストールを先に行う -----
## ソースコードを入れる前にGemfileをコピーしbundle installしておく
COPY Gemfile /usr/local/src/app/Gemfile
COPY Gemfile.lock /usr/local/src/app/Gemfile.lock
WORKDIR /usr/local/src/app
RUN bundle install
# ----- ここまで -----
# アプリケーション本体コードの配置
COPY . /usr/local/src/app/
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "3000"]
事前にノードにイメージをプルしておく
うえ2つの方法については、Kubernetesを利用している多くの方が実践していることではないかなと思っています。 これらの対策をしたとしても、ベースイメージが変更になってしまうときにはプルするイメージのサイズが大きくなってしまいます。 よりコンテナのアップデートを早くするためにデプロイ時にそもそもプルさせたくないこともあると思います。 こういったニーズに応えるために、イメージを各ノードに事前にダウンロードさせておくといった方法もあります。
今日紹介するkubernetes-image-pullerは、このような問題を解消する1つの手段です。
kubernetes-image-puller
上の説明したような問題点を解決するため、事前にノードにイメージをプルさせておくことが1つの対策になるわけですが、それを実装したのがこのkubernetes-image-pullerです。
このソフトウェアはEclipse Cheのレポジトリで公開されているわけですが、もともとの課題を少し書いておきます。
Eclipse CheはKubernetes上で動作するオンラインのエディタです。利用者毎にコンテナとしてエディタ環境を提供します。
より速く開発環境の提供するためにはイメージのプルの効率化が必要だっとのことです。
では、さっそく簡単に仕組みを見ていきましょう。
仕組み
全体像は以下のとおりです。
ImagePullerを理解する上で重要なポイントは下記のとおりです。
- ImagePullerを利用するためには、Deployment/ConfigMap/ServiceAccountの3つをデプロイする
- デプロイ方法はHelmかOpenShift Templateで可能
- Deploymentは、ImagePullerの本体を動作させるためのもの。
- ImagePuller本体は、DaemonSetを作成し各ノードにPodをデプロイすることで、イメージをプルさせる
- ConfigMapは、主にDaemonSetに関する設定値
- ノードにプルさせたいイメージ
- 利用するNamespaceなど
- 詳しい設定はこちらを参照
- ServiceAccountは、ImagePuller自身がDaemoSetなどKubernetesリソースを操作するために利用
- DaemonSetの作成権限などを付与
使ってみる
筆者はOpenShift 4.3の環境で行いましたが、GKEやEKSなどKubernetes環境であれば問題なく動作します。
以下、デプロイ部分以外はoc xxxx
はkubectl xxxx
と読み替えてもらってよいです。
環境
本環境ではWorkerノードが3台ある状態です。
$ oc get node | grep worker
ip-10-0-129-134.ap-southeast-1.compute.internal Ready worker 75m v1.16.2
ip-10-0-158-129.ap-southeast-1.compute.internal Ready worker 75m v1.16.2
ip-10-0-175-177.ap-southeast-1.compute.internal Ready worker 50m v1.16.2
デプロイ
OpenShift Templateを使ってデプロイします。OpenShift以外の環境の人はHelmをお使いください。
デプロイ方法は異なりますが、デプロイされるリソースや仕組み自体は変わりません。
今回、各ノードにcentos:8
とnginxinc/nginx-unprivileged:1.18
の2つを事前にプルしておきたいと考えた想定とします。
$ oc process -f deploy/openshift/serviceaccount.yaml | oc apply -f -
$ oc process -f deploy/openshift/configmap.yaml \
-p IMAGES="centos=centos:8;nginx=nginxinc/nginx-unprivileged:1.18" \
-p DAEMONSET_NAME="puller" \
| oc apply -f -
$ oc process -f deploy/openshift/app.yaml | oc apply -f -
動作確認
デプロイしたので、どのようなリソースが作成されているかみていきます。
ポイントは以下のとおりです。
- ImagePuller本体がdeploymentとしてデプロイ
- Pod
pod/kubernetes-image-puller-b556c8bd4-p8shz
が作成されている
- Pod
- Puller DaemonSetが作成され、
pod/puller-xxxxx
が3つ作成- 各ノード分
- このDaemonSetは前の項目のデプロイ時にデプロイしたものではない。ImagePullerが作成したもの。
pod/puller-94tfv
内では2つのコンテナが起動。- centosとnginxのイメージを使った2つのコンテナ
$ oc get ds,deployment,pod
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.extensions/puller 3 3 3 3 3 <none> 35m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.extensions/kubernetes-image-puller 1/1 1 1 36m
NAME READY STATUS RESTARTS AGE
pod/kubernetes-image-puller-b556c8bd4-p8shz 1/1 Running 0 36m
pod/puller-94tfv 2/2 Running 0 35m
pod/puller-p9mcm 2/2 Running 0 35m
pod/puller-tzd6n 2/2 Running 0 35m
DaemonSet
DaemonSetの中の設定が気になるので見てみます。
spec.template.spec.containers
には、ConfigMapで指定したcentosとnginxの2つのイメージを利用したコンテナが起動しているのがわかります。
実行コマンドはsleep 30d
となっています。ImagePullPolicy: Always
となっていることから、30日後にこのPodは終了し、その際に最新となっているイメージをまたプルすることになります。
あまり好ましくないですがlatestタグで運用していても定期的にイメージをプルすることができるわけです。
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: puller
namespace: k8s-image-puller
spec:
revisionHistoryLimit: 10
selector:
matchLabels:
test: daemonset-test
template:
metadata:
creationTimestamp: null
labels:
test: daemonset-test
name: test-po
spec:
containers:
- args:
- 30d
command:
- sleep
image: centos:8
imagePullPolicy: Always
name: centos
resources:
limits:
memory: 20Mi
requests:
memory: 10Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
- args:
- 30d
command:
- sleep
image: nginxinc/nginx-unprivileged:1.18
imagePullPolicy: Always
name: nginx
resources:
limits:
memory: 20Mi
requests:
memory: 10Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 1
templateGeneration: 1
updateStrategy:
rollingUpdate:
maxUnavailable: 1
type: RollingUpdate
まとめ
kubernetes-image-pullerを参考に、Kubernetes環境におけるイメージの効率化について考えてみました。
このソフトウェアをそのまま利用するかどうかは別にしても、DaemonSetを活用してイメージの事前のプルなどが実現でき活用できる場面もありそうです。
今後もKubernetesについて情報発信していきます。