使用 Terraform 部署 nginx
2015 年,Gogo 在 Vultr 上购买了第一个 VPS,在其上搭建了这个 blog、配置了域名、申请了 https 证书。我在这台 VPS 上玩了很多东西,有 Wordpress 时代的 LAMP、邮箱服务的 Postfix、网盘 NextCloud、以及早被遗忘的各种服务。这些服务大多都已被遗弃,但许多痕迹却长久地留在了机器上。现在,是时候清理下服务器了!
目标
部署的服务比较多,准备先搞定两个最常用的:
- Nginx,部署了 blog 在内的多个静态网页
- acme.sh,用于申请 https 证书
部署方案一开始准备在 Vultr 上创建一台新机子,用 Docker 逐步把服务迁移过来。但如此就相当于是把老机子丢掉了,但 go 还是想留个纪念 (虽然不知道有什么意义 😂)。于是选择退而求其次,给老机子重新装下系统吧……
准备
把 Nginx 上可能要用到的数据下载到本地。并且用 Vultr 的 Snapshots 功能备份了一个快照,如果后续发现有数据丢了,可以从快照恢复一台机子把数据取回来。
接下来需要重建系统了。这台机子上的系统还停留在上古的 Ubuntu 16.04 上,Vultr 早已不支持重新安装这个版本,借机安装个 Ubuntu 22.04。
之后调了一通配置,手动配置了 docker 和 acme.sh,终于要部署 Nginx 了。
Terraform 介绍
Terraform 是一个 IaC (infrastructure as code) 工具,可以通过配置文件声明的方式,高效地创建 计算实例、存储、网络 或是 DNS、SaaS 等基础设施。
想尝试一下 Terraform 是因为它有两个优势:
- 可以用配置文件声明的方式创建资源,这很 coooool!
- Terraform 会缓存当前资源的状态,修改资源后,会对比新的配置与当前状态,仅修改变更状态。比调试时手动修改状态方便得多。
Terraform 有 CLI 和 Cloud 两个产品。Terraform CLI 是开源的 CLI 工具,需要自行管理生成的状态文件。Terraform Cloud 提供了版本控制系统来管理状态文件,并提供了协作、访问控制等功能。
使用 Terraform 部署 Nginx
按照原计划,我应该使用 Terraform 去创建一个 Vultr 实例。既然已经手动把前几步做了,接下来就用 Terraform 部署个 Nginx 的 Docker 容器吧。
Terraform 使用称为 Terraform Language 的 DSL 作为配置文件,在本地新建 main.tf
并写入如下内容。
1 | terraform { |
Terraform 使用插件模块的形式提供各种资源。在 terraform { }
块中我们声明资源的提供者,这里使用 docker provider
。在 provider "docker" { }
块中配置提供者,我配置了通过 ssh 使用 VPS 上的 docker service。随后是资源的配置,想要启动一个 nginx 容器,需要声明 docker_image
和 docker_container
两个资源对象。这个配置写起来非常地直观,资源的定义和 docker 的使用方式完全一致。
在 main.tf
的目录下执行:
1 | terraform init |
这条命令会初始化工作目录,包括下载 provider 插件、模块。
随后执行:
1 | terraform plan |
这条命令首先让 Terraform 读取远程的资源,确保本地状态是最新的;其次将状态与当前配置文件对比,打印变更之处;最后显示一组建议的变更动作。部分输出如下:
1 | # docker_container.nginx will be created |
这个 diff 显示两个资源会被创建,接下来应用这次变更:
1 | terraform apply |
输出如下:
1 | Plan: 2 to add, 0 to change, 0 to destroy. |
访问一下 http://gogo.moe ,确实能看到 nginx 默认首页了,好耶🚀!
修改 Nginx 配置
通过 Terraform 修改 nginx 配置有很多种方法,最直观的显然是通过 docker volume 映射一个新的配置替换默认配置。我同样先从简单的开始改,这一步的目标就先给 nginx 默认页面启动 https 吧。
编辑配置文件 default.conf
,这个文件拷贝自 nginx 的默认配置文件,添加 443 端口的配置:
1 | server { |
这里的配置直接复制 80 端口的配置,只添加了最后两行 ssl 证书的设置。
接下来编辑 main.tf
,首先我需要上传本地的 default.conf
,参考了 stackoverflow 上的一个配置。使用 Terraform 上传文件实际上不是一种很好的方式,后文会提到。
1 | locals { |
我们定义了一个空资源,这个资源仅仅是为了上传文件。在 connection { }
块中配置 ssh 连接方式,在 provisioner
块中我们可以定义一些配置动作,这里新建了一个文件夹,并将 default.conf
文件上传至远程主机的指定。另外,我们使用了 locals { }
定义一些局部变量以简化书写。
Nginx 容器的配置也需要变更,我增加了 443 端口的映射,配置文件和证书两个 volume 的挂载。
1 | resource "docker_container" "nginx" { |
证书是之前手动用 acme.sh 已经配置在服务器上的,(没有用 terraform 配 acme.sh 是因为它过于复杂了🤣)。不过 acme.sh 每隔一段时间会自动更新一遍证书,而 nginx 需要 reload 才能加载证书,因此使用 acme.sh 自动 reload 下 docker 里的 nginx:
1 | # remote |
好了,万事具备,重新 terraform plan
,部分输出如下:
1 | ... |
符合预期,terraform apply
!
访问 https://gogo.moe ,能跑通了。
这个例子不太 Terraform
虽然这个例子能 run,但是仍然有两个地方存在不足,让我感觉这个 Terraform 不够 Terraform
- 生硬的上传配置文件
- bash 脚本就能搞定 docker,有点小题大做
使用 Terraform 上传配置文件确实有点硬伤。麻烦之处在于,上文中使用的 provisioner
上传文件方法,并不能检测到资源文件变更,修改资源文件后进行 plan 只会提示:
1 | No changes. Your infrastructure matches the configuration. |
这一个问题可以通过 nginx provider
来解决,这个 provider 提供了一种 nginx_server_block
的资源,这很 terraform。
第二个问题,使用 terraform 拉起 docker 确实不见得比写个 bash 脚本来的简单。在开头也提到过,使用 terraform 创建一个 vultr 实例,这才是更符合 terraform 的定位。
我是 Gogo,好久不见!
这是工作后的第一篇 blog,预计在上周日发布的。工作后可支配时间少了很多,一直拖到现在才完成 😂。
Terraform 挺好玩的,不愿意花钱创建一些实例的话,可以像本文一样用 docker 试水哦!
下次再见!