使用 Amazon Elastic Beanstalk 和 Amazon CDK Pipelines 部署 Web 应用程序

了解如何使用 Amazon Elastic Beanstalk 和 Amazon CDK Pipelines 部署 Web 应用程序,利用版本控制、变更追踪、代码审查、测试和回滚等最佳实践简化开发过程。
发布时间:2023 年 4 月 10 日
数据工程
数据分析
Python
Spark
EMR
Apache Spark
S3
教程
亚马逊云科技
Olawale Olaleye
亚马逊云科技使用经验
200 - 中级
完成所需时间
40 分钟
所需费用
示例代码

本教程中使用的示例代码来自 GitHub

上次更新时间
2023 年 4 月 10 日

作为开发人员,我们希望能避免管理底层基础设施,以最快的方式部署 Web 应用程序。要达成这一目的,重中之重是将应用程序和基础设施打包为代码,并使之通过持续集成和持续部署(CI/CD)管道运行。这样,我们就可以对其统一应用版本控制、追踪变更、审查代码、执行测试和允许回滚等最佳实践。

我们可以使用 Amazon Elastic Beanstalk 来实现这一点。这是一种易于使用的服务,用于在 Apache、Nginx、Passenger 和 IIS 等您熟知的服务器上部署和扩展使用 Java、.NET、PHP、Node.js、Python、Ruby、Go 和 Docker 开发的 Web 应用程序和服务。我们只需用单个 ZIP 文件或 WAR 文件上传代码,Elastic Beanstalk 就会自动处理容量预配、负载均衡、自动扩缩、应用程序运行状况监控等部署步骤。同时,我们仍能完全控制为应用程序提供支持的亚马逊云科技资源,可以随时访问底层资源。

除此之外,我们还可以使用代码构建 Amazon Elastic Beanstalk 资源!

Amazon 云开发工具包(Amazon CDK)是一个开源软件开发框架,借助它,您可以用您熟知的编程语言定义云基础设施,并通过 Amazon CloudFormation 对其进行预配。它有三个主要组件:对可重用基础设施组件进行建模的核心框架、与框架交互的 CLI,以及抽象了亚马逊云科技资源高级组件的构造库

CDK Pipelines(管道)是一个高级构造库,能帮您轻松地为 CDK 应用程序设置持续部署管道,它由 Amazon CodePipeline 支持。

在本指南中,我们将学习如何:

  • 创建简单的非容器化 Node.js Web 应用程序
  • 使用 Amazon CDK 执行以下操作:
    • 打包 Web 应用程序源代码
    • 使用 Amazon Elastic Beanstalk 资源创建部署基础设施,以及
    • 使用 Amazon CDK 管道创建 CI/CD 管道。

前期准备

在继续操作之前,请确保前提条件都已具备:

  • 亚马逊云科技账户,且已安装 Amazon CLI:如果您还没有账户,请按照设置亚马逊云科技环境指南操作,了解概要信息和 CLI 安装步骤。
  • 已安装 CDK:请访问 Amazon CDK 入门指南,了解更多信息。
  • GitHub 账户:请访问 GitHub.com,并按照提示创建账户。

构建 Web 应用程序

首先我们要创建将部署到云中的非容器化应用程序。本例中,我们将使用 Node.js 构建 Web 应用程序。

该 Web 应用程序将是一个简单的 Web 应用程序服务器,它将提供静态 HTML 文件,同时具有一个 REST API 端点。本教程的重点并非介绍如何构建 Web 应用程序,因此使用示例应用程序或构建自己的应用程序均可。虽然本教程侧重于使用 Node.js,但您也可以使用 Elastic Beanstalk 支持的其他编程语言(Go、Java、Node.js、PHP、Python、Ruby)、应用程序服务器(Tomcat、Passenger、Puma)和 Docker 容器构建类似的 Web 应用程序。

您可以在本地计算机或 Amazon Cloud9 环境中实现本教程内容。

创建客户端应用程序

第一步是为应用程序创建一个新目录。

mkdir my_webapp
cd my_webapp

接下来可以初始化 Node.js 项目。这将创建 package.json 文件,该文件将包含 Node.js 应用程序的所有定义。

npm init -y

如果没有安装 npm,请按照设置 Node.js 开发环境中的说明,在本地终端进行安装。

创建 Express 应用程序

我们将使用 Express 作为 Web 应用程序框架。要使用 Express,我们需要将其作为依赖项安装在 Node.js 项目中。

npm install express

运行此命令后,我们将看到该依赖项显示在 package.json 文件中。此外,还将创建 node_modules 目录和 package-lock.json 文件。

