mazyu36の日記

某SIer所属のクラウドエンジニアのブログ

ハンズオンやWorkShopをAWS CDKで実装するようにしたら学習効果が上がった話

🎄本記事は、AWS CDK Advent Calendar 2023 の 18日目の記事となります

AWSのサービスを学ぶときにハンズオンを行うことは多いと思いますが、最近は基本的にCDKで実装しています。 メリットや具体的な流れを簡単にまとめます。

ハンズオンをCDKで実装するようにしている背景

AWSのサービスを学ぶときにハンズオンやWorkshopをマネコンでポチポチすることは多いと思うのですが、私の場合「何か知らんけどできた」みたいになり頭に残らないことが多々ありました。

※以下の記事で言及されている「腹落ち問題」。

皆さんは「ハンズオン」を実施したときに「問題なく動いた・・・! だけど・・・! なぜ動いているのかはわからない・・・!」と感じた経験はありませんか ? 私はこういう状態のことを「腹落ち問題」と個人的に呼んでいます。

aws.amazon.com

そんなときに以下の記事を見かけました。

yomon.hatenablog.com

この記事を読んでからなるほどなーと思い、ハンズオンを基本的にIaC(私の場合はCDK)で実装してみることを続けています。

メリット

個人的に感じているメリットです。

メリット1. 頭に残りやすい

ハンズオンはマネコン操作などで準備されていることが多いので、CDKで実装していく際にマネコンの設定項目をConstructのプロパティにマッピングしていくという過程が必要になります。この過程で設定値の意味を理解して正しく設定する必要があるので、頭に残りやすいと感じています

ただしトライアンドエラーが必要になったり等あるので、その分しんどい時があります(その分頭に残りやすくなると思います)。

メリット2. リソースの削除や再作成が容易

リソースを一旦全部削除する、再作成するなどが簡単にできます。これが役立つ場面としては以下2つのケースがあるかなと思います。

  • ハンズオンを中断したいとき:ハンズオンの中では時間を要するものもあり、一日でこなすことは困難で中断したいこともあります。その場合はIaCのメリットを活かして、中断時に一旦全リソースを消して課金を防止、後日再開したいときに再作成ということが容易にできます。
  • リソースの消し忘れ防止:ハンズオンで作成したリソースを消し忘れて課金されてしまった、というのはあるあるの一つだと思います。IaCだとまとめて削除ということが容易なので消し忘れを減らすことができます。例えばCDKであればハンズオンが終わったら cdk destroyで根こそぎリソースを削除するなど。

メリット3. コードを再利用できる

実装したCDKのコードを自分の資産として残せるので、将来的に再利用することが可能です。何かを構築する際に「これあのハンズオンで近いもの作ったな...」と思い出し再利用するということが多々あります。

デメリット

デメリット1. マネコン操作より時間がかかる

マネコン操作等→CDKの実装に落とし込む作業が必要なので、その分ポチポチするより時間がかかります。そのためサッと触ってサービスの概要を把握したい方には向かないかもしれません。

私も簡易にサービスの概要を確認したい場合はマネコン操作で済ませる場合もあります。メリットの1~3の方が勝ると感じた場合は、CDKで実装しています。

デメリット2. そもそもCDKで実装できない場合がある

CDKでハンズオンを行う場合、大前提としてCDKやCloudFormationが対応している可能性があります(CloudFormationが最低限対応していればL1 Constructで実装可能)。

その場合は、できる範囲でCDKで実装して一部はマネコンで構築 or 全部マネコンで構築などを行う必要があります。

ハンズオンをCDKで実装するときの流れ

私がよくやる流れを記載します。

1. 元ネタを探す

学びたいサービスが出てきたときに、元ネタとなるハンズオンを探します。

サービスによっては複数見つかることがあるので、目的に適したものを取捨選択します。単一のサービスについて深く学びたいのか、組み合わせを学びたいのか、、、等。

「サービス名 + Workshop」でググる

学びたいサービス名 + Workshop でググると該当するものが見つかることが多いです。

AWS Workshops

AWS公式のワークショップがまとまっているサイトです。サービス個別のワークショップなども豊富です。 workshops.aws

AWS 日本語ハンズオン

日本語で行えるハンズオンがまとまっているサイトです。こちらもたまに使います。

