Amazon API Gateway エンドポイント(REST API)のカスタムドメイン設定・認可・アクセス制限

この記事は個人ブログと同じ内容です

www.ritolab.com


サーバレスアーキテクチャ構築の第二弾です。

今回は Amazon API Gateway で作成したエンドポイントを使いやすくしたり制限をかけたりしていきます。

開発環境

今回の開発環境は以下になります。

Terraform v1.0.2
なお、今回は前回の続きになるので、操作する Amazon API Gateway については、AWS Lambda / Amazon API Gateway の連携・エンドポイント作成 で作成したものをベースに行っていきます。

API のタイプは REST API です)

カスタムドメインを設定する

デフォルトでは API Gateway で作成されるエンドポイントは以下のようになります。

https://<REST_API_ID>.execute-api.<REGION>.amazonaws.com/<LAMBDA_FUNCTION_NAME>/<RESOURCE_NAME>...

これをカスタムドメインを設定することで以下のように短くする事ができます。

https://<CUSTOM_DOMAIN>/<RESOURCE_NAME>...

以下、カスタムドメインを設定していきます。

ACM 証明書発行

ACM で証明書を発行します。ネイキッドドメインは Route 53 に登録済みの前提です。

main.tf

locals {
  api_gateway_sub_domain_name = "${var.sub_domain_host_name}.${var.domain_name}"
}

# 作成済みホストゾーン情報の取得
data "aws_route53_zone" "main" {
  name = var.domain_name
}

# ACM 証明書作成
resource "aws_acm_certificate" "APIGateway" {
  domain_name       = local.api_gateway_sub_domain_name
  validation_method = "DNS"

  tags = {
    Name = var.domain_name
  }
}

## ACM 検証用 CNAME レコード
resource "aws_route53_record" "api_gateway_acm_c" {
  for_each = {
    for d in aws_acm_certificate.APIGateway.domain_validation_options : d.domain_name => {
      name   = d.resource_record_name
      record = d.resource_record_value
      type   = d.resource_record_type
    }
  }
  zone_id         = data.aws_route53_zone.main.zone_id
  name            = each.value.name
  type            = each.value.type
  records         = [each.value.record]
  ttl             = 60
  allow_overwrite = true
}

## ACM 証明書 / CNAME レコード 連携
resource "aws_acm_certificate_validation" "APIGateway" {
  certificate_arn         = aws_acm_certificate.APIGateway.arn
  validation_record_fqdns = [for record in aws_route53_record.api_gateway_acm_c : record.fqdn]

  depends_on = [
    aws_acm_certificate.APIGateway,
    aws_route53_record.api_gateway_acm_c
  ]
}

API Gateway カスタムドメイン登録

API Gateway でカスタムドメインを適用します。

main.tf

# カスタムドメイン
resource "aws_api_gateway_domain_name" "main" {
  domain_name              = local.api_gateway_sub_domain_name
  regional_certificate_arn = aws_acm_certificate.APIGateway.arn
  security_policy          = "TLS_1_2"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
}

## API マッピング
### ドメイン名から API ステージへのパスをマッピング
resource "aws_api_gateway_base_path_mapping" "main" {
  api_id      = aws_api_gateway_rest_api.to_lambda_node.id
  stage_name  = aws_api_gateway_stage.hello_world.stage_name
  domain_name = aws_api_gateway_domain_name.main.domain_name
}

# route53 A レコード作成
resource "aws_route53_record" "APIGatewayCustomDomain" {
  name    = aws_api_gateway_domain_name.main.domain_name
  type    = "A"
  zone_id = data.aws_route53_zone.main.id

  alias {
    evaluate_target_health = true
    name                   = aws_api_gateway_domain_name.main.regional_domain_name
    zone_id                = aws_api_gateway_domain_name.main.regional_zone_id
  }
}

カスタムドメイン名を登録したら APIマッピングを行い、カスタムドメイン名を A レコードで登録(ルーティング先に API Gateway ドメイン名を指定)します。

これでエンドポイントがカスタムドメインを用いた URI になります。

https://<CUSTOM_DOMAIN_NAME>/hello_world こんな感じです。

IAM 認証を導入してエンドポイントに認可制限を設ける

前回までの時点ではこのエンドポイントはどこから・誰からでも叩ける状態なので、必要に応じて何らかの制限を掛けてあげる必要があります。

サーバレスという事で今回のアプリケーションを静的サイトと想定した場合、S3 にホスティングして CloudFront で配信の構成が多いと考えます。

その場合 VPC や IP で制限を掛けるといった事が難しいので、IAM 認証を導入して API Gateway で作成したエンドポイントに認可制限を掛けたいと思います。

IAM ユーザー作成

まずはエンドポイントにリクエストする際の IAM ユーザーを作成します。

main.tf

# IAM Role for ApiGateway
## AWS管理ポリシー
data "aws_iam_policy" "AmazonAPIGatewayInvokeFullAccess" {
  arn = "arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess"
}

# IAM User for ApiGateway execution
resource "aws_iam_user" "api_gateway_requester" {
  name = "api_gateway_requester"
}

resource "aws_iam_user_policy_attachment" "api_gateway_requester" {
  user       = aws_iam_user.api_gateway_requester.name
  policy_arn = data.aws_iam_policy.AmazonAPIGatewayInvokeFullAccess.arn
}

今回はテストなのでポリシーは AWS 管理ポリシーである AmazonAPIGatewayInvokeFullAccess を使用しています。

サービスやエンドポイントによって細かくリクエストを制限したい場合は管理ポリシーではなく個別にリソースを指定します。

メソッドの authorization を IAM 認証へ変更

次に、作成したメソッド(GET)の authorization を IAM 認証へ変更します

main.tf

### メソッド設定
resource "aws_api_gateway_method" "hello_world" {
  rest_api_id   = aws_api_gateway_rest_api.to_lambda_node.id
  resource_id   = aws_api_gateway_resource.hello_world.id
  http_method   = "GET"
  authorization = "AWS_IAM" // <-  ここを NONE から AWS_IAM へ変更
}

これまで NONE としていた部分を AWS_IAM へ変更します。

動作確認

AWS 側の設定はこれだけなので、動作確認を行なってみます。

まずはローカルのターミナルから curl コマンドを叩いてみた結果です。