现在我们可以创建一个名为 app.js 的新文件。该文件将包含 Node.js Express 服务器所在位置的业务逻辑。

我们现在可以开始添加一些代码了。首先需要添加的是应用程序的依赖项,在本例中,我们要添加 Express 以便使用之前安装的模块,然后添加启动 Web 服务器的代码。我们将指定 Web 服务器使用端口 8080,因为这是 Elastic Beanstalk 默认使用的端口。

var express = require('express');
var app = express();
var fs = require('fs');
var port = 8080;

app.listen(port, function() {
 console.log('Server running at http://127.0.0.1:', port);
});

我们现在可以启动我们的应用程序,但它还无法执行任何操作,因为我们还没有定义任何处理请求的代码。

创建 REST API

现在我们将添加代码,为 HTTP REST API 调用提供响应。要创建第一个 API 调用,请在 app.js 文件中添加以下代码:

var express = require('express');
var app = express();
var fs = require('fs');
var port = 8080;
/*global html*/

// New code
app.get('/test', function (req, res) {
 res.send('the REST endpoint test run!');
});


app.listen(port, function() {
 console.log('Server running at http://127.0.0.1:%s', port);
});

这只是为了说明如何将 /test 端点连接到我们的代码;您可以添加不同的响应,或执行特定操作的代码。

提供 HTML 内容

我们的 Express Node.js 应用程序也可以提供静态网页服务。我们需要创建一个 HTML 页面,作为示例进行说明。我们创建一个名为 index.html 的文件。

在该文件中,添加以下 HTML,其中包含指向我们之前创建的 REST 端点的链接,以显示如何连接到后端:

<html>
 <head>
 <title>Elastic Beanstalk App</title>
 </head>

 <body>
 <h1>Welcome to the demo for ElasticBeanstalk</h1>
 <a href="/test">Call the test API</a>
 </body>
</html>

要从 Express 服务器提供该 HTML 页面,我们需要再添加一些代码,以便在调用 /path 时进行呈现。为此,请在 app.js 文件中的 /test 调用之前添加以下代码:

app.get('/', function (req, res) {
 html = fs.readFileSync('index.html');
 res.writeHead(200);
 res.write(html);
 res.end();
});

每当请求应用程序的根目录 (/) 时,此代码就会提供 index.html 文件。

在本地运行代码

现在我们可以在本地运行我们的应用程序,测试其是否正常运行。为此,我们将更新 package.json 中的脚本,使其更易于运行。在 package.json 文件中,将 scripts 部分替换为以下内容:

"scripts": {
 "start": "node app.js"
 },

现在我们可以转到终端并运行:

npm start

这将启动一个本地服务器,URL 为 http://127.0.0.1:8080 或http://localhost:8080。
当我们将此 URL 粘贴到浏览器中时,应该会看到以下内容:

要停止服务器,请按 ctrl + c,在运行 npm start 的终端停止进程。

使用 Amazon CDK 创建基础设施

我们已经有了示例应用程序,下面我们创建一个 CDK 应用程序,用它来创建使用 Amazon Elastic Beanstalk 部署 Node.js Web 应用程序所需的所有基础设施。

创建 GitHub 存储库和个人访问令牌

GitHub 上创建一个存储库,用于存储这些应用程序文件。存储库可以是公有存储库,也可以是私有存储库。

如果需要帮助,可以阅读有关如何创建存储库的 GitHub 文档

另外,最好使用令牌(而不是密码)通过 GitHub API 或命令行访问您的 GitHub 账户。有关更多信息,请阅读创建个人访问令牌

将令牌保存在安全之处,供以后使用。我们将使用此令牌实现两个目的:

  1. 提供身份验证以试运行、提交代码,并将代码从本地存储库推送到 GitHub 存储库。也可以使用 SSH 密钥实现此目的。
  2. 将 GitHub 连接到 CodePipeline,这样每当新代码提交到 GitHub 存储库时,就会自动触发管道执行。

如下图所示,该令牌需要有 repo(用于读取存储库)和 admin:repo_book(使用 webhook 时默认启用选中)的访问权限。

创建 CDK 应用程序

创建一个新目录并移至该目录。

# Assuming at this point, you are inside the my_webapp folder 
cd ..
mkdir cdk-pipeline-eb-demo
cd cdk-pipeline-eb-demo

请安装特定版本的 CDK,以与稍后安装的依赖项相匹配。

示例:

npm install cdk@2.70.0

初始化 CDK 应用程序以用于创建基础设施。

npx cdk init app —-language typescript

CDK 还将启动一个本地 Git 存储库。将分支重命名为 main。

git branch -m main

将应用程序移至 GitHub

创建 GitHub 存储库后,我们可将本地应用程序文件推送到该存储库。

