koni blog

東京のウェブエンジニア koni です!ウェブサービスをガシガシ作っていきます!

Google App Engine PHP 7.2 がリリース!これまでの違いと利用手順

この記事はAutoScale Advent Calendar 2018 - Qiitaの11日目です。

2018年7月12日、Google App Engine(GAE) がついに PHP 7.2 に対応しました!

SocialDogや、AutoScaleのウェブサイトなど、ちょっと動的な要素があって、キャッシュが効きそうなページでは、Google App Engine を積極的に活用してきました。*1

いままでGAEではPHP5.5しか対応していなかったので、PHP7.2 への対応で一気に未来にきた感じがあります。 ちなみにPHP5.6, 5.7, 7.1 など間のバージョンには対応していません。GAE でPHPを使う場合は、PHP5.5か7.2の2択です。

この記事では既存のPHP5.5のプロジェクトをPHP7.2で使うための設定を見てみようと思います。

Google App Engine PHP5.5 とPHP 7.2 の主な違い

  • 自由度が大幅アップ!
    • ランタイム環境が第一世代から第二世代(gVisor ベースのコンテナサンドボックス環境)になった
    • 拡張の導入が可能に。しかも簡単
    • URL取得関数を使わなくても外部ネットワークアクセスが可能に
    • ファイルシステムへのアクセス権が付与。/tmpにアクセス可能に
  • app.yaml の仕様変更
  • Compose の自動インストール(composer.json をルートに置くだけで自動的にデプロイ可能に)
    • .gitigonore している場合は無視されるそうなので通常は特段の対応不要?
  • App Engine 独自の機能へのアクセスの廃止
    • GAE は Google Cloud Platform となる前からあるサービスであるという歴史的経緯から残っていた、GAEの中の Memcached ,BlobStore, Mail, Taskキュー, 検索、ログサービスなどが廃止になりました。今後はCloud Storage や Cluod Datastore などのGCPの他のサービスを使ってということのようです。
    • ただし、メール送信*2や、Memcache*3、検索*4などGCPにはないのに廃止されている機能もあるので、これらに依存している場合は、PHP7.2への以降はかなり大変そうです。
  • ローカルでのテストコマンド dev_appserver.py の廃止

