ぱいそんにっき

The limits of my language are the limits of my world.

Docker でフロントエンド開発も楽できるか?(後編)

../../../_images/img22.jpg

引き続きよろしくね Docker さん

前編では、

  • Mac に Docker をインストールする
  • Docker の VM をインストール、起動
  • イメージの取得、コンテナ起動、管理

までをざっくりやってみた。 今日は、 nodejs が動く環境のイメージを作成して使ってみる。 また、コンテナ管理ツールの fig をインストールし、触ってみる。

アジェンダ

  • イメージの作成
    • イメージの作成
    • 作成したイメージを元にコンテナを作成、起動
  • fig を使ってコンテナを管理する
    • fig をインストールする
    • まずはチュートリアル
  • フロントエンド開発環境を fig で管理する
    • 設定ファイルを追加
    • 環境構築し、fig を起動する
    • おまけ: DockerHub に登録
  • Docker を使った私見

イメージの作成

前回は Dockerhub に公開されているイメージを取得して使ったが、今回はイメージを作って触ってみる。

Dockerfile

イメージのレシピ(環境構築の手順書的なもの)は Dockerfile というファイルに書いて管理するらしい。 最初なので、試しに公式のをみながら nodejs と ruby 環境を構築するための Dockerfile を書いてみた。 nodejs は version 0.10.32、ruby は 1.9 系にしている。それから、bower、grunt、gulp、compass などよく使うモジュールもインストールしておく。

### Dockerfile

# Pull base image.
FROM dockerfile/python

# Timezone Setting
RUN     echo "Asia/Tokyo" > /etc/timezone

