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

GCPコンソールのヘッダの色を変更するChrome拡張を作りました

GCP のコンソールで複数のプロジェクトを操作していると、特定のプロジェクトだけ視覚的に目立たせたいことがあります。 特に開発用のプロジェクトと本番のプロジェクトを行き来していると、たまにプロジェクトを間違えそうになってヒヤッとすることがありますよね。

そこで GCP コンソール画面のヘッダの色をプロジェクト毎に変更できる Chrome 拡張を作りました。
GCP console colorize

img1

条件は複数書けるので、プロジェクト毎に細かく設定できます。
(正規表現も使えます)

Chrome Web Store からインストールできるので是非導入してみてください。

GAE Datastore の Single-property Index と Composite Index は全く違うものと理解する

Datastore の Single-property Index と Composite Index はどちらも似たようなものだと思っていたのですが、実際のところはかなり違う性質をそれぞれ持っていることが段々とわかってきたので、現時点の自分の理解をメモしておきます。 恐らく Cloud Datastore にも該当する話だと思います。

Datastore のインデックスの種類

Datastore には2種類のインデックスが存在します。

  • Single-property Index (Built-in Index とも呼ばれる)
  • Composite Index (Custom Index とも呼ばれる)

このうち Single-property Index はデフォルトで全てのプロパティに有効になっているインデックスであり、Entity を保存した段階でそれぞれのプロパティに対応するインデックスが自動で作られます。 Composite Index はあらかじめ設定ファイルで定義しておく必要があり、それによって複数のプロパティを組み合わせた複合インデックスを作ることが出来ます。

両インデックスの違い

一番大きな違いだと思うのが、Single-property Index が Entity 単位にインデックスの ON/OFF をコントロールできる一方、Composite Index は Datastore の Kind 毎に ON/OFF されるという点です。 この性質の違いがいくつかの挙動の違いを生みます。

1. Single-property Index を途中からつけても、既存の Entity には効果がない

これはドキュメントに書かれてる内容ですが、今まで明示的に Unindex としていたプロパティに途中からインデックスを定義しようとしても、既存の Entity に対応するインデックスは作成されません。 そのため、古い Entity は MapReduce などを用いて fetch → put と再保存してインデックスを構築し直す必要があります。 これは Entity 単位にインデックスの ON/OFF が制御されるという性質上しょうがないものかと思います。

逆に Composite Index は All or Nothing なので、途中から定義したとしても既存を含む全ての Entity に作られることになります。

2. Single-property Index がないと Composite Index にエントリが追加されない

これは(自分の知る限り)アンドキュメントな内容ですが、Composite Index を設定ファイルで定義したとしても、そこで使用しているプロパティの Single-property Index が一つでも存在しない場合、Composite Index にはエントリが追加されません。 実際にその複合インデックスを使うようなクエリを投げても、単純にクエリに引っかからないような挙動になり、エラーもでないのでかなりハマります。 なぜこのような挙動になっているのか原理や理由はわかっていませんが、Composite Index は全ての Entity が対象となるので、一部の Entity をインデックスに追加しないようにするためにそういう機構があるのでしょうか。

Single-property Index は必ず定義しておくべき

上記のようなことがあるため、特別な理由がない限り Single-property Index は定義しておくべきだと考えています。 以前はインデックスの作成操作も Datastore write ops に含まれてしまっていたので必要のないインデックスはなるべく避ける傾向にあったと思いますが、今はそれらが Entity 単位の Quota になったため、あまり気にしなくていい感じになりました。 Single-property Index が既に存在していると、あとから Composite Index も張りやすくなりますし、なにより Datastore のコンソール画面からクエリを投げられるのでデバッグがやりやすくなると思います。

