CDK Constructs 是 AWS 雲端資源的程式碼抽象層,讓開發者能以程式設計方式定義和管理雲端架構。Constructs 分為 L1、L2 和 L3 三個層級,分別對應 CloudFormation 資源、帶有輔助功能的 Constructs 以及高階的最佳實務模式。理解 Constructs 的層級關係和使用方法,有助於更有效地組織和管理 CDK 程式碼,並簡化雲端資源的佈署流程。本文將以 S3 Bucket 建立為例,示範如何利用 CDK 建構自定義 Constructs,並進一步探討如何整合 DynamoDB 和 ECS 等服務,構建完整的 Web 應用程式。

AWS CDK 概念深入解析:Constructs 的組織與實踐

AWS CDK(Cloud Development Kit)是一種強大的工具,允許開發者使用程式語言定義雲端基礎架構。在 CDK 的世界中,Constructs 是核心概念,代表了 AWS 雲端元件的封裝。本篇文章將探討 Constructs 的組織、分類別及其在實際專案中的應用。

Constructs 的本質與分類別

Constructs 是 CDK 中的基本建構單元,可以視為類別(classes)。它們可以簡單到僅代表單一資源的宣告,也可以複雜到代表整個負載平衡的網頁伺服器。AWS CDK 提供了標準的 Construct library,詳細檔案可參考官方檔案。

根據抽象層級的不同,CDK Constructs 主要分為以下三類別:

  1. L1 Constructs(CFN Resources):這是最低層級的 Constructs,直接對應到 CloudFormation 中的資源宣告。它們以 Cfn 為字首,例如 CfnBucket 代表 AWS::S3::Bucket
  2. L2 Constructs:建立在 L1 Constructs 之上,提供額外的便利功能和預設值。例如 s3.Bucket 提供了 grantPublicAccess() 方法,簡化了 S3 存取控制的設定。
  3. L3 Constructs(Patterns):最高層級的 Constructs,代表複雜的最佳實踐模式。例如 ApplicationLoadBalancedFargateService 可以快速建立負載平衡的 ECS Fargate 容器服務。

程式碼解析:自定義 S3 Bucket Construct

以下是一個自定義的 S3Bucket Construct 範例,用於在不同環境中建立 S3 Bucket:

import { RemovalPolicy } from "aws-cdk-lib";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";

interface Props {
  environment: string;
}

export class S3Bucket extends Construct {
  public readonly bucket: Bucket;

  constructor(scope: Construct, id: string, props: Props) {
    super(scope, id);
    const bucketName = props.environment === 'production' ? 'bucket-s3' : 'bucket-s3-dev';
    this.bucket = new Bucket(this, 'Bucket-S3', {
      bucketName,
      removalPolicy: RemovalPolicy.DESTROY,
      publicReadAccess: true,
    });
  }
}

內容解密:

  1. 匯入必要的模組:從 aws-cdk-libconstructs 匯入需要的類別和介面。
  2. 定義 Props 介面:用於傳遞環境變數給 Construct。
  3. 建立 S3Bucket 類別:繼承自 Construct,代表自定義的 S3 Bucket Construct。
  4. 建構函式(Constructor):初始化 S3 Bucket,根據環境變數設定 Bucket 名稱,並設定刪除策略和公開存取許可權。

圖表說明:Constructs 的層級關係

圖表翻譯: 此圖示展示了 CDK 中 Constructs 的層級關係,從基本的 Construct 類別衍生出 L1、L2 和 L3 三種不同層級的 Constructs。

在主 Stack 中使用自定義 Construct

要在主 Stack 中使用上述自定義的 S3Bucket Construct,需要在 infrastructure/lib/index.ts 中匯入並例項化它:

import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { S3Bucket } from './constructs/S3Bucket';

export class WebStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    new S3Bucket(this, 'S3Bucket', {
      environment: 'development', // 或 'production'
    });
  }
}

使用 AWS CDK 建構完整應用程式

在前一章中,我們學習了單一儲存函式庫(monorepos)的概念,以及它們如何幫助組織 CDK 程式碼與更高層級的區隔,例如基礎設施、前端和後端程式碼。正如我們之前提到的,在使用 CDK 時,這並不是一個固定的規則。隨著流程的不斷演進和更多開發者將 CDK 應用於他們的專案中,CDK 開發社群一定會發現更好的方法。就目前而言,這種組織程式碼的方式已經足夠好了,所以讓我們來看看如何在實際應用中實作它。

在本章中,我們將學習以下主要主題:

  • 建構根據 Node.js 和 Express.js 的後端 API
  • 建立與 API 整合的 React 應用程式
  • 利用 AWS CDK 將所有內容整合,並使用諸如 Elastic Container Service(ECS)和 DynamoDB 等服務
  • CDK 如何協助為 ECS 建構 Docker 映像檔

注意事項

在本章中,我們還將使用 Docker 工具。您可以透過造訪 https://docs.docker.com/get-docker/ 來找到適合您作業系統的安裝設定。

