コミュニケーションの癖

qiita.com

「箇条書きはあなたを馬鹿にする」かどうかはわからないが、長年しみついた癖というのは直していくのが難しい。

かくいう自分も、箇条書きを多用しがちな一人。
また、上記の記事とは関係はないが、文章中の括弧も多用しがちという癖がある。

将来の自分も含めて誰かに読まれるためにあるものなので、読みやすい文章を書くことは重要なスキルの一つだ。 が、読み手の理解を妨げる、あるいは、負担を強いるようなコミュニケーションの「癖」というのは、大人になると誰かから指摘される機会も少ない気がする。指摘してくれる人がいるとしたら、その人には感謝しておこう。

意味の希薄化

t-wadaさんの「【翻訳】テスト駆動開発の定義」を読んで。

t-wada.hatenablog.jp

冒頭に書かれている「意味の希薄化」というのは、そういう概念があるということを改めて認識しておこうと思った。
引用されているマーティン・ファウラーの記事では、「アジャイル」や「Web2.0」などが引き合いに出されているが、自分の身近なところだと「心理的安全性」とかもそうであろうか。また、最近よく聞くようになった「disagree and commit」などもそうなっていきそうな予感がある。

話をTDDに戻すと、いい意味でも悪い意味でも「レッド・グリーン・リファクタリング」というサイクルが、耳に残りやすく、センセーショナルだったのかなと感じる。
私もTDDというのはこの3ステップのサイクルであると理解していた一人だった。
当時は今よりももっと未熟だったので、t-wadaさんの講演を聴いても、話の内容の1から100までを理解しておらず、覚えやすい・記憶に残りやすい部分だけが自分の脳にインプットされていたんだろうなと思う。そういった意味では、時間を空けてから同じ資料を読んでみるとか、実践や経験を積んだ後で改めて読み返してみることも大事だなと思わせるきっかけになった。

何千回やっていても本番リリースは神経を使うという話

今日、チームでの昼会で「私はいつでも本番リリースは神経を使っているので、CIの安定性は何より重要」みたいな話をした。

私は今のチームでも古株だし、メンバーのリリース作業を代理することもあるので、測ってはないけど、多分リリースの実施回数は一番だと思う。
それでも、常に本番リリースの時は緊張感をもち、常に全神経を集中して行うようにしている。
「何百、何千回もやっているから目をつむっていたってできるぜ」みたいな漫画のようなことはない。

大前提として、今のプロジェクトの基盤では、CI/CDパイプラインや自動テストはかなり整備されているし、ビッグバンリリースみたいなこともしてなくて、週に1~5回のペースでデプロイをしている。
リリース作業といっても、GitHub Actionsで定義されているワークフローを実行するだけだし、デプロイの成否はSlackで通知され、万が一問題があった場合はロールバックされるし、複数のメトリクスで正常動作が監視されているんだけどね。

これは単純に性格的なものなのかも。

Four Keysにも含まれているようにデプロイ回数は多い方が正義、みたいな風潮もあるが、自分たちのチームで何が大事なのかは見失わないようにしたい。

会社でテックブログをはじめてみた

気がつけば1年半ぶりの投稿になってしまいました。

2010年から毎年1つは記事を書くように意識してきたものの、2023年で途絶えてしまいましたね。

さて、少し前から会社(会社というよりは、私の所属しているプロダクト開発チーム単体かな)の方でテックブログを運営することにしました。もちろん会社からの許可は取っており、唐突な提案にも二つ返事でOKしていただいた会社には感謝です。

今はまだ手探りの段階で、ひっそりと週1~2回くらいのペースを目指して、私だけが更新をしている感じですが、こちらです(↓)!

zenn.dev

当面はAWSの記事を中心に、更新していこうかなーと思っています。

こちらのブログの方は閉鎖するつもりはなくて、業務外での技術的な内容を記事にしたり、読んだ本の感想を書いたり、より緩い感じで続けていこうと思います。

情報共有の「フロー」「ストック」に思うこと

設計書やPRD(Product Requirements Document)などのドキュメントについて、鮮度を落とさずに保守していくか、というのは、常に頭を悩ませる課題である。