将应用程序源文件移至新文件夹 src。

我们还将更新 .gitignore 文件。我们要求 git 包含 src/* 文件夹中的所有文件,​node_modules 和 package-lock.json 除外。这是为了确保每次 Beanstalk 将应用程序部署到新的虚拟机时,都会安装 node_modules。有关更多信息,请阅读有关在 Elastic Beanstalk 包中处理 Node.js 依赖项的说明。

cp -r ../my_webapp ./src
echo '!src/*'>> .gitignore
echo 'src/package-lock.json'>> .gitignore
echo 'src/node_modules'>> .gitignore

此时,我们的文件夹结构应如下所示:

在以下命令中,我们将添加当前文件夹中的所有文件,以试运行、提交,并将其推送到我们的远程 github 存储库。我们还将使用 Git 凭证缓存命令缓存凭证。

请确保将 YOUR_USERNAME 替换为您的 GitHub 组织,将 YOUR_REPOSITORY 替换为您的存储库名称。

git add .
git commit -m "initial commit"
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPOSITORY.git 
git config credential.helper 'cache --timeout=3600'
git push -u origin main

首次访问时,系统会要求提供 Git 存储库的用户名和密码,稍后会进行缓存。如果您按照前面的建议创建了令牌,则在提示输入密码时使用该令牌。

为资源堆栈创建代码

我们将删除 CDK 创建的默认文件,并为所有 Elastic Beanstalk 资源堆栈定义我们自己的代码。

只需运行以下代码即可删除 ./lib/cdk-pipeline-eb-demo.ts 并创建新文件 ./lib/eb-appln-stack.ts。

rm -rf ./lib/cdk-pipeline-eb-demo.ts
vi ./lib/eb-appln-stack.ts

将以下内容粘贴到 lib/eb-appln-stack.ts:

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// Add import statements here

export interface EBEnvProps extends cdk.StackProps {
 // Autoscaling group configuration
 minSize?: string;
 maxSize?: string;
 instanceTypes?: string;
 envName?: string;
}

export class EBApplnStack extends cdk.Stack {
 constructor(scope: Construct, id: string, props?: EBEnvProps) {
 super(scope, id, props);

 // The code that defines your stack goes here

 }
}

我们已经定义了一个 CDK Stack 类和一个 StackProps 接口,用于接受可选的堆栈属性。这些将在稍后的初始化过程中引用。

在此文件 lib/eb-appln-stack.ts 中,我们将为本部分中要创建的所有资源堆栈编写代码。您也可以从此处复制粘贴此文件的内容。

资源堆栈是一组云基础设施资源(在本例中,也就是亚马逊云科技资源),这些资源将预配到特定的账户。将在其中预配这些资源的账户是您在前期准备中配置的堆栈。在此资源堆栈中,我们将创建以下资源:

  • IAM 示例配置文件和角色:是 Amazon Identity and Access Management(IAM)角色的容器,可以用于将角色信息在实例启动时传递到 Amazon EC2 实例。
  • S3 Assets:有助于将压缩的应用程序上传到 Amazon Simple Storage Service(S3),并为 CDK 应用程序提供一种获取对象位置的方法。
  • Elastic Beanstalk 应用程序:Elastic Beanstalk 组件的逻辑集合,包括环境、版本和环境配置。
  • Elastic Beanstalk 应用程序版本:Web 应用程序中的可部署代码的某一特定的、带标签的迭代版本。应用程序版本指向一个包含可部署代码的 Amazon S3 对象。在本例中,指的就是我们将使用 S3 Assets 上传至 S3 的 zip 文件。应用程序可以有许多版本,并且每个应用程序版本都是唯一的。
  • Elastic Beanstalk 环境:运行应用程序某一版本的亚马逊云科技资源的集合。每个环境一次只运行一个应用程序版本。

自动将应用程序上传到 S3

为了部署 Web 应用程序,我们需要将其打包并上传到 Amazon S3,以便 Elastic Beanstalk 可以在环境中部署该应用程序。

为此,我们将使用一个名为 S3 Assets 的 CDK 构造函数。该 S3 Assets 模块将压缩所提供目录中的文件,并将 zip 上传到 S3。

在 lib/eb-appln-stack.ts 文件中,在该文件顶部添加依赖关系:

import * as s3assets from 'aws-cdk-lib/aws-s3-assets';

在堆栈中的 The code that defines your stack goes here 注释行下,添加以下代码:

// Construct an S3 asset Zip from directory up.
 const webAppZipArchive = new s3assets.Asset(this, 'WebAppZip', {
 path: `${__dirname}/../src`,
 });

这段代码使用 S3 Assets 模块,并获取位于 CDK 应用程序根目录中的 Web 应用程序文件夹,压缩为 zip 文件并上传到 S3。每当我们更新应用程序源代码并推送到 GitHub 存储库时,该文件都会在 S3 中自动更新。

添加 Elastic Beanstalk CDK 依赖关系

接下来,我们将创建 Elastic Beanstalk 应用程序、应用程序版本和环境,以便可以部署刚刚用 S3 Assets 上传到 S3 的 Web 应用程序。

在 lib/eb-appln-stack.ts 文件顶部添加 CDK 对 Elastic Beanstalk 模块的依赖关系。

import * as elasticbeanstalk from 'aws-cdk-lib/aws-elasticbeanstalk';

创建 Elastic Beanstalk 应用程序

现在可以创建 Elastic Beanstalk 应用程序。如前所述,Elastic Beanstalk 应用程序是 Elastic Beanstalk 组件的逻辑集合,类似于文件夹。

将以下代码放在 lib/eb-appln-stack.ts 文件中 S3 Assets 的代码下方。这段代码将在 Elastic Beanstalk 中创建名为 MyWebApp 的应用程序。

// Create a ElasticBeanStalk app.
const appName = 'MyWebApp';
const app = new elasticbeanstalk.CfnApplication(this, 'Application', {
 applicationName: appName,
});

创建 Elastic Beanstalk 应用程序版本

现在需要从前面创建的 S3 资产创建应用程序版本。下面这段代码将使用 S3 Assets 和 CDK 提供给该方法的 S3 存储桶名称和 S3 对象密钥创建应用程序版本。

// Create an app version from the S3 asset defined earlier
const appVersionProps = new elasticbeanstalk.CfnApplicationVersion(this, 'AppVersion', {
 applicationName: appName,
 sourceBundle: {
 s3Bucket: webAppZipArchive.s3BucketName,
 s3Key: webAppZipArchive.s3ObjectKey,
 },
});

继续之前,我们要确保在创建应用程序版本之前存在 Elastic Beanstalk 应用程序。我们可以对 CDK 添加依赖关系来实现,如以下代码段所示。

// Make sure that Elastic Beanstalk app exists before creating an app version
appVersionProps.addDependency(app);

创建实例配置文件

为创建 Elastic Beanstalk 环境,我们需要提供现有的实例配置文件名称。

实例配置文件是 Amazon Identity and Access Management(IAM)角色的容器,可以在实例启动时将角色信息传递到 Amazon EC2 实例。

在本例中,角色将附加到托管策略 AWSElasticBeanstalkWebTier 上,该策略授予应用程序权限,使之可以将日志上传到 Amazon S3、将调试信息上传到 Amazon X-Ray。

在本教程所用的 CDK 堆栈中导入 IAM 模块依赖关系:

import * as iam from 'aws-cdk-lib/aws-iam';

在创建应用程序版本的代码后面,添加以下代码:

// Create role and instance profile
const myRole = new iam.Role(this, `${appName}-aws-elasticbeanstalk-ec2-role`, {
 assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
});

const managedPolicy = iam.ManagedPolicy.fromAwsManagedPolicyName('AWSElasticBeanstalkWebTier')
myRole.addManagedPolicy(managedPolicy);

const myProfileName = `${appName}-InstanceProfile`

const instanceProfile = new iam.CfnInstanceProfile(this, myProfileName, {
 instanceProfileName: myProfileName,
 roles: [
 myRole.roleName
 ]
});

首先这段代码要做的是创建一个新的 IAM 角色 (myRole)

为了允许我们环境中的 EC2 实例担任该角色,实例配置文件将 Amazon EC2 指定为信任关系策略中的可信实体。

然后,我们将托管策略 AWSElasticBeanstalkWebTier 添加到该角色。然后,我们使用该角色和配置文件名称创建实例配置文件。

创建 Elastic Beanstalk 环境

最后,我们需要创建 Elastic Beanstalk 环境。环境是运行应用程序某一版本的亚马逊云科技资源集合。对于环境,我们需要提供一些有关基础设施的信息。

我们先来创建环境。创建环境时,我们需要为其提供环境名称,该名称将显示在 Elastic Beanstalk 控制台中,在本例中,我们将环境命名为 MyWebAppEnvironment。

然后,我们需要提供应用程序名称,我们将从前面的 Elastic Beanstalk 应用程序定义中获取该名称。

解决方案堆栈名称是 Elastic Beanstalk 为运行 Web 应用程序提供的托管平台的名称。使用正确的解决方案名称时,Elastic Beanstalk 可以为应用程序预配正确的资源,例如 Amazon EC2 实例。我们应该根据所选用的开发 Web 应用程序的框架和平台来选择正确的软件堆栈。此处我们将 solutionStackName 的值设为此字符串 '64bit Amazon Linux 2 v5.8.0 running Node.js 18'。在这篇博客的最后,提供了更多关于解决方案堆栈名称的信息,如果您有兴趣,可以了解一下该字符串的来源。

通过选项设置属性,可以根据需要配置 Elastic Beanstalk 环境:

  • IamInstanceProfile:我们在这里引用之前创建的实例配置文件。
  • MinSize、MaxSize 和 InstanceTypes:这些是 Elastic Beanstalk 生成的实例和自动扩缩组的配置。这些参数是可选的。如果我们不设置这些参数,Elastic Beanstalk 将根据平台定义选择实例类型以及自动扩缩组的最小和最大大小。我们将其定义为自己的默认值,这样我们就仍可享有亚马逊云科技免费套餐

有关这些设置的更多信息,请参见 Elastic Beanstalk 的配置选项

要定义这些配置选项,请添加以下代码:

// Example of some options which can be configured
const optionSettingProperties: elasticbeanstalk.CfnEnvironment.OptionSettingProperty[] = [
 {
 namespace: 'aws:autoscaling:launchconfiguration',
 optionName: 'IamInstanceProfile',
 value: myProfileName,
 },
 {
 namespace: 'aws:autoscaling:asg',
 optionName: 'MinSize',
 value: props?.maxSize ?? '1',
 },
 {
 namespace: 'aws:autoscaling:asg',
 optionName: 'MaxSize',
 value: props?.maxSize ?? '1',
 },
 {
 namespace: 'aws:ec2:instances',
 optionName: 'InstanceTypes',
 value: props?.instanceTypes ?? 't2.micro',
 },
];

如果我们没有明确为这些选项提供值,系统将考虑使用 ?? 后面的默认值。例如,如果在堆栈初始化期间我们不提供任何 InstanceTypes,那么 CDK 将考虑使用默认值 t2.micro。

最后是版本标签。这是一个重要的属性,因为需要将其用于引用我们在上一步中创建的应用程序版本。

具备这些信息后,我们现在可以创建 Elastic Beanstalk 环境了。

我们在此说明,如果在堆栈/阶段初始化期间没有提供 envName 属性,则使用默认名称 "MyWebAppEnvironment"。

在堆栈定义文件 lib/eb-appln-stack.ts 中添加以下代码:

// Create an Elastic Beanstalk environment to run the application
const elbEnv = new elasticbeanstalk.CfnEnvironment(this, 'Environment', {
 environmentName: props?.envName ?? "MyWebAppEnvironment",
 applicationName: app.applicationName || appName,
 solutionStackName: '64bit Amazon Linux 2 v5.8.0 running Node.js 18',
 optionSettings: optionSettingProperties,
 versionLabel: appVersionProps.ref,
});

创建 CDK 管道堆栈

定义空管道

定义了构成应用程序的堆栈之后,我们可以通过 CI/CD 管道进行部署。如果您想了解更多关于 CI/CD 的信息,请查看我们的 DevOps 基础知识指南

CDK 管道是一个高级构造库,能帮您轻松地为 CDK 应用程序设置持续部署管道,它由 Amazon CodePipeline 支持。

管道由几个阶段组成,这些阶段表示部署的逻辑阶段。每个阶段都包含一项或多项操作,这些操作描述了在该特定阶段要执行的任务。CDK 管道从几个预定义的阶段和操作开始。

在本步骤中,我们只创建了以下预定义阶段 - Source、Build 和 UpdatePipeline,因此它是一个空管道。在下一部分中,我们将向管道添加阶段(PublishAssets、Stage1)和操作,以满足应用程序的需要。

为了整齐、有条理,可以将管道定义放入自己的堆栈文件中。创建一个新文件 lib/cdk-pipeline-stack.ts。请注意替换以下代码中的 OWNER 和 REPO:

import { CodePipeline, CodePipelineSource, ShellStep } from 'aws-cdk-lib/pipelines';
import { Construct } from 'constructs';
import { Stack, StackProps } from 'aws-cdk-lib';

/**
 * The stack that defines the application pipeline
 */
