Read parameters across AWS Regions with AWS CloudFormation custom resources

Đọc các tham số qua các Khu vực AWS bằng tài nguyên tùy chỉnh AWS CloudFormation

Một thách thức khi triển khai các ứng dụng đa bộ AWS Cloud Development Kit (AWS CDK) là chia sẻ các tham số giữa các tài nguyên trong các Khu vực AWS khác nhau. Trong khi bạn có thể sử dụng đầu ra AWS CloudFormation để đọc các tham số giữa các bộ trong cùng một Khu vực, bạn không thể làm điều này qua các Khu vực khác nhau. Ví dụ, bạn có thể muốn phục vụ một trang web tĩnh với Amazon CloudFront từ một Khu vực khác đọc một chứng chỉ SSL ở Khu vực khác. Câu trả lời nằm ở việc sử dụng tài nguyên tùy chỉnh AWS CloudFormation.

Trong bài viết này, chúng tôi sẽ hướng dẫn cách triển khai một phân phối CloudFront được liên kết với tường lửa ứng dụng web và một chứng chỉ SSL ở Khu vực khác. Chúng tôi lưu trữ Amazon Resource Numbers (ARNs) của chứng chỉ SSL và tường lửa ứng dụng web trong Parameter Store, một khả năng của AWS Systems Manager. Chúng tôi tạo một tài nguyên tùy chỉnh để CloudFront có thể đọc ARN được lưu trữ trong một Khu vực khác. Chúng tôi triển khai toàn bộ giải pháp với một construct gồm ba bộ AWS CDK stacks.

Về bài đăng blog này
Thời gian đọc~10 phút
Thời gian hoàn thành~30 phút
Chi phí hoàn thành~$1
Learning levelAdvanced (300)
AWS servicesAWS Cloud Development Kit (AWS CDK)Amazon CloudFrontAWS WAFAWS Systems Manager Parameter StoreAWS Certificate ManagerAmazon Simple Storage Service (Amazon S3)

Tổng quan

Hình 1 hiển thị các tài nguyên bạn triển khai trong hướng dẫn.

Hình 1. Sơ đồ kiến trúc cho triển khai ứng dụng web đa khu vực

Giải pháp triển khai các tài nguyên sau đây:

  • Trong Khu vực US East (N. Virginia) (us-east-1):
    • AWS WAF để quản lý các yêu cầu của CloudFront.
    • AWS Certificate Manager (ACM) để cung cấp và quản lý chứng chỉ SSL.
  • Trong Khu vực Europe (Frankfurt) (eu-central-1):
    • CloudFront để phục vụ nội dung trang web tĩnh.
    • Parameter Store để lưu trữ ARN của chứng chỉ SSL và tường lửa ứng dụng web.
    • Một nhóm Amazon Simple Storage Service (Amazon S3) để lưu trữ nội dung trang web tĩnh.

Yêu cầu tiên quyết

Trước khi bắt đầu, đảm bảo bạn có những điều sau:

Hướng dẫn

Trong hướng dẫn, bạn thực hiện các bước sau:

  • Bước 1: Khởi tạo ứng dụng AWS CDK.
  • Bước 2: Tạo một tài nguyên tùy chỉnh.
  • Bước 3: Tạo stack chứng chỉ SSL.
  • Bước 4: Tạo stack AWS WAF.
  • Bước 5: Tạo stack S3.
  • Bước 6: Tạo một thư mục S3 cho nội dung trang web.
  • Bước 7: Xác minh và kiểm tra giải pháp.

Bước 1: Khởi tạo ứng dụng AWS CDK

Khởi tạo ứng dụng AWS CDK bằng mẫu ứng dụng AWS CDK và TypeScript.

“`bash

cdk init app –language typescript

“`

Bước 2: Tạo một tài nguyên tùy chỉnh

Để tham chiếu các tham số từ Parameter Store, hãy tạo một tài nguyên tùy chỉnh bằng cách sử dụng construct AwsCustomResource. Trong thư mục lib của dự án, tạo và lưu một tệp có tên là ssm-parameter-reader.ts và chứa mã sau đây:

import { Arn, Stack } from ‘aws-cdk-lib’;

import * as CustomResource from ‘aws-cdk-lib/custom-resources’;

import { Construct } from ‘constructs’;

interface SSMParameterReaderProps {

