Dockerfileの無駄なレイヤリングを排除してビルドを高速化する
Docker Advent Calendar 2016の10日目の記事です。土曜日は子供と遊んでいると思うので1日早く金曜日に公開しちゃいます。
Dockerでコンテナイメージを作成するときにはDockerfileを利用するのが最も一般的ですが、標準のdocker buildはインストラクション毎に中間レイヤーを作成し保存することを強制するようになっており、イメージ肥大化およびイメージビルドのパフォーマンス低下を避けるために、ユーザはコマンドを&&
で繋げるRUN
インストラクションを必要最小限な数になるよう最適化した奇妙なDockerfileを記述するという理不尽な作業が必要となります。
この強制レイヤリングは一応イメージビルド時の試行錯誤をしているときに中間までキャッシュできるので良い、という建前になっており、最初のうちはちょっと便利かなぁと思ったりもしますが、要件が固まっているベースレイヤは名前つけて必要ならpushもしちゃってFROM
に書くと思いますので、実際に慣れてくるとこの仕組みはただ単に遅くてイライラするだけのものになり、Dockerfileの見た目も汚なくなってしまいます。
実例をみてみましょう。ENV
とRUN
が3つずつある単なるDockerfileです。イメージの読み出しと保存があるので、ビルドの実行には3秒もあれば良いと考えるのが妥当でしょうか。実行環境はThinkPad T440s, 12GB mem, SSD storage, Fedora 25です。
FROM fedora ENV FOO1=BAR1 ENV FOO2=BAR2 ENV FOO3=BAR3 RUN pwd RUN pwd RUN pwd
標準のdocker build
を実行してみます。13秒かかりました。
$ docker -v Docker version 1.12.3, build 97974ae/1.12.3 $ time docker build . --no-cache -t normal-dockerbuild:latest Sending build context to Docker daemon Step 1 : FROM fedora ---> 5bafccc4d5dc Step 2 : ENV FOO1 BAR1 ---> Running in fa7318e71dac ---> 7751ce4fb55e Removing intermediate container fa7318e71dac Step 3 : ENV FOO2 BAR2 ---> Running in e9f8c9062182 ---> d4240ff674a4 Removing intermediate container e9f8c9062182 Step 4 : ENV FOO3 BAR3 ---> Running in 9108d53e3193 ---> 2b1dd6f364fb Removing intermediate container 9108d53e3193 Step 5 : RUN pwd ---> Running in f77b5b36eb26 / ---> e22d9ef35156 Removing intermediate container f77b5b36eb26 Step 6 : RUN pwd ---> Running in 43e1e4b3a758 / ---> 6e5bec6562f6 Removing intermediate container 43e1e4b3a758 Step 7 : RUN pwd ---> Running in d6a73a87c283 / ---> b8a0040efd28 Removing intermediate container d6a73a87c283 Successfully built b8a0040efd28 real 0m13.206s user 0m0.041s sys 0m0.056s $ docker images -a REPOSITORY TAG IMAGE ID CREATED SIZE normal-dockerbuild latest b8a0040efd28 1 seconds ago 199.9 MB <none> <none> 6e5bec6562f6 3 seconds ago 199.9 MB <none> <none> e22d9ef35156 5 seconds ago 199.9 MB <none> <none> 2b1dd6f364fb 8 seconds ago 199.9 MB <none> <none> d4240ff674a4 10 seconds ago 199.9 MB <none> <none> 7751ce4fb55e 12 seconds ago 199.9 MB docker.io/fedora latest 5bafccc4d5dc 2 weeks ago 199.9 MB
次のテストのために作ったイメージは一旦削除します。
$ docker rmi normal-dockerbuild:latest $ docker images -q -f dangling=true | xargs --no-run-if-empty docker rmi
ここでおもむろにOpenShiftのリリースからocクライアントをダウンロードします。今回利用したのはv1.4.0-rc1のLinux版です。OpenShiftにはdocker buildと同等のoc ex dockerbuild
コマンドがあり、成果物はdocker build
と同じですが、中間レイヤをDocker daemonに投げることはしません。
oc ex dockerbuild
を実行してみます。2.5秒でビルドできました。このケースでは標準のdocker build
より5倍高速にビルドできています。
$ time ./oc ex dockerbuild . ex-dockerbuild:latest --> FROM fedora --> ENV FOO1=BAR1 --> ENV FOO2=BAR2 --> ENV FOO3=BAR3 --> RUN pwd / --> RUN pwd / --> RUN pwd / --> Committing changes to ex-dockerbuild:latest ... --> Done real 0m2.543s user 0m0.205s sys 0m0.011s $ docker images -a REPOSITORY TAG IMAGE ID CREATED SIZE ex-dockerbuild latest 6b854accd835 1 seconds ago 199.9 MB docker.io/fedora latest 5bafccc4d5dc 2 weeks ago 199.9 MB
また、Dockerfileを利用しないコンテナイメージビルドの方法として、Ansible Containerというのも絶賛開発中ですのでお試しください。DockerfileではなくAnsibleを利用してコンテナイメージをビルドします。
テストに利用したスクリプトを以下に置いておきます。
#!/bin/bash set -x docker ps -q -f status=exited | xargs --no-run-if-empty docker rm docker images -q -f dangling=true | xargs --no-run-if-empty docker rmi docker images -a time docker build . --no-cache -t normal-dockerbuild:latest docker images -a docker rmi normal-dockerbuild:latest docker ps -q -f status=exited | xargs --no-run-if-empty docker rm docker images -q -f dangling=true | xargs --no-run-if-empty docker rmi time ./oc ex dockerbuild . ex-dockerbuild:latest docker images -a docker rmi ex-dockerbuild:latest
Storage Driverの差異について追記
ブコメでStorage Driverがdevicemapperのせいで差が大きく出ているのでは、という指摘があった(いいご指摘です、ありがとうございます)ので、overlayfsでの結果も載せておきます。差は縮まりましたが4倍の差がでており十分に高速です。
$ time docker build . --no-cache -t normal-dockerbuild:latest real 0m6.784s user 0m0.043s sys 0m0.053s $ time ./oc ex dockerbuild . ex-dockerbuild:latest real 0m1.511s user 0m0.172s sys 0m0.013s
個人的にはもうdockerは専用のクラスタへオフロードしてしまって手元のラップトップでは動作させていない、かつ最近リリースされたFedora 25にしたときにすっかりStorage Driverの設定を忘れていたので、追記前の結果はもっさりdevicemapper loopbackでした。
OpenShiftでプロジェクトの各種制限を設定する
内部利用などのユースケースではあまり問題にはなりませんが、OpenShiftを外部のユーザに利用させたりする場合はうっかり手がすべってビットコインを掘ったりする人が居るかもしれないのでそこそこにリソースを制限しないとなりません。
OpenShiftではユーザ毎のプロジェクト数はmaster-config.yamlのProjectRequestLimitで制限できます。
プロビジョニングされるプロジェクトの各種制限は、プロジェクトのテンプレートに設定します。
プロジェクトのデフォルトのテンプレートは以下のコマンドでファイル化できます。これを編集して制限を記述していきます。
oadm create-bootstrap-project-template -o yaml > project-request-template.yaml
制限には二つあり、LimitRangeという各Pod/Containerに割り当てることのできるリソースの制限と、Quotaというプロジェクト内のリソースの総数、もしくは個々のリソースを加算した全体に制限をかけるものがあります。
- https://docs.openshift.com/container-platform/3.3/admin_guide/limits.html
- https://docs.openshift.com/container-platform/3.3/admin_guide/quota.html
例えば自分が会社で運用していて他の人に使っていいよ、って置いてあるOpenShiftのクラスタでは、Quotaは内部利用なので特に設定していませんが、Java系のコンテナはlimitsが無い場合のデフォルトが-Xmx1303mとかになっていて1.5GBのメモリを無駄に確保してしまう関係上LimitRangeは設定しています。
以下の設定をプロジェクトテンプレートに追記することで、デフォルトで作成されるコンテナには128MBのメモリ、100m (1秒間あたり1CPUを占有した場合を1000ミリコアとしたときの100ミリコア)のCPUを最低で確保できるようにしています。コンテナはホストのリソースがある場合、512MBのメモリと4000ミリコア=4CPUまでの利用を許可しています。リソースはユーザが各デプロイの設定でこれらの範囲内であれば自由に設定できます。つまり、最初から4CPU 512MBを確保できるようにしてデプロイすることができます。それを超えるようなものをデプロイする場合(運用環境なのでリソース多めでお願いします、的なユースケース)はOpenShiftのcluster-admin権限でリソース制限を書き換える、というような流れになります。
- apiVersion: v1 groupNames: null kind: LimitRange metadata: creationTimestamp: null name: default namespace: ${PROJECT_NAME} spec: limits: - type: "Pod" max: cpu: "4" memory: "512Mi" min: cpu: "100m" memory: "128Mi" - type: "Container" max: cpu: "4" memory: "512Mi" min: cpu: "100m" memory: "128Mi" default: cpu: "4" memory: "512Mi" defaultRequest: cpu: "100m" memory: "128Mi"
あとは高負荷が予想されるクラスタでは、コンテナにリソースを持って行かれてKubeletなどが満足に動作できない、というような状況を回避するためにシステム用にメモリをCPUを事前に予約しておくという機能があるので、本番運用ではこれらも設定しておく必要があります。
https://docs.openshift.com/container-platform/3.3/admin_guide/allocating_node_resources.html
このような設定をすることで安心してビットコインを掘ることができます。
KubernetesとOpenShiftの違い
Kubernetes Advent Calendar 2016の三日目の記事です。
OpenShiftというのはRed Hatが主導で開発しているコンテナプラットフォームで、最近の一行説明は"Enterprise Kubernetes for Developers"ということになっています。
OpenShiftはKubernetesの強化版であり、Kubernetesを内包しています。Red HatはKubernetesの公開直後から開発に参加しており、コントリビュータのグラフを見たときにコミット数第二位のsmarterclaytonさんがRed HatでOpenShiftの開発をリードしている人です。第一位の人はGoogleからMicrosoftに転職してKubernetesへのcommit数が低下しているので、このままのペースであれば第一位になります。
Red Hatは商用版のOpenShift Container Platformを2015/06(Kubernetesの発表はちょうどその一年前の2014/06)から展開しており、既に政府系、銀行や保険などの金融系、通信サービス業、旅行サービス業、IoT関連分野などで多く利用されています。日本のKubernetesのコミュニティの事例ではクラウドプロバイダと先進的なユーザ企業が多く、OpenShiftのようなエンタープライズ寄りな分野の利用事例はあまり聞かないのですが、サポートが無いからという側面が大きいのかもしれません。サポートと実績のあるKubernetesとしてOpenShiftが採用されているのだと思います。
KubernetesとOpenShiftの違い
KubernetesとOpenShiftの大きな違いですが、OpenShiftは「開発のスコープまでカバーできるKubernetes」になっています。Kubernetes単体ではほとんどのケースではインフラエンジニアが利用するインフラツールという位置付けになると思いますが、OpenShiftはデプロイだけではなくビルドを含めた開発のスコープまでをカバーすることで、様々なユースケースに利用できるようになっています。逆に、利用できる機能やユースケースが広すぎるために、最初はなんだか大きくてよくわからない、というような印象を受けるかもしれません。
一番基本的な機能は以下のようにソースコードを指定すると依存ライブラリの取得などの各言語の特有のビルド、コンテナイメージのビルドを行い、コンテナイメージをレジストリに登録してデプロイして実行、URLでのアクセスを可能にしてくれます。このあたりの基本機能はHerokuやCloud Foundryと言った他のPaaSとそれほど変わりません。
$ oc new-app http://github.com/nekop/hello-sinatra $ oc expose svc hello-sinatra $ open "http://hello-sinatra-myproject.apps.example.com"
コンテナエンジンには現状Dockerを利用していますが、利用者は手元のマシンにDockerをインストールしたりする必要はまったくありません。ワークロードは全部クラスタリソースに投げることができます。
OpenShiftがフィットする状況は以下のようなものがあります。
- アプリケーションのコンテナ化を進めたいけど生Docker遅くてつらい
- 隔離されていない共有開発環境しかないので開発がやりづらい、開発者ごとにさくっと開発環境を払い出したい
- 開発者に個々に環境セットアップをさせたくないのでPaaSを利用させたい
- 多言語利用マイクロサービス化したアプリケーションの開発運用ができるプラットフォームが欲しい
- ビルド、テスト、デプロイなどを自動化してビルドパイプライン実現したい
- 開発メンバーには軽量ラップトップを配布して開発サーバでVMを払い出せるようにしているけど、VMのセットアップのコストがどうにも高い
OpenShiftを使うと何ができるの?
典型的な例で、以下の図のようなものができます。大手のユーザ企業の事例では、開発テストアプリの運用を全てOpenShift上で行っており、近年のトレンドであれるマイクロサービス化にも伴って数千という数のアプリケーションがOpenShift上で開発され、動作しています。
OpenShiftの機能って何があるの?
代表的なものをいくつかピックアップします。ドキュメントはdocs.openshift.orgにあります。
- Kubernetesの機能ほぼ全部
- リッチなWebコンソール
- コンテナイメージのビルド機能
- 手元で遅いdocker buildを実行する必要がなくなる
- ソースツリーを指定すればビルドしてコンテナイメージとしてデプロイしてくれる機能
- フロントエンドルータ
- デフォルトはHAProxy、F5 BIG-IPもプラグイン可能
- コンテナレジストリ
- イメージの情報の詳細を参照可能
- セキュリティ
- 基本的にコンテナ内でuid 0 rootは利用不可
- SELinuxによる各コンテナ間およびホストの隔離
- 各ネームスペース間は基本的に隔離されており不可侵
- RBAC (ロールベースアクセス制御)
- ネットワーキング
- OpenShift SDNによるプロジェクト毎のネットワーク隔離
- マルチテナント
- 上記セキュリティとネットワーク, Linux cgroupsでのリソース制限の組み合わせ
- イベントトリガー
- GitHubにコミットpushされたらビルドを実行してデプロイ
- イメージアップデートトリガーを利用してベースのコンテナイメージのセキュリティ修正などのアップデートを検知、関連するイメージを全てリビルドしてローリングアップデート
- 柔軟なデプロイ制御
- 例えばデプロイの進捗に合わせてスクリプト実行
- Blue-Green, A/B deploymentsなどの適用
- Jenkins Pipeline Pluginによるビルドパイプラインのサポート
- 複数のビルドの連結、デプロイの制御やイメージのタグ付け、リリースなど
- テンプレート
- 事前に定義したテンプレートからビルドやデプロイの生成、WikiやGitサーバを立ち上げたり、データベースをアプリケーションにインスタントに追加など
- 標準インストーラ
- Ansible
OpenShiftのWeb Consoleってどんな感じ?
こんな感じです。
どうやって試すのがいいの?
fedoraのVM立ち上げてoc cluster upをするスクリプトを流せばすぐ使えます。
日本語で質問とかできる場所はどこ?
PaaS JPのSlackのopenshiftチャネルがあります。
今日はこのへんで。
OpenShift Container Platform 3.3をシングルノードでインストール
OpenShift Container Platform 3.3をシングルノードでインストールするときのAnsibleのinventoryファイルのメモです。ホスト名はsingle.example.com
、ワイルドカードアプリケーションドメインは*.apps.example.com
という名前の想定です。
[OSEv3:children] masters nodes [OSEv3:vars] ansible_ssh_user=nekop ansible_become=true deployment_type=openshift-enterprise openshift_master_identity_providers=[{'name': 'allow_all', 'login': 'true', 'challenge': 'true', 'kind': 'AllowAllPasswordIdentityProvider'}] os_sdn_network_plugin_name=redhat/openshift-ovs-multitenant osm_default_subdomain=apps.example.com openshift_hosted_metrics_deploy=true openshift_hosted_metrics_public_url=https://hawkular-metrics.apps.example.com/hawkular/metrics openshift_hosted_logging_deploy=true openshift_master_logging_public_url=https://kibana.apps.example.com/ [masters] single.example.com openshift_node_labels="{'region': 'infra'}" openshift_schedulable=true [nodes] single.example.com openshift_node_labels="{'region': 'infra'}" openshift_schedulable=true
シングルノードの場合はinfraノードの役割も持つということなので、openshift_node_labels="{'region': 'infra'}" openshift_schedulable=true
にしないとregistry/routerがデプロイされません。
この範囲で3.2と比較すると、メトリクスのホスト名openshift_public_metrics_public_url
がopenshift_hosted_metrics_public_url
にリネームされています。3.2からinventory設定を引き継いでいる場合は修正が必要です。まぁこの設定はインストール後に一瞬で直せるものなので、直さなくても大丈夫です。似たような設定のloggingのほうはリネームされていません。
loggingデプロイはまだサポートではなく、リリース時点でのインストーラではPVを追加すると正常に動作しません。
OpenShift Container Platform 3.3リリース
OpenShift Container Platform 3.3をリリースしました。Enterprise Kubernetesのコンテナプラットフォームです。
今回のリリースはかなり実用や応用に踏み込んだ機能追加が数多く実装されています。
ハイライトをざくっと抜き出すと以下のような感じです。
- 1,000ノードでの動作サポート
- Source IPなどのEgressの制御
- A/Bテストのためのロードバランシングサポート
- ルータの分割(sharding)
- HTTP以外のport指定トラフィックを専用のIPプールで受けるIngress制御
- リクエストが来ないときにpodを停止、リクエストが来たら自動的にpodを起動するアイドリングのサポート
- ストレージ(PV)のラベル付けとラベル選択のサポート
- Jenkinsビルドパイプライン設定のサポート
- グラフィカルなイメージ表示などのコンテナレジストリの強化
- 当初「透過イメージキャッシュ」と記述していましたが、blobのキャッシュ実装がまだできていませんでした
- Web ConsoleのUI/UX改善、モニタリング機能の強化
RHEL7のVMが用意できるのであれば、ocコマンドをダウンロードしていつもと同じ手順でoc cluster upしてすぐ動かせます。
oc cluster up --image="registry.access.redhat.com/openshift3/ose" --version=v3.3
OpenShift Origin v1.3.0リリース
いつものoc cluster up
の手順を書いておきます。oc cluster up
のドキュメントはLocal Cluster Managementにあります。Windows/Macや、デフォルトの毎回初期化する挙動ではなく、データを保存して引き継いで起動するオプションとかも載っています。
VagrantでVMセットアップする部分は前回のFedora 24でoc cluster upを利用してOpenShift Originをセットアップするを参照。メモリは4096でやってます。
DOWNLOAD_URL=https://github.com/openshift/origin/releases/download/v1.3.0/openshift-origin-client-tools-v1.3.0-3ab7af3d097b57f933eccef684a714f2368804e7-linux-64bit.tar.gz FILENAME=$(basename $DOWNLOAD_URL) PATHNAME=${FILENAME%%\.tar\.gz} sudo dnf install git docker -y sudo sh -c "echo INSECURE_REGISTRY='--insecure-registry 172.30.0.0/16' >> /etc/sysconfig/docker" sudo systemctl start docker sudo systemctl enable docker curl -LO $DOWNLOAD_URL sudo tar xf $FILENAME --strip=1 -C /usr/bin $PATHNAME/oc sudo curl -L https://raw.githubusercontent.com/openshift/origin/master/contrib/completions/bash/oc -o /etc/bash_completion.d/oc . /etc/bash_completion.d/oc sudo oc cluster up --metrics mkdir ~/.kube/ sudo cat /var/lib/origin/openshift.local.config/master/admin.kubeconfig > ~/.kube/config
Nexus 7 2012にPure Nexus 6.0.1をインストール
Nexus 7 2012 "grouper"のオフィシャルアップデートがパフォーマンスが致命的に悪いポンコツ5.1.1で終わってしまっていて使い物にならなかったのでCyanogenMod 12.1を入れて子どものYouTube端末にしていたんだけど、こちらも最近パフォーマンスが非常に悪い状態で10秒くらい固まることが多くなってきていた。
CyanogenModもstableは2015-11のままでアップデートされていないようなので、Pure Nexus 6をつっこんだ。前回CyanogenModを入れたときには、Pure Nexus 6はまともに動かなかったので、CyanogenModを入れたのだった。
- 材料
- TWRP
- Pure Nexus 6.0.1 for Nexus 7 2012 grouper
- Open GApps ARM 6.0 nano
- nano/pico以外は/systemの容量が足りなくてerror code 70で中断する。
Fedora 24ではandroid-toolsを入れるとadb/fastbootが使える。
sudo dnf install -y android-tools
bootloaderを起動した状態でunlockを発行する。bootloaderはUSB Debugging有効でsudo adb reboot bootloader
か、電源ボタン+ボリューム下ボタン長押しで電源ONにするかで起動できる。
sudo fastboot oem unlock
recovery領域をTWRPに入れ替える。
sudo fastboot flash recovery twrp.img
flashしたら、bootloaderからRecover起動を選択するとTWRPが起動する。
USB接続からPure NexusとOpen GAppsのzipをNexus 7のDownloadディレクトリに転送する。転送したらTWRP上のInstallでzip選択して実行。再起動するとPure Nexusになる。
さて、Pure NexusだとYouTube閲覧端末として十分なパフォーマンス出るかな?