• 加筆
  • 2017.03.14
  • 修正
三 月
9
木曜日
2017

打刻システムの構築(前編)

毎日の出勤退勤時間の記録の煩わしさと しばしば付け忘れて後に「あれこの日何時に出勤(退勤)したっけ?」の苦悩から解放されるため、自分による自分のため打刻システムを構築する。

このようなものは既存のサービスだったりツールの組み合わせで出来たりするのだが、敢えて自分で作る事により楽しみながら利便性も向上させるのが本件の目的。


方式

スマホのジオフェンス機能を利用し職場エリアへの進入と退出イベントを捕捉。イベントの種類と発生時刻を記録する。

ジオフェンスと打刻API呼び出し

iPhone版IFTTTアプリを利用しLocationサービスによる指定領域への進入と退出を if にMakerサービスによるWEBリクエスト送信を then とすることで打刻APIを呼び出す。

打刻API

AWS上に構築する。
API Gatewayを利用しAPIがコールされたらSNSの打刻トピックへパブリッシュする。
API上から直接Lambdaで処理するのではなくSNSを挟むことで打刻イベントに対して複数の処理(エンドポイント)を紐付けられるようにしておく。

打刻情報の保存

打刻情報をGoogleスプレッドシートへ追記するLambdaをエンドポイントとして用意する。


本稿ではGoogleスプレッドシートへ打刻情報を書き込むLambdaを作成、SNS経由で実行できるところまで構築する。

Googleサービスアカウントの準備

AWS側からGoogleスプレッドシートへの書き込みが出来るようにするため以下を行う。

  • プロジェクトの作成
  • DriveとSheets APIの有効化
  • サービスアカウントの発行
  • G Suite側でサービスアカウントからのアクセスを認可

プロジェクトの作成

Google デベロッパー コンソール を開く。
組織ドロップダウンリスト から組織を<自分のG Suiteのドメイン>に変更。
プロジェクトドロップダウンリスト から プロジェクトを作成

プロジェクト名
dakoku

作成 で実行。

APIの有効化

Googleスプレッドシートの読み書きをAPI経由で行うため対象APIを有効化する。

デベロッパーコンソールのハンバーガーメニューより API Manager を選択。

APIを有効にする から Google Drive APIGoogle Sheets API を有効にする。

サービスアカウントの作成

サービスアカウントは個々のエンドユーザではなくアプリケーションが属するアカウント。 G Suiteドメイン全体の委任を有効にすることで任意のエンドユーザに成り代わって操作することができる。

今回はAWS側からGoogleのAPIを呼び出す際にこのサービスアカウントでアクセスする。

デベロッパーコンソールのハンバーガーメニューより IAMと管理 を選択。

サービスアカウント サービスアカウントを作成 を選択してサービスアカウントを追加する。

サービスアカウント名
dakoku-google-sheets-writer
新しい秘密鍵の提供
キーのタイプ
JSON
G Suite ドメイン全体の委任を有効にする
同意画面のサービス名
dakoku

作成 を実行。

サービスアカウントの認証情報がJSON形式でダウンロードされるので保持しておく。

G Suite側で作成したサービスアカウントを承認する

G Suiteの管理コンソールから セキュリティ もっと見る 詳細設定 認証 API クライアント アクセスを管理する

承認済み API クライアントにサービスアカウントを登録する。

クライアント名
作成したサービスアカウントのクライアントID (ダウンロードしたサービスアカウント認証情報JSONのclient_idプロパティ値)を指定する。
1 つ以上の API の範囲
ここにはサービスアカウントに付与するアクセス権限(OAuth 2.0のスコープ)を指定する。
Google DriveとSheetsの読み書きを許可する。 https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/driveをカンマ区切りで設定する。

承認 を実施。

SNS 打刻トピックの作成

打刻情報を送受信するためのトピックを作成する。

AWS マネージメントコンソールから SNS を開き Topics から Create new topic を実行。

Topic name
dakoku

Lambda実行環境の準備