ソースコードがきれいに書かれていれば、それを読めばよいはず」という説は一理あると思うが、個人的にはやはりドキュメントは必要と思う派だ。 もちろん、きれいなソースコードを書くことを目指すのは当たり前として。
ソースコードは「現状の動作がそうなっていること」は表明できるものの、「本当にその動作が意図したものか」を表明するものではないからだ。また、そういった背景/Whyの部分はコメントに書かれているべきだという説もあるが、コメントだけでは読み取れない、より深いコンテキストが欲しくなることもある。

なので、ドキュメントもしっかり書くことを今のチームでは目指している。
今は、某SaaSに備え付けのWikiをストック情報のハブにしていて、基本的にはここに必要な情報を集約させるように心がけている。
大きいファイルなんかは、別のストレージに置いているが、必ずWikiからたどれるようにしている。
(余談だが、出来にすごく満足しているわけではなくて、最低限の機能だけをもったWikiという評価だ。なので、Notionとか使ってみたいなと、軽く持ち掛けたことはある。)

話を戻して、ドキュメントの運用で意識しているのは以下の点だ。

  • 同じ情報を複数の場所に書かない。
  • 置き場所を事前にかっちり決めすぎない。

これにより、

  • (情報を得る側の視点で)雑にでもとにかく検索すれば、必要な情報がhitすること
  • (情報を貯める側の視点で)どこに書くべきかに頭を悩まないこと

を図り、情報の活用サイクルが回るように心がけている。

個人的にはそこそこうまく回っているように感じてはいる。
「置き場所を決めない」という発想は、アマゾンの倉庫だったり、野口 悠紀雄氏の「超」整理法(学生の頃なので十数年以上前の記憶だけど)と通じるところもあり、モノや情報を整理する上でのコツかもしれない。

とはいえ、まだまだ課題もある。
最近の開発はモノリシックなアプリケーションではなく、Lambdaの利用などが進んできたこともあり、各機能(サービス)ごとの小~中規模なまとまりでドキュメントを作ることが多くなってきた。
その一方で、完全にそれらがきれいに分離できているわけでもないので、共通で参照するデータストアに関するドキュメントなど、機能横断的なドキュメントの探し方がけっこうしんどくて、テーブル(DynamoDB)やS3バケットの逆引きなども作りながら、まだまだ試行錯誤の段階である。こういったことをやりだすと、どんどんドキュメントだけが太っていきそうな懸念がある。

昔から変わらないテーマなんだろうけど、未だ銀の弾丸はないんだろうなと、そんなことを、Slack初の値上げのニュースを見て思ったのであった。
ちなみにSlackはフロー型の情報ストックだと思うのだが、とにかく検索の応用の幅が広くて、過去のQ&Aとかも簡単に探せるので、今となっては巨大なナレッジベースの一翼になりつつある。もちろんそのためには有料プランを契約する必要があるけど。

というわけで特にオチはないけれど、この辺で。
同じような主張の記事も過去にあるだろうが(多数派かもしれないし少数派かもしれない)、何となくポエムとして書いてみた。

AWS CDKでLambda関数から別アカウントのS3にアクセスする

本記事はAWS CDK Advent Calendar 2021の14日目の記事です。

本記事では、AWS CDKを使って、「あるAWSアカウントにあるLambda関数から、別のAWSアカウントにあるS3バケットにオブジェクトをアップロードする」というユースケースを実現します。

ざっくりいうと、下記のナレッジに記載されているような仕組みを、CDK化してみた、というものになります(なので、出てくるロール名やアカウントIDは同じものを使って説明します)

aws.amazon.com

なお、今月の頭にAWS CDK v2.0.0がリリースされましたが、v1.124.0で動作確認した経験を元にしていますので、その点ご了承ください。

想定シナリオ

ステージング(stg)と本番環境(prd)のそれぞれでとあるワークロードを稼働させており、各環境でLambda関数内で処理を行って収集・加工したデータを、一元的に分析するために、別のアカウント(audit)のS3バケットにアップロードします。
…とまぁ、かなり濁して書いちゃっていますが、要するに、図のようなことをやりたいと想定してもらえればと思います。

stg

account (111111111111 = stg)         account (222222222222 = audit)
 Lambda(my-lambda)             =>   S3 Bucket (cdk-advent-calendar-21-14)

prd

account (333333333333 = prd)         account (222222222222 = audit)
 Lambda(my-lambda)             =>   S3 Bucket (cdk-advent-calendar-21-14)

