AWS API Gateway(HTTP API) + Lambda + DynamoDB でサーバレスアーキテクチャを構築する

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

www.ritolab.com


AWSAPI Gateway(HTTP API) + Lambda + DynamoDB を使ってサーバレスアーキテクチャを構築してみます。

開発環境 - Terraform v1.0.2

DynamoDB テーブルを作成

データベースを用意します。DynamoDB にテーブルを作成します。

構成としては、date(年月日)をパーティションキーにして、ソートキーに time(時分秒)を起きたいと思います。

先に見せるとこんな感じのテーブルを作ります

f:id:ro9rito:20210909170857p:plain

main.tf

resource "aws_dynamodb_table" "event_log_table" {
  name           = "EventLog"
  billing_mode   = "PROVISIONED"
  read_capacity  = 1
  write_capacity = 1
  hash_key       = "date"
  range_key      = "time"

  attribute {
    name = "date"
    type = "S"
  }

  attribute {
    name = "time"
    type = "S"
  }
}

DynamoDB のテーブル作成自体は簡単で、ミニマムは上記のように数行書けば作成できます。

f:id:ro9rito:20210909171633p:plain

Lambda function 作成

次に DynamoDB に情報を記録していく Lambda function を作成します。

CloudWatch Log Group と Role の作成

関数に行く前に Lambda function 実行のログを CloudWatchLogs に流すためにロググループと、そこへの書き込み、および DynamoDB にアクセスできる Lambda 用のロールを作成しておきます。

main.tf

# CloudWatch ロググループ作成
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group
resource "aws_cloudwatch_log_group" "log_events_lambda_function" {
  name = "/aws/lambda/${var.log_events_lambda_function_name}"
}

# Iam Role 作成
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role
resource "aws_iam_role" "lambda_function" {
  name = "${var.app_name}-log-events-lambda-function-role"

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

# Iam Policy 作成
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy
resource "aws_iam_policy" "lambda_function_put_log_events" {
  name        = "log-events-lambda-function-policy"
  description = "IAM policy for log events Lambda function."

  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.log_events_lambda_function.arn}:*"
        ]
      },
      // AWS Lambda: Lambda 関数に Amazon DynamoDB テーブルへのアクセスを許可します
      // https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_examples_lambda-access-dynamodb.html
      {
        Action = [
          "dynamodb:Scan",
          "dynamodb:GetItem",
          "dynamodb:PutItem",
        ]
        Effect   = "Allow"
        Resource = aws_dynamodb_table.event_log_table.arn
      },
    ]
  })
}

# Policy を Role にアタッチ
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment
resource "aws_iam_role_policy_attachment" "lambda_function_put_log_events" {
  policy_arn = aws_iam_policy.lambda_function_put_log_events.arn
  role       = aws_iam_role.lambda_function.name
}

Lambda function 作成

function は node.js で、ソースコードは terraform 管理です。

index.js

const AWS = require("aws-sdk");

const dynamo = new AWS.DynamoDB.DocumentClient();

const tableName = "EventLog";

exports.handler = async (event, context) => {
    let body;
    let statusCode = 200;
    const headers = {
        "Content-Type": "application/json"
    };

    try {
        switch (event.routeKey) {
            case "GET /events":
                body = await dynamo.scan({
                    TableName: tableName
                }).promise();
                break;
            case "POST /events":
                let requestJSON = JSON.parse(event.body);
                await dynamo
                .put({
                    TableName: tableName,
                    Item: {
                        date: requestJSON.date,
                        time: requestJSON.time,
                        userId: requestJSON.userId,
                        eventType: requestJSON.eventType
                    }
                })
                .promise();
                body = `Put item ${requestJSON.userId}`;
                break;
            default:
                throw new Error(`Unsupported route: "${event.routeKey}"`);
        }
    } catch (err) {
        statusCode = 400;
        body = err.message;
    } finally {
        body = JSON.stringify(body);
    }

    return {
        statusCode,
        body,
        headers
    };
};

Lambda 関数自体は API Gatewayチュートリアルを参考にしています。

docs.aws.amazon.com

main.tf

# ファイルの ZIP 化
## https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/archive_file
data "archive_file" "log_events_lambda_function" {
  type        = "zip"
  source_dir  = "path/to/functions/${var.log_events_lambda_function_name}"
  output_path = "path/to/functions/upload/${var.log_events_lambda_function_name}.zip"
}