  parameterName: string;

  region: string;

}

function removeLeadingSlash(value: string): string {

  return value.slice(0, 1) == ‘/’ ? value.slice(1) : value;

}

export class SSMParameterReader extends CustomResource.AwsCustomResource {

  constructor(scope: Construct, name: string, props: SSMParameterReaderProps) {

    const { parameterName, region } = props;

    const ssmAwsSdkCall: CustomResource.AwsSdkCall = {

      service: ‘SSM’,

      action: ‘getParameter’,

      parameters: {

        Name: parameterName,

      },

      region,

      physicalResourceId: CustomResource.PhysicalResourceId.of(Date.now().toString()),

    };

    const ssmCrPolicy = CustomResource.AwsCustomResourcePolicy.fromSdkCalls({

      resources: [

        Arn.format(

          {

            service: ‘ssm’,

            region: props.region,

            resource: ‘parameter’,

            resourceName: removeLeadingSlash(parameterName),

          },

          Stack.of(scope),

        ),

      ],

    });

    super(scope, name, { onUpdate: ssmAwsSdkCall, policy: ssmCrPolicy });

  }

  public getParameterValue(): string {

    return this.getResponseField(‘Parameter.Value’).toString();

  }

}

Mã trong tệp ssm-parameter-reader.ts chứa các nội dung sau:

  • Một tài nguyên tùy chỉnh để đọc các tham số trong Parameter Store (ssmAwsSdkCall).
  • Một chính sách để cho phép gọi tài nguyên tùy chỉnh trong quá trình tạo stack (ssmCrPolicy).

Bước 3: Tạo stack chứng chỉ SSL

Tạo stack đầu tiên. stack triển khai một chứng chỉ SSL cho tên miền tùy chỉnh được quản lý bằng ACM tại Khu vực US East (N. Virginia) (us-east-1). Trong thư mục lib của dự án, hãy tạo và lưu một tệp có tên là certificate-stack.ts chứa mã sau đây:


import * as route53 from ‘aws-cdk-lib/aws-route53’;

import * as acm from ‘aws-cdk-lib/aws-certificatemanager’;

import { CfnOutput, Stack } from ‘aws-cdk-lib’;

import { Construct } from ‘constructs’;

import { StringParameter } from ‘aws-cdk-lib/aws-ssm’

export interface StaticSiteProps {

    domainName: string;

    siteSubDomain: string;

}

export class CertificateStack extends Construct {