export class CdkPipelineStack extends Stack {
 constructor(scope: Construct, id: string, props?: StackProps) {
 super(scope, id, props);

 const pipeline = new CodePipeline(this, 'Pipeline', {
 // The pipeline name
 pipelineName: 'MyServicePipeline',

 // How it will be built and synthesized
 synth: new ShellStep('Synth', {
 // Where the source can be found
 input: CodePipelineSource.gitHub('OWNER/REPO', 'main'),
 
 // Install dependencies, build and run cdk synth
 installCommands: ['npm i -g npm@latest'],
 commands: [
 'npm ci',
 'npm run build',
 'npx cdk synth'
 ],
 }),
 });

 // This is where we add the application stages
 }
}

这段代码定义了管道的以下基本属性:

  • 管道的名称
  • 在 GitHub 中查找源代码的位置。这是 Source 阶段。将新的 Git 提交推送到此存储库就会触发该管道。
  • 如何进行构建和合成。对于此使用场景,Build 阶段将安装最新的 npm 包和标准的 NPM 构建(这种类型的构建将运行 npm run build,然后运行 npx cdk synth)。

我们还要用部署管道的账户和亚马逊云科技区域来实例化 CdkPipelineStack。在 bin/cdk-pipeline-eb-demo.ts 中放入以下代码。如有必要,请务必替换其中的 ACCOUNT 和 REGION:

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

