Pro Yearly is on sale from $80 to $50! »

AWS CDK Deep Dive - migration -

0b28f5164b540e5a31bc63cca39b2917?s=47 curry9999
September 13, 2020

AWS CDK Deep Dive - migration -

JAWS SONIC 2020 & MIDNIGHT JAWS 2020
登壇資料

0b28f5164b540e5a31bc63cca39b2917?s=128

curry9999

September 13, 2020
Tweet

Transcript

  1. AWS CDK 勝手に Deep Dive AWS CDK の移行と向き合ってみた JAWS SONIC

    2020 & MIDNIGHT JAWS 2020 2020/09/13(日)
  2. 自己紹介 ▪ 名前:かれー ▪ 業務:とある企業にてインフラ運用およびクラウド移行 ▪ 好きな技術分野:Infrastructure as Code(AWS CDK,

    Ansible) ▪ 所属支部:JAWS-UG アーキテクチャ専門支部 ▪ 趣味:読書、音楽鑑賞、ものまね
  3. アジェンダ 1. はじめに 2. Ansible + CloudFormation to AWS CDK

    へ移行する 3. AWS CDK to CDK for Terraform へ移行する 4. まとめ
  4. 1. はじめに ▪ 本資料の対象者 ▪ AWS CDK に興味がある方 ▪ 以降で、本資料で使用するソフトウェアを簡単に説明する

  5. ソフトウェア前提条件 ソフトウェア バージョン AWS CDK v1.62.0 Terraform v0.13.2 CDK for

    Terraform v0.0.17 Node.js v14.10.0 TypeScript v4.0.2
  6. Ansible とは ▪ シンプル、パワフル、エージェントレスというコンセプトがある ▪ Ansible は OSS の構成管理ツールである ▪

    Jinja2 とは、 Python 製のテンプレートエンジンである
  7. AWS Cloud Development Kit (AWS CDK) とは ▪ CloudFormation をプログラミング言語で定義する

    ▪ 様々なプログラミング言語を使用することができる ▪ TypeScript ▪ Python ▪ Java ▪ .NET ▪ CloudFormation テンプレートを生成、Stack の作成や更新および削除ができる ▪ AWS Construct ライブラリが用意されており、プログラミングが容易になる ▪ AWS CDK について、AWS サポートに問合せることができる
  8. CDK for Terraform とは ▪ CDK を使用して、様々なクラウドリソースをプログラミングすることができる ▪ AWS ▪

    GCP ▪ Azure ▪ Kubernetes ▪ 様々なプログラミング言語を使用することができる ▪ TypeScript ▪ Python ▪ Terraform と同様に、Backend に S3 や remote を指定することができる
  9. 2. Ansible + CloudFormation to AWS CDK へ移行する ▪ AWS

    CDK を導入していない環境から、AWS CDK に移行した事例について紹介する ▪ Ansible で CloudFormation のテンプレートとスタックを作成していた ▪ AWS CDK に移行した事例について紹介する ▪ 追加で行えるアプローチについて紹介する
  10. CloudFormation デプロイフロー

  11. ソースコード - Ansible + CloudFormation Ansible Jinja2 Resources: {% for

    line in item.linedata %} {% set num = loop.index %} Ec2Instance{{ num }}: Type: AWS::EC2::Instance Properties: ImageId: {{ line.imageid }} ... - Key: Name Value: {{ line.name }} {% endfor %} CloudFormation Temaplte Resources: Ec2Instance1: Type: AWS::EC2::Instance Properties: ImageId: ami-0eb48a19a8d81e20b ... Tags: - Key: Name Value: prd-linux-ubuntu-1804 ... Ansible Vars ec2s: linux_stack: - stack_name: "{{ env }}-ec2-linux" jinja2_name: ec2.jinja2 linedata: - name: "{{ env }}-linux-ubuntu-1804" keypair: keypair_cloud9 os: linux imageid: ami-0eb48a19a8d81e20b instance_type: t2.micro ...
  12. AWS CDK に移行した理由 ▪ Jinja2 が複雑化して、Jinja2 の更新、Jinja2 の流用が困難な状況となっていた ▪ AWS

    の方に AWS CDK を勧められて、AWS CDK に興味を持った ▪ AWS CDK のプログラミング言語は TypeScript を採用した ▪ AWS CDK を導入した当時は、Python が使用できなかった ▪ Example が豊富であったため ▪ 差分がないか確認するため、移行前後の CloudFormation テンプレートを比較した ▪ AWS Constructs Library は、Low-level constructs を使用した
  13. CloudFormation デプロイフロー 変更後

  14. ソースコード - AWS CDK TypeScript #!/usr/bin/env node import cdk =

    require('@aws-cdk/core'); import ec2 = require('@aws-cdk/aws-ec2’); ... for (var i = 1; i <= cnt; i++) { new ec2.CfnInstance(this, 'Ec2Instance' + i, { imageId: this.node.tryGetContext("image_id"), ... app.synth(); Context { "context": { "key_pair": "keypair_cloud9", "instance_type": "t2.micro", "image_id": "ami-0eb48a19a8d81e20b" }, "app": "node index" } CloudFormation Temaplte Resources: Ec2Instance1: Type: AWS::EC2::Instance Properties: ImageId: ami-0eb48a19a8d81e20b ... Tags: - Key: Name Value: prd-linux-ubuntu-1804 ...
  15. 今ならどうする? ▪ Low-level constructs から High-level constructs へ移行を検討する ▪ AWS

    CDK に移行後、Stack の内容を書き換える必要があれば ▪ AWS CDK だけでなく、CDK for Terraform と併せて検討する ▪ AWS CDK に移行後、Terraform に移行したい場合のみ ▪ AWS CDK の品質をあげるために、テストの導入を検討する ▪ Context の値は変動するためテストを導入する ▪ テストを実施する方法の一例 ▪ Testing constructs (AWS CDK) ▪ Open Policy Agent ▪ CloudFormation Guard
  16. サンプル - Open Policy Agent 実行結果 $ ./opa eval --format

    pretty -i cdk.out/GAStack.template.json -d opa_cdk.rego "data" { "opa_cdk": { "deny_role_created": [ true ], "deny_too_many_vpc": [ true ] } } ソースコード $ cat > opa_cdk.rego <<EOF package opa_cdk import input # deny if it creates VPC deny_too_many_vpc[deny] { instances := [res | res:=input.Resources[_]; res.Type == "AWS::EC2::VPC"] count(instances) > 0 deny := true } # deny if it creates IAM role deny_role_created[deny] { input.Resources[_].Type == "AWS::IAM::Role" deny := true } EOF
  17. サンプル - CloudFormation Guard 実行結果 $ cfn-guard -t cdk.out/GlobalAcceleratorStack.template.json -r

    cfn-guard-rulesets/GlobalAcceleratorStack.template.ruleset $ echo $? 0 ソースコード $ head cfn-guard-rulesets/GlobalAcceleratorStack.template.ruleset AWS::AutoScaling::AutoScalingGroup LaunchConfigurationName == {"Ref":"AutoScalingGroupLaunchConfigDEEB160C"} AWS::AutoScaling::AutoScalingGroup MaxSize == 1 AWS::AutoScaling::AutoScalingGroup MinSize == 1 AWS::AutoScaling::AutoScalingGroup Tags == [{"Key":"Name","PropagateAtLaunch":true,"Value":"GlobalAcceleratorStack/AutoScalingGroup"}] AWS::AutoScaling::AutoScalingGroup TargetGroupARNs == [{"Ref":"ApplicationLoadBalancerListenerListenerTargetsGroup97AA098E"}] AWS::AutoScaling::AutoScalingGroup VPCZoneIdentifier == [{"Ref":"VPCPrivateSubnet1Subnet8BCA10E0"},{"Ref":"VPCPrivateSubnet2SubnetCFCDAA7A"},{"Ref":"xxxxxxxx"}] AWS::AutoScaling::LaunchConfiguration IamInstanceProfile == {"Ref":"AutoScalingGroupInstanceProfile342FAC7C"} AWS::AutoScaling::LaunchConfiguration ImageId == {"Ref":"SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter"} AWS::AutoScaling::LaunchConfiguration InstanceType == t2.micro AWS::AutoScaling::LaunchConfiguration SecurityGroups == [{"Fn::GetAtt":["AutoScalingGroupInstanceSecurityGroup9D2E0C5E","GroupId"]}]
  18. 3. AWS CDK to CDK for Terraform へ移行する ▪ AWS

    CDK で動かしている環境を、CDK for Terraform に移行した ▪ AWS CDK では、Low-level constructs を使用している ▪ AWS CDK でデプロイする AWS リソース ▪ accelerator ▪ listener ▪ endpoint group ▪ 移行手順 ▪ AWS CDK のソースコードを、CDK for Terraform にあわせて修正する ▪ AWS リソースの情報から、tfstate を生成する(terraform import) ▪ cdktf diff で差分が出た場合、tfstate を編集する(手動) ▪ 移行が完了した CloudFormation の Stack のみを削除する ▪ Stack のみを削除する(大事なことなので)
  19. ソースコード AWS CDK #!/usr/bin/env node import { App, Construct, Stack

    } from '@aws-cdk/core'; import { CfnAccelerator, CfnListener, CfnEndpointGroup } from '@aws-cdk/aws-globalaccelerator'; export class GlobalAcceleratorStack extends Stack { constructor(scope: Construct, id: string) { super(scope, id); const cfnAccelerator = new CfnAccelerator(this, 'CfnAccelerator', { name: 'AcceleratorNameTest' }) const cfnListener = new CfnListener(this, 'CfnListener', { acceleratorArn: cfnAccelerator.attrAcceleratorArn, portRanges: [{ fromPort: 80, toPort: 80 }], protocol: 'TCP', }) new CfnEndpointGroup(this, 'CfnEndpointGroup', { endpointGroupRegion: 'ap-northeast-1', listenerArn: cfnListener.attrListenerArn, }) } } const app = new App(); new GlobalAcceleratorStack(app, 'cdk-migrantion-test'); app.synth(); CDK for Terraform #!/usr/bin/env node import { Construct } from 'constructs'; import { App, TerraformStack as Stack, Token } from 'cdktf'; import { AwsProvider } from './.gen/providers/aws' import { GlobalacceleratorAccelerator as CfnAccelerator } from './.gen/providers/aws/globalaccelerator-accelerator'; import { GlobalacceleratorListener as CfnListener } from './.gen/providers/aws/globalaccelerator-listener'; import { GlobalacceleratorEndpointGroup as CfnEndpointGroup } from './.gen/providers/aws/globalaccelerator-endpoint-group'; export class GlobalAcceleratorStack extends Stack { constructor(scope: Construct, id: string) { super(scope, id); new AwsProvider(this, 'aws', { region: 'ap-northeast-1' }); const cfnAccelerator = new CfnAccelerator(this, 'CfnAccelerator', { name: 'AcceleratorNameTest' }) const cfnListener = new CfnListener(this, 'CfnListener', { acceleratorArn: Token.asString(cfnAccelerator.id), portRange: [{ fromPort: 80, toPort: 80 }], protocol: 'TCP', }) new CfnEndpointGroup(this, 'CfnEndpointGroup', { endpointGroupRegion: 'ap-northeast-1', listenerArn: Token.asString(cfnListener.id), }) } } const app = new App(); new GlobalAcceleratorStack(app, 'cdk-migrantion-test'); app.synth();
  20. ソースコードの差分とポイント ソースコード差分 2,3c2,7 < import { App, Construct, Stack }

    from '@aws-cdk/core'; < import { CfnAccelerator, CfnListener, CfnEndpointGroup } from '@aws-cdk/aws-globalaccelerator'; --- > import { Construct } from 'constructs'; > import { App, TerraformStack as Stack, Token } from 'cdktf'; > import { AwsProvider } from './.gen/providers/aws' > import { GlobalacceleratorAccelerator as CfnAccelerator } from './.gen/providers/aws/globalaccelerator-accelerator'; > import { GlobalacceleratorListener as CfnListener } from './.gen/providers/aws/globalaccelerator-listener'; > import { GlobalacceleratorEndpointGroup as CfnEndpointGroup } from './.gen/providers/aws/globalaccelerator-endpoint-group'; 8c12,16 < --- > > new AwsProvider(this, 'aws', { > region: 'ap-northeast-1' > }); > 16,17c24,25 < acceleratorArn: cfnAccelerator.attrAcceleratorArn, < portRanges: [{ --- > acceleratorArn: Token.asString(cfnAccelerator.id), > portRange: [{ 25c33 < listenerArn: cfnListener.attrListenerArn, --- > listenerArn: Token.asString(cfnListener.id), ポイント import ▪ CDK と Terraform でクラスモジュールが異なる ▪ クラスモジュールについては別名を定義した ▪ 別名を定義してソースコードの修正量を少なくした AwsProvider ▪ Terraform で AWS Provider を指定する ▪ Terraform のソースコードに追記する ARN ▪ Terraform の記述に合わせて修正した ▪ acceleratorArn ▪ listenerArn ▪ Token.asString を使用して ARN を取得する ▪ 参考にさせていただいたブログ ▪ AWS CDKでプロバイダーとして Terraformが使える!! CDK for Terraform が発表されました!! #awscdk portRanges ▪ 変数名が CDK と Terraform で異なる ▪ Terraform は portRange を指定する
  21. 移行してみた感想 ▪ CDK for Terraform に、AWS CDK と同等のクラスモジュールがある ▪ Constructs

    が、CDK for Terraform に存在していない場合がある ▪ 変数名 の命名が異なることがある ▪ 移行する AWS リソースの数が多くなると、tfstate の書き換えが大変となるかも ▪ Terraform の猛者であれば苦労しないのかも・・・
  22. 4. まとめ ▪ AWS CDK への移行について紹介した ▪ 本当に移行する必要があるのかは検討すること ▪ 大量の

    Stack が存在する場合、工数がかかることが想定されるため ▪ 興味がわいたら、CDK を使用してみよう ▪ CDK を始める際には、下記の資料を是非一読していただきたい ▪ ソフトウェア開発者のためのAWS環境構築フレームワーク AWS Cloud Development Kit (CDK) ▪ https://pages.awscloud.com/rs/112-TZM-766/images/B-3.pdf ▪ 機会があれば・・・「個人アプリを AWS CDK で構築してみた」を発表する
  23. ご清聴ありがとうございました!