Google App Engine PHP5.5 とPHP 7.2 で変わらない点

  • 無料枠の定義
  • SLA(ただしPHP7.2 はまだベータなので正式版になったら変わらないはず)
  • デプロイコマンド(gcloud app deploy

PHP 7.2 に移行できない環境

App Engine 独自の機能を使っていた場合は、PHP7.2環境への移行はかなり困難そうです。すでに動いているプロジェクトなのであれば今は諦めて、当該の機能が GCP でフルマネージドなサービスが出るまで待つ、というのもありかと思います。

app.yaml の仕様変更

以下のパラメータは廃止予定なので削除します。

threadsafe, api_version, application_readable, builtins, libraries

runtimeをPHP7.2にします。

runtime: php72

handlers の子の scriptscript: autoのみ設定できるようになりました。

生のPHPを直接置くよりもフレームワークのエントリーポイントのファイルをひとつだけ指定してルーティングするのが普通なので、この修正は納得感があります。

以下のように指定すると、/images 以下は直接呼びつつ、それ以外は index.phppublic/index.phpを勝手に呼んでくれます。

handlers:
- url: /images
  static_dir: static/images

- url: /.*
  script: auto

詳細は以下を参照:

app.yaml ドキュメント

拡張機能の追加

拡張機能を追加したい場合は、php.iniをおいて、以下のようにします。

extension=memcached.so
extension=grpc.so
extension=protobuf.so
extension=mongodb.so
extension=imagick.so
extension=opencensus.so
extension=redis.so
extension=stackdriver_debugger.so

ローカルでのテスト

dev_appserver.py が廃止されたので、以下のコマンドでテストします。

$ php -S localhost:8080

デプロイ

いままでどおりです。

$ gcloud app deploy --project [Project ID]

まとめ

PHP 7.2 では、PHP5.5 と比べてかなり自由度が高くなっています。

特に拡張が入れられる or 予め入っているというのと、ファイルシステムにアクセスできるあたりは、より柔軟に GAE を活用できるようにしてくれそうです。

一方で、既存の GAE のみの機能が使えなくなっているので、既存の PHP5.5 でそれらを使っていた場合は移行はかなり難しくなりそうです。特に Memcached を使っていた場合はかなりきついのではないでしょうか。

PHP5.5 から PHP7.2 とかなりバージョンアップしているので、パフォーマンス面でも改善しそうです(未検証)。

参考資料

*1:SocialDog 本体は別のサブドメインでGCEでうごいています

*2:ドキュメントではSendGridやMailjetがおすすめされている

*3:Radis Labs Cloud がおすすめされている

*4:ドキュメントには ElasticSearch on GCE を使ったらどう?と書いてあるが、PaaS とはなんだったのかw

【具体例で学ぶ】SaaS KPI 入門 2018(MRR, ARR, Customer / Revenue Churn Rate, LTV, CPA)

この記事はAutoScale Advent Calendar 2018 - Qiitaの6日目です。

こんにちは、小西です。

スマートで効率的なTwitter アカウント管理ツール「SocialDog」というサービスを運営しているAutoScale という会社の代表をしています。

SaaS ビジネスでは、最重要指標として、MRR, ARR, Customer / Revenue Churn Rate, CPA, LTV などといった指標があります。

SocialDog もサブスクプション型の SaaS 型サービスなので、これらの指標を日々見ています。

今回の記事では、できるだけ具体例を交えながら、なぜこれらの指標が必要なのか、どんな指標なのか、どうやって使うのか、を書いてみます。

なぜこれらの指標が必要?

売上は様々な施策、影響の最終結果

ふつう、SaaS のサービスに限らず、事業の成否を決めるのは、月次・年次の売上の金額だと思います。 そこで売上の推移をグラフにしたり、前年比・前月比で何パーセント成長したかを計算したりしますが、その数字は

  • 新規のお客さんの増減
    • 広告費の増減
    • バズった
  • プラン設計の変更
  • 割引キャンペーンなどの実施
  • サービスの新機能の追加
  • メール配信を行ったことで、解約を忘れていたユーザーの解約が増えた

などなど、期間中に行ったすべての要因が合わさった数字なので、売上の増減だけを見ても「何がうまくいったのか」「本当にうまくいっているのか」、売上の中身はよくわかりません。

月額プラン・年額プランがある場合

SocialDog のように月額プランと年額プランがあるようなサービスの場合、年額プランの売上は初月に入るものの、サービスは翌月から11ヶ月提供しなくてはならないため、単純に売上だけで見てしまうと、売上だけ最初に計上され、あとからコストだけがかかることになってしまいます。

また、年額プランの顧客の割合によって、初月の売上が大きく変わります。

例えば、年額プランは年額10万円、月額プランが月額1万円の場合、ある月に12人の新規顧客がいたとして、年額プランの人が2人と4人だと以下のような違いがあります。

月額(人) 年額プラン(人) 初月売上(万円) 2-12ヶ月目売上(万円) 1年間の合計売上(万円)
10人 2人 1 * 10 + 10 * 2 = 30 10 30 + 10 * 11 = 140
8人 4人 1 * 8 + 10 * 4 = 48 8 48 + 8 * 11 = 136

人数がたった2人変わっただけ(年額プラン選択率が15%変わっただけ)で、初月の売上は30万円と48万円でかなり異なっています。 初月の売上は1.6倍ですが、1年間の合計売上はほとんど同じです。これでは初月の売上で好調!と判断するのは危険そうです。

MRR (Monthly Recurring Revenue/月間定額収益)

そこで、売上を予測しやすくし、売上に似た数字「MRR」という指標を導入します。

MRRは月間で決まって入ってくる収益です。

サービスにもよりますが、毎月の売上は、以下2種類に分類されます。

  • 毎月決まって発生する売上(月額プラン、年額プランなど。Product Revenue や Reccuring Revenueという)
  • 上記以外の「1回だけ」発生する売上(初期費用やポイント購入、追加枠購入など。Service Revenueという)

このうち上については、毎月決まった額が入ってくるので予測が立てやすく、先月と今月の数値を比較することで解約率(≒定着率)や顧客一人あたりの売上の変化がわかりやすいので、MRR では上についてのみを対象に計算します。

月額のプランの場合はその金額、年額プランの場合は 1/12 を含めます。 例えば年額12万円のプランの顧客が一人増えた場合、初月に12万円の売上が計上されますが、MRR では1万円だけ計上されます。この方法なら上述の初月だけ売上高く見える問題は起きません。

したがって、有料ユーザーが増えている局面では、ふつう MRR は売上より低くなります。

MRR の分類

チャーンレートを計算するためには、MRR を①解約、②下位プランへの変更など、③上位プランへの変更、④新規契約の4つに分ける必要があります。 このうち④の新規契約はチャーンレートには関係ありません。

  • Churn チャーン・解約(前月は有料顧客だったものの今月は有料顧客でない場合。MRR:有→0)
  • Contraction コントラクション(ダウンセル。前月も今月も有料顧客だがMRRが下がった場合。MRR↘)
  • Expansion エクスパンション(アップセル。前月も今月も有料顧客だがMRRが上ががった場合。MRR↗)
  • New 新規契約(前月は有料顧客でなかったものの今月は有料顧客の場合。MRR:0→有)

例えば、以下の図のようになります。

f:id:konisimple:20181205003801p:plain

前月のMRRが100万円だったとして、①Churnが5万円、②Contractionが3万円、③Expansionが4万円、④Newが15万円の場合、今月のMRRは、100-5-3+4+15=111万円となります。

ARR(Annual Recurring Revenue/年額定額収益)

(ARR) = (MRR) * 12

1年に決まって入ってくる収益です。 これは単純にMRRの12倍(1年は12ヶ月なので)となります。

サービスの性質によってMRRかARRのどちらかを追うことになります。 AutoScale では月額プランのユーザーのほうが多く、月次の売上と比較しやすいので MRR を見ています。

Churn Rate (チャーンレート/解約率)

ざっくりいうと前月の有料顧客が、今月はどのくらい課金してくれているかの割合です。

SaaS ビジネスにとって、Churn Rate は非常に重要な指標です。 SaaS というのは、毎月お支払いしてもらえるからこそパッケージ型の製品よりも月額は安価で提供できますが、すぐに解約されてしまっては回収できなくなってしまいます。

チャーンレートは毎月掛け算される複利の計算になるので、ちょっとした数字の違いで大きく結果が変わります。 例えば、チャーンレートが5%だと平均継続月数(∝LTV)は20ヶ月ですが、チャーンレートが6%だと16.7ヶ月まで減ってしまいます。

このチャーンレートには顧客数ベース(Customer Churn Rate/カスタマーチャーンレート)と金額ベース(Revenue Churn Rate/レベニューチャーンレート)の2種類があります。それぞれの定義は以下になります。

Customer Churn Rate(カスタマーチャーンレート)

(Customer Churn Rate) = (前月に有料顧客だったユーザーのうち今月も有料顧客だった数) / (前月の有料顧客数)

Revenue Churn Rate(レベニューチャーンレート)

(Revenue Churn Rate) = (前月の有料顧客からの今月のMRR) / (前月のMRR)

Gross Revenue Churn Rate

(Gross Revenue Churn Rate) = (Churn + Contraction) / (前月MRR)

Net Revenue Churn Rate

(Net Revenue Churn Rate) = (Churn + Contraction - Expansion) / (前月MRR)

Net のチャーンレートには既存顧客のアップセル施策の影響が含まれるため、解約する人を減らす系の施策の効果を見るときなどは、Gross Revenue Churn Rate を指すことが多いと思います。

一方で、売上の予測や全体をざっくり考えたい場合は、Net Revenue Churn Rate を見たほうが正確です。

ARPU(アープ、平均月間課金単価、Average Revenue Per User)

(ARPU) = (MRR) / (有料顧客数)

1アカウントあたりがいくら支払っているかという金額がARPUになります。

SocialDog のように無料プランと有料プランがある場合は、ARPPU (Average Revenue Per Paid User)と呼んで区別する場合もあります。

個人的には紛らわしいので口頭では簡単に「単価」などと呼んでしまうことも多いです。

LTV(エルティーブイ、顧客生涯価値、Lifetime Value)

(LTV) = (ARPU) / (Net Revenue Churn Rate)

1人の顧客が最終的にいくら課金するかいう金額です。

継続決済なのに最終的にいくら課金するなんてわからないわけなのですが、解約率が変わらないと仮定すると、以下の式で計算で求めることができます。

LTV はなぜこの式になるのか

ある顧客の売上の合計は、毎月の売上の和であり、解約率を確率と考えると毎月の売上の期待値は (ARPU) * (Net Revenue Churn Rate) となります。

解約率は0から1を取るので、これは等比級数の和の公式から (ARPU) / (Net Revenue Churn Rate) に収束するとわかります。

平均継続月数

(平均継続月数) = 1 / (Net Revenue Churn Rate)

Net Revenue Churn Rate が 5%なら20ヶ月、10%なら10ヶ月となります。

(LTV) = (ARPU) * (平均継続月数) とも言えます。こちらのほうが直感的ですね。

CPA / CAC (Cost Per Acquisition / Customer Acquisition Cost)

ユーザー一人あたりの獲得コストです。これは広告費と獲得できた顧客数の割り算で簡単に求まります。

例)10万円の広告費で100人のユーザーを獲得できた場合は、以下より1,000円となります。 (CAC) = 100,000円 / 100人 = 1,000円/人