% curl -IX GET  https://<CUSTOM_DOMAIN_NAME>/hello_world
HTTP/2 403
content-type: application/json
content-length: 42
x-amzn-requestid: xxxx-xxx-xxx-xxxx
x-amzn-errortype: MissingAuthenticationTokenException
x-amz-apigw-id: xxxxxxxxxxxxx

認可エラーを示す HTTP ステータスコード 403 が返ってきたので、しっかり制限がかかっている事が確認できます。

一方で、IAM 認証を行なった上でリクエストを行えば、認可で弾かれずにエンドポイントにアクセスする事ができます。

SigV4 - API リクエストに認証情報を追加する

AWS API Gateway で作成したエンドポイントのリクエストで IAM 認証を通すためには、Signature Version 4 (SigV4) を用いて AWS API リクエストに認証情報を追加する必要があります。

docs.aws.amazon.com

docs.aws.amazon.com

ここはアプリケーション側の実装になりますが、JavaScript 及び PHPAWS SDK を用いて実装してみたので参考までに記します。

AWS SDK for JavaScript v2(SigV4 部分を抜粋)

import aws from 'aws-sdk'
import core from  'aws-sdk/lib/core'

-----------------------------------------------

const awsAccessKey = '<AWS_ACCESS_KEY>';
const awsSecretKey = '<AWS_SECRET_KEY>'
const awsRegion  = '<AWS_REGION>';
const awsComponentServiceName    = "execute-api";
const awsAPiGatewayHelloWorldUrl = 'https://<CUSTOM_DOMAIN>/hellow_world';

const splits = awsAPiGatewayHelloWorldUrl.split('?');
const host   = splits[0].substr(8, splits[0].indexOf("/", 8) - 8);
const path   = splits[0].substr(splits[0].indexOf("/", 8));
const query  = splits[1];

const options = {
  url: awsAPiGatewayHelloWorldUrl,
  region: awsRegion,
  method: 'GET',
  headers: {
    host: host,
  },
  pathname: () => path,
  search: () =>  query ? query : '',
};

const signer = new core.Signers.V4(options, awsComponentServiceName);

signer.addAuthorization(new aws.Credentials(awsAccessKey, awsSecretKey), new Date());

const response = await axios.get(awsAPiGatewayHelloWorldUrl, {
  'headers': {
    'authorization': options.headers['Authorization'],
    'x-amz-date': options.headers['X-Amz-Date']
  }
});
// => Hello from Lambda!

こちらについては以下の記事を参考にさせていただきました

dev.classmethod.jp

API Gateway で生成された JavaScript SDK を使用

var apigClient = apigClientFactory.newClient({
    accessKey: awsAccessKey,
    secretKey: awsSecretKey,
    region: awsRegion
});

apigClient.helloWorldGet()
            .then(function(result) {
                console.log(result.data);
                // => Hello from Lambda!
            })
            .catch(function(result) {
                console.log(result);
            });

docs.aws.amazon.com

AWS SDK for PHP v3

/** @var \GuzzleHttp\Client $client */
$client = new Client();

/** @var \GuzzleHttp\Psr7\Request $request */
$request = new Request('GET', 'https://<CUSTOM_DOMAIN>/hellow_world');

/** @var \Aws\Credentials\Credentials $credentials */
$credentials = new Credentials('<AWS_ACCESS_KEY>', '<AWS_SECRET_KEY>');

/** @var \Aws\Signature\SignatureV4 $signer */
$signer = new SignatureV4('execute-api', 'ap-northeast-1');

// クレデンシャルを使用し必要なヘッダーをリクエストに追加することで指定されたリクエストに SigV4 で署名します。
$requestWithSign = $signer->signRequest($request, $credentials);

// エンドポイントへ HTTP リクエストを送信
$response = $client->send($requestWithSign);

$response->getBody()->getContents()
// -> "Hello from Lambda!"

指定している AWSACCESS_KEY と SECRET_KEY は、API Gateway へのリクエスタとして(このセクションの冒頭で)作成した IAM ユーザーのものになります。

IP Address でアクセス制限を掛ける

API Gateway のエンドポイントに対して IP 制限をかけたい場合は、リソースポリシーを用いることで実現できます。

docs.aws.amazon.com

main.tf

resource "aws_api_gateway_rest_api_policy" "to_lambda_node" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id
  policy = jsonencode({
    Version : "2012-10-17",
    Statement : [
      {
        Effect : "Allow",
        Principal : "*",
        Action : "execute-api:Invoke",
        Resource : "arn:aws:execute-api:${var.aws_region}:${var.aws_id}:${aws_api_gateway_rest_api.to_lambda_node.id}/*",
        Condition : {
          // IP アドレス制限
          "IpAddress": {
            "aws:SourceIp": "xx.xxx.xx.xxx/32"
          }
        }
      }
    ]
  })
}

CORS の有効化を行う

作成したリソースの CORS を有効化するのなら、terraform モジュール api-gateway-enable-cors が簡単でいい感じに使えます。

OPTIONS メソッドを追加してクロスオリジンリソースシェアリング(CORS)プリフライトリクエストを許可する Terraform モジュールです。

main.tf

## CORS 設定
module "cors" {
  source  = "squidfunk/api-gateway-enable-cors/aws"
  version = "0.3.3"

  api_id          = aws_api_gateway_rest_api.to_lambda_node.id
  api_resource_id = aws_api_gateway_resource.hello_world.id

  allow_headers = ["Content-Type", "authorization", "x-amz-date"]
  allow_methods = ["OPTIONS", "GET"]
  allow_origin  = "*"
}

必要な項目を設定すると、OPTIONS メソッドが作成され CORSプリフライトリクエストが可能になります。

今回はここまでになります。Amazon API Gateway で作成したエンドポイントの方はあらかた使いやすくなったので、次回は DynamoDB を絡めてデータ操作周りをやっていこうと思います。

image-magick-lambda-layer を使ってオリジナル LGTM 画像を作ろう

この記事は個人ブログと同じ内容です

image-magick-lambda-layer を使ってオリジナル LGTM 画像を作ろう


LGTM画像といえば、LGTMoon、いつもお世話になっています。 でも、たまにはオリジナリティ出したいですよね。

そこで、今回は5秒でオリジナルLGTM画像を生成するサービスを作成します。