とはいってもインデックスの作成はその分ストレージを消費しますし、インデックスに追加されるまでの時間もインデックス数に応じて増えていくと思うので、他のインデックス追加に影響を及ぼして参照整合性が弱くなってしまうのを避けたい、といった場合には控えたほうが良さそうです。 (cf. Anti Pattern #2: Too Many Indexes - Balancing Strong and Eventual Consistency with Google Cloud Datastore)

一点実際のプログラミング時に注意が必要なのは、GAE/Java + Objectify で開発していると明示的に @Index アノテーションをプロパティに付加しておかないと setUnindexedProperty でプロパティが定義されてしまい、インデックス=OFFとなってしまう点です。 これが例えば GAE/Python + ndb だとプロパティを定義するとデフォルトでインデックス=ONとなるので、個人的には Objectify もデフォルトはインデックス=ONの挙動にしておいて欲しかった感じはあります...。

まとめ

Datastore の Single-property Index と Composite Index の性質をまとめると以下のようになります。

制御単位 インデックスの構築 注意点
Single-property Index Entity 単位 半自動 古い Entity は手動でインデックスを作る
Composite Index Kind 単位 自動 Single-property Index が作られていないとダメ

手探りで色々試している面もあるので、理解が間違っている等ありましたらご指摘下さい。

参考

GAE Task Queue をマイクロサービスのサービス間通信として使う

昨今マイクロサービスアーキテクチャに基づいたアプリケーション構築が話題ですが、Google App Engine を用いてる場合 Task Queue をサービス間の通信として便利に使用することが出来ます。

GAE の Task Queue (Push) について

背景

マイクロサービスでよくやることとして、あるサービスでイベントが発生したらそれを別のサービスに非同期に通知したいことがあります。 通常のアプリケーションの場合、何かしらの Message Queue を用いて Job Worker 経由で別のサービスに通知することが多いと思います。

+-----------+
| Service A |
+-----------+
     ↓ Push
+-----------+
|    MQ     |
+-----------+
     ↑ Pull
+-----------+
| Job Worker|
+-----------+
     ↓ HTTP
+-----------+
| Service B |
+-----------+

この場合 Job Worker は確実に Service B に通知を行なうよう、リトライ処理などをきちんと実装する必要があります。

一方 Google App Engine でアプリケーションを構築してる場合、代わりに Push 型の Task Queue を使うことで実現できます。

+-----------+
| Service A |
+-----------+
     ↓ Push
+-----------+
| Task Queue|
+-----------+
     ↓ HTTP
+-----------+
| Service B |
+-----------+

Task Queue はタスクの通知を HTTP で行なうため、サービス間に Job Worker を挟まずダイレクトに別のサービスに通信できる、という算段です。

Task Queue のコード例

以下のコードは Java で別のサービスに通信する例です。 アプリケーション特有のヘッダや、HTTP Body などを割と自由に付与できます。

// キューの取得
Queue queue = QueueFactory.getDefaultQueue();

// タスクの定義
TaskOptions task = TaskOptions.Builder
    .withMethod(TaskOptions.Method.POST)
    .url("/service_b/evetns")
    .header("X-My-Header", "foo, hoge")
    .payload("{\"eventId\": 123}".getBytes(), "application/json");

// タスクのキューイング
queue.add(task);

Task Queue だと何が嬉しいか

Job Worker となる部分を用意しなくて済むのも大きなメリットですが、他にも以下の様なメリットがあります。

  • Task Queue は App Engine のフルマネージドな部分なので、上述のリトライ処理を適切に行なってくれます。
    • タスクの種類ごとにリトライの間隔なども調整可能です。
  • 通常の MQ を用いた場合の欠点として、データベースのトランザクションの中に MQ へのキューイングを含められませんが、App Engine の場合 Datastore のトランザクションの中に Task Queue へのキューイングを含めることができます。
    • サービス自体の処理は成功したけど、別のサービスへの通知は失敗していた、などの中途半端な状態を防げれます。

注意点

Task Queue にもいくつか制約があります。

  • あくまでも非同期通信です。同期的なサービス間通信をしたい場合は、通常通りサービス内で HTTP 通信をします。
  • 使用できる HTTP メソッドは GET/POST/PUT/DELETE のみです。PATCH など別のメソッドを使いたい場合は X-HTTP-Method-Override などの Method Override を併用することになると思います。
  • Task Queue からタスクを受け取ったサービスは 200 番台のレスポンスを返さないとリトライされてしまいます。仮に 300/400 番台のレスポンスで成功を表すものがある場合は注意が必要です。
  • Task Queue で指定できる URL は、同じ App Engine アプリケーションで動いているサービスに対してのみです。App Engine 外に別のサービスを置いている場合は、一度 App Engine 内でタスクを受け取ったあとに行う必要があります。

まとめ

Task Queue を用いたサービス間通信のメリットについて書きました。 何より Task Queue によるタスクの受け渡しが HTTP で通信されるというのが大きな特徴ではないでしょうか。 マイクロサービスではサービス間通信を HTTP で行なうことが多いので、今回挙げた例のように「リトライ付きの非同期 HTTP クライアント」として Task Queue を使えるというのは非常に魅力的に思えます。

Let's Encrypt でサイトをHTTPS化する

Let's Encrypt が Public Beta になり事前申請なしで証明書の発行ができるようになったので、試しに使ってみました。

環境

事前準備

証明書を取得する

Let's Encrypt のドキュメントの通り、letsencrypt-auto スクリプト一発で証明書を取得することが出来ます。

# EC2 内において
$ git clone https://github.com/letsencrypt/letsencrypt
$ cd letsencrypt
$ ./letsencrypt-auto --debug certonly

letsencrypt-auto を実行すると、Linux の設定画面のような TUI の青い画面が表示され、対話形式でメールアドレスやドメイン名を入力していきます。

この間に裏では ACME プロトコルによるチャレンジが行われており、問題なく終了すると /etc/letsencrypt/live/{domain}/ 以下に証明書等が作られます。

作られた証明書を見てみる

$ sudo ls /etc/letsencrypt/live/{domain}
cert.pem  chain.pem  fullchain.pem  privkey.pem
  • cert.pem : 証明書
  • chain.pem : 中間証明書
  • fullchain.pem : cert.pem と chain.pem がくっついたもの
  • privkey.pem : 秘密鍵

実際に取得した証明書を見てみると、確かに Let's Encrypt の CA による署名が付いていることがわかります。 証明書の有効期限は3ヶ月と短いですね。

$ sudo openssl x509 -in /etc/letsencrypt/live/{domain}/cert.pem -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            01:45:4d:37:ef:ab:46:11:f5:b9:8d:e5:d0:55:97:af:02:cc
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X1
        Validity
            Not Before: Dec 26 06:00:00 2015 GMT
            Not After : Mar 25 06:00:00 2016 GMT
    ...
    ...
    ...

nginx に設定する

nginx.conf の server ディレクティブに証明書と秘密鍵のパスを指定します。 nginx をリロードして、ブラウザから https でアクセスできれば成功です。

server {
    listen 443;
    server_name localhost;
    root /var/www/html;
    
    ssl on;
    ssl_certificate /etc/letsencrypt/live/{domain}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{domain}/privkey.pem;
    location / {
    }
}

Let's Encrypt を使うと、手作業でも最初の git clone からものの 10 分ほどでサイトをHTTPS化することができました。 いやー簡単だなー。

MySQL で JWT の中身を確認する

MySQLJSON Web Token (JWT) をデコードして表示してくれるプラグインを作りました。

addsict/mysql_jwt

こんな風に使えます。

> SET @token = 'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vZXhhbXBsZS5jb20iLCJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cDovL2FwcC5leGFtcGxlLmNvbSIsImV4cCI6MTQyNDQzNzQ5MSwiaWF0IjoxNDI0NDM2NTkxLCJqdGkiOiIxMjM0NTY3ODkwIn0.V0SEo1Y1kurWp2bSYU9gEQ2K9nweII_RNIlYEBRHdWY';
> SELECT decode_jwt(@token)\G
*************************** 1. row ***************************
decode_jwt(@token): {"iss":"http://example.com","sub":"1234567890","aud":"http://app.example.com","exp":1424437491,"iat":1424436591,"jti":"1234567890"}
1 row in set (0.00 sec)

decode_jwt()の第二引数に claim を指定することもできます。

> SELECT decode_jwt(@token, 'iss');
+---------------------------+
| decode_jwt(@token, 'iss') |
+---------------------------+
| http://example.com        |
+---------------------------+
1 row in set (0.00 sec)

> SELECT FROM_UNIXTIME(decode_jwt(@token, 'exp'));
+------------------------------------------+
| FROM_UNIXTIME(decode_jwt(@token, 'exp')) |
+------------------------------------------+
| 2015-02-20 22:04:51                      |
+------------------------------------------+
1 row in set (0.00 sec)

DB に格納されてる JWT の中身をささっと見たい時に便利かなと思うので、是非使ってみてください。

Web アプリケーションに HAL を適用する

HAL (Hypertext Application Language) とは Web API でやり取りされるリソースを、ハイパーメディアとしても扱えるようにするための仕様です。 ハイパーメディアとは HTML のアンカータグをイメージするとわかりやすいですが、ドキュメントといったメディアがリンクを介して繋がっている状態のことを指します。 例えば通常の Web API で取得できるリソース同士は特別何かで繋がっているわけではなく、お互いのリソースがある意味孤立した状態にありますが、そこにリンクを含めてあげることでお互いのリソース間を行き来することが出来るようになります。

HTML と HAL をハイパーメディアの観点で言うと、このような感じになるでしょうか。

  • HTML: 人間が操作するためのハイパーメディア
  • HAL: 機械が操作するためのハイパーメディア

具体的にリソースを HAL + JSON で表すとこんな感じになります。

{
    "userId": 10,
    "lastOrderDate": "Mon, 07 Jul 2014 18:26:21 GMT",
    "_links": {
        "self": { "href": "/users/10/orders" },
        "next": { "href": "/users/10/orders?page=2" }
    },
    "_embedded": {
        "orders": [{
            "orderId": 12,
            "orderDate": "Mon, 01 Jul 2014 18:26:21 GMT",
            "_links": {
                "self": { "href": "/users/10/orders/12" }
            }
        }, {
            "orderId": 11,
            "orderDate": "Mon, 01 Jul 2014 18:26:21 GMT",
            "_links": {
                "self": { "href": "/users/10/orders/11" }
            }
        }]
    }
}

userIdlastOrderDate といったリソースの情報と共に、それに付随したリンクが _links というプロパティに含まれています。 このリソースを使用するクライアントはそのリンクを「辿る」ことにより、別のリソースの操作を行おうという試みです。

こういったハイパーメディアを Web API に利用する考えは HATEOASREST Level3 など、昨今注目を集めているように思えます。

ブラウザと Web API の相性の悪さ

ここで少しブラウザと Web API*1 の関係について考えてみます。 ブラウザは人間が操作するものであり、HTML 上のリンクをクリックして別のドキュメントに遷移したりします。 しかし昨今の Web アプリはそういった画面遷移の他にも、JavaScript で Web API 用の URL を組み立て、Ajax を使って呼び出したりすることが多くなっています。 しかしそういった Ajax を使った Web API の呼び出しは、それまで HTML で表せていたハイパーメディアとしての領域をはみ出してしまいます。 リソース同士の繋がりを HTML とは別のもの(例えばクライアントサイドで定義したURLなど)を利用して表現しなければならないからです。 このように Web API が絡んでくると、クライアントサイドは既存のハイパーメディアの上にただ単純に乗っかっていれば済む話ではなくなるため、ブラウザとの相性が悪いと個人的には思っています。

ブラウザで HAL を使う

そこで、前述した HAL をブラウザでも使いたい!というのが本エントリーの主旨です。 具体的に言うと、HTML の他に HAL 形式のリソースを同時にブラウザに渡し、JavaScript ではその HAL 形式のリソースを参照します。 同じリソースを表しているのに、HTML と HAL という2つの表現として渡されてしまうという欠点はありますが、クライアントサイドは HAL に書かれているものだけを使えばいいので、サーバサイドとクライアントサイドが疎結合アーキテクチャになります。

例えば以下の様な HTML があるとします(HAL で表したリソースを type="application/hal+json" の script タグに埋め込んでおきます)。

<html>
<head>
    <script type="application/hal+json" id="hal-resource">
    {
        "_links": {
            "self": { "href": "http://example.com/users/10/orders" },
            "next": { "href": "http://example.com/users/10/orders?page=2" }
        },
        "_embedded": [{
            "id": "order-1",
            "_links": {
                "self": { "href": "http://example.com/users/10/orders/1" }
            }
        }, {
            "id": "order-2",
            "_links": {
                "self": { "href": "http://example.com/users/10/orders/2" }
            }
        }]
    }
    </script>
</head>
<body>
    <ol>
        <li id="order-1">Order 1</li>
        <li id="order-2">Order 2</li>
    </ol>
</body>
</html>

ここで次のページを Ajax で取得したいといった時、普通のやり方だと JS で URL を組み立ててそれを使ったりするでしょう。

var userId = 10;
var nextPage = 2;
var url = "/users/" + userId + "/orders?page=" + nextPage;
$.ajax({
    type: "GET",
    url: url,
    success: function (...) { ... },
    error: function (...) { ... },
});

一方、HAL を使うと、JS 側では渡されたリソースのリンクを辿るだけで次のページを取得できます。

JavaScript で HAL を扱うには halfred(https://github.com/basti1302/halfred) というライブラリがおすすめです。

var hal = $("#hal-resource").html();
var resource = halfred.parse(JSON.parse(hal));
var url = resource.link("next").href;

$.ajax({
    type: "GET",
    url: url,
    success: function (...) { ... },
    error: function (...) { ... },
});

このように単純にブラウザを HAL のクライアントとみなすことで、Web API との親和性も高くなります。

さいごに

HAL を Web アプリケーションに使用した場合の一番わかりやすい利点は、エンドポイントの管理がサーバサイドで完結し疎結合アーキテクチャになる点です。 もちろん HAL なんて使わなくても同様のことは簡単に実現できますが、標準に乗っかることでブラウザ以外の別のクライアントでも同じやり方を採用できたりするので、色々ハッピーかなと思ったりする年の瀬でした。

参考

*1:ここでは WebAPI を「HTTP プロトコルを利用してリソースを操作するためのコンピュータ用のインターフェース」として考えています。

Canonical な JSON エンコーディング

あるデータ表現を JSONエンコーディングする際、常に同じキーの順番でエンコーディングして欲しいことがあります。 例えば JSON Web Token (参考) は JSON 文字列をトークンとして用いるので(正確には更に Base64 エンコーディングしたもの)、 中身は同じ内容でも JSON へのエンコーディングの仕方によっては最終的なトークン文字列が異なるものになってしまいます。 特に Perl の場合は Perl v5.18 からハッシュのランダム化 がされるので、 JSON エンコーディングする度に異なる文字列が得られる可能性が高いです。

Canonical JSON

常に同じキーの順番でエンコーディングされる JSON のことを Canonical (正準) JSON と言ったりするそうです。 Perl では JSON.pm の canonical メソッドCanonical モードにすることが出来ます。

use JSON;

my $json = JSON->new->canonical;

Example

#!/usr/bin/env perl

use strict;
use warnings;
use v5.18;

use JSON;

for (1..3) {
    my $hash = +{ foo => 1, bar => 2, baz => 3 };
    say JSON->new->encode($hash);
}

say '';

for (1..3) {
    my $hash = +{ foo => 1, bar => 2, baz => 3 };
    say JSON->new->canonical->encode($hash);
}

結果

{"bar":2,"baz":3,"foo":1}
{"foo":1,"baz":3,"bar":2}
{"baz":3,"bar":2,"foo":1}

{"bar":2,"baz":3,"foo":1}
{"bar":2,"baz":3,"foo":1}
{"bar":2,"baz":3,"foo":1}

CanonicalJSON は常にキーでソートされた順番でエンコーディングされていることがわかると思います。 JSON Web Token など常に同じ JSON 文字列を期待する場面では Canonical JSON を使用しましょう。