用Terraform管理雲端Infra

2022/12/03

Terrform

Terraform是一套管理雲端資源的工具

傳統上在設定雲端資源都需要透過UI人工處理

進階一點可透過一些shell script加上各雲端平台的CLI工具來建立資源

甚至像AWS還有推出Cloud Formation這種工具來快速佈署資源做到自動化

 

但每個平台的設定方式都不相同

Terrform則是可以跨各種平台使用

它其實就類似AWS的Cloud Formation但不限於用在AWS上

用在AWS你可以開EC2、S3 Bucket、RDS

也可以同樣用在GCP上開Compute Engine、Cloud Storage Bucket、Cloud SQL

而且這些資源可同時間一起佈建

要刪除也是一鍵就可以同時刪除所有資源

 

建立AWS EC2資源

這邊參考Terraform的AWS Turtorial

 

設定AWS憑證

首先要設定一組AWS憑證(Key ID/Key Secret)

然後放到~/.aws/credentials中設定好profile

例如這邊profile設定為terraform-dev

[default]
aws_access_key_id = key_id_1
aws_secret_access_key = key_secret_1

[terraform-dev]
aws_access_key_id = key_id_2
aws_secret_access_key = key_secret_2

 

建立terrform設定檔

依照官方Turtoial的範例我做了一些小調整

首先可以在provider區塊看到我將AWS Region設定在ap-southeast-1(新加坡)、憑證profile選擇前面設定的terraform-dev

接著EC2做了一些設定(resource "aws_instance"區塊)

  • ami(ref): Instance Image, 這邊設定為Ubuntu 18.04
  • instance_type(ref): t3.nano
  • security_groups(ref): 設定為預先建立好的web, 這邊要注意security group要設定name不是ID
  • key_name(ref): EC2 Key Pair Name
  • user_data(ref): EC2的user data, 可以在EC2啟動的時候執行一些shell script
  • root_block_device(ref): EC2的Storage設定, 這邊設定為gp2/10GB
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "~> 4.16"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  region = "ap-southeast-1"
  # aws credentials profile
  profile = "terraform-dev"
}

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["amazon/ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical
}

resource "aws_instance" "app_server" {
  # 安裝ubuntu
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.nano"

  # 設定security group name(不是ID)
  security_groups = ["web"]

  # key pair
  key_name = "devops"

  # startup script
  user_data = <<EOF
#!/bin/bash
touch /home/ubuntu/foobar.txt
EOF

  # disk設定
  root_block_device {
    volume_size = 10
    volume_type = "gp2"
  }

  tags = {
    Name = "terraform test ec2"
    developer = "ciao chung"
  }
}

 

Init

使用terraform init指令

此步驟terraform會將provider的一些相關要用的程式先下載下來放到.terraform目錄中

另外terraform還會建立一個叫.terraform.lock.hcl的lock檔案鎖定provider的版本

以便後續有新的版本的時候比對是否要下載

 

Format and validate the configuration

ref

這邊主要是說可以透過terraform fmt指令來將terraform設定檔排版好

以及可以用terraform validate指令來驗證設定檔是否正確

除了可驗證格式外

還可以根據每個設定屬性去驗證填入的值是否正確

例如如果將EC2的storage disk type改為"gp1"

驗證的時候就會跳出錯誤

並且跟你說可用值有哪些

image.png

 

開始建立資源

ref

使用terraform apply指令即可開始建立資源

執行之後會顯示這次要操作的資源設定細節

並跳出confirm再次確認是否真的要執行

terraform-apply-ec2.png

 

如果在自動化的情況想跳過這個confirm

可使用terraform apply --auto-approve直接執行

 

更動資源

ref

當資源建立好之後想更動資源設定

可以重新apply一次

apply的時候terraform會顯示異動的設定

以下方截圖來說

我將instance type由t3.nano改為t2.nano

這時候就會顯示差異化的部份

確認後就可以進行自動更新

terraform-change-ec2.png

 

刪除資源

ref

刪除資源也非常簡單

使用terraform destroy指令確認後即可將所有資源全部刪除

 

使用File Function載入檔案內容

以前面的EC2設定來說

user_data如果要執行的shell script非常多會造成terraform設定檔很亂

所以可以額外抽出一個獨立的shell檔案(例如: start-script.sh)

接著使用File Function將這個start-script.sh載入即可

 

start-script.sh

#!/bin/bash
mkdir -p /home/ubuntu/foo/bar
echo "text..." > /home/ubuntu/foo/bar/data.txt

 

terraform的user_data這樣設定即可

user_data = file("start-script.sh")

 

Terraform變數

ref

Terraform內還可以定義變數

variable "instance_name" {
  description = "Value of the Name tag for the EC2 instance"
  type        = string
  default     = "ExampleAppServerInstance"
}

 