コードをどう管理するか?

上の図の通り、111111111111(stg)と333333333333(prd)はコードが共通利用できそうですが、それらと222222222222(audit)は扱うAWSリソース群がまったく別物なので、必然的に別のスタック定義をする必要があり、すなわち2つのスタック定義が必要です。

そのために、単一のCDKプロジェクトで実現する方法と、それぞれ別々のCDKプロジェクトで実現する方法の2つがありますが、今回は前者を選択しました。
結果的に、ロールのARN(やその一部となるAWSアカウントID)を相互で橋渡しする必要があり、単一プロジェクトの方が管理上の収まりがよかったためです。
なお、その代償(?)として、デプロイを行う際にオプションや引数を間違えて間違ったスタックをデプロイしてしまう事故のリスクが生まれますが、デプロイに関しては個人のローカル環境から行うことは禁止し、Jenkins, GNU Makeによって正しくない組み合わせでの利用ができないように担保することで、このリスクを回避しています。

111111111111(stg) & 333333333333(prd) 側のスタック定義

Lambda関数を実行する側のスタック定義は以下のようにしました。

import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import * as iam from '@aws-cdk/aws-iam';
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs';

export class HogeStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // #1
    const auditAccount = { accountId: '222222222222' }; // this.node.tryGetContext('auditAccount');

    // #2
    const role = new iam.Role(this, 'MyLambdaExecutionRole', {
      roleName: 'my-lambda-execution-role',
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')
      ]
    });

    // #3
    role.addToPoicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        resource: [`arn:aws:iam::${auditAccount.accountId}:role/role-on-source-account`],
        actions: ['sts:AssumeRole']
      });
    );

    const fn = new NodejsFunction(this, 'MyLambdaFunction', {
      entry: 'src/index.ts',
      handler: 'fooHandler',
      functionName: 'my-lambda',
      environment: {
        //  必要に応じて環境変数
        ASSUME_ROLE_ARN: `arn:aws:iam::${auditAccount.accountId}:role/role-on-source-account`
      },
      runtime: lambda.Runtime.NODEJS_14_X,
      memorySize: 512,
      role: role // #4
    });
  }
}

ポイントは以下の通りです。

  • #1. 別アカウントの情報を取得します。今回はべた書きしていますが、実際の例ではコメントアウト部分のように、各アカウントのID等のパラメータは cdk.json で共通定義し、Contextの仕組みを使って値を参照します。
  • #2. Lambda関数の実行ロールをスクラッチで定義します。通常、CDKを使って作成されるLambda関数の実行ロールはCDKが気を利かせて作成してくれるため、必要な権限だけを後付けするだけでよいのですが、ロール名が HogeStack-myLambda-12345678ABCD のように末尾にオマケがついてしまいます。これだと、もう一方のスタックから参照させる場合に都合が悪かったので、ロール名決め打ちの実行ロールを使うようにしました。
  • #3. 別のアカウント(222222222222)でIAMロールを引き受けることを許可する権限を追加で与えます。
  • #4. 作成した実行ロールをLambda関数に割り当てます。

222222222222(audit)側のスタック定義

S3を配置する側のスタック定義は以下のようにしました。

import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import * as iam from '@aws-cdk/aws-iam';
import * as s3from '@aws-cdk/aws-s3';

export class FugaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, 'Cdk2014Bucket', {
      bucketName: 'cdk-advent-calendar-21-14',
      removalPolicy: cdk.RemovalPolicy.RETAIN
    });

    // #5
    const role = new iam.Role(this, 'RoleOnSourceAccount', {
      roleName: 'role-on-source-account',
      assumedBy: new iam.CompositePrincipal(
        new iam.ArnPrincipal('arn:aws:iam::111111111111:role/my-lambda-execution-role'),
        new iam.ArnPrincipal('arn:aws:iam::333333333333:role/my-lambda-execution-role')
      )
    });

    // #6
    bucket.grantReadWrite(role);
  }
}