const app = new cdk.App();

new CdkPipelineStack(app, 'CdkPipelineStack', {
 env: { account: 'ACCOUNT', region: 'REGION' },
});

app.synth();

CDK 管道使用了 CDK 框架的一些新功能,我们需要明确启用这些功能。将以下内容添加到 cdk.json 文件的 "context" 部分中,并相应地添加逗号:

{
 ...
 "context": {
 "@aws-cdk/core:newStyleStackSynthesis": true
 }
}

当该功能标志设置为 true 时,CDK 合成将使用 DefaultStackSynthesizer;否则,将使用 LegacyStackSynthesizer。CDK 管道部署通过 DefaultStackSynthesizer 提供支持。此外,CDK v2 不支持 CDK v1 中的旧模板。

如果您有兴趣了解这两种内置堆栈合成器之间的区别,请阅读 CDK v1CDK v2 的文档,了解更多信息。

将 GitHub 连接到 CodePipeline

为了使 Amazon CodePipeline 从此 GitHub 存储库读取数据,我们还需要配置之前创建的 GitHub 个人访问令牌。

此令牌应当以明文密钥(而非 JSON 密钥)形式存储在 Amazon Secrets Manager 中,名称为 github-token。

在以下命令中,请将 GITHUB_ACCESS_TOKEN 替换为您的明文密钥和 REGION,然后运行它:

