使用 AWS CodePipeline 在 Amazon EC2 上部署 NodeJS 应用程序
结论
由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!
尽管大多数开发者都在转向无服务器和容器化架构来构建应用程序,但 EC2 实例仍然是最流行、使用最广泛的 AWS 服务之一。在本博客中,我将引导您完成使用 AWS CodePipeline 在 Amazon EC2 上部署可扩展 NodeJS 应用程序所需的步骤,并提及您在设置此解决方案时可能遇到的一些挑战。乍一看似乎很简单,但相信我,它需要的精力比您预想的要多,而这正是我今天撰写这篇博客的主要原因。
好了,不多说了,现在让我们摇滚起来吧! 🎸
本博客涵盖的服务包括:
我假设您已经使用您首选的方法(手动、CDK、CloudFormation、Terraform 等)成功设置了底层基础设施。
您已配置好 EC2 实例、CodeDeploy Agent 和自动扩展组,并在 EC2 实例上安装了最新版本的 Nginx、NodeJS 和 PM2,现在准备通过 AWS CodePipeline 部署您的 NodeJS 应用程序。首先,您需要创建一个新的 Pipeline 项目,连接到您的源代码提供商(例如 GitHub),然后使用 CodeBuild 编译源代码并运行一些单元测试。最后,选择 AWS CodeDeploy 通过部署组将最新版本部署到 Amazon EC2 实例上。难点在于 buildspec.yml 和 appspec.yml 文件,您可以在其中设置用于构建和部署代码的一系列命令。首先想到的是创建以下 buildspec 和 appspec 文件。
buildspec.yml 文件
version: 0.2
phases:
install:
runtime-versions:
nodejs: 10
commands:
- echo Installing
pre_build:
commands:
- echo Installing source NPM dependencies.
- npm install
build:
commands:
- echo Build started on `date`
- echo Compiling the Node.js code
- npm run build
post_build:
commands:
- echo Build completed on `date`
artifacts:
files:
- '**/*'
appspec.yml 文件
version: 0.0
os: linux
files:
- source: /
destination: /usr/share/nginx/html
你将代码推送到版本控制系统(在本例中是 GitHub),并触发了你的第一个 CodePipeline 流水线,猜猜会发生什么?流水线在这个阶段成功完成。现在,我们正兴奋地使用“npm start”运行我们的 Node 脚本,却突然遇到了以下错误:
Error: Cannot find module '../package.json'
但是该如何解决呢?我们非常确定 package.json 文件位于根目录下,而 libraries 位于 node_modules 文件夹中。说实话,解决这个问题的唯一办法就是运行npm rebuild或删除 node_modules 文件夹,然后npm install在 EC2 实例上重新运行。完成这些操作后,您就可以启动 Node 脚本了。这固然不错,但并不符合我们的需求。我们想要的是一个完全自动化的部署,无需任何人工干预。幸运的是, Code Deploy 的 appspec.yml 文件中的生命周期事件钩子部分可以帮我们解决这个问题,它创建了几个 bash 脚本来替换 Code Build 执行的“npm install 和 build”步骤,从而将 AWS Code Build 仅用于测试用例阶段。以下是我们现在的两个文件的样子:
buildspec.yml 文件
version: 0.2
phases:
pre_build:
commands:
- echo Installing source NPM dependencies...
- npm install
build:
commands:
- echo Build started on `date`
- echo Compiling the Node.js code
- echo Running unit tests
- npm test
post_build:
commands:
- echo Build completed on `date`
artifacts:
files:
- '**/*'
appspec.yml 文件
version: 0.0
os: linux
files:
- source: /
destination: /usr/share/nginx/html
hooks:
BeforeInstall:
- location: scripts/BeforeInstallHook.sh
timeout: 300
AfterInstall:
- location: scripts/AfterInstallHook.sh
timeout: 300
- BeforeInstall:用于在创建替换任务集之前运行任务。一个目标组与原始任务集关联。如果指定了可选的测试监听器,则该监听器与原始任务集关联。此时无法回滚。
#!/bin/bash
set -e
yum update -y
pm2 update
- AfterInstall:用于在创建替换任务集并将其与某个目标组关联后运行任务。如果指定了可选的测试监听器,则该监听器与原始任务集关联。此生命周期事件中钩子函数的结果可以触发回滚。
#!/bin/bash
set -e
cd /usr/share/nginx/html
npm install
npm run build
注意:我们设置了 -e 标志,以便在发生错误时停止脚本的执行。
即使更新了 appspec 和 buildspec 文件,您可能还会遇到以下问题:The deployment failed because a specified file already exists at this location: /usr/share/nginx/html/.cache/plugins/somefile.js
在本例中,我们将通过简单地要求 CodeDeploy 使用该选项替换已存在的文件来解决这个问题overwrite:true。
最终的 appspec.yml 文件
version: 0.0
os: linux
files:
- source: /
destination: /usr/share/nginx/html
overwrite: true
hooks:
BeforeInstall:
- location: scripts/BeforeInstallHook.sh
timeout: 300
AfterInstall:
- location: scripts/AfterInstallHook.sh
timeout: 300
完美!AWS CodePipeline 成功部署后,我们现在可以顺利启动 npm 脚本,不会遇到任何问题。接下来,我们需要使用 PM2(一款负责运行和管理 Node.js 应用的进程管理工具)在每次部署后自动重启应用。
只需sudo npm install pm2@latest -g在您的 EC2 实例上运行此命令,然后生成 pm2 ecosystem.config.js 文件,以声明您希望将代码部署到的应用程序/服务pm2 ecosystem。PM2 将为您生成一个示例文件,请确保其与您的应用程序结构相匹配。
ecosystem.config.js 文件
module.exports = {
apps : [{
name: "npm",
cwd: '/usr/share/nginx/html',
script: "npm",
args: 'start',
env: {
NODE_ENV: "production",
HOST: '0.0.0.0',
PORT: '3000',
},
}]
}
现阶段,您只需运行pm2 start ecosystem.config.jsPM2 即可启动您的应用程序。但这并非 PM2 的全部功能。只需在 ecosystem.config.js 文件中添加 watch 参数,该模块即可在每次新版本发布时自动重启您的应用程序。
最终的 ecosystem.config.js 文件
module.exports = {
apps : [{
name: "npm",
cwd: '/usr/share/nginx/html',
script: "npm",
args: 'start',
watch: true,
env: {
NODE_ENV: "production",
HOST: '0.0.0.0',
PORT: '3000',
},
}]
}
太棒了!我们已经建立了一个全自动的部署管道,可以运行单元测试,在 Amazon EC2 实例上安装、构建和部署节点模块,然后 PM2 会负责为我们重启应用程序。
如果服务器因为某种原因重启了怎么办?我们希望应用程序能够自动启动,这也可以通过pm2 startup在应用程序启动后执行的参数来实现。
我们之前是不是漏掉了什么?哦,对了!自动扩缩容。
我们希望确保生产环境具有足够的扩展性,能够应对应用程序的巨大负载。
这可以通过 AWS CodeDeploy 轻松实现,只需将部署组环境配置从 Amazon EC2 实例的“标记策略”更新为 Amazon EC2 自动扩展组即可。AWS CodeDeploy 的这项强大功能可以自动将最新版本部署到新实例,并在整个部署过程中保持所需数量的实例处于健康状态。然而,我们在这里会遇到另一个挑战。PM2 启动机制确保应用程序在任何实例重启后都能启动,但遗憾的是,当自动扩展组启动新实例时,它无法正常工作,因此在水平扩展的情况下,应用程序不会自动运行。不过别担心,我会帮你解决这个问题!
要解决此问题,请转到启动配置设置,并在“userdata”部分中添加以下 bash 脚本。
#!/bin/bash -ex
# restart pm2 and thus node app on reboot
crontab -l | { cat; echo "@reboot sudo pm2 start /usr/share/nginx/html/ecosystem.config.js -i 0 --name \"node-app\""; } | crontab -
# start the server
pm2 start /usr/share/nginx/html/ecosystem.config.js -i 0 --name "node-app"
好了!现在您拥有了一个高度可扩展的 NodeJS 应用程序,它使用 AWS CodePipeline 实现了完全自动化。
结论
希望这篇博客对大家有所帮助。我尽量让这篇博客读起来像个故事,因为写它的主要目的是向大家展示DevOps工程师和开发人员在搭建这套解决方案时面临的诸多挑战,以及他们使用的各种解决方案。我会持续更新这个项目,并确保它有改进计划,因为我知道它还可以做得更好!
参考:
- https://regbrain.com/article/node-nginx-ec2
- https://pm2.keymetrics.io/docs/usage/startup
- https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-20-04
- https://cloudnweb.dev/2019/12/a-complete-guide-to-aws-elastic-load-balancer-using-nodejs/
- https://pm2.keymetrics.io/docs/usage/watch-and-restart/
- https://pm2.keymetrics.io/docs/usage/application-declaration/#cli