ポイントは以下の通りです。

  • #5. 111111111111(stg), 333333333333(prd)側のLambda関数がロールを引き受けることを許可するようにして、ロールを作成します。ここでの assumedBy は、マネコンでの信頼関係を指すといった方がピンとくるかもしれません。今回、信頼したい別アカウントが2つ(複数)だったので、 CompositePrincipal というものを使いました。引数は可変長なので、3つ以上のアカウントを信頼したい場合にも対応できます(はず)。ただ、この部分については、アカウントごとに別のロールを定義してもよいかもしれません。
  • #6. ロールにS3バケットへの読み書き権限を付与します。

Lambda関数

冒頭に紹介したナレッジ記事ではLambda関数のランタイムはPythonですが、プロジェクトの事情でLambda関数はNode.js(TypeScript)で実装しています。

要点だけですが、以下のようなコードになります。

export const fooHandler = async (event: any, context: any): Promise<any> => {
  const sts = new AWS.STS({ apiVersion: '2011-06-15' });

  const assumeRoleRequest: AWS.STS.AssumeRoleRequest = {
    RoleArn: process.env.ASSUME_ROLE_ARN,
    RoleSessionName: new Date().getTime().toString()
  };

  const assumeRoleResponse = await sts.assumeRole(assumeRoleRequest).promise();
  const credentials = assumeRoleResponse.Credentials;

  const s3 = new AWS.S3({
    apiVersion: '2006-03-01',
    accessKeyId: credentials.AccessKeyId,
    secretAccessKey: credentials.SecretAccessKey,
    sessionToken: credentials.SessionToken
  });

  await s3.putObject({
    Bucket: '.....',
    Key: '.....',
    Body: '.....'
  });
};

AWS STS AssumeRole API を呼び出して、サービスクライアントの作成に使用できる認証情報を取得することで、S3にアクセスするための許可を引き受けたことになって、別アカウントへのS3アカウントができる、という寸法ですね。

デプロイ

最後はデプロイですが、ここまでできていればあと一息なので簡単に。
複数スタックが定義されているため、どのスタックをデプロイするか、明示的に指定してデプロイします。

$ AWS_PROFILE=stg npx cdk deploy HogeStack
$ AWS_PROFILE=prd npx cdk deploy HogeStack
$ AWS_PROFILE=audit npx cdk deploy FugaStack

おわりに

というわけで、AWS CDKでのクロスアカウントアクセスについてでした。

改めて整理しましたが、だいぶ力技が多いなと思ってしまいました…。
実際に開発している最中は、 cdk synth で出力されるCFnテンプレートと突き合わせながら、四苦八苦した記憶が蘇ります…。
特に、今回紹介したようなスタックは、頻繁にデプロイする必要性が薄いため、たまにデプロイした時にあまりに力技・黒魔術が多いと、動かすの怖くなってしまうんですよね。

余談ですが、今回みたいなユースケースが今後どれくらい発生するかはわかりませんが、CloudTrailなどの証跡ログは、監査用のアカウント/S3バケットに集約することがセキュリティの上での一つのプラクティスとしても挙げられており、「別アカウントのS3にデータを集める」という選択肢は、多く用意しておいて損はないかなと思ったりはします。
まぁ、この場合は、このあたりの仕組みを使えば、今回紹介するような方法は不適合かもしれませんが…。

もし間違いやツッコミがあれば、コメントか、 Twitter: @tq_jappy までお願いします。

Lightning Web Component開発で有用だったサイト~JavaScript(ECMAScript)編~

Lightning Web Component(LWC)を開発していく上で、ある程度JS側で混み入った実装が必要になりそうな時に、事前に目を通しておくと便利だったろうなと思うページを紹介します。

LWCでの最新のESXX機能の活用

developer.salesforce.com

Developer Blogの1エントリに留まらせずに、LWCの公式ページに載せてほしいなと思うくらい(多分言及されているのでしょうが…)、知っておきたかった情報が要約されています。

見ておくことで、「複数ブラウザ対応を考慮した実装する上で、どの機能が使えるのか?」が大枠アタリをつけられるようになります。

VisualforceでIE9対応ページ(もちろんSalesforce Classic…)を開発していた身からすると、未サポートなAPIを使ってしまわないよう、かなり敏感になっちゃってるんですよね…。

Locker API Viewer

developer.salesforce.com

標準の DOM API と Locker API のインデックスです。

IntersectionObserverを使おうとして、使えなくて残念な思いをしたので、先にこのページをチェックする癖をつけておけばよかったと思いました。