準備するもの

lambda layer とは

Lambdaレイヤーは、追加のコードまたはデータを含むことができる .zip ファイルアーカイブです。レイヤーには、ライブラリ、 カスタムランタイム 、データ、または設定ファイルを含めることができます。レイヤーを使用すると、コードの共有と責任の分離を促進し、ビジネスロジックの記述をより迅速に繰り返すことができます。

素の lambda にライブラリを追加する方法。layer に分けることで処理速度を上げることができる。今回はAWSで用意されているレイヤーを使用するが、もちろん独自のレイヤーを作成することもできる。関数に追加できるLayerは5つまでで、合計サイズが250MB以下となる必要がある。

lambdaファンクションを作成する

lambda > 関数 に行き、「関数の作成」をします。 関数名は適当に lambda-image-magick(任意)、ランタイムは Node.js 14.x とします。

API Gateway、POST メソッドを作成する

これから作成する API Gateway の POST エンドポイントに画像のURLをリクエストし、 lambda で受け取れるようにしていきます。

{
  url: '画像のアドレス'
}

API Gateway から REST API を作成、アクション > メソッドの作成 から POST を作成。先程作成した lambda 関数名を指定します。

CORS を有効化する

「CORSを有効にして既存のCORSヘッダーを置換」をクリックし、「はい、既存の値を置き換えます」をクリックするとOPTIONSメソッドが作成されます。

image-magick-lambda-layer をデプロイする

image-magick-lambda-layer にアクセスしてデプロイをクリックします。

デプロイしただけでは使用できないので有効化します。

レイヤー > レイヤーを追加。 AWSレイヤーに出てこなかったため、ARN を指定してレイヤーを追加しました。

コードを用意

ImageMagick, GraphicsMagick を node.js で扱えるようにする gm を入れます。axios は画像ダウンロードで使用。

$ yarn init -y
$ yarn add axios gm 

ローカルで動作を確認する場合はインストールをお忘れなく。

$ brew install imagemagick
$ brew install graphicsmagick

重ねる lgtm.png を用意します。

LGTM画像は以下のように、適当に数パターンサイズを用意。 (当初は送られてきた画像のサイズに合わせてリサイズを考えましたが、lambda上に一時的に画像を保存しておくことができないため断念。)

├── assets
│   ├── lgtm.png
│   ├── lgtm100.png
│   ├── lgtm150.png
│   ├── lgtm200.png
│   ├── lgtm250.png
│   ├── lgtm300.png
│   ├── lgtm500.png
│   └── lgtm700.png
├── index.js
├── node_modules
├── package.json
└── utils.js

lambda 関数を作成

  1. 画像パスを受け取り、画像をダウンロード
  2. 画像とあらかじめ用意している lgtm.png と合成
  3. 合成したバッファをリサイズ(出力サイズは width 300px とした)
  4. response 返却
// index.js
const { downloadImage, composite, resize } = require('./utils');

exports.handler = async (event) => {
  try {
    const buf = await downloadImage(event.url); // 1
    const composited = await composite(buf); // 2
    const resized = await resize(composited, 300); // 3
    const base64 = 'data:image/png;base64,' + Buffer.from(resized).toString('base64');
    const response = {
      statusCode: 200,
      headers: {
          'Content-Type': 'image/png',
          'Access-Control-Allow-Origin': '*',
      },
      body: base64,
      isBase64Encoded: false,
    };
    return response; // 4
  } catch(e) {
    console.error(e);
  }
};

処理の詳細はこちら

// utils.js
const GM = require('gm');
const gm = GM.subClass({ imageMagick: true });
const axios = require('axios');

exports.downloadImage = async (url) => {
  const res = await axios.get(url, { responseType: 'arraybuffer' });
  return Buffer.from(res.data);
}

const getBufferSize = (buf) => {
  return new Promise((resolve, reject) => {
    gm(buf)
      .size((err, { width, height }) =>
        err ? reject(err) : resolve({ width, height }))
  })
}

const getLgtmPng = (width) => {
  if (width >= 1000) {
    return './assets/lgtm700.png'
  } else if (width < 1000 && width >= 800) {
    return './assets/lgtm500.png'
  } else if (width < 800 && width >= 500) {
    return './assets/lgtm300.png'
  } else if (width < 500 && width >= 300) {
    return './assets/lgtm150.png'
  } else if (width < 300) {
    return './assets/lgtm100.png'
  }
}

const getPosition = (lgtmPath, width, height) => {
  return new Promise((resolve, reject) => {
    gm(lgtmPath).size((err, size) => {
      const centerWidth = width / 2;
      const centerHeight = height / 2;
      const left = Math.floor(centerWidth - (size.width / 2));
      const top = Math.floor(centerHeight - (size.height / 2));
      const geometry = '+' + left + '+' + top;
      return err ? reject(err) : resolve(geometry)
    });
  });
}

const getCompositedBuffer = (buf, lgtmPath, geometry) => {
  return new Promise((resolve, reject) => {
    return gm(buf)
      .composite(lgtmPath)
      .geometry(geometry)
      .quality(100)
      .noProfile()
      .toBuffer((err, buffer) =>
         err ? reject(err) : resolve(buffer));
  })
}

exports.composite = async (buf) => {
  try {
    const { width, height } = await getBufferSize(buf) 
    const lgtmPath = getLgtmPng(width)
    const geometry = await getPosition(lgtmPath, width, height);
    return await getCompositedBuffer(buf, lgtmPath, geometry);
  } catch(e) {
    throw e;
  }
}

exports.resize = async (buf, width, height) => {
  return new Promise((resolve, reject) => {
    gm(buf)
      .resize(width, height)
      .noProfile()
      .toBuffer('PNG', (err, buffer) =>
        err ? reject(err) : resolve(buffer));
  });
};

デプロイ

コードができたら zip にします。

$ zip -r deploy.zip ./

lambda の画面から .zip ファイルをアップロード。

完成 🎉

ブラウザの任意の jpegpng の画像のURLをエンドポイントにリクエストする bookmarklet を作成する。

Next.js にレイアウトに関するドキュメントが追加されました

この記事は個人ブログと同じ内容です

zenn.dev

 

 

Next.js の Layout 周りのドキュメントが新しく追加されました。

単一のレイアウトを扱う場合

