こんにちは。ソニー・ミュージックエンタテインメント しんろくです。
「極めよう AWS CDK」と題しまして、ぼくの経験値における AWS CDK にまつわるエトセトラを連載でご紹介していきたいと思います。
はじめに
ぼくは Linuxキャリア20年ほどのエンジニア兼 プログラマとして業務に従事しております。ですので、本連載は基本的に Linux 環境での作業を前提にしております。Windows や Mac 環境での動作についてはほとんど言及いたしませんのでご了承ください。
またAWS歴は2016年からと浅めですが、AWS CDKについてはバージョン1がGAされたタイミングから利用していますので、CDK界隈ではそれなりに長い経験値があるかもしれません(笑)。
前置きは以上、この記事を読んでAWS CDKに興味をもっていただけたら幸いです。
想定読者
本連載は以下のような読者のみなさんにオススメできると思っています。
- AWS CDKを始めてみたい人
- CloudFormation テンプレートにイライラさせられたことのある人
- AWS環境構築で楽をしたい人
- AWSリソースやAWS APIをより深く理解したい人
- AWSリソースの変更管理をきちんとやりたい人
- マルチアカウント戦略を実施していて、デプロイ作業に苦痛を感じている人
また、AWS CDKは複数のプログラミング言語に対応していますが、 本稿での使用言語は TypeScript で進めます。
AWS CDK とは
公式ドキュメントを読んでいただいたほうが正確ですが ぼくの理解でざっくりいうと、汎用プログラミング言語から CloudFormation テンプレート(YAML or JSON)を生成し CloudFormation スタックを実行してくれるフレームワークのことです。
その他、Lambda 関数のデプロイをおこなってくれたり、Docker ビルドを実行して ECR にプッシュしてくれたり、単純な CloudFormation スタックの実行だけではない便利な機能もたくさんあります。 このあたりは次回以降 にご紹介できればと思います。
さっそく始めよう
作業環境は厳密に一致していなくても大丈夫ですが、できるだけ新しい環境が良いと思います。
- nodejs 18.x が動く Linux 環境(EC2でもWSLでも大丈夫です)
- エディタは VSCode をおすすめしますが、これも好きなものを使ってください。
- のちのち Docker が必要になってきます。
ちなみにぼくの執筆時点の環境です。
$ cat /etc/os-release PRETTY_NAME="Ubuntu 22.04.3 LTS" NAME="Ubuntu" VERSION_ID="22.04" VERSION="22.04.3 LTS (Jammy Jellyfish)" VERSION_CODENAME=jammy ID=ubuntu ID_LIKE=debian HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" UBUNTU_CODENAME=jammy $ node -v v18.15.0 $ npm -v 9.8.1 $ code --version 1.81.1 6c3e3dba23e8fadc360aed75ce363ba185c49794 x64
インストール
AWS CDK の CLI をインストールしましょう。 ルート権限がない人は、自分のローカルで大丈夫です。(適宜 PATHを通してください。)
$ sudo npm i -g aws-cdk
$ cdk --version
2.93.0 (build 724bd01)
プロジェクトディレクトリ作成
好きなところに作業用ディレクトリを作りましょう。
$ cd path/to/work $ mkdir hello-cdk
AWS CDK CLIを実行しよう
CDKの初期設定を行います。 これを行うと、骨組みのファイルがすべて生成されます。便利! 言語は TypeScript を指定します。
$ cd hello-cdk $ cdk init -l typescript # Welcome to your CDK TypeScript project This is a blank project for CDK development with TypeScript. The `cdk.json` file tells the CDK Toolkit how to execute your app. # Useful commands * `npm run build` compile typescript to js * `npm run watch` watch for changes and compile * `npm run test` perform the jest unit tests * `cdk deploy` deploy this stack to your default AWS account/region * `cdk diff` compare deployed stack with current state * `cdk synth` emits the synthesized CloudFormation template Executing npm install... ✅ All done!
これでプロジェクトディレクトリができあがりました。
$ tree -L 1 . ├── README.md ├── bin ├── cdk.json ├── jest.config.js ├── lib ├── node_modules ├── package.json ├── test └── tsconfig.json 4 directories, 5 files
細かいファイルの内容は今後の記事で深掘りしていきますので 今回はみんな大好き VPC を作ってみます。
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; // import * as sqs from 'aws-cdk-lib/aws-sqs'; export class HelloCdkStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // The code that defines your stack goes here // example resource // const queue = new sqs.Queue(this, 'HelloCdkQueue', { // visibilityTimeout: cdk.Duration.seconds(300) // }); } }
上記ファイルができあがっているので、下記のように改造します。
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; export class HelloCdkStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const vpc = new cdk.aws_ec2.Vpc(this, 'vpc', { maxAzs: 99, // おまじない natGateways: 0, }); new cdk.CfnOutput(this, 'vpcId', { value: vpc.vpcId }); } }
なんとなくVPCを作っているんだな~と思っていただければ大丈夫です。 こまかいオプションの説明は次回以降掘り下げていきます。
ではこれをビルド(正確にはTSからJSへのトランスパイル)します。
$ npm run build > hello-cdk@0.1.0 build > tsc
エラーが出なければ下記のようなディレクトリになっているはずです。
. ├── README.md ├── bin │ ├── hello-cdk.d.ts │ ├── hello-cdk.js │ └── hello-cdk.ts ├── cdk.json ├── jest.config.js ├── lib │ ├── hello-cdk-stack.d.ts │ ├── hello-cdk-stack.js │ └── hello-cdk-stack.ts ├── node_modules ├── package.json ├── test │ ├── hello-cdk.test.d.ts │ ├── hello-cdk.test.js │ └── hello-cdk.test.ts └── tsconfig.json 218 directories, 14 files
では、cdk synth コマンドから CloudFormation テンプレートを作成してみましょう。
$ cdk synth > hello-cdk.yml
Resources: vpcA2121C38: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsHostnames: true EnableDnsSupport: true InstanceTenancy: default Tags: - Key: Name Value: HelloCdkStack/vpc Metadata: aws:cdk:path: HelloCdkStack/vpc/Resource vpcPublicSubnet1Subnet2E65531E: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" CidrBlock: 10.0.0.0/18 MapPublicIpOnLaunch: true Tags: - Key: aws-cdk:subnet-name Value: Public - Key: aws-cdk:subnet-type Value: Public - Key: Name Value: HelloCdkStack/vpc/PublicSubnet1 VpcId: Ref: vpcA2121C38 Metadata: aws:cdk:path: HelloCdkStack/vpc/PublicSubnet1/Subnet vpcPublicSubnet1RouteTable48A2DF9B: Type: AWS::EC2::RouteTable Properties: Tags: - Key: Name Value: HelloCdkStack/vpc/PublicSubnet1 VpcId: Ref: vpcA2121C38 Metadata: aws:cdk:path: HelloCdkStack/vpc/PublicSubnet1/RouteTable vpcPublicSubnet1RouteTableAssociation5D3F4579: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: vpcPublicSubnet1RouteTable48A2DF9B SubnetId: Ref: vpcPublicSubnet1Subnet2E65531E Metadata: aws:cdk:path: HelloCdkStack/vpc/PublicSubnet1/RouteTableAssociation vpcPublicSubnet1DefaultRoute10708846: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: vpcIGWE57CBDCA RouteTableId: Ref: vpcPublicSubnet1RouteTable48A2DF9B DependsOn: - vpcVPCGW7984C166 Metadata: aws:cdk:path: HelloCdkStack/vpc/PublicSubnet1/DefaultRoute vpcPublicSubnet2Subnet009B674F: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" CidrBlock: 10.0.64.0/18 MapPublicIpOnLaunch: true Tags: - Key: aws-cdk:subnet-name Value: Public - Key: aws-cdk:subnet-type Value: Public - Key: Name Value: HelloCdkStack/vpc/PublicSubnet2 VpcId: Ref: vpcA2121C38 Metadata: aws:cdk:path: HelloCdkStack/vpc/PublicSubnet2/Subnet vpcPublicSubnet2RouteTableEB40D4CB: Type: AWS::EC2::RouteTable Properties: Tags: - Key: Name Value: HelloCdkStack/vpc/PublicSubnet2 VpcId: Ref: vpcA2121C38 Metadata: aws:cdk:path: HelloCdkStack/vpc/PublicSubnet2/RouteTable vpcPublicSubnet2RouteTableAssociation21F81B59: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: vpcPublicSubnet2RouteTableEB40D4CB SubnetId: Ref: vpcPublicSubnet2Subnet009B674F Metadata: aws:cdk:path: HelloCdkStack/vpc/PublicSubnet2/RouteTableAssociation vpcPublicSubnet2DefaultRouteA1EC0F60: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: vpcIGWE57CBDCA RouteTableId: Ref: vpcPublicSubnet2RouteTableEB40D4CB DependsOn: - vpcVPCGW7984C166 Metadata: aws:cdk:path: HelloCdkStack/vpc/PublicSubnet2/DefaultRoute vpcIsolatedSubnet1Subnet8B28CEB3: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" CidrBlock: 10.0.128.0/18 MapPublicIpOnLaunch: false Tags: - Key: aws-cdk:subnet-name Value: Isolated - Key: aws-cdk:subnet-type Value: Isolated - Key: Name Value: HelloCdkStack/vpc/IsolatedSubnet1 VpcId: Ref: vpcA2121C38 Metadata: aws:cdk:path: HelloCdkStack/vpc/IsolatedSubnet1/Subnet vpcIsolatedSubnet1RouteTable0D6B2D3D: Type: AWS::EC2::RouteTable Properties: Tags: - Key: Name Value: HelloCdkStack/vpc/IsolatedSubnet1 VpcId: Ref: vpcA2121C38 Metadata: aws:cdk:path: HelloCdkStack/vpc/IsolatedSubnet1/RouteTable vpcIsolatedSubnet1RouteTableAssociation172210D4: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: vpcIsolatedSubnet1RouteTable0D6B2D3D SubnetId: Ref: vpcIsolatedSubnet1Subnet8B28CEB3 Metadata: aws:cdk:path: HelloCdkStack/vpc/IsolatedSubnet1/RouteTableAssociation vpcIsolatedSubnet2Subnet2C6B375C: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" CidrBlock: 10.0.192.0/18 MapPublicIpOnLaunch: false Tags: - Key: aws-cdk:subnet-name Value: Isolated - Key: aws-cdk:subnet-type Value: Isolated - Key: Name Value: HelloCdkStack/vpc/IsolatedSubnet2 VpcId: Ref: vpcA2121C38 Metadata: aws:cdk:path: HelloCdkStack/vpc/IsolatedSubnet2/Subnet vpcIsolatedSubnet2RouteTable3455CBFC: Type: AWS::EC2::RouteTable Properties: Tags: - Key: Name Value: HelloCdkStack/vpc/IsolatedSubnet2 VpcId: Ref: vpcA2121C38 Metadata: aws:cdk:path: HelloCdkStack/vpc/IsolatedSubnet2/RouteTable vpcIsolatedSubnet2RouteTableAssociation8A8FAF70: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: vpcIsolatedSubnet2RouteTable3455CBFC SubnetId: Ref: vpcIsolatedSubnet2Subnet2C6B375C Metadata: aws:cdk:path: HelloCdkStack/vpc/IsolatedSubnet2/RouteTableAssociation vpcIGWE57CBDCA: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: HelloCdkStack/vpc Metadata: aws:cdk:path: HelloCdkStack/vpc/IGW vpcVPCGW7984C166: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: Ref: vpcIGWE57CBDCA VpcId: Ref: vpcA2121C38 Metadata: aws:cdk:path: HelloCdkStack/vpc/VPCGW vpcRestrictDefaultSecurityGroupCustomResourceA6EBC6D0: Type: Custom::VpcRestrictDefaultSG Properties: ServiceToken: Fn::GetAtt: - CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E - Arn DefaultSecurityGroupId: Fn::GetAtt: - vpcA2121C38 - DefaultSecurityGroup Account: Ref: AWS::AccountId UpdateReplacePolicy: Delete DeletionPolicy: Delete Metadata: aws:cdk:path: HelloCdkStack/vpc/RestrictDefaultSecurityGroupCustomResource/Default CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: lambda.amazonaws.com ManagedPolicyArns: - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: Inline PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - ec2:AuthorizeSecurityGroupIngress - ec2:AuthorizeSecurityGroupEgress - ec2:RevokeSecurityGroupIngress - ec2:RevokeSecurityGroupEgress Resource: - Fn::Join: - "" - - "arn:" - Ref: AWS::Partition - ":ec2:" - Ref: AWS::Region - ":" - Ref: AWS::AccountId - :security-group/ - Fn::GetAtt: - vpcA2121C38 - DefaultSecurityGroup Metadata: aws:cdk:path: HelloCdkStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E: Type: AWS::Lambda::Function Properties: Code: S3Bucket: Fn::Sub: cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region} S3Key: 4b996a3e5a083d5c78c6f30a8571a94fb7ec557eecbe54dbc065faba0d9076e6.zip Timeout: 900 MemorySize: 128 Handler: __entrypoint__.handler Role: Fn::GetAtt: - CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0 - Arn Runtime: nodejs18.x Description: Lambda function for removing all inbound/outbound rules from the VPC default security group DependsOn: - CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0 Metadata: aws:cdk:path: HelloCdkStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler aws:asset:path: asset.4b996a3e5a083d5c78c6f30a8571a94fb7ec557eecbe54dbc065faba0d9076e6 aws:asset:property: Code CDKMetadata: Type: AWS::CDK::Metadata Properties: Analytics: v2:deflate64:H4sIAAAAAAAA/22Qva7CMAyFn4U9DX9CghF1QHe6VUGsKE0NGFoHJU4RQrw7LiCyMNn+bPkce6IXUz0amGvIbH3OGqz0fc3GnpWgHdiJvm8vVuV72ha5KmLVoF3HioB7lrLSRYaNqRpIPLFlCM6iYXT0HVaFx84wpB1/xOAlXwm9mttH9FMtWVwdWyB+qBKCi96KVAzs2lTK6t+twrsOa/BKnADLgQekQz//H/kSX+q5oxp7hw9FrgZ9CsNuPNfjmbznFBAzH4mxBV2+4xO5ekmdOgEAAA== Metadata: aws:cdk:path: HelloCdkStack/CDKMetadata/Default Condition: CDKMetadataAvailable Outputs: vpcId: Value: Ref: vpcA2121C38 Conditions: CDKMetadataAvailable: Fn::Or: - Fn::Or: - Fn::Equals: - Ref: AWS::Region - af-south-1 - Fn::Equals: - Ref: AWS::Region - ap-east-1 - Fn::Equals: - Ref: AWS::Region - ap-northeast-1 - Fn::Equals: - Ref: AWS::Region - ap-northeast-2 - Fn::Equals: - Ref: AWS::Region - ap-south-1 - Fn::Equals: - Ref: AWS::Region - ap-southeast-1 - Fn::Equals: - Ref: AWS::Region - ap-southeast-2 - Fn::Equals: - Ref: AWS::Region - ca-central-1 - Fn::Equals: - Ref: AWS::Region - cn-north-1 - Fn::Equals: - Ref: AWS::Region - cn-northwest-1 - Fn::Or: - Fn::Equals: - Ref: AWS::Region - eu-central-1 - Fn::Equals: - Ref: AWS::Region - eu-north-1 - Fn::Equals: - Ref: AWS::Region - eu-south-1 - Fn::Equals: - Ref: AWS::Region - eu-west-1 - Fn::Equals: - Ref: AWS::Region - eu-west-2 - Fn::Equals: - Ref: AWS::Region - eu-west-3 - Fn::Equals: - Ref: AWS::Region - me-south-1 - Fn::Equals: - Ref: AWS::Region - sa-east-1 - Fn::Equals: - Ref: AWS::Region - us-east-1 - Fn::Equals: - Ref: AWS::Region - us-east-2 - Fn::Or: - Fn::Equals: - Ref: AWS::Region - us-west-1 - Fn::Equals: - Ref: AWS::Region - us-west-2 Parameters: BootstrapVersion: Type: AWS::SSM::Parameter::Value<String> Default: /cdk-bootstrap/hnb659fds/version Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip] Rules: CheckBootstrapVersion: Assertions: - Assert: Fn::Not: - Fn::Contains: - - "1" - "2" - "3" - "4" - "5" - Ref: BootstrapVersion AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.
たった数行のTypeScriptから なんと389行もあるYAMLが生成されました。
このテンプレートの中には
- VPCがひとつ
- Subnet / RouteTable / SubnetRouteTableAssociation / Route が public / isolate で 2つずつ
- InternetGateway がひとつ
- VpcRestrictDefaultSG というカスタムリソースがひとつ
- Role がひとつ
- VpcRestrictDefaultSG を動かすためのLambda関数がひとつ
- メタデータがひとつ
- Parameter がひとつ
という構成が記述されています。 このYAMLを人が記述しようとすると大変なことになるのは一目瞭然かと思います。
本日は「CDKをはじめよう」と題して AWS CDK の紹介をいたしました。 今後は CDK の深掘りやアーキテクチャを構築するときの考え方 ソニーミュージックグループならではの使い方などを ご紹介していければと考えています。
ではまた次回お会いしましょう!