# Lambda function 作成
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function
resource "aws_lambda_function" "log_events" {
  filename      = data.archive_file.log_events_lambda_function.output_path
  function_name = var.log_events_lambda_function_name
  role          = aws_iam_role.lambda_function.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_function_put_log_events,
    aws_cloudwatch_log_group.log_events_lambda_function
  ]
}
  • Lambda function を適用させる際に ZIP で上げるため、作成した js ファイルを ZIP 化しています。
    • source_code_hash で ZIP ファイルをハッシュ化しています(filebase64sha256() 関数は Terraform 0.11.12 以降。それ以前は base64sha256() と file() 関数を使う)
    • apply や plan 時に ZIP ファイルが作成される
  • role でに先程作成した IAM Role を指定しています。

apply すると Lambda 関数が作成されます

f:id:ro9rito:20210909171217p:plain

API Gateway エンドポイント作成

ここからは Amazon API Gateway を使って先程作成した lambda function を利用する際のエンドポイントを作成していきます。

今回は HTTP API で作成します。作成に必要なステップは以下になります。

  1. API 作成
  2. 統合設定
  3. ルート設定
  4. ステージ設定

API 作成

まずは大本になる API を作成します。

main.tf

# API 作成
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_api
resource "aws_apigatewayv2_api" "log_events" {
  name          = "xxxxxxxxxxxx"
  protocol_type = "HTTP"

  cors_configuration {
    allow_origins = ["*"]
    allow_methods = ["GET", "POST"]
    allow_headers = ["*"]
  }
}

cors_configuration - CORS の設定を行っています。設定できる項目は以下(すべて optional)

  • allow_origins
  • allow_headers
  • allow_methods
  • allow_credentials
  • expose_headers
  • max_age

HTTP API の CORS の設定

docs.aws.amazon.com

統合設定(API と Lambda function の関連付け)

先程作成した API と Lambda function を紐付けます。

main.tf

# 統合設定(API と Lambda function の紐付け)
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_integration
resource "aws_apigatewayv2_integration" "log_events" {
  api_id           = aws_apigatewayv2_api.log_events.id
  integration_type = "AWS_PROXY"
  integration_method = "POST"
  integration_uri = aws_lambda_function.log_events.invoke_arn
  payload_format_version = "2.0"
}

integration_type - 統合タイプを選択。HTTP API の場合は websocket もサポートしているので選択肢がいくつかあり、Lambda function と統合させる場合は AWS_PROXY を設定する。

選択できる項目

  • AWS(WebSocket API でのみサポート)
  • AWS_PROXY
  • HTTP(WebSocket API でのみサポート)
  • HTTP_PROXY
  • MOCK(WebSocket API でのみサポート)

docs.aws.amazon.com

integration_method - 統合先へのリクエストの HTTP method を指定。Integration_type が MOCK でない場合は指定は必須。Lambda との統合なので POST を指定。

docs.aws.amazon.com

ルート設定

作成した API に対してメソッドやリソースを設定します。

docs.aws.amazon.com

main.tf

# ルート設定
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_route
variable "routeList" {
  type    = list(string)
  default = ["GET /events", "POST /events"]
}
resource "aws_apigatewayv2_route" "log_events" {
  count  = length(var.routeList)

  api_id = aws_apigatewayv2_api.log_events.id
  route_key = element(var.routeList, count.index)

  target = "integrations/${aws_apigatewayv2_integration.log_events.id}"
}

target - アタッチする統合を指定する - Integrations/{IntegrationID} の形式で指定 - IntegrationID は aws_apigatewayv2_integration リソースの識別子

route_key - "GET /events" のように「<HTTP メソッド> <リソースパス>」で指定 (今回は 2 メソッド分まとめた記述になっている)

ステージ設定

ステージを設定します。

docs.aws.amazon.com

リクエストのログも残したいので CloudWatch のロググループも併せて作成して設定します。

main.tf

resource "aws_cloudwatch_log_group" "log_events_http_api" {
  name = "/aws/apigateway/${var.log_events_api_name}"
}

# ステージ設定
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_stage
resource "aws_apigatewayv2_stage" "log_events" {
  api_id      = aws_apigatewayv2_api.log_events.id
  name        = "$default"
  auto_deploy = true

  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.log_events_http_api.arn

    format = jsonencode({
      requestId : "$context.requestId",
      ip : "$context.identity.sourceIp",
      requestTime : "$context.requestTime",
      httpMethod : "$context.httpMethod",
      routeKey : "$context.routeKey",
      status : "$context.status",
      protocol : "$context.protocol",
      responseLength : "$context.responseLength",
      errorMessage : "$context.error.message",
      errorResponseType : "$context.error.responseType"
      authorizerError : "$context.authorizer.error",
      integrationErrorMessage : "$context.integrationErrorMessage"
    })
  }
}

access_log_settings でアクセスをログに記録するための設定を行なっています。format でアクセスログ 1 行の形式を指定、$context 変数で値を指定します。