aws secretsmanager create-secret --name github-token --description "Github access token for cdk" --secret-string GITHUB_ACCESS_TOKEN --region REGION

如需更多帮助,请参阅创建和检索密钥

如果您有兴趣使用默认名称 github-token 之外的其他密钥名称,这篇博客的最后提供了更多信息,可供您了解。

部署 Web 应用程序

在您的账户中引导 CDK

如果这是您首次在此账户和此亚马逊云科技区域中使用 Amazon CDK,则您需要引导它。如果您不确定是否已引导,请运行以下命令。如果环境已经引导,将在必要时升级其引导堆栈。否则,什么都不会发生。

在将 Amazon CDK 应用程序部署到亚马逊云科技账户和区域中时,CDK 需要预配执行部署所需的资源。这些资源包括用于存储部署文件的 Amazon S3 存储桶,以及授予执行部署所需权限的 IAM 角色。预配这些初始资源的过程称为引导。

请运行以下命令,引导您的亚马逊云科技账户和区域:

npx cdk bootstrap aws://ACCOUNT-NUMBER/REGION

此命令应如下所示:

npx cdk bootstrap aws://123456789012/us-east-1

您可以从亚马逊云科技管理控制台获取账号,从此列表获取区域名称。

所需资源定义在名为 bootstrap stack(引导堆栈)的 Amazon CloudFormation 堆栈中,通常命名为 CDKToolkit,您可以在 CloudFormation 控制台中找到它。

构建和部署 CDK 应用程序

亚马逊云科技账户和区域引导完成后,我们就可以构建和部署 CDK 应用程序了。

第一步是构建 CDK 应用程序。

npm run build

如果我们的应用程序中没有错误,这一步应该能够顺利完成。我们现在可以将所有代码推送到 GitHub 存储库。

git add .
git commit -m "empty pipeline"
git push

我们现在可以在云中部署 CDK 应用程序。

请注意,CDK 管道创建的管道可自行改变。这意味着,我们只需要运行一次 cdk deploy 就可以启动管道。之后,当我们在源代码中添加新阶段(或 CDK 应用程序)时,管道将自动更新。

因此,作为一次性操作,部署管道堆栈:


npx cdk deploy

由于我们创建了一个新角色,因此系统将要求我们确认账户安全级别的更改。请注意,资源更改列表将比下图所示的更长。此处仅作示例参考。

