Slide 1

Slide 1 text

aws-cdkで複数アプリの構成標 準化に取り組んだ話 JAWS-UG CDK支部 #12 2024/02/22 桜井輝忠

Slide 2

Slide 2 text

目次 - 職場の開発PJTで標準化チーム発足 - AWSのインフラ領域を標準化するためにaws-cdkを採用 - 複数アプリのインフラ構成をcdk管理する - 今後の課題

Slide 3

Slide 3 text

自己紹介 星野リゾートのインフラエンジニア。内製開発でDevOps目指して仕事をしている。 2023年4月に中途入社する前は小売業の内製開発をしていた(Webアプリ開発とSREが 半々ほど)。AWSとの付き合いは2016年に資格をとって以降ずっと仕事で関わっている。現 在有効なAWS認定はAWS Certified Security - Specialty。 略歴: インフラSE(AWSパートナーのSIer) -> Web開発 & AWSインフラ(小売事業の内製開発) -> (現在) 「旅に魔法をかけるITチーム」をコンセプトに、観光 x ITに関してや 内製化を頑張るメン バーの日常などを発信している情報システムグループ公式noteもチェックしてみてください。 https://note.com/hoshino_technote/

Slide 4

Slide 4 text

開発PJTで標準化チーム発足 サーバサイド/フロントエンド/インフラ、それぞれの領域で共通化した手法を使って複数の異 なるWebアプリを開発することを目指した。 目的; ● エンジニアのオンボーディング効率を上げる(開発チーム間の敷居を下げたい) ● 開発品質の低下を防ぐ(独自の仕組みを量産しない) ● アプリ開発するときの立ち上げを早くする(より設計に時間を使う)

Slide 5

Slide 5 text

開発PJTで標準化チーム発足 対象; 必要とされるAWSサービスを aws-cdkで構築する CodeDeploy Blue/Green ECS Rolling Update SecretManagerと aws-advanced-jdbc-wrapper ECS FireLens経由 のログ転送 IAMとGitHub のOIDC接続

Slide 6

Slide 6 text

AWSインフラ領域を標準化するためにaws-cdkを採用 今までも可能な部分は優先して aws-cdkに移行(CFn -> terraform -> aws-cdk)していて知見もあ り、3rdパーティツールとの比較・議論もそこまで無く決定した。 ● 言語はTypeScript ● 複数アプリのAWSサービス構成をaws-cdkのモノレポで管理する ● サーバサイド・インフラ標準化チームとして 4名で活動する ○ 自分自身はインフラやCI/CD周りの担当

Slide 7

Slide 7 text

複数アプリのインフラ構成をCDK管理する 出来上がった CDKディレクトリ構成抜粋。 . ├── cdk.json ├── main-common │ ├── bin │ │ └── main-common.ts │ ├── lib │ │ ├── cloudfront-functions │ │ ├── cloudfront-functions-stack.ts │ │ ├── cognito-stack.ts │ │ ├── ecr-repositories-stack.ts │ │ ├── ecs-cluster-stack.ts │ │ ├── ip-restriction-stack.ts │ │ ├── lambda │ │ ├── log-buckets-stack.ts │ │ ├── mq-stack.ts │ │ ├── rds-stack.ts │ │ ├── route53-stack.ts │ │ ├── slack-notify-stack.ts │ │ ├── vpc-lattice-network-stack.ts │ │ └── waf-stack.ts │ ├── test.json │ ├── prod.json │ └── types │ └── main-common-context.ts ├── lib │ ├── constants.ts │ ├── context-reader.ts │ ├── ecs-firelens-function.ts │ ├── ecs-service-construct.ts │ └── types │ └── common-context.ts ├── main │ ├── firelens_container.md │ ├── ABC │ │ ├── bin │ │ ├── fluentbit │ │ ├── lib │ │ ├── test.json │ │ └── types └── secure ├── README.md ├── bin │ └── secure.ts ├── cdk.json ├── common │ ├── lib │ └── secure-common-stack.ts ├── firelens_container.md ├── DEF │ ├── fluentbit │ ├── DEF-stack.ts │ ├── lib │ └── test.json ├── test ├── test.json ├── prod.json └── types └── secure-context.ts

Slide 8

Slide 8 text

複数アプリのインフラ構成をCDK管理する アプリ横断的に必要とするリソース群。 . ├── cdk.json ├── main-common │ ├── bin │ │ └── main-common.ts │ ├── lib │ │ ├── cloudfront-functions │ │ ├── cloudfront-functions-stack.ts │ │ ├── cognito-stack.ts │ │ ├── ecr-repositories-stack.ts │ │ ├── ecs-cluster-stack.ts │ │ ├── ip-restriction-stack.ts │ │ ├── lambda │ │ ├── log-buckets-stack.ts │ │ ├── mq-stack.ts │ │ ├── rds-stack.ts │ │ ├── route53-stack.ts │ │ ├── slack-notify-stack.ts │ │ ├── vpc-lattice-network-stack.ts │ │ └── waf-stack.ts │ ├── test.json │ ├── prod.json │ └── types │ └── main-common-context.ts ├── lib │ ├── constants.ts │ ├── context-reader.ts │ ├── ecs-firelens-function.ts │ ├── ecs-service-construct.ts │ └── types │ └── common-context.ts ● リソースの依存関係上、一番最初に構築する必要 がある ● 名前の通り、AWSサービス単位のcdk.Stackで分け ている ● クロスリージョンの場合、crossRegionReferences を有効化してSSMパラメータに値を保存することで スタック間参照する ● 定数や共通化した関数、L3コンストラクタを `cdk/lib/*`に保存して各アプリのcdk.Stackから呼 び出している ○ lookupやリソース命名規則の定義 ○ 複数のコンテキストファイルをマージするた めのcontext-reader ● WebアプリはECS Fargateを使う方針を決めていた のでECRやECS Clusterも先に構築している