docs.aws.amazon.com

APIGateway に Lambda へのアクセスを許可

APIGateway 側の一通りの設定が済んだので、最後に APIGateway から Lambda へのアクセスを許可します。

main.tf

# APIGateway に Lambda へのアクセスを許可
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission
resource "aws_lambda_permission" "execution_log_access_api_gateway" {
  statement_id  = "test-AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.log_events.function_name
  principal     = "apigateway.amazonaws.com"

  source_arn = "${aws_apigatewayv2_api.log_events.execution_arn}/*/*"
}

動作確認

ここまでで一連の構成は定義できたので、apply して環境を構築し、エンドポイントへリクエストを送信すれば API Gateway, Lambda, DynamoDB が連携して動作し、データが保存されます。

f:id:ro9rito:20210909172147p:plain

カスタムドメイン設定

API Gateway で作成されたエンドポイントの URL ですが、でデフォルトでは

https://<api_id>.execute-api.<region>.amazonaws.com/xxxxxxx...

という形式になっています。

カスタムドメインを設定すると、自分のドメインを使ってエンドポイントの URL を作成できるので、こちらの設定を行なってみます。

(ネイキッドドメインは Route53 に登録済みの前提です。

ACM 証明書発行

まずは、利用するサブドメインの証明書を ACM で作成しておきます。

main.tf

# 作成済みホストゾーン情報の取得
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone
data "aws_route53_zone" "myDomain" {
  name = var.domain_name
}

# ACM 証明書作成
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate
resource "aws_acm_certificate" "APIGatewayHttpApi" {
  domain_name       = "samle-htttp-api.ritolab.com"
  validation_method = "DNS"
}

## ACM 検証用 CNAME レコード
### https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record
resource "aws_route53_record" "api_gateway_http_api_acm_c" {
  for_each = {
  for d in aws_acm_certificate.APIGatewayHttpApi.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.myDomain.zone_id
  name            = each.value.name
  type            = each.value.type
  records         = [each.value.record]
  ttl             = 60
  allow_overwrite = true
}

## ACM 証明書 / CNAME レコード 連携
### https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation
resource "aws_acm_certificate_validation" "APIGatewayHttpApi" {
  certificate_arn         = aws_acm_certificate.APIGatewayHttpApi.arn
  validation_record_fqdns = [for record in aws_route53_record.api_gateway_http_api_acm_c : record.fqdn]

  depends_on = [
    aws_acm_certificate.APIGatewayHttpApi,
    aws_route53_record.api_gateway_http_api_acm_c
  ]
}

カスタムドメイン設定

カスタムドメインを設定します。

main.tf

## カスタムドメイン登録
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_domain_name
resource "aws_apigatewayv2_domain_name" "http_api" {
  domain_name = "sub.domain.com"

  domain_name_configuration {
    certificate_arn = aws_acm_certificate.APIGatewayHttpApi.arn
    endpoint_type   = "REGIONAL"
    security_policy = "TLS_1_2"
  }

  // 証明書作成が完了してから
  depends_on = [
    aws_acm_certificate_validation.APIGatewayHttpApi,
  ]
}

## API マッピング
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apigatewayv2_api_mapping
resource "aws_apigatewayv2_api_mapping" "http_api" {
  api_id      = aws_apigatewayv2_api.log_events.id
  domain_name = aws_apigatewayv2_domain_name.http_api.id
  stage       = aws_apigatewayv2_stage.log_events.id
}

# route53 A レコード作成
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record
resource "aws_route53_record" "http_api" {
  name    = aws_apigatewayv2_domain_name.http_api.domain_name
  type    = "A"
  zone_id = data.aws_route53_zone.myDomain.zone_id

  alias {
    name                   = aws_apigatewayv2_domain_name.http_api.domain_name_configuration[0].target_domain_name
    zone_id                = aws_apigatewayv2_domain_name.http_api.domain_name_configuration[0].hosted_zone_id
    evaluate_target_health = false
  }
}

カスタムドメインを登録し、API マッピングを行なった後に、route53 へ使用するサブドメインの A レコードを作成しています。

ポイントとしては、カスタムドメイン登録のときに depends_on を指定し、証明書の作成を待ってからカスタムドメインの登録を行うようにします。 依存関係を指定しておかないと証明書が有効になる前にカスタムドメインを登録しようとしてエラーになります。

これでカスタムドメインでエンドポイントへアクセスできるようになります。

% curl https://sample-http-api.ritolab.com/events
{"Items":[{"date":"2021-08-28","eventType":"test","time":"14:19:00.000","userId":"EX8C9MRZfhL"},{"date":...

IAM 認証

現状ではエンドポイントは開放されている状態なので、IAM 認証をかけてアクセス制限を掛けます。(既に Cognito とか Auth0 とかで認証のあるサービスなら IAM 認証ではなく Lambda オーソライザー使うのが良さそう)

API Gateway のルートの設定において、認証を IAM に変更します。

main.tf

resource "aws_apigatewayv2_route" "log_events" {
  .
  .
  .
  authorization_type = "AWS_IAM"
}

これだけです。あとはリクエスタとしての IAM ユーザーを作成して、適宜必要な権限を設定してあげます。

main.tf

# IAM Role for ApiGateway
resource "aws_iam_policy" "api_gateway_log_event_requester" {
  name        = "xxxxxxxxx"

  policy = jsonencode({
    Version : "2012-10-17",
    Statement = [
      {
        Effect: "Allow",
        Action: [
          "execute-api:Invoke"
        ],
        Resource: [
          "arn:aws:execute-api:${var.aws_region}:${var.aws_id}:${aws_apigatewayv2_api.log_events.id}/*/GET/events",
          "arn:aws:execute-api:${var.aws_region}:${var.aws_id}:${aws_apigatewayv2_api.log_events.id}/*/POST/events",
        ],
      },
    ]
  })
}

# IAM User for ApiGateway execution
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user
resource "aws_iam_user" "api_gateway_log_event_requester" {
  name = "api-gateway-log-event-requester"
}

## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy_attachment
resource "aws_iam_user_policy_attachment" "api_gateway_log_event_requester" {
  user       = aws_iam_user.api_gateway_log_event_requester.name
  policy_arn = aws_iam_policy.api_gateway_log_event_requester.arn
}

これで、アクセスが許可されたユーザー以外からのリクエストは遮断されます。

f:id:ro9rito:20210909172439p:plain

一方で、許可された IAM ユーザーからのリクエストは受け入れられデータが取得できている事を確認できました。

f:id:ro9rito:20210909172457p:plain

まとめ

エンドポイントからバックエンド、DB まで一連をサーバレスアーキテクチャで構築しました。

レスポンスは遅くないので、これでサーバ運用の手間が減ると考えるとサーバレス化は手段としてはアリだなと感じました。

あとやはり個々のサービスが疎結合になるため、必要に応じて最適なプログラミング言語を選択できるのも場合によっては利点になり得る要素だと思いました。

それと、API Gateway でのエンドポイントを今回は HTTP API で構築してみましたが、オートデプロイが行えるのは現時点では HTTP API のみで REST API には無い機能だったので、開発時にデプロイを気にしなくて良いのはとても便利でした。

AWS Well-Architected フレームワークの「パフォーマンス効率の柱 設計の原則」においてサーバレスアーキテクチャが推奨されているように、サーバレス構成は適所で採用すればとても使えるアーキテクチャでした。

docs.aws.amazon.com

データサイエンスの始め方

この記事は 個人Qiita と同じ内容です

qiita.com/sekiyaeiji

プロダクトのグロースにデータを活用したい...

サービスグロースにデータを活用したいと思ったとき、わたしはまず最初にどこに着目し、ナニから始めればいいのだろう?

よくわからなかったので調べ始めてみた。

小さなテーマからはじめよう

データサイエンスは、最初、できるだけコンパクトに小さく始めて、その成果をもとに大きく波及させるのが、うまくいきやすいポイントらしい。

小さなテーマを100個ぐらい仕込んで、大きく化けるテーマを探すらしい。

小さく始めると、失敗しても影響が小さく、軌道修正も容易で、何度でもチャレンジできるメリットがあるとのこと。

...で、ナニをやるんだろう

始め方はわかったけど、小さくナニを始めるのか?

活用目的と、活用ストーリーと、分析ストーリー

このデータ分析は何のためにやるのか、を明確にするのが、活用目的を決めることだが、 データサイエンスの世界では、目的を決めるだけでは不十分らしく、 その目的に対して、分析データの活用ストーリーを明確にする必要がある。

分析結果をただ提示しても利用してもらえない

「分析結果がAの場合、1の施策を、また結果がBの場合は、2の対策を実践する」、のような、 結果と活用方法の対応が分かる情報を、 「活用ストーリー」として分析結果とセットで提示すれば、 分析結果は上手に活用してもらえるらしい

また、分析結果と「活用ストーリー」を事前にしっかり設計することにより、 提供すべき分析結果の精度や、確実に活用してもらえるデータ提供が可能になると思われる

そしてさらに、一元的な分析結果ではなく、いくつかの中間データを経て分析結果を得る場合に、 その各ステップや全体の構造を説明したものを、「分析ストーリー」と呼ぶことができる

多層構造を要する分析においてはこの分析ストーリーを明確にすることが 保守の面においても「活用ストーリー」の設計においても大切になってくる

筋のいいテーマ

以上の3つの要素、

  • 活用目的
  • 活用ストーリー
  • 分析ストーリー

が明確で、さらに

  • 成果が大きい
  • やりやすい

テーマは、"筋のいいテーマ"らしい

"筋のいいテーマ"を見つけることが、 確実かつスピーディーに成果を出せるテーマを選択できるコツ、と言ってよさそうだ

よって、必須3要素である、活用目的、活用ストーリー、分析ストーリーを 上手に設計するトレーニングを重ねることが データ分析上達の鍵な気がする

モデルの選び方

チートシート

分析を設計する際に必要になる統計解析・機械学習モデルについて、
世にはチートシートというサンプルモデルも出回っているらしい

モデルの種類と特徴

採りたいデータごとに分析方法を選択して利用する

  • フィッシュボーンチャート 特性要因図
    • 目的変数と複数の説明変数からなる魚の骨状の図
    • わかりやすく、要素を組み立てやすくてかなり便利
  • 線形判別モデル
  • ロジスティック回帰モデル
  • クラスター分析
  • 主成分分析
    • 似たような傾向を持つデータ項目(変数)を集約する
  • グラフィカルモデリング
    • データ項目間(変数間)の構造を描く

分析の実践に役立ついくつかの手法 メモ

小さく始める際に、ジョハリの窓における「開放の窓」を狙うことで、 現場の感覚とズレのない項目から着手する

選択肢の分岐点となる閾値を算出するために、決定木(ディシジョンツリー)を利用する

ニーズ(needs) より ウォンツ(wants) 変革(change) よりも 改善(improvement) つまり、ウォンツ ✕ 改善 の象限から着手する

痛み よりも 楽になること 全体最適 よりも 個々の部署のメリット(メリットの平準化) つまり、楽になる ✕ メリットの平準化 の象限、 できるだけ多くの関係者がメリットを感じられるテーマから着手する

「実験計画法」(少ない実験で効率的にデータを取得する方法論)で、 データ取得計画を作り、実験しデータを取得し、 取得データから「応答曲面法」で設計変数、品質特性の関係性を数式化し、 その数式を使い「数理計画法」により最適な「設計変数値」を算出する

改善・変革系データサイエンスと、データエコノミー系データサイエンス

日本で成果が出ているデータサイエンスはSQCのような改善・変革系データサイエンス

GAFA系が実現したデータエコノミー系データサイエンスへの発展を目指す場合、 各社がふつうのタスクとしてあたりまえに取り組むことと、 社内で始めて自社外への拡大を実践することで、市場向けのデータサイエンスは可能になる

まとめ

データサイエンスに着手するための基本情報を以上の通りピックアップしてみた

大枠として以下を意識してまずは実績を作成してみるのがよさそうだ

  • 小さなテーマを100個作成して、大きく成長するテーマを探す
  • 活用目的、活用ストーリー、分析ストーリーの3要素で設計する
  • 用途に応じて使えるツールと手法のテンプレートが多く存在する

ネクストアクション

次はこれを読んで情報をまとめる予定

それともう一冊、

データサイエンスにおいて『孫氏』は必読の書、らしいが、

どういうことかよくわからないので、とりあえず読まなければならない

参考図書

本稿では以下の書籍を参考にさせていただきました

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 agent bank開発チームのorifujiです。チーム内での取り組み「Rock Activity」の一環で本記事を投稿します。 元々、何かしら新しく身につけたい技術に触ってみてそのコード解説をしようかとも思っていました。いたのですが、ごく最近の自分の取り組みとして最もちゃんとしたActivityだったのはこれだな、ということで、直近の生活改善内容を綴ってゆく方向へ舵を切りました。 生活改善に関わるちょっとしたtipsと、それに対するわたしの所感が書かれています。たぶん、ギリギリ技術ブログなのでは……

ちなみに最近始めた個人ブログで先に公開しています

「これは、ポンコツポンコツによるポンコツのための生活改善案だ。万国のポンコツよ、とりあえず朝起きろ!」

というわけで、生活習慣を見直すための記事を始めます。ここ数ヶ月のわたしの悪戦苦闘の様子が見られます。

TL;DR

  • 意志ほど信用ならないものはありません。良い行動を仕組みや条件反射に落とし込み、自らの身体を傀儡のように扱いましょう
  • 睡眠をコントロールしましょう
  • 適切に休憩するためのフックを用意しましょう
  • 疲れにくくするために身体を鍛えましょう
  • 業務中に自然とできていることは私生活にもだいたい応用可能なので、とりあえずやってみましょう(まずは、スクラム的なふりかえりから!)

序:経緯・動機

なんかうまくいかない日々

冒頭に偉そうな発言を引用しましたが、何を隠そう、誰よりもまずわたしがポンコツだったのです……

具体的には、現職への転職後、概ね下記のような状態に陥っていました。

  • 毎日、業務時間内に体力が尽きる
  • ちょっとしたことで生活リズムが崩壊する
  • 結果、入社2ヶ月目で2回も遅刻
  • 趣味・学習に時間が費やせず、成果もイマイチ
  • 思考が澱み、たいして面白いアイデアも出てこない
  • 何よりも仕事が遅い

前職での心身の疲れが尾を引いていたとはいえ、これはなかなか手痛いズッコケです。 「面倒だけど、どうにかしないとなぁ」などと零しつつ、どうにか挽回するためにさっと簡単にできるとこから始めちゃおうぜ、というポジティブシンキングを開始しました。

そのあたりまえ、あたりまえに出来ていますか?

そこで、簡単にできるNext Actionとして、「問題解決方法・生活改善に関して書かれた本」を多読してみてアイデアを拝借するを案出しました。

のですが、白状すると、実は筆者はこれまで、ビジネス書・自己啓発書をあまり読んできませんでした。正直に申し上げると、「内容が薄い」「あたりまえのことが書かれているだけ」「一面的な思想を煽情的なキャッチコピーとともに押し出している」ような印象を抱いていたのです。そのため、長年に渡り距離を置いていました。

そのため、アイデアとして思いついておきながらも、ちょっぴり及び腰になります。

しかし、昨今の為体から、自身の考えに対して疑問が生じてきます。例えば、「本当にデキるビジネスマンが参考にしているなら一読の価値はあるのでは?」「あたりまえのことって……本当にできてる?」というように。

そんなわけで、とりあえず馬鹿にせずに読みまくってみよう、と決めました。だいたい、2021年6月から7月の出来事です。

読んだ本

書店やAmazonで幾らか見繕います。あまり知識のない領域なので、売れ線や「わたしでもよく聞く本」から選んできました。

  • 安宅和人『イシューからはじめよ』
  • ジム・クウィック『LIMITLESS 超加速学習: 人生を変える「学び方」の授業』
  • グレッグ・マキューン『エッセンシャル思考』
  • Testosterone・久保孝史『超 筋トレが最強のソリューションである 筋肉が人生を変える超・科学的な理由』
  • 樺沢紫苑『インプット大全』
  • 樺沢紫苑『アウトプット大全』
  • 樺沢紫苑『神・時間術』

などなど。時には(神の時間術ってなんだよ……)などと思いつつも、全て読み切りました。結構楽しい体験でした。折を見てジュンク堂書店池袋本店1F, 5F(↑のような本がいっぱい置いてあるところ)とか覗きに行くかもしれません。

生活習慣改善・行動改善の本は少なめかも知れませんが、いずれの本も今回のテコ入れには少なからず役に立ってと思っております。

振り返ってみると、今回採用した改善案には幾らかの書籍に共通して書かれていた事項が多いです。また、著者独自というよりは一般論的な見解も多かったと判断しているため、直接的な引用をしない限りは個別に詳しく典拠を注釈することはしません。上記の紹介に留めます。

本:やったこと、わかったこと

学習効率化や問題解決の本が多めになったのですが、今回はいったん、そういうものよりも生活習慣改善・行動改善にフォーカスして、得た知識をまとめてみようかと思っています。 今回はとりあえず最近の取り組みをまとめるだけですが、後々、プラクティスをより体系化して記事にします。

睡眠改善

ツラミポイント

  • 起きるのが遅いと仕事の立ち上がりが悪い
  • そもそも睡眠時間が短い
  • 目覚めが悪い
  • 昼食後、かなり眠くなる

対処

早寝のための行動

上掲の書籍群の一部を参考にしつつ、下記を行いました。

  • 後述する筋トレ活動で身体を追い込み、疲れで寝てしまう状態をなるべく作るようにした
  • 生活リズム補正が必要なときのために、かかりつけの医師と相談のうえ、メラトニン錠剤を個人輸入した
早起きのための行動と、目覚めを良くするための行動

「意識が覚醒したら意地でも目を瞠ることを習慣とする」は効果的でした。セロトニンが云々……などと説明することもできますが、それ以前の単純な話、目をあけていれば二度寝はしないんです。意識が安定し、立ち上がることができるまで目を開き続けます。 気合いで解決、みたいな方法に見えますが、慣れると条件反射的に目を開けるようになりますし、いきなり起き上がるよりはたぶん楽です。

しばらくして立ち上がることができたら、廊下に出て日光を浴びます。自宅の場合、窓の構造上の問題等で自室に日光を入れることが期待できないため、都度廊下に出ています。日光浴の最中は軽く動的ストレッチなどをすると、手持ち無沙汰感がないです。

それが終わったら、階段昇降または散歩を行い、軽い運動とします。これやるだけで頭の冴え方が違う気がしています。

日中の眠気や集中力低下を抑制するための行動

これは端的に「昼寝」です。30分程度で済ませるのがベストだと思います。

なお、万一の寝落ち防止のため、布団に臥せることは避けます。万年床は危険なのです。朝のうちに畳んでおくと良いでしょう。

布団の代わりに、ネックピローをつけながら椅子で軽く寝ることにしています。目覚ましがなったら確実に起きられる程度の状態で寝る、がちょうどいいですね。

得たもの

  • 過度な夜更かしをしなくなった
  • 毎日コンスタントに7-8時間の睡眠が確保できるようになった
  • 午後も集中してタスクに取り組めるようになった

筋トレ

ツラミポイント

  • 体力がない
  • 気分が沈みがち
  • 音ゲーで疲れる

対処

筋トレする、が対処な訳ですが、具体的にどういうメニューをやっているか、継続するためにどのようなことをしたかと綴っていこうかと思います。

メニュー
  • 拳立て 10 x 3
  • 腹筋 10 x 3
  • 腹斜 10 x 3
  • プランク 30sec x 3
  • 足上げ腹筋 10 x 3
  • 背筋 10 x 3
  • ダンベル(計12kg)有スクワット 10 x 3
  • ハンドグリップ(60kg) 左右 10 x 3
  • ランニング(距離不定 range: 0-8km)

上記を基本として、回復させたい部位は適宜メニューから除外、その分は別のメニューを補填する形で運用しています。例えば腕を重点的に鍛えたいときはダンベルカールを追加しています。

取り掛かる

このメニューをPC画面に大きく表示させて、上から順に

  1. メニュー内容を声に出す
  2. 声に出したら「やらないとな……」という気分になって取り掛かる
  3. 次のメニューに移る

を繰り返します。思いのほかこの声出し法は推進力が高くぶっ続けになりがちです。「続かないしんどい追い込み」を避ける意味でも、適宜休憩宣言(21:50まで休憩! とひとりで叫ぶ)をしています。現状、タスクの区切りごとに叫ぶのは本当に効果的なので、筋トレ以外にも応用したいところです。

続ける

筋トレやダイエットの三日坊主はよく聞くところです。予防的に、以下の対策をしておきました。

  • おいしいと思えるプロテインを購入し、「プロテインを飲みたい→せっかくなら筋トレしなきゃ」のサイクルを確立(プロテイン駆動)
  • 会社のslackに筋トレchを作成し、毎日筋トレ宣言or報告
  • 筋トレのアニメをNetflixで自宅上映し、気分を盛り上げる

得たもの

  • 目覚めが良くなった
  • 体力が戻り、一日中業務に集中できるようになってきた
  • 前向きな気持ちになってきた

氾濫する情報の群れをシャットアウトする

つらみポイント

下記について流石に嫌気がさしていました。

  • 怒り、焦り、そのほか諸々の良くない感情
  • コロナ禍の不安なニュース
  • オリンピックなどの時事にまつわるネガティブなニュース

社会課題が浮き彫りになる事件ではありますし、議論を避けることが現状追認を招いてしまう側面もあるかとは思います。 ただ、あまりにもネガティブな感情が出回っているため、健康を取り戻すまでは離れた方がいいな、と。 行動改善系の書籍でもSNS断捨離的なワードがよく目につきました。 そういうわけでとりあえずTwitterから始める形で、各種SNSと距離を置くことに。 それにしても、インターネット歴約20年にして、ようやくTwitter疲れを検知できたわけです。遅すぎですね。

対処

で、もうばっさりやってしまいました。

  • 手元端末からの公式クライアントアプリの削除
  • 自発的な更新をほぼ停止
    • またその旨を宣言してプロフィールに固定
  • フォロー先全てに対してRTミュートを適用
    • 例外的に見に行く必要が出た場合にも「知らない人の知らない言葉」を表示しないように

ここまでしてやっとTwitterに費やす時間を大幅に減らせました。

得たもの

イライラする契機が格段に減りました。ながら作業的に触って集中力を乱されることもなくなりました。

「独り言を多方面に向けて発信する」欲はslack, discordで代替できています。

スクラム的な考え・活動を私生活に輸入する

弊社開発チームでは一週間スプリントでスクラム開発を回しており、特に毎週金曜日には

  • スプリントレビュー
  • ふりかえり(レトロスペクティブ)
  • リファイメント
  • プランニング

などのスクラムイベントを行っています。このFWをプライベートにも持ち込んでみるのは面白そうだなと思い、同居人と、2週間スプリントで軽くスクラム生活をしてみることにしました。その施策の内容を書き出してゆきます。厳密さよりも手軽さを優先する方針ですので、ここに関してはツッコミご容赦を。

ふりかえり

良かったこと・悪かったこと・生活上の課題などを出してゆきます。例えば、以下のような感じに。

  • Good:仕事に結構集中できた
  • Good:オンラインゲームで遊ぶのに時間を捻出できたのは良かった
  • Good:筋トレ用具を買い足したおかげでモチベUP
  • Bad:ゲームに熱中しすぎて睡眠時間が減った
  • Bad:浪費の傾向がある
  • Problem:玄関先の雑草が邪魔で歩きにくく、雨の降った後はズボンがやたらに濡れる
  • Problem:2人で半日かければ終わりそうだから、来週末にカレンダーを押さえておこう

リファイメント

検知された問題への具体的な対処方法を検討します。例えば先程のProblem「玄関先の雑草が邪魔で歩きにくく、雨の降った後はズボンがやたらに濡れる」「お米のストック追加がギリギリになるから夕飯を食べ逃すことがある」などの困りごとについて、対処法を検討します。 前者は「草むしりとりあえずやろうか」「除草剤撒いちゃう?」などのようなアイデアが出るかと思います。そんな感じで、ブレインストーミング→タスク化→合意形成を進めます。

プランニング

リファイメントで詳細化したタスクがどれだけ時間をかければ問題なくこなせる作業かを見積り、いつ着手するかを計画します。例えば、リファイメント時の結論「玄関先の草むしりを歩くときに邪魔にならない程度にやっておく」というタスクに対して、「2人で半日かければ終わりそう」のような見積もりを行い、「カレンダーに予定を入れておこう」というように計画を立て、アクションに繋げます。

やってみた感想

やってみれば、これもやはり「当たり前の話し合い」に尽きるところはあったかと思います。特別なこともない営みではありましたが、意識的に実行すること自体の良さが存在しました。 「アイデア出しの時間を決めて、付箋に書き出し、ラウンドロビン形式でメンバーに展開する」などのチームである程度慣れていた方法を流用したことで、時間対効率を高められましたし、問題検知が普通に雑談しているよりかは高精度でできるようになった気がしています。 決まった日時に話し合う機会を設けることで、事前に考えておくこともでき、そもそも問題を棚上げにしてなあなあで済ませることもなくなるのが、体験として素晴らしいですね。

跋:成果、つぎにやること

自分に期待しない、という期待の仕方

意志ほど信用ならないものはないですね。頑張るとかどうにかするとか、曖昧な対処ではなく、仕組みに落とし込むことこそが大事。あたりまえですね。繰り返しになりますが、そのあたりまえができていない点に、病因があります。

いまだにポンコツのままですが、幾らかの成果は出ました

生来のポンコツ具合ゆえになかなか思うようにはいきませんが、当初目的は達成できたと認識しています。

  • 仕事内容の質的向上
  • 学習・趣味に費やす時間の増大
  • 生活リズムの安定
  • メンタルの安定

ですね。読書時間が増えたので、積読が高速消化されています。こんなに楽しいことはありません。

今後の予定

「◯◯をエンジニアリングする」シリーズを継続的に投稿してゆこうと考えています。

題材候補

  • 集中力をエンジニアリングする
  • 業後学習をエンジニアリングする
  • 家計をエンジニアリングする

……エンジニアリング、の意味が良くわからなくなりそうですが、小さなことでも「明確な目的意識を持ち」「なるべく技術的に問題を解決しようとする」態度をそう表現するのも悪くないかもなと思い、気軽になんでもエンジニアリングに含める方向でこのまま突き進みます。

感想

全体、気分が良くなったので満足しています。 レベルの低いこと、当たり前のこと、でもできていないこと。そういう見落としを認識する契機としては有意義な時間だったのではないかと省みる次第です。

今後は本記事に載せたような生活改善施策を高精度のプラクティスとして洗練させ続け、生活の質を高めたいなと思っています。引き続き、自己改善を行ったり、身近な人としっかり話し合ったり。生活にメリハリをもつと気持ちいいんですね。ごく最近、気がつきました。

プロダクトを改善するなら自分も改善していかないと、との思いで今後も続けてゆきます。

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