键入 y,然后部署将开始。此过程需要几分钟才能完成。完成后,我们将收到一条消息,其中包含此部署为我们创建的 CloudFormation 堆栈的 ARN(Amazon 资源名称)

打开 CloudFormation 管理控制台,查看新的 CloudFormation 堆栈。

这需要几分钟才能完成。最后,我们会在 CodePipeline 控制台中发现一个管道,如下面的页面截图所示。

故障排除提示:如果在创建管道的过程中,在此步骤看到内部故障错误,请仔细检查您是否有一个 Secrets Manager 密钥,且该密钥的名称正确,在其中配置了您的 GitHub 令牌,如上一部分中所述。

为 Beanstalk 环境添加部署阶段

我们现在预配好了一个空管道,该管道还没有部署我们的 Web 应用程序。

第一步是定义我们的 Stage 子类,用于描述应用程序的单个可部署逻辑单元。这与定义 Stack 的自定义子类来描述 CloudFormation 堆栈是类似的。不同之处在于,一个 Stage 可以包含一个或多个 Stack,这样我们能够灵活地通过管道对可能复杂的应用程序创建多个副本。在这个使用场景中,我们的 Stage 仅包含一个 Stack。

创建一个新文件 lib/eb-stage.ts,并在其中输入以下代码:

import { Stage } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { EBEnvProps, EBApplnStack } from './eb-appln-stack';

/**
 * Deployable unit of web service app
 */
export class CdkEBStage extends Stage {
 
 constructor(scope: Construct, id: string, props?: EBEnvProps) {
 super(scope, id, props);

 const service = new EBApplnStack(this, 'WebService', {
 minSize : props?.minSize, 
 maxSize : props?.maxSize,
 instanceTypes : props?.instanceTypes,
 envName : props?.envName
 } );

 }
}

现在,将 CdkEBStage 的实例添加到管道。

在 lib/cdk-pipeline-stack.ts 的顶部添加一个新的导入行:

import { CdkEBStage } from './eb-stage';

并在所述注释后面添加以下代码:

// This is where we add the application stages

 // deploy beanstalk app
 // For environment with all default values:
 // const deploy = new CdkEBStage(this, 'Pre-Prod');

 // For environment with custom AutoScaling group configuration
 const deploy = new CdkEBStage(this, 'Pre-Prod', { 
 minSize : "1",
 maxSize : "2"
 });
 const deployStage = pipeline.addStage(deploy); 

这里,我们为 minSize 和 maxSize 提供了自定义值。我们使用 CDK 堆栈中定义的默认 instanceTypes 和 environment name。如果要在管道中添加另一个阶段,请确保为 envName 添加自定义值,以区分 Beanstalk 环境。

我们现在所要做的就是提交并推送,管道将自动重新配置自己,以添加新阶段并部署到其中。先运行 npm run build,以确保没有拼写错误。

为此,运行以下命令:

npm run build
git add .
git commit -m 'Add Pre-Prod stage'
git push

在 CodePipeline 控制台中,UpdatePipeline 阶段识别另一个阶段的新代码后,它将自行改变并添加 2 个新阶段,一个是 Assets,另一个是 Pre-Prod。

UpdatePipeline 阶段成功完成,管道将再次从头开始运行。这次,不会在 UpdatePipeline 阶段处停止。将进一步转换到新阶段 ​Assets 和 Pre-prod,以部署 Beanstalk 应用程序、环境和 ​my_webapp 应用程序。

在 CloudFormation 控制台中,我们会发现 Pre-Prod 阶段的 2 个新 CloudFormation 堆栈。

Pre-Prod-WebService 这一堆栈包含我们在前一模块中创建的所有 Elastic Beanstalk 资源:Elastic Beanstalk 应用程序、应用程序版本、实例配置文件、环境。

名字中有随机字符串 awseb-e-randomstring-stack 的另一个堆栈是由 Elastic Beanstalk 创建的,它包含 Elastic Beanstalk 应用程序运行所需的所有资源:自动扩缩组、实例、Amazon CloudWatch 警报和指标、负载均衡器和安全组。

查看在云中部署的应用程序

在管道运行完最后的 Pre-Prod 阶段后,我们可以确认服务已启动并正在运行。

要找到这个 URL,请您转到亚马逊云科技管理控制台中的 Elastic Beanstalk 服务,然后查找名为 MyWebAppEnvironment 的环境。点击该 URL 以启动 Web 应用程序。

应用程序现在应该可以从任何位置进行访问。

更新 Node.js 应用程序部署