LTV vs CPA で適正な広告費がわかってくる

さて、ここまでで LTV と CPA が計算できるようになりました。 SaaS の成否は、この LTV と CPA の比率にかかっています。

LTV と CPA の関係は、顧客をいくらで獲得して、その顧客がいくらの売上になるのか、ということになります。

そして、LTV > CPA なら儲かり、LTV < CPA なら儲からない、ということになります。

BtoB の SaaS の場合、3 * CPA = LTV くらいだとちょうどよいと言われているそうです。が、正直結構きつい数字だと思いますw

うちくらいの単価のサービス、そこまで特に体力のないスタートアップだと、LTVは6ヶ月、長くても12ヶ月くらいで見ているところが多いんじゃないでしょうか(広告費が先に出ていってそれを1年で回収とかはキツイ、1年分の資金がないとショートしてしまう)。

どうやって使う?

上記の指標はこんな感じで使います。

例1:売上が上がっている/下がっている要因がわかる編

対象月 月末MRR Churn New
9月 100万 5万 10万
10月 130万 10万 40万
11月 135万 35万 40万

11月は、10月と比べると MRR が あまり伸びていない。その要因は既存顧客の解約であり、新規顧客獲得ではない。

例2:リスティング広告のクリック単価編

「CPA が 5,000円、ARPU が 750円、Net Revenue Churn Rate が 5%くらいです」と言われたら:

「LTV が 15,000円(750 / 0.05)なので、CPA 5000円くらいならよいか、リスティング広告でクリックから有料顧客になる割合(CVR)が1%であればクリック単価50円だと妥当」かな

実際にはCPAやLTVは顧客の獲得経路により大きく異なります。ふつう獲得経路別(SocialDog の場合、リスティング広告、オウンドメディア、アフィリエイト、オーガニックなど)にCPA, LTVを計算しています。

おすすめ SaaS メトリクス分析 SaaS

MRRやARRの計算をちゃんとやろうとするとかなり複雑になります。 当月のMRRやARRの計算だけでも、年額プランのユーザーを12ヶ月に配分しなければなりませんし、ChurnRateについてはすべての売上を前月の売上かどうか判別する必要があります。

ここは、これらのメトリクスと計算してくれる SaaS に頼りましょう。 Stripe や Paypal, iTunes Connect などの決済データを取り込んで、勝手にMRRやChurnRateを計算してくれるSaas を紹介します。

Baremetrics

Stripe, Braintree, Recurly に対応しています。今年デザインがリニューアルされてかっこよくなりました。 SocialDog でも契約して使っています。

なんとBaremetrics ではMRR 数十万円から数億円までのスタートアップ20社のダッシュボードが公開されています!かなり雰囲気がつかめますのでおすすめです。

Open Startups - Baremetrics

こんな感じで非常に見やすいです。

f:id:konisimple:20181205001816p:plain

Buffer 社の MRR

ChartMogul

Stripe, Paypal, iTunesConnect に対応しています。iOS のアプリ内課金があるならこれですね。

ProfitWell

Stripe, Braintree, Zuora, ChargeBee, Recurly, Paypal, Chargify に対応しています。

なんと無料です!対応している場合は、まずは登録することをおすすめします。ただし iTunes Connect には対応していません。

ちなみにサービスごとに、MRR の分類方法、Churn の計上タイミングが若干異なります。 基本的にはそんなに変わりませんが、数字を見る際は各サイトの表示をご覧ください。

参考文献

運営しているサービスを React で SPA にしてから1年が経ったので、最初から知っておけばよかった!という点を書いておく

