前言
像寫程式碼一樣管理基礎設施。
Terraform 使用較為高階的配置檔案語法來描述基礎設施,這個特性讓你對配置檔案進行版本化管理後,就等於對生產環境的基礎設施進行類似於程式碼一樣的版本化管理,而且這些基礎設施的配置檔案可以複用或者分享。
介紹
Terraform(https://www.terraform.io/)是 HashiCorp 旗下的一款開源(Go 語言開發)的 DevOps 基礎架構資源管理運維工具。他的本質是基於版本化的管理能力上,安全、高效地建立和修改使用者生產環境的基礎設施。
Terraform 有很多非常強大的特性值得我們參考:
- 基礎設施即程式碼:Infrastructure as Code。基礎設施可以使用高階配置語法進行描述,使得基礎設施能夠被程式碼化和版本化,從而可以進行共享和重複使用。
- 執行計劃:Execution Plans。Terraform有一個 "計劃 "步驟,在這個步驟中,它會生成一個執行計劃。執行計劃顯示了當你呼叫apply時,Terraform會做什麼,這讓你在Terraform操作基礎設施時避免任何意外。
- 資源圖譜:Resource Graph。Terraform建立了一個所有資源的圖,並行建立和修改任何非依賴性資源。從而使得Terraform可以儘可能高效地構建基礎設施,操作人員可以深入瞭解基礎設施中的依賴性。
- 自動化變更:Change Automation。複雜的變更集可以應用於您的基礎設施,而只需最少的人工干預。有了前面提到的執行計劃和資源圖,你就可以準確地知道Terraform將改變什麼,以及改變的順序,從而避免了許多可能的人為錯誤。
Terraform 術語
術語 | 基本介紹 |
---|---|
Provider | 又稱為Plugin,主要用來跟其他的服務進行互動從而實現資源管理,服務安裝等 |
Module | Module是一個將多種資源整合到一起的一個容器,一個module由一些列的.tf或者.tf.json字尾檔案組成 |
Resource | 主要用來定義各種資源或者服務,而這些服務就組成了我們的基礎設施架構 |
Registry | Provider倉庫,主要用來儲存各種的provider,同時我們也會從Registry下載本地定義的provider到本地 |
Terraform 如何工作
Terraform 採用了外掛模式的執行機制。Terraform 使用 RPC(遠端介面呼叫) 跟 Terraform Plugins 進行通訊,同時也提供了多種方式來發現和載入 Plugins;而 Terraform Plugins 會和具體的 Provider 進行對接,例如:AWS、Kubernates、Azure 等等,封裝各種資源操作的介面供 Terraform Core 使用。
Terraform使用的是HashiCorp自研的go-plugin庫(https://github.com/hashicorp/go-plugin),本質上各個Provider外掛都是獨立的程式,與Terraform程式之間透過rpc進行呼叫。Terraform引擎首先讀取並分析使用者編寫的Terraform程式碼,形成一個由data與resource組成的圖(Graph),再透過rpc呼叫這些data與resource所對應的Provider外掛;Provider外掛的編寫者根據Terraform所制定的外掛框架來定義各種data和resource,並實現相應的CRUD方法;在實現這些CRUD方法時,可以呼叫目標平臺提供的SDK,或是直接透過呼叫Http(s) API來操作目標平臺。
關於provider
預設情況下Terraform從官方Provider Registry下載安裝Provider外掛。Provider在Registry中的原始地址採用類似registry.terraform.io/hashicorp/aws
的編碼規則。通常為了簡便,Terraform允許省略地址中的主機名部分registry.terraform.io
,所以我們可以直接使用地址hashicorp/aws。
有時無法直接從官方Registry下載外掛,例如我們要在一個與公網隔離的環境中執行Terraform時。為了允許Terraform工作在這樣的環境下,有一些可選方法允許我們從其他地方獲取Provider外掛。
安裝
Terraform是以二進位制可執行檔案釋出,只需下載terraform,然後將terraform可執行檔案所在目錄新增到系統環境變數PATH中即可。
登入Terraform官網,下載對應作業系統的安裝包。
解壓安裝包,並將terraform可執行檔案所在目錄新增到系統環境變數PATH中。
在命令列中執行如下命令驗證配置路徑是否正確。
terraform version
開啟本地快取
有的時候下載某些Provider會非常緩慢,或是在開發環境中存在許多的Terraform專案,每個專案都保有自己獨立的外掛資料夾非常浪費磁碟,這時我們可以使用外掛快取。
Windows下是在相關使用者的%APPDATA%目錄(如C:\Users\Administrator\AppData\Roaming)下建立名為"terraform.rc"的檔案,Macos和Linux使用者則是在使用者的home下建立名為".terraformrc"的檔案。在檔案中配置如下:
plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"
當啟用外掛快取之後,每當執行terraform init命令時,Terraform引擎會首先檢查期望使用的外掛在快取資料夾中是否已經存在,如果存在,那麼就會將快取的外掛複製到當前工作目錄下的.terraform資料夾內。如果外掛不存在,那麼Terraform仍然會像之前那樣下載外掛,並首先儲存在外掛資料夾中,隨後再從外掛資料夾複製到當前工作目錄下的.terraform資料夾內。為了儘量避免同一份外掛被儲存多次,只要作業系統提供支援,Terraform就會使用符號連線而不是實際從外掛快取目錄複製到工作目錄。
需要特別注意的是,Windows 系統下plugin_cache_dir的路徑也必須使用/作為分隔符,應使用C:/somefolder/plugin_cahce而不是C:\somefolder\plugin_cache
demo1(docker+nginx)
可以用阿里雲等做demo,但云資源一次執行要好幾分鐘,而且很多操作都是要收費的,學習的時候很不方便,就在本地用docker進行學習吧,第一次下載provider慢一點,後面就是幾秒鐘就可以測試一個例子了。
所以看下面的demo,需要先下載並安裝docker。可以使用菜鳥教程
demo來自官方,但做了適當的簡化。
使用docker,並在裡面安裝一個nginx。
provider 的使用文件,見 https://registry.terraform.io/browse/providers
新建一個檔案,檔名隨便,就叫main.tf
,內容如下
terraform {
required_providers {
// 宣告要用的provider
docker = {
source = "kreuzwerker/docker"
// 要用的版本,不寫預設拉取最新的
//version = "~> 2.13.0"
}
}
}
// 語法 resource resource_type name
// 宣告docker_image的映象,nginx只是一個名字,只要符合變數命名規則即可
resource "docker_image" "nginx" {
// 使用的是映象名,https://hub.docker.com/layers/nginx/library/nginx/latest/images/sha256-3536d368b898eef291fb1f6d184a95f8bc1a6f863c48457395aab859fda354d1?context=explore
name = "nginx:latest"
}
resource "docker_container" "nginx" {
image = docker_image.nginx.name
name = "nginx_test"
ports {
internal = 80 // docker 內部的埠
external = 8000 // 對外訪問的埠
}
volumes {
container_path = "/usr/share/nginx/html"
host_path = "/Users/jasper/data/nginx_home"
}
}
新建目錄/Users/jasper/data/nginx_home
,windows去VirtualBox那操作,裡面新建一個檔案index.html
,內容隨便來點,就“Hello Terraform”吧。
terraform init
下載需要的provider,第一次可能有點慢
terraform plan
檢視執行計劃
terraform apply
輸入yes即可
最後就可以在瀏覽器進行訪問了
http://127.0.0.1:8000/index.html
demo2(docker+zookeeper+kafka)
kafka的安裝是需要依賴zookeeper的,這個例子就展示terraform是如何處理依賴的,並且展示在多資源的情況下,模組編寫的推薦方式。
下面顯示了一個遵循標準結構的模組的完整示例。這個例子包含了所有可選的元素。
$ tree complete-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── ...
├── modules/
│ ├── nestedA/
│ │ ├── README.md
│ │ ├── variables.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ ├── nestedB/
│ ├── .../
├── examples/
│ ├── exampleA/
│ │ ├── main.tf
│ ├── exampleB/
│ ├── .../
- README檔案用來描述模組的用途。
- examples資料夾用來給出一個呼叫樣例(可選)
- variables.tf檔案,包含模組所有的輸入變數。輸入變數應該有明確的描述說明用途
- outputs.tf檔案,包含模組所有的輸出值。輸出值應該有明確的描述說明用途
- modules子目錄,嵌入模組資料夾,出於封裝複雜性或是複用程式碼的目的,我們可以在modules子目錄下建立一些嵌入模組。
- main.tf,它是模組主要的入口點。對於一個簡單的模組來說,可以把所有資源都定義在裡面;如果是一個比較複雜的模組,我們可以把建立的資源分佈到不同的程式碼檔案中,但引用嵌入模組的程式碼還是應保留在main.tf裡
程式碼見:https://gitee.com/zhongxianyao/terraform-demo/tree/master/demo-zk-kafka
main.tf
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
}
}
}
module "zookeeper" {
// 一個本地路徑必須以./或者../為字首來標明要使用的本地路徑,以區別於使用Terraform Registry路徑。
source = "./modules/zk"
}
module "kafka" {
source = "./modules/kafka"
// 由於依賴了zk模組是輸出引數,terraform能夠分析出來依賴關係,這裡無需depends_on引數
# depends_on = [
# module.zookeeper
# ]
zk_port = module.zookeeper.zk_port
}
modules/zk/main.tf
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
}
}
}
resource "docker_image" "zookeeper" {
name = "ubuntu/zookeeper:latest"
}
resource "docker_container" "zookeeper" {
image = docker_image.zookeeper.name
name = "zookeeper_test"
ports {
internal = 2181
external = 12181
}
}
modules/zk/outputs.tf
output "zk_port" {
// 在表示式中引用資源屬性的語法是<RESOURCE TYPE>.<NAME>.<ATTRIBUTE>。
value = docker_container.zookeeper.ports[0].external
}
modules/kafka/main.tf
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
}
}
}
resource "docker_image" "kafka" {
name = "ubuntu/kafka:latest"
}
resource "docker_container" "kafka" {
image = docker_image.kafka.name
name = "kafka_test"
// zk的埠傳遞
env = [
"ZOOKEEPER_PORT=${var.zk_port}"
]
ports {
internal = 9092
external = 19092
}
}
modules/kafka/inputs.tf
variable "zk_port" {
default = 2181
description = "zookeeper的埠"
}
最後terraform命令
terraform init
terraform plan
terraform apply