細かすぎて伝わらないかもしれない、CircleCIでDockerをごにょごにょするときのスピードアップテク
前回、「Dockerコンテナにcookしserverspecでテストをする」ということをやりました。 キャッシュ活用などによる高速化などが課題でした。今回はいくつかの課題を解決させ、テスト時間の短縮を図りました。
(「スピードアップテク」とか書きましたが、勝手に自分がハマってたところを改善したりしてるだけの箇所もあります。)
先に結論:対策後のビルド時間
対策前(build#9) | docker imageキャッシュ有効時(build#55) | docker imageキャッシュ無効時(build#54) | |
---|---|---|---|
load docker image | 1m41s | 0m20s | 1m00s |
knife solo cook(nginxのインストール) | 2m56s | 0m12s | 0m21s |
(other) | (1m03s) | (0m41s) | (1m06s) |
TOTAL | 5m40s | 1m13s(!!!!!) | 2m27s |
結構早くなりました(5m40s -> 1m13s)!! 大勝利の予感!!!!
実施した5個の細かい対策
- chefをdocker imageに含める
- docker imageのキャッシュ
- gemのキャッシュ
- docker imageのレイヤー数を最小限にする
- docker runでホスト側に空けたポートを利用しない
それぞれ説明していきます
1. chefをdocker imageに含める (差分)
もともとknife solo bootstrap(prepare + cook)
でコンテナ側にchefをインストールしていたのですが、docker imageにchefも含めるように修正しました。
これにより、docker imageをキャッシュする場合、chefのインストールがスキップできるので、多少早くなります。
Dockerfile
diff --git a/Dockerfile b/Dockerfile index 57936bb..05cdedb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM centos:6.6 MAINTAINER Tsuyoshi HOSHINO RUN yum update -y -RUN yum -y install openssh-server sudo +RUN yum -y install openssh-server sudo rsync ## Create user RUN useradd docker; passwd -f -u docker @@ -23,6 +23,11 @@ RUN sed -ri 's/#PermitRootLogin yes/PermitRootLogin yes/g' /etc/ssh/sshd_config RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config RUN sed -ri 's/#UsePAM no/UsePAM no/g' /etc/ssh/sshd_config +## Prepare chef +RUN cd /tmp && \ + curl -LO https://opscode-omnibus-packages.s3.amazonaws.com/el/6/x86_64/chef-12.0.3-1.x86_64.rpm && \ + rpm -ivh ./chef-12.0.3-1.x86_64.rpm + ## Init SSHD RUN /etc/init.d/sshd start RUN /etc/init.d/sshd stop
circle.yml
- prepareは行わず、cookだけにします
diff --git a/circle.yml b/circle.yml index 816ffb9..a7b4863 100644 --- a/circle.yml +++ b/circle.yml @@ -16,7 +16,7 @@ test: - cp ./ssh-config.circleci ~/.ssh/config override: - docker run -d -p 40022:22 hoshinotsuyoshi/centos-sshd; sleep 10 - - bundle exec knife solo bootstrap centos-sshd: + - bundle exec knife solo cook centos-sshd: timeout: 900 - bundle exec rake: timeout: 900
2. docker imageのキャッシュ (差分)
docker imageの実体は/var/lib/docker
の下とかにあると思うのですが、これをキャッシュするのは試さず、docker save
とdocker load
を使ってキャッシュしてみました。
docker save
とdocker load
は、それぞれimageをtarに固める・tarからimageを展開するコマンドです。使い方はこんな感じ。
$ docker save [IMAGE名] > image.tar $ docker load < image.tar
Dockerfileに変更があった場合はキャッシュimageを利用せずに再buildしてほしいので、直近のDockerfileのダイジェスト値も保存しておき、docker imageを再buildするかどうかを判定するようにしました。(何かを再発明してる感がすごいですが気にしない)
DockerhubのAutomated buildも良いのですが、pullにかかる時間と、Dockerfileに変更があった場合に結局すぐビルドできないということがあるので、今回は見送りました。
circle.yml と docker buildのスクリプト
こんな感じ。
diff --git a/circle.yml b/circle.yml index a7b4863..d73eecf 100644 --- a/circle.yml +++ b/circle.yml @@ -3,11 +3,11 @@ machine: Asia/Tokyo services: - docker - dependencies: + cache_directories: + - "~/cache" override: - - docker info - - docker build -t hoshinotsuyoshi/centos-sshd . + - ./docker-build.sh test: pre: - bundle -j4 --path=vendor/bundle diff --git a/docker-build.sh b/docker-build.sh new file mode 100755 index 0000000..1f00b75 --- /dev/null +++ b/docker-build.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -xe + +docker info + +if [ -e ~/cache/centos-sshd.tar ] && [ $(md5sum Dockerfile | cut -d' ' -f1) = $(cat ~/cache/dockerfile.digest) ] +then + docker load < ~/cache/centos-sshd.tar +else + mkdir -p ~/cache + docker build -t hoshinotsuyoshi/centos-sshd . + md5sum Dockerfile | cut -d' ' -f1 > ~/cache/dockerfile.digest + docker save hoshinotsuyoshi/centos-sshd > ~/cache/centos-sshd.tar +fi
3. gemのキャッシュ (差分)
これはそのまんまですがvendor/bundle
をキャッシュするようにしました。これでbundle
にかかってた時間16秒ほどが不要になる。
circle.yml
diff --git a/circle.yml b/circle.yml index d73eecf..26df530 100644 --- a/circle.yml +++ b/circle.yml @@ -6,11 +6,12 @@ machine: dependencies: cache_directories: - "~/cache" + - "vendor/bundle" override: - ./docker-build.sh + - bundle -j4 --path=vendor/bundle test: pre: - - bundle -j4 --path=vendor/bundle - cp ./id_rsa_insecure ~/.ssh/id_rsa - sudo chown 600 ~/.ssh/id_rsa - cp ./ssh-config.circleci ~/.ssh/config
4. docker imageのレイヤー数を最小限にする (差分)
「RUNを1つにまとめたりしてDockerfileでの命令の数を少なくするとビルドが早くなる」のは直感として知っていたのですが、 なんでもイメージサイズも小さくなるそうです。
極端かもしれませんがこれも限界までやってしまいます。&&
を多用してRUN
を1つにまとめます。これでdocker build
も早くなりましたし、docker load
も20秒以内に終わるようになりました。
before
つまりはこういうDockerfileの
# Dockerfile # inspired from http://www.nerdstacks.net/2014/03/ssh-ready-centos-dockerfile/ FROM centos:6.6 MAINTAINER Tsuyoshi HOSHINO RUN yum update -y RUN yum -y install openssh-server sudo rsync ## Create user RUN useradd docker; passwd -f -u docker ## Set up SSH RUN mkdir -p /home/docker/.ssh; chown docker /home/docker/.ssh; chmod 700 /home/docker/.ssh ADD id_rsa_insecure.pub /home/docker/.ssh/authorized_keys RUN chown docker /home/docker/.ssh/authorized_keys RUN chmod 600 /home/docker/.ssh/authorized_keys ## setup sudoers RUN echo "docker ALL=(ALL) ALL" >> /etc/sudoers.d/docker ## Set up SSHD config RUN sed -ri 's/#PermitRootLogin yes/PermitRootLogin yes/g' /etc/ssh/sshd_config RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config RUN sed -ri 's/#UsePAM no/UsePAM no/g' /etc/ssh/sshd_config ## Prepare chef RUN cd /tmp && \ curl -LO https://opscode-omnibus-packages.s3.amazonaws.com/el/6/x86_64/chef-12.0.3-1.x86_64.rpm && \ rpm -ivh ./chef-12.0.3-1.x86_64.rpm ## Init SSHD RUN /etc/init.d/sshd start RUN /etc/init.d/sshd stop CMD /usr/sbin/sshd -D
after
RUN
は1回にしてしまえ! という感じです
# Dockerfile # inspired from http://www.nerdstacks.net/2014/03/ssh-ready-centos-dockerfile/ FROM centos:6.6 MAINTAINER Tsuyoshi HOSHINO RUN yum update -y && \ yum -y install openssh-server sudo rsync && \ yum clean all && \ useradd docker && \ passwd -f -u docker && \ mkdir -p /home/docker/.ssh && \ chown docker /home/docker/.ssh && \ chmod 700 /home/docker/.ssh && \ echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQTEOxQRimMlKlE67ofwnbLsvaNPO7VydzSQIzrFTDxa3wH5wPxLymA9zqpXiVAymZIaWOUiFP5+TKoMSBpaaQirv9fbstB7CMH/NYLA5ureJvuII91RkSKVKQn0ddHBtqbDFvHXCFsdoh/gNCN0/6joKZQjQFiCCT9jjwwkroK7NPnUlmLv+g6M02orCwEohd4CBqSKzKcvJ7+rrQlPPQlRVyLUrXiYcHutIphxcU5Ls04cGsXhcMu/LmNzfM7RaFee/02JodZ14QWDHlw8KxTdzqBP6rod4G3319aFdJ8t2SBCGqVUGP0uPpKxGkt2CKZCc43qNPPVZVCJtacBxX berlin@berlin.local' >> /home/docker/.ssh/authorized_keys && \ chown docker /home/docker/.ssh/authorized_keys && \ chmod 600 /home/docker/.ssh/authorized_keys && \ echo "docker ALL=(ALL) ALL" >> /etc/sudoers.d/docker && \ sed -ri 's/#PermitRootLogin yes/PermitRootLogin yes/g' /etc/ssh/sshd_config && \ sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config && \ sed -ri 's/#UsePAM no/UsePAM no/g' /etc/ssh/sshd_config && \ cd /tmp && \ curl -LO https://opscode-omnibus-packages.s3.amazonaws.com/el/6/x86_64/chef-12.0.3-1.x86_64.rpm && \ rpm -ivh ./chef-12.0.3-1.x86_64.rpm && \ /etc/init.d/sshd start && \ /etc/init.d/sshd stop CMD /usr/sbin/sshd -D
Dockerfileを育てるのが目的ではないので、こんなノリでもいいはずです。
5. docker runでホスト側に空けたポートを利用しない (差分)
これはうまく説明できないし、もともとハマっていただけ?、という話なのですが、もともとdocker runするときに
$ docker run -d -p 40022:22
みたいにしてホスト側のポート(40022)も指定し、cookするときは「localhostのポート40022にsshする」という感じにしていました。
理由は、「dockerコンテナのIPアドレスを調べるのがめんどいから」ホスト側のポートで固定してしまおう、という思いからでした。
しかしどうやら、、これのせいで、「sshがめっちゃ遅い」という謎の事態に陥っていたようでした。
で、やめたところ、遅さが解消されました。cook(nginxのインストールだけ)に2分以上かかってたのが10秒くらいになりました。
「dockerコンテナのIPアドレスを調べるのがめんどい」に関しては、しぶしぶ対応します。docker run
後にdocker inspect
でIPアドレスを調べ、.ssh/config
に書き込むようにしました。
docker inspect
はJSONを吐くのですが、ruby
で適当にパースして欲しいもの(IPアドレス)だけ取り出します。
こんな感じでいけます。
$ ruby -rjson -e 'puts JSON.parse(%x(docker inspect [コンテナ名])).first["NetworkSettings"]["IPAddress"]' #=> 172.x.x.x # 的なIPアドレスが返るはず
最終的に以下の様な感じになりました。
circle.yml
diff --git a/circle.yml b/circle.yml index 26df530..9f08ab6 100644 --- a/circle.yml +++ b/circle.yml @@ -16,7 +16,8 @@ test: - sudo chown 600 ~/.ssh/id_rsa - cp ./ssh-config.circleci ~/.ssh/config override: - - docker run -d -p 40022:22 hoshinotsuyoshi/centos-sshd; sleep 10 + - docker run -d -p 22 --name sshd hoshinotsuyoshi/centos-sshd; sleep 10 + - host=$(ruby -rjson -e 'puts JSON.parse(%x(docker inspect sshd)).first["NetworkSettings"]["IPAddress"]'); echo ' HostName '$host >> ~/.ssh/config - bundle exec knife solo cook centos-sshd: timeout: 900 - bundle exec rake: diff --git a/ssh-config.circleci b/ssh-config.circleci index f0b5396..8ee4139 100644 --- a/ssh-config.circleci +++ b/ssh-config.circleci @@ -1,7 +1,5 @@ Host centos-sshd - HostName 0.0.0.0 User docker - Port 40022 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no
(これほんと説明面倒でうまく説明できない)
以上です
まだ本格的なcookbookで試してないので、まだまだ壁はあるかもしれませんが、実用度はだいぶ増してきた気がします!