こんにちは、小西です。

AutoScale という会社で スマートで効率的なTwitter アカウント管理ツール「SocialDog」というサービスを運営しています。

2017年4月にこちらのサービスを React 化してから、約1年が経過しました。 それまでの jQuery のコードと比べ圧倒的に可読性が向上し、バグも激減、SPAになったことでページ遷移もなくなり動作もサクサクになりました。

そこで今回は、開発初期に知っておきたかったなということを中心に、ハマったことや、あの時こうしておけばよかったという点を記事にしてみます。

ちゃんと JS を書いたことがない、これからサービスを SPA 化したい、という方の参考になれば幸いです。

ちなみに現状 SocialDog のJSのコードは、コンポーネント約40個、テスト含め合計行数3万行くらいの規模です。 Twitter アカウントをお持ちの方はずっと無料で使えますので、ぜひ実際使ってみてください。

SocialDog

React コンポーネントの props, state そのものは簡単

React は、データを渡すと、HTML っぽい JSX という言語を使って、DOM を吐き出すものです。 データが変更されると、DOM の差分だけ自動的に変更されます。非常にシンプルでわかりやすいです。

React コンポーネントの概念は非常に簡単なので、入門サイトを見れば30分もあれば理解できると思います。 React 単体の学習コストはそんなに高くない印象で、React そのものはあまりハマることはありませんでした。

ハマったのは、どちらかというと以下のような React そのものの周辺の環境整備や仕組みの理解でした。

複数のツールを組み合わせてビルド環境を作る必要があり、開発環境整備のハードルが高い

React の開発では、ES6などの最新の文法でソースコードを書いて、トランスパイラを使ってブラウザで動く状態にすることが一般的です。

私達は Webpack + Babel という構成でビルドしているので、以下のような流れになります。

  • ソースコードを書く
  • Webpack +Babel が変更を検知し、トランスパイルする(このとき依存関係もimportされる)
  • ブラウザから読み込む

私はそれまでJavaScript といえば jQuery しか書いたことなかったので、適当にソースコード書いてHTMLから script タグで呼ぶだけだったところからすると、大分面倒になった感じがあります。

今となっては、トランスパイルのプロセスがあることによって、syntax エラーにはすぐ気づけますし、ソースコードの可読性・再利用性も圧倒的に上がったのでいいことづくしだと思えます。

苦痛なのは最初だけで、すぐに快感に変わりますw

初期のうちから Webpack や Babel の役割をちゃんと理解して開発をすすめるとよいと思います。

React は View しかないので、多数のライブラリの導入が必要

React そのものには、View の機能しかありません。

SPAを作ろうとすると、ルーティングやデータの管理など、それぞれについてライブラリを選択し、導入する必要があります。

いままで jQuery しかまともに JS を書いたことがなく、まだ概念も完全に理解できていない初期に、一個ずつドキュメントを読みコードに統合していくのは結構大変でした。

ちょっとSPAを書こうと思ったら、おそらく以下のようなものを導入することになると思います。 最初にちゃんとドキュメントを読んでからコードを書くことを強くオススメします。

できるだけベーシックな構成になるよう、以下のライブラリを使うことにしました。

  • react-redux (stateの管理をする)
  • redux-thunk (Redux の Action Creator の処理を非同期にかける)
  • react-router (URLとコンポーネントの紐付け、画面遷移まわり)
  • immutable.js (変数をイミュータブルにすることで Reducer の処理や state の管理が楽になります)
  • superagent (Ajax ライブラリ。jQuery.ajax さようなら。)

この点 AngularJS はルーティングなどがコミコミで、書き方も決まっているので、最初迷うことが少なそうです。

ただこの部分は、一度行ってしまえばチームメンバーはやらなくていいので、最初に作る人が辛みを引き受けることでチームメンバーはそこまで辛くないかもしれません。

React に対応していないライブラリは導入がやや面倒

全部のコンポーネントをReact の「管理された」コンポーネントにするためには、ライブラリ側の React 対応が必須になります。

例えば、グラフ描画の HighCharts というライブラリがありますが、このライブラリは React 用のコンポーネントがあるので、簡単に既存のアプリに統合できます。

↓こんな感じで、データはReact らしく、props として渡すことができます。 この書き方ができると超気持ちいいですね。

<ReactHighcharts config = {config} />

React に対応していない場合は、React のコンテナ外に作るか、消えない <div> 要素とかを作ってそこに入れる感じになります。

その部分だけ componentDidMount なんかに 以下のような コードを書いたりしないといけません。

    $('#calendar').fullCalendar({config})

イベントハンドラが必要なら、componentDidMountcomponentWillUnmount で bind したり unbind したりが必要になったりします。

たぶん一回はイベントハンドラを unbind しわすれてイベントが2回発火する事件がおきますw

Redux (Flux) に慣れるのが大変

React では、Redux や Flux などを使ってデータを管理することが多いと思います。 Action Creator, Reducer, Store なんかのアレです。

この概念を理解するまではコードを書くのが少しつらいです。 理解してしまえば難しいことはないので、辛いのは最初の1週間だけです。 私は、データの受け渡しの例の図を開発初期ずっとデスクトップに表示していました。

私が以前まで書いていた jQuery のコードでは、グローバル変数地獄か、メソッド間で変数を渡しまくり地獄で、非常に大変でした。 Redux を導入したおかげでデータの受け渡し方法が明確になり、コードの可読性が大幅に高まりました。

ES6, JSXの文法に慣れる

ES6 やJSX でJSを書くとそれまでの書き方とは大きく変わります。 最初の2週間くらいは、わかっていないことが多くて、辛かったです。