単一のレイアウトで十分な場合はカスタマイズした <Layout/><Component /> タグを囲むだけで実装できます。

// pages/_app.js

import Layout from '../components/layout'

export default function MyApp({ Component, pageProps }) {
  return (
    <>
      // Layout でアプリケーションのコンポーネントを囲むだけ
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </>
  )
}

ページごとに異なるレイアウトを扱う場合

ページごとに異なるレイアウトが必要な場合は getLayout でページごとにレイアウトを定義します。 getLayout を使用してレイアウトの切り替えを行うことで、コンポーネントツリーがページ遷移間で維持されるため永続的にレイアウト内の state を保持することができます。 コンポーネントツリーが維持されると React は state を維持しつつ、必要な要素のみ再レンダリングします。

// pages/index.js

import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

export default function Page() {
  return {
    /** Your content */
  }
}
// 個別のページごとにレイアウトを定義する
Page.getLayout = (page) => (
  <Layout>
    <NestedLayout>{page}</NestedLayout>
  </Layout>
) 
// pages/_app.js

export default function MyApp({ Component, pageProps }) {
  // ページごとに定義されたレイアウトがある場合はそれを使用する
  const getLayout = Component.getLayout || ((page) => page)

  return getLayout(<Component {...pageProps} />)
}

データフェッチ

Layout 内でのデータフェッチはクライアント側でのみ可能です。 Layout はページではないため、今のところ getStaticPropsgetServerSideProps は使えません。

プレビュー

Next.js 公式リポジトリのサンプルがわかりやすいのでぜひ触ってみてください。

Open in StackBlitz

ROXXに転職して2ヶ月が経ったエンジニアから見た開発チームのあれこれ

2021年6月に開発チームにjoinしたpoisonと申します。これからよろしくお願いいたします。

今回、開発チームのタスク内で自分の価値を発信しよう!という動きがあったので、ブログを書くことに決めました。

はじめに

私はSESで各現場にアサインされJavaScriptFWでよく使われているAngular・Reactを使用したWebアプリケーション製造に携わっておりました。

そしてこの度、Vueを扱っている弊社にjoinすることができ3大制覇したことをきっかけに、その扱い方の違いについて書こうと思いました。

ただ、2ヶ月経った今、開発チームの雰囲気や1週間の流れを入ったばかりのエンジニア視点で書けるのは今しかないと思い、今回はこちらを題材にしました。

(3大制覇に関してのブログは後日書けたら書きます…) 

私のスペック

2017年新卒、ROXXで3社目(1~2社目はSESで現場常駐)

主に扱ってきた技術は、JavaJavaScript(Angular,React)・Redux等

もっと自分の価値市場を上げたい、事業会社に入ってプロダクトを育てる立場になりたいという思いから2020年12月に転職活動を開始し、翌年3月にROXXから内定をもらい今に至る

開発チームの実情

ROXXには「agent bank」「back check」の2つのサービスがあり、私が所属しているのはagent bankのDevチームです。

今は、フルリモートで働いており、業務中は常にDiscordというアプリを使用して、常時通話状態で作業している状況です。

常時通話状態なので、リモートなのに出社している緊張感を感じることができ、家にいるけど職場の雰囲気を感じながら仕事しております。

基本はスクラムチームとして動き、1週間スプリントでタスクをこなし毎週金曜日にスプリントレビュー・振り返り・リファイメント・プランニングを実施しているという流れになってます。

個人的には、この流れがすごく好きで、1週間できちんと終えて、また次の週から始められることでスッキリ週を終えることができます。

チームの人数は執筆時点で12人ほどいて、プロジェクトが複数動いていることから半分ずつチームが分かれ、日々のタスク消化をしている人数は5~6名で回しています。そこから、基本ペアプロを行っているため、2~3レーンで進めてます。

毎日、同じメンバーとペアプロするのではなく、日々シャッフルしながら実施しているためメンバー内で話したことがないというのはないです!日々、和気藹々とやっている反面、真面目に議論するときもあるので個人的にはいい雰囲気で過ごしています。

いろんな現場を見てきた身からすると、技術が強いメンバーもいるため、わからないこととかは気軽に聞くことができる環境となっており、個人的にはとても助かっております。 

おすすめの活動

私がチーム活動の中でおすすめする部分として2点あります。

1つ目は、毎日の振り返りです。

私たちのチームでは、月曜〜木曜までは、朝会・昼会・夕会を実施しており、メンバーの進捗を随時確認してます。

夕会では、「やったこと」「おきたこと」「わかったこと」の頭文字をとりYOWというFWを使用して取り組んでおります。

その日のうちに、実際やったこと・それをしたことで起きたこと・その中で分かったことを振り返りし、週末の振り返りの際にはそれを見返すことで忘れず、1週間の流れを確認することができます。

その後、何がよかったのか・問題点はなんだったのか・今後どうアクションしていくかを決めスプリントを閉じるという流れで実施しております(俗にいうKPTですね)。

デイリーの中でメンバーそれぞれが出し合ったものを確認すると、分かってなかったことその日に起きたことなどを知る機会が増えるので、いろいろと話し合ったりしてお互いに知識を深め合う場としても活用しております。

2つ目は、リファイメントについてです。

スクラムチームの活動は、転職前にも経験していたのですが、その時は、タスクの見積もりをするだけの活動となっていました。

私たちのチームでは、タスクの見積もりはプランニングで行い、リファイメントではPOが何個かの改善点をBizの方々から抽出し、チームに落とし込んで、何をどうすればその状況が解決するかを全員で考える場として使ってます。

これをすることで、受動的ではなく能動的にタスクと向き合えることができる気がしております。

それだけでは終わらず、リファイメントした後に設計タスクと題して、DB構造であったりどんな機能が必要かであったりを複数人で考えるタスクがあり、コーディングだけでなく設計能力も高めることができる場が設けられております。

コーディングだけを主に行ってきた身としてはとても新鮮で、毎度関わることができているので、どんどん価値を出していきたいと思っております!

私が思うチームの課題点

新参者がいきなりチームの課題を出すというものも、大変烏滸がましいと思いつつまだまだここは足りないかもしれないと思う点があります。

それは、属人化についてです。

主に、インフラに関してですが、作業できるレベルの方が少数しかおらず、その方が休んだ暁には、危機的状況になっているのが現状です。

