読者です 読者をやめる 読者になる 読者になる

koni blog

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

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

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 のリージョンは変更できないようです。

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

Creating a Project and Managing Billing  |  App Engine standard environment for PHP  |  Google Cloud Platform

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

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

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

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

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

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

参考

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

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

Twitter API PHP 技術メモ

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 のデータを読み書きする

技術メモ Google App Engine Google Cloud Datastore Google Cloud Platform PHP

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使ってねと書いてある。

macOS SierraにしたらPHPStormがエラーで起動しない問題の解消 (java, git)

PHPStorm 技術メモ

対応バージョンのJavaが入ってないと言われるので、エラーメッセージに従って、Appleのホームページから落としてきて入れる。

Can't start git /usr/bin/git
Probably the path to Git executable is not valid. Fix it.

というエラーが出た。

whichすると、パスはあっている。

$ which git
/usr/bin/git

$ git
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

コマンドラインツールがinvalidらしいので、macOS Sierra用のCommand Line toolsをダウンロードする。

Command Line toolsは、XCode 8を入れるとついてくるが、XCode要らないのに5GB近くのものを入れたくないので、Command Line toolsだけインストールする。

Apple Developerにログインして、ダウンロードページに行き、Command Line Tools (macOS 10.12) for Xcode 8を入れる。

https://developer.apple.com/download/more/

問題なく使えるようになった。

この問題、OSのアップデートするといつも起きるのだが、自動的にやってほしい気がする。 できるのかな。

Google Cloud SDK 入れたら、Google App Engine の開発サーバー dev_appserver.py でPHPが動かなくなる問題への対処

技術メモ Google App Engine

Google Cloud SDKを入れたら、Google App Engine(GAE)用のローカルの開発サーバー dev_appserver.pyが以下のようなエラーを吐いて、動かなくなってしまいました。