打刻情報をGoogleスプレッドシートへの書き込むLambda関数 dakoku-google-sheets-writer の実行環境を整えるため以下を行う。

  • ロールの作成
  • S3バケットの作成
  • サービスアカウントの認証情報を暗号化して管理する

ロールの作成

Lambda関数 dakoku-google-sheets-writer を実行する為のロールを定義する。

AWS マネージメントコンソールから IAM を開き ロール から 新しいロールの作成 を実施。

ロール名
dakoku-google-sheets-writer

次のステップ へ。

ロールタイプの選択で AWSサービスロール を指定。 AWS Lambda選択 する。 ポリシーのアタッチから AWSLambdaExecute を選択して 次のステップ 確認画面に ロール ARN が表示されるのでメモしておく。

S3バケットの作成

lambdaのデプロイパッケージやサービスアカウントの認証情報JSONを配置するためのバケットを作成する。

AWS マネージメントコンソールから S3 を開き バケットを作成する

バケット名
dakoku.nijohando.jp
リージョン
Tokyo

次へ 、プロパティ設定、アクセス許可設定はスキップし バケットを作成 を実行。

作成後、アクセス権限 タブから バケットポリシー を編集。

lambdaがバケット上のサービスアカウントの認証情報JSONにアクセスできるようにする。