Slide 9

Slide 9 text

複数アプリのインフラ構成をCDK管理する アプリ横断的に必要とするリソース群。 . ├── cdk.json ├── main-common │ ├── bin │ │ └── main-common.ts │ ├── lib │ │ ├── cloudfront-functions │ │ ├── cloudfront-functions-stack.ts │ │ ├── cognito-stack.ts │ │ ├── ecr-repositories-stack.ts │ │ ├── ecs-cluster-stack.ts │ │ ├── ip-restriction-stack.ts │ │ ├── lambda │ │ ├── log-buckets-stack.ts │ │ ├── mq-stack.ts │ │ ├── rds-stack.ts │ │ ├── route53-stack.ts │ │ ├── slack-notify-stack.ts │ │ ├── vpc-lattice-network-stack.ts │ │ └── waf-stack.ts ECSサービスの作成(≒デプロイ)は ECSクラスターやECRが存在している必要があり、アプリの cdk プロジェクトではECSサービスの設定やタスク定義だけを意識したい ログ保存のS3バケットはアプリごとに集約する FQDNの命名規則を最低限守るために共用するホストゾーンを作成してアプリごとにゾーン委任して もらう 凡例:{ProjectName}.{EnvName}.{ApexDomain} WAF ACLは共通・顧客向け・社内向けの 3つを作成して、一部のルールは共用している CI/CDを標準化する一環として、デプロイ通知の仕組み (EventBridgeやAWS Chatbot)も同じ仕組 みで構築する ECSサービス作成時にDB接続できないとアプリも起動できない

Slide 10

Slide 10 text

import { HopTabiWafStack } from '../lib/waf-stack' ; const regionalWafStack = new HopTabiWafStack ( app, 'HopCommonRegionalWafStack' , appContext , 'REGIONAL' , wafParameterNames .regionalAclArn , stackProps ); const globalWafStack = new HopTabiWafStack ( app, 'HopCommonGlobalWafStack' , appContext , 'CLOUDFRONT' , undefined , globalRegionStackProps ); 複数アプリのインフラ構成をCDK管理する コード例:AWS WAFをクロスリージョンで構築してスタック間参照させる const wafSsmStack = new cdk.Stack(app, 'HopCommonWafSsmStack' , { ...stackProps, crossRegionReferences: true, }); new ssm.StringParameter (wafSsmStack, 'GlobalAclArn' , { parameterName: wafParameterNames .globalAclArn, stringValue: globalWafStack .webAcl.attrArn, }); new ssm.StringParameter (wafSsmStack, 'HopGlobalCustomerAclArn' , { parameterName: wafParameterNames .globalCustomerAclArn , stringValue: globalWafStack .hopCustomerWebAcl .attrArn, }); wafSsmStack.addDependency(globalWafStack );

Slide 11

Slide 11 text

複数アプリのインフラ構成をCDK管理する アプリケーションごと (例)。 ├── main │ ├── web-app │ │ ├── bin │ │ │ └── web-app.ts │ │ ├── fluentbit │ │ │ ├── backend │ │ │ └── frontend │ │ ├── lib │ │ │ ├── acm_ap-northeast-1.ts │ │ │ ├── backend.ts │ │ │ ├── cloudfront.ts │ │ │ ├── ecs-cluster.ts │ │ │ ├── ecs-deploy-listener-automation.ts │ │ │ ├── frontend.ts │ │ │ ├── lambda_functions │ │ │ └── route53_acm.ts │ │ │ │ │ ├── test.json │ │ ├── prod.json │ │ └── types │ │ └── tabi-raku-contexts.ts ● アプリ毎のスタックでもcontext-readerを介して3つの コンテキストを使える ○ cdk.json ○ main-common/{profile}.json ○ {profile}.json ● cdkのルートディレクトリはcdk/だけだが、cdk.App() はアプリケーションごとのディレクトリでも作成するた めにcdk実行方法をカスタマイズしている ○ 参考:app コマンドの指定 ● main-commonのリソースを使う(参照)する場合は SSMパラメータストアの値から実際のAWSリソースを lookupする ● lib/配下はアプリ構成によって変わり、その中のstack 構成はアプリごとに自由に作成できる Appスタック Globalスタック SSMスタック

