koni blog

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

運営しているサービスを 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

超簡単に PHP で Twitter Streaming API を叩く

Twitterでつぶやかれたリンクをすべて取得できないかなと考えていた。 そこでまずリンクが分量的にどのくらいのスピードで流れているか確認したい。

「http」とかの検索ワードで検索APIを一定時間おきに叩くというのが簡単にできそうだけど、分量が多すぎて抜け漏れがありそうだし、なんかスマートじゃない。

Twitter Streaming APIを使ってみる

Twitterのデータをリアルタイムに取得できる、Twitter Streaming APIというものがある。以前から使ってみたかったのだが、なかなか使う機会がなかったので、今回はそちらを使ってみることにする。

Streaming APIには、全ツイートの一部を返す、statuses/sampleと、statuses/filterがある。後者ならフィルタされていないんじゃなかろうか。

POST statuses/filter | Twitter Developers

ちなみにどのくらいのスピードで流れているか確認したいだけなので、Twitter 公式のテストツールでやってみようと思ったが、そもそもストリーミングには対応していなかった。Streaming APIは、ずーっと延々とデータが送られてくるというものなので、単純なAPI用のコンソールでは対応していないんだろう。WebSocketとかじゃなくて、Commet。

そこで、phirehose というライブラリを使って確認したのだが、あまりにも簡単だったのでメモしておく。

phirehose

fennb/phirehoseというライブラリを使う。 他ライブラリに依存せず、ストリーミングAPIのインターフェースのみを提供、というのが良い。 OAuthを使うライブラリだと、PEARのライブラリとかに依存していると面倒だけど、これは入れるだけでいいので非常に楽。 5分くらいでできる。

ファイルの取得

git clone git@github.com:fennb/phirehose.git

OAuth の登録

https://apps.twitter.com/ から、アプリを登録して、「Keys and Access Tokens」から自分のアクセストークンを取得。

ファイルの修正

いくつかサンプルが入っているので、これを直接編集して使う。 今回は example/filter-otuah.phpを使うので、これを開いて、TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, OAUTH_TOKEN, OAUTH_SECRETをさっきのサイトで取得した値を入れる

標準出力に出してもいいけど、せっかくなのであとから分析したいので、

      print $data['user']['screen_name'] . ': ' . urldecode($data['text']) . "\n";

している箇所を、

      file_put_contents('sample.txt', $data['created_at'] .' '.$data['user']['screen_name'] . ': ' . str_replace("\n", '', urldecode($data['text'])) . "\n", FILE_APPEND);

とかに変えておく。

実行

$ cd /path/to/project/example
$ php filter-oauth.php

...

Phirehose: Consume rate: 69 status/sec (4146 total), avg enqueueStatus(): 0.64ms, avg checkFilterPredicates(): 0ms (0 total) over 60 seconds, max stream idle period: 1 seconds.

ファイルに保存されるので、tail -fしながら眺める 処理中は1分おきにステータスが標準出力に出てくる。

結果

2016年10月4日9:30頃から30分ほど行った際に取得できたデータは以下。

/statuses/filter track=http 27分26秒で78218件 → 44.3件/s

/statuses/sample 26分33秒で78218件で62810件 → 39.4件/s

全世界のツイートは約5億/日らしい*1ので、約5800/sが発生していることになる。 ということは、sampleでとれたツイートは全体の約0.6%

リンク付きのツイート、全体の3割とすると、filterでとれたツイートは全体の2.5%((44.3/(5800*.3)))

参考

Streaming APIで日本全国のツイートを収集する - メヘンニミン

PHP で Google Cloud Datastore のデータを読み書きする

PHP で GCP の Cloud Datastore のデータを読み書きしてみました。 あまり情報がないので、とっかかりとしてメモしておきます。

Google App Engine から行いましたが、他の環境からでも動くはずです。

google/google-api-php-clientをありがたく使わせてもらいます。

このライブラリは、Google Cloudではなく、Google APIのライブラリなので、Google Cloudのライブラリ一覧のページには載っていません*1。 が、Google Cloud Client library for PHP はないようなので、こちらを使います。

ライブラリの導入

githubのreadme通りに導入します。

$ composer require google/apiclient:^2.0

ソースコードにautoload.phpをrequireします。

プロジェクトの作成と証明書の取得

Google Developer Consoleから、プロジェクトを作成し(ない場合)、サービスアカウントを作成します。