    constructor(parent: Stack, name: string, props: StaticSiteProps) {

        super(parent, name);

        const zone = route53.HostedZone.fromLookup(this, ‘Zone’, { domainName: props.domainName });

        const siteDomain = props.siteSubDomain + ‘.’ + props.domainName;

        new CfnOutput(this, ‘Site’, { value: ‘https://’ + siteDomain });

        // TLS certificate

        const certificateArn = new acm.DnsValidatedCertificate(this, ‘SiteCertificate’, {

            domainName: siteDomain,

            hostedZone: zone,

            region: ‘us-east-1’, // Cloudfront only checks this region for certificates.

        }).certificateArn;

        new CfnOutput(this, ‘Certificate’, { value: certificateArn });

        new StringParameter(this, ‘CertificateARN’, {

            parameterName: “CERTIFICATE_ARN”,

            description: ‘Certificate ARN to be used with Cloudfront’,

            stringValue: certificateArn

        });

    }

}

Mã trong certificate-stack.ts thực hiện các công việc sau:

  • Nhập zone của bạn được lưu trữ trên Route 53 (zone).
  • Tạo một tên miền (siteDomain) bằng cách nối các tham số siteSubDomain và domainName.
  • Tạo một chứng chỉ SSL được liên kết với tên miền (Certificate).
  • Tạo một tham số kiểu chuỗi chứa ARN của chứng chỉ SSL (CertifiateArn).

Bước 4: Tạo stack AWS WAF

Tạo một stack triển khai tường lửa ứng dụng web cho Khu vực US East (N. Virginia) (us-east-1). Trong thư mục lib của dự án, hãy tạo và lưu một tệp có tên là waf-stack.ts chứa mã sau đây:

import { Stack, StackProps } from ‘aws-cdk-lib’;

import { Construct } from ‘constructs’;

import * as wafv2 from ‘aws-cdk-lib/aws-wafv2’;

import { StringParameter } from ‘aws-cdk-lib/aws-ssm’

export class WafStack extends Construct {

    constructor(parent: Stack, name: string, props: StackProps) {

        super(parent, name);

        const webacl = new wafv2.CfnWebACL(this, “BasicWaf”, {

            defaultAction: {

                allow: {}

            },

            scope: “CLOUDFRONT”,

            visibilityConfig: {

                cloudWatchMetricsEnabled: false,

                metricName: “waf-country-block-acl”,

                sampledRequestsEnabled: false,

            },

        });

        new StringParameter(this, ‘WebAclID’, {

            parameterName: “WEBACL_ID”,

            description: ‘Web ACL ID’,

            stringValue: webacl.attrArn

        });

    }

}

Mã trong tệp waf-stack.ts bao gồm các thành phần sau:

  • Một tường lửa ứng dụng web cơ bản (webacl) với CloudFront là phạm vi.
  • Tham số kiểu chuỗi Parameter Store (WEBACL_ID) chứa ARN của tường lửa ứng dụng web.

Bước 5: Tạo stack S3

Tạo một stack để triển khai một stack S3. Trong thư mục lib của dự án, hãy tạo và lưu một tệp có tên là s3-stack.ts chứa mã sau đây:

import * as route53 from ‘aws-cdk-lib/aws-route53’;

import * as s3 from ‘aws-cdk-lib/aws-s3’;

import * as s3deploy from ‘aws-cdk-lib/aws-s3-deployment’;

import * as targets from ‘aws-cdk-lib/aws-route53-targets’;

import * as cloudfront from ‘aws-cdk-lib/aws-cloudfront’;

import * as cloudwatch from “aws-cdk-lib/aws-cloudwatch”;

import * as iam from ‘aws-cdk-lib/aws-iam’;

import { Aws, CfnOutput, RemovalPolicy, Stack } from ‘aws-cdk-lib’;

import { Construct } from ‘constructs’;

import { SSMParameterReader } from ‘./ssm-parameter-reader’;

export interface StaticSiteProps {

    domainName: string;

    siteSubDomain: string;

}

export class S3Stack extends Construct {

    constructor(parent: Stack, name: string, props: StaticSiteProps) {

        super(parent, name);

        const zone = route53.HostedZone.fromLookup(this, ‘Zone’, { domainName: props.domainName });

        const siteDomain = props.siteSubDomain + ‘.’ + props.domainName;

        const cloudfrontOAI = new cloudfront.OriginAccessIdentity(this, ‘cloudfront-OAI’, {

            comment: `OAI for ${name}`

        });

        new CfnOutput(this, ‘Site’, { value: ‘https://’ + siteDomain });

        // Content bucket

        const siteBucket = new s3.Bucket(this, ‘SiteBucket’, {

            bucketName: siteDomain,

            websiteIndexDocument: ‘index.html’,

            websiteErrorDocument: ‘error.html’,

            publicReadAccess: false,

            blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,

     removalPolicy: RemovalPolicy.RETAIN, 

            autoDeleteObjects: false,

        });

        // Grant access to cloudfront

        siteBucket.addToResourcePolicy(new iam.PolicyStatement({

            actions: [‘s3:GetObject’],

            resources: [siteBucket.arnForObjects(‘*’)],

            principals: [new iam.CanonicalUserPrincipal(cloudfrontOAI.cloudFrontOriginAccessIdentityS3CanonicalUserId)]

        }));

        new CfnOutput(this, ‘Bucket’, { value: siteBucket.bucketName });

        const certificateArnReader = new SSMParameterReader(this, ‘CertificateARNReader’, {

            parameterName: “CERTIFICATE_ARN”,

            region: ‘us-east-1’

        });

        const webAclIdReader = new SSMParameterReader(this, ‘WebAclIdReader’, {

            parameterName: “WEBACL_ID”,

            region: ‘us-east-1’

        });

        new CfnOutput(this, ‘Certificate’, { value: certificateArnReader.getParameterValue() });

        const viewerCertificate = cloudfront.ViewerCertificate.fromAcmCertificate({

            certificateArn: certificateArnReader.getParameterValue(),

            env: {

                region: Aws.REGION,

                account: Aws.ACCOUNT_ID

            },

            node: this.node,

            stack: parent,

            metricDaysToExpiry: () => new cloudwatch.Metric({

                namespace: “TLS Viewer Certificate Validity”,

                metricName: “TLS Viewer Certificate Expired”,

            }),

            applyRemovalPolicy: function (policy: RemovalPolicy): void {

                throw new Error(‘Function not implemented.’);

            }

        },

            {

                sslMethod: cloudfront.SSLMethod.SNI,

                securityPolicy: cloudfront.SecurityPolicyProtocol.TLS_V1_1_2016,

                aliases: [siteDomain]

            })

        // CloudFront distribution

        const distribution = new cloudfront.CloudFrontWebDistribution(this, ‘SiteDistribution’, {

            viewerCertificate,

            originConfigs: [

                {

                    s3OriginSource: {

                        s3BucketSource: siteBucket,

                        originAccessIdentity: cloudfrontOAI

                    },

                    behaviors: [{

                        isDefaultBehavior: true,

                        compress: true,

                        allowedMethods: cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,

                    }],

                }

            ],

            webACLId: webAclIdReader.getParameterValue()

        });

        new CfnOutput(this, ‘DistributionId’, { value: distribution.distributionId });

        // Route53 alias record for the CloudFront distribution

        new route53.ARecord(this, ‘SiteAliasRecord’, {

            recordName: siteDomain,

            target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),

            zone

        });

        // Deploy site contents to S3 bucket

        new s3deploy.BucketDeployment(this, ‘DeployWithInvalidation’, {

            sources: [s3deploy.Source.asset(‘./site-contents’)],

            destinationBucket: siteBucket,

            distribution,

            distributionPaths: [‘/*’],

        });

    }

}

Đoạn mã trong tệp s3-stack.ts bao gồm các thành phần sau đây:

  • Một hộp S3 để lưu trữ nội dung trang web tĩnh.
  • Hai người đọc để lấy các giá trị sau từ Parameter Store:
    • ARN chứng chỉ SSL từ tham số CertificateArn.
    • ARN tường lửa ứng dụng web từ tham số WEBACL_ID.
  • Một chứng chỉ CloudFront xem dựa trên CertificateArn được nhập.
  • Một phân phối CloudFront với chứng chỉ xem và ARN tường lửa ứng dụng web, để liên kết phân phối với tường lửa.
  • Cấu trúc BucketDeployment để tải nội dung trang web tĩnh vào hộp S3.

Bước 6: Tạo một thư mục S3 cho nội dung trang web tĩnh

Tiếp theo, tạo một thư mục con có tên ./site-contents. Trong thư mục này, tạo và lưu trữ một tệp có tên index.html chứa mã sau đây. Đây là nội dung mà giải pháp cung cấp từ Amazon S3.

<!doctype html>

<html>

  <head>

    <title>This is the title of the webpage!</title>

  </head>

  <body>

    <p>This is an example paragraph. Anything in the <strong>body</strong> tag will appear on the page, just like this <strong>p</strong> tag and its contents.</p>

  </body>

</html>

Bước 7: Triển khai ứng dụng AWS CDK

Để triển khai giải pháp, trước hết gói lại các stack Amazon S3, tường lửa ứng dụng web và chứng chỉ SSL vào một ứng dụng duy nhất. Trong thư mục bin của dự án, hãy tạo và lưu tệp có tên app.ts chứa mã sau đây. Mã chứa lệnh AWS CDK synth, dịch các tài nguyên được định nghĩa trong ba stack thành một mẫu CloudFormation. Trong mã, thay thế mọi trường hợp của ACCOUNT_ID bằng ID tài khoản AWS của bạn.


import ‘source-map-support/register’;

import * as cdk from ‘aws-cdk-lib’;

import { S3Stack } from ‘../lib/s3-stack’;

import { CertificateStack } from ‘../lib/certificate-stack’;

import { WafStack } from ‘../lib/waf-stack’;

class MyStaticSiteStack extends cdk.Stack {

  constructor(parent: cdk.App, name: string, props: cdk.StackProps) {

    super(parent, name, props);

    new S3Stack(this, ‘StaticSite’, {

      domainName: “DOMAIN_NAME”,

      siteSubDomain: “www”,

    });

  }

}

class MyCertificateStack extends cdk.Stack {

  constructor(parent: cdk.App, name: string, props: cdk.StackProps) {

    super(parent, name, props);

    new CertificateStack(this, ‘CertificateStack’, {

      domainName: “DOMAIN_NAME”,

      siteSubDomain: “www”,

    });

  }

}

class MyWafStack extends cdk.Stack {

  constructor(parent: cdk.App, name: string, props: cdk.StackProps) {

    super(parent, name, props);

    new WafStack(this, ‘WafStack’, {});

  }

}

const app = new cdk.App();

async function main() {

  const certificateStack = new MyCertificateStack(app, ‘MyCertificateStack’, {

    env: {

      account: “ACCOUNT_ID”,

      region: ‘us-east-1’,

    }

  });

  const wafStack = new MyWafStack(app, ‘MyWafStack’, {

    env: {

      account: “ACCOUNT_ID”,

      region: ‘us-east-1’,

    }

  });

  const mySiteStack = new MyStaticSiteStack(app, ‘MyStaticSite’, {

    env: {

      account: “ACCOUNT_ID”,

      region: ‘eu-central-1’,

    }

  })

  mySiteStack.addDependency(certificateStack);

  mySiteStack.addDependency(wafStack);

  app.synth();

}

main();

Sau đó, chạy các lệnh sau để triển khai ứng dụng.

cdk bootstrap

cdk deploy –all

Bước 8: Xác minh và kiểm tra giải pháp

  1. Trong bảng điều khiển Amazon CloudWatch, chọn Khu vực Châu Âu (Frankfurt) (eu-central-1) từ thanh công cụ phía trên.
  2. Chọn Stacks (stack). Xác minh rằng các stack MyStaticSite và CDKToolkit hiển thị trong danh sách, như hình 2.

Hình 2. Các stack đã triển khai tới eu-central-1.

  1. Chọn Khu vực US East (N. Virginia) (us-east-1) từ thanh công cụ phía trên.
  2. Chọn Stacks (stack). Xác minh rằng các stack MyWafStack ứng dụng web, MyCertificateStack chứng chỉ SSL và CDKToolkit đã triển khai hiển thị trong danh sách, như hình 3.

Các stack đã triển khai tới us-east-1.

  1. Chọn MyWafStack.
  2. Chọn Outputs (Đầu ra).
  3. Chọn URL trong cột Giá trị cho khóa StackSite. Xác minh rằng nội dung từ ./site-contents/index.html hiển thị trên trình duyệt của bạn.

Dọn dẹp

Khi bạn đã hoàn thành việc kiểm tra triển khai, hãy chạy lệnh sau để xóa các stack.

cdk destroy

Hoặc, để xóa các stack bằng cách sử dụng bảng điều khiển, xem Xóa một stack trên bảng điều khiển AWS CloudFormation.

Lưu ý: Việc giữ AWL CLI cài đặt không tạo ra chi phí trong tương lai. Để biết thêm thông tin, xem Cài đặt, cập nhật và gỡ bỏ cài đặt AWS CLI phiên bản 2.

Kết luận

Trong bài viết này, chúng ta đã triển khai một ứng dụng web trên nhiều Khu vực sử dụng AWS CDK. Chúng ta đã tạo một cấu trúc AWS CDK để triển khai tài nguyên trong nhiều ngăn xếp và tạo một tài nguyên tùy chỉnh để đọc giá trị tham số trên nhiều Khu vực.

Tùy chỉnh giải pháp được sử dụng trong bài viết này để tạo các cấu trúc và tài nguyên tùy chỉnh cho các triển khai qua Khu vực khác nhau trong AWS Cloud. Ví dụ, bạn có thể xây dựng một cấu trúc để triển khai một hàm AWS Lambda có quyền truy cập vào một bảng Amazon DynamoDB ở Khu vực khác. Bạn có thể sử dụng tài nguyên tùy chỉnh để chia sẻ thông tin về bảng và khóa được quản lý bởi khách hàng qua các Khu vực khác nhau. Để biết thêm thông tin, hãy xem thư viện aws/constructs trên GitHub và tìm hiểu về Construct Hub – hệ thống đăng ký trực tuyến của thư viện CDK.

Hãy cho chúng tôi biết bạn sử dụng AWS CDK và tài nguyên tùy chỉnh như thế nào để triển khai các giải pháp qua nhiều Khu vực. Sử dụng phần Bình luận để đặt câu hỏi và bình luận.