Slide 12

Slide 12 text

複数アプリのインフラ構成をCDK管理する コード例:cdk実行 # アプリケーションスタックのコンテキスト合成 import readContext from '@/lib/context-reader' ; import { WebAppContext } from '../types/web-app-contexts' ; const appContext = readContext (app, profileName , appRootDir ); $ npm run cdk:web-app -- ls -c profile=test # cdk/package.json抜粋 { "scripts": { "cdk:web-app": "npx cdk -a \"npx ts-node --prefer-ts-exts web-app/bin/web-app.ts \"", # cdk/lib/context-reader.ts 抜粋 const readContext = ( app: cdk.App, profileName : string, appRootDir : string ): T => { const rootProps = app.node.tryGetContext (profileName ); const appProps = JSON.parse( readFileSync (path.join(appRootDir , `${profileName }.json`), 'utf-8') ); return { ...rootProps , ...appProps }; }; コード例:コンテキスト参照の仕組み

Slide 13

Slide 13 text

複数アプリのインフラ構成をCDK管理する コード例:アプリケーションスタックの ECSクラスタ export class EcsCluster extends Construct { public readonly ecsCluster: ecs.ICluster; constructor(scope: Construct, id: string, appContext: HopUswContext) { super(scope, id); const { profileName, vpcName } = appContext; this.ecsCluster = lookupEcsCluster( scope, 'EcsCluster', profileName, 'tabiuilder', vpcName ); } } # lookupEcsCluster() 抜粋 return ecs.Cluster.fromClusterAttributes (scope, id, { clusterName, vpc, defaultCloudMapNamespace: servicediscovery .PrivateDnsNamespace .fromPrivateDnsN scope, 'Namespace', { namespaceArn, namespaceId, namespaceName } ), }); };

Slide 14

Slide 14 text

複数アプリのインフラ構成をCDK管理する 機微データを扱うアプリケーション例。 └── secure ├── README.md ├── bin │ └── secure.ts ├── cdk.json ├── common │ ├── lib │ └── secure-common-stack.ts ├── web-app │ ├── fluentbit │ ├── web-app.ts │ ├── lib │ └── test.json ├── test ├── test.json ├── prod.json └── types └── secure-context.ts ● 基本的なAWSサービス構成はアプリケーション全般と同様だが、VPC Lattice Serviceとして構築する ○ Lattice Networkはmain-common(通信元)で作成している ● VPC LatticeのL2コンストラクタはないためL1(Cfn*)コンストラクタを使うこ とになったが、標準化したアプリ構成(ALB + ECS Fargate)をそのままセ キュアな構成にすることができる ○ VPC Lattice構築では改めてクラメソさんに感謝 ○ 参考:DEVELOPERS iO 2023: VPC間通信ができる新サービスVPC Lattice

Slide 15

Slide 15 text

Tips: 複数アプリケーションを CDK管理する中で知ったこと ● リソース命名規則を強制する何らかの仕組みはあった方が良い ○ 今回はcdk/lib/constants.tsで管理している ● アプリ構成(の作り方)を標準化したい場合、L3コンストラクタや共通関数は必要になった ○ cdk/lib/ecs-service-construct.ts:ECS Fargateを作成する(ALBあり or ALBなし) ○ cdk/lib/ecs-firelens-function.ts:fluent-bitコンテナを追加してFireLens設定する ● CI/CD含めて標準化したい場合にcdk管理していることは強力 ○ ChatbotやEventBridgeでAWSからデプロイ通知 ○ GitHub Actions(ex. aws-actions/amazon-ecs-deploy-task-definition)からECSをBlue/Greenデプロイ ● リージョンが異なるAWSサービスを組み合わせるためにcrossRegionReferencesがある ○ Route 53のホストゾーンをuse1のスタックとして作成して、apne1のスタックで作成したアプリのカスタムドメイン名を そこにレコード登録する場合など ● for文を使ってAWSリソースを作成する場合、将来的にもStack内で論理IDの重複を避けられるように実装する ○ 後から論理IDを修正する場合、cdkは対象リソースをdestroy & createする... ● 標準化チームの総合力が試された 複数アプリのインフラ構成をCDK管理する

Slide 16

Slide 16 text

GitHub ActionsによるCI/CD標準化も同じチームで対応し、標準化したアプリ構成に対してデプロイパイプラインを作成しました。 appendix: CI/CD構成例

Slide 17

Slide 17 text

今後の課題 ・システム構成の安定性を重視してモノレポで管理しているが、それなりのボリュームに育ったこと で標準化チーム以外のメンバーにとって敷居も少し高くなった  →標準化によって開発プロセスまで静的に扱いたいわけではないので「真似しやすい形式を提供 する」レベルを目指している ・コストの最適化 ・CDK管理するモチベーションは少ないが、本格的なサーバレス構成はまだ無い  →現時点では運用ツールとして組み込んでいる程度 ・システム信頼性(Reliability)や可観測性(Observability)  →CDK管理による標準化で可用性は一定レベルを達成でき、 Datadog APMとの連携も容易に なったのでこれから