特に、以下のようなことは、一番最初に理解すべきだっだなと今になって思います。

  • ES6のimport/export の使い方、default の有無、export の有無の違い
  • JavaScript のプリミティブな変数、オブジェクトの違い
  • アロー関数と this, bind() について
  • 関数の巻き上げ
  • Array.* 系のメソッド(便利な関数がたくさんあるのですが、最初その存在を知らず、ついforEachとかで書いてしまいがちです。 map, reduce, every, some で書くのが読みやすく JS っぽいと思います。MDNが日本語で網羅されており、非常にわかりやすいです。最初にざっと眺めておくことをオススメします)
  • Promise (非同期処理のコールバック地獄を防ぐため Promise も必須だと思います)
  • async/await(非同期処理は、Promise よりもasync/waitで書くほうがわかりやすい処理が多いと思います。最初は async/wait の存在を知らなかったので、Promise で書いているせいで冗長な箇所が残ってしまいました。もっと早く使えばよかった・・・。こちらは ES7 にする必要があります)

React をはじめてから、「はじめての JavaScript 」を急遽読み、このへんは克服しました。 ちなみにこの本、「はじめての」とありますが、プログラミングがはじめての人にはきついので買わないほうがよいですw

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

  • 作者: Ethan Brown,武舎広幸,武舎るみ
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2017/01/20
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

まとめ

React 開発で大変なのは環境構築と、初期の概念の理解です。 環境構築は最初だけですし、慣れてしまえばなんてことないので、今となっては React にして本当によかったと思います。

次は react-native でネイティブアプリを作りたいなー。

React での SPA のサンプルとして、SocialDog もぜひ触ってみてください! 無料で登録できます!(PR)

AutoScale では、一緒に React で SPA を書いてくれる方を募集しております! 少しでもご興味がありましたら、コチラまでご連絡ください!

【超簡単】Docker でモダンな PHP 開発環境を作る (PHP, MySQL, PHP-FPM, nginx, memcached)

こんにちは。小西です。

開発環境の構築って面倒ですよねー。

今回、PHP, MySQL, PHP-FPM, nginx, memcached のローカル開発環境を、Docker を使ってコマンド一発で作られるようにしたところ、あまりに簡単で驚いたので、その方法をご紹介します。

ソースコードをgithubにおいておきます ので、すぐに起動できます!

開発環境構築のめんどくささ

僕はPHP+MySQL+nginx+PHP-FPMの環境をよく使うのですが、こういった構成をそれぞれのマシンで再現するのって結構面倒なんですよね。1プロジェクトならまだいいですが、大体プロジェクトによってそれぞれのバージョンや設定ファイルのパラメータなどが微妙に違うので、phpenvでの切り替えなきゃ・・・なんて話になってきて、なかなか面倒です。

やはりプロジェクトごとに環境作成手順を共有したほうが効率がよさそうです。

なぜ Docker ?

Docker を使うと、

  • プロジェクトごとに開発環境を作ることができる
  • イメージを共有するわけではなく、設計図だけの共有なのでリポジトリが重くならない
  • Vagrant などと違い、ビルドのたびに VM が起動したりしないので軽い

Docker Toolbox ではなく Docker for Mac (Docker for Windows)を使う理由は、

  • GUI でインストールが完結するので、導入が簡単
  • Docker 社も今後はこちらを進めていく方針っぽい

2つの違いは以前こちらに書きました:これからは Docker Toolbox よりも Docker for Mac を使おう - koni blog

Docker、Docker Compose とは

今回作るものの概念をすごくざっくり説明すると、

  • 動かしたいプロセスごとに Docker イメージ を作ります
    • Docker イメージは、Dockerfile というファイルをもとにビルドします
    • プロジェクトメンバーには、イメージ ではなく、Dockerfile を共有します(Dockerfile はテキストファイルなので軽い)
    • 基本的には、1イメージにつき1つのデーモンが動くように作ります
      • 今回の場合は、nginx, php-fpm, mysqldがデーモンになります
    • イメージはゼロから作ることもできるし、他のイメージを元にして作ることもできます
    • Docker HubDocker イメージ が多数共有されているので、それを活用することも可能
  • イメージ から コンテナ を起動します
    • コンテナ は1つのプロセスで、1つのデーモンが動いています。このデーモンが止まると、コンテナは終了します
  • docker-compose.yaml という yaml のファイルを書いて、docker-compose コマンドを実行することで、複数の コンテナ をまとめて起動し、協調して動けるようにします

という感じです。

Docker 初めてだとよくわからないかもしれませんが、設定ファイル見て触って覚えたほうが早いですので、よくわからなくても次進みましょう。

ディレクトリ構成

以下のような構成にします。

├── application (ここにプロジェクトファイルが入る想定)
│   └── initial.sql (データベースの初期のSQLが入っているファイル)
├── docker
│   ├── docker-compose.yml (docker-compose の設定ファイル)
│   ├── misc
│   │   └── data (MySQLのデータフォルダが入るディレクトリ)
│   ├── nginx
│   │   ├── Dockerfile
│   │   ├── fastcgi.conf
│   │   ├── localhost.crt
│   │   ├── localhost.csr
│   │   ├── localhost.key
│   │   ├── nginx.conf
│   │   └── server.conf
│   └── php-fpm
│       ├── Dockerfile
│       └── php.ini
└── public (nginx のドキュメントルート)
    └── index.php (テスト用の PHP ファイル)

さっそく動かしてみよう

今回の記事で作成するファイルを github においておきますので、こちらをベースに説明していきます。

Docker for Mac / Docker for Windows のインストール

まずは 以下のページから Docker for Mac (または Docker for Windows )をインストールします。安定版とベータ版がありますが、安定版(stable)を選んでおきましょう。

Get started with Docker for Mac | Docker Documentation

指示に従ってインストールします。 これで、docker コマンド、docker-compose コマンドが使えるようになりました。