建立自訂建構模組(Construct)

首先,讓我們來看看如何建立自訂的 L2 建構模組。以下是一個範例程式碼:

import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';

export class MyRemovableBucket extends cdk.Construct {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, 'MyRemovableBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      environment: 'development'
    });
  }
}

內容解密:

此程式碼定義了一個名為 MyRemovableBucket 的自訂建構模組,它繼承自 cdk.Construct。在建構函式中,我們使用 super 關鍵字呼叫父類別的建構函式。然後,我們建立了一個新的 S3 儲存桶,並將其命名為 MyRemovableBucket。我們還設定了 removalPolicyDESTROY,這意味著當堆積疊被刪除時,儲存桶將被刪除。此外,我們還傳遞了一個 environment 屬性,其值為 'development'

使用 Construct Hub

AWS 提供了一個名為 Construct Hub 的平台,用於分享和管理建構模組。您可以造訪 https://constructs.dev/ 來檢視 AWS 的嘗試結果。在 Construct Hub 上,您可以找到由 AWS 或社群開發者開發的各種實用建構模組。

CDK 應用程式架構

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title AWS CDK Constructs 深入解析與實踐

package "AWS 雲端架構" {
    package "網路層" {
        component [VPC] as vpc
        component [Subnet] as subnet
        component [Security Group] as sg
        component [Route Table] as rt
    }

    package "運算層" {
        component [EC2] as ec2
        component [Lambda] as lambda
        component [ECS/EKS] as container
    }

    package "儲存層" {
        database [RDS] as rds
        database [DynamoDB] as dynamo
        storage [S3] as s3
    }

    package "服務層" {
        component [API Gateway] as apigw
        component [ALB/NLB] as lb
        queue [SQS] as sqs
    }
}

apigw --> lambda
apigw --> lb
lb --> ec2
lb --> container
lambda --> dynamo
lambda --> s3
ec2 --> rds
container --> rds
vpc --> subnet
subnet --> sg
sg --> rt

@enduml

圖表翻譯: 此圖表呈現了 CDK 應用程式的架構。它顯示了 CDK 應用程式如何包含多個堆積疊,而每個堆積疊又包含多個建構模組。這些建構模組可以代表不同的 AWS 資源,例如 S3 儲存桶和 DynamoDB 表格。

使用 CDK 建置全端應用程式

在探討 CDK 的強大功能之前,我們先來瞭解如何使用它建置一個完整的前後端應用程式。本章節將引導你完成一個 TODO 應用程式的建置,該程式由 React 前端、Express.js 後端以及 DynamoDB 資料函式庫組成。

前端建置

首先,我們需要建置前端資產,以便 CDK 可以將其上傳到 AWS S3 進行託管。執行以下步驟:

  1. 切換到 web 目錄並執行 yarn 安裝依賴套件。
  2. 執行 yarn build 編譯 React 資產。

成功執行後,你將在 build 目錄下看到編譯好的前端資產。

後端建置

接下來,我們來建置後端服務。執行以下步驟:

  1. 切換到 server 目錄。
  2. 設定環境變數:export AWS_PROFILE=cdkexport PORT=3001
  3. 執行 yarn 安裝依賴套件。
  4. 執行 yarn dev 啟動後端服務。

基礎設施建置

現在,讓我們來建置 CDK 應用程式的基礎設施部分。執行以下步驟:

  1. 切換到 infrastructure 目錄。
  2. 執行 yarn 安裝依賴套件。
  3. 執行 cdk synth 合成 CDK 應用程式。
  4. 執行 cdk deploy --profile cdk 佈署應用程式。

