1. 程式人生 > >雲平臺例項操作神器Terraform

雲平臺例項操作神器Terraform

Terraform功能簡介

TerraformIT 基礎架構自動化編排工具,它的口號是 "Write,Plan, and create Infrastructure as Code", 基礎架構即程式碼。

怎麼理解這句話,我們先假設在沒有Terraform的年代我們是怎麼操作雲服務。

方式一:直接登入到雲平臺的管控頁面,人工點選按鈕、鍵盤敲入輸入引數的方式來操作,這種方式對於單個或幾個雲伺服器還可以維護的過來,但是當雲服務規模達到幾十幾百甚至上千以後,明顯這種方式對於人力來說變得不再現實,而且容易誤操作。

方式二:雲平臺提供了各種SDK,將對雲服務的操作拆解成一個個的API供使用廠商通過程式碼來呼叫。這種方式明顯好於方式一,使大批量操作變得可能,而且程式碼測試通過後可以避免人為誤操作。但是隨之帶來的問題是廠商們需要專業的開發人員(Java

PythonPhpRuby等),而且對複雜雲平臺的操作需要寫大量的程式碼。

方式三:雲平臺提供了命令列操作雲服務的工具,例如AWS CLI,這樣租戶廠商不再需要軟體開發人員就可以實現對平臺的命令操作。命令就像Sql一樣,使用增刪改查等操作元素來管理雲。

方式四:Terraform主角登場,如果說方式三中CLI是命令式操作,需要明確的告知雲服務本次操作是查詢、新增、修改、還是刪除,那麼Terraform就是目的式操作,在本地維護了一份雲服務狀態的模板,模板編排成什麼樣子的,雲服務就是什麼樣子的。對比方式三的優勢是我們只需要專注於編排結果即可,不需要關心用什麼命令去操作。

 

Terraform的意義在於,通過同一套規則和命令來操作不同的雲平臺(包括私有云)。

 

Terraform知識準備:

核心檔案有2個,一個是編排檔案,一個是狀態檔案

main.tf檔案:是業務編排的主檔案,定製了一系列的編排規則,後面會有詳細介紹。

terraform.tfstate:本地狀態檔案,相當於本地的雲服務狀態的備份,會影響terraform的執行計劃。

如果本地狀態與雲服務狀態不一樣時會怎樣?

這個大家不需要擔心,前面介紹過Terraform是目的式的編排,會按照預設結果完成編排並最終同步更新本地檔案。

ProviderTerraform定製的一套介面,跟OpenStack

DirverJavaInterface的概念是一樣的,阿里雲、AWS、私有云等如果想接入進來被Terraform編排和管理就要實現一套Provider,而這些實現對於Terraform的頂層使用者來說是無感知的。

 

 

Terraform安裝:

官方安裝指南:https://www.terraform.io/intro/getting-started/install.html

本質是下載二進位制的檔案安裝到linux中,然後通過terraform命令來操作。

安裝後需要在path中配置terraform

export PATH=$PATH:/path/to/dir
export PATH=$PATH:/home/terraform
source ~/.bashrc

source只是讓配置立刻生效,如果要永久生效需要直接修改檔案

 

 方案1:在/etc/profile檔案中新增變數【對所有使用者生效(永久的)】

# vi /etc/profile

export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib

方案2:在使用者目錄下的.bash_profile檔案中增加變數【對單一使用者生效(永久的)】

 

 

Terraform的基本命令:

1mkdir一個乾淨的工作目錄,為後續操作做準備,該目錄就像git的倉庫,或者像軟體開發中的workspace

2,需要建立一個.tf檔案,指定provider等資訊,工作目錄下需要有至少一個tf檔案,否則後續命令無法進行。

3,執行terraform init命令,就像git init一樣,對當前目錄做初始化,下載tf中的provider,並喂後續的操作準備必要的環境條件。

4terraform plan,預覽執行計劃,不是必須的,但是強烈建議,好明白這次要把雲服務弄成什麼樣子。Ps:該命令在後期版本與apply合併成一個,所以請根據自己的版本來使用plan命令。

5terraform apply,真正執行編排計劃

6terraform show,展示現在狀態。

6terraform destroy,銷燬雲服務,將tf中的雲服務清理乾淨

 

 

Terraform的編排:

先看個簡單的官網的例子:

provider "aws" {
  access_key = "ACCESS_KEY_HERE"
  secret_key = "SECRET_KEY_HERE"
  region     = "us-east-1"
}

resource "aws_instance" "example" {
  ami           = "ami-2757f631"
  instance_type = "t2.micro"
}

Provider說明是個AWSprovider,剩下的鑑權和區域比較好理解;請注意provider的名字必須嚴格按照terraform的規則,不是你隨便亂填的。例如阿里雲對應的provider名稱是“alicloud”,你如果寫個“aliyun”是會出錯的。

Resource是在定義資源,第一個屬性aws_instance說明是個aws的例項,通過命名規則中的字首來指明provider;第二個屬性example是本resourcenameami指明用哪個映象來啟動例項;instance_type指定的是例項的“規格”,在雲服務裡定義了不同的型別來代表著伺服器不同的配置(CPU、記憶體、磁碟等硬體資源)。

 

Terrafomr執行時的output如下圖:

號代表的是新增操作,與使用git一樣,同理如果看到-號那就是要在雲上做刪除操作,修改操作+-會同時出現,本質上在雲上會先刪除再新增。向amiinstance_type我們顯示給定的,或者說本地倉庫檔案已知的屬性,會直接在右邊顯示出來;其他未知的顯示的是computed,代表的是要在雲上操作結束後才能知道。

 