Mac の場合、右上で状況が確認できるようになります。クジラのアイコンがキュートですね。

f:id:konisimple:20170128133109p:plain:w300

ソースコードの展開

続いて、今回作成するファイルが置かれたリポジトリから、ファイルをダウンロードします。

$ git clone git@github.com:koni/docker-php-nginx-mysql-memcached.git

ビルド&起動

続いてビルド&起動します。初回は多少時間がかかりますが、2回目からは数秒で起動します。

$ cd docker-php-nginx-mysql-memcached/docker
$ docker-compose up

無事に成功すると、ログが出始めます。

ここで MAMP や Apache, nginx など、TCP80, 443, 13306 ポートをLIESTENしているプロセスがいると、以下のようなエラーがでます。 他のプロセスを終了してから再度 docker-compose up を実行しましょう。

ERROR: for nginx  Cannot start service nginx: driver failed programming external connectivity on endpoint nginx (9ad3d10cef4673ee7a48fbd6c7c59fbe6f3043eeac8eb0e27797da03cfbfa60b): Bind for 0.0.0.0:443 failed: port is already allocated
ERROR: Encountered errors while bringing up the project.

動作確認

ログが止まったら、ブラウザで http://localhost/ にアクセスします。 以下のような表示が出たら成功です。

f:id:konisimple:20170128133559p:plain

nginx のドキュメントルートにあるテスト用の PHPファイル(public/index.php)が実行されます。このファイルでは MySQL につないで、データを INSERT し、SELECT するので、リロードするたびに数字が増えていくはずです。

今回の動作の概念図

f:id:konisimple:20170128145930p:plain

各ファイルの説明

では、各ファイルについて見ていきましょう。

docker-compose.yaml

このファイルがあるディレクトリで、docker-compose up コマンドを実行すると、このファイルを使ってコンテナの起動を試みます。対応するイメージがまだない場合は、自動的にイメージが生成されます。必要なファイルをダウンロードしてイメージを作成するので、初回は時間がかかりますが、一度イメージが作られるとその作業はないので1,2秒で起動できるようになります。

docker-compose.yaml には、起動したいコンテナと、それぞれが協調して動くための設定を記述します。

docker-compose の書き方

version は '2' で固定です。

services以下がそれぞれのコンテナの設定です。

ports を使うと、コンテナ内のポートとローカルのポートにバインドできます。コロン(:)の左がローカルのポート、右がコンテナ内のポートです。MySQL コンテナでは、ローカルから直接コンテナの MySQL にアクセスできると便利なので、コンテナの 3306 をローカルの 13306 にバインドします。3306 でなく 13306 にしているのは、既に MySQL がローカルで動いているとバッティングしてしまうためです。この数字はなんでもかまいません。

volumes と使うと、コンテナ内のディレクトリをローカルのディレクトリにバインドできます。コロン(:)の左がローカルのディレクトリ、右がコンテナのディレクトリです。

container_name でコンテナに名前をつけることができます。これは必須ではないですが、docker-compose exec でコンテナ内に入る時に、打つ文字数が減るので便利です。ただし、同じコンテナ名で複数のコンテナが起動できないので、1つに固定しておくと、同時に1つしか同じコンテナを起動できなくなります。

links を使うと、その名前で他のコンテナにアクセスできるようになります。 これを使えばコンテナのIPを知らなくても他のコンテナにアクセスできます。