証明書を取得します。

  1. コンソールを開く
  2. API Manager
  3. 認証情報
  4. 「サービスアカウントキー」を作成
  5. 「App Engine default service account」を選択(新規作成しても可)
  6. jsonファイルがダウンロードできる

ソースコードの設置

hogehoge.json さきほどの証明書

app.yaml

application: practice161003
version: 1
module: default
runtime: php55
api_version: 1

threadsafe: true

handlers:
- url: /
  script: index.php

index.php

<?php

require('vendor/autoload.php');

# 証明書のパス
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . __DIR__ . '/hogehoge.json');

$client = new Google_Client();
$client->useApplicationDefaultCredentials();

$client->addScope(Google_Service_Datastore::DATASTORE);

$datastore = new Google_Service_Datastore($client);

$kind            = new Google_Service_Datastore_KindExpression(['name' => 'User']);
$property        = new Google_Service_Datastore_PropertyReference(['name' => 'name']);
$value           = new Google_Service_Datastore_Value(['stringValue' => 'konishi']);
$property_filter = new Google_Service_Datastore_PropertyFilter([
    'property' => $property, 'op' => 'EQUAL', 'value' => $value]);
$filter          = new Google_Service_Datastore_Filter(['propertyFilter' => $property_filter]);
$query           = new Google_Service_Datastore_Query(['kind' => [$kind], 'filter' => $filter]);
$request         = new Google_Service_Datastore_RunQueryRequest(['query' => $query]);
$result          = $datastore->projects->runQuery('practice161003', $request);
var_dump($response->toSimpleObject());

上記のコードで、kind=Userのエンティティをname=konishiでフィルタしたデータをname列の降順で取得、となります。

ちなみにここに出てくるクラスはすべてGoogle_model クラスを継承しています。 Google_modelを継承するクラスは、setterを使わなくても、コンストラクタに連想配列で渡せるので、$queryは以下のようにもかけます。

$query = new Google_Service_Datastore_Query([
    'kind'   => [
        [
            'name' => 'User',
        ],
    ],
    'filter' => [
        'propertyFilter' => [
            'property' => [
                'name' => 'name',
            ],
            'op'       => 'EQUAL',
            'value'    => [
                'stringValue' => 'konishi'
            ]

        ]
    ],
    'order'  => [
        'property'  => [
            'name' => 'name',
        ],
        'direction' => 'descending',
    ],
    'limit'  => 10,
]);
$request  = new Google_Service_Datastore_RunQueryRequest(['query' => $query]);
$response = $datastore->projects->runQuery('プロジェクト名', $request);

このあたりは好みですかね。 個人的にはどうせラッパー書いてしまうことになると思うので、jsonでだーっと書いてしまう方にしたいと思います。

insert

$mutation = new Google_Service_Datastore_Mutation([
    'insert' => [
        'key'      => [
            'path' => [
                'kind' => 'User',
                'name' => 'keyとなるテキスト',
            ]
        ],
        'properties' => [
            'age' => [
                'excludeFromIndexes' => false,
                'integerValue'       => 27,
            ]
        ]
    ]
]);

$request = new Google_Service_Datastore_CommitRequest([
    'mode'      => 'NON_TRANSACTIONAL',
    'mutations' => [$mutation],
]);

$response = $datastore->projects->commit('プロジェクト名', $request);

jsonは、以下のリファレンスを見て組み立てます。

Method: projects.runQuery  |  Cloud Datastore  |  Google Cloud Platform

便利そうなラッパー作っている人もいますが、2013年で更新されておらず、今は動きません。

https://gae-php-tips.appspot.com/2013/12/24/getting-started-with-the-cloud-datastore-on-php-app-engine-part-ii/

感想

ローカル環境から実行したところ、上記のコードのrunQueryの部分だけで2秒ほどかかっていました。

GAE上だと10msほどになっていました。 Cloud Datastoreのデータはマルチリージョンということですが、GAEのリージョンus-centralになっているので、テストでアップしたデータはus-centralにあるようです。

東京リージョン早く来て欲しいなー。

参考

少し古かったので、現在動かすためには何点か修正が必要でした。

Google Cloud Datastore APIをPHPから使ってみる(データ取得) - Qiita

*1:検索したらありました。https://github.com/GoogleCloudPlatform/google-cloud-php 対応していないサービスが多いので、それ以外のサービスの場合は、API Client Library使ってねと書いてある。