この件に関しては、チームだけでなく開発事業部全体で考えており、属人化を回避するために、2つのサービスの開発チーム合同で勉強会を行ったりしております。そういった行動を積み重ねて、各人が作業レベルで触れるようになるために、まずは基礎知識をつけてその上でわかる方とペアプロする形で学んでいくような体制を作ることができればベストかと思っております。

私もまだまだインフラに関してはど素人なので、とりあえず環境を作ってみようということで、今後触っていこうと思っております。

今後の目標

今後の目標としては、今までは主にフロントエンドしか触ってなく、サーバーエンドやインフラに関してもどんどん積極的にタスクをこなして、自分のものにしたいと考えております。

それを実施することができる、チームにはとても感謝しております。今後も自分の価値を発揮できるように精進していきたいと思います。

終わりに

この記事を読んで、もしROXXや開発チームに興味が出てきた方がいましたら、ぜひご応募ください!お待ちしております!

 

careers.roxx.co.jp

AWS Lambda / Amazon API Gateway の連携・エンドポイント作成(REST API)

この記事は個人ブログと同じ内容です

www.ritolab.com


サーバレスアーキテクチャ構築の第一弾です。

今回は AWS の Lambda と API Gateway を連携させて、エンドポイントを作成していきます。

サーバーレス・コンピューティング

自身でサーバーを運用せずに IaaS 等のサービスを利用して処理のロジックだけをデプロイ。必要な時にだけホストした処理を動作させる一連の仕組み。

例えば、いくつかの処理を行う API サーバを持っているとして、そのサーバは 24 時間 365 日稼働させる必要がある。

サーバレスにすれば処理のロジックをリモートにホストしておき、必要な時にだけ動作させる事ができて、自身でサーバーを運用する手間が省ける。(サーバーに関するセキュリティや可用性をその分気にしなくて良くなる)

さらには動作した時間だけの課金になるので、24 時間 365 日サーバーを稼働させるよりも(大抵は)費用が抑えられる。

サーバーレス・コンピューティングを提供している主な IaaS

余談ですが AWS では、Well-Architected Framework という指針(AWS を用いた設計のベストプラクティスなど)を出していて、その中の「パフォーマンス効率」の章でサーバーレスアーキテクチャを推奨していたりもします。

docs.aws.amazon.com

AWS Well-Architected Framework

wa.aws.amazon.com

AWS Lambda

AWS Lambda はサーバーレスコンピューティングサービスで、サーバーのプロビジョニングや管理、ワークロード対応のクラスタースケーリングロジックの作成、イベント統合の維持、ランタイムの管理を行わずにコードを実行できます。

引用元:AWS Lambda(イベント発生時にコードを実行)| AWS

AWS でサーバレスやるぞってなったらこいつですね。

aws.amazon.com

Amazon API Gateway

フルマネージド型サービスの Amazon API Gateway を利用すれば、デベロッパーは規模にかかわらず簡単に API の作成、公開、保守、モニタリング、保護を行えます。API は、アプリケーションがバックエンドサービスからのデータ、ビジネスロジック、機能にアクセスするための「フロントドア」として機能します。

引用元:Amazon API Gateway(規模に応じた API の作成、維持、保護)| AWS

こちらはサーバレス云々というより、API を作成できるサービスです。今回は API Gateway でステートレス API を作成して、アプリケーションからそのエンドポイントを叩こうと思います。

API Gateway で作成したエンドポイントにリクエストしたら、Lambda の処理が動く。という流れです。

aws.amazon.com

開発環境

今回の開発環境は以下になります。

  • Terraform v1.0.2

ちなみに今回、AWS Lambda は Node でやります。

ホストするコードは JavaScript になるわけですが、今回はこれも terraform で管理します。

実運用だと別々での管理が望ましいと考えますが、それよりもとにかく動かしてみたい欲が先行したので今回はその辺は割愛します。

それと、tf ファイル作成していく中で functions へのパスを書いているのでディレクトリ構成を事前共有しておきます。

.
├── functions
├── src
└── terraform
  • functions(Lambda にホストするコード)
  • src(アプリケーション)

Lambda にホストするコード

まずは、AWS Lambda にホストするコードを作成しておきます。

functions/tf-test-node-hello-world/index.js

exports.handler = async (event) => {
    return {
        isBase64Encoded: false,
        statusCode: 200,
        headers: {},
        body: JSON.stringify('Hello from Lambda!'),
    };
};

「Hello from Lambda!」を返すだけの関数です。

また、API Gateway が Lambda からレスポンスを受け取る場合は形式が決まっているため、そちらに倣った形式にしています。

docs.aws.amazon.com

Lambda の構築

Lambda を構築していきます。

CloudWatch

Lambda の実行ログを CloudWatch に流すのでロググループとロールを作成します。

AWS Lambda の Amazon CloudWatch ログへのアクセス

docs.aws.amazon.com

main.tf

variable "function_name" {
  type    = string
  default = "tf-test-node-hello-world"
}

# CloudWatch Logs for lambda
resource "aws_cloudwatch_log_group" "node_lambda_hello_world" {
  name = "/aws/lambda/${var.function_name}"
}