例えば、weblinks: mysqlとあるので、nginx コンテナ内からは、mysqlというホスト名でmysqlにアクセスできるようになります。この場合、アプリ側のDBの設定ファイルのホストの欄には mysqlと書けば接続できるようになります。こんな感じです。この例では`docker-compose.yaml から環境変数でホスト名を渡しています。

memcached
services:
  memcached:
    image: memcached:1.4

これは、memcached:1.4 イメージで起動しろ、という設定です。ローカルにイメージがない場合は、Docker Hub から自動的に取得されます。今回は、ここから取得されます。

mysql
  data:
    image: busybox
    volumes:
      - ./misc/data:/var/lib/mysql
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: mysql_database
      MYSQL_USER: mysql_user
      MYSQL_PASSWORD: mysql_pw
    ports:
      - "13306:3306"
    volumes_from:
      - data
    volumes:
      - ../application/initial.sql:/docker-entrypoint-initdb.d/initial.sql
    container_name: mysql

こちらもイメージは Docker Hubのオフィシャルリポジトリから取得されます。

このイメージでは、MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORDという環境変数を設定しておくと、自動的にROOTパスワードを設定し、ユーザーとデータベースを作成してくれる機能があるので、環境変数を設定しておきます。ちなみに、この処理はデータベースがないときだけ行われます。一回起動するとこの処理は行われません。misc/data以下を消すと、再度ユーザーとデータベースが作成されます。

またこのイメージには、/docker-entrypoint-initdb.d/*.sql というファイルを置いておくと、起動時にそれを実行してくれる機能があるので、volumesを使って、そこにアプリケーションのDBスキーマをおいておきます。今回はテストなので、hoges というテーブルを作成するCREATE TABLE 文だけおいておきます。実際にはここでアプリケーションのDBスキーマが書かれたファイルのパスを指定します。

コンテナは終了するとデータが消えてしまいます。コンテナを終了するたびデータが消えると開発上不便なので、busybox を使ってMySQLのデータはコンテナ外においておくことにします。 ローカルの./misc/dataを、コンテナ内の /var/lib/mysql にバインドします。これで、misc/dataにMySQLのデータがある状態になるので、コンテナを終了してもデータは消えません。データを消したいときは、misc/data 以下を削除すればOKです。 git で管理する場合は、このディレクトリは .gitignore しておくことをおすすめします。

nginx

  nginx:
    build: ./nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ../:/var/www/html
    links:
      - web
    container_name: nginx

今度は image ではなく、 build があります。 イメージ内のnginxの設定ファイルに変更を加えたいので、別途 Dockerfileを使ってイメージをビルドするようにします。

nginx/Dockerfile

このファイルで nginx イメージをビルドします。

FROM nginx

ADD nginx.conf /etc/nginx/nginx.conf
ADD fastcgi.conf /etc/nginx/fastcgi.conf
ADD server.conf /etc/nginx/conf.d/default.conf
ADD localhost.key /etc/ssl/private/localhost.key
ADD localhost.crt /etc/ssl/certs/localhost.crt

FROM nginx とあるので、Docker Hub のリポジトリからベースのイメージがダウンロードされます。

それから、設定ファイルをいくつかを変更します。 今回は nginxの設定ファイルである、nginx.conf、ポートと処理の対応の設定であるserver.conf、nginxとPHP-FPMとの連携の設定である fastcgi.conf を変更します。

また、おまけとして、自己署名したSSL証明書もおいておきます。記事最後の手順を実行すると、https://localhostでもアクセスできるようになります。

web (PHP-FPM)

  web:
    build: ./php-fpm
    volumes:
      - ../:/var/www/html
    links:
      - mysql
    environment:
      DATABASE_HOST: 'mysql'
      DATABASE_NAME: 'mysql_database'
      DATABASE_USER: 'mysql_user'
      DATABASE_PASSWORD: 'mysql_pw'
    container_name: web

volumes で、プロジェクトルートと /var/www/htmlをバインドしておきます。これで、コンテナ起動中にソースコードを修正して、すぐに確認することができます。

environment を利用して、アプリの実行設定を環境変数で渡すことができます。今回はDBへの接続の設定を渡しました。

よく使うコマンド

イメージのビルド

$ docker-compose build

起動

$ docker-compose up

終了

$ docker-compose stop

起動中のコンテナを見る

$ docker ps

コンテナに入る

ログファイルや、設定ファイルの位置の確認などで、コンテナに入るときは以下を使います。

$ docker exec -it mysql bash
root@a0d91988b7db:/#  # コンテナに入れる

mysql の部分には、コンテナ名 (docker-compose.yamlcontainer_name)か、コンテナID (docker ps コマンドの出力の CONTAINER ID)を入れます。

その他使い方メモ

コンテナのMySQLに接続したい

MySQL クライアントで以下のように設定すると、コンテナの MySQL に入れます。もちろん mysql コンテナが起動していないと入れません。

Host: 127.0.0.1:13306
User: mysql_user
PW: mysql_pw
Database: mysql_database

みんな大好きSequelProならこんな感じですね。

f:id:konisimple:20170128141637p:plain:w300

SSL で入れるようにしたい

今回、こちらで生成したSSL自己署名証明書が入っているので、この証明書を信頼する設定にすれば、SSLで接続できます。

この証明書は、自己署名なのでそのままだとブラウザでエラーが出てしまいます。 これを回避するために、localost.crt をキーチェーンアクセスの「システム」として証明書を登録します。

$ open docker/nginx/localhost.crt

上のコマンドを実行するとキーチェーンアクセスが起動するので、「システム」を選択し、証明書を追加する。 「キーチェーンアクセス」を起動し、「システム」を選択、今追加した証明書(名前が「localhost」になっている)をダブルクリックし、「この証明書を使用するとき」欄を「常に信頼」にし、画面を閉じる。画面を閉じるときに保存されるので、必ず画面を閉じる。

SSL 自己署名証明書の作成手順

リポジトリに含まれている証明書は、以下の手順で作成したものです。

$ openssl genrsa 2048 > localhost.key
$ openssl req -new -key localhost.key > localhost.csr
$ openssl x509 -req -days 3650 -signkey localhost.key < localhost.csr > localhost.crt

まとめ

というわけで、これで開発環境をコマンド一発で起動できるようになりました。

これを git などでメンバーに共有して、メンバーには「Docker for Mac 入れて、docker-compose up 実行して」と伝えるだけで開発環境の構築が済むようになりました。

<宣伝> 株式会社AutoScaleでは、この環境を使って、Twitterフォロワー管理・運用支援ツール Cheetah というサービスを開発しています! Twitter アカウントをお持ちの方は、ぜひお試しください!

追記 (2017.11.30)

先日、最新版であるPhpStorm 2017.3 がリリースされました!! エディタベースのREST クライアントを搭載しています。 エディタベース、というのは結構新しいですが、意外と便利です。

ちなみにPhpStorm のバージョニングは、2017.3のような形式になっています。 2個目の数値は月ではなく、何個目のリリースか、という数字です。 なので、2番目の数値が1違うと、結構機能も増えたりします。 ここ最近は、3月, 7月, 11月の4ヶ月おきで年3回のリリースとなっています。

参考

DevOps導入指南 Infrastructure as Codeでチーム開発・サービス運用を効率化する

この本は、2016年10月発売と新しいので、Docker for Mac についても書いてあり、開発の運用についてかなり具体的に書いてありおすすめです。

Compose ファイル・リファレンス — Docker-docs-ja 17.06.Beta ドキュメント

追記 (2017.9.28)

「モダン」と行っておきながら PHP 5.6 だったので、PHP 7.1 に対応させました。 PHP5.6 に対応させたい場合は、docker-compose.ymlbuild: ./php-fpm56 にしてください。

これからは Docker Toolbox よりも Docker for Mac を使おう

2016年8月に正式リリースされた Docker for Mac/Windows。

勉強もかねて、Docker for Mac とそれまでの Docker Toolbox との違いをまとめました。

まとめ

Docker「これからはコンテナより下の部分についてはDocker for Mac/windows を使ってね!そのほうが高速だし、楽ちんだよ」

(ということだと思う)

Docker for Mac

  • Mac のネイティブアプリケーションであり、 /Applicationsにインストールされる
  • インストール時に /usr/local/bindockerdocker-composeにシンボリックリンクが貼られる。実体はネイティブアプリケーション内にある(/Applications/Docker.app/Contents/Resources/bin)
  • Docker for Mac は Docker社が公開したオープンソースの軽量な仮想化ツール「Hyperkit」で動く(VirtualBoxは使わない!)
    • Hyperkitは、macOS 10.10 Yosemite から実装されている仮想化フレームワーク「Hypervisor Framework」を利用しているので、macOS 10.10 以上でしか使えない 「Hyperkit」である VirtualBoxを使わず、
  • Docker for Macをインストールしても、Docker Machine で作成されたマシンには影響を与えない。
  • Docker for Mac アプリケーションは、 docker-machine は使わず、直接VMを作成し、管理する
  • Docker for Mac のインストール時に Docker Engine が動いている Alphine Linux ベースの Hyperkit VM がプロビジョンされ、ソケット /var/run/docker.sock と通信できるようになる。それ以降、docker コマンドや docker-compose コマンドで環境の指定をしない限り、デフォルトで呼ばれることになる。
  • Docker for Mac をインストールすると、唯一の VM を手に入れたことになり、管理する必要はなくなる。Docker for Mac アプリケーションが Docker のサーバー、クライアントバージョンを自動でアップデートする。

Docker Toolbox

  • Docker Toolbox をインストールすると、 docker, docker-compose, docker-machine/usr/local/bin にインストールされ、VirtualBoxもインストールされる。
  • docker-machine コマンドを使って、VirtualBox VM である default ホストをプロビジョニングしていた
    • この VM では、boot2docker という Linux Distribution で Docker Engine が動く。

参考

Google App Engine の場所(リージョン)選択のタイミングが変わった

これまで Google App Engine (GAE) のリージョン選択のタイミングは、プロジェクト作成時だったのだが、今試したらAppEngineを使い始めるタイミングに変更されていた。

あれ、と思いながらもプロジェクト作って、appcfg update .したら以下のようなエラーが。 これまではGCPのプロジェクト作るだけでよかったのが、App Engine の初期化が必要になった。

Error 404: --- begin server output ---
This application does not exist (project_id=u'cheetah-help'). To create an App Engine application in this project, run "gcloud beta app create" in your console.

一度間違えると変えられないので、不安だったので初期化はGUIからやった方がなんとなく安心ということでGUIでやりました。

こんな感じでとてもわかりやすくなりました。

f:id:konisimple:20170111002130p:plain

ヘルプも古い記述のままだったので注意!

サーバーの場所の設定

プロジェクトを作成するときに、サービスの提供元になる場所を指定できます。新しいプロジェクトのダイアログで、[詳細設定を表示] リンクをクリックして、プルダウン メニューから場所を選択してください。

Managing Cloud Platform Projects, App Engine Applications, and Billing  |  App Engine standard environment for PHP  |  Google Cloud Platform

Google App Engine プロジェクトのリージョンを東京リージョンに移す(移せない)

こんにちは。

以前から予告されていた、Google Cloud Platform (GCP) の東京リージョン開設がついに発表されました!

Google Cloud Platform Japan 公式ブログ: 東京 GCP リージョン の正式運用を開始しました

東京リージョン開設前は東京から最寄りのリージョンは台湾でしたが、台湾リージョンでは Google App Engine (GAE) は使えません。このため、これまではアメリカ西海岸(us-east1) を使っていることが多かったのではないでしょうか。 このせいで、RTTだけで200ms程度がどうしてもかかってしまっていました。

これが東京になると、数十ms台で通信できるようになります。

これはもう既存のプロジェクトをアメリカ西海岸から東京に移すしかない! ということで、既存の App Engine のリージョンを移す方法を調べてみました。

GAE のリージョンは変えられない

しかし、ドキュメントによると、どうやら一度決めた App Engine のリージョンは変更できないようです。

プロジェクトの作成後に場所を変更することはできません。

Managing Cloud Platform Projects, App Engine Applications, and Billing  |  App Engine standard environment for PHP  |  Google Cloud Platform

今回はほぼ静的なサイトのホスティングのみで、StackDriver なども使っていなかったので、プロジェクトごと作り、DNS の設定変更をすることにします。

※2017/1/11追記 プロジェクト作成時ではなく、AppEngineを使用開始するときに設定するように変わりました。 Google App Engine の場所(リージョン)選択のタイミングが変わった - koni blog

  1. 普通にプロジェクトを作成します。 f:id:konisimple:20161109201054p:plain

ここではロケーションを選ばなくなりました。上記は古い画面です。

いままではなかった、asia-northeast1が選べるようになっています。 これが東京リージョンです! ここで間違って選択すると、後からは変えられません!ご注意。

  1. AppEngineの画面を開き、「はじめてのアプリへようこそ」の下の言語名をクリック f:id:konisimple:20170111015624p:plain

  2. 「ロケーションを選択」で東京である「asia-northeast1」を選択 f:id:konisimple:20170111015653p:plain

  3. デプロイします。

  4. ドメインの設定を変更します。 DNS レコードの TTL を短くしておく、GAE 側の変更する、レコードの向き先を変える、といういつもの手順ですね。

無事に200msとかだったのが、10ms台が出るようになりました! (このサイトは実際にはほぼキャッシュしてもらっているので、あんまり関係ないけどね!w)

台湾リージョンでも GAEが使えるようになれば、マルチリージョンでスケールするようになって冗長性が高まるのですが、今でもリージョン単位でスケールする(複数のゾーンにまたがってスケールする)ので、十分そうですね。

参考

リージョン・ゾーンの一覧があります。 Global Locations - Regions & Zones  |  Google Cloud Platform