# Install Ruby.
RUN \
  apt-get update && \
  apt-get install -y ruby ruby-dev ruby-bundler && \
  rm -rf /var/lib/apt/lists/*

# Install Gems
RUN \
  gem install compass

# Install Node.js
RUN \
  cd /tmp && \
  wget http://nodejs.org/dist/v0.10.32/node-v0.10.32.tar.gz && \
  tar xvzf node-v0.10.32.tar.gz && \
  rm -f node-v0.10.32.tar.gz && \
  cd node-v* && \
  ./configure && \
  CXX="g++ -Wno-unused-local-typedefs" make && \
  CXX="g++ -Wno-unused-local-typedefs" make install && \
  cd /tmp && \
  rm -rf /tmp/node-v* && \
  npm install -g npm && \
  echo -e '\n# Node.js\nexport PATH="node_modules/.bin:$PATH"' >> /root/.bashrc

# Install Node modules
RUN \
  npm install -g grunt-cli gulp webpack bower

# Define working directory.
WORKDIR /data

# Define default command.
CMD ["bash"]

このファイルのあるディレクトリで、以下のコマンドを実行してイメージを作成する。

$ docker build -t sample-nodejs:v0.10.32 ./
Sending build context to Docker daemon 75.78 kB
Sending build context to Docker daemon
Step 0 : FROM dockerfile/python
...()
Step 1 : RUN echo "Asia/Tokyo" > /etc/timezone
---> Running in f19240aa9215
---> 26fd3dc22d0b
...()
Step 2 : RUN apt-get update &&   apt-get install -y ruby ruby-dev ruby-bundler &&   rm -rf /var/lib/apt/lists/*
---> Running in 3886c51ac19f
...()
Step 3 : RUN gem install compass
---> Running in 8c1264fc6d17
...()
Step 4 : RUN cd /tmp &&   wget http://nodejs.org/dist/v0.10.32/node-v0.10.32.tar.gz &&   tar xvzf node-v0.10.32.tar.gz &&   rm -f node-v0.10.32.tar.gz &&   cd node-v* &&   ./configure &&   CXX="g++ -Wno-unused-local-typedefs" make &&   CXX="g++ -Wno-unused-local-typedefs" make install &&   cd /tmp &&   rm -rf /tmp/node-v* &&   npm install -g npm &&   echo -e '\n# Node.js\nexport PATH="node_modules/.bin:$PATH"' >> /root/.bashrc
---> Running in ed22415781ad
...()
Step 5 : RUN npm install -g grunt-cli gulp webpack bower
---> Running in 1d1e3d14a1f2
...()
Step 6 : WORKDIR /data
---> Running in ab2ba49fafac
...()
Removing intermediate container ab2ba49fafac
Step 7 : CMD bash
---> Running in dc1afeb6fbba
...()
Successfully built 5d1d9e32b456

## イメージが作成されたか確認
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
sample-nodejs       v0.10.32            5d1d9e32b456        2 minutes ago       610.7 MB

この操作による副作用

  • 作成した Dockerfile をもとに新規にイメージが作られる

作成したイメージを元にコンテナを作成、起動

作成したイメージを元にコンテナを作ってみる。 –name というオプションを付けて、sample-container という名前のコンテナを作る。

$ docker run -itd --name sample-container sample-nodejs:v0.10.32 /bin/bash
d2974da42b0c8686130d35355ca712e8817dc64181a26be27fc62daae37bef82
$ docker ps -a
CONTAINER ID  IMAGE                    COMMAND     CREATED        STATUS        PORTS   NAMES
d2974da42b0c  sample-nodejs:v0.10.32   "/bin/bash" 4 minutes ago  Up 4 minutes          sample-container

Dockerfile には nodejs と ruby のインストール、そしてフロントエンド開発でよく使う、gulp、grunt、webpack、bower、compass などのモジュールをインストールしたので、コンテナにログインして確認してみる。

$ docker attach sample-container
[ root@d2974da42b0c:/data ]$ node -v
v0.10.32
[ root@d2974da42b0c:/data ]$ npm -v
2.1.18
[ root@d2974da42b0c:/data ]$ gulp -v
[17:14:55] CLI version 3.8.10
[ root@d2974da42b0c:/data ]$ grunt -v
grunt-cli: The grunt command line interface. (v0.1.13)
[ root@d2974da42b0c:/data ]$  bower -v
1.3.12
[ root@d2974da42b0c:/data ]$ webpack -v
webpack 1.4.15
[ root@d2974da42b0c:/data ]$ ruby -v
ruby 1.9.3p484 (2013-11-22 revision 43786) [x86_64-linux]
[ root@d2974da42b0c:/data ]$ compass -v
Compass 1.0.1 (Polaris)

この操作による副作用

  • 作成したイメージをもとに新規にコンテナが作られる

今回はイメージを作ってみたけど、実際に開発するときは Dockerhub においてある dockerfile/nodejs や dockerfile/nodejs-bower-gulp なんかを pull して使うと良さそう。

fig を使ってコンテナを管理する

イメージの作成ができ、コンテナの作成も慣れてきたところでお次は fig を使ってみる。 fig は複数のコンテナの構成を管理する目的で使うみたい。

fig をインストールする

$ brew install fig
$ fig --version
fig 1.0.1

この操作による副作用

  • fig コマンドが使えるようになる

まずはチュートリアル

まずは公式チュートリアルを試してみる。Radis を使った超簡易な Flask アプリのデモがあるのでそのまま写経。 フォルダ構成は以下。

└── figtest
    ├── Dockerfile
    ├── fig.yml
    ├── app.py
    └── requirements.txt

Dockerfile

python 環境のコンテナを用意し、必要な pip をインストールする。

FROM orchardup/python:2.7
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt

fig.yml

web と redis の 2 つのコンテナを起動させる。 web では 5000 番ポートを開け、redis コンテナとリンクさせるみたい。

web:
  build: .
  command: python app.py
  ports:
  - "5000:5000"
  volumes:
  - .:/code
  links:
  - redis
redis:
   image: orchardup/redis

app.py

/ の URL に GET メソッドでアクセスすると redis に 1 ずつインクリされ、現在のカウントを含む文字列を body に返す web API。

from flask import Flask
from redis import Redis
import os
app = Flask(__name__)
redis = Redis(host="redis_1", port=6379)

@app.route('/')
def hello():
    redis.incr('hits')
    return 'Hello World! I have been seen %s times.' % redis.get('hits')

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True)

requirements.txt

この Flask アプリに必要な pip モジュール。

flask
redis

これらのファイルのあるディレクトリで、コンテナを起動させ、Flask アプリと Redis を起動させる。 コマンドは $ fig up のみ。オプションに -d をつけることでデーモン化する。

$ fig up -d
Recreating figtest_redis_1...
Recreating figtest_web_1...

イメージが作成されているか確認。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
figtest_web         latest              ba03d2cfe7e1        13 minutes ago      519.7 MB
orchardup/redis     latest              adf31ad75e8a        5 months ago        180.1 MB
orchardup/python    2.7                 cdc18ed4ed6f        7 months ago        514.3 MB

コンテナが起動したか確認。

$ docker ps -a
CONTAINER ID  IMAGE                    COMMAND                CREATED             STATUS              PORTS      NAMES
76d813354959  figtest_web:latest       "python app.py"        About a minute ago   Exited (0) 59 seconds ago     figtest_web_1
7fdd74decd6f  orchardup/redis:latest   "/usr/local/bin/run"   About a minute ago   Exited (0) 59 seconds ago     figtest_redis_1
1cb36b68a34d  orchardup/redis:latest   "/bin/echo"            About an hour ago    Exited (0) About an hour ago  elated_darwin

アプリが起動しているか確認。

$ fig ps
        Name              Command         State           Ports
---------------------------------------------------------------------
figtest_redis_1   /usr/local/bin/run   Up      6379/tcp
figtest_web_1     python app.py        Up      0.0.0.0:5000->5000/tcp

コンテナ内の 5000 番ポートで Flask アプリを起動させ、これがローカルの 5000 番ポートに対応しているようだ。 このコンテナの IP を調べて Curl して動作確認する。

$ boot2docker ip
192.168.59.103

IP まわりの詳しい設定やしくみはよくわかっていない。 ひとまず Curl で GET する。

$ curl http://192.168.59.103:5000 -D -
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 38
Server: Werkzeug/0.9.6 Python/2.7.6
Date: Tue, 06 Jan 2015 08:11:40 GMT

Hello World! I have been seen 1 times.%

ここまでで、fig を使って flask アプリ用のコンテナと redis コンテナを起動させアプリが動作することを確認した。

この操作による副作用

  • 新たにイメージ、コンテナが作成される。
  • コンテナの IP と指定のポート番号を使って、コンテナに HTTP 通信でアクセスできる。

フロントエンド開発環境を fig で管理する

設定ファイルを追加

やっとでここまで来た。手持ちのフロントエンド開発用リポジトリを fig で管理してみる。 作業は既存のリポジトリに、Dockerfile と fig.yml を加えて設定を書くだけで済むみたい。

ディレクトリ構成 (後ろに * がついたものはフォルダ)

$ tree -L 3 .
.
├── Dockerfile
├── Gemfile
├── README.md
├── bower.json
├── fig.yml
├── gulpfile.coffee
├── media
│   └── img*
├── package.json
├── src
│   ├── coffee
│   │   └── index.coffee
│   ├── jade
│   │   ├── index.jade
└── │   └── layout**
    └── sass
        ├── config.rb
        ├── index.sass
        └── lib**

依存モジュール

  • gulp: 全タスク管理
  • browserify: bower と JS モジュール管理
  • coffee-script: AltJS
  • jade: AltHTML
  • compass + sass: AltCSS
  • $ npm start でサーバ起動 + ファイル監視コンパイルタスクが始まる

ずっと昔に作った my-gulp-template を使って試すことにした。

Dockerfile

Dockerfile には環境構築のタスクを書いていく。イメージは公式にあった dockerfile/nodejs-bower-gulp を使う。

# Pull base image.
FROM dockerfile/nodejs-bower-gulp

# Install Ruby
RUN \
apt-get update && \
apt-get install -y ruby ruby-dev ruby-bundler && \
rm -rf /var/lib/apt/lists/*

# Create source dir
RUN mkdir /home/project

# Install
RUN gem install bundler compass
RUN npm install -g coffee-script

# Define working directory.
WORKDIR /home/project

# set up
ADD package.json /home/project/package.json
RUN npm install

ADD bower.json /home/project/bower.json
RUN bower install --config.interactive=false --allow-root

ADD Gemfile /home/project/Gemfile
RUN bundle install

ADD . /home/project

# Expose ports.
EXPOSE 8765
EXPOSE 35729

fig.yml

web:
  build: .
  command: npm start
  volumes:
    - "./src:/home/project/src"
  ports:
  - "8765:8765"
  - "35729:35729"

この操作による副作用

  • 無し

環境構築し、fig を起動する

コンテナを作成し環境構築する

以下のコマンドで Dockerfile の RUN タスクまでを完了させる。

$ fig build
Building web...
---> 38e49058fd98
Step 1 : RUN apt-get update &&   apt-get install -y ruby ruby-dev ruby-bundler &&   rm -rf /var/lib/apt/lists/*
---> Using cache
---> 2c927ade5af7
Step 2 : RUN mkdir /home/project
---> Using cache
---> f5bb325d867c
Step 3 : RUN gem install bundler compass
---> Using cache
---> ec10c5c101a7
Step 4 : RUN npm install -g coffee-script
---> Using cache
---> 5ac28713296e
Step 5 : WORKDIR /home/project
---> Using cache
---> a81a6754a622
Step 6 : ADD package.json /home/project/package.json
---> Using cache
---> cca41d3614d1
Step 7 : RUN npm install
---> Using cache
---> 415eb854198c
Step 8 : ADD bower.json /home/project/bower.json
---> Using cache
---> ee5bf7c1c077
Step 9 : RUN bower install --config.interactive=false --allow-root
---> Using cache
---> 7a01cf07e813
Step 10 : ADD Gemfile /home/project/Gemfile
---> Using cache
---> db438a312fd6
Step 11 : RUN bundle install # --path vendor/bundle --no-root
---> Using cache
---> 40299e5d1c7c
Step 12 : ADD . /home/project
---> 133f0a7e1f4f
Removing intermediate container 10fbde86e007
Step 13 : EXPOSE 8765
---> Running in 9b8b9b29ed67
---> b7ff885c89ef
Removing intermediate container 9b8b9b29ed67
Step 14 : EXPOSE 35729
---> Running in 9aa22049aef9
---> 50f7c8b3fcaa
Removing intermediate container 9aa22049aef9
Successfully built 50f7c8b3fcaa

タスクランナー開始

以下のコマンドで Dockerfile の環境構築が完了していたら fig.yml の command オプションを走らせる。

$ fig up
Recreating mygulptemplate_web_1...
Attaching to mygulptemplate_web_1
web_1 |
web_1 | > my-gulp-template@0.0.0 start /home/project
web_1 | > gulp --gulpfile gulpfile.coffee --require coffee-script
web_1 |
web_1 | [01:33:18] Requiring external module coffee-script
web_1 | [01:33:18] Requiring external module coffee-script/register
web_1 | [01:33:22] Using gulpfile /home/project/gulpfile.coffee
web_1 | [01:33:22] Starting 'default'...
web_1 | [01:33:22] Starting 'preBuild'...
web_1 | [01:33:22] Starting 'clean'...
web_1 | [01:33:22] Finished 'clean' after 23 ms
web_1 | [01:33:22] Starting 'copyMedia'...
web_1 | [01:33:22] Finished 'copyMedia' after 3.99 ms
web_1 | [01:33:22] Starting 'compass'...
web_1 | [01:33:22] Individual stylesheets must be in the sass directory.
web_1 |
web_1 | [01:33:22] Compass Error: { [Error: Compass failed]
web_1 |   plugin: 'gulp-compass',
web_1 |   showStack: false,
web_1 |   message: 'Compass failed' }
web_1 | [01:33:22] Finished 'compass' after 526 ms
web_1 | [01:33:22] Starting 'jade'...
web_1 | [01:33:22] Starting 'browserify'...
web_1 | [01:33:22] Finished 'browserify' after 12 ms
web_1 | [01:33:22] Finished 'jade' after 167 ms
web_1 | [01:33:22] Finished 'preBuild' after 724 ms
web_1 | [01:33:22] Starting 'watchify'...
web_1 | [01:33:22] 'index.coffee' has changed.
web_1 | [01:33:22] Finished 'watchify' after 7.72 ms
web_1 | [01:33:22] Starting 'watch'...
web_1 | [01:33:22] Finished 'watch' after 56 ms
web_1 | [01:33:22] Starting 'connect'...
web_1 | [01:33:22] Server started http://localhost:8765
web_1 | [01:33:22] LiveReload started on port 35729
web_1 | [01:33:22] Finished 'connect' after 70 ms
web_1 | [01:33:22] Finished 'default' after 862 ms
TODO:
Compass のタスクが失敗しているのであとで直そう。半年以上前に書いた化石 Gulpfile なので、後でリファクタもする。

ひとまず $ boot2docker ip で IP を取得し、ブラウザで http://<CONTAINER_IP>:8765/ を開くと、、、サーバが起動してコンパイルされたファイルが取得できる(^^) (Compass 調子悪いが) Jade ファイルと Coffee ファイルを更新すると、コンパイルできている(^^)

しかも、ローカルのディレクトリにはインストールしたはずの node_modules や bower_components フォルダは存在しない。 これらはコンテナ上でインストールされる。ソースファイルは、yml にマウントするパスを書いておくことでコンテナと共有(同期)することができるっぽい。

この操作による副作用

  • 新たにイメージを作成し、コンテナを起動する
  • コンテナは Dockerfile や fig.yml によって構成が管理されている

おまけ: DockerHub に登録

ついでなので、自分で作ったイメージを DockerHub に登録してみた。

webpack のイメージ。流行っているのかイマイチ分からないけど、プライベートで最近触り始めた:P

Docker を使った私見

あくまでフロントエンド開発目線。

  • Docker + fig が便利そうなのは掴めた。
  • Docker クライアントと fig さえ入っていたら、$ fig up だけでローカルの環境を一切汚さずに、専用の環境構築を全てやってくれるのは魅力。
  • フロントエンド開発環境が Docker で楽ちんになるかは微妙。
    • ローカルで問題なく環境構築できるなら Docker を無理に使うことはなさそう。
    • Docker の学習コストをかけてまで導入するコストをかけるより、フロントエンドの技術を習得したほうがよさそう(← わたしのことです)。
    • ものすごい特殊な環境(レガシーすぎるバージョンの環境)や大人数での開発で環境構築の設定にコストがかかりすぎると判断したときは、考えても良いかもしれない。
  • ここまで書いておいてなんだけど、やっぱり Docker の真の力はサーバアプリケーション含め複数のアプリケーションやクラスタ環境を管理するときだろうなぁ。
  • Vagrand + Chef よく分かっていなかったり、競合ツールとのは比較できない。
  • Mac の容量を食うので、イメージの容量を気にし始めたほうがよさそう。そしてこまめに使わないコンテナやイメージは削除する。

目的と用途によっては選択肢のひとつになりえそう。なーんて、月並みな感想で閉めることになってしまったけど、個人的には結構気に入った。とっかかりはフロントエンド開発で使えるかなって思いつきから触りだした Docker だったけど、フロントエンドに縛られずプライベートでは使っていこうかなと思う(・・)

photo: http://www.thegroundedkitchen.com/?p=765