モバイルファーストなサービス開発におけるDockerの活用術

こんにちは!ポイント交換サイト「PeX」の開発を行っていますVOYAGE MARKETINGの加藤です。
Crewからはちゃむと呼ばれています。

少し長くなりますので先に本エントリーの概略を3行でまとめると

  • Docker成分多め
  • 作ってみた
  • 後半でテーマ深掘り

です。
最後までお付き合い頂けると嬉しく思います。

はじめに

タイトルは若干釣り気味ですが、個人的にDockerをwatchし続けています。

先月のDockercon 16で、ついにDocker for  (Mac|Windows)がpublic betaになりましたね。
以前はDocker Toolboxで必要だったVirtualBoxを必要とせず、それぞれのOSでのネイティブ仮想化技術(MacOSはxhyve、WindowsはHyper-V)で動作するようになりました。
ますますDockerが使いやすくなってきていますね。

さて今回のお話の本題に入りたいと思いますが、本ブログをご覧になっていらっしゃるエンジニア・デザイナーの方はどこまでモバイルファーストを意識されていますでしょうか?

VOYAGE GROUPでは幾つものサービスを展開しておりますが、スマートフォン・タブレットでの利用を想定して社内に多数の実機を用意して検証を行っております。

f:id:katzumi:20160721184416j:plain

PCとは違いスマートフォン・タブレットではディスプレイサイズの制約や種類も多いことから実機での検証は欠かせないでしょう。サービスによってはレスポンシブなページを用意したり、アプリケーションで専用のUIを提供していたりするのではないでしょうか?
ここまでは普通に取り組みされていることかと思いますが、でも開発で利用している実機は本当にユーザーの利用している環境とイコールか?というとそうでないと思います。
実際にサービス利用されているPCユーザーとモバイルユーザーの利用環境の違いとして、ディスプレイサイズ以外にネットワーク環境も大きく異なることを忘れてはいけません。
このネットワーク環境の違いは普段PCでサービス開発をしていると意外に忘れがちで、いざ実際にキャリア回線を用意しようとするとコスト面で対応できないケースが多いです。
社内のモバイルの実機もSIMなしでWifi運用していますので、ネットワーク速度は実環境のそれとは異なります。
このネットワーク速度の違いを吸収する為にキャリアのネットワークをシミュレーションするネットワーク機器があったりします。

さて前置きが長くなりましたが、今回は簡易的に回線シミュレーションを行えるツールをDockerで構築してみました。

仕組みについて

構成はごくシンプルです。
Dockerのコンテナで  squidサーバーを立てます。
そのsquidサーバーに対して通信速度を制限したものを回線シミュレーションを行ないたいクライアント側でプロキシとして利用します。

f:id:katzumi:20160715150051p:plain

こちらの通信速度のコントロールはDockerの仮想環境が提供しているリソースの分離(isolation)を利用しています。
リソースの分離(isolation)という言葉はあまり耳慣れない方がいらっしゃるかと思いますが、Dockerを代表とするコンテナ型仮想化の特徴となりますので軽く説明したいと思います。

サーバー仮想化では物理的なCPU、メモリ等のリソースを分割(時分割に近いイメージ)して動くと説明されるケースが多いですが、コンテナ型仮想化ではユーザー・プロセスに対してリソースを分離(空間分割のイメージ)すると明示的に説明されます。
これはサーバー仮想化がハードウェアウェアレベルの仮想化を行ない、仮想マシン(ゲストOS)間でリソースが分離されていることに対して、コンテナ型仮想化はOSの仮想化を行っており、コンテナはホストOSのプロセスとして扱われている中でも複数コンテナが独立して動く仕組み(リソースの分離)が用意されているからです。

この各コンテナのリソースが相互参照できない様に資源管理する仕組みがNamespaceとcgroupというLinux  Kernelが持つコンテナ技術です。
DockerはNamespaceとcgroupを利用しており、CPU、メモリの他にネットワークも分離(仮想化)することができます。ネットワークが分離されていることで、該当のコンテナのみ(ホストOSも含まず)に対して通信速度を変更(制限)することが出来る特性を利用しています。

Traffic control proxy

github.com

こちらが今回作成したDockerfileです。 回線シミュレーションできるパターン毎にプロビジョニング出来る様にDocker ComposeのYAMLを用意しています。 docker run時に環境変数を指定して、コンテナ内部の挙動を変更させられなくもないですが、予めイメージファイルとして組み込んでおいて必要とするコンテナをドカドカと立ち上げる方が、Immutableにできて良いと考えています。 docker-compose.ymlでDockerfileのbuild argsで回線シミュレーションするネットワークタイプを指定しています。

