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を通しておくと便利です。

フロントエンドの画像軽量化まとめ【2017年版】

こんにちは、面白法人カヤック フロントエンドエンジニアのごんです! 今回は、Webの画像の軽量化について、フロントエンドチームで使ってるツールややり方をまとめてみました。 画像の軽量化などで困ってる方の参考になればと思います。

なぜ画像の軽量化をするのか

Webサイトのローディング時間は、ユーザーの直帰率やコンバージョン率に関わる大切な指標です。 ローディング時間に関わる要因はさまざまですが、 特に画像は容量が大きいため、画像の軽量化をすることで表示速度の大きな改善を望むことが出来ます。 例えば、当ブログのある記事は、画像が全体の容量の約1/3を占めていました。

f:id:umai_bow:20170927154506p:plain

一般に、PhotoshopやIllustratorから出力された画像は、十分な色数やクオリティで出力されており、 ツールなどを使うことで、見た目をそれほど損なわず、容量を大幅に減らすことができます。

f:id:umai_bow:20170927154524p:plain

また、一部の画像形式には、メタデータと呼ばれる撮影機器や編集ソフトの情報など、表示には不要な情報が含まれることがあり、それらの情報を削る目的もあります。

f:id:umai_bow:20170927154456p:plain

PNG

PNGの特徴

PNGは、ざっくり言うと画素列をgzipした形式です。 色数を限定したインデックスカラーモードと、全ての色が使えるトゥルーカラーモードがあります。 一般的には、インデックスカラーモードのほうが容量が少なく、またgzipをかけるため、規則的な画像のほうが容量が小さくなる傾向があります。

f:id:umai_bow:20170927154519p:plain

後者の特徴は意外と馬鹿にできず、 パターンのある画像でも、わずかにノイズを加えると容量が大きくなってしまうという傾向を持っています。 例えば、先ほどの虹色の画像に知覚できない程度のノイズを加えた場合、10倍以上の容量増大となります。

f:id:umai_bow:20170927154521p:plain

なので、PNGの容量を少なくする戦略は大きく以下の2つになります。

  • 色数を減らす
  • 画素の起伏を減らす(規則的にする)

次の節でツールの紹介をしますが、これらの特徴を踏まえて、画像を書き出す段階で、容量が大きくならないように工夫することも重要です。

PNGの軽量化ツール

pngquant

代表的なPNGの軽量化ツールです。 色数を減らしたり、ポスタライズすることでPNGの軽量化ができます。 CUIツールなので、ターミナルから操作することが基本になりますが、いっぺんに軽量化をかけたり、ビルドタスクに組み込むときに便利です。 引数で0-100のクオリティや色数が指定できます。

pngnq

pngquantと似た機能ですが、ニューラルネットワークを使った最適化が行われるらしいです。

ImageAlpha

pngquantやpngnqをGUIで確認しながら適用できるツールです。 元画像と比較しながら軽量化をできるので、クリティカルな画像を圧縮するときや、クオリティにギリギリまで拘りたいときに便利です。 逆に、大量の画像を一気に処理する場合にはあまり向かないかもしれません。

JPEG

JPEGの特徴

JPEGがざっくりいうと、画像を細かく分割したあと、周波数空間に直して高周波成分(特に色相)を取り除いた形式です。 わかりませんね。 JPEGは実写やノイズの多い画像が向いている形式です。 PNGとは違い、劣化圧縮が基本でPhotoshopなどから保存するときにクオリティを0〜100の数値で選ぶことになります。 クオリティを下げすぎると、ブロックノイズと呼ばれる矩形のノイズや、モスキートノイズと呼ばれる波打つようなノイズが生じます。 以下の画像では、圧縮率を上げすぎてしまったため、船のマストの辺りにモスキートノイズが強く発生しています。

f:id:umai_bow:20170927154515j:plain

JPEGはPNGと異なり複雑な形式のため、人類が工夫してどうこうできることは少ないです。 目視で確認しながら、適切にクオリティを設定するのがベストですが、いくつか軽量化に便利なツールがあるため紹介をします。

JPEGの軽量化ツール

jpegtran

jpegtranはJPEGの最適化をおこなって、無劣化で容量の削減をすることが出来ます。 その他にも画像の反転や回転も出来ます。 多くのjpegエンコーダで利用されている、由緒正しいツールです。

mozjpeg

mozjpegはMozillaが公開しているjpegエンコーダです。 libjpeg-turboを基にしており、一般的なエンコーダより小さいサイズでエンコードができるとされています。 また、PNG画像にもmozjpegを利用することで、サイズの縮小や画質の向上が望めるとされています。

guetzli

guetzliはGoogleが公開しているjpegエンコーダです。 2017年に発表されたライブラリで、まだ新しく、情報や評価はまだ定まっていないように見えますが、Butteraugliと呼ばれる独自の評価関数を採用しており、一般的なエンコーダより20〜30%程度小さいサイズでエンコードができるとされています。 エンコードにはそれなりに実行時間がかかるようです。 lossyな圧縮だけでなく、qualityを100にすることで、無劣化の圧縮もできます。

SVG

SVGの特徴

SVGは、ざっくり言うとベクター画像のパスデータをHTMLのようなタグで表現した形式です。 実際には、パスデータだけではなく、ぼかしなどのフィルターやラスタ画像、アニメーションなども含むことが出来ます。 SVGは、PNGやJPEGなどとは異なり、バイナリではなくテキストのデータで、ベクター画像のため、拡大してもボケたりジャギったりしません。 また、CSSで色を変更できたり、JSで操作できるなど、他の画像形式にはない特徴を持っています。

f:id:umai_bow:20170927154527p:plain

SVGは一般的には容量が少ない傾向にありますが、専用のツールを使うことで、無駄なデータを除去し、軽量化をすることが出来ます。 また、テキストデータのため、サーバでgzip圧縮をかけて配信すると容量が大きく減るという特性があります(逆にPNGやJPEGは減りません)。 SVGを多用するサイトでは、サーバのgzip圧縮を有効にするようにしましょう。

SVGの軽量化ツール

SVGO

SVGOをNodeベースのSVG最適化ツールです。 不要なIDやクラス名を除去や、パスデータの最適化やマージなど、かなり色々なことをやってくれます。 SVGOによる最適化は非劣化圧縮ですが、メタデータなどが抜け落ちることで、セマンティックな情報が抜け落ちることがあるかもしれません。

まとめ

今回は個別の画像形式について、軽量化の方法をまとめてみました。 画像自体の軽量化だけではなく、転送量の削減に目を向ければ、サーバサイドで出来る対策や、スプライト化・遅延ロードなど、様々な方法が考えられます。 適材適所で利用し、軽いサイトを作りましょう〜。

おしらせ

カヤックでは、画像容量の1byteまで拘りたいフロントエンドエンジニアを募集しています。 応募は下記からどうぞ!

【新卒採用】 https://www.kayac.com/recruit/fresh

【中途採用】 https://www.kayac.com/recruit/career

【インターン】 https://www.kayac.com/recruit/intern