Terraform 使用入门
本文通过在阿里云 ECS 实例上搭建一个 Flask Web 应用,带您体验和快速开始使用 Terraform。
本文的主要包含两部分内容:
通过 Terraform 在阿里云上自动化创建 ECS 实例以及所依赖的其他云服务,如网络,安全组等
在 ECS 实例上通过 Terraform 自动化搭建 Flask Web 服务
本教程所含示例代码支持一键运行,您可以直接运行代码。一键运行
准备工作
本文将阿里云 ClousShell 作为 Terraform 的运行环境,因此在正式开始之前,首先需要准备以下内容:
RAM 子账号(可选)如果您已经有一个 RAM 子账号了,此子账号已经被授权了访问 ECS 和 VPC 的权限,那么可以跳过这一步。访问 RAM 用户页面,新建一个用于操作 Terraform 的子用户,比如取名
terraform-starter
,并为该子用户授权 ECS 和 VPC 的权限:AliyunECSFullAccess
、AliyunVPCFullAccess
。启动 Cloud Shell使用 RAM 子账号登录阿里云,并启动阿里云 Cloud Shell。阿里云 Cloud Shell 是阿里云为您创建的一个虚拟机,预安装了 Terraform,并在启动的过程中自动配置了与此子账号相关的访问凭证,因此您无需进行任何配置可直接在 Cloud Shell 中运行 Terraform。
注意:阿里云 Cloud Shell 在无交互式操作30分钟或者关闭所有会话窗口将视为终止操作,在终止操作后15分钟将销毁此台虚拟机。再次启动云命令行时,会为您创建一台全新的虚拟机。因此,为了数据安全和持久化,强烈建议您在首次启动 Cloud Shell 的时候创建并绑定一个文件存储磁盘,详见使用限制。
创建工作目录
在 Cloud Shell 中创建一个新目录,比如命名为:tf-quickstart。
mkdir tf-quickstart && cd tf-quickstart
编写 Flask Web 应用代码
本文将构建一个 Python Flask 应用,并使用单个文件描述要在 Web 上输出的内容。
创建名为 app.py
的文件:
touch app.py
并添加如下的内容:
# coding=utf-8
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return '你好,阿里云!'.decode('utf-8').encode('utf-8')
app.run(host='0.0.0.0')
编写 Terraform 代码
在此目录中创建一个 Terraform 文件main.tf
文件,此文件用于定义和描述 ECS 实例以及所依赖的其他阿里云资源:
touch main.tf
定义 Provider
在main.tf
文件中添加 Provider 的配置信息,用于描述将在哪个地域创建阿里云资源:
variable "region" {
default = "cn-shanghai"
}
provider "alicloud" {
region = var.region
}
示例代码中将阿里云的地域设置为了上海(cn-shanghai),您可以根据需要将其更改为其他地域。
定义 VPC 网络环境
在main.tf
文件中添加以下 Terraform 资源,用于创建 VPC 和子网 vSwitch:
alicloud_vpc:创建 VPC 网络
alicloud_vswitch:创建 vSwitch 子网
alicloud_zones:动态查询可以创建特定实例规格的可用区
variable "instance_name" {
default = "deploying-flask-web-server"
}
variable "instance_type" {
default = "ecs.n1.tiny"
}
data "alicloud_zones" "default" {
available_disk_category = "cloud_efficiency"
available_resource_creation = "VSwitch"
available_instance_type = var.instance_type
}
resource "alicloud_vpc" "vpc" {
vpc_name = var.instance_name
cidr_block = "172.16.0.0/12"
}
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id
cidr_block = "172.16.0.0/21"
zone_id = data.alicloud_zones.default.zones.0.id
}
示例代码中 VPC 的名称通过变量引用的方式声明为 deploying-flask-web-server
,vSwitch 的可用区通过动态查询的方式获取其中一个可用区。
定义安全组
在main.tf
文件中添加以下 Terraform 资源,用于创建安全组和安全组规则:
alicloud_security_group:创建安全组
alicloud_security_group_rule:创建安全组规则,开放 Flask Web 应用的访问端口
resource "alicloud_security_group" "default" {
name = var.instance_name
vpc_id = alicloud_vpc.vpc.id
}
resource "alicloud_security_group_rule" "allow_tcp_22" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "22/22"
priority = 1
security_group_id = alicloud_security_group.default.id
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_security_group_rule" "allow_tcp_5000" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "5000/5000"
priority = 1
security_group_id = alicloud_security_group.default.id
cidr_ip = "0.0.0.0/0"
}
示例代码中声明了在 VPC 网络中创建一个名为deploying-flask-web-server
的安全组,并声明了两个访问端口 22 和 5000,其中 22 端口用于您使用 SSH 连接到 ECS 实例部署 Flask 应用,5000 端口是 Flask 应用的默认访问端口。
定义 ECS 实例
将 alicloud_instance 资源添加到您创建的 main.tf
文件中,用于创建 ECS 实例。
variable "image_id" {
default = "ubuntu_18_04_64_20G_alibase_20190624.vhd"
}
variable "internet_bandwidth" {
default = "10"
}
variable "password" {
default = "Test@12345"
}
resource "alicloud_instance" "instance" {
availability_zone = data.alicloud_zones.default.zones.0.id
security_groups = alicloud_security_group.default.*.id
password = var.password
instance_type = var.instance_type
system_disk_category = "cloud_efficiency"
image_id = var.image_id
instance_name = var.instance_name
vswitch_id = alicloud_vswitch.vsw.id
internet_max_bandwidth_out = var.internet_bandwidth
}
output "flask_url" {
value = format("http://%v:5000", alicloud_instance.instance.public_ip)
}
示例代码中选择了一个最小的共享型实例规格族n1ecs.n1.tiny
,选择 Ubuntu 18 作为操作系统,并通过设置参数 internet_bandwidth
来为实例分配公网 IP 地址。除此之外,通过 output 输出 Flask 应用的访问地址
注意:这只是一个开发服务器,并且设置了一个简单的登录密码,请勿将其用于生产部署。
定义 Flask Web 应用
在main.tf
文件中,添加null_resource资源来将 app.py 文件上传到 ECS 实例的 tmp 目录下,并完成自动执行:
# deploy flask
resource "null_resource" "deploy" {
triggers = {
script_hash = filesha256("app.py")
}
# 上传文件
provisioner "file" {
connection {
type = "ssh"
user = "root"
password = var.password
host = alicloud_instance.instance.public_ip
}
source = "app.py"
destination = "/tmp/app.py"
}
# 部署
provisioner "remote-exec" {
connection {
type = "ssh"
user = "root"
password = var.password
host = alicloud_instance.instance.public_ip
}
inline = [
# 安装 Flask
"pip install flask",
# 部署前先停止 Flask (运行端口是 5000)
"nohup python /tmp/app.py &>/tmp/output.log &",
"sleep 2"
]
}
}
运行 Terraform 命令
在完成 Terraform 代码的编写后,开始运行 Terraform 命令来完成 ECS 实例的自动创建和 Flask 应用的自动搭建。
选择 Terraform 版本
阿里云 Cloud Shell 中预安装了多个 Terraform 版本,可以通过命令tfenv list
进行查看,并且0.12.31
为默认版本。您可以根据自身需要,通过tfenv use <版本号>
命令来修改默认版本。本文选择 1.3.7 版本(建议选择 1.x.x 版本):
tfenv use 1.3.7
初始化 Terraform
运行terraform init
构建.terraform
目录并添加阿里云的插件:
terraform init
预览 Terraform 代码
(可选)您可以预览当前已构建的 Terraform 代码。运行terraform plan
实现如下功能:
验证
main.tf
中 Terraform 代码的语法是否正确显示当前 Terraform 代码将要创建的资源的预览结果
terraform plan
预览结果显示,总计将创建 7 个资源。
执行 Terraform 代码
根据预览结果,确认无误后,运行terraform apply
来实现 7 个资源的自动化创建。
terraform apply
出现提示时,输入yes
以允许 Terraform 创建所有定义的资源。
执行成功,Terraform 会调用阿里云的 API 完成了 ECS 实例的自动化创建以及 Flask 应用的自动化搭建。可访问“ECS 实例”页面查看实例详情。
查看结果
Terraform 执行成功后,输出了 Flask 应用的访问 URL,您可直接点击 URL 进行访问:
清理资源
完成本文的操作后,您可以通过运行terraform destroy
命令来删除代码文件中定义的所有资源,以免产生任何额外费用:
terraform destroy
输入yes
以允许 Terraform 删除您的资源。
执行成功,所有资源均已被释放。
完整示例
为了便于您快速体验 Terraform,本文提供了完整的 Terraform 代码,并且将 Flask Web 页面中要输出的内容通过变量web_content
进行了定义,Flask Python 代码也直接内嵌到了 Terraform 代码中,您可以一键复制后直接运行。
本教程所含示例代码支持一键运行,您可以直接运行代码。一键运行
variable "region" {
default = "cn-shanghai"
}
variable "web_content" {
default = "你好,阿里云!"
}
variable "instance_name" {
default = "deploying-flask-web-server"
}
variable "instance_type" {
default = "ecs.n1.tiny"
}
variable "image_id" {
default = "ubuntu_18_04_64_20G_alibase_20190624.vhd"
}
variable "internet_bandwidth" {
default = 10
}
variable "password" {
default = "Test@12345"
}
provider "alicloud" {
region = var.region
}
data "alicloud_zones" "default" {
available_disk_category = "cloud_efficiency"
available_resource_creation = "VSwitch"
available_instance_type = var.instance_type
}
resource "alicloud_vpc" "vpc" {
vpc_name = var.instance_name
cidr_block = "172.16.0.0/12"
}
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id
cidr_block = "172.16.0.0/21"
zone_id = data.alicloud_zones.default.zones.0.id
}
resource "alicloud_security_group" "default" {
name = var.instance_name
vpc_id = alicloud_vpc.vpc.id
}
resource "alicloud_instance" "instance" {
availability_zone = data.alicloud_zones.default.zones.0.id
security_groups = alicloud_security_group.default.*.id
password = var.password
instance_type = var.instance_type
system_disk_category = "cloud_efficiency"
image_id = var.image_id
instance_name = var.instance_name
vswitch_id = alicloud_vswitch.vsw.id
internet_max_bandwidth_out = var.internet_bandwidth
}
resource "alicloud_security_group_rule" "allow_tcp_22" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "22/22"
priority = 1
security_group_id = alicloud_security_group.default.id
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_security_group_rule" "allow_tcp_5000" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "5000/5000"
priority = 1
security_group_id = alicloud_security_group.default.id
cidr_ip = "0.0.0.0/0"
}
# deploy flask
resource "null_resource" "deploy" {
triggers = {
web_content = var.web_content
script_hash = sha256("${local.app_content}")
}
provisioner "remote-exec" {
connection {
type = "ssh"
user = "root"
password = var.password
host = alicloud_instance.instance.public_ip
}
inline = [
# 安装 Flask
"pip install flask",
# 部署前先停止 Flask (运行端口是 5000)
"kill $(netstat -tulnp | grep \":5000\" | awk '{ print $7 }' | cut -d'/' -f1)",
# 部署 Flask
"echo \"${local.app_content}\" > /tmp/app.py",
"nohup python /tmp/app.py &>/tmp/output.log &",
"sleep 2"
]
}
}
output "flask_url" {
value = format("http://%v:5000", alicloud_instance.instance.public_ip)
}
locals {
app_content = <<EOF
# coding=utf-8
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return '${var.web_content}'.decode('utf-8').encode('utf-8')
app.run(host='0.0.0.0')
EOF
}