Dockerで開発環境構築を10倍楽にしたはなし

Lobi事業部 サービス基盤チームの長田です。

最近プロジェクト内で使用する開発環境にDockerを利用するようになったので、その紹介をします。

Dockerにしたってどういうこと?

公開済みのWebサービスに変更を加えて動作確認をする場合、本番環境でそれを行うわけにはいきません。 ほとんどの場合はローカルマシンでWebサービスの全体または一部のコピーを動かして動作確認を行うことでしょう。 その後ステージング環境などの他の開発メンバーも触ることができる環境で動作確認やQAを行い、 問題がなければ晴れて本番環境に反映、という流れが一般的かと思います。

この「ローカルマシンでWebサービスのコピーを動かす」部分にDockerを利用している、ということです。

Dockerにしてどうなった?

Before

開発環境構築に1〜2日かかっていた

After

開発環境構築がランチに行っている間に終わるようになった

利用者の声

  • 新しくジョインする方の環境セットアップが格段に楽になった
  • 以前は、開発環境に問題が起きるとその修復だけでかなり時間とられていたが、そういうことがなくなった
  • それはすてきなことです

動機

とにかく時間がかかる

Lobiは2010年にサービス開始しました。 開発環境構築にも当時からの蓄積があります。 「歴史的な理由でxxが必要」とか、「このミドルウェアのバージョンはxxじゃないとだめ」とか、 そういう細かい決まりごとのようなものが複数存在します。 これらを新しく来たエンジニアに説明するのは容易ではありません。

細かい問題を解決していくと、結局開発環境のセットアップに 丸1〜2日 かかってしまっていました。 Webサービス開発に慣れていない新人エンジニアに至っては、まともに動くようになるまで 1週間 かかった、なんてこともざらにありました。

セットアップは最初にしかやらないので、ベテランエンジニアでもやり方をすっかり忘れてしまっていたりします。 なによりこういう細かいハードルは解決してもその恩恵は自分の環境にしかないので、時間の無駄になることがほとんどでした1

各人の開発環境が微妙に異なる

かといってchefやansibleを使って開発マシンをセットアップしようとしても簡単にはいきません。 各自の開発マシンの構成は微妙に異なるため、それらの際を吸収するためのメンテナンスコストのほうが大きくなってしまいます。 「日頃の行い」のせいでその人のマシンでしか発生しない問題も・・・。

Crypt::OpenSSL::X509インストールできない問題

ピンポイントな動機としては、OSX上でPerlのモジュールであるCrypt::OpenSSL::X509がインストール出来ないという問題がありました。

https://github.com/dsully/perl-crypt-openssl-x509/issues/46

Makefileをいじったりすればなんとかなるといえばなるのですが、これもまた無駄な時間に分類するべきものでしょう。


諸々の問題を解決するために、「各種ミドルウェア・アプリケーションをDockerコンテナ上で動かす」という選択をするに至りました。

構成

Docker Composeで各アプリケーションを起動

Docker Composeで各アプリケーションごとにコンテナを用意しました。 docker-compose.ymlは以下のような感じです。 なお、説明に不要と思われるオプション値については適宜省略しています。

version: "2"

services:
  nginx:
    build: ./nginx
    image: "lobi/openresty"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ...
    depends_on:
      - app
      - memcached
      - katsubushi

  app:
    build: ./app
    image: lobi/app
    volumes:
      - "./..:/lobi"
    depends_on:
      - mysql
      - redis
      - memcached
      - katsubushi
      - gearmand
      - gearman-worker
    command: plackup -R ./lib -Ilib -E development script/app.psgi

  gearmand:
    image: lobi/app
    ports:
      - "4730:4730"
    volumes:
      - ...
    command: gearmand --port=4730

  gearman-worker:
    image: lobi/app
    volumes:
      - ...
    depends_on:
      - mysql
      - redis
      - memcached
      - katsubushi
      - gearmand
    command: perl -Ilib script/boot_gearman_worker.pl

  mysql:
    image: "mysql:5.5"
    ports:
      - "3306:3306"
    volumes:
      - "./mysql:/var/lib/mysql"

  redis:
    image: "redis:2.8"
    ports:
      - "6379:6379"
    volumes:
      - "./redis:/data"

  memcached:
    image: memcached
    ports:
      - "11211:11211"

  katsubushi:
    image: katsubushi/katsubushi

各種ミドルウェア

nginxやmysqlなどのミドルウェアは公式配布されているDocker Imageを利用すればいいので簡単ですね。

各アプリケーションには依存関係がある場合があるので(例:Perlアプリが立ち上がっていないとnginxでproxyできない)、 depends_onで起動順を指定しておきましょう。

このあたりはDocker Composeを使う際の普通の方法です。

mysqlおよびredisのデータはコンテナ外に保存しておきたいので、 各データ保存ディレクトリをvolumesでホストマシンと共有しています。

Perlアプリケーション

Perlアプリケーション用のコンテナにはamazonlinuxを使用しています。 本番環境でもAmazon Linuxを使っているので、「手元では動いたのに〜」という事が起こりにくいのも嬉しいポイントです。 問題になっていたPerlモジュールのCrypt::OpenSSL::X509もすんなりインストールできます。