こちらのdocker-compose.ymlのargsの指定を行うには、Docker Composeの1.6からとなり、YAMLの記法のバージョンをv2にする必要があります。 Docker自体のbuild argsのサポートは以前からありましたが、Compose側の対応がされずにいました。

github.com

上記のIssuesで自身も+1してウォッチしていましたが、ようやくのサポートになります。 Docker自身の開発スピードが早すぎて、Composeも含めて周辺ツールの連携が追いついていない印象があります。 今後、Compose自体にもどんどん機能が追加されていくと思われるので、はやくv2のフォーマットに慣れておく必要があると思います。

build argsによってentry pointのシェルに組み込んでいる帯域制限のコマンドを選択して組み入れています。 上記の仕組みの所で説明したcgroupsのサブシステムであるnet_clsを制御するコマンドがtc(Traffic controller)です。

コマンドの意味を全て説明するには本エントリーでは長くなりすぎる為、割愛しますがqdiscと呼ばれるキューイング規則を設定することで帯域制限やパケット遅延や破棄を制御することができます。qdisc等の説明についてはこちらが参考になります。

Linux TC (帯域制御、帯域保証) 設定ガイドライン | エンジニアブログ | GREE Engineering

Docker上で tcコマンドを使う上での注意点としてはdocker run時に--privileged オプションを指定することです。compose経由の場合はYAMLで既にオプションを指定していますのでそのまま利用できます。

Demo

概要の説明が終わったところで、デモをしていきたいと思います。
簡単に利用出来るようにDocker Hubへ登録しています。
Docker環境があれば以下のコマンドのみで試せます。

$ docker run --rm -it --privileged -p 3128:3128 katzumi/tc-proxy:3g

上記コマンドで3G回線のシミュレーションを行えるProxyが起動します。
Proxyはホストのポート3128で利用出来るようになりますので、検証したい端末からプロキシのポートに指定します。プロキシーのIPは DockerホストのIPを指定します。
Docker環境をDocker Toolboxで構築した場合、ホストIPは以下のコマンドで調べられます。

$ docker-machine ip
192.168.99.100

Docker for Macの場合はDocker Toolboxとは違い、VirtualBoxの仮想環境が不要となりますのでlocalhostのIPがDockerホストのIPとなります。

上記コマンドで回線の種類やProxyのポートを変更する場合、それぞれ以下の様にします。

  • 回線種類の変更
    katzumi/tc-proxy:(回線種類)
    Docker Hubに登録しているイメージはタグ毎に回線種類を分けてbuildしています。
    例)4G 回線
    katzumi/tc-proxy: 4g

  • ポート変更
    (ホストポート):3128
    例) 8080
    8080:3128

検証したい端末のプロキシ設定ができたらブラウザでアクセスしてみてください。
 Dockerを起動したコンソールログに以下の様な出力がされればOKです。

Proxyを終了する場合はコンソール上でCtrl+Cしてください。

回線スピードによるレスポンスの違い

まずは何もしていない自宅のケーブルテレビのインターネット回線を使った場合のレスポンスです。 f:id:katzumi:20160709194212p:plain

次にtc-proxyの4Gネットワークのコンテナ経由でのレスポンスです。 f:id:katzumi:20160715153337p:plain

比較でわかりやすい様に、1回目の計測の結果を残したまま2回目の計測を行っています。
図のタイムライン上にコメントを入れていますが、レスポンスタイムが大幅に拡大していることがわかると思います。

まとめるとこんな感じです。

計測値 Cable 4G 比率*1
Finish 1.8 s 8.55 s 4.75
Load 1.57 s 6.98 s 4.45
DOMContentLoaded 628 ms 2.39 s 3.81
First response *2 141 ms 761 ms 5.40

2秒かからなかったのが、5倍ぐらい遅い8.5秒となりました。
こちらのレスポンスの違いがサービスの使い勝手に大きく影響します。
又、ページの作りが悪く

  • 外部CSS/JavaScriptをheadタグ内で大量に読み込む
  • レスポンシブデザインで、PC用画像を縮小表示
  • 上記に併せてレンダリングブロックがされるHTML構造

となっていると、更に体感速度が悪くなり利用者へのストレスがより高まる原因となります。

回線スピードによっては見過ごされがちなHTML上の問題点が、顕著に現れますので回線スピードを併せて確認することが大事かと思います。

より便利なツール達