$ cd /path/to/project_root
$ dev_appserver.py .
INFO     2016-06-15 02:15:02,903 devappserver2.py:769] Skipping SDK update check.
INFO     2016-06-15 02:15:02,964 api_server.py:205] Starting API server at: http://localhost:52471
INFO     2016-06-15 02:15:02,972 dispatcher.py:197] Starting module "default" running at: http://localhost:8080
INFO     2016-06-15 02:15:02,974 admin_server.py:116] Starting admin server at: http://localhost:8000
ERROR    2016-06-15 02:15:03,980 php_runtime.py:348] The PHP runtime is not available
Traceback (most recent call last):
  File "/Users/koni/src/tool/google-cloud-sdk/platform/google_appengine/google/appengine/tools/devappserver2/php_runtime.py", line 344, in new_instance
    self._check_binaries(php_executable_path, gae_extension_path)
  File "/Users/koni/src/tool/google-cloud-sdk/platform/google_appengine/google/appengine/tools/devappserver2/php_runtime.py", line 265, in _check_binaries
    raise _PHPBinaryError('The development server must be started with the '
_PHPBinaryError: The development server must be started with the --php_executable_path flag set to the path of the php-cgi binary.

--php_executable_pathでPHP-CGIのバイナリのパスを指定しろとのこと。 GAEのPHPのバイナリは、GAEのプラグインが入っている専用のものなので、ローカルのPHP使うならエクステンションを入れないといけません。

原因

ではなぜいままで動いていたかというと、dev_appserver.pyはこれまでGoogle Cloud SDKのものではなくて、GoogleAppEngineLauncher.app内蔵のものを使っていたようです。 それがGoogle Cloud SDKを入れた時にそちらのものを使うようになってしまったようです。 GoogleAppEngineLauncher.appには、GAE用のエクステンションが入ったphp-cgiが内蔵されているので、それを使って動いていたようです。

解決方法

dev_appserver.pyは、Google Cloud SDKのものではなく、GoogleAppEngineLauncher.appのものを使う。

私の環境では以下のパスにありました。

/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/dev_appserver.py

こっちを使ったら普通に動きました。

ちょっと逃げですが、ローカル環境だし動いたからこれでいいかと。。

参考

The PHP Development Server  |  PHP  |  Google Cloud Platform

Let's encryptで無料のSSL/TLS証明書を取得して独自ドメイン運用のGAEに入れて使う

Google App Engine 技術メモ

無料でSSL/TLS証明書が発行できるLets' Encryptプロジェクトが、2016年4月、ついにベータ版から正式版になりました。

jp.techcrunch.com

無料といっても、MozillaやGoogle, Facebookなどのウェブ企業や、シスコやアカマイなどのネットワーク関係の大御所も支援しているプロジェクトなので、審査がなく全自動で発行されるタイプの1,000-2,000円くらいの有料のSSL証明書と全く遜色ありません*1

業界的にウェブの暗号化に向けてこのようなプロジェクトを動かしているのは素晴らしいですね。

GAEへのSSL/TLS証明書の導入

さて、独自ドメイン運用をしているGoogle App Engine(GAE)のサイトをSSL/TLS対応をしてみましょう。

Let's Encrypt では、証明書発行の際に、ドメインの所有の確認のため、指定されたURLにファイルをアップロードする必要があります。よくあるプロセスですね。

通常のサーバーの場合は、この確認用のファイルの設定や、Apache/nginxなどの設定もcertbotというプログラムが証明書の取得とあわせてやってくれるのですが、GAEの場合証明書の設定はGUIでしかできません。

そこで、証明書の取得のみ行うmanualモードで行います。なのでこのためにわざわざサーバーを立てる必要はありません。以下では Mac OSX 10.11.4で作業しました。

1. 独自ドメインの設定を終えておく

まずは、独自ドメインがGAEで表示できる状態にしておきます。 あとのステップで、Let's Encrypt から取得しようとする証明書用のサーバーにリクエストが来て所有の確認が行われるので、DNSの設定などは済ませておく必要があります。

2. 証明書の取得

まずは、Let's Encrypt のクライアントのインストールを行います。

cloneします。10MBほどあるので、そこそこ時間かかります。

$ git clone https://github.com/certbot/certbot
$ cd certbot

それでは早速証明書を取得してみましょう。初回は必要なプログラムのインストールが行われるので、時間がかかります。 また環境のrootのPWを求められる場合があります。

初回のみ、メールアドレスを聞かれるのと、利用規約への同意を求められます。 ちなみにこのcert-onlyのonlyというのは、インストールも行うrunに対して、インストールはしない、という意味です。 また以前は、letsencrypt-autoでしたが、2016年5月からcertbot-auto に変更になったそうです。

$ ./certbot-auto certonly --manual
# PWを求められるかも

必要なミドルウェアのインストールが終わると、次のような画面になります。 ここに取得予定のドメインを入力します。カンマ区切りスペース区切りで複数可。

f:id:konisimple:20160602230440p:plain

次にクライアントのIPアドレスがログに記録され公開されることに対する同意確認画面。Yesで進みます。

f:id:konisimple:20160602230413p:plain

次に以下のようなメッセージが表示されます。

Make sure your web server displays the following content at
http://cheetahapp.net/.well-known/acme-challenge/gTIjcnRpydLV5lcRDD-b-2TcTRh4YyZC26jDSZOx2ck before continuing:

gTIjcnRpydLV5lcRDD-b-2TcTRh4YyZC26jDSZOx2ck.5Hvrmt1oBopQ03IDSAdTZRRoXZACheMnLi_Y272bLx4

If you don't have HTTP server configured, you can run the following
command on the target server (as root):

mkdir -p /tmp/certbot/public_html/.well-known/acme-challenge
cd /tmp/certbot/public_html
printf "%s" gTIjcnRpydLV5lcRDD-b-2TcTRh4YyZC26jDSZOx2ck.5Hvrmt1oBopQ03IDSAdTZRRoXZACheMnLi_Y272bLx4 > .well-known/acme-challenge/gTIjcnRpydLV5lcRDD-b-2TcTRh4YyZC26jDSZOx2ck
# run only once per server:
$(command -v python2 || command -v python2.7 || command -v python2.6) -c \
"import BaseHTTPServer, SimpleHTTPServer; \
s = BaseHTTPServer.HTTPServer(('', 80), SimpleHTTPServer.SimpleHTTPRequestHandler); \
s.serve_forever()"
Press ENTER to continue

ここははやる気持ちを抑えてまだENTERは押しません!!

指示どおりに、Let's Encryptからのメッセージの存在確認用URLにテキストを置きます。 上記の場合は、以下のURLで以下が表示されるようにします。

URL: http://cheetahapp.net/.well-known/acme-challenge/gTIjcnRpydLV5lcRDD-b-2TcTRh4YyZC26jDSZOx2ck

内容: gTIjcnRpydLV5lcRDD-b-2TcTRh4YyZC26jDSZOx2ck.5Hvrmt1oBopQ03IDSAdTZRRoXZACheMnLi_Y272bLx4

GAEでは以下の様に設定します。

lets_encrypt.txt:

gTIjcnRpydLV5lcRDD-b-2TcTRh4YyZC26jDSZOx2ck.5Hvrmt1oBopQ03IDSAdTZRRoXZACheMnLi_Y272bLx4

app.yaml:

- url: /.well-known/acme-challenge/gTIjcnRpydLV5lcRDD-b-2TcTRh4YyZC26jDSZOx2ck
  static_files: lets_encrypt.txt
  upload: lets_encrypt.txt

デプロイします。

$ appcfg update .

さっきのURLをクリックして内容が表示されることを確認したら、エンターを押します。

しばらくすると、以下のような内容が表示されて無事証明書が取得できました。

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/cheetahapp.net/fullchain.pem. Your cert will
   expire on 2016-08-31. To obtain a new or tweaked version of this
   certificate in the future, simply run certbot-auto again. To
   non-interactively renew *all* of your ceriticates, run
   "certbot-auto renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

証明書と秘密鍵は/etc/letsencrypt/live/[domain_name]にあります。

$ sudo ls /etc/letsencrypt/live/cheetahapp.net
cert.pem    chain.pem   fullchain.pem   privkey.pem

秘密鍵を復号化します。

$ sudo openssl rsa -in /etc/letsencrypt/live/[domain_name]/privkey.pem -out /etc/letsencrypt/live/[domain_name]/privkey_no_pass.pem

3. GAEへの登録

GCPのコンソールから、GAE→設定→SSL 証明書→新しい証明書をアップロードの順に開きます。 「PEM でエンコードされた X.509 公開鍵証明書」の欄に、fullchain.pemを、 「復号化された PEM でエンコードされた RSA 秘密鍵」の欄にprivkey_no_pass.pemをペーストします。

ここで復号化を忘れると選択した秘密鍵は有効ではないようです。というエラーメッセージが表示されます。これ結構はまりましたw

4. 動作確認

無事に設定できると、以下のようにアドレスバーが緑になります。やった!!

f:id:konisimple:20160602231741p:plain

課題 証明書が3ヶ月で切れてしまう

SSL証明書は3ヶ月で切れてしまいます。通常年単位での更新なのでこれはちょっと面倒です。 サーバー自体にcertbotを入れれば、/etc/letsencrypt/live/[domain_name]に常に最新に証明書のシンボリックリンクが貼られるので手間はないのですが、GAEだとそうはいきません。 証明書の再取得はコマンドできるのですが、GAEへの証明書の送信はAPIなどあるのでしょうか。軽く探したところは見つかりませんでした。

3ヶ月おきにフォーム行くのは地味に面倒なので、そこに1000円払うと割りきって既存のSSL業者を使う、というのもありかもしれません。

証明書の更新 (2016/11/1 追記)

早速3か月経ったので、証明書の更新を行いました。 更新でもファイルをアップロードする必要がありますが、アップロードするURLとファイル内容は同じなので、コマンド押していけば簡単に新しい証明書を取得できます。 コマンドさえメモっておけば5分もあれば更新完了するので、そんなに手間でもありませんね。

ただまあGAEとかに関してはこの作業完全自動化してくれたら嬉しいんですけどねー。。

まとめ

GAEのSSL対応、これがあれば5分くらいで終わってしまいますね。 いつもSSL対応は、秘密鍵作ってそれでCSR作って会社のサイトのフォームに行って発行依頼して、メールクリックして、証明書DLしてなど複雑な手順だったので、かなり簡単になりました。 しかも全部無料、提供しているのがシスコ・アカマイ、Google、Facebookなので割と安心、ということで、銀行などが使っているアドレスバーに緑の組織名が表示されるEV SSL以外はどんどん移行が進むんじゃないでしょうか。

参考

qiita.com 秘密鍵の復号化で同じはまりをされていた方w

qiita.com 同様のことをGCEのインスタンスで行った事例。

*1:どちらも改ざんと盗聴には効果がありますが、なりすましには無力です。

Twitter API「Read-only application cannot POST」というエラーの解決方法

Twitter API 技術メモ

Twitter API でPostのAPIを叩いたら以下のような見慣れないエラーが。

Read-only application cannot POST

原因は、Twitterのアプリの設定で、Access LevelReadになっていること。

僕の場合は、Read and writeにはしていたのですが、コンシューマーキーを再生成していないことでした。

Access Levelを変えるページにちゃんと書いてありました。 小さくて薄い字で・・

Access Levelを変えたらコンシューマーキーを再生成しましょう。

Note:
Changes to the application permission model will only reflect in access tokens obtained after the permission model change is saved. You will need to re-negotiate existing access tokens to alter the permission level associated with each of your application's users.