morishitaです。
AWSのリソースを作るときにはコンソールをポチポチやるよりは Cloud Formation を使ったほうが楽に感じるようになってきました。
そんなところに aws-cdk が GA になりました。Typescript で Cloud Formation スタックが書けるということで気になっていました。
ちょっとググると DyanamoDB とか Lambda を使ってサーバレスアプリケーションを作りました的な記事が出て来るのですが、今回はちょっと地味にIAMユーザを作ってみました。
背景
当社ではほとんどのプロダクトで Docker Compose による開発環境を用意しています。
更に、いくつかのプロダクトでは本番DBのデータ1を含むDockerイメージを毎日ビルドしてECRにプッシュしています。それを Pull すれば、ほぼ最新の本番DB同様のデータをローカルの開発環境で利用できるようになっています。
アクトインディではデザイナが Slim のテンプレートを書くので、デザイナーも開発環境を作ります。DBのイメージを Pull するにはECRにアクセスできる必要があるので、そのためのデザイナにもIAMユーザを振りだそうということになりました。
そのときは次の様な要件で YAMLでテンプレートを作って Cloud Formation スタックを作りました。
- デザイナ用グループを作る
- グループに ECR から Pull できるだけのポリシーをつける
- そのグループに各デザイナ用の個別のIAMユーザを作成する
- IAMユーザには AccessKey と SecretAccessKey も発行する
やってみて思ったのは、同じ様なIAMユーザを作るのに繰り返しのシンタックスがないので不便だということでした。
コピペしてちょっと変えていくだけで増やせるのですが、そのコピペがイマイチだなぁと。
でも、Typescript で書ける CDK ならもっとスッキリ書けるのでは、ということ入門からやってみました。
AWS CDK のインストールとスタックの初期化
CDKのCLIはnpmパッケージです。
それをインストールして、iam-users という名前で CDK プロジェクトを初期化するには次のコマンドを実行します。
言語は、 Python、Java、.NET、TypeScript から選択できます。
私は TypeScript を指定します。
$ npm install -g aws-cdk # CDKのインストール $ mkdir iam-users $ cd iam-users $ cdk init app --language=typescript # CDKプロジェクトの初期化
ちなみに、このときインストールされたCDKは 1.3.0 でした。
cdk init app
の結果、次のファイル群が生成されます。
iam-users/ ├── README.md ├── bin/ │ └── iam-users.ts # CDKコマンドが実行する ├── cdk.json ├── cdk.out ├── lib/ │ └── iam-users-stack.ts # 実装対象 ├── node_modules/ ├── package-lock.json ├── package.json └── tsconfig.json
実装
さて、IAMユーザを作るための実装に入りますが、その前にIAMのリソースを扱うには更に追加のモジュールが必要です。
次のコマンドで @aws-cdk/aws-iam を追加できます。
$ npm install -S @aws-cdk/aws-iam
CDKが実行するのは bin/iam-users.ts です(実際にはトランスパイルされたJS)。
bin/iam-users.ts を見てみると、lib/iam-users-stack.ts をインポートしてインスタンス生成をしているだけです。
ということで、lib/iam-users-stack.ts が実装対象となるファイルということです。
次の様に実装しました。
AWSの提供するサンプルを参考に実装して見たので、コンストラクタに全コードを書いてしいましたが、適宜メソッドに切り出しても構わないだろうと思います。
import { Construct, Stack, StackProps, CfnOutput } from '@aws-cdk/core'; import { Group, Policy, PolicyStatement, User, CfnAccessKey } from '@aws-cdk/aws-iam'; const account = 'your-account-id'; const repositoryName = 'your-repository-name'; const groupName = 'Group01'; const userNames = ['User01', 'User02', 'User03']; export class CdkLessonStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // ポリシーステートメントの定義 const policyStatement = new PolicyStatement({ resources: [`arn:aws:ecr:ap-northeast-1:${account}:repository/${repositoryName}`], actions: [ "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecr:DescribeImages", "ecr:BatchCheckLayerAvailability", "ecr:GetRepositoryPolicy", ], }) // ポリシーの定義 const policyName = 'AllowGetImagesFromECR'; const policy = new Policy(this, policyName, { policyName, statements: [policyStatement], }); // グループの定義 const group = new Group(this, groupName, { groupName }); group.attachInlinePolicy(policy); // ユーザとアクセスキーの定義 userNames.forEach((userName) => { const user = new User(this, userName, { userName, groups: [group] }); const key = new CfnAccessKey(this, `${userName}Key`, { userName: user.userName }); new CfnOutput(this, `${userName}AccessKey`, { value: key.ref }); new CfnOutput(this, `${userName}SecretAccessKey`, { value: key.attrSecretAccessKey }); }) } }
変数userNames
をforEach
で回して複数のユーザを作っています。
このuserNames
を変更するだけでユーザの作成、削除ができるはずです。
YAMLに比べるととてもスッキリ書けたのではないかと思います。
ビルド、デプロイ
さて、実装できたところで、これを実行してリソースを生成するのですが、 デプロイ前にはビルドが必要なので次のコマンドを実行します。
$ npm run build
ビルドは単に tsc
が実行されるだけです。これで Typescript のコードがトランスパイルされ、Javascriptのコードが出力されます。
ビルドが済んだら次のコマンドでデプロイできます。
$ cdk deploy
実行結果は次の様に出力されます。
Cloud Formationのチェンジセットが作られてリソースが生成される様子が出力されています。
お、狙い通りuserNames
の要素数分、IAMユーザが作成されたようです。
すでに、Cloud Formationに慣れていてYAMLでも確認したいという場合にはcdk synth
を実行すれば出力されます。
生成されたリソースの確認
AWSコンソールで生成されたものを確認してみます。
Cloud Formation スタック
AWSコンソールを確認するとCloud Formation スタックが作成され、コード通りのリソースが作成されています。
狙い通り、AWS::IAM::USER
が3件作成されています。
また、Cloud Formation で AWS::IAM::AccessKey を作った場合、Outputで出力しないとsecretAccessKey
の値が見れないですが、それもちゃんと出力されています。
Outputに出力してしまうとsecretAccessKey
がいつでも見れてしまうので、スタックを参照できる人の管理が重要です。更にCFnで作るユーザの権限を大きくしすぎないようにするといいと思います。
余談
本当はCloud FormationのOutputにせず、console.logで出力するだけにしたかったのですが、無理そうです。
ユーザを作っているforEach
の中で次のコードを追加してみました。
console.log({ name: user.userName, key: key.ref, secret: key.attrSecretAccessKey});
しかし、出力されたのは値ではなく、次の様にCloud Formationスタック内での参照を表しているであろう文字列のみでした。
{ name: '${Token[TOKEN.24]}', key: '${Token[TOKEN.28]}', secret: '${Token[TOKEN.27]}' } { name: '${Token[TOKEN.36]}', key: '${Token[TOKEN.40]}', secret: '${Token[TOKEN.39]}' } { name: '${Token[TOKEN.48]}', key: '${Token[TOKEN.52]}', secret: '${Token[TOKEN.51]}' }
これを見ても、CDKがやってくれるのは Cloud Formationのテンプレートを作って、スタックに反映するところだけで、リソース自体はCloud Formationが作っているのだなと思います。
IAMリソース
次に作られたIAMリソースをそれぞれ確認してみます。
まずはグループ。
インラインポリシーも指定した名前でちゃんとできています。
ポリシーの中身もこの通り。
ユーザも次の通り作られています。もちろん3件作られています。
グループに所属し、ポリシーも割り当てられています。
削除
要らなくなったら次のコマンドで削除できます。
$ cdk destroy
IAMのリソースの場合、残るものもなく綺麗サッパリなくなります。
メリット・デメリット
メリット
- Typescriptは書きやすい
- エディタによるサジェストも便利すぎる
- 環境変数による条件分岐や繰り返しなどの制御構文を活用できる
デメリット
- npm パッケージのアップデートが面倒そう
所感
AWSの各リソースの知識はやはり必要なものの、慣れないJSONやYAMLの仕様と格闘するより、使い慣れたTypescriptで書けるのがやりやすいです。 私はVSCodeを使っているのですが、マウスオーバしたメソッドのコメントも参照できます。 各メソッドの引数はインタフェースが定義されているでどんな値を設定すればいいかもわかりやすいです。
文字列の連結もCloud FormationのDSLとして用意された使いづらい関数を使わなくてもよく、普通に文字列中の変数展開ができ楽々です。
デメリットはこれといったものが思いつかなかったのですが、強いて言うならCDK自身、そして依存しているnpmのアップデートに追従するのがちょっと面倒そうかなということくらいですかね。
とか思っていたら早速CDKの更新が!
************************************************* *** Newer version of CDK is available [1.4.0] *** *** Upgrade recommended *** *************************************************
まあ、この程度の面倒は無視できるくらいCDKを使うとCloud Formationの作成、運用が楽になるのではないでしょうか。
もう私はYAMLに戻れないかも。
参考
最後に
アクトインディではエンジニアを募集しています。 actindi.net
-
もちろん、開発環境用にはメアドやユーザ名、パスワードなどの情報は書き換えてマスクしています。↩