如果我们想对 Web 应用程序进行更改,并将其重新部署到云,请执行以下步骤:

  • 在 Web 应用程序中进行更改
  • 试运行、提交,并将更改推送到 GitHub 存储库。
git add . && git commit -am 'YOUR COMMIT MESSAGE GOES HERE'&& git push

就是这么简单!

只要代码推送到 GitHub 存储库,就会触发 CodePipeline。它将自动在整个管道中运行代码,并将新的应用程序版本部署到我们的 Elastic Beanstalk 环境中。这需要一些时间。

CodePipeline 的 Pre-prod 阶段成功完成后,我们就可以验证是否部署了新版本的 Elastic Beanstalk 应用程序。只需在浏览器中刷新应用程序 URL 即可查看已部署的更改。

清理资源

使用 Amazon CDK 和 CloudFormation 管理所有基础设施的好处在于,清理亚马逊云科技环境非常轻松。在 CDK 应用程序目录中运行以下命令:

cdk destroy

我们可以访问 Amazon CloudFormation 管理控制台来验证 CdkPipelineStack 堆栈是否已删除。

但是,请注意,当我们运行 cdk deploy 命令时,仅会销毁由 cdk 创建的资源。该命令不会销毁 Pre-Prod 阶段部署的 2 个 CloudFormation 堆栈。

我们必须从 CloudFormation 控制台或使用以下命令手动删除 Pre-Prod-WebService:

aws cloudformation delete-stack --stack-name Pre-Prod-WebService

如果堆栈删除失败,并显示错误 Cannot delete entity, must detach all policies first.,则请从 IAM 控制台手动删除名称以 Pre-Prod-WebService-MyWebAppawselasticbeanstalkec2-randomstring 开头的 IAM 角色,然后重试删除 CloudFormation 堆栈。如需帮助,请阅读亚马逊云科技文档中的删除角色或实例配置文件

转到 CloudFormation 控制台,检查是否已成功删除创建的所有三个堆栈。您可以选择删除在引导过程中创建的名为 CDKToolkit 的 CloudFormation 堆栈。

最后,CDK 管道会创建一个 Amazon S3 存储桶,用于存储构件。即使在删除堆栈之后,此存储桶也会保留。清空并删除以 cdkpipelinestack-pipelineartifactsbucket<random-letters> 开头的存储桶。有关更多信息,请阅读 S3 文档中的清空删除存储桶。

总结

恭喜您!我们现在已经学会了如何在云中部署非容器化应用程序。我们创建了一个简单的 Node.js Web 应用程序,然后在 Amazon CDK 的帮助下,利用 Amazon Elastic Beanstalk 资源创建了部署基础设施,利用 Amazon CDK 管道创建了 CI/CD 管道。

更多信息和故障排除

使用 CDK 库的多个版本

使用 CDK 时可能会遇到的一个常见错误是,当您导入库并开始在应用程序中使用时,单词 this 会突出显示,并提示编译错误。

这可能是因为您使用的 CDK 模块版本不同于 CDK 核心库的版本。CDK 经常会有更新,所以这种错误很常见。

要修复此问题,需要将所有 CDK 包更新到同一版本。可以在 CDK 应用程序的 package.json 文件中查看CDK 包的版本。

Elastic Beanstalk 解决方案堆栈名称

在此文档中,可以了解 Elastic Beanstalk 支持的所有平台。随着新平台的增加和旧平台的停用,我们会更新此页面。

如果您想知道如何获得正确的平台名称,例如,64bit Amazon Linux 2 v5.8.0 running Node.js 18,可以使用 Amazon CLI 获取所有受支持的平台列表

aws elasticbeanstalk list-available-solution-stacks

这条命令将返回一个冗长的受支持平台字符串列表,您可以在 CDK 应用程序中使用这些平台字符串。

用于将 GitHub 存储库与 CodePipeline 相连接的 GitHub 令牌

如果您的管道创建失败,并显示错误 Access Denied,无法访问源代码 GitHub 存储库,说明您没有名为 github-token 的密钥,这是 CDK 中的默认值。

如果您有不同名称的令牌,例如 github-access-token-secret,则必须在 ​lib/cdk-pipeline-stack.ts 中更新 CDK 代码。使用以下代码导入 cdk 并添加具有密钥名称的身份验证:

import * as cdk from 'aws-cdk-lib';

const pipeline = new CodePipeline(this, 'Pipeline', {
 // The pipeline name
 pipelineName: 'MyServicePipeline',

 // How it will be built and synthesized
 synth: new ShellStep('Synth', {
 // Where the source can be found
 input: CodePipelineSource.gitHub('OWNER/REPO', 'main', {
 authentication: cdk.SecretValue.secretsManager('`github-access-token-secret`'), }),