如何在Ubuntu14.04上使用Terraform部署Node.js应用程序

来自菜鸟教程
跳转至:导航、​搜索

Stream的一篇文章

介绍

在编排工具的帮助下,DevOps 专业人员可以通过利用一些 API 调用来部署堆栈。 Terraform 是一个非常简单但功能强大的工具,它允许您将堆栈编写为代码,然后通过使用 Git 提交定义文件来共享它并使其保持最新。 Terraform 由 HashiCorp 创建,他是 VagrantPackerConsul 等流行开源工具的作者。

Terraform 提供了一个通用配置来启动您的基础设施,从物理和虚拟服务器到电子邮件和 DNS 提供商。 一旦启动,Terraform 会随着配置的发展安全有效地更改基础设施。

本教程向您展示如何使用 DigitalOceanTerraform、Cloud-init[ 为功能齐全、复杂的 Node.js 应用程序设置环境X183X] 和 PM2 在 Ubuntu 14.04 上。 作为我们的示例应用程序,我们将使用 , 一个开源的反应 & 还原 Node.js 应用程序由 GetStream.io . 最终输出将是一个功能丰富、可扩展的社交网络应用程序!

您将首先使用 Terraform 使用预定义的配置部署 Cabin。 然后,您将深入了解该配置,以便熟悉它的工作原理。

如果您只对在 DigitalOcean 服务器上安装 Terraform 感兴趣,请参阅 如何将 Terraform 与 DigitalOcean 一起使用。

先决条件

要学习本教程,您需要:

  • 一台 2 GB Ubuntu 14.04 服务器,您将在本教程中使用 Terraform 创建该服务器。
  • 安装在本地计算机上的 Git 客户端。
  • Facebook 帐户,因此您可以创建 Facebook 应用程序,因为 Cabin 使用 Facebook 登录。
  • 一个域,例如 cabin.example.com; 您需要将此域指向您将在第 4 步中获得的 IPv4 地址,并且您将需要此地址作为 Facebook 中的站点 URL。

虽然不是必需的,但本教程假设您已完成 Stream 的小屋教程系列 。 您将需要多个提供程序的 API 密钥和设置,这是 Cabin 在生产中工作所必需的,因为它们在 Cabin 的功能中起着不可或缺的作用。

如果您没有获得这些密钥,本教程仍然有效。 您仍然可以使用 Terraform 来配置和部署 Cabin 应用程序,但在配置所有必需的组件之前,该应用程序将无法使用。


有关这些服务的更多信息,请随时访问 Stream 的以下博客文章:

第 1 步 — 获取示例应用程序

将 Cabin 示例应用程序从 GitHub 克隆到您在本地计算机上选择的目录中。 我们使用的是 Mac,假设您也是。

首先,导航到您的主目录。

cd ~

然后使用 git 克隆存储库:

git clone https://github.com/GetStream/stream-react-example.git

这会将示例应用程序克隆到一个名为 stream-react-example 的新文件夹中。 导航到包含 Cabin 的 Terraform 项目的 stream-react-example/terraform/do/cabin 文件夹。

cd stream-react-example/terraform/do/cabin

我们稍后会处理这个文件夹。 但首先,让我们设置 Terraform。

第 2 步 — 安装 Terraform

对于 OSX 上的简单安装,您可以使用 Homebrew 通过发出以下命令来安装 Terraform:

brew install terraform

或者,您可以从 http://terraform.io 下载 Terraform。 下载后,使其可用于您的命令路径,如下所示。

PATH=location/of/terraform:$PATH

这会暂时将 Terraform 添加到您的路径中。 如果您希望此更改永久生效,请在 OSX 上编辑文件 ~/.bash_profile 并添加以下行:

~/.bash_profile

export PATH=location/of/terraform:$PATH

接下来,要检查 Terraform 是否已正确安装,请运行以下命令:

terraform

您将看到以下输出,其中显示了 Terraform 的选项:

Outputusage: terraform [--version] [--help] <command> [<args>]