佈署完成後,你將看到輸出中的 API URL 和前端 URL。請在瀏覽器中開啟前端 URL(記得加上 http:///index.html)。

程式碼範例:CDK 佈署

$ cdk deploy --profile cdk

內容解密:

此命令用於佈署 CDK 應用程式。其中,--profile cdk 指定了要使用的 AWS 組態檔。CDK 將根據你的組態檔中的憑證資訊佈署應用程式。

疑難排解:修正前端程式碼

當你嘗試使用 TODO 應用程式時,你可能會發現前端無法呼叫後端 API。這是因為前端程式碼中的 backend_url 設定為 http://localhost:3333/,而非 CDK 佈署輸出的實際後端 URL。

要修正此問題,請開啟 ./web/src/components/Main/index.ts 檔案並更新 backend_url 常數:

const backend_url = 'http://Chapt-LB8A1-ZXACMP3PMFL0-1880097408.us-east-1.elb.amazonaws.com';

程式碼範例:更新 backend_url

const backend_url = 'http://你的實際後端URL';

內容解密:

此段程式碼定義了前端用於呼叫後端 API 的 URL。請將其替換為 CDK 佈署輸出的實際後端 URL,以確保前端可以正確呼叫後端服務。

使用 CDK 建構全端應用程式

錯誤的做法與正確佈署

手動複製一個命令的輸出並將其儲存到檔案中以佈署堆積疊絕對不是正確的佈署方式。我們將在第4章中介紹如何最佳地完成這項工作。

儲存檔案並透過在終端機中導航到 /web 目錄來重新構建前端,然後執行以下命令:

$ yarn build

接下來,前往終端機視窗中的 /infrastructure 目錄,並透過執行以下命令重新佈署堆積疊:

$ cdk deploy --profile cdk

觀察結果

您會注意到堆積疊的佈署時間變短了。這是因為 CDK 和底層的 AWS CloudFormation 足夠智慧,只更改需要修改的部分,在本例中是前端程式碼。

等待堆積疊佈署完成,並複製新的 FrontendURL,在開頭新增 http://,在結尾新增 /index.html,然後透過網頁瀏覽器導航到該頁面。再次新增 TODO 專案,您將看到它正確地進行了 API 呼叫: 此圖示 – TODO 專案新增成功

讓我們來看看我們的 DynamoDB 資料函式庫的狀態。登入您的 AWS 控制檯,並在服務列表中找到 DynamoDB。點選左側選單上的 Tables 按鈕。點選 main_table,然後點選右上角的大橙色按鈕,即 Explore table items

檢查 CDK 程式碼

CDK 程式碼結構

正如您已經猜到的那樣,CDK 程式碼位於 /infrastructure 目錄中,CDK 入口點是 /infrastructure/bin/chapter-3.ts 檔案:

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { Chapter3Stack } from '../lib/chapter-3-stack';

const app = new cdk.App();
new Chapter3Stack(app, 'Chapter3Stack', {});

這裡沒有什麼特別的,我們只是告訴它載入 CDK 應用程式的根堆積疊,即 Chapter3Stack。開啟 /infrastructure/lib/chapter-3-stack.ts 檔案:

export class Chapter3Stack extends Stack {
  public readonly dynamodb: Dynamodb;
  public readonly s3: S3;
  public readonly ecs: ECS;

  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    this.dynamodb = new Dynamodb(this, 'Dynamodb');
    this.s3 = new S3(this, 'S3');
    this.ecs = new ECS(this, 'ECS', {
      dynamodb: this.dynamodb,
    });
  }
}

我們的堆積疊由三個主要構件組成:DynamoDB 資料函式庫、用於存放前端檔案的 S3 儲存桶,以及託管後端的 ECS 服務。請注意,一旦我們例項化了 DynamoDB 表格,ECS 服務就會獲得對它的參照。這是合理的,因為我們的後端需要知道如何連線到 DynamoDB 表格。

DynamoDB 表格

使用 CDK 建立 DynamoDB 表格非常簡單。我們只需要從 aws-cdk-lib/aws-dynamodb 包中例項化 Table 類別。開啟 /infrastructure/lib/constructs/Dynamodb.ts 檔案:

constructor(scope: Construct, id: string) {
  super(scope, id);
  this.main_table = new Table(scope, 'MainTable', {
    partitionKey: {
      name: 'partition_key',
      type: AttributeType.STRING,
    },
    sortKey: {
      name: 'sort_key',
      type: AttributeType.STRING,
    },
    tableName: 'main_table',
    billingMode: BillingMode.PAY_PER_REQUEST,
    removalPolicy: RemovalPolicy.DESTROY
  });
}

內容解密:

  • partitionKeysortKey 是 DynamoDB 資料函式庫的索引屬性。
  • tableName 是表格的名稱,您可以在 AWS 控制檯中看到它。
  • billingMode 有兩種計費模式:PROVISIONEDPAY_PER_REQUEST。每種模式在不同的情況下都很有用。
  • removalPolicy 設定為 DESTROY,表示當堆積疊被銷毀時,表格及其內容將被刪除。

S3 儲存桶

我們的 React 前端應用程式是一個由瀏覽器渲染的單頁應用程式(SPA),S3 可以充當我們的檔案的內容伺服器。

開啟 /infrastructure/lib/constructs/S3.ts 檔案:

this.web_bucket = new Bucket(scope, 'WebBucket', {
  bucketName: `chapter-3-web-bucket-${uuidv4()}`,
  websiteIndexDocument: 'index.html',
  websiteErrorDocument: 'index.html',
  publicReadAccess: true,
  removalPolicy: RemovalPolicy.DESTROY,
  autoDeleteObjects: true,
});

內容解密:

  • bucketName 是儲存桶的名稱,我們使用 uuidv4() 生成一個唯一的名稱。
  • websiteIndexDocumentwebsiteErrorDocument 都設定為 index.html,這意味著當使用者存取我們的網站時,S3 將提供 index.html 檔案。
  • publicReadAccess 設定為 true,表示儲存桶中的檔案可以被公開讀取。
  • removalPolicy 設定為 DESTROY,表示當堆積疊被銷毀時,儲存桶將被刪除。
  • autoDeleteObjects 設定為 true,表示當儲存桶被刪除時,其中的物件也將被刪除。