エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Googleフォームへの投稿を Google Apps Script で Amazon SQS にメッセージ送信する話

この記事は エムスリー Advent Calendar 2019 の15日目の記事です。

こんにちは。エンジニアリンググループ、SREチームの平岡(@uhtter)です。

思い返せば早1年前、エムスリーに入社してから最初に書いたのが AdventCalender の記事でした。 今回は、エムスリーのSREチームの業務に関するお話をします(タイトルもそれに関連します)。 最後までお付き合いいただければ幸いです。

f:id:uhm3:20191213194016j:plain
開発合宿*1で撮った風景写真です。これからフェリーに乗って帰るところです。

背景:エムスリーSREチームの役割

エムスリーのエンジニアリンググループには、サービスを開発するUnitチームと、サービス横断で技術支援を行う横断チームとがあります。SREチームは横断チームの1つとして、各Unitチームでのサービス開発とその運用を支援するための様々な業務 *2 を担っています。

また、エンジニアリンググループはその文化として「開発で扱う技術の選定は基本的にチームに一任する」という方針をとっています。事実、各サービス/チームで扱っている技術スタックは多種多様な状況*3であり、結果として、SREチームにも多種多様な技術支援(=テックサポート)の依頼が毎日のように来ます。そこでSREチームでは、これらの技術支援の窓口として以下の窓口を提供しています。*4

  • SREテックサポート依頼フォーム
    • 基本となる依頼受付用窓口(Googleフォーム)
  • Slack での @sre のメンション
    • 緊急の依頼受付、依頼前の相談用
  • 依頼受付用メールアドレス
    • エンジニアリンググループ外 or その他の依頼対応用

今回の記事は、1つ目に挙げたテックサポート依頼フォームに関するお話です。

Google フォームからの依頼フロー

SREテックサポート依頼フォームはGoogleフォームで作成されており、利用者には依頼のカテゴリを選択&各種項目を入力してもらうだけのシンプルなものになっています。このフォームで依頼を送信すると、 Google Apps Script(以降、GAS)がトリガー実行され、そこからのロジックを経て最終的にエムスリーのオンプレミス環境で運用されている JIRA Server(以降、JIRA)に Issue(=チケット)として登録されます。

f:id:uhm3:20191214211815p:plain
SREテックサポート依頼フォームからJIRA ServerへのIssue登録まで

ここでネックになるのは、GASが実行されているのはパブリックなネットワーク(+G Suiteユーザー認証)である一方、JIRA が動作するのはプライベートなオンプレミス環境であることです。JIRA に Issue を登録するためには、パブリック→プライベートの方向で依頼の情報を渡すための手段が必要です。

以前はこの手段として、こちらの記事で説明されている方法を使っていました。ただこの方法だと、登録する Issue のフィールドを詳細に設定できない等の地味に融通がきかない部分があったため、別の手段を検討することにしました。

Google Apps Script + Amazon SQS で壁を越える

ここで話が記事のタイトルに繋がってきます。Amazon SQS(以降、SQS)を間に介することで、パブリック→プライベートの方向で情報をPushするのではなく、パブリック→SQSまで情報をメッセージとしてPushし、SQS→プライベートではメッセージをPullして受け取る方式で壁を超えることができます*5。SQS自体はメッセージキューとして汎用的に使えるものですが、ここでは情報伝達のフローを逆転させることを主目的として利用します。

このとき、パブリック→SQSの部分をGASのロジックで実装したい、という発想になるのは自然なことだと思います。であれば、 GAS で aws-sdk でも使おうかと思うところですが、残念ながら(筆者の知る限り)これに類するライブラリは提供されていません。だったら aws-sdk 内部の認証ロジック等を移植、再実装すれば……というのは、つらい道のりになることが進む前から明らかだと思います。

そこで今回は Amazon API Gateway の AWS Service Integration を利用して、 SQSへのメッセージ送信のAPIをシンプルな REST API としてラッピングすることにしました。単純な REST API であれば、GASから呼び出すこともさほど難しいことではありません。加えて、今回のケースで AWS Service Integration を利用すること以下のようなメリットも享受できます。

  • 特定のSQSキューへのメッセージ送信だけに絞ったAPIを構築できる
  • APIキーの認証を導入、キーの発行・失効を制御できる
  • CloudWatch でリクエストのメトリクス/ログを取得できる

これらの全体像を図で表現すると、以下のようになります。

f:id:uhm3:20191214211711p:plain
SREテックサポート依頼フォームからのフローの全体像

以降では、SQS キュー作成などの事前準備に始まって、API Gateway でのメソッド設定、GAS でのコード例について紹介した後にSQSからの受け手側についても少しだけ言及します。

事前準備

事前準備として、以下のリソースを用意しておきます。

  • SQSキュー
    • キュータイプを選択して作成し、可視性タイムアウト、配信遅延、保持期間などを要件に合わせて選択
      • 今回はフォームからの依頼を仲介するだけ → 標準キューを選択
      • メッセージ受信側のサービス==クライアントは常に単一で、処理も単純 → 適切に項目設定
  • IAMロール(API GatewayからSQSへのメッセージ送信用)
    • 作成したSQSキューへのSendMessageアクションをポリシーで許可すること
      • AmazonSQSFullAccess の定義済みポリシーをカスタムすると楽
  • IAMユーザー or ロール(SQSからのメッセージ受信用)
    • 作成したSQSキューからメッセージを取得するためのIAMリソースを用意する
      • 今回はオンプレミス環境で動作するサービスからアクセスしたい
        → IAMユーザーを作成してアクセスキーを発行

ここまでできたら、API Gateway で REST API のメソッド作成に移ります。