aws-samples.github.io

2. GitHubリポジトリを作成する

GitHub上でCDKプロジェクトを格納するリポジトリを作成します。

3. CDKでハンズオンを実装していく

一番の頑張りどころです。ハンズオンの手順をCDKに落とし込み実装していきます。

4. READMEにアーキテクチャ図やWorkShopのリンクをつけてプッシュ

実装が終わったらREADMEにアーキテクチャ図やWorkShopのリンクを簡単につけておきます。 WorkShopはたまにリンク自体が消滅することあるので、簡単でもアーキテクチャ図をつけておくと後々便利かと思います。

なお私はアーキテクチャ図はVSCodeのDraw.io拡張機能を使用して作成することが多いです。

実際にやってみる

実際にWorkShopを使用して、実装するまでの詳細な流れを以下に示します。 なお、事前準備としてサービスの概要の知識を仕入れておきたい場合はBlackBeltの動画を見る等しておきます。

今回私は「マネージドランタイムを使ったApp Runner」を学習したかったので、それを元にやってみます。

なお、実際に作成したものは以下になります。

github.com

1. 元ネタを探す

App Runner Workshopでググったところ、以下のWorkShopが見つかったのでこちらで実施していきます。

コンテンツをざっと眺めて、どの範囲を実施するか決めていきます(もちろん全部やる場合もあり)。

今回はマネージドランタイムに関して学習したかったので赤枠の部分をやっていきます。

2. GitHubリポジトリを作成する。

サービス名を含む名前などでリポジトリを作成します。

3. CDKでハンズオンを実装していく

メインのところです。

事前準備の実装

ハンズオンのあるあるパターンとして、主題のサービス以外の部分のCloudFormationテンプレートが提供されており、準備としてそれを元にデプロイする、というのがあります。

今回のWorkShopで言うと赤線のリポジトリのところに、サンプルAPと関連するCloudFormationテンプレートが入っています。

CloudFormationテンプレートを確認したところ、DynamoDBやApp Runner用のIAMロールが定義されています。

※DynamoDBの部分のみ抜粋。

Resources:
  Table:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: repostatus_cache
      AttributeDefinitions:
        - AttributeName: repoSlug
          AttributeType: S
      KeySchema:
        - AttributeName: repoSlug
          KeyType: HASH
      BillingMode: PAY_PER_REQUEST
      TimeToLiveSpecification:
        AttributeName: ttl
        Enabled: true

この事前準備部分についてもCDKで改めて実装することが多いです。他サービス含めて一つのデザインパターンみたいな形で資産を残せるため。

今回のDynamoDBの場合は以下のように実装しています(一部カスタマイズ)。

    // DynamoDBのテーブルを作成
    const dynamoDbTable = new dynamodb.Table(this, 'DynamoDB', {
      tableName: 'repostatus_cache',
      partitionKey: {
        name: 'repoSlug',
        type: dynamodb.AttributeType.STRING
      },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      timeToLiveAttribute: 'ttl',
      removalPolicy: cdk.RemovalPolicy.DESTROY  // Stack削除時にテーブルも消すように設定
    });

あとはハンズオン手順をCDKの実装に落とし込んでいきます。

例えば以下のランタイムや、コマンドを設定する部分に関して言うと

CDKの実装では以下の★の部分が該当します

※今回App Runnerの実装はalphaモジュールを使用しています。

import * as apprunner from '@aws-cdk/aws-apprunner-alpha';

// 中略