HTMLの問題点をより詳細に深掘りしたい、毎回ネットワーク設定を変えて確認するのは面倒くさいという貴方に、オススメのツールを紹介いたします。

  • PageSpeed Insights
    HTMLのレスポンススピードを確認して適切にアドバイスをしてくれます。
    Googleはページランクの要因にページ表示速度を見ていると公表していますが、どういった指標でページ表示スピードを判定しているかがわかります。
    又、モバイルでのUX(ユーザー・エクスペリエンス)もチェックしてくれますので、そちらも併せて改善しておくと良いでしょう。

  • Google Chrome ウェブブラウザ
    今更敢えて紹介しなくても良いくらいメジャーなブラウザですw
    今年の4月にブラウザシェア1位(国内は2位)を獲得したようです。
    Demoのレスポンス計測にも利用していましたが、実はこれ単体で回線速度を変更することが出来るのです!!(ドヤァ

今までの流れを全く無視してGoogle Chromeの回線速度の変更の仕方をご説明したいと思います。
やり方としてはレスポンス計測の画面からタイムラインの上部にあるプルダウン(初期表示はNo throttling)から変更できます。
レスポンス計測の画面を表示するにはメニューから表示 > 開発/管理 > デベロッパー ツールで表示して、デベロッパーツールが表示されたら、Networkを選択します。

f:id:katzumi:20160717103704p:plain

プルダウンを開くとPresetsにいくつかの回線種類が予め登録されていますのでそちらを選択します。
tc-proxyとの違いとしては予め用意されている回線種類に対してのそれぞれの通信速度と遅延が微妙に異なります。
ここら辺は日本とキャリア回線の規格の違いや回線品質による実効速度をどう見るか?によるものとProxy経由にすることのオーバーヘッド分が含まれていると思います。
あと一つ大きな違いとして、Google Chromeでは通信速度を上りと下りとで細かく制御していますが、tc-proxyはアウトバウンドの帯域を一括して制限しています。
ネットワークインターフェースを追加して帯域制限を分けられなくはないと思いますが、今回は簡易版ということで。。

あれ?今回のネタはDockerと関係なくない?

うん。そうなんだ。
済まない。

今回のネタはcgroupとDockerを組み合わせてた活用方法を紹介するつもりでしたが、リソースを制御するという地味な話題になるので取っ付き易いネタを提供したという裏話*3がありました。。
NamespaceとcgroupはLinux標準で使える機能ですが、Dockerを使うことでより簡単にリソース制御を行えることを「実証してみた」というお話でした。

Dockerでのリソース・コントロールの使いドコロ

こちらが裏テーマになりますが、cgroupとDockerを組み合わせた活用方法についてお話をしたいと思います。
例えば以前のRedisのバージョンではmaster-slave間のデータの再同期が発生した場合にI/O、ネットワーク帯域を使いきってしまう問題がありました。ミドルウェア側でリソースの制限を行う手段がなく、バージョンアップされるまで多くの開発者を悩ませ続けていました。
そういった場合にコンテナに閉じ込めてリソースを分離させて、cgroupでCPU、メモリ、ディスクI/Oやネットワークの制限をすることでシステムを安定的に稼働させる手段を提供できるかと考えています。
アプリケーションやミドルウェア側で、細かくリソースの上限を設定出来ないケースでもQoS(Quality of Service)を実現することが出来ます。
特にシステムがマルチテナントでサービスを提供している場合に、一つのテナントが大暴れしても他のテナントに対して影響を及ばさない様にするにはDockerは相性が良いと考えています。

感想など

最後に本エントリーを投稿するにあたっての感想で締めくくりたいと思います。

  • Google Chormeは
    Webアプリを確認するにはGoogle Chromeは使いやすいと改めて認識しましたw
  • DockerHubのAutobuild時にbuild argsが使えないのが残念
    DockerHubにイメージを登録しましたが、Autobuildの際にブランチやタグを切らないと使えないのが残念でした。
    元々バージョンが異なるものをイメージとして登録することがほとんどなので、今回の様にプロビジョニングの違いをイメージとして扱うことは稀なことかも知れません。
  • キャリアの通信速度制限は絶望的遅さ
    キャリア回線でパケットを使いすぎた際のアレを実装してみました。
$ docker run --rm -it --privileged -p 3128:3128 katzumi/tc-proxy:4g-limit

是非その絶望的な遅さを体感してみてください。パケットのご利用はご計画的にw

*1:Cableを1とした場合

*2:トップページ単体のレスポンスタイムです。

*3:今回紹介したGoogle chrome以外にもXCodeやAndroid エミュレータでも通信速度を変更する機能が標準サポートされていることを後から気づいたというのは内緒の話