Available commands are:
    apply       Builds or changes infrastructure
    destroy     Destroy Terraform-managed infrastructure
    fmt         Rewrites config files to canonical format
    get         Download and install modules for the configuration
    graph       Create a visual graph of Terraform resources
    init        Initializes Terraform configuration from a module
    output      Read an output from a state file
    plan        Generate and show an execution plan
    push        Upload this Terraform module to Atlas to run
    refresh     Update local state file against real resources
    remote      Configure remote state storage
    show        Inspect Terraform state or plan
    taint       Manually mark a resource for recreation
    untaint     Manually unmark a resource as tainted
    validate    Validates the Terraform files
    version     Prints the Terraform version

在 Terraform 可以启动您的基础架构之前,我们需要配置两件事:

  1. 数字海洋代币
  2. SSH 密钥对

因此,让我们首先处理 DigitalOcean 令牌。

第 2 步 — 配置 DigitalOcean 访问令牌

Terraform 需要您的 DigitalOcean 访问令牌才能使用 DigitalOcean API。

登录您的 DigitalOcean 帐户并单击 API 链接。 然后点击生成新令牌按钮。 请务必检查 写访问 。 用户界面将显示一个新的访问密钥,您应该将其复制到剪贴板,因为如果您重新访问该页面,该密钥将不可见。

现在使用您喜欢的文本编辑器打开文件 variables.tf 并找到 token 部分:

变量.tf

variable "token" {
  description = "DO Token"
}

添加以文本 default = 开头的新行并包含您的 DigitalOcean API 令牌。 请记住用引号将标记括起来。

变量.tf

variable "token" {
  description = "DO Token"
  default = "57eaa5535910eae8e9359c0bed4161c895c2a40284022cbd2240..."
}

保存并关闭文件。

现在让我们配置 Terraform 以使用我们的 SSH 密钥对。

第 3 步 — 添加您的 SSH 密钥对

Terraform 在创建后需要一个 SSH 密钥来连接到我们的服务器,因此它可以安装包和部署应用程序。

查看您的 ~/.ssh 目录,看看您是否已有密钥对:

ls -al ~/.ssh

最有可能的是,您至少有一个由私钥和公钥组成的密钥对。 例如,您可能有 id_rsa.pubid_rsa

警告:如果您现有的密钥对已经与您的 DigitalOcean 帐户相关联,您需要使用 DigitalOcean 仪表板将其删除,或生成一个新密钥对以避免冲突。


如果您没有任何密钥对,或者您拥有的密钥已经与您的 DigitalOcean 帐户关联,请查看 DigitalOcean 的 SSH 密钥设置教程 进行设置。

您需要将 .pub 文件的内容粘贴到 variables.tf 文件中,就像使用 API 令牌一样。 如果您使用的是 Mac,则可以通过发出以下命令将 SSH 公钥复制到剪贴板:

pbcopy < ~/.ssh/your_key.pub

您还可以使用 cat 命令将公钥的内容显示到屏幕上,然后手动将其复制到剪贴板:

cat  ~/.ssh/your_key.pub

然后在编辑器中打开文件 variables.tf 并将 SSH 公钥文件的内容添加到 sshkey 设置中:

变量.tf

variable "sshkey" {
  description = "Public ssh key (for Cabin user)"
  default = "ssh-rsa AAAAB3NzaC1yc2EAAAADA...== nick@getstream.io"
}

完成此步骤后,保存并退出文件。

如果您已生成用于 Terraform 和 DigitalOcean 的新密钥,则需要运行这两个命令,以便使用新密钥而不是默认密钥:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/your_id_rsa

如果您使用备用密钥对,则可能需要在每次打开新 shell 时运行此命令。

现在您已经为 Terraform 提供了它所需的变量,您已经准备好创建您的服务器并使用 Terraform 部署您的应用程序。

第 4 步 — 运行 Terraform

有趣的部分来了! 让我们看看我们将要构建的基础设施。 Terraform 将为我们做很多工作,从设置我们的服务器到部署我们的应用程序。 我们可以让 Terraform 准确地向我们展示它将使用以下命令执行的操作:

terraform plan

此命令的输出非常冗长,因此请尝试关注以下语句:

Output+ digitalocean_droplet.cabin-web
...
+ digitalocean_floating_ip.cabin-web-ip
...
+ digitalocean_ssh_key.cabin-ssh-key
...
+ template_file.pm2_processes_conf
...
+ template_file.userdata_web
...

