

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 将 Node.js Express 应用程序部署到 Elastic Beanstalk
<a name="create_deploy_nodejs_express"></a>

本部分演示如何使用 Elastic Beanstalk 命令行界面（EB CLI）向 Elastic Beanstalk 部署示例应用程序，然后更新该应用程序以使用 [Express](http://expressjs.com/) 框架。

## 先决条件
<a name="create_deploy_nodejs_express.prerequisites"></a>

本教程需要以下先决条件：
+ Node.js 运行时
+ 默认 Node.js 程序包管理器软件 npm
+ Express 命令行生成器
+ Elastic Beanstalk 命令行界面 (EB CLI)

有关安装列出的前三个组件和设置本地开发环境的详细信息，请参阅 [为 Elastic Beanstalk 设置 Node.js 开发环境](nodejs-devenv.md)。在本教程中，您无需安装 AWS 适用于 Node.js 的 SDK，参考主题中也提到了这一点。

有关安装和配置 EB CLI 的详细信息，请参阅 [使用设置脚本安装 EB CLI（推荐）](eb-cli3.md#eb-cli3-install) 和 [配置 EB CLI](eb-cli3-configuration.md)。

## 创建 Elastic Beanstalk 环境
<a name="create_deploy_nodejs_express.eb_init-rds"></a>

**您的应用程序目录**  
本教程为应用程序源包使用名为 `nodejs-example-express-rds` 的目录。为本教程创建 `nodejs-example-express-rds` 目录。

```
~$ mkdir nodejs-example-express-rds
```

**注意**  
本章中的每个教程都为应用程序源包使用自己的目录。该目录名称与教程使用的示例应用程序的名称相匹配。

将您当前的工作目录更改为 `nodejs-example-express-rds`。

```
~$ cd nodejs-example-express-rds
```

现在，让我们设置运行 Node.js 平台和示例应用程序的 Elastic Beanstalk 环境。我们将使用 Elastic Beanstalk 命令行界面（EB CLI）。

**要为您的应用程序配置 EB CLI 存储库，并创建运行 Node.js 平台的 Elastic Beanstalk 环境**

1. 使用 **[**eb init**](eb3-init.md)** 命令创建存储库。

   ```
   ~/nodejs-example-express-rds$ eb init --platform node.js --region <region>
   ```

   此命令在名为 `.elasticbeanstalk` 的文件夹中创建配置文件，该配置文件指定用于为您的应用程序创建环境的设置；并创建以当前文件夹命名的 Elastic Beanstalk 应用程序。

1. 使用 **[**eb create**](eb3-create.md)** 命令创建运行示例应用程序的环境。

   ```
   ~/nodejs-example-express-rds$ eb create --sample nodejs-example-express-rds
   ```

   此命令使用 Node.js 平台的默认设置以及以下资源来创建负载均衡环境：
   + **EC2 实例** — 配置为在您选择的平台上运行 Web 应用程序的亚马逊弹性计算云 (Amazon EC2) 虚拟机。

     各平台运行一组特定软件、配置文件和脚本以支持特定的语言版本、框架、Web 容器或其组合。大多数平台使用 Apache 或 NGINX 作为 Web 应用程序前的反向代理，向其转发请求、提供静态资产以及生成访问和错误日志。
   + **实例安全组** — 配置为允许端口 80 上的入站流量的 Amazon EC2 安全组。此资源允许来自负载均衡器的 HTTP 流量到达运行您的 Web 应用程序的 EC2 实例。默认情况下，其他端口不允许流量进入。
   + **负载均衡器** – 配置为向运行您的应用程序的实例分配请求的 Elastic Load Balancing 负载均衡器。负载均衡器还使您无需将实例直接公开在 Internet 上。
   + **负载均衡器安全组**-配置为允许端口 80 上的入站流量的 Amazon EC2 安全组。利用此资源，HTTP 流量可从 Internet 到达负载均衡器。默认情况下，其他端口不允许流量进入。
   + **Auto Scaling 组** – 配置为在实例终止或不可用时替换实例的 Auto Scaling 组。
   + **Amazon S3 存储桶** – 使用 Elastic Beanstalk 时创建的源代码、日志和其他构件的存储位置。
   + **Amazon CloudWatch CloudWatch 警**报 — 两个警报，用于监控您环境中实例的负载，并在负载过高或过低时触发。警报触发后，您的 Auto Scaling 组会扩展或收缩以进行响应。
   + **CloudFormation 堆栈** — Elastic CloudFormation Beanstalk 用于在您的环境中启动资源并传播配置更改。这些资源在您可通过 [CloudFormation 控制台](https://console.aws.amazon.com/cloudformation)查看的模板中定义。
   + **域名**-以表单形式路由到您的 Web 应用程序的域名**subdomain*。 *region*.elasticbeanstalk.com。*
**域安全**  
为增强 Elastic Beanstalk 应用程序的安全性，已将 *elasticbeanstalk.com* 域注册到 [公共后缀列表（PSL）](https://publicsuffix.org/)。  
如果您需要在 Elastic Beanstalk 应用程序的默认域名中设置敏感 Cookie，我们建议您使用带有前缀 `__Host-` 的 Cookie 来提高安全性。这种做法可以保护您的域免遭跨站点请求伪造（CSRF）攻击。要了解更多信息，请参阅 Mozilla 开发者网络中的 [Set-Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes) 页面。

1. 当环境创建完成后，使用 [**eb open**](eb3-open.md) 命令在默认浏览器中打开环境 URL。

   ```
   ~/nodejs-example-express-rds$ eb open
   ```

您现在已经使用示例应用程序创建了 Node.js Elastic Beanstalk 环境。您可以使用自己的应用程序对其进行更新。接下来，我们会更新示例应用程序，以使用 Express 框架。

## 更新应用程序以使用 Express
<a name="create_deploy_nodejs_express.update"></a>

在创建具有示例应用程序的环境后，可将其更新为自己的应用程序。在此过程中，首先运行 **express** 和 **npm install** 命令，以在您的应用程序目录中设置 Express 框架。然后，使用 EB CLI 通过更新后的应用程序更新您的 Elastic Beanstalk 环境。

**更新您的应用程序以使用 Express**

1. 运行 `express` 命令。这将生成 `package.json`、`app.js`，以及几个目录。

   ```
   ~/nodejs-example-express-rds$ express
   ```

   在系统提示您是否要继续时，键入 **y**。
**注意**  
如果 **express** 命令不起作用，则您可能没有按照前面的*先决条件*部分所述安装 Express 命令行生成器。或者，可能需要设置本地计算机的目录路径设置才能运行 **express** 命令。有关设置开发环境的详细步骤，请参阅*先决条件*部分，以便您可以继续学习本教程。

1. 设置本地依赖项。

   ```
   ~/nodejs-example-express-rds$ npm install
   ```

1. （可选）验证 Web 应用程序服务器已启动。

   ```
   ~/nodejs-example-express-rds$ npm start
   ```

   您应该可以看到类似于如下所示的输出内容：

   ```
   > nodejs@0.0.0 start /home/local/user/node-express
   > node ./bin/www
   ```

   默认情况下，服务器在端口 3000 上运行。要测试，请在另一个终端中运行 `curl http://localhost:3000`，或在本地计算机上打开浏览器并输入 URL 地址 `http://localhost:3000`。

   按 **Ctrl\$1C** 以停止该服务器。

1. 使用 [**eb deploy**](eb3-deploy.md) 命令将更改部署到您的 Elastic Beanstalk 环境。

   ```
   ~/nodejs-example-express-rds$ eb deploy
   ```

1. 在环境变为绿色并准备就绪后，刷新 URL 以验证环境是否工作。您应看到一个显示 **Welcome to Express**（欢迎使用 Express）的网页。

接下来，让我们更新 Express 应用程序以使用静态文件并添加新页面。

**配置静态文件并向 Express 应用程序添加新页面**

1. 添加包含以下内容的 [`.ebextensions`](ebextensions.md) 文件夹中的第二个配置文件：

   **`nodejs-example-express-rds/.ebextensions/staticfiles.config`**

   ```
   option_settings:
       aws:elasticbeanstalk:environment:proxy:staticfiles:
           /stylesheets: public/stylesheets
   ```

   此设置将代理服务器配置为从应用程序的 `public` 路径上的 `/public` 文件夹中提供文件。从代理服务器静态提供文件可减少应用程序的负载。有关更多信息，请参阅本章前面的[静态文件](create_deploy_nodejs.container.md#nodejs-platform-console-staticfiles)。

1. （可选）要确认静态映射配置正确，请注释掉 `nodejs-example-express-rds/app.js` 中的静态映射配置。这将从节点应用程序中删除映射。

   ```
   //  app.use(express.static(path.join(__dirname, 'public'))); 
   ```

   即使在将此行注释掉之后，上一步的 `staticfiles.config` 文件中的静态文件映射仍应成功加载样式表。要验证静态文件映射是通过代理静态文件配置而不是快速应用程序加载的，请删除 `option_settings:` 后的值。将其从静态文件配置和节点应用程序中删除后，样式表将无法加载。

   测试完成后，记得重置 `nodejs-example-express-rds/app.js` 和 `staticfiles.config` 的内容。

1. 添加 `nodejs-example-express-rds/routes/hike.js`。键入以下内容：

   ```
   exports.index = function(req, res) {
    res.render('hike', {title: 'My Hiking Log'});
   };
   
   exports.add_hike = function(req, res) {
   };
   ```

1. 更新 `nodejs-example-express-rds/app.js` 以包含三个新行。

   首先，添加以下行来为此路由添加 `require`：

   ```
   var hike = require('./routes/hike');
   ```

   您的文件应类似于以下代码段：

   ```
   var express = require('express');
   var path = require('path');
   var hike = require('./routes/hike');
   ```

   然后，将以下两行添加到 `nodejs-example-express-rds/app.js` 中的 `var app = express();` 后面

   ```
   app.get('/hikes', hike.index);
   app.post('/add_hike', hike.add_hike);
   ```

   您的文件应类似于以下代码段：

   ```
   var app = express();
   app.get('/hikes', hike.index);
   app.post('/add_hike', hike.add_hike);
   ```

1. 将 `nodejs-example-express-rds/views/index.jade` 复制到 `nodejs-example-express-rds/views/hike.jade`。

   ```
   ~/nodejs-example-express-rds$ cp views/index.jade views/hike.jade
   ```

1. 使用 [**eb deploy**](eb3-deploy.md) 命令部署更改。

   ```
   ~/nodejs-example-express-rds$ eb deploy
   ```

1. 您的环境将在几分钟后进行更新。在环境变为绿色并准备就绪后，通过刷新浏览器并将 **hikes** 追加到 URL 末尾（例如 `http://node-express-env-syypntcz2q.elasticbeanstalk.com/hikes`），验证环境是否正常工作。

   您应看到标题为 **My Hiking Log** 的网页。

现在，您已经创建使用 Express 框架的 Web 应用程序。在下一节中，我们将修改应用程序以使用 Amazon Relational Database Service（RDS）来存储 Hiking 日志。

## 更新应用程序以使用 Amazon RDS
<a name="create_deploy_nodejs_express.add_rds"></a>

在下一步中，我们将应用程序更新为使用 Amazon RDS for MySQL。

**要更新您的应用程序以使用 RDS for MySQL**

1. 要创建耦合到您的 Elastic Beanstalk 环境的 RDS for MySQL 数据库，请按照本章后面所包含的[添加数据库](create-deploy-nodejs.rds.md)主题中的说明进行操作。添加一个数据库实例大约需要 10 分钟。

1.  使用以下内容更新 `package.json` 中的依赖项部分：

   ```
   "dependencies": {
       "async": "^3.2.4",
       "express": "4.18.2",
       "jade": "1.11.0",
       "mysql": "2.18.1",
       "node-uuid": "^1.4.8",
       "body-parser": "^1.20.1",
       "method-override": "^3.0.0",
       "morgan": "^1.10.0",
       "errorhandler": "^1.5.1"
     }
   ```

1. 运行 **npm install**。

   ```
   ~/nodejs-example-express-rds$ npm install
   ```

1. 更新 `app.js` 以连接到数据库、创建表并插入单个默认 Hiking 日志。每次部署此应用程序时，它都会删除之前的 hikes 表并重新创建它。

   ```
   /**
    * Module dependencies.
    */
   
    const express = require('express')
    , routes = require('./routes')
    , hike = require('./routes/hike')
    , http = require('http')
    , path = require('path')
    , mysql = require('mysql')
    , async = require('async')
    , bodyParser = require('body-parser')
    , methodOverride = require('method-override')
    , morgan = require('morgan')
    , errorhandler = require('errorhandler');
   
   const { connect } = require('http2');
   
   const app = express()
   
   app.set('views', __dirname + '/views')
   app.set('view engine', 'jade')
   app.use(methodOverride())
   app.use(bodyParser.json())
   app.use(bodyParser.urlencoded({ extended: true }))
   app.use(express.static(path.join(__dirname, 'public')))
   
   
   app.set('connection', mysql.createConnection({
   host: process.env.RDS_HOSTNAME,
   user: process.env.RDS_USERNAME,
   password: process.env.RDS_PASSWORD,
   port: process.env.RDS_PORT}));  
   
   function init() {
    app.get('/', routes.index);
    app.get('/hikes', hike.index);
    app.post('/add_hike', hike.add_hike);
   }
   
   const client = app.get('connection');
   async.series([
    function connect(callback) {
      client.connect(callback);
      console.log('Connected!');
    },
    function clear(callback) {
      client.query('DROP DATABASE IF EXISTS mynode_db', callback);
    },
    function create_db(callback) {
      client.query('CREATE DATABASE mynode_db', callback);
    },
    function use_db(callback) {
      client.query('USE mynode_db', callback);
    },
    function create_table(callback) {
       client.query('CREATE TABLE HIKES (' +
                           'ID VARCHAR(40), ' +
                           'HIKE_DATE DATE, ' +
                           'NAME VARCHAR(40), ' +
                           'DISTANCE VARCHAR(40), ' +
                           'LOCATION VARCHAR(40), ' +
                           'WEATHER VARCHAR(40), ' +
                           'PRIMARY KEY(ID))', callback);
    },
    function insert_default(callback) {
      const hike = {HIKE_DATE: new Date(), NAME: 'Hazard Stevens',
            LOCATION: 'Mt Rainier', DISTANCE: '4,027m vertical', WEATHER:'Bad', ID: '12345'};
      client.query('INSERT INTO HIKES set ?', hike, callback);
    }
   ], function (err, results) {
    if (err) {
      console.log('Exception initializing database.');
      throw err;
    } else {
      console.log('Database initialization complete.');
      init();
    }
   });
   
   module.exports = app
   ```

1. 将以下内容添加到 `routes/hike.js`。这将使路线能够将新的 Hiking 日志插入 *HIKES* 数据库。

   ```
   const uuid = require('node-uuid');
   exports.index = function(req, res) {
     res.app.get('connection').query( 'SELECT * FROM HIKES', function(err,
   rows) {
       if (err) {
         res.send(err);
       } else {
         console.log(JSON.stringify(rows));
         res.render('hike', {title: 'My Hiking Log', hikes: rows});
     }});
   };
   exports.add_hike = function(req, res){
     const input = req.body.hike;
     const hike = { HIKE_DATE: new Date(), ID: uuid.v4(), NAME: input.NAME,
     LOCATION: input.LOCATION, DISTANCE: input.DISTANCE, WEATHER: input.WEATHER};
     console.log('Request to log hike:' + JSON.stringify(hike));
     req.app.get('connection').query('INSERT INTO HIKES set ?', hike, function(err) {
         if (err) {
           res.send(err);
         } else {
           res.redirect('/hikes');
         }
      });
   };
   ```

1. 将 `routes/index.js` 的内容替换为以下内容：

   ```
   /*
    * GET home page.
    */
   
   exports.index = function(req, res){
     res.render('index', { title: 'Express' });
   };
   ```

1. 添加以下 jade 模板到 `views/hike.jade` 中，以提供添加 Hiking 日志的用户界面。

   ```
   extends layout
   
   block content
     h1= title
     p Welcome to #{title}
   
     form(action="/add_hike", method="post")
       table(border="1")
         tr
           td Your Name
           td
             input(name="hike[NAME]", type="textbox")
         tr
           td Location
           td
             input(name="hike[LOCATION]", type="textbox")
         tr
           td Distance
           td
             input(name="hike[DISTANCE]", type="textbox")
         tr
           td Weather
           td
             input(name="hike[WEATHER]", type="radio", value="Good")
             | Good
             input(name="hike[WEATHER]", type="radio", value="Bad")
             | Bad
             input(name="hike[WEATHER]", type="radio", value="Seattle", checked)
             | Seattle
         tr
           td(colspan="2")
             input(type="submit", value="Record Hike")
   
     div
       h3 Hikes
       table(border="1")
         tr
           td Date
           td Name
           td Location
           td Distance
           td Weather
         each hike in hikes
           tr
             td #{hike.HIKE_DATE.toDateString()}
             td #{hike.NAME}
             td #{hike.LOCATION}
             td #{hike.DISTANCE}
             td #{hike.WEATHER}
   ```

1. 使用 [**eb deploy**](eb3-deploy.md) 命令部署更改。

   ```
   ~/nodejs-example-express-rds$ eb deploy
   ```

## 清理
<a name="create_deploy_nodejs_express.delete"></a>

如果使用完 Elastic Beanstalk，则可终止您的环境。

使用 **eb terminate** 命令终止您的环境以及其中包含的所有资源。

```
~/nodejs-example-express-rds$ eb terminate
The environment "nodejs-example-express-rds-env" and all associated instances will be terminated.
To confirm, type the environment name: nodejs-example-express-rds-env
INFO: terminateEnvironment is starting.
...
```