2021.01.06

はじめてのGoogle App Engine

こんにちは。次世代システム研究室のM.Mです。
今までいくつかのWEBアプリ開発・運用を担当したことがありますが、すべてオンプレミス環境かクラウド環境に作成したVMにWEBアプリで利用する機能をインストールしてWEBアプリの環境構築を行っていました。
今回、始めてGoogle Cloud Platformを利用する機会があったため、今までと違いサーバーレス環境であるGoogle App Engineを使った場合、今まで担当していたWEBアプリで行っていたことは、どのようにすれば実現できるのか調べてみることにしました。

1.Google App Engineへのデプロイ

プロジェクト名はmm-gae-test01として作成し、Google App Engine スタンダード環境でPHP7を利用することにします。
Google App EngineでPHP7のWEBアプリを動かすこと自体はクイックスタートをやればすぐにできました。
すごく丁寧な手順になっているのでここでは記載しませんが、Cloud SDKをダウンロードしてapp.yamlとindex.phpを用意してデプロイコマンドを実行するだけでした。

2.今まで担当していたWEBアプリで行っていたこと

サーバーレス環境の場合、サーバーにログインしてログファイルや負荷状況を調査することはないだろうから、まず今までWEBアプリで行っていたログ調査や監視がどう変わるのかが一番気になりました。
なので今回は、ログ・監視関連を中心に今まで担当していたWEBアプリで行っていた事をいくつかピックアップして調査しました。

  • WEBサーバー(Nginx)のアクセスログの確認
  • WEBサーバー(Nginx)によるIPアクセス制限の設定
  • WEBアプリが出力したログの確認
  • WEBアプリログの長期保存
  • WEBアプリのパフォーマンスチェック&遅延アラート送信
  • KVS(redis)の利用
  • DB(MySQL)の利用

3.どのようすれば実現できるのか

3-1.WEBサーバー(Nginx)のアクセスログの確認

サーバーレス環境なのでサーバーにログインしてWEBサーバーのログファイルを確認することができません。
ログについてのドキュメントに記載があるように、リクエストログ、アプリログはCloud Loggingエージェントに自動的に送信されて、ログビューアやコマンドラインで確認できるようです。

以下の図のようにGoogle Cloud PlatformのConsole画面のロギングから確認することができます。

参照したいリソースやログ名を選択することで確認したいログを抽出することができます。


また、Console画面からではなく、以下のようにgcloudコマンドにて取得することもできます。