{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::xxxxxxxxxxxx:role/dakoku-google-sheets-writer"
      },
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::dakoku.nijohando.jp/*"
      ]
    }
  ]
}

サービスアカウントの認証情報を暗号化して管理する

Googleサービスアカウントの認証情報JSONをdakoku.nijohando.jpバケットへアップロードする。

アップロードする際はSSE-KMSを有効にしてサーバサイドにて暗号化を施す。 今回は暗号化で使用するマスターキーは特に明示せずAWS管理のマスターキーを利用する。

aws s3 cp --sse aws:kms <サービスアカウントの認証情報JSON> s3://dakoku.nijohando.jp

KMSでファイルを暗号化しておくことで万が一オペミス等でバケットを公開してしまったとしても署名バージョン4を付与されたリクエスト以外は失敗するため内容の流出を防ぐことができる。

Lambdaの実装

打刻情報をGoogleスプレッドシートへの書き込むLambda関数 dakoku-google-sheets-writer を実装。

実装時のポイントは以下。

  • Clojureで書く(Lambda関数ハンドラ部分のみJava)
  • Amazon SNS経由で呼び出す
  • REPL駆動開発を妨げないようにする
  • Google API Clientライブラリは利用しない

Clojureで書く

Lambdaの対応言語のうちJava、JavaScript、C#は奇しくもClojureのホスト言語でもある。 このためClojureでLambda関数を書く際はどのホスト言語をベースにするか選択することができる。
今回Lambda(Java) + Clojureの知見を得るためにJavaを選択した。

また依存ライブラリにはホスト言語がJavaでもJavaScriptでも動くことができるcljcなライブラリが含まれる。 今回は完全に不要であり、そのままだとuberjarした際に不要な依存ライブラリを巻き込んでしまうので project.clj に以下を定義し、 ClojureScript関連の依存を除外した。

:exclusions [[org.clojure/clojurescript]]

Amazon SNS経由で呼び出す

SNS経由で動かすためLambdaへの入力は以下の例ようなJSONとなる。

メッセージ本文はJSONではなく文字列であるため、JSONにパースし直す必要がある。

REPL駆動開発を妨げないようにする

基本的にLambdaへ依存するコードは書かず、通常のClojureの関数として処理を記述する。

またAOTコンパイルの対象となるLambda関数ハンドラはsrc/main/配下には置かず、本番環境ビルド時にのみ対象となるsrc/prod配下に配置する。

これはAOTコンパイルの対象とそれが依存する名前空間はtools.namespace を利用したコードのリロードが効かなくなるため、AOTの起点となる関数ハンドラをREPL駆動開発の対象から外すことで問題を回避する。

Google API Clientライブラリは利用しない

Googleの認証、Drive、Sheets APIの呼び出しにGoogle謹製のAPI Clientライブラリしてみたが冗長で使い辛かった。
Clojureから利用する場合はライブラリでは無くRest APIを直接扱った方が簡潔に書けたのでAPI Clientライブラリは利用しない方針とした。

Lambdaデプロイパッケージの配備

Lambda関数 dakoku-google-sheets-writer のデプロイパッケージをAWS上へ配備する。
今回はCIによる自動デプロイは行わず手動によるデプロイとする。

jarの作成

本番用ビルドプロファイル prod を指定してデプロイパッケージを作成する。

lein with-profile prod uberjar

S3に配置

aws s3 cp target/google-sheets-writer-1.0.0-standalone.jar s3://dakoku.nijohando.jp

lambdaの登録

関数の作成

AWS マネージメントコンソールから Lambda を開き 関数の設定

設計図の選択 から ブランク関数 を選択。

トリガの設定 画面から SNS を選択。

SNSトピック
dakoku
トリガの有効化

次へ

関数の設定 にて

名前
dakoku-google-sheets-writer
ランタイム
Java8
コードエントリタイプ
Amazon S3からファイルアップロード
S3リンクのURL
https://s3-ap-northeast-1.amazonaws.com/dakoku.nijohando.jp/google-sheets-writer-1.0.0-standalone.jar
ハンドラ
jp.nijohando.dakoku.google_sheets_writer.lambda
clojureのハイフンを含む名前空間がjavaのパッケージに変換される際にアンダースコアに変更されている点に注意
ロール
既存のロールを選択
既存のロール
dakoku-google-sheets-writer
メモリ(MB)
512
KMS キー
(デフォルト)aws/lambda

次へ 関数の作成

環境変数の設定

作成した関数が要求する環境変数を定義する。

関数を選択し、コード タブから環境変数を設定する。

暗号化ヘルパーを有効にする
なし
環境変数: PRIVATE_KEY_JSON_URL
サービスアカウントの認証情報JSONの場所を s3://<バケット名>/パス の形式で指定する。
例)s3://dakoku.nijohando.jp/dakoku-google-secret.json
環境変数: DELEGATED_USER_EMAIL
Googleスプレッドシートを操作する際にサービスアカウントが成り代わるG Suiteのユーザのメールアドレスを指定する。
例)example@nijohando.jp
環境変数: GOOGLE_DRIVE_BASE_PATH
打刻情報を保存するGoogleスプレッドシートを配置するフォルダを指定する。
例) /dakoku
環境変数: GOOGLE_SPREADSHEET_FILE_PREFIX
打刻情報を保存するGoogleスプレッドシートのファイル名プレフィックスを指定する。
例)dakoku_event_
環境変数: ZONE
打刻情報の日時のタイムゾーンを指定する。
例)Asia/Tokyo

保存

テスト

AWS マネージメントコンソールから SNS を開き Topics から dakoku を選択して Publish to topic

Subject
test
Message format
Raw

Messageにテスト用の打刻情報JSONを指定。

{
  "subject": "nijohando",
  "event" : {
    "name": "office",
    "modifiers": ["enter"]
  }
}

MessageのJSONフォーマットは以下のように定義。

subject
打刻を実施する人や物
例)自分、扉
event.name
発生したイベント名
例)office(職場)
event.modifiers
イベントの修飾子
例)enter(進入)、leave(退出)

Publish message を実行。

成功すると

  • CloudWatchログにLambdaの実行ログが正常に出力
  • Googleドライブ上のフォルダ/dakoku の下にdakoku_event_<現在の西暦> というファイル名でGoogleスプレッドシートが作成される
  • スプレッドシートには月ごとのシートが作成され、以下の打刻情報が1行追加される
2017-03-09T22:16:07.857 | nijohando | office | enter

打刻システムの構築(後編)ではAPI Gateway、CloudFrontをセットアップ。実際にFTTTアプリから打刻APIを呼び出し出勤退勤時間の記録の煩わしさから本当に解放されるのかを検証する。