Terraform 使用入門(mén)
本文通過(guò)在阿里云 ECS 實(shí)例上搭建一個(gè) Flask Web 應(yīng)用,帶您體驗(yàn)和快速開(kāi)始使用 Terraform。
本文的主要包含兩部分內(nèi)容:
通過(guò) Terraform 在阿里云上自動(dòng)化創(chuàng)建 ECS 實(shí)例以及所依賴的其他云服務(wù),如網(wǎng)絡(luò),安全組等
在 ECS 實(shí)例上通過(guò) Terraform 自動(dòng)化搭建 Flask Web 服務(wù)
本教程所含示例代碼支持一鍵運(yùn)行,您可以直接運(yùn)行代碼。一鍵運(yùn)行
準(zhǔn)備工作
本文將阿里云 ClousShell 作為 Terraform 的運(yùn)行環(huán)境,因此在正式開(kāi)始之前,首先需要準(zhǔn)備以下內(nèi)容:
RAM 子賬號(hào)(可選)如果您已經(jīng)有一個(gè) RAM 子賬號(hào)了,此子賬號(hào)已經(jīng)被授權(quán)了訪問(wèn) ECS 和 VPC 的權(quán)限,那么可以跳過(guò)這一步。訪問(wèn) RAM 用戶頁(yè)面,新建一個(gè)用于操作 Terraform 的子用戶,比如取名
terraform-starter
,并為該子用戶授權(quán) ECS 和 VPC 的權(quán)限:AliyunECSFullAccess
、AliyunVPCFullAccess
。啟動(dòng) Cloud Shell使用 RAM 子賬號(hào)登錄阿里云,并啟動(dòng)阿里云 Cloud Shell。阿里云 Cloud Shell 是阿里云為您創(chuàng)建的一個(gè)虛擬機(jī),預(yù)安裝了 Terraform,并在啟動(dòng)的過(guò)程中自動(dòng)配置了與此子賬號(hào)相關(guān)的訪問(wèn)憑證,因此您無(wú)需進(jìn)行任何配置可直接在 Cloud Shell 中運(yùn)行 Terraform。
注意:阿里云 Cloud Shell 在無(wú)交互式操作30分鐘或者關(guān)閉所有會(huì)話窗口將視為終止操作,在終止操作后15分鐘將銷(xiāo)毀此臺(tái)虛擬機(jī)。再次啟動(dòng)云命令行時(shí),會(huì)為您創(chuàng)建一臺(tái)全新的虛擬機(jī)。因此,為了數(shù)據(jù)安全和持久化,強(qiáng)烈建議您在首次啟動(dòng) Cloud Shell 的時(shí)候創(chuàng)建并綁定一個(gè)文件存儲(chǔ)磁盤(pán),詳見(jiàn)使用限制。
創(chuàng)建工作目錄
在 Cloud Shell 中創(chuàng)建一個(gè)新目錄,比如命名為:tf-quickstart。
mkdir tf-quickstart && cd tf-quickstart
編寫(xiě) Flask Web 應(yīng)用代碼
本文將構(gòu)建一個(gè) Python Flask 應(yīng)用,并使用單個(gè)文件描述要在 Web 上輸出的內(nèi)容。
創(chuàng)建名為 app.py
的文件:
touch app.py
并添加如下的內(nèi)容:
# 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')
編寫(xiě) Terraform 代碼
在此目錄中創(chuàng)建一個(gè) Terraform 文件main.tf
文件,此文件用于定義和描述 ECS 實(shí)例以及所依賴的其他阿里云資源:
touch main.tf
定義 Provider
在main.tf
文件中添加 Provider 的配置信息,用于描述將在哪個(gè)地域創(chuàng)建阿里云資源:
variable "region" {
default = "cn-shanghai"
}
provider "alicloud" {
region = var.region
}
示例代碼中將阿里云的地域設(shè)置為了上海(cn-shanghai),您可以根據(jù)需要將其更改為其他地域。
定義 VPC 網(wǎng)絡(luò)環(huán)境
在main.tf
文件中添加以下 Terraform 資源,用于創(chuàng)建 VPC 和子網(wǎng) vSwitch:
alicloud_vpc:創(chuàng)建 VPC 網(wǎng)絡(luò)
alicloud_vswitch:創(chuàng)建 vSwitch 子網(wǎng)
alicloud_zones:動(dòng)態(tài)查詢可以創(chuàng)建特定實(shí)例規(guī)格的可用區(qū)
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 的名稱通過(guò)變量引用的方式聲明為 deploying-flask-web-server
,vSwitch 的可用區(qū)通過(guò)動(dòng)態(tài)查詢的方式獲取其中一個(gè)可用區(qū)。
定義安全組
在main.tf
文件中添加以下 Terraform 資源,用于創(chuàng)建安全組和安全組規(guī)則:
alicloud_security_group:創(chuàng)建安全組
alicloud_security_group_rule:創(chuàng)建安全組規(guī)則,開(kāi)放 Flask Web 應(yīng)用的訪問(wèn)端口
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 網(wǎng)絡(luò)中創(chuàng)建一個(gè)名為deploying-flask-web-server
的安全組,并聲明了兩個(gè)訪問(wèn)端口 22 和 5000,其中 22 端口用于您使用 SSH 連接到 ECS 實(shí)例部署 Flask 應(yīng)用,5000 端口是 Flask 應(yīng)用的默認(rèn)訪問(wèn)端口。
定義 ECS 實(shí)例
將 alicloud_instance 資源添加到您創(chuàng)建的 main.tf
文件中,用于創(chuàng)建 ECS 實(shí)例。
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)
}
示例代碼中選擇了一個(gè)最小的共享型實(shí)例規(guī)格族n1ecs.n1.tiny
,選擇 Ubuntu 18 作為操作系統(tǒng),并通過(guò)設(shè)置參數(shù) internet_bandwidth
來(lái)為實(shí)例分配公網(wǎng) IP 地址。除此之外,通過(guò) output 輸出 Flask 應(yīng)用的訪問(wèn)地址
注意:這只是一個(gè)開(kāi)發(fā)服務(wù)器,并且設(shè)置了一個(gè)簡(jiǎn)單的登錄密碼,請(qǐng)勿將其用于生產(chǎn)部署。
定義 Flask Web 應(yīng)用
在main.tf
文件中,添加null_resource資源來(lái)將 app.py 文件上傳到 ECS 實(shí)例的 tmp 目錄下,并完成自動(dòng)執(zhí)行:
# 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 (運(yùn)行端口是 5000)
"nohup python /tmp/app.py &>/tmp/output.log &",
"sleep 2"
]
}
}
運(yùn)行 Terraform 命令
在完成 Terraform 代碼的編寫(xiě)后,開(kāi)始運(yùn)行 Terraform 命令來(lái)完成 ECS 實(shí)例的自動(dòng)創(chuàng)建和 Flask 應(yīng)用的自動(dòng)搭建。
選擇 Terraform 版本
阿里云 Cloud Shell 中預(yù)安裝了多個(gè) Terraform 版本,可以通過(guò)命令tfenv list
進(jìn)行查看,并且0.12.31
為默認(rèn)版本。您可以根據(jù)自身需要,通過(guò)tfenv use <版本號(hào)>
命令來(lái)修改默認(rèn)版本。本文選擇 1.3.7 版本(建議選擇 1.x.x 版本):
tfenv use 1.3.7
初始化 Terraform
運(yùn)行terraform init
構(gòu)建.terraform
目錄并添加阿里云的插件:
terraform init
預(yù)覽 Terraform 代碼
(可選)您可以預(yù)覽當(dāng)前已構(gòu)建的 Terraform 代碼。運(yùn)行terraform plan
實(shí)現(xiàn)如下功能:
驗(yàn)證
main.tf
中 Terraform 代碼的語(yǔ)法是否正確顯示當(dāng)前 Terraform 代碼將要?jiǎng)?chuàng)建的資源的預(yù)覽結(jié)果
terraform plan
預(yù)覽結(jié)果顯示,總計(jì)將創(chuàng)建 7 個(gè)資源。
執(zhí)行 Terraform 代碼
根據(jù)預(yù)覽結(jié)果,確認(rèn)無(wú)誤后,運(yùn)行terraform apply
來(lái)實(shí)現(xiàn) 7 個(gè)資源的自動(dòng)化創(chuàng)建。
terraform apply
出現(xiàn)提示時(shí),輸入yes
以允許 Terraform 創(chuàng)建所有定義的資源。
執(zhí)行成功,Terraform 會(huì)調(diào)用阿里云的 API 完成了 ECS 實(shí)例的自動(dòng)化創(chuàng)建以及 Flask 應(yīng)用的自動(dòng)化搭建。可訪問(wèn)“ECS 實(shí)例”頁(yè)面查看實(shí)例詳情。
查看結(jié)果
Terraform 執(zhí)行成功后,輸出了 Flask 應(yīng)用的訪問(wèn) URL,您可直接點(diǎn)擊 URL 進(jìn)行訪問(wèn):
清理資源
完成本文的操作后,您可以通過(guò)運(yùn)行terraform destroy
命令來(lái)刪除代碼文件中定義的所有資源,以免產(chǎn)生任何額外費(fèi)用:
terraform destroy
輸入yes
以允許 Terraform 刪除您的資源。
執(zhí)行成功,所有資源均已被釋放。
完整示例
為了便于您快速體驗(yàn) Terraform,本文提供了完整的 Terraform 代碼,并且將 Flask Web 頁(yè)面中要輸出的內(nèi)容通過(guò)變量web_content
進(jìn)行了定義,F(xiàn)lask Python 代碼也直接內(nèi)嵌到了 Terraform 代碼中,您可以一鍵復(fù)制后直接運(yùn)行。
本教程所含示例代碼支持一鍵運(yùn)行,您可以直接運(yùn)行代碼。一鍵運(yùn)行
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 (運(yùn)行端口是 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
}