nekop's blog

OpenShift / JBoss / WildFly / Infinispanの中の人 http://twitter.com/nekop

Dockerfileの無駄なレイヤリングを排除してビルドを高速化する

Docker Advent Calendar 2016の10日目の記事です。土曜日は子供と遊んでいると思うので1日早く金曜日に公開しちゃいます。

Dockerでコンテナイメージを作成するときにはDockerfileを利用するのが最も一般的ですが、標準のdocker buildはインストラクション毎に中間レイヤーを作成し保存することを強制するようになっており、イメージ肥大化およびイメージビルドのパフォーマンス低下を避けるために、ユーザはコマンドを&&で繋げるRUNインストラクションを必要最小限な数になるよう最適化した奇妙なDockerfileを記述するという理不尽な作業が必要となります。

この強制レイヤリングは一応イメージビルド時の試行錯誤をしているときに中間までキャッシュできるので良い、という建前になっており、最初のうちはちょっと便利かなぁと思ったりもしますが、要件が固まっているベースレイヤは名前つけて必要ならpushもしちゃってFROMに書くと思いますので、実際に慣れてくるとこの仕組みはただ単に遅くてイライラするだけのものになり、Dockerfileの見た目も汚なくなってしまいます。

実例をみてみましょう。ENVRUNが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というプロジェクト内のリソースの総数、もしくは個々のリソースを加算した全体に制限をかけるものがあります。

例えば自分が会社で運用していて他の人に使っていいよ、って置いてある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"ということになっています。

f:id:nekop:20161203173054p:plain

OpenShiftはKubernetesの強化版であり、Kubernetesを内包しています。Red HatKubernetesの公開直後から開発に参加しており、コントリビュータのグラフを見たときにコミット数第二位の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上で開発され、動作しています。

f:id:nekop:20161203173112p:plain

OpenShiftの機能って何があるの?

代表的なものをいくつかピックアップします。ドキュメントはdocs.openshift.orgにあります。

  • Kubernetesの機能ほぼ全部
  • リッチなWebコンソール
  • コンテナイメージのビルド機能
    • 手元で遅いdocker buildを実行する必要がなくなる
    • ソースツリーを指定すればビルドしてコンテナイメージとしてデプロイしてくれる機能
  • フロントエンドルータ
  • コンテナレジストリ
    • イメージの情報の詳細を参照可能
  • セキュリティ
    • 基本的にコンテナ内でuid 0 rootは利用不可
    • SELinuxによる各コンテナ間およびホストの隔離
    • 各ネームスペース間は基本的に隔離されており不可侵
    • RBAC (ロールベースアクセス制御)
  • ネットワーキング
    • OpenShift SDNによるプロジェクト毎のネットワーク隔離
  • マルチテナント
    • 上記セキュリティとネットワーク, Linux cgroupsでのリソース制限の組み合わせ
  • イベントトリガー
    • GitHubにコミットpushされたらビルドを実行してデプロイ
    • イメージアップデートトリガーを利用してベースのコンテナイメージのセキュリティ修正などのアップデートを検知、関連するイメージを全てリビルドしてローリングアップデート
  • 柔軟なデプロイ制御
  • Jenkins Pipeline Pluginによるビルドパイプラインのサポート
    • 複数のビルドの連結、デプロイの制御やイメージのタグ付け、リリースなど
  • テンプレート
    • 事前に定義したテンプレートからビルドやデプロイの生成、WikiやGitサーバを立ち上げたり、データベースをアプリケーションにインスタントに追加など
  • 標準インストーラ
    • Ansible

OpenShiftのWeb Consoleってどんな感じ?

こんな感じです。

f:id:nekop:20161203173150p:plain f:id:nekop:20161203173154p:plain

どうやって試すのがいいの?

fedoraVM立ち上げてoc cluster upをするスクリプトを流せばすぐ使えます。

nekop.hatenablog.com

日本語で質問とかできる場所はどこ?

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_urlopenshift_hosted_metrics_public_urlにリネームされています。3.2からinventory設定を引き継いでいる場合は修正が必要です。まぁこの設定はインストール後に一瞬で直せるものなので、直さなくても大丈夫です。似たような設定のloggingのほうはリネームされていません。

loggingデプロイはまだサポートではなく、リリース時点でのインストーラではPVを追加すると正常に動作しません。

OpenShift Container Platform 3.3リリース

OpenShift Container Platform 3.3をリリースしました。Enterprise Kubernetesのコンテナプラットフォームです。

今回のリリースはかなり実用や応用に踏み込んだ機能追加が数多く実装されています。

ハイライトをざくっと抜き出すと以下のような感じです。

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や、デフォルトの毎回初期化する挙動ではなく、データを保存して引き継いで起動するオプションとかも載っています。

VagrantVMセットアップする部分は前回の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を入れたのだった。

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閲覧端末として十分なパフォーマンス出るかな?