@exabugs/dynamodb-client 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/dist/notify/ops/index.d.ts +10 -0
- package/dist/notify/ops/index.d.ts.map +1 -0
- package/dist/notify/ops/index.js +9 -0
- package/dist/notify/ops/index.js.map +1 -0
- package/dist/notify/ops-notify.d.ts +106 -0
- package/dist/notify/ops-notify.d.ts.map +1 -0
- package/dist/notify/ops-notify.js +205 -0
- package/dist/notify/ops-notify.js.map +1 -0
- package/dist/notify/store.d.ts +5 -0
- package/dist/notify/store.d.ts.map +1 -1
- package/dist/notify/store.js +9 -2
- package/dist/notify/store.js.map +1 -1
- package/dist/server/handler.cjs +3 -3
- package/dist/server/media-handler.cjs +2 -2
- package/dist/server/media-process-handler-dlq.cjs +2 -2
- package/dist/server/media-process-handler.cjs +2 -2
- package/docs/DYNAMODB_ACCESS.md +1 -1
- package/docs/NOTIFY.md +53 -2
- package/docs/SYSTEM_CHANGES.md +1 -1
- package/package.json +6 -1
- package/terraform/README.md +9 -0
- package/terraform/alerts-cleanup/README.md +65 -0
- package/terraform/alerts-cleanup/main.tf +111 -0
- package/terraform/alerts-cleanup/outputs.tf +14 -0
- package/terraform/alerts-cleanup/variables.tf +42 -0
- package/terraform/alerts-cleanup/versions.tf +16 -0
- package/terraform/ops-notify/README.md +109 -0
- package/terraform/ops-notify/main.tf +295 -0
- package/terraform/ops-notify/outputs.tf +19 -0
- package/terraform/ops-notify/us-east-1.tf +68 -0
- package/terraform/ops-notify/variables.tf +105 -0
- package/terraform/ops-notify/versions.tf +18 -0
- package/terraform/system-change-recorder/README.md +71 -0
- package/terraform/system-change-recorder/main.tf +122 -0
- package/terraform/system-change-recorder/outputs.tf +14 -0
- package/terraform/system-change-recorder/variables.tf +53 -0
- package/terraform/system-change-recorder/versions.tf +16 -0
package/docs/NOTIFY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 運用通知(`@exabugs/dynamodb-client/notify`)
|
|
2
2
|
|
|
3
|
-
ログ(`warn` 以上)・CloudWatch
|
|
3
|
+
ログ(`warn` 以上)・CloudWatch アラーム・直接呼び出しを、管理者へ通知するための**部品**。配線のうち、Lambda のエントリと秘密の置き場は各アプリが行う。購読フィルタ・Lambda・テーブルの Terraform は `terraform/ops-notify`(1.21.0)。既存の挙動は変えない(サブパス `./notify` の追加のみ)。
|
|
4
4
|
|
|
5
5
|
```text
|
|
6
6
|
ログ(購読フィルタ) ─┐
|
|
@@ -119,6 +119,57 @@ export const handler = createNotifyHandler({
|
|
|
119
119
|
});
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
### 通知 Lambda のエントリ(`createOpsNotifyHandler`。1.22.0)
|
|
123
|
+
|
|
124
|
+
`terraform/ops-notify` の Lambda のエントリは、ハンドラのファクトリを export するだけでよい。上の組み立て(環境変数の読み取り、Slack〔SSM の Webhook〕・メール〔SES v2〕のチャネル、通知ストア、管理者の一覧、alerts の記録)をライブラリが行う。asanowa・astro のエントリ(`functions/ops-notify/src/index.ts`・`apps/server/src/notify.ts`)を 1 つにしたもの:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { createOpsNotifyHandler } from '@exabugs/dynamodb-client/notify/ops';
|
|
128
|
+
|
|
129
|
+
export const handler = createOpsNotifyHandler();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- 環境変数(`terraform/ops-notify` が設定する。`readOpsNotifyConfig` が読む): `PROJECT_NAME`・`ENV_NAME`・`RECORDS_IAM_URL`・`NOTICE_TABLE`(必須。無ければ `<名前> environment variable is required`)、`SLACK_WEBHOOK_PARAM`・`MAIL_FROM`(空なら、そのチャネルを作らない)、`SES_REGION`(既定 `us-east-1`)、`DRY_RUN=true`、`ADMIN_BASE_URL`(あるときだけ alerts に記録する)、`SKIP_RECORD_LOG_GROUPS`(カンマ区切り。`ADMIN_BASE_URL` があって空なら warn `ALERTS_SKIP_LOG_GROUPS_EMPTY`)。リージョンは `AWS_REGION`(無ければ `REGION`。どちらも無ければ例外)
|
|
133
|
+
- ハンドラは最初の呼び出しで組み立て、コンテナの中で使い回す(遮断・管理者の一覧のキャッシュを保つ)。組み立てに失敗したら、次の呼び出しでやり直す
|
|
134
|
+
- 既定: `failOnAnyChannelError: true`(一部のチャネルの失敗も Lambda の失敗先へ)、記録の上限 `OPS_NOTIFY_RECORD_LIMITS`(3000 ms・50 件・並列 5)、遮断 `OPS_NOTIFY_RECORD_BREAKER`(3 回・60 秒)、alerts の書き込みの HTTP の時間切れ `OPS_NOTIFY_RECORD_HTTP_TIMEOUT_MS`(2500 ms。記録の上限より短く)
|
|
135
|
+
- Webhook の URL は SSM(SecureString)から最初の送信で読み、実行環境の間はキャッシュする
|
|
136
|
+
- **サブパスは `notify/ops`**(`notify` 本体と分ける)。SSM・SES の SDK(`@aws-sdk/client-ssm`・`@aws-sdk/client-sesv2`。どちらもライブラリの依存)を静的に import するので、`notify` 本体(alerts-cleanup など)は SDK を読まないまま。メールの送信は、ライブラリが既定で SES v2 を使う(`sesSender` に SDK を渡す形は、`createNotifyHandler` を直接使うときのもの)
|
|
137
|
+
- バンドル(どちらも CJS。`src/notify/ops/__tests__/bundle.test.ts` で固定):
|
|
138
|
+
- SDK を外部にする(asanowa: `esbuild --bundle --platform=node --format=cjs --external:@aws-sdk/*`): SDK は `require("@aws-sdk/…")` のまま残り、Lambda の実行環境の SDK(NODE_PATH)で解決される。動的 `import()` は残らない
|
|
139
|
+
- SDK も同梱する(astro: `--external` なし): ライブラリの依存の版の SDK がバンドルに入る
|
|
140
|
+
- alerts の書き込み先は Records のコレクション(`lazyCollection`。最初の書き込みで接続し、使い回す)。管理者の一覧は呼び出しごとに接続して閉じる(`withCollection`)
|
|
141
|
+
|
|
142
|
+
オプション(どれも任意):
|
|
143
|
+
|
|
144
|
+
| オプション | 既定 | 内容 |
|
|
145
|
+
| --------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
146
|
+
| `app` | `PROJECT_NAME` | 通知の `app`。渡せば `PROJECT_NAME` は不要 |
|
|
147
|
+
| `region` | `AWS_REGION` | 通知ストア・SSM・Records・ログ参照のリージョン |
|
|
148
|
+
| `admins` | 宛先は `id`、生のコレクション | `createAdminDirectory` の設定(`recipientFields`・`sortById`・`maxAdmins`・`collection`・`timeout`)と、`lister: (raw) => AdminsLister`(読み取りを権限チェック層で包む)。url・region はハンドラが渡す |
|
|
149
|
+
| `alertsCollection` | 生のコレクション | `(raw) => AlertsCollection`。alerts の書き込みを自分の層で包むアプリが使う |
|
|
150
|
+
| `recordHttpTimeoutMs` | 2500 | alerts の書き込みの HTTP の時間切れ |
|
|
151
|
+
| `log` | `logJson` | 設定の誤りの警告(`ALERTS_SKIP_LOG_GROUPS_EMPTY`)の出力先 |
|
|
152
|
+
| `notify` | 上の既定 | `createNotifyHandler` に渡す設定(`windows`・`rate`・`adminCacheSec`・`failOnAnyChannelError`・`now`・`logger`・`skipRecordEvents`・`recordLimits`・`recordBreaker`) |
|
|
153
|
+
| `overrides` | なし | テスト用の差し替え(`store`・`channels`・`admins`・`alertsCollection`・`ssmClient`・`emailSender`) |
|
|
154
|
+
|
|
155
|
+
アプリごとの書き方(現行の挙動を再現する):
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// asanowa: 既定のまま(管理者は id、生のコレクション)
|
|
159
|
+
export const handler = createOpsNotifyHandler();
|
|
160
|
+
|
|
161
|
+
// astro: 管理者は email ?? id・id の昇順で、読み取りを権限チェック層に通す
|
|
162
|
+
export const handler = createOpsNotifyHandler({
|
|
163
|
+
admins: {
|
|
164
|
+
recipientFields: ['email', 'id'],
|
|
165
|
+
sortById: true,
|
|
166
|
+
lister: (raw) => authorizedCollection(ADMINS, raw, PERMISSION_MATRIX, { role: 'system' }),
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
アプリに残るもの: エントリの数行(と、権限チェック層を通すアプリの包み方)、バンドルの作成、`terraform/ops-notify` の module ブロック。IAM は `terraform/ops-notify` が付けるもの(notices の UpdateItem、Records の IAM URL、SSM の GetParameter、SES の送信)で足りる。
|
|
172
|
+
|
|
122
173
|
## 配線の注意(各アプリ)
|
|
123
174
|
|
|
124
175
|
- **通知ハンドラのロググループは購読しない**(自分のログで無限ループになる)。失敗は Lambda の失敗先(SNS など)→ 固定の運用者のアドレスへ届ける
|
|
@@ -177,7 +228,7 @@ export const handler = createNotifyHandler({
|
|
|
177
228
|
- 環境変数: 既存の `NOTICE_TABLE` / `RECORDS_IAM_URL` に加えて、`ADMIN_BASE_URL`(任意。無ければ URL を載せない)
|
|
178
229
|
- IAM: 通知ストアへは `dynamodb:UpdateItem` だけ(既存の権限のまま。`GetItem` は要らない)。Records の IAM URL の呼び出し(`lambda:InvokeFunctionUrl`)は、管理者の一覧の取得で既に持っている
|
|
179
230
|
- 同時実行数: 同じレコードへの同時の書き込みはシャドーを孤立させうるので、**記録を有効にするときだけ**、通知 Lambda の予約同時実行数を 1 にする(今の通知 Lambda は未設定。入れると、記録なしでも遅れ方が変わる)。直列化は努力目標で、強制はされない(打ち切った HTTP は止まらない・削除の定時処理と重なる・管理画面から書ける)。孤立したシャドーは `src/scripts/repair-shadows.ts` で修復できる
|
|
180
|
-
-
|
|
231
|
+
- Terraform は `terraform/ops-notify`・`terraform/alerts-cleanup`(1.21.0。[README](../terraform/ops-notify/README.md))。Lambda の成果物(エントリ)と、秘密の置き場は各アプリ
|
|
181
232
|
|
|
182
233
|
### 削除(定時 Lambda)
|
|
183
234
|
|
package/docs/SYSTEM_CHANGES.md
CHANGED
|
@@ -183,4 +183,4 @@ ADR 0006 の段階 2 の手順: astro で先に置き換えて dev で確認し
|
|
|
183
183
|
- Slack の文言は asanowa に揃えた。astro の「記録を省いた Lambda」は「ほかに記録しきれなかった Lambda」になる
|
|
184
184
|
- Slack の送信は `postSlackText` を通る: webhook の URL が `https://hooks.slack.com/` で始まらないと送らない(`slack.urlPrefix`)。失敗の warn の理由は従来と同じ `HTTP 500` やエラー名(時間切れは `AbortError` ではなく `TimeoutError`)
|
|
185
185
|
- asanowa の `changeId` の読み取りは `findOne`(404 を null にする)から `find({ id }, { sort: { id: 'asc' }, limit: 1 })`(Records の ID の経路。PK/SK の Query 1 回) に変わる。Records の ITEM_NOT_FOUND の warn が出なくなる
|
|
186
|
-
- 記録 Lambda の Terraform
|
|
186
|
+
- 記録 Lambda の Terraform は `terraform/system-change-recorder`(1.21.0。段階 6。[README](../terraform/system-change-recorder/README.md))。成果物はアプリのバンドルのまま(`source_file` で渡す)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exabugs/dynamodb-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "DynamoDB Single-Table Client SDK with MongoDB-like API, Shadow Records, and Lambda implementation for serverless applications",
|
|
5
5
|
"author": "exabugs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -127,6 +127,10 @@
|
|
|
127
127
|
"import": "./dist/notify/index.js",
|
|
128
128
|
"types": "./dist/notify/index.d.ts"
|
|
129
129
|
},
|
|
130
|
+
"./notify/ops": {
|
|
131
|
+
"import": "./dist/notify/ops/index.js",
|
|
132
|
+
"types": "./dist/notify/ops/index.d.ts"
|
|
133
|
+
},
|
|
130
134
|
"./system-changes": {
|
|
131
135
|
"import": "./dist/system-changes/index.js",
|
|
132
136
|
"types": "./dist/system-changes/index.d.ts"
|
|
@@ -173,6 +177,7 @@
|
|
|
173
177
|
"@aws-crypto/sha256-js": "5.2.0",
|
|
174
178
|
"@aws-sdk/client-dynamodb": "3.946.0",
|
|
175
179
|
"@aws-sdk/client-s3": "3.946.0",
|
|
180
|
+
"@aws-sdk/client-sesv2": "3.946.0",
|
|
176
181
|
"@aws-sdk/client-ssm": "3.946.0",
|
|
177
182
|
"@aws-sdk/cloudfront-signer": "3.946.0",
|
|
178
183
|
"@aws-sdk/credential-provider-node": "3.946.0",
|
package/terraform/README.md
CHANGED
|
@@ -219,6 +219,15 @@ make logs-records ENV=dev
|
|
|
219
219
|
aws logs tail /aws/lambda/my-project-dev-records --follow
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
+
## 運用のモジュール(1.21.0)
|
|
223
|
+
|
|
224
|
+
Records のクライアントとして動く運用の Lambda を、別のモジュールとして持つ(ADR 0006 の (b))。Lambda の成果物(エントリ)はアプリが作り、`source_file` で渡す。
|
|
225
|
+
|
|
226
|
+
- [ops-notify](ops-notify/README.md): 運用通知(ログ・アラーム・生存確認 → Slack とメール。alerts の記録)
|
|
227
|
+
- [alerts-cleanup](alerts-cleanup/README.md): alerts の定時削除
|
|
228
|
+
- [system-change-recorder](system-change-recorder/README.md): システム変更履歴の記録
|
|
229
|
+
- [media](media/README.md): メディア(アップロード・処理・配信)
|
|
230
|
+
|
|
222
231
|
## 関連ドキュメント
|
|
223
232
|
|
|
224
233
|
- [パッケージREADME](../README.md) - クライアントライブラリの使用方法
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# alerts の定時削除 Lambda Terraformモジュール
|
|
2
|
+
|
|
3
|
+
ADR 0006 の (b)(1.21.0)。asanowa・astro の `infra/modules/alerts-cleanup` を 1 つにしたもの。使い方の詳細は [docs/NOTIFY.md](../../docs/NOTIFY.md) の「削除(定時 Lambda)」。
|
|
4
|
+
|
|
5
|
+
## 作るもの
|
|
6
|
+
|
|
7
|
+
- Lambda `<project>-<env>-alerts-cleanup`(Node.js 22、arm64、120 秒、256 MB。構造化ログ JSON)と、そのロググループ・実行ロール
|
|
8
|
+
- EventBridge の定時ルール(既定 毎日 03:00 JST)とターゲット(再試行 0)、呼び出しの許可
|
|
9
|
+
- 権限: Records の IAM 版 URL の呼び出しだけ。DynamoDB には直接触れない
|
|
10
|
+
- 環境変数: `PROJECT_NAME`(`createAlertsCleanupHandler` の `app` の既定)、`RECORDS_IAM_URL`
|
|
11
|
+
|
|
12
|
+
## 前提
|
|
13
|
+
|
|
14
|
+
- 参照は `node_modules` のパス(版は npm の版で固定される。git の `ref` ではない)。例: アプリの infra が `infra/` なら `source = "../node_modules/@exabugs/dynamodb-client/terraform/alerts-cleanup"`、`infra/apps/<app>/` なら `"../../../node_modules/..."`
|
|
15
|
+
- **Lambda の成果物はアプリが作り、`source_file`(バンドル済みの 1 ファイル)と `handler` で渡す**。エントリはアプリの数行(`createAlertsCleanupHandler`。アプリ名や、権限チェック層を通すアプリの包み方を渡す)なので、ライブラリの dist は使わない
|
|
16
|
+
- zip は `${path.root}/.terraform/archives/alerts-cleanup.zip` に作る(`path.module` を使わない。pnpm の版つきのパスが state に入ると、版を上げるたびに差分が出るため)
|
|
17
|
+
- pnpm では、版を上げた後に `.terraform/modules` を消してから `terraform init` する([../README.md](../README.md) の注意)
|
|
18
|
+
- モジュール全体の `depends_on` は使わない(依存先に変更があると、中のデータソースが apply 時の読み取りになり、既存のリソースが作り直しになる)。依存は、出力の値(`log_group_name` など)で渡す
|
|
19
|
+
|
|
20
|
+
## 使い方
|
|
21
|
+
|
|
22
|
+
```hcl
|
|
23
|
+
module "alerts_cleanup" {
|
|
24
|
+
source = "../node_modules/@exabugs/dynamodb-client/terraform/alerts-cleanup"
|
|
25
|
+
|
|
26
|
+
project_name = var.project_name
|
|
27
|
+
environment = var.environment
|
|
28
|
+
records_iam_function_url = module.records.iam_function_url
|
|
29
|
+
records_iam_alias_arn = module.records.iam_alias_arn
|
|
30
|
+
log_retention_days = var.log_retention_days
|
|
31
|
+
|
|
32
|
+
source_file = "${path.root}/../apps/server/dist/alerts-cleanup.cjs"
|
|
33
|
+
handler = "alerts-cleanup.handler"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 変数
|
|
38
|
+
|
|
39
|
+
| 変数 | 既定 | 説明 |
|
|
40
|
+
| ---------------------------------------------------- | -------------------- | ------------------------------------------------------- |
|
|
41
|
+
| `project_name` / `environment` | 必須 | 名前の接頭辞 `<project>-<env>`。環境変数 `PROJECT_NAME` |
|
|
42
|
+
| `records_iam_function_url` / `records_iam_alias_arn` | 必須 | Records の IAM 版 Function URL と、そのエイリアスの ARN |
|
|
43
|
+
| `source_file` / `handler` | 必須 | アプリのバンドル(1 ファイル)と、ハンドラ |
|
|
44
|
+
| `schedule_expression` | `cron(0 18 * * ? *)` | 実行間隔(毎日 03:00 JST) |
|
|
45
|
+
| `log_retention_days` | 7 | ロググループの保持日数 |
|
|
46
|
+
|
|
47
|
+
## 出力
|
|
48
|
+
|
|
49
|
+
`function_name`、`function_arn`、`log_group_name`(ops-notify に購読させる)
|
|
50
|
+
|
|
51
|
+
## アプリに残るもの
|
|
52
|
+
|
|
53
|
+
- 上の module ブロック(`source`・`source_file`・`handler` と設定の値)
|
|
54
|
+
- Lambda のエントリ(`createAlertsCleanupHandler` を呼ぶ数行)とバンドルの作成(esbuild など)
|
|
55
|
+
- 秘密(webhook の URL など)の SSM パラメータの作成
|
|
56
|
+
|
|
57
|
+
## アプリのモジュールからの移行(置き換えを起こさない)
|
|
58
|
+
|
|
59
|
+
リソースのアドレス・名前は、asanowa / astro の旧モジュール(`infra/modules/alerts-cleanup`)と同じ。**module ブロックの名前(`module "alerts_cleanup"`)を変えずに `source` だけを差し替え**、`source_file`・`handler` を足す。アドレスが変わらないので `moved` は要らない(ラッパーのモジュールで包むと、アドレスが変わり `moved` が要る)。
|
|
60
|
+
|
|
61
|
+
1. `terraform init`(モジュールの取り直し)→ `terraform plan`
|
|
62
|
+
2. **0 to add / 0 to destroy、`must be replaced` が無い**ことを確かめる。出てよいのは in-place の更新だけ(文言の差。astro ではルールの description が英語の文言に、Lambda に `PROJECT_NAME` が加わる)
|
|
63
|
+
3. 確認のうえ apply。旧モジュールのディレクトリを消す
|
|
64
|
+
|
|
65
|
+
詳細と実機の確認は [ADR 0006](../../docs/adr/0006-library-owns-table-operations.md) の段階 6。
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# 運用通知の記録(alerts)の定時削除(ADR 0003・ADR 0006 の (b)。docs/NOTIFY.md の「削除(定時 Lambda)」)
|
|
2
|
+
# 最後の発生から 28 日を過ぎた alerts を、定時に消す(ネイティブ TTL は効かない)。
|
|
3
|
+
# Lambda の成果物はアプリが作り、source_file で渡す(実装は `createAlertsCleanupHandler`)。
|
|
4
|
+
#
|
|
5
|
+
# 失敗は構造化ログ(error)に出す。このロググループは、通知(../ops-notify)に購読させる。
|
|
6
|
+
# リソースのアドレス・名前は、asanowa / astro の旧モジュール(infra/modules/alerts-cleanup)と同じ。
|
|
7
|
+
|
|
8
|
+
locals {
|
|
9
|
+
prefix = "${var.project_name}-${var.environment}"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
data "archive_file" "cleanup" {
|
|
13
|
+
type = "zip"
|
|
14
|
+
source_file = var.source_file
|
|
15
|
+
output_path = "${path.root}/.terraform/archives/alerts-cleanup.zip"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
resource "aws_cloudwatch_log_group" "cleanup" {
|
|
19
|
+
name = "/aws/lambda/${local.prefix}-alerts-cleanup"
|
|
20
|
+
retention_in_days = var.log_retention_days
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
resource "aws_iam_role" "cleanup" {
|
|
24
|
+
name = "${local.prefix}-alerts-cleanup-role"
|
|
25
|
+
|
|
26
|
+
assume_role_policy = jsonencode({
|
|
27
|
+
Version = "2012-10-17"
|
|
28
|
+
Statement = [{
|
|
29
|
+
Action = "sts:AssumeRole"
|
|
30
|
+
Effect = "Allow"
|
|
31
|
+
Principal = { Service = "lambda.amazonaws.com" }
|
|
32
|
+
}]
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
resource "aws_iam_role_policy_attachment" "cleanup_logs" {
|
|
37
|
+
role = aws_iam_role.cleanup.name
|
|
38
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Records Lambda の IAM 認証版 URL(エイリアス)を呼ぶ権限だけ(最小権限)。DynamoDB には直接触れない
|
|
42
|
+
resource "aws_iam_role_policy" "cleanup_invoke_records" {
|
|
43
|
+
name = "${local.prefix}-alerts-cleanup-invoke-records"
|
|
44
|
+
role = aws_iam_role.cleanup.id
|
|
45
|
+
|
|
46
|
+
policy = jsonencode({
|
|
47
|
+
Version = "2012-10-17"
|
|
48
|
+
Statement = [{
|
|
49
|
+
Effect = "Allow"
|
|
50
|
+
Action = ["lambda:InvokeFunctionUrl"]
|
|
51
|
+
Resource = [var.records_iam_alias_arn]
|
|
52
|
+
}]
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
resource "aws_lambda_function" "cleanup" {
|
|
57
|
+
function_name = "${local.prefix}-alerts-cleanup"
|
|
58
|
+
role = aws_iam_role.cleanup.arn
|
|
59
|
+
filename = data.archive_file.cleanup.output_path
|
|
60
|
+
source_code_hash = data.archive_file.cleanup.output_base64sha256
|
|
61
|
+
handler = var.handler
|
|
62
|
+
runtime = "nodejs22.x"
|
|
63
|
+
architectures = ["arm64"]
|
|
64
|
+
timeout = 120
|
|
65
|
+
memory_size = 256
|
|
66
|
+
|
|
67
|
+
environment {
|
|
68
|
+
# PROJECT_NAME は createAlertsCleanupHandler の app の既定(app を渡すアプリは読まない)
|
|
69
|
+
variables = {
|
|
70
|
+
PROJECT_NAME = var.project_name
|
|
71
|
+
RECORDS_IAM_URL = var.records_iam_function_url
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
logging_config {
|
|
76
|
+
log_format = "JSON"
|
|
77
|
+
log_group = aws_cloudwatch_log_group.cleanup.name
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
depends_on = [aws_iam_role_policy_attachment.cleanup_logs]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# 再試行は 0(翌日の定時実行で残りを消す。失敗はログ → 通知で知る)
|
|
84
|
+
resource "aws_lambda_function_event_invoke_config" "cleanup" {
|
|
85
|
+
function_name = aws_lambda_function.cleanup.function_name
|
|
86
|
+
maximum_retry_attempts = 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
resource "aws_cloudwatch_event_rule" "cleanup" {
|
|
90
|
+
name = "${local.prefix}-alerts-cleanup"
|
|
91
|
+
description = "Delete ops alerts records older than 28 days (daily 03:00 JST)"
|
|
92
|
+
schedule_expression = var.schedule_expression
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resource "aws_cloudwatch_event_target" "cleanup" {
|
|
96
|
+
rule = aws_cloudwatch_event_rule.cleanup.name
|
|
97
|
+
arn = aws_lambda_function.cleanup.arn
|
|
98
|
+
|
|
99
|
+
retry_policy {
|
|
100
|
+
maximum_retry_attempts = 0
|
|
101
|
+
maximum_event_age_in_seconds = 3600
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
resource "aws_lambda_permission" "cleanup_events" {
|
|
106
|
+
statement_id = "AllowEventBridgeInvoke"
|
|
107
|
+
action = "lambda:InvokeFunction"
|
|
108
|
+
function_name = aws_lambda_function.cleanup.function_name
|
|
109
|
+
principal = "events.amazonaws.com"
|
|
110
|
+
source_arn = aws_cloudwatch_event_rule.cleanup.arn
|
|
111
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
output "function_name" {
|
|
2
|
+
description = "alerts の定時削除 Lambda の関数名"
|
|
3
|
+
value = aws_lambda_function.cleanup.function_name
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "log_group_name" {
|
|
7
|
+
description = "alerts の定時削除 Lambda のロググループ名(通知 Lambda が購読する)"
|
|
8
|
+
value = aws_cloudwatch_log_group.cleanup.name
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
output "function_arn" {
|
|
12
|
+
description = "alerts の定時削除 Lambda の ARN"
|
|
13
|
+
value = aws_lambda_function.cleanup.arn
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
variable "project_name" {
|
|
2
|
+
description = "プロジェクト名"
|
|
3
|
+
type = string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
variable "environment" {
|
|
7
|
+
description = "環境識別子(dev, prd)"
|
|
8
|
+
type = string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
variable "records_iam_function_url" {
|
|
12
|
+
description = "Records Lambda の AWS_IAM 認証版 Function URL(alerts を読み、消す)"
|
|
13
|
+
type = string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
variable "records_iam_alias_arn" {
|
|
17
|
+
description = "Records Lambda の IAM 認証用エイリアスの ARN(lambda:InvokeFunctionUrl の Resource)"
|
|
18
|
+
type = string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
variable "schedule_expression" {
|
|
22
|
+
description = "実行間隔(既定: 毎日 03:00 JST = 18:00 UTC)"
|
|
23
|
+
type = string
|
|
24
|
+
default = "cron(0 18 * * ? *)"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
variable "log_retention_days" {
|
|
28
|
+
description = "CloudWatch Logsの保持期間(日数)"
|
|
29
|
+
type = number
|
|
30
|
+
default = 7
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
variable "source_file" {
|
|
34
|
+
description = "Lambda の成果物(バンドル済みの 1 ファイル。例: apps/server/dist/alerts-cleanup.cjs)。zip にして使う"
|
|
35
|
+
type = string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
variable "handler" {
|
|
39
|
+
description = "Lambda のハンドラ(例: alerts-cleanup.handler)"
|
|
40
|
+
type = string
|
|
41
|
+
}
|
|
42
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# provider の版: astro(5.x)と asanowa(6.x)の両方で使う。
|
|
2
|
+
# 下限は Lambda の logging_config(5.x の途中で追加)。上限は data.aws_region の name(6.x で非推奨)が消える 7.x の前まで
|
|
3
|
+
terraform {
|
|
4
|
+
required_version = ">= 1.5"
|
|
5
|
+
|
|
6
|
+
required_providers {
|
|
7
|
+
aws = {
|
|
8
|
+
source = "hashicorp/aws"
|
|
9
|
+
version = ">= 5.30, < 7.0"
|
|
10
|
+
}
|
|
11
|
+
archive = {
|
|
12
|
+
source = "hashicorp/archive"
|
|
13
|
+
version = ">= 2.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 運用通知 Terraformモジュール
|
|
2
|
+
|
|
3
|
+
ADR 0006 の (b)(1.21.0)。asanowa・astro の `infra/modules/ops-notify` を 1 つにしたもの。使い方の詳細は [docs/NOTIFY.md](../../docs/NOTIFY.md)。
|
|
4
|
+
|
|
5
|
+
## 作るもの
|
|
6
|
+
|
|
7
|
+
- 通知 Lambda `<project>-<env>-notify`(Node.js 22、arm64、60 秒、256 MB。構造化ログ JSON)と、そのロググループ・実行ロール
|
|
8
|
+
- 重複抑制・流量制限の記録のテーブル `<project>-<env>-notices`(pk、TTL `ttl`。Records の本体テーブルではない内部用)
|
|
9
|
+
- 失敗先の SNS トピック `<project>-<env>-notify-failure`(運用者のメールの購読つき)。非同期の再試行 0
|
|
10
|
+
- 入口 1: `watched_log_group_names` のロググループの購読(広めの語句。通知するかはハンドラが判定する)
|
|
11
|
+
- 入口 2: CloudWatch アラームの状態変化(名前が `<project>-<env>-` で始まるもの)
|
|
12
|
+
- 入口 3: 生存確認(既定 週 1 回)
|
|
13
|
+
- us-east-1 のアラーム(CloudFront など)の転送(`forward_us_east_1_alarms = true` のときだけ): us-east-1 の EventBridge ルールと、ホームのリージョンの default バスへ送るロール。転送されたイベントは入口 2 のルールが拾う
|
|
14
|
+
- 権限: notices の `UpdateItem`、Records の IAM 版 URL の呼び出し、webhook のパラメータの読み取り、失敗先の SNS、SES(`mail_from` があるときだけ。送信元アドレスとドメインに絞る)
|
|
15
|
+
|
|
16
|
+
## 前提
|
|
17
|
+
|
|
18
|
+
- 参照は `node_modules` のパス(版は npm の版で固定される。git の `ref` ではない)。例: アプリの infra が `infra/` なら `source = "../node_modules/@exabugs/dynamodb-client/terraform/ops-notify"`、`infra/apps/<app>/` なら `"../../../node_modules/..."`
|
|
19
|
+
- **Lambda の成果物はアプリが作り、`source_file`(バンドル済みの 1 ファイル)と `handler` で渡す**。エントリはアプリの数行(1.22.0 からは `@exabugs/dynamodb-client/notify/ops` の `createOpsNotifyHandler()`。権限チェック層を通すアプリは包み方を渡す。[docs/NOTIFY.md](../../docs/NOTIFY.md) の「通知 Lambda のエントリ」)なので、ライブラリの dist は使わない
|
|
20
|
+
- zip は `${path.root}/.terraform/archives/notify.zip` に作る(`path.module` を使わない。pnpm の版つきのパスが state に入ると、版を上げるたびに差分が出るため)
|
|
21
|
+
- pnpm では、版を上げた後に `.terraform/modules` を消してから `terraform init` する([../README.md](../README.md) の注意)
|
|
22
|
+
- モジュール全体の `depends_on` は使わない(依存先に変更があると、中のデータソースが apply 時の読み取りになり、既存のリソースが作り直しになる)。依存は、出力の値(`log_group_name` など)で渡す
|
|
23
|
+
|
|
24
|
+
## 使い方
|
|
25
|
+
|
|
26
|
+
```hcl
|
|
27
|
+
module "ops_notify" {
|
|
28
|
+
source = "../node_modules/@exabugs/dynamodb-client/terraform/ops-notify"
|
|
29
|
+
|
|
30
|
+
# 必須(forward_us_east_1_alarms が false でも)。モジュールが aws.us_east_1 の別名を宣言しているため
|
|
31
|
+
providers = {
|
|
32
|
+
aws.us_east_1 = aws.us_east_1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
project_name = var.project_name
|
|
36
|
+
environment = var.environment
|
|
37
|
+
records_iam_function_url = module.records.iam_function_url
|
|
38
|
+
records_iam_alias_arn = module.records.iam_alias_arn
|
|
39
|
+
log_retention_days = var.log_retention_days
|
|
40
|
+
|
|
41
|
+
source_file = "${path.root}/../apps/server/dist/notify.cjs"
|
|
42
|
+
handler = "notify.handler"
|
|
43
|
+
|
|
44
|
+
# 通知 Lambda 自身のロググループは入れない(無限ループ)
|
|
45
|
+
watched_log_group_names = [
|
|
46
|
+
module.records.log_group_name,
|
|
47
|
+
module.alerts_cleanup.log_group_name,
|
|
48
|
+
module.system_change_recorder.log_group_name,
|
|
49
|
+
]
|
|
50
|
+
slack_webhook_param_name = "/${var.project_name}/${var.environment}/ops/slack-webhook"
|
|
51
|
+
mail_from = var.ops_mail_from
|
|
52
|
+
ses_identity_domain = var.ops_ses_identity_domain
|
|
53
|
+
operator_email = var.ops_operator_email
|
|
54
|
+
|
|
55
|
+
# alerts(通知を管理画面のレコードとして残す)。空なら記録しない
|
|
56
|
+
admin_base_url = var.ops_admin_base_url
|
|
57
|
+
skip_record_log_groups = [module.records.log_group_name]
|
|
58
|
+
reserved_concurrent_executions = var.ops_admin_base_url == "" ? null : 1
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## provider
|
|
63
|
+
|
|
64
|
+
`aws`(ホームのリージョン)に加えて、**別名 `aws.us_east_1` を `providers` で必ず渡す**(`configuration_aliases`。転送を使わないときも要る。リージョンは us-east-1)。呼び出し側に無ければ、次を足す。
|
|
65
|
+
|
|
66
|
+
```hcl
|
|
67
|
+
provider "aws" {
|
|
68
|
+
alias = "us_east_1"
|
|
69
|
+
region = "us-east-1"
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 変数
|
|
74
|
+
|
|
75
|
+
| 変数 | 既定 | 説明 |
|
|
76
|
+
| ---------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
77
|
+
| `project_name` / `environment` | 必須 | 名前の接頭辞 `<project>-<env>`。環境変数 `PROJECT_NAME` / `ENV_NAME` |
|
|
78
|
+
| `records_iam_function_url` / `records_iam_alias_arn` | 必須 | Records の IAM 版 Function URL(管理者の一覧を読み、alerts を書く)と、そのエイリアスの ARN |
|
|
79
|
+
| `source_file` / `handler` | 必須 | アプリのバンドル(1 ファイル)と、ハンドラ |
|
|
80
|
+
| `watched_log_group_names` | 必須 | 購読するロググループ。通知 Lambda 自身のものは入れない |
|
|
81
|
+
| `slack_webhook_param_name` | 必須 | Slack の webhook の SSM パラメータ名(SecureString。値は Terraform で扱わない) |
|
|
82
|
+
| `operator_email` | 必須 | 通知 Lambda 自身の失敗の届け先(SNS の確認メールが届く) |
|
|
83
|
+
| `mail_from` / `ses_region` / `ses_identity_domain` | `""` / `us-east-1` / `""` | メールの送信元と SES。`mail_from` が空ならメールは送らない |
|
|
84
|
+
| `heartbeat_schedule` | `rate(7 days)` | 生存確認の間隔 |
|
|
85
|
+
| `log_retention_days` | 7 | ロググループの保持日数 |
|
|
86
|
+
| `admin_base_url` | `""` | 管理画面のベース URL。空でなければ alerts に記録し、通知に詳細の URL を載せる。空なら環境変数に入れない |
|
|
87
|
+
| `skip_record_log_groups` | `[]` | alerts に記録しないロググループ。**Records のロググループを必ず入れる**(ループ防止)。空なら環境変数に入れない |
|
|
88
|
+
| `forward_us_east_1_alarms` | `false` | us-east-1 のアラームの状態変化を、ホームのリージョンの default バスへ転送する |
|
|
89
|
+
| `reserved_concurrent_executions` | `null` | 予約同時実行数。alerts の記録を有効にするときだけ 1 |
|
|
90
|
+
|
|
91
|
+
## 出力
|
|
92
|
+
|
|
93
|
+
`function_name`、`function_arn`、`failure_topic_arn`、`notice_table_name`
|
|
94
|
+
|
|
95
|
+
## アプリに残るもの
|
|
96
|
+
|
|
97
|
+
- 上の module ブロック(`source`・`providers`・`source_file`・`handler` と設定の値)
|
|
98
|
+
- Lambda のエントリ(`createOpsNotifyHandler` を export する数行)とバンドルの作成(esbuild など)
|
|
99
|
+
- 秘密(webhook の URL など)の SSM パラメータの作成
|
|
100
|
+
|
|
101
|
+
## アプリのモジュールからの移行(置き換えを起こさない)
|
|
102
|
+
|
|
103
|
+
リソースのアドレス・名前は、asanowa / astro の旧モジュール(`infra/modules/ops-notify`)と同じ。**module ブロックの名前(`module "ops_notify"`)を変えずに `source` だけを差し替え**、`source_file`・`handler` を足す。アドレスが変わらないので `moved` は要らない(ラッパーのモジュールで包むと、アドレスが変わり `moved` が要る)。
|
|
104
|
+
|
|
105
|
+
1. `terraform init`(モジュールの取り直し)→ `terraform plan`
|
|
106
|
+
2. **0 to add / 0 to destroy、`must be replaced` が無い**ことを確かめる。出てよいのは in-place の更新だけ(asanowa では zip の名前〔`ops-notify.zip` → `notify.zip`〕の `filename` が変わる。中身・`source_code_hash` は同じ。astro では環境変数 `PROJECT_NAME` が加わる)
|
|
107
|
+
3. 確認のうえ apply。旧モジュールのディレクトリを消す
|
|
108
|
+
|
|
109
|
+
詳細と実機の確認は [ADR 0006](../../docs/adr/0006-library-owns-table-operations.md) の段階 6。
|