Use Infrastructure as Code to provision and manage any cloud, infrastructure, or service
为什么需要使用Terraform,这篇文章 说得比较透彻.跟传统配置管理工具的区别可以看看下面这张图,Terraform主要是提供资源本身,而系统配置不是Terraform的重点.加之近些年docker已经基本成为了事实标准,配置管理工具的生存范围会逐渐被docker image替代.同样的逻辑,如果kubernetes成为部署应用的标准解决方案后,Terraform的使用范围也会被蚕食.因为kubernetes的抽象层次更高,而且天然具备跨云的能力.目前Terraform在海外客户的接受程度还比较高,国内倒没怎么听说哪家大公司在重度使用.我个人判断,国内的这些企业客户很有可能会跳过这个阶段,直接进入k8s时代,即使用也很可能会架在k8s下面.接下来简单介绍一下Terraform在aliyun中的使用.
跟其他配置软件工具的区别
准备 先看一下Terraform的三种AK认证方式
1.明文写在配置文件(不推荐) 1 2 3 4 5 provider "alicloud" { access_key = "your_access_key" secret_key = "your_access_secret" region = "cn-hangzhou" }
2.用环境变量暴露
然后export对应的环境变量
ALICLOUD_ACCESS_KEY
ALICLOUD_SECRET_KEY
ALICLOUD_REGION
3.使用Aliyun CLI配置文件中的Profile 1 2 3 provider "alicloud" { profile = "default" }
CLI的使用方式看这里 ,初始化完成后,直接使用配置文件中的Profile就行,默认的配置文件在~/.aliyun/config.json
,配置文件中默认的Profile是default
基本概念 配置文件的语法可以看官方文档 ,我这里只简单介绍一下本文会用到的
data: 数据源,可以被resource引用,比如region列表,可用的image列表都是数据源,能提高可复用性
resource: 配置文件中最重要的概念,一台机器,一个IP,一个域名,一个绑定关系都是resource
output: 可以用于信息的打印,可以作为一种调试手段
小试牛刀 通过上面的介绍,对基本的概念有了一个大概的了解,下面写个最简单的配置测试一下.(推荐使用vscode+terraform插件 )
提示: 把配置文件直接拷贝到vscode中,可能会报错,不用管.那是因为目前插件还不支持最新语法,这个插件已经被Terraform官方接管 ,相信很快就会推出新的版本
update [2020-07-03]: vscode的terraform插件已经升级,支持了最新的terraform语法
1 2 3 4 5 6 7 8 9 10 11 12 13 # 这里使用的AK 和region都是从profile里面来的 provider "alicloud" { profile = "default" } # 列出阿里云所有的region data "alicloud_regions" "region_list" { } # 定义一个输出,方便调试 output "regions" { value = data.alicloud_regions .region_list }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 terraform init terraform plan terraform apply Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: regions = { "id" = "2613364496" "ids" = [ "cn-qingdao" , "cn-beijing" , "cn-zhangjiakou" , "cn-huhehaote" , "cn-hangzhou" , ......
这里有个小技巧,terraform init
完后可以把对应的aliyun provider拷贝到~/.terraform.d/
目录下,这样新建的terraform就不用再重新下载provider了.具体可以查看官方文档
进入主题 选题 上面已经完成最简单的hello world
,接下来我们开始动手配置一个比较真实的场景,直接看图
默认你已经对阿里云的产品有一定的了解
从这个图中可以看出,这是一个最简单的web应用,后端使用mysql.前面用slb做负载均衡.涉及到的云产品有:
SLB
VPC
VSW
EIP
安全组
ECS (2)
RDS
在开始创建ECS之前,需要先把VPC,VSW,安全组创建好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 创建VPC resource "alicloud_vpc" "charles_vpc" { name = "charles_vpc" cidr_block = "172.16.0.0/12" } # 创建交换机 resource "alicloud_vswitch" "charles_vsw" { name = "charles_vsw" vpc_id = alicloud_vpc.charles_vpc .id cidr_block = "172.16.0.0/24" availability_zone = "cn-hangzhou-h" } # 创建安全组 resource "alicloud_security_group" "charles_security_group" { name = "charles_security_group" vpc_id = alicloud_vpc.charles_vpc .id }
还是一样,执行terraform apply
就能创建好,之后可以用terraform state list
查看创建好的资源
创建ECS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 创建ECS -> web1 649849 resource "alicloud_instance" "charles-web1" { image_id = data.alicloud_images .ubuntu .ids .0 internet_charge_type = "PayByBandwidth" instance_type = "ecs.s6-c1m1.small" system_disk_category = "cloud_efficiency" security_groups = [alicloud_security_group.charles_security_group .id ] instance_name = "charles-web1" host_name = "charlesweb1" password = "pass_1234" private_ip = "172.16.0.1" vswitch_id = alicloud_vswitch.charles_vsw .id } # 创建ECS -> web2 124428 resource "alicloud_instance" "charles-web2" { image_id = data.alicloud_images .ubuntu .ids .0 internet_charge_type = "PayByBandwidth" instance_type = "ecs.s6-c1m1.small" system_disk_category = "cloud_efficiency" security_groups = [alicloud_security_group.charles_security_group .id ] instance_name = "charles-web2" host_name = "charlesweb2" password = "pass_1234" private_ip = "172.16.0.2" vswitch_id = alicloud_vswitch.charles_vsw .id }
配置负载均衡 这里的步骤稍微多一点
创建SLB,默认没有公网地址
创建EIP并与SLB绑定
启动SLB的HTTP的监听,对外80,对后8080
将上面的两台ECS加入负载均衡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # 创建SLB resource "alicloud_slb" "charles_slb" { name = "charles_slb" vswitch_id = alicloud_vswitch.charles_vsw .id internet_charge_type = "PayByTraffic" } # 创建EIP resource "alicloud_eip" "charles_eip" {} # 将EIP 绑定到SLB resource "alicloud_eip_association" "charles_eip_slb_asso" { allocation_id = alicloud_eip.charles_eip .id instance_id = alicloud_slb.charles_slb .id } # SLB 的监听配置 resource "alicloud_slb_listener" "charles_listener_http" { load_balancer_id = alicloud_slb.charles_slb .id backend_port = "8080" frontend_port = "80" protocol = "http" bandwidth = "5" } # 配置负载均衡的后端服务器 resource "alicloud_slb_backend_server" "charles_slb_backend_server" { load_balancer_id = alicloud_slb.charles_slb .id backend_servers { server_id = alicloud_instance.charles -web1.id weight = 100 } backend_servers { server_id = alicloud_instance.charles -web2.id weight = 100 } }
到这一步基本的框架已经成型了,可以写个代码测试一下,我这里用go起了一个http server
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "log" "net/http" ) func main () { log.Fatal(http.ListenAndServe(":8080" , http.FileServer(http.Dir("/tmp" )))) }
1 2 3 4 nohup go run hello.go &curl "http://[eip address]"
配置数据库 步骤:
创建一个RDS Mysql实例
创建一个高权限用户
创建一个database实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # RDS resource "alicloud_db_instance" "charles_db_instance" { engine = "MySQL" engine_version = "5.6" instance_type = "rds.mysql.s1.small" instance_storage = "10" vswitch_id = alicloud_vswitch.charles_vsw .id instance_name = "charles_db_rds" } # RDS 账号 resource "alicloud_db_account" "charles_account" { instance_id = alicloud_db_instance.charles_db_instance .id type = "Super" name = "charles" password = "pass_1234" } # RDS database resource "alicloud_db_database" "charles_rds_db_test" { instance_id = alicloud_db_instance.charles_db_instance .id name = "mytest" character_set = "utf8mb4" description = "just for test" }
1 2 3 4 5 6 7 8 # 建好测试用的表 CREATE TABLE `accesslog` ( `id` int NOT NULL AUTO_INCREMENT, `log` varchar (1000 ) NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB DEFAULT CHARACTER SET = utf8COMMENT= 'access log' ;
到这里,最上面架构图里面的东西都已经部署完毕,并且配置好.接下来写一个简单的测试代码.
业务代码 这个web服务非常简单,就是每次用户访问都写一条log到mysql.还是用go来写,代码里用到了mysql的driver,部署的时候需要在机器上先下载依赖go get -u github.com/go-sql-driver/mysql
.完整代码在这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package mainimport ( "database/sql" "fmt" "net/http" "os" "strconv" "time" _ "github.com/go-sql-driver/mysql" ) func main () { http.HandleFunc("/hello" , indexHandler) http.HandleFunc("/" , rootHandler) http.ListenAndServe(":8080" , nil ) } func rootHandler (w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "ok" ) } func indexHandler (w http.ResponseWriter, r *http.Request) { result := dolog() fmt.Fprintf(w, result) } func dolog () string { db, err := sql.Open("mysql" , "root:password@tcp(mysql_ip:3306)/mytest?charset=utf8" ) if err != nil { panic (err) } insert(db) return query(db) } func query (db *sql.DB) string { rows, err := db.Query("select * from accesslog order by id desc" ) if err != nil { panic (err) } var result string for rows.Next() { var id int var log string err = rows.Scan(&id, &log) result += "id: " + strconv.Itoa(id) + ", log: " + log + "\n" } return result } func insert (db *sql.DB) { stmt, err := db.Prepare("insert accesslog set log=?" ) timeStr := time.Now().Format("2006-01-02 15:04:05" ) hostname, err := os.Hostname() stmt.Exec(timeStr + " @ " + hostname) if err != nil { panic (err) return } }
curl测试几次,大概会拿到下面的结果
1 2 3 4 5 6 7 8 9 10 11 id: 593, log: 2020-05-28 21:57:34 @ charlesweb1 id: 592, log: 2020-05-28 21:57:33 @ charlesweb2 id: 591, log: 2020-05-28 21:57:32 @ charlesweb1 id: 590, log: 2020-05-28 21:56:59 @ charlesweb2 id: 589, log: 2020-05-28 18:20:02 @ charlesweb1 id: 588, log: 2020-05-28 18:20:01 @ charlesweb2 id: 587, log: 2020-05-28 18:19:59 @ charlesweb2 id: 586, log: 2020-05-28 18:19:56 @ charlesweb1 id: 585, log: 2020-05-28 18:19:52 @ charlesweb2 id: 584, log: 2020-05-28 18:13:20 @ charlesweb1 id: 583, log: 2020-05-28 18:13:16 @ charlesweb1
总结 近年来,不管是上层的应用(k8s),到系统(docker),再到基础设施(terraform)都是声明式的.这个最大的好处就是继承了代码管理中的一些成熟经验,比如:
一旦习惯了这个就再也回不去了,真香! ^_^
Terraform在国内的普及率还非常低,希望没有尝试过的同学都可以试用一下.文章中涉及的完整配置文件在这里