Terraform 使用入门

更新时间:

本文通过在阿里云 ECS 实例上搭建一个 Flask Web 应用,带您体验和快速开始使用 Terraform。

本文的主要包含两部分内容:

  • 通过 Terraform 在阿里云上自动化创建 ECS 实例以及所依赖的其他云服务,如网络,安全组等

  • 在 ECS 实例上通过 Terraform 自动化搭建 Flask Web 服务

说明

本教程所含示例代码支持一键运行,您可以直接运行代码。一键运行

准备工作

本文将阿里云 ClousShell 作为 Terraform 的运行环境,因此在正式开始之前,首先需要准备以下内容:

  1. RAM 子账号(可选)如果您已经有一个 RAM 子账号了,此子账号已经被授权了访问 ECS 和 VPC 的权限,那么可以跳过这一步。访问 RAM 用户页面,新建一个用于操作 Terraform 的子用户,比如取名 terraform-starter,并为该子用户授权 ECS 和 VPC 的权限:AliyunECSFullAccessAliyunVPCFullAccess

  2. 启动 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:

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 资源,用于创建安全组和安全组规则:

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

截屏2024-09-05 20.32.13.png

初始化 Terraform

运行terraform init构建.terraform目录并添加阿里云的插件:

terraform init

截屏2024-09-05 20.38.27.png

预览 Terraform 代码

(可选)您可以预览当前已构建的 Terraform 代码。运行terraform plan实现如下功能:

  • 验证main.tf中 Terraform 代码的语法是否正确

  • 显示当前 Terraform 代码将要创建的资源的预览结果

terraform plan

截屏2024-09-13 17.36.18.png

预览结果显示,总计将创建 7 个资源。

执行 Terraform 代码

根据预览结果,确认无误后,运行terraform apply来实现 7 个资源的自动化创建。

terraform apply

出现提示时,输入yes以允许 Terraform 创建所有定义的资源。

截屏2024-09-06 09.24.43.png

执行成功,Terraform 会调用阿里云的 API 完成了 ECS 实例的自动化创建以及 Flask 应用的自动化搭建。可访问“ECS 实例”页面查看实例详情。

查看结果

Terraform 执行成功后,输出了 Flask 应用的访问 URL,您可直接点击 URL 进行访问:

截屏2024-09-06 09.38.47.png

清理资源

完成本文的操作后,您可以通过运行terraform destroy命令来删除代码文件中定义的所有资源,以免产生任何额外费用:

terraform destroy

输入yes以允许 Terraform 删除您的资源。

截屏2024-09-05 21.06.39.png

执行成功,所有资源均已被释放。

完整示例

为了便于您快速体验 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
}