概要
CDK(L2 Construct)でAWSリソースを作っていると指定できないプロパティがあります。
CDKのバージョンを上げれば解消されることもありますが、いつでも最新のCDKが使える状況ではないかもしれません。また、最新化したとしても依然として指定できないケースもあります。
より低レイヤーなL1 Constructを使うことで解決する方法を紹介します。
前提
バージョン情報
- CDK:2.89.0
- TypeScript
CDKのレイヤーについて
CDKには大きく3つのレイヤーが存在しています。
レイヤー名 | 説明 |
---|---|
L1 | Cfnのテンプレートを直接記述できる。L2よりも細かい指定ができる。 |
L2 | 普通に書いてたらこのレイヤーを使ってる。 |
L3 | 本記事とは無関係なので割愛。 |
極端な例ですが、L2でS3バケットを作ろうと思った場合以下のように1行書けば作れます。
const bucket = new s3.Bucket(this, 'MyFirstBucket');
作成したバケットにバケットポリシーをつけたらかったらaddToResourcePolicy()
を使えばいいです。
const bucket = new s3.Bucket(this, 'MyBucket');
const result = bucket.addToResourcePolicy(
new iam.PolicyStatement({
actions: ['s3:GetObject'],
resources: [bucket.arnForObjects('file.txt')],
principals: [new iam.AccountRootPrincipal()],
})
);
L2を使うことでこのように少ないコード量でリソースを定義することができるのは大きなメリットでしょう。
L2では書けないケース
CDKのバージョンが上がるにつれて少しずつ減ってきていますが、新しいサービスや整備が進んでいないサービスだとL2では書けないことがあります。いくつか例を出しましょう。
例1:S3 サーバーアクセスのログ記録
S3にはアクセスログを記録する機能があります。(下図)
ログの保存先としてS3バケットを指定することができますが、設定パラメータの中でログオブジェクトキーの形式を指定することができます。
Athenaでの検索を考慮するとパーティション分割されている後者を使いたいところです。最新のCDKだとtargetObjectKeyFormat
が用意されているので問題なく指定できますが、バージョンが2.89.0
だとこれが指定できません。
例2:Glue
L2すらないようなケースもあります。
aws-cdk-lib.aws_glue module
のリファレンスの冒頭には次のような記述があります。
There are no official hand-written (L2) constructs for this service yet. Here are some suggestions on how to proceed:
実験的なライブラリとして@aws-cdk/aws-glue-alpha
が提供されていますのでこちらを利用することで少し書くのが楽になります。
ただし、不足している部分は多いです。Serde parametersの設定などはできませんので例えば先ほどのアクセスログをAthenaで検索するには以下のようなテーブル定義をすればいいのですが、input.regex
などは指定できないです。
CREATE EXTERNAL TABLE `s3_access_logs_db.mybucket_logs`(
`bucketowner` STRING,
`bucket_name` STRING,
`requestdatetime` STRING,
`remoteip` STRING,
`requester` STRING,
`requestid` STRING,
`operation` STRING,
`key` STRING,
`request_uri` STRING,
`httpstatus` STRING,
`errorcode` STRING,
`bytessent` BIGINT,
`objectsize` BIGINT,
`totaltime` STRING,
`turnaroundtime` STRING,
`referrer` STRING,
`useragent` STRING,
`versionid` STRING,
`hostid` STRING,
`sigv` STRING,
`ciphersuite` STRING,
`authtype` STRING,
`endpoint` STRING,
`tlsversion` STRING,
`accesspointarn` STRING,
`aclrequired` STRING)
ROW FORMAT SERDE
'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
'input.regex'='([^ ]*) ([^ ]*) \\[(.*?)\\] ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) (\"[^\"]*\"|-) (-|[0-9]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) (\"[^\"]*\"|-) ([^ ]*)(?: ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*))?.*$')
STORED AS INPUTFORMAT
'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
's3://amzn-s3-demo-bucket1-logs/prefix/'
解決策
こういったケースの解決策の一つとしてL1の利用が考えられます。
先の例のケースで具体的に見ていきます。
例1:S3 サーバーアクセスのログ記録
// バケット作成(これはL2)※ バケット作成時のプロパティは割愛
const bucket = new s3.Bucket(this, 'MyBucket');
// L2で定義したリソースをL1で再定義
const cfnBucket = bucket.node.defaultChild as CfnBucket;
// L1でのリソース設定
cfnBucket.addPropertyOverride("LoggingConfiguration", {
TargetObjectKeyFormat: {
"PartitionedPrefix": {
"PartitionDateSource": "EventTime" // パーティション分割の基準となる日付はイベント発生日時とする。
}
}
L1で再定義するところは{L2リソース}.node.defaultChild as {適切なデータ型}
という形式で取得できます。
addPropertyOverride()
の中身はCfnのリファレンスを見て頑張って記載します。今回の例だとTargetObjectKeyFormat
で指定できることはわかっているのでそこを起点に調べてみます。
PartitionedPrefix
が用意されていて、PartitionDateSource
としてEventTime
かDeliveryTime
が指定できるそうなのでそのように指定しています。
例2:Glue
こっちも同じです
(table.node.defaultChild as CfnTable).addPropertyOverride("TableInput", {
"StorageDescriptor": {
"SerdeInfo": {
"Parameters": {
"input.regex": '([^ ]*) ([^ ]*) \\[(.*?)\\] ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) (\"[^\"]*\"|-) (-|[0-9]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) (\"[^\"]*\"|-) ([^ ]*)(?: ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*))?.*$',
"serialization.format": '1'
}
}
}
});
プロパティの部分は今までL1を使ってないとしんどいですが、慣れれば難しくないと思います。
参考文献