然後使用var套用在其他地方

 resource "aws_instance" "app_server" {
   ami           = "ami-08d70e59c07c61a3a"
   instance_type = "t2.micro"

   tags = {
    Name = var.instance_name
   }
 }

 

在terrform apply的時候

還可以帶入變數將其取代

terraform apply -var "instance_name=new-instance-1"

 

Store Remote State

ref

前面在執行terraform apply

會發現在執行目錄下會出現terraform.tfstate檔案

仔細去查看該檔案會看到terraform在上面紀錄這些佈署資源的資訊

這樣才能知道更新或刪除的時候要操作哪些資源

 

通常在單人運作的情況下這些檔案存在本機沒什麼問題

但如果是多人一起佈署同樣的一個資源

就會出現terraform.tfstate檔案無法同步的問題

所以terraform提供將state存到雲端的方式大家可以一起共用協作

 

這部份可直接參考這份文件

透過大家登入共同的Terraform Workspace

就可以共用AWS的憑證以及State

 

建立EC2的時候一併建立固定IP並綁定

有時候可能需要一建立EC2的時候就一併建立固定IP並且綁定在一起

可參考此段落範例

 

terrform設定檔

resource "aws_instance" "app_server" {
  ami             = "ami-031b15fc0d7ee2414"
  instance_type   = "t3.nano"
  security_groups = ["web"]
  key_name        = "ciao"

  tags = {
    Name = "EC2+EIP"
  }
}

resource "aws_eip" "app_eip" {
  vpc = true

  tags = {
    Name = "App Static IP"
  }
}

resource "aws_eip_association" "eip_assoc" {
  instance_id   = aws_instance.app_server.id
  allocation_id = aws_eip.app_eip.id
}

 

aws_eip

ref

AWS Elastic IP資源

 

aws_eip_association

ref

將EC2與Elastic IP綁定的操作

透過設定instance_id及allocation_id(elastic ip的id)來綁定

這邊可以看到instance_id的格式為aws_instance.app_server.id

此格式分解如下

  • aws_instance用來指定設定檔內的resource type
  • app_server則是用來指定該resource name
  • id: 該資源的ID, 這個是terraform在管理的所以它會自動去找出來關聯

 

而allocation_id的值aws_eip.app_eip.id其實也是一樣的意思

格式就是<resource_type>.<resource_name>.id

 

使用在GCP上

 

建立可操作GCP資源的Service Account

ref

這部份照官方文件建立即可就不贅述

只要依照需求把該給SA的權限給夠即可

 

terraform設定檔

這邊的範例為建立一個Cloud Storage Bucket

主要就是credentials要設定至service account json key的位置

然後設定project id

接下來就是依照要設定的資源去做找該資源的設定文件即可

這邊範例將projectcredentials抽為變數使用

 

另外這邊可以看到使用一個random_id的資源

可以用來隨機產生亂數用來建立bucket name

因為bucket name是唯一值用此方式才不會容易撞名

variable "project_id" {
  description = "GCP Project ID"
  type        = string
}

variable "sa_key" {
  description = "Service account json key path"
  type        = string
}

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "4.43.0"
    }
  }
}

provider "google" {
  credentials = var.sa_key
  project     = var.project_id
}

resource "google_storage_bucket" "my-bucket" {
  name          = "${random_id.bucket_prefix.hex}-my-bucket"
  location      = "asia"
  force_destroy = true
}

resource "random_id" "bucket_prefix" {
  byte_length = 8
}

 

使用.tfvars檔案帶入變數

雖然可以使用terraform apply --var project_id=foobar --var sa_key=/path/to/json-key這種方式來帶入變數

但如果變數越來越多

指令將會越來越複雜

因此可使用.tfvars這種方式將所有指令的變數集中至一個.tfvars變數檔案檔案中管理

例如建立一個var.tfvars檔案內容如下

# GCP project id
project_id="foobar"

# service account json key
"sa_key"="/path/to/json-key"

 

如此一來執行指令只要向這樣使用-var-file指定var.tfvars即可

terraform apply -var-file var.tfvars

 

使用tfvars.json檔案帶入變數

tfvars.json則是另一種變數檔案的格式

 

例如建立一個var.tfvars.json檔案內容如下

{
  "project_id": "foobar",
  "sa_key": "/path/to/json-key"
}

 

一樣使用-var-file指定tfvars.json即可

terraform apply -var-file var.tfvars.json

 

其他

 

.gitignore

ref

有些terraform操作的相關資訊不需要進版控

因此.gitignore需要排除這些檔案

.terraform/
*.tfstate
*.tfstate.backup
*.tfstate.lock.info
*.tfvars