行首的“+”符号表示将创建资源。 以 digitalocean 为前缀的资源是将在 DigitalOcean 上创建的资源。 在这种特定情况下,Terraform 将创建一个 Droplet、一个浮动 IP,并将添加我们的 SSH 密钥。

警告 对于您的实例或第三方服务在线时可能产生的费用,我们概不负责。 命令 terraform apply 将创建一个具有 2GB RAM(约 0.03 美元/小时)和 DigitalOcean 免费提供的浮动 IP 的 Droplet。 有关确切数字,请仔细检查 DigitalOcean 网站上的更新价格。


现在是时候在 Droplet 上运行 Terraform 并启动 Cabin 了。

terraform apply

不久之后,您将看到 Terraform 打印出以下内容:

OutputApply complete! Resources: 6 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

Expected output:

  web_ipv4 = 111.111.111.111

web_ipv4 是您可以用来访问 Droplet 的浮动 IP 地址。

使用您看到的 web_ipv4 值登录到新创建的 Droplet:

ssh cabin@your_value_for_web_ipv4

你也可以使用命令

terraform output web_ipv4

如果您错过了该值,则显示与该值关联的 IP 地址。

当您登录时,您将看到此欢迎消息:

   _____      _     _
  / ____|    | |   (_)
 | |     __ _| |__  _ _ __
 | |    / _` | '_ \| | '_ \
 | |___| (_| | |_) | | | | |
  \_____\__,_|_.__/|_|_| |_|

Initializing Cabin. Please wait... (up 1 minute) | CTRL+C to interrupt

您可能需要等待几分钟,DigitalOcean 才能配置实例,cloud-init 才能安装 Cabin 所需的软件包。 但是一旦它准备好了,你会看到:

Cabin initialized!
Check running processes...
┌──────────┬────┬──────┬───────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode │ pid   │ status │ restart │ uptime │ memory      │ watching │
├──────────┼────┼──────┼───────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ api      │ 0  │ fork │ 14105 │ online │ 0       │ 36s    │ 75.898 MB   │  enabled │
│ app      │ 1  │ fork │ 14112 │ online │ 0       │ 36s    │ 34.301 MB   │  enabled │
│ www      │ 2  │ fork │ 14119 │ online │ 0       │ 36s    │ 50.414 MB   │  enabled │
└──────────┴────┴──────┴───────┴────────┴─────────┴────────┴─────────────┴──────────┘
 Use `pm2 show <id|name>` to get more details about an app

Cabin 启动并运行后,将您的移动浏览器指向 http://your_value_for_web_ipv4。 客舱是实时的,你应该看到一个加载屏幕。 但在我们对服务器上的代码进行一些更改之前,这就是我们所能得到的。

第 5 步 - (可选)配置客舱

Cabin 应用程序已部署,但尚不可用。 如果我们想让 Cabin 全面运行,我们必须配置 Facebook 和其他一些服务。

首先,您需要使用有效域名创建 Facebook 应用程序,例如映射到在安装过程中生成的 web_ipv4 地址的 cabin.example.com。 向您的 DNS 添加一条记录或向您的 /etc/hosts 文件添加一个条目,将您的域映射到 IP 地址。

要创建 Facebook 应用程序,请执行以下步骤:

  1. 访问 https://developers.facebook.com/docs/apps/register#step-by-step-guide。
  2. 登录 Facebook。
  3. 在我的应用程序下,单击 添加新应用程序
  4. 输入您的应用程序的名称(例如 Cabin - My Example App)。
  5. 输入您的 联系邮箱
  6. 对于 Category,使用下拉菜单选择应用的类别。 在我们的例子中,它是 Lifestyle
  7. 单击创建应用程序ID按钮。
  8. 如果需要,请完成验证码。
  9. 复制 appId。 它将是在屏幕顶部找到的数值。 你很快就会需要它。
  10. 从左侧边栏中选择 Dashboard
  11. 在标题 开始使用 Facebook SDK 下,单击 选择平台
  12. 平台选择Web
  13. 找到 站点 URL 字段并输入 http://cabin.example.com
  14. 单击下一步

如果遇到问题,您可以按照 这个分步指南 。 如果您遇到困难,可以在 此处 找到有关在 Facebook 上调试应用程序设置的精彩文章。

拥有 appID 后,您需要替换服务器上的默认 appID 设置。

因此,请确保您已登录到您的服务器。 如果不是,请使用以下命令重新登录:

ssh cabin@your_value_for_web_ipv4

登录后,打开文件 ~/stream-react-example/app/views/index.ejs

nano ~/stream-react-example/app/views/index.ejs

将默认的 appId 更改为 Facebook 提供的。

strea-react-example/app/views/index.ejs

FB.init({
    appId   : 'your_facebook_app_id',
    xfbml   : true,
    version : 'v2.6',
    status  : true,
    cookie  : true,
})

保存此文件并关闭它。

接下来,您需要知道 Cabin 的数据库密码,该密码由 Terraform 在创建服务器时生成。 要获取此值,请键入以下命令:

grep DB_PASSWORD processes.yml

复制此密码; 你很快就会需要它。

文件 env.sh 是您输入 Cabin 所依赖的各种提供商和服务的凭据的地方。 该文件将这些凭据放入环境变量中,然后由应用程序读取。 这是一项安全预防措施,因为它将密码和密钥保留在 Git 之外。

打开env.sh

nano env.sh

您将看到以下内容:

Outputexport NODE_ENV=production
export JWT_SECRET=ABC123
export DB_USERNAME=cabin
export DB_HOST=localhost
export DB_PASSWORD=VALUE
export DB_PORT=3306
export MAPBOX_ACCESS_TOKEN=ADD_VALUE_HERE
export S3_KEY=ADD_VALUE_HERE
export S3_SECRET=ADD_VALUE_HERE
export S3_BUCKET=ADD_VALUE_HERE
export STREAM_APP_ID=ADD_VALUE_HERE
export STREAM_KEY=ADD_VALUE_HERE
export STREAM_SECRET=ADD_VALUE_HERE
export ALGOLIA_APP_ID=ADD_VALUE_HERE
export ALGOLIA_SEARCH_ONLY_KEY=ADD_VALUE_HERE
export ALGOLIA_API_KEY=ADD_VALUE_HERE
export KEEN_PROJECT_ID=ADD_VALUE_HERE
export KEEN_WRITE_KEY=ADD_VALUE_HERE
export KEEN_READ_KEY=ADD_VALUE_HERE
export IMGIX_BASE_URL=https://react-example-app.imgix.net/uploads
export API_URL=http://localhost:8000

如您所见,该文件导出了一堆环境变量,其中包含有关 Cabin 所需的各种服务的信息。 为了让 Cabin 在生产中工作,您需要填写所有这些值。

以下是这些设置的快速细分:

  1. NODE_ENV:Node.js 将运行的环境。 (生产将提供速度增强)。
  2. JWT_SECRET:API 和 Web(应用)接口之间 JSON Web Token 身份验证的身份验证密钥。
  3. DB_USERNAME:数据库的用户名。
  4. DB_HOST:数据库主机名。
  5. DB_PASSWORD:数据库的密码,你刚才通过查看processes.yml查看。
  6. DB_PORT:数据库端口(MySQL 的默认端口 3306)。
  7. MAPBOX_ACCESS_TOKEN:MapBox 的访问令牌(用于映射照片位置)。
  8. S3_KEY:用于图像存储的 Amazon S3 密钥。
  9. S3_SECRET:用于图像存储的 Amazon S3 密钥。
  10. S3_BUCKET:用于图像存储的 Amazon S3 存储桶。 确保此存储桶存在。
  11. STREAM_APP_ID:流应用 ID。 确保与此 ID 关联的应用程序中存在所有必需的提要组。
  12. STREAM_KEY:流 API 密钥。
  13. STREAM_SECRET:流式应用密码。
  14. ALGOLIA_APP_ID:用于搜索的 Algolia 应用程序 ID。
  15. ALGOLIA_SEARCH_ONLY_KEY:Algolia 搜索唯一键用于搜索。
  16. ALGOLIA_API_KEY:用于搜索的 Algolia API 密钥。
  17. KEEN_PROJECT_ID:敏锐跟踪项目ID(用于统计)。
  18. KEEN_WRITE_KEY:敏锐跟踪写入键(用于统计)。
  19. KEEN_READ_KEY:敏锐跟踪读取键(用于统计)。
  20. IMGIX_BASE_URL:Imgix 基本 URL(用于渲染特定尺寸的照片)。
  21. API_URL:此应用程序用于其 API 的 URL。 您需要将其从 localhost 更改为指向您的 IP 地址的域,例如 cabin.example.com

有关引用的环境变量和服务的更多详细信息,请访问以下博客文章并确保您已按指定配置每个应用程序:

配置完所有提供程序后,在 env.sh 文件中输入数据库的密码和提供程序的值。

退出并保存 env.sh 文件。 然后获取文件,将值加载到 Cabin 将使用的环境值中:

source ./env.sh

接下来,您需要运行 webpack 命令。 Webpack 是一个 JavaScript 构建工具,用于管理 Cabin 的前端代码。 Webpack 将根据您刚刚更改的 env.sh 文件设置的值重新生成 JavaScript 和 CSS 文件。 所以,切换到 app 目录:

cd app

然后运行 webpack 命令重建前端 JavaScript 文件。 这会将一些提供者令牌注入到前端代码中。

webpack --progress --color

您将看到以下输出:

OutputHash: 64dcb6ef9b46a0243a8c  
Version: webpack 1.13.1
Time: 21130ms
                  Asset     Size  Chunks             Chunk Names
     ./public/js/app.js  2.22 MB       0  [emitted]  app
./public/css/styles.css    23 kB       0  [emitted]  app
   [0] multi app 28 bytes {0} [built]
    + 685 hidden modules
Child extract-text-webpack-plugin:
        + 2 hidden modules
Child extract-text-webpack-plugin:
        + 2 hidden modules

设置好后,运行 PM2 以重新加载所有应用程序进程,以确保所有组件都使用新设置:

pm2 restart all
Output[PM2] Applying action restartProcessId on app [all](ids: 0,1,2)
[PM2] [api](0) ✓
[PM2] [app](1) ✓
[PM2] [www](2) ✓
┌──────────┬────┬──────┬───────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode │ pid   │ status │ restart │ uptime │ memory      │ watching │
├──────────┼────┼──────┼───────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ api      │ 0  │ fork │ 30834 │ online │ 516     │ 0s     │ 39.027 MB   │  enabled │
│ app      │ 1  │ fork │ 30859 │ online │ 9       │ 0s     │ 22.504 MB   │  enabled │
│ www      │ 2  │ fork │ 30880 │ online │ 9       │ 0s     │ 19.746 MB   │  enabled │
└──────────┴────┴──────┴───────┴────────┴─────────┴────────┴─────────────┴──────────┘

就是这样! 您现在可以注销远程服务器。

exit

最后,再次在浏览器中访问 http://your_value_for_web_ipv4 以查看该站点。 这将显示带有登录 Facebook 链接的封面图片。 登录后,您将能够稍后探索该应用程序。

PM2 管理 Cabin 的流程,它可以成为帮助您调试问题的好工具。 您可以使用 pm2 list 查看应用程序组件的状态,并使用 pm2 logs 查看应用程序的日志流,这可以帮助您诊断任何配置错误。


现在让我们深入研究使此部署成为可能的 Terraform 配置。

第 6 步 — 探索配置图块

那么这一切是如何运作的呢? 让我们看看我们克隆到本地机器的存储库中的文件。 虽然在本节中您无需修改任何内容,但您仍应在您自己的机器上进行操作,以便了解这些部分是如何组合在一起的。

Terraform 项目分为多个文件和目录,以保持应用程序的清洁和易于理解。 我们已将所有 DigitalOcean 文件放在存储库的 terraform/do 目录中,该目录具有以下结构:

地形文件夹

do
└── cabin
    ├── files
    │   ├── cabin-web-nginx.conf
    │   └── cabin_mysql_init.sh
    ├── main.tf
    ├── outputs.tf
    ├── templates
    │   ├── processes.tpl
    │   └── web.tpl
    └── variables.tf

让我们看看上面的文件,从main.tf开始。 在您喜欢的文本编辑器中打开它。

我们要做的第一件事是告诉 Terraform 我们将使用哪个云提供商。

主文件

provider "DigitalOcean" {
  token = "${var.token}"
}

定义 DigitalOcean 提供者就这么简单。 您可以在 Terraform 文档 中找到支持的提供程序的完整列表。

可变配置

Terraform 允许您定义变量,这意味着您可以为部署设置默认值。 这样您就不必每次都输入详细信息或在整个配置中硬编码值。 让我们看看如何设置在 DigitalOcean 上部署的变量。

看看 variables.tf,我们定义了运行 Cabin 应用程序所需的变量的位置。

变量.tf

variable "token" {
  description = "DO Token"
}

variable "region" {
  description = "DO Region"
}

为了帮助您更好地理解变量在 Terraform 中的处理方式,让我们看一下上面的示例。

对于区域变量,我们指定了一个默认值。 如果您不指定默认值,Terraform 会提示您输入一个,如下例所示:

Outputterraform plan
var.token
  DO Token

  Enter a value:

您还可以在运行 terraform apply 时提供变量。 例如,如果要指定不同的区域,可以使用 var 参数运行 Terraform:

terraform -var 'region=ams3' apply

这会覆盖任何已配置的设置。

液滴设置

main.tf 中,我们告诉 Terraform 在 DigitalOcean 上配置一个 Droplet。 默认情况下,我们部署具有以下特征的服务器:

主文件

resource "digitalocean_droplet" "cabin-web" {
  image = "ubuntu-14-04-x64"
  name = "cabin-web"
  region = "${var.region}"
  size = "2gb"
  ssh_keys = [ "${digitalocean_ssh_key.cabin-ssh-key.id}" ]
  user_data = "${template_file.userdata_web.rendered}"
}

我们正在创建一个具有 2GB RAM 的新 DigitalOcean Droplet,名为 cabin-web,并使用图像 ubuntu-14-04-x64。 通过查看上面的资源定义,您可以看到更改服务器的图像和大小很容易。

用户数据和云初始化

好的,那么 user-data 到底是什么? 这是在启动时向云实例发送命令和指令的最简单方法。 与 cloud-init 结合使用,它成为配置实例的强大方式,无需利用不必要的第三方应用程序,如 ChefPuppet

cloud-init 程序嵌入在许多 Linux 发行版中。 它有一小部分指令,可让您执行简单的任务,例如添加用户、管理组、创建文件以及以 root 权限运行脚本或 shell 命令。

让我们深入研究 user_data 属性,以便您更好地了解它是什么:

主文件

resource "digitalocean_droplet" "cabin-web" {
  ...
  user_data = "${template_file.userdata_web.rendered}"
}

我们的目标是在 Cabin 启动并运行的情况下启动一个新的 Droplet,并让 cloud-init 为我们处理繁重的工作。 user_data 字段指向一个模板文件,使用一个变量指向 main.tf 中的另一个声明:

主文件

resource "template_file" "userdata_web" {
  template = "${file("${path.module}/templates/web.tpl")}"

  vars {
    userdata_sshkey = "${var.sshkey}"
    userdata_nginx_conf = "${base64encode(file("${path.module}/files/cabin-web-nginx.conf"))}"
    userdata_mysql_init = "${base64encode(file("${path.module}/files/cabin_mysql_init.sh"))}"
    userdata_pm2_conf = "${base64encode("${template_file.pm2_processes_conf.rendered}")}"
    userdata_env = "${base64encode("${template_file.env.rendered}")}"
    userdata_motd = "${base64encode(file("${path.module}/files/motd"))}"
    userdata_motd_script = "${base64encode(file("${path.module}/files/motd.sh"))}"
    userdata_giturl = "${var.git_url}"
    userdata_index = "${base64encode(file("${path.module}/files/index.html"))}"
  }
}

Terraform 提供了允许您转换文本的函数。 我们可以使用此功能通过读取文件然后将内容转换为 Base64 编码的字符串来将值注入模板,以便它们可以通过 API 调用传输。

此特定部分为模板 templates/web.tpl 准备数据,其中包含要在服务器上执行的所有设置和命令。

让我们浏览一下 web.tpl 文件,看看它做了什么。

第一部分设置初始用户并禁用 root 访问:

模板/web.tpl

#cloud-config
users:
  - name: cabin
    groups: sudo
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    shell: /bin/bash
    home: /home/cabin
    lock_passwd: true
    ssh-authorized-keys:
      - ${userdata_sshkey}

disable_root: true

web.tpl 中的第一条语句必须是 #cloud-config。 如果您忘记添加它,cloud-init 将不会选择配置,并且给定的命令将不会在目标实例上执行。

本节中的命令执行以下操作:

  • cabin 用户添加到系统并获得授权成为超级用户
  • lock-passwd: true 拒绝密码验证,因此 cabin 用户将需要使用 SSH 密钥验证来访问服务器。
  • ssh-authorized-keys 将用户的 ssh-key 安装到 authorized_keys 文件中。
  • disable_root: true 用于禁用以 root 身份访问 SSH

请记住,${userdata_sshkey} 是我们在 main.tf 中调用模板时设置的变量。

接下来,我们安装应用程序所需的 MySQL、Nginx、Git 和其他包:

package_update: true
packages:
 - mysql-server-5.6
 - libmysqlclient-dev
 - iptables-persistent
 - git
 - nginx
 - npm
 - pwgen

使用 cloud-init 安装软件包的最简单方法是利用 Package 模块安装给定软件包的列表。 该模块使用默认的包管理器进行分发。 由于我们使用的是 Ubuntu,因此此过程将安装带有 apt 的软件包。

接下来,我们将一些文件写入文件系统,使用我们传入模板的数据作为文件内容:

write_files:
 - encoding: b64
   content: ${userdata_nginx_conf}
   path: /tmp/cabin-web.conf
 - encoding: b64
   content: ${userdata_pm2_conf}
   path: /tmp/processes.yml
 - encoding: b64
   content: ${userdata_mysql_init}
   path: /tmp/cabin_mysql_init.sh
   permissions: '0554'

本节利用 write_file 模块来创建文件。 在上面的示例中,我们正在创建以下文件:

  • cabin-web.conf 包含 NGINX 配置。
  • PM2 使用 processes.yml 处理 Node.js 进程。
  • cabin_mysql_init.sh 是用于初始化 MySQL 数据库的自定义脚本。

请记住,当我们将数据传递给模板时,我们将其编码为 Base64。 我们在写入文件时指定编码,以便可以对内容进行解码。

在下一节中,我们使用 runcmd 模块运行一些 shell 命令来使用 iptables 创建防火墙规则:

runcmd:
 - iptables -A INPUT -i lo -j ACCEPT
 - iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
 - iptables -A INPUT -p tcp --dport ssh -j ACCEPT
 - iptables -A INPUT -p tcp --dport 80 -j ACCEPT
 - iptables -A INPUT -p tcp --dport 8000 -j ACCEPT
 - iptables -A INPUT -p tcp --dport 3000 -j ACCEPT
 - iptables -A INPUT -j DROP
 - iptables -A OUTPUT -j ACCEPT
 - invoke-rc.d iptables-persistent save
…

然后,代码使用 iptables-persistent 使防火墙配置在实例重新启动时可用。

防火墙规则到位后,将执行其余设置和启动 Cabin 的命令:

 - apt-get update --fix-missing
 - curl -sL https://deb.nodesource.com/setup_5.x | bash && apt-get install -y nodejs
 - npm install pm2 webpack -g
 - cd /home/cabin && sudo -u cabin git clone ${userdata_giturl}
 - mv /tmp/env.sh /home/cabin/stream-react-example/env.sh
 - cd /home/cabin/stream-react-example/api && sudo -u cabin npm install
 - cd /home/cabin/stream-react-example/app && sudo -u cabin npm install
 - cd /home/cabin/stream-react-example/www && sudo -u cabin npm install
 - chown cabin.cabin /home/cabin/stream-react-example/env.sh && /home/cabin/stream-react-example/env.sh
 - mv /tmp/processes.yml /home/cabin/stream-react-example/processes.yml
 - chown cabin.cabin /home/cabin/stream-react-example/processes.yml
 - /tmp/cabin_mysql_init.sh
 - cd /home/cabin/stream-react-example && sudo -u cabin pm2 start processes.yml
 - mv /tmp/cabin-web.conf /etc/nginx/sites-available/cabin-web
 - rm /etc/nginx/sites-enabled/default
 - ln -s /etc/nginx/sites-available/cabin-web /etc/nginx/sites-enabled
 - service nginx reload

所有这些命令都以 root 权限执行,并且 仅在第一次启动时发生 。 如果您重新启动机器,runcmd 将不会再次执行。

现在您已经了解了有关 Terraform 的更多信息,让我们探索如何处理您的基础架构的生命周期。

第 7 步 — 管理堆栈的生命周期

Terraform 使保存堆栈状态、更新堆栈、销毁堆栈和部署代码更改成为可能。

您可能已经注意到,在运行 terraform apply 后,会在 cabin 目录中创建一个名为 terraform.tfstate 的文件。

该文件非常重要,因为它包含对在 DigitalOcean 上创建的实际资源的引用。 基本上,这个文件告诉 Terraform 它管理的资源的标识符。

如果您再次运行 terraform apply,Terraform 将不会重新开始并清除您创建的所有内容。 相反,它只会做尚未完成的部分。 因此,如果您的进程由于网络问题或 API 问题而在中间失败,您可以解决问题并再次运行该命令。 Terraform 将从中断处继续。

更改 Droplet 配置

您也可以使用 terraform apply 更改 Droplet 的配置。 例如,如果您需要更改数据中心或区域,或增加 Droplet 使用的内存以容纳更多流量,Terraform 使这两项任务变得非常容易。

您可以通过运行 terraform apply 命令并覆盖 regiondroplet_size 变量来调整 Droplet 区域。 这让 Terraform 知道需要销毁现有的 Droplet,并且需要配置新的 Droplet 以满足要求。

警告Terraform 将丢弃现有的 Droplet。 鉴于您在与应用程序相同的服务器上运行 MySQL 数据库, 这也会破坏您的 MySQL 数据。。 为避免这种情况,我们建议在此步骤之前执行数据库导出,或者更好的是, 在专用的 Droplet 上运行您的 MySQL 数据库。


如果要更改保存 Droplet 的区域或数据中心,请执行以下命令:

terraform apply -var "region=sfo2"

而且,随着用户群的增长,您可能需要更改 Droplet 大小以适应额外的流量。 您可以使用 droplet_size 变量来做到这一点,如下所示:

terraform apply -var "droplet_size=4gb"

Droplet 将被删除并替换为新的,并且应用程序将被重新部署和配置。

销毁堆栈

Terraform 令人惊奇的事情之一是它可以处理堆栈的整个生命周期。 您可以通过运行一个简单的 Terraform 命令 (destroy) 轻松地销毁您构建的内容。

terraform destroy

然后 Terraform 将提示您确认您确实要销毁所有资源:

OutputDo you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

一旦 Terraform 完成,最终输出将如下所示:

Outputdigitalocean_droplet.cabin-web: Destroying...
digitalocean_droplet.cabin-web: Still destroying... (10s elapsed)
digitalocean_droplet.cabin-web: Destruction complete
digitalocean_ssh_key.cabin-ssh-key: Destroying...
template_file.userdata_web: Destroying...
template_file.userdata_web: Destruction complete
template_file.pm2_processes_conf: Destroying...
template_file.pm2_processes_conf: Destruction complete
digitalocean_ssh_key.cabin-ssh-key: Destruction complete

Apply complete! Resources: 0 added, 0 changed, 5 destroyed.

如您所见,所有资源都被破坏了。

部署新版本的代码

如果您对代码库进行更改,您需要在几乎没有停机时间的情况下将更改提交到服务器。 我们的服务器上安装了 PM2,它将为我们处理繁重的工作。

PM2 监听应用程序中的文件系统更改。 为了运行更新版本的代码,只需 SSH 进入 Droplet 并在包含应用程序的目录中发出 git pull 命令。 这将指示服务器从您的存储库中提取。 当文件发生变化时,PMZ 会自动重启 Node 进程。

例如,如果有新版本的 Cabin,并且您想将最新版本的代码部署到服务器,您将登录到您的服务器:

ssh cabin@your_value_for_web_ipv4

然后,在服务器上,导航到包含 Cabin 应用程序的文件夹:

cd ~/stream-react-example

最后拉下最新版本。

git pull

新代码到位后,您的应用将自动重启,访问者将看到最新版本。 如果由于某种原因 PM2 没有发现变化,请手动重新启动

pm2 restart all

并且所有组件都将重新启动。

结论

使用 DigitalOcean、Terraform、Cloud-init 和 PM2,您已经成功地为 Cabin 设置了生产环境。

使用 Terraform 时,您的所有基础设施都存储为代码。 这使您的团队可以轻松跟踪更改和协作。 它还使您能够相对轻松地进行大型基础架构更改。