# Lambda Role for logging CloudWatchLogs
resource "aws_iam_role" "lambda_node_logging" {
  name = "${var.app_name}-lambda-role"

  assume_role_policy = jsonencode({
    Version : "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          Service = "lambda.amazonaws.com"
        },
        Action = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_policy" "lambda_node_logging" {
  name        = "${var.app_name}-lambda-policy"
  description = "IAM policy for logging from a lambda"

  policy = jsonencode({
    Version : "2012-10-17",
    Statement = [
      {
        Action   = "logs:CreateLogGroup",
        Effect   = "Allow",
        Resource = "arn:aws:logs:${var.aws_region}:${var.aws_id}:*"
      },
      {
        Action = [
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ],
        Effect = "Allow",
        Resource = [
          "${aws_cloudwatch_log_group.node_lambda_hello_world.arn}:*"
        ]
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_node_logging" {
  policy_arn = aws_iam_policy.lambda_node_logging.arn
  role       = aws_iam_role.lambda_node_logging.name
}

Lambda へのデプロイ

今回は terraform で Lambda にデプロイするコードも管理するので、ローカルで ZIP を作成するようにしておきます。

main.tf

data "archive_file" "lambda_function" {
  type        = "zip"
  source_dir  = "../functions/tf-test-node-hello-world"
  output_path = "../functions/upload/tf-test-node-hello-world.zip"
}

これで terraform plan ないし terraform apply 時に ZIP が作成されます。

Lambda function の作成

最後に、メインである関数を定義します。

main.tf

resource "aws_lambda_function" "hello_world" {
  filename      = data.archive_file.lambda_function.output_path
  function_name = var.function_name
  role          = aws_iam_role.lambda_node_logging.arn

  handler = "index.handler"

  source_code_hash = filebase64sha256(data.archive_file.lambda_function.output_path)

  runtime = "nodejs14.x"

  depends_on = [
    aws_iam_role_policy_attachment.lambda_node_logging,
    aws_cloudwatch_log_group.node_lambda_hello_world
  ]
}

docs.aws.amazon.com

docs.aws.amazon.com

Lambda 構築実行

ここまで書いて terraform apply を実行すると、Lambda に関数が出来上がります。

f:id:ro9rito:20210720113408p:plain

API Gateway の構築

次に、作成した Lambda 関数を実行するためのインターフェースを Amazon API Gateway を使って作成していきます。

API 作成

API を作成します。インターフェースの大本を作成するイメージ。

API タイプは用途によって最適なものを選択する必要があります。今回は REST API で作成します。

docs.aws.amazon.com

main.tf

# API Gateway
resource "aws_api_gateway_rest_api" "to_lambda_node" {
  name        = "${var.app_name}-to-lambda-node-api"
  description = "REST API for lambda node test"
}

リソース作成

/hello_world のリソースを作成します。

main.tf

## for Function: hello world
### リソース作成
resource "aws_api_gateway_resource" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id
  parent_id   = aws_api_gateway_rest_api.to_lambda_node.root_resource_id
  path_part   = "hello_world"
}

メソッド作成

メソッドはリソースに対して GET や POST などの HTTP リクエストメソッドを作成します。ここでは GET で作成します。

main.tf

### メソッド設定
resource "aws_api_gateway_method" "hello_world" {
  rest_api_id   = aws_api_gateway_rest_api.to_lambda_node.id
  resource_id   = aws_api_gateway_resource.hello_world.id
  http_method   = "GET"
  authorization = "NONE"
}

lambda 統合を設定

今回は API Gateway で作成したエンドポイントがリクエストを受け取ったら Lambda function を実行するので、API Gateway と Lambda を紐付けます。

API Gateway では Lambda との連携がスムーズにできるようにこういった設定も用意されているので紐付けが簡単に行えます。

main.tf

### lambda 統合
resource "aws_api_gateway_integration" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id
  resource_id = aws_api_gateway_resource.hello_world.id
  http_method = aws_api_gateway_method.hello_world.http_method

  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.hello_world.invoke_arn
}

ポイントとしては、integration_http_method を POST に指定している点です。

lambda への統合 method は POST で固定となっています。

aws.amazon.com

デプロイメント作成

デプロイメントを作成します。デプロイメントは、REST API 構成のスナップショットのイメージ。作成したデプロイメントを次の、ステージと紐付ける事で API が公開されます。

main.tf

### デプロイメント
resource "aws_api_gateway_deployment" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id

  depends_on = [
    aws_api_gateway_integration.hello_world
  ]
}

再度デプロイメントを行う場合の注意点

新しいリソースやメソッド作成、もしくは既存のリソースやメソッドの変更を terraform から反映する場合は、再度デプロイメントを作成する必要があります。

単純にリソースやメソッドをコード上で追加して反映しても、リソース上で追加されるだけでそれはデプロイされないからです。

そしてデプロイメントは、リソースやメソッドを追加・変更しても apply 時に変更対象とはなりません。(既に作成済みのため)

そのため AWS コンソールから手動でデプロイしてあげる必要があります。

リソースやメソッドの新規作成や変更を検知する、もしくは毎回の apply 時にデプロイメントの作成を強制する仕組みも作れますが、terraform 上での反映でデプロイメントを作成する場合は現在のものを削除した上で作り直す必要があるため、カスタムドメイン名を設定していない場合はエンドポイントのサブドメイン名が変更になってしまうので注意が必要です。

resource "aws_api_gateway_deployment" "hello_world" {
  .
  .
  .
  lifecycle {
    # 変更に対応するため一度削除して作り直す
    # REST API ID の変更によりエンドポイントの URI が変わるので注意
    create_before_destroy = true
  }
}

また、上記の設定を行わなずにデプロイメントの変更を apply した場合は以下のエラーが出力されます。

Error: error deleting API Gateway Deployment (xxxxx): BadRequestException: Active stages pointing to this deployment must be moved or deleted

どうしても仕組みが必要な場合以外はここは記述しないか false にして手動で API デプロイを行うのが良いと思います。(今回は特段必要としていないので lifecycle については記述しない(=false)で進めます。)

ステージ作成

ステージを作成します。ここでデプロイメントを参照して作成する事で API が公開されます。

main.tf

### ステージ
resource "aws_api_gateway_stage" "hello_world" {
  deployment_id = aws_api_gateway_deployment.hello_world.id
  rest_api_id   = aws_api_gateway_rest_api.to_lambda_node.id
  stage_name    = var.app_name
}

API Gateway 側の設定はこれで終わりです。

Lambda へのアクセスを制限する

最後に Lambda 関数(hello_world)へのアクセス許可を API Gateway に付与します。

main.tf

resource "aws_lambda_permission" "execution_hello_world_for_api_gateway" {
  statement_id  = "test-AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.hello_world.function_name
  principal     = "apigateway.amazonaws.com"

  source_arn = "${aws_api_gateway_rest_api.to_lambda_node.execution_arn}/*/*"
}

source_arn を本 API のみにしたので、今回作成したエンドポイントからでしかこの Lambda 関数は実行できないようになっています。

API Gateway 構築実行

ここまで書いて terraform apply を実行すると、API Gateway が作成され、Lambda 関数にも関連付いた事が確認できます。

f:id:ro9rito:20210720120201p:plain

動作確認

一通りミニマムで作成したので、アプリケーションからエンドポイントを叩いてみます。

f:id:ro9rito:20210720120235p:plain

正常にレスポンスが返ってきました。これで API Gateway 経由で Lambda 関数を実行できました。

今回はここまでになります。作成したエンドポイントはデフォルトの URL であったり制限がかかっていなかったりするので、次回はエンドポイントの設定や制御を行っていこうと思います。

【読書メモ】「成長する企業はなぜ SSO を導入するのか」

この記事は個人ブログと同じ内容です

【読書メモ】「成長する企業はなぜ SSO を導入するのか」

---

最近チームのメンバーが誕生日で、よくバラエティ番組で流れる曲「Happy Birthday - Stevie Wonder」が頭から離れない sekitats です。

今月は個人的に認証について学ぶ月間で、本を3冊読みました。認証周りわからんことばかりだったので。

OAuth については、別記事を書いてみたのでそちらも興味があれば読んでみてください。

ratatatat30.hatenablog.jp

2冊目の Auth0 + Nuxt + Rails のデモも試した。

本記事は3冊目「成長する企業はなぜ SSO を導入するのか」の要約となります。
引用元:日本ヒューレット・パッカード株式会社 (著) . 『成長する企業はなぜSSOを導入するのか 』. 日経BP, 2017

なぜ、この本を読んだのかというと、SSO に関する本を amazon で検索したら、本書しかなかったからです。(あとはミリタリーバックパック

認証はすでに経営課題

以下のような企業における認証の課題があげられている。

  • 社員のパスワードの使い回しによってパスワード盗難リスクが高まる。企業側の危機意識が低い
  • 厳しすぎるパスワードポリシーが返って生産性を低下させる
  • 個々の業務システムの担当者は、自分が担当している業務システムだけを考えて対策を立ててしまう傾向にある → サイロ化
  • サイロ化した中で認証強化してもそれは「部分最適化」でしかない。「全体最適化」とは程遠い。

    ※ サイロ化:企業内にある部門が他部門との連携をすることなく、自らの業務の部分最適化のみを優先するようになった状態をいう。元々サイロとは穀物などを貯蔵するタンクのことだが、英語圏では「窓がなく周囲が見えない」という意味も含んでいる。

出ました。サイロ化。大規模になればなるほどサイロ化の弊害は大きくなっていく。

企業経営にまで影響を与える問題となっているということ。

パスワード認証の課題

ユーザーの本人確認を行う認証にも問題は多い。

  • 他人に知られた場合になりすましが可能
  • 安易なパスワードの場合類推や解読が容易
  • 忘れたり間違えたりしやすい
  • 複雑なパスワードは記憶が困難
  • パスワードの使い回しにより、安全性が低下

パスワード認証は、「知られない」、「推測されない」、「同じものを使い続けない」、「同じものを使わない」といった「努力」をユーザーに強いていて、その「努力」には限界がある。

パスワード認証を強化する他要素認証(ワンタイムパスワード、生体認証)も導入が難しい。

そんな、企業の課題、ユーザーの課題を SSO(シングルサインオン)が解決してくれると言うてます。

SSO 導入メリット

  • SSO とは、業務システムで個別に行っている認証を一元化し、ユーザーがいずれかのシステムを利用する際に認証を一度行えば、連携している全てのシステムが利用できるようになる。ユーザー ID,パスワード、ユーザー属性などのユーザー情報の管理や、問い合わせ対応の負荷が軽減される。
  • SSO が導入されるということは、入口が1カ所で管理されるということ。唯一の入口に防犯対策をを一本化できる。情報漏洩の防止策として SSO は役に立つ。
  • 新規に導入する業務システムだけでなく、既存の業務システムにも SSO は効果を発揮する。既存の業務システムで管理されているユーザー情報に大きな変更を加えないため、導入も用意で短期間かつ少ない労力で導入できる
  • 導入によってユーザーの利便性が向上するだけでなく、認証に関する管理の負荷も軽減される。
  • 今後登場する未知のクラウドサービスの導入と行った変化にも柔軟に対応できる
  • 各業務システム間のサイロ化の課題も、個々の業務システム担当者に大きな負担を強いることなく解決でき、IT 投資を効率化できる。

なんのことはない。一つのユーザーID・パスワードだけ管理できるようになれば問題は解決する。

SSO の 4 つの方式と特徴

方式には「リバースプロキシ方式」、「エージェント方式」、「クライアントエージェント方式」「フェデレーション」の 4 つ紹介されているが、最初の 3 つはオンプレミスでSSOを用意する方式。クラウドサービスを利用するものが「フェデレーション」。SSO = フェデレーションと捉えてしまってよい。

フェデレーションの仕組み

フェデレーションの仕組みの一つとして「SAML」や「OpenID Connect」といった。「標準規約」が用意されている。
SAML とは(Security Assertion Markup Language)の頭文字で、OASIS(Organization for the Advancement of Structured Information Standards)が定めた、クラウドサービスと企業の間で認証の連携を行う標準規格のこと。ほとんどのクラウドサービスがこの SAML に対応しており、クラウドサービスと認証の連携を行う場合は、SAML を利用することが多い。

OpenID Connect つまり JWT を使うこともできるのだろう。が、SSO では SAML を使う。

SSO(フェデレーション) はシンプル

本書では、フェデレーションの仕組みをパスポートの仕組みに例えて説明している。

パスポートは所持する本人の国籍の国で発行され、その発行には比較的厳格な本人確認、つまりユーザーの認証が求められる。 ただし、一旦パスポートが発行されれば、渡航先の国ではパスポート自体が本人確認となる。 入国の際にパスポート以外の本人確認の手続きは必要とされない。

f:id:sekilberg:20210713002050p:plain

  1. IdP(IdentityProvider)で厳格な本人確認をする
  2. IdP がアサーションSAML = パスポート)を発行
  3. アサーションがブラウザを介して SP(Service Provider = アプリケーション)に提示される
  4. アサーションの正当性と信頼した IdP による発行かどうかを SP が確認する
  5. SP がユーザーに利用を許可する

SAMLアサーションがパスポートと異なるのは、一度発行されたアサーションは特定の SP へのログインにしか使えないこと。SP 毎にアサーションを発行することが必要。(これは SAMLの仕様)


IdP による厳格な審査を通過していることとSPとの信頼関係によって成り立っているわけですね。

おまけ

いきなり、IdP(IdentityProvider)、SP(Service Provider)といった用語が出てきました。 OAuth(= 認可)の世界での登場人物は、リソースオーナー、クライアント、認可サーバー、リソースサーバーですが、OpenID Connect や SAML(= 認証)の世界になるとユーザー、SP、IdPといった用語になるところも混乱しがちなところです。(トークン→アサーションとか)

まとめ

そもそも技術書ではないし、SSO導入事例、オンプレ導入方法といった企業の経営者や情報システム担当者向けの内容だったので、OneLogin, okta などの IDaaS に関するお目当ての情報はなかった。
ただ、SSOの概要は理解できました。特にパスポートに例えた説明がわかりやすく、OAuth に比べずっとシンプルに感じました。(どう実装するかは別として。。)SSO=パスポートの仕組みということがわかっただけでも収穫アリですね。

SAML に関してはほとんど説明されていませんが、id token のような個人情報が XML 形式になっただけと捉えれば良いかと思います。

近い将来 SSO による生体認証が当たり前になって、フォームに E メール, パスワードを入力していたことが時代遅れになる未来もそう遠くないのかもしれません。

プロジェクトの振り返りに LeanCoffee を試してみた

back check 事業部開発チームの匠平@show60です。

プロジェクトの振り返りに LeanCoffee を用いてみたので、今回はこちらを振り返ってみたいと思います。

振り返りを行った経緯

back check チームではスプリントの終わりに KPT を用いた振り返りを行っていますが、今回これとは別にプロジェクト単位での振り返りを行いました。

チームではこの1ヶ月ほど新機能の開発を進めていたのですが、進行の障害となる事象が起こり始め、うまくいっていないと感じるようになりました。 何が原因で "うまくいってない" のかを洗い出すため、各自が課題と感じていることを収集し議論する場を設けることにしました。

LeanCoffee の実施方法

LeanCoffee とは

何が課題であるかを探るところから始めるため、振り返り手法には LeanCoffee を用いることにしました。

アジェンダのないミーティング方法です

参加者が集まり、アジェンダを作り、議論を始めます

Lean coffee

メリットとしては以下のことが挙げられています。

  • 広い範囲のトピックを扱うことができる
  • 事前・事後の準備が (ほぼ) 不要
  • ネクストアクションを出すこともできる

LeanCoffee の進め方

準備と進行方法は、こちらのスライドを参考に実施しました。

www.slideshare.net

振り返り会場は miro で作成し、事前に以下のような枠組みだけ用意しました。

f:id:show-hei:20210706205703j:plain

話したいトピックを黄緑色の付箋に書いて左の Ready の枠に入れていきます。出揃った付箋のなかから話す順番を決めるため、緑の●を各自2票ずつ投票します。

話すトピックをピックアップして Doing に移します。トピックについて話す時間を7分間とし議論します。

7分経ったら、このトピックの議論を続けるかを投票します。手のジェスチャーで意思表示しますが、今回 miro で行ったためカーソルアイコンを手の絵文字に乗せることで投票としました。

「議論を継続」に4票以上投票されていたら議論時間を4分追加します。これを繰り返し、4票未満になるまで議論を行っていきます。

やってみた結果どうだったか

良かったこと

ミーティング時間が延々と長引くことがない

議論が白熱したり、煮詰まるとついつい長引いてしまうことがあります。LeanCoffee では投票時間を設けてあるため、ミーティング時間を区切る機会が多く、コントロールがしやすいと思います。

広いトピックを扱うことができる

投票によって議論するトピックが決まります。選出理由に制限はなく、多くのメンバーの興味を引いたトピックが選出されます。個人で抱えている課題も場に出すことができるため、相互理解にも効果があると感じました。

良くなかったこと

トピックが大きい場合、別途切り出すなど工夫したほうがいい

LeanCoffee に議論の制限時間を設けているのは、トピックを速く処理するための仕掛けだと思っています。その特性上、取り扱う課題が大きいトピックや、前後関係の深いトピックの場合は扱いが難しいと感じました。

これらのネクストアクションまで話し合うには、ファシリテーションを含め慣れや訓練が必要そうです。

「◯回の延長が行われた場合、別のミーティングを設ける」など独自ルールを決めてもいいかもしれません。

他の手法を上回るメリットが出てこなかった

課題の収集という点ではタイムライン振り返りのほうが抜け落ちが少ないため、LeanCoffee を課題収集・理解の目的と割り切るには難しいように思います。

また課題を選出して議論するという手法は KPT も同じですが、ネクストアクションを必ず出すという点においては KPT のほうが強力です。

KPT での振り返りに慣れているチームとしては、上記で挙げている「良かったこと」を KPT で享受できており、今回試してみた限りでは大きく勝るメリットを感じられませんでした。

準備の手軽さという点は大きなメリットなので、これから振り返りを導入しようというチームにとっては選択肢としてありなのではと思います。

同じように、普段とは違う部署やメンバーとミーティングするときなんかも上手く機能するのではないかと感じました。

おまけ: 振り返り方法の選定

振り返り方法として LeanCoffee 以外にも KPT とタイムライン振り返りも検討しましたので、不採用理由もそれぞれ記述します。

結果として、今回の目的であればタイムライン振り返りを採用してもよかったなと思っていますので、ぜひ次回チャレンジしてみます。

KPT

スプリントの終わりの振り返りには KPT を用いています。 KPT はチームの課題 (Problem) に対して改善のためのネクストアクション (Try) を決めるという強いメリットを持つフレームワークです。

議論したいトピックを投票で選出することで短時間でネクストアクションを考えられる反面、選出されなかったトピックは議論されないまま、また日の目を見ることを期待してそっと過去に消えていきます。

今回の目的は各自が課題に思っていることを洗い出し、できる限り多くを議論の場に持っていくことが目的であったため KPT の採用は見送りました。

タイムライン振り返り

プロジェクトの始まりから終わりまでを時系列に沿って書き出す、データの収集を目的としたフレームワークです。 今回 LeanCoffee を試してみたかったこともあり採用を見送ったのですが、振り返り材料の収集という点では効果のある手法です。

タイムラインで収集した振り返り材料を KPT に持っていく、という組み合わせでぜひチャレンジしてみたいです。