Terrafrom的執行計劃:

有過Spark基礎的開發人員都知道一個概念DAG(有向無環圖),是為了最大程度地並行同時也要保證各任務間的依賴性。Terraform也是個並行執行的框架,而任務間的依賴性是通過顯示依賴和隱式依賴來實現的。

如基於上面的tf基礎上又配置了一個資源:

resource "aws_eip" "ip" {
  instance = "${aws_instance.example.id}"
}

那麼在建立aws_eip.id這個資源時由於裡面的instance需要指定aws_instance.exampleid,而aws_instance.exampleid只有例項建立後才能獲得,所以這就形成了一個隱式的依賴,執行計劃就要先執行aws_instance.example這個resource再執行aws_eip.ip這個resource

 

再看下面這個例子:

resource "aws_instance" "example" {
  ami           = "ami-2757f631"
  instance_type = "t2.micro"
  depends_on = ["aws_s3_bucket.example"]
}
resource "aws_s3_bucket" "example" {
  bucket = "terraform-getting-started-guide"
  acl    = "private"
}

aws_instance.example這個resource建立時通過depends_on屬性顯示的指定了依賴,所以先執行aws_s3_bucket.example這個執行計劃再回頭來建立這個例項。

如果resource間沒有依賴,terraform會並行的傳送任務到雲端完成任務。

 

如果你想在resource成功建立後執行某些操作,就需要用到Provisioner配置,示例:

resource "aws_instance" "example" {
  ami           = "ami-b374d5a5"
  instance_type = "t2.micro"

  provisioner "local-exec" {
    command = "echo ${aws_instance.example.public_ip} > ip_address.txt"
  }
}

resource建立完畢後可以在ip_address.txt檔案中看到該awsip地址。

Provisioner的使用會帶來一個問題,如果例項建立成功但是provisioner失敗會如何?Terraform並不具有關係型資料庫那樣的事務,一定要保證一起成功或失敗,如果發生這種情況,resource的例項會被成功建立但是狀態會被置為“tainted”汙染的,是為了告知雲使用者該服務並不是安全的。當再次執行resouce計劃時Terraform並不會在原來基礎上retry失敗的provisioner,而是整個resource剷掉重新執行一邊編排。

 

Terraform的出入參變數:

有些引數我們不想通過硬編碼的方式寫入到tf中,我們就會採用變數方式來搞定這種場景。

一般我們會把所有的變數都單獨拿到一個tf檔案裡去宣告,例如variables.tf,雖然不是必須要命名成variables.tf,但是我們約定俗成這麼做。

Variables.tf內容如下:

variable "access_key" {}
variable "secret_key" {}
variable "region" {
  default = "us-east-1"
}

很好理解,region我們給了預設值。

在其他tf中引用方式如下:

provider "aws" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region     = "${var.region}"
}

也很理解,那麼剩下的問題是如何在使用時設定這些變數?

方式一:命令引數設定

$ terraform apply -var 'access_key=foo' -var 'secret_key=bar'

方式二:預設引數檔案

Terraform預設會載入terraform.tfvars or *.auto.tfvars的檔案為初始化引數的檔案,檔案內容是鍵值對的方式:

access_key = "foo"
secret_key = "bar"

方式三:命令制定引數檔案

如果不按照方式二的命名規則,而是自己自定義檔名,可以採用方式一和方式二結合的方式指定引數檔案:

$ terraform apply -var-file="secret.tfvars" -var-file="production.tfvars"

方式四:操作使用者的環境變數中去獲取

Terraform會環境變數path中找TF_VAR_開頭的變數並把後面的內容對映成自己的變數引數,本方法不推薦。

方式五:什麼都不預配置,執行Terraform時遇到沒有賦值的變數會在控制檯給出提示讓操作員直接輸入。該方式不推薦,但是當輸入密碼等場景時從安全形度來說可以考慮使用。

 

除開String型別變數,Terraform還支援ListMap型別:

List的定義:

variable "cidrs" { type = "list" }

List的賦值:

cidrs = [ "10.0.0.0/16", "10.1.0.0/16" ]

Map的定義和賦值:

variable "amis" {
  type = "map"
  default = {
    "us-east-1" = "ami-b374d5a5"
    "us-west-2" = "ami-4b32be2b"
  }
}

Map的使用時會呼叫Terraform的內部函式:

resource "aws_instance" "example" {
  ami           = "${lookup(var.amis, var.region)}"
  instance_type = "t2.micro"
}

Lookup就是從amis這個map變數中根據region這個變數去get

 

 

學完Terraform的入參,Terraform的出參就變得很簡單了。

把一次編排看成Oracle的一個儲存過程,Terraform的出參就像是存過的產出,開發人員可以在編排時定義output出參來指定自己關心的內容,該內容會在任務執行的日誌中高亮顯示,而且在任務執行完畢後我們可以通過terrafomr output var_name的方式檢視引數結果。

出參宣告:

output "ip" {
  value = "${aws_eip.ip.public_ip}"
}

 

生產級應用我們往往將Terraformstate檔案維護在雲端或遠端伺服器,這樣既可以保證高可用性,也可以方便多名編排人員共同維護。

需要新增以下配置:

terraform {
  backend "consul" {
    address = "demo.consul.io"
    path    = "getting-started-RANDOMSTRING"
    lock    = false
  }
}

這樣在執行terraform init時就會在本地和remote端各維護一份狀態檔案。

 

TerraformIaas基礎維護方面的側重點,只是對雲平臺例項級別的管理,如果要對例項內部進行更復雜的編排需要配合ansible元件。