Amazon API Gateway の設定

API Gateway で作成したメソッドの(差し支えのない範囲での)具体的な設定を、以下に抜粋します。

  • メソッドリクエスト
    • API キーの必要性: true
  • 統合リクエスト
    • 統合タイプ: AWS サービス
    • AWS リージョン: SQSキューと同じリージョンを指定
    • AWS サービス: Simple Queue Service (SQS)
    • HTTP メソッド: POST
    • パス上書き: /
    • 実行ロール: 事前準備で作成したIAMロールを指定
    • コンテンツの処理: パススルー
    • HTTPヘッダー
      • Content-Type: application/x-www-form-urlencoded
    • マッピングテンプレート
      • Content-Type: application/json
        • テンプレート本文は ↓
Action=SendMessage##
&QueueUrl=$util.urlEncode('事前準備で作成したSQSキューのURL')##
&MessageBody=$util.urlEncode($input.body)##

上記の設定・テンプレートは、AWSドキュメントのSQS開発者ガイドにある クエリAPIのリファレンス にある内容をもとに構成したものです。地味な引っかかりどころとして、ドキュメントで先に載っている GET メソッドでの SendMessage を使おうとすると、メッセージ本文をURLのクエリ文字列に埋め込むため、一定以上の長さのメッセージを送ろうとしたときに URL Too Long で失敗します。後に載っている POST メソッドで SendMessage を呼び出す設定にしておけば大丈夫です。

Google Apps Script でのコード例

API Gateway と対になる GAS 側での API 呼び出しについては、ざっくりと以下のようなコードになります。

var API_KEY = "API Gateway で設定したAPIキー";
var METHOD_URL = "API Gateway で作成したメソッドのURL";
...
var content = {"質問1": "回答1", "質問2": "回答2"};
...
var options = {
  "method": "POST",
  "headers": {
    "x-api-key": API_KEY,
    "Content-Type": "application/json",
    "Accept": "application/json"
  },
  "payload": JSON.stringify(content)
};
var res = UrlFetchApp.fetch(METHOD_URL, options);
var result = JSON.parse(res.getContentText("UTF-8"));

というわけで、これ以上ないくらい簡単に呼び出せることがわかると思います。GAS 側で気を付けるポイントを挙げるとしたら、トリガーを設定後のスクリプトで OAuthスコープの変更された場合、マニフェストの更新だけでなくトリガー自体も再設定する必要があることです。トリガーを再設定しないと、以前の設定時のスコープのままで動作して権限エラーになるのでご注意ください。

サービス側のコード

SQSからメッセージを受け取るサービス側は、特に変わったロジックを組む必要はありません。GAS の制約に縛られることもないので、普通に実装言語で提供されている AWS のクライアントライブラリ(例: aws-sdk など)を利用してメッセージを受け取ってください。

以下は、実際のサービス(TypeScript + aws-sdk)でメッセージを受け取って処理する部分を簡略化したコードです。

const SQS_URL = "事前準備で作成したSQSキューのURL";
...
const receiveAnswerFromSQS = async function* (): AsyncIterableIterator<Answer> {
  const rparams: AWS.SQS.ReceiveMessageRequest = {
    QueueUrl: SQS_URL,
    WaitTimeSeconds: 20, // ロングポーリングでの最大待ち時間(20秒)を指定
  };
  for (; ;) {
    const data = await SQS.receiveMessage(rparam).promise();
    if (data.Messages != null) {
      for (let msg of data.Messages as SQSMessage[]) {
        if (msg.ReceiptHandle && msg.Body != null) {
          const ans = JSON.parse(msg.Body);
          yield ans as Answer;
          // 処理が完了したらSQSキューからdeleteする
          const dparam = {QueueUrl: queueUrl, ReceiptHandle: msg.ReceiptHandle};
          await SQS.deleteMessage(dparam).promise();
        }
      }
    }
  }
};
...
for await (let ans of receiveAnswerFromSQS()) {
  await createJiraIssueFromAnswer(ans);
}

ご覧の通り、上記では SQS からロングポーリングでメッセージを受け取っています。receiveMessage で受け取ったメッセージで JIRA に Issue を登録し、登録が完了したら deleteMessage する、という一般的な使い方に従っています。

ここまでで説明した作業・実装を行うことで、最初に説明した依頼フォームからのチケット登録フローを実現できました。

おわりに

今回は、普段はあまり語らないエムスリーSREチームのテックサポート業務を支える仕組み(の一部)について紹介しました。

さらなる改善の余地ですが、もう1つの依頼の窓口である Slack と連携・連動する仕組みをつくったり、あるいは依頼の登録時点で内容を特定して対処までを自動化したり……等々、できることはまだまだありそうです。「日々発生する Toil の負荷をエンジニアリングで低減する」という SRE の基本姿勢を堅持しながら、これからもガンガン業務の効率化に邁進していきたいと思います!

We are hiring!

エムスリーでは、より良いサービスの開発・運用のためにエンジニアリングを駆使するパワフルなエンジニア(特にSRE!)を募集しています。

技術が大好きで持て余している方は大歓迎ですので、ぜひ一度カジュアル面談にお越し下さい! 社内勉強会の Tech Talk もオススメです!!

jobs.m3.com

*1:今回の記事で紹介している一連の仕組みは開発合宿で作りました

*2:詳しくは こちらの採用情報 をご覧ください

*3:ご覧の Tech Blog からもよく伝わってくると思います

*4:実際には口頭で依頼されるケースも少なからずありますが、ここでは割愛します

*5:他にも手段はありますが、諸般の事情でSQSを使う方法にしています