Perlアプリケーションのソースコードはホストマシン上にあり、 docker-compose.ymlでvolumes指定して共有しています。 ホストマシン上で普段使っているエディタで編集すれば、コンテナからもその変更が見えるわけです。

ソースコード変更時のアプリケーション再起動は、 plackupコマンドに-Rオプションを指定すればファイル変更時に自動で行ってくれます。 Dockerを使う使わないにかかわらず、plackアプリを開発する上ではお世話になるオプションですね。 詳しくはplackのドキュメントを参照して下さい。

スクリプト&テスト実行

開発中に必要なスクリプト(サンプルデータの生成とか)やテストの実行もコンテナ上で行います。 基本的にはdocker-compose runにコマンドを渡せばOK。

$ docker-compose run --rm app $command

よく使うコマンド実行には専用の実行スクリプトを用意していたりします。 たとえばテスト実行するために以下のようなスクリプトを用意して、

#!/bin/bash

test="$1"
cd path/to/docker/dir
docker-compose run --rm app carton exec -- prove -lr $test

こんな風に呼び出しています (dpvは上記スクリプトの名前2)。

$ dpv t/api/post_chat.t

メリット

ハマり所を共通の問題に

個々人の開発マシン上に直接開発環境を構築していた頃は、 その開発マシン固有の問題が発生することがありました。 インストールされているライブラリのバージョンとか、パッケージマネージャの設定とか、そういう日頃の行いによるものです。

DockerコンテナにしてDockerfile他セットアップスクリプトをリポジトリ上で共有するようになったので、 何か問題が起こった場合は誰かが解決すれば別の誰かもうれしいという状態になりました。

壊れてもすぐに作り直せる

うっかり環境をぶっ壊しても、コンテナを終了して再度立ち上げれば元通りです。 安心。

サーバーサイドエンジニア以外でも開発環境をセットアップできる

Front-end Engineerなど、サーバーサイドのコードをいじるわけではないけどサーバーアプリを動かしたいという需要がある人も、 開発環境のDocker化によって簡単に環境構築ができるようになりました。 本番環境等にアクセスしてローカルプロキシを使ってJSなどを書き換える方法もありますが、 本番未実装の機能の場合にはこの方法は使えませんし、データの用意なども手元に環境があったほうが何かと便利です。

また、

「サーバー側のコントローラーをちょっといじればいいだけなんだけど、サーバー側だから手が出ないな〜開発環境無いしな〜」

が、

「フロント直すついでにサーバー側もちょろっと直しとくか〜」

に変わりました(そのスキルがある人限定ではありますが・・・)。 もちろん本番環境に反映する前にはCIと動作確認とコードレビューが入るので、 取り返しのつかないコードが紛れ込んでしまうことはまずありません。

cpanfile.snapshotが手元でできる

Lobiではcpanモジュールのversioningにcartonを使用しています。 実行環境による影響をなくすため、以前はcpanfile.snapshotの生成をAWS上のAmazon Linuxインスタンスで行なっていました。 これをDockerコンテナ上で行うことで誰でも手元の環境で生成できるようになりました。

デメリット

Dockerの知識が必要

ただ開発環境を使うだけであればDockerのことは知らなくても済みます。 セットアップ操作は全て自動化してあるので。

ただ、何か問題が発生した場合はどのような仕組みで開発環境が動作しているのかを知る必要があります。 「共通の開発環境なので誰かが直せばみんな直る」とはいいつつも、「誰か」がいつも同じ人になってしまうというリスクはあります。

Dockerに限らず、新しいツールや手法を導入した際にセットでついてくる問題ですね。

重い

複数のコンテナを起動することになるため、ホストマシンのリソースを食いがちです。 Lobiには複数のPerlアプリケーションがあるため、それらを別々のコンテナで動かすことになります。 開発中に全てのPerlアプリケーションが必要になるわけではないので、適宜不要なコンテナをstopするなどの対処療法くらいしか無いのが現状です。

お金で解決するという手も無きにしもあらず。

Windows環境では使えない

いまは開発環境として全員Macを使っているのですが、Windowsマシンでは今回用意したDocker開発環境が動かないことがあるようです。 絶対に動かないのではなく、動かないことがある、というのがまた嫌なところです。 全員Macである以上実害はないということで、原因調査は先のばしになっています。

おわり

何事も楽になるのはよいことですが、そのための道程は険しいものです。 中途半端に手を付けてしまっては余計に手間が増えてしまうこともあります。 「Dockerによる開発環境構築」も実用レベルということで一段落はしましたが、 これからもよりよい環境にするべく改良を加えていきたいと思います。

カヤックでは開発スピードを上げたいエンジニアを募集しています!


  1. もちろん、そういう経験が後に生きるということもなくはないですが、しなくてもいい苦労は極力避けたいものです。

  2. こういうプロジェクト固有のスクリプトは、direnvを使ってPATHを通しておくと便利です。