> gcloud logging read "resource.type=gae_app logName=projects/mm-gae-test01/logs/appengine.googleapis.com%2Frequest_log" --limit=10 --format=json
[
  {
    "httpRequest": {
      "status": 200
    },
    "insertId": "5faca63e000da34db0d75728",
    "labels": {
      "clone_id": "00c61b117cc.....2de8c04f699ba32b"
    },
    "logName": "projects/mm-gae-test01/logs/appengine.googleapis.com%2Frequest_log",
    "operation": {
      "first": true,
      "id": "5faca63e00ff0a1cb3314ef94b00016.....32303131313274313034333239000100",
      "last": true,
      "producer": "appengine.googleapis.com/request_id"
    },
    "protoPayload": {
      "@type": "type.googleapis.com/google.appengine.logging.v1.RequestLog",
      "appEngineRelease": "1.9.71",
      "appId": "b~mm-gae-test01",
      "cost": 3.3561e-08,
.....

3-2.WEBサーバー(Nginx)によるIPアクセス制限の設定

Console画面からAPP Engine、ファイアウォールルールを選択すれば、以下の図のようにアクセスを許可・拒否するIPアドレスの登録ができます。
(始めはすべてのIPが許可になっているのですぐに設定したほうがよいです)


3-3.WEBアプリが出力したログの確認

Loggingクライアントライブラリを利用することで簡単にアプリ独自のログを出すことができます。
PHPの場合、以下のようLoggingクライアントライブラリを取得すれば、すぐに利用することができます。

composer require google/cloud-logging

PHPコードは以下の通り、LoggingClientを利用してWEBアプリの独自ログとして、12行目でログ名をtestapp1とし、13行名でLogger Hello Worldというログを出力しています。

<?php
require(__DIR__ . '/vendor/autoload.php');

use Google\Cloud\Logging\LoggingClient;

echo "Hello World";

$logging = new LoggingClient([
    'projectId' => 'mm-gae-test01'
]);

$logger = $logging->psrLogger('testapp1');
$logger->info('Logger Hello World');

アプリ独自のログも上記、WEBサーバー(Nginx)のアクセスログの確認で行ったように、Console画面、ロギングのページにてログを確認することができます。今回は、testapp1というログ名で出力したので、以下の図のクエリーのプレビューにあるように
logName=”projects/mm-gae-test01/logs/testapp1″
として検索すればアプリからのログを確認することができました。

また、今回は簡単な内容だったので、ローカル環境で動作確認をせず、いきなりApp Engineにデプロイしましたが、ローカル環境で実行すると以下のエラーがでました。

Fatal error: Uncaught Google\Cloud\Core\Exception\ServiceException: {
  "error": {
    "code": 403,
    "message": "The request is missing a valid API key.",
    "status": "PERMISSION_DENIED"
  }
}

ローカル環境から利用する権限がないようなので、認証周りを確認しました。
ドキュメント(サービス アカウントとして認証する)を確認するとサービスアカウントを作成してそのアカウントの秘密鍵をダウンロード、環境変数に設定することで利用できることが分かりました。
手順に沿ってサービスアカウントを作成して、以下のように秘密鍵をローカル環境に保存しました。

取得した秘密鍵を利用して実行したらエラーはでなくなり、Console画面のロギングページからもログが追加されていることが確認できました。(以下はWindows PowerShellでの実行例)

> $env:GOOGLE_APPLICATION_CREDENTIALS="C:\xxxxx\xxxxx\mm-gae-test01-c34e76cd8674.json"
> php .\index.php

3-4.WEBアプリログの長期保存

WEBアプリの運用をしていると重要なログ情報は長期保存が求められるケースがあります。
上記で確認したリクエストのログやアプリのログは、以下の図にある_Default logs bucketに保存されており、デフォルトで30日保存になっているようです。

ログのエクスポートの概要

今回は上記図にあるようにLogs Routerの設定をして、別途用意したCloud Storageにログを保存してみます。
まず、Cloud Storageのバケットを作ります。
Console画面からStorageを選択して、以下の図のようにバケットの作成を行います。

今回はtestapp1_logというバケットを作成しました。

まだ、何も保存されていないので、「表示する行がありません」と表示されています。
バケットの作成は終わったので、次はログルーターの設定をします。
Console画面のロギングからログルーターを選択して、シンクの作成を行います。
以下の図のように、シンクの宛先にて、作成したバケットを選択することができます。
(以下の図の右側(白い背景の個所)にバケットの一覧が表示されていて、一番下に上記にて作成したtestapp1_logが表示されています。)

続いて、どのログを対象にするかをフィルターの設定で行います。
今回は、testapp1というログを対象とするので、以下の図のようにシンクに含めるログの選択の項目で、
logName=”projects/mm-gae-test01/logs/testapp1″
と設定します。

これで設定は完了したので、何回かアクセスしてログを出力させます。
Cloud Storageへの反映は1時間に1回のようです。しばらく待つと以下の図のようにオブジェクトが登録されました。

バケット > testpp1_log > testapp1 > 2020 > 12 > 03
となっていて、名前も02:00:00_02:59:59_S0.jsonとなっていることから時間単位だということも分かります。

保存されたオブジェクトの取得方法は、gsutilコマンドやREST APIでも取得できます。

gsutilでの取得例

> gsutil ls gs://testapp1_log/testapp1/2020/12/03/
gs://testapp1_log/testapp1/2020/12/03/02:00:00_02:59:59_S0.json

> gsutil ls gs://testapp1_log/testapp1/2020/12/03/02:00:00_02:59:59_S0.json
gs://testapp1_log/testapp1/2020/12/03/02:00:00_02:59:59_S0.json

> gsutil cat gs://testapp1_log/testapp1/2020/12/03/02:00:00_02:59:59_S0.json
{"insertId":"1lzpbqzg1bqxmkp","jsonPayload":{"message":"Logger Hello World"},...}
{"insertId":"hhibmogcp5s4a4","jsonPayload":{"message":"Logger Hello World"},"...}
{"insertId":"1g64p62gci5oy9f","jsonPayload":{"message":"Logger Hello World"},...}
...

REST APIでの取得例(Windows PowerShellにて)

# アクセストークンの取得
> $env:GOOGLE_APPLICATION_CREDENTIALS="C:\xxxxx\xxxxx\mm-gae-test01-c34e76cd8674.json"
> gcloud auth application-default print-access-token
ya29.c.Kp0B.........taydPYNQzpCov-A


# オブジェクトの確認
> curl.exe -s -X GET --tlsv1.2 -H "Authorization: Bearer ya29.c.Kp0B.........taydPYNQzpCov-A" "https://storage.googleapis.com/storage/v1/b/testapp1_log/o"
{
  "kind": "storage#objects",
  "items": [
    {
      "kind": "storage#object",
      "id": "testapp1_log/testapp1/2020/12/03/02:00:00_02:59:59_S0.json/1606964769916429",
      "selfLink": "https://www.googleapis.com/storage/v1/b/testapp1_log/o/testapp1%2F2020%2F12%2F03%2F02:00:00_02:59:59_S0.json",
      "mediaLink": "https://storage.googleapis.com/download/storage/v1/b/testapp1_log/o/testapp1%2F2020%2F12%2F03%2F02:00:00_02:59:59_S0.json?generation=1606964769916429&alt=media",
      "name": "testapp1/2020/12/03/02:00:00_02:59:59_S0.json",
      "bucket": "testapp1_log",
      ...
      "timeStorageClassUpdated": "2020-12-03T03:06:09.916Z"
    }
  ]
}

# オブジェクト内容の取得
> curl.exe -s -X GET --tlsv1.2 -H "Authorization: Bearer ya29.c.Kp0B.........taydPYNQzpCov-A" "https://storage.googleapis.com/download/storage/v1/b/testapp1_log/o/testapp1%2F2020%2F12%2F03%2F02:00:00_02:59:59_S0.json?generation=1606964769916429&alt=media"
{"insertId":"1lzpbqzg1bqxmkp","jsonPayload":{"message":"Logger Hello World"},...}
{"insertId":"hhibmogcp5s4a4","jsonPayload":{"message":"Logger Hello World"},...}
{"insertId":"1g64p62gci5oy9f","jsonPayload":{"message":"Logger Hello World"},...}
...

3-5.WEBアプリのパフォーマンスチェック&遅延アラート送信

WEBアプリのパフォーマンスの傾向をチェックして、1リクエストの処理に数秒以上かかったらアラートメールを送信するといった監視をすることがあります。
Google Cloud Platformにはロギング同様、モニタリングのサービスも提供されており、今回はそのモニタリングのサービスを利用して実現してみました。
実施内容としては、「Response Latencyが3秒以上だったらアラートメールを送信する」となります。
Console画面からMonitoringを選択すると、ダッシュボードが表示され、以下の図のようなResponse Latencyのグラフを確認することができます。

では、Response Latencyが3秒以上だったらアラートメールを送信する設定をします。
まず、アラートメールの送信先となるメールアドレスを通知チャンネルに登録します。

以下の図の上部にあるEDIT NOTIFICATIONS CANNELSから登録できます。

メールアドレスを登録すると以下の図のように登録したメールアドレスが表示されます。

続いて、アラートポリシーの登録をします。同様に画面上部にあるCREATE POLICYをクリックして作成します。
以下の図のようにMetricにResponse latencyを指定して、Thresholdに3000msと設定します。

その後、WEBアプリに3秒のスリープ処理を入れてアラートメールが送信されるか試してみます。
結果、アラートのダッシュボードにあるIncidentsにアラート内容が追加されて、アラートメールが送信されてきました。
以下の図のようにIncidentsの個所にアラートが追加されます。

また、アラートメールは以下のような内容のものが送信されてきました。


3-6.KVS(redis)の利用

PHPをつかったWEBアプリでは、セッション情報や、DBから取得した情報のキャッシュの保存先としてmemcachedやredisを利用するケースはあると思います。
今回はどのようにすればredisと連携できるのか試してみました。
まず、以下の図のように、Console画面のMemorystoreからRedisを選択してRedisインスタンスを生成します。

今回はインスタンスIDをmm-test-redisとして作成しました。
作成すると以下の図のようにIPアドレスとポートが分かります。

ただ、redisのIPアドレスとポートが分かればApp EngineのPHPアプリから接続できるという訳ではなく、VPCネットワークの設定をする必要がありました。
続いて、VCPネットワークの設定をします。
Console画面のVPCネットワークからサーバレスVPCアクセスを選択してコネクタを作成します。
今回は、コネクタ名をmm-test-redis-conとして作成しました。

Console画面を使っての操作は上記のみですが、さらにPHPアプリ側の設定もする必要があります。
App EngineのPHPはデフォルトではredisのモジュールが使えないらしく、redisのモジュールを利用する設定を追加する必要があります。
app.yamlと同じ場所にphp.iniを作って、

extension=redis.so

と記載してデプロイすれば、redisのモジュールが読み込まれます。
後は、上記で作成したVPCネットワークの設定をapp.yamlに追加します。

vpc_access_connector:
  name: projects/mm-gae-test01/locations/asia-northeast1/connectors/mm-test-redis-con

PHPコードは以下のようにすれば接続することができました。

$redis = new Redis();
$redis->connect('10.13.152.203', 6379);

$redis->set('testapp_key', 'hogehoge', 3600);
$val = $redis->get('testapp_key');
echo $val;

3-7.DB(MySQL)の利用

WEBアプリではRDBMSを利用したシステム構成になっていることが多いと思います。
そこで今回はCloud SQLのMySQLを利用してみました。
Redisと同様、Console画面からSQLを選択するとMySQLインスタンスを作成することができます。
また、このMySQLはApp Engine内のPHPアプリからしか使わないので、以下の図のようにプライベートIPのみ使うようにして生成しました。

DBやユーザーもConsole画面上で作成することができました。
今回は、DB名をmmtest、ユーザーをmmuser、パスワードをhogehogeとしてユーザーを作りました。
後は、Redisの時と同じようにVPCネットワークの設定を行い、以下のPHPコードで接続確認を行いました。

$username = 'mmuser';
$password = 'hogehoge';

$db_name = 'mmtest';
$host = "10.120.96.5";
$dsn = sprintf('mysql:dbname=%s;host=%s', $db_name, $host);

try {
    $conn = new PDO($dsn, $username, $password);
    
    $sql = "SELECT current_timestamp FROM dual";
    $stmt = $conn->query($sql);
    $results = $stmt->fetchAll();
    var_dump($results);

} catch (PDOException $e) {
    echo $e->getMessage();
}

無事にApp EngineのPHPからMySQLに接続できました。

次に、MySQLに接続してテーブルを作成します。
プライベートIPだけで作成したけど、Console画面から利用できるCloud Shellから接続できるのかなと思いましたが接続できませんでした。
考えが甘すぎました。
とはいえ、いちいちテーブル作成する処理をPHPで書いてApp Engine経由でテーブルを作成するなんて運用はできないので、パブリックIPの設定も行いローカルPCから接続できるようにしました。
また、以下の図に「パブリックIPを使用してインスタンスに接続する場合は、SSL暗号化をおすすめします。」と記載があるので、SSL接続のみ許可します。

SSL接続するため、以下の図のようにクライアント証明書をダウンロードしておきます。

また、ローカル環境から接続するためのサービスアカウントを作成します。
以下の図のようにCloud SQLを操作するロールを持ったアカウントを作成します。

アカウントを作成したら、上記、WEBアプリが出力したログの確認のところで行ったようにアカウントの秘密鍵をダウンロードしておきます。
そして、ローカル環境から接続するために、Cloud SQL Proxyを利用します。
まず、以下のコマンドのパラメータcredential_fileに上記にてダウンロードしたアカウントの秘密鍵を指定してプロキシを起動します。
すると、127.0.0.1:3306で接続できることが分かります。

> cloud_sql_proxy_x64.exe -instances=mm-gae-test01:asia-northeast1:mmtestdb01=tcp:3306 -credential_file="C:\...\mm-gae-test01-602ac5c1a8a5.json"
2020/12/17 11:37:32 using credential file for authentication; [email protected]
2020/12/17 11:37:32 Listening on 127.0.0.1:3306 for mm-gae-test01:asia-northeast1:mmtestdb01
2020/12/17 11:37:32 Ready for new connections
2020/12/17 11:38:33 Received TERM signal. Waiting up to 0s before terminating.

その後、MySQLのクライアントにて、接続先を127.0.0.1:3306として、接続時の証明書にダウンロードしたクライアント証明書を指定することで、ローカル環境から接続することができました。
これで無事にローカル環境からテーブルの作成をすることができました。
(先ほど接続確認したApp EngineのPHPからの接続もSSL接続が必要になるため、作成した証明書を利用した接続方法に変更する必要があります。)

4.最後に

今回は始めてApp EngineとGoogle Cloud Platformとして提供されているロギング、モニタリング、Redis、MySQLを利用しました。
気になっていたログ調査や監視についても、追加で何か用意しないといけないということもなく手軽に利用できるのはよいなと感じました。
ただ、ログを分析したり、細かい監視設定を行うようなサービスの場合、App EngineのみでWEBアプリを構築するのではなく、最終的にはGoogle Cloud Platformにて提供されている各種リソースと連携したシステム構成になると感じました。そのためVPCネットワークと各種リソースとの連携については、しっかり理解したうえで環境構築をする必要があると感じました。

最後に、次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

皆さんのご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事