export class AppRunnerConstruct extends Construct {
  constructor(scope: Construct, id: string, props: AppRunnerConstructProps) {
    super(scope, id);


    // App Runner Serviceを作成
    const appRunnerService = new apprunner.Service(this, 'AppRunnerService', {
      source: apprunner.Source.fromGitHub({
        repositoryUrl: config.repositoryUrl, // 環境依存パラメータとして外だし
        branch: 'main',  
        configurationSource: apprunner.ConfigurationSourceType.API,  // ★Configuration fileの設定に相当
        codeConfigurationValues: {
          runtime: apprunner.Runtime.PYTHON_3,  // ★Runtimeの設定に相当
          buildCommand: 'pip install pipenv && pipenv install',  // ★Build commandの設定に相当
          startCommand: 'pipenv run flask run -h 0.0.0.0 -p 8080',  // ★Start Commandの設定に相当
          port: '8080',  // ★Portの設定に相当
        },
        connection: apprunner.GitHubConnection.fromConnectionArn(config.connectionARN) // 環境依存パラメータとして外だし
      })
    })

また場合によっては独自のカスタマイズをすることもあります。

今回はcdkとapを一つのGithubリポジトリに格納したかったので、「モノレポ設定」を行なっていきます。

[アップデート]AWS App Runnerは、ソースディレクトリを指定してコマンド実行できるようになり、モノレポのデプロイに対応しました | DevelopersIO

ただし執筆時点ではAlphaモジュールのConstructにモノレポ設定のためのプロパティがなかったのでEscape Hatchesで設定しています。

    // EscapeHatchesでモノレポの設定
    const appRunnerCfnService = appRunnerService.node.defaultChild as CfnService;

    // サンプルAPを格納するパスを指定
    appRunnerCfnService.addPropertyOverride('SourceConfiguration.CodeRepository.SourceDirectory', '/ap')

最後にIAMロールの設定を行なっていきます。 App RunnerからDynamoDBを操作するための権限が必要になります。

WorkShopの事前準備で提供されているCloudFormationテンプレートの以下の部分です。

  Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ['tasks.apprunner.amazonaws.com']
            Action: ['sts:AssumeRole']
      Path: /
      Policies:
        - PolicyName: ddb-access
          PolicyDocument:
            Statement:
            - Effect: Allow
              Action:
                - dynamodb:GetItem
                - dynamodb:BatchGetItem
                - dynamodb:Scan
                - dynamodb:Query
                - dynamodb:ConditionCheckItem
                - dynamodb:PutItem
                - dynamodb:UpdateItem
                - dynamodb:BatchWriteItem
                - dynamodb:DeleteItem
              Resource:
                - !GetAtt Table.Arn

今回は上記を参考にApp Runnerのロールに対して直接権限を付与します。

    // DynamoDBへのアクセス権限を付与
    appRunnerService.addToRolePolicy(
      new iam.PolicyStatement({
        actions: [
          'dynamodb:GetItem',
          'dynamodb:BatchGetItem',
          'dynamodb:Scan',
          'dynamodb:Query',
          'dynamodb:ConditionCheckItem',
          'dynamodb:PutItem',
          'dynamodb:UpdateItem',
          'dynamodb:BatchWriteItem',
          'dynamodb:DeleteItem',
        ],
        resources: [props.tableArn],  // DynamoDBテーブルのARNはpropsで受け取って設定
      })
    )

これで今回のハンズオンの実装は概ね完了です。

あとはcdk deployをして動作確認を行います。

今回はWorkShopの手順を行い、サンプルAPのページが表示されることを確認しました。

問題なければcdk deployでリソースを削除してハンズオンとしては終わりです。

4. READMEにアーキテクチャ図やWorkShopのリンクをつけてプッシュ

ハンズオンが終わったらまとめとして、READMEに後で振り返る用の情報を整理していきます。

以下イメージです。

主に書いている内容は以下です。

  • どのWorkShopを実装したか。リンクなど
  • アーキテクチャ図。作成方法は後述
  • リポジトリの構成。apも含めたモノレポ構成などしている場合は特に。
  • デプロイ方法。事前に手動のリソース作成が必要、context値の指定が必要など、単にcdk deployではデプロイを行えない場合は特に。

アーキテクチャ図はVSCodeのDraw.io拡張機能で作成してリポジトリに格納することが多いです。

qiita.com

このときによくやっていることは以下です。

  • アーキテクチャのファイル名はファイル名.drawios.svgにする。こうするとVSCodeで開いた場合はDraw.io拡張機能で編集が行え、他のツールからはsvgファイルとして扱えます。READMEからパス指定すれば画像として埋め込めるので便利です。

  • 背景は白にする。そのままだと背景が透過になり、ダークモードの場合などに見辛くなるので背景は白にします。以下の部分になります。

終わったらリポジトリにPushし一連の作業完了です。

終わりに

CDKでハンズオンを行うようになってから頭に残りやすくなり、コードを資産として持てるなどメリットを感じています。

他に、自分はハンズオンこうやっている!などテクニックがあればぜひ教えてください。