iT邦幫忙

2023 iThome 鐵人賽

DAY 18
0
DevOps

AWS ECS + Gitlab + Laravel + Terraform 從入門到摔坑系列 第 18

Day 18 Terraform variable & 驗證 configuration

  • 分享至 

  • xImage
  •  

import 完 resource 需要 resource 們是否正確及完整,否則佈建另一個環境(例如正式環境)可能才發現 configuration 是無法運作的。那要怎麼驗證呢?可以到另一個 AWS region 進行 terraform apply 把 resource 建立上去,檢查是不是一切正常。(本日程式碼

Terraform 的變數(variable)

要用剛寫好的 configuration 到另一個 region 佈建,第一件要做的事情是:把所有 import 過程產生的 region 名稱 ap-northeast-1 變成變數。(不然…要…怎麼…換 region… 😶)

這邊介紹一個新的 block:variable block。它長得像這樣:

variable "[NAME]" {
  type = [TYPE]
  default = [DEFAULT VALUE]
}

接在 variable 關鍵字後面的是變數名稱,type 指定變數是字串或者數字或者 boolean 等等型態,不指定也沒關係。default 是這個變數的預設值,也就是如果沒有給值,它就會使用的值。假如變數沒有給預設值,terraform 在 apply 跟 plan 的時候會要求給變數值。

給值的方式有兩種,一個是用 .tfvars 檔案配合參數 -var-file 指定,或者不用參數指定、直接使用預設檔案 terraform.tfvarsterraform.tfvars.json 。如果 terraform 沒找到 terraform.tfvars ,也沒有用 -var-file 指定,變數又沒有預設值的話,plan 跟 apply 的時候會跳訊息要求輸入變數值:

https://ithelp.ithome.com.tw/upload/images/20230928/20160671Asj57jQ6F5.png

如果變數多……都是直接用檔案指定啦……一個個手 key……嗯… 😨

把值抽(extract)成變數之後,使用變數的地方用 var.[NAME] 來 reference 變數。我們通常會把 variable block 集中放在 variables.tf 這個檔案裡。(註:筆者習慣用 extract variable 這個詞,寫成中文就是「抽變數」)

下面來看看實際操作~

新增 variables.tf 檔案,在裡面加入 region 變數:

variable "region" {
  type    = string
  default = "ap-northeast-1"
}

要使用變數的地方改成:

region           = var.region

如果字串裡用到變數,要用 ${}var 跟變數名稱包起來,像這樣:

name        = "CodeBuildBasePolicy-my-app-${var.region}"

name 最後的值會是 CodeBuildBasePolicy-my-app- 加上 region 變數展開的值。

terraform.tfvars 裡指定變數值:

region = "ap-northeast-1"

這樣 apply 跟 plan 的時候 region 變數的值就是 ap-northeast-1

前面說到 -var-file 可以指定不同的 .tfvars 檔案,還有 Day 6 說到 stage 跟 production 環境幾乎一樣、差別在機器與資源的規模比較小。所以我們可以為不同環境各自建立 .tfvars 檔案(像是 stage.tfvarsprod.tfvars),裡面設定各個環境所需要的參數(EC2 instance type、task desired count 等等),然後用 -var-file 做 plan 跟 apply,就能用同一份 configuration 對多個環境進行佈建了!

利用變數我們可以透過一份 configuration 建立多個環境,避免手動操作或者使用多份 configuration 造成環境有所差異,這對以「跟正式環境相同,只是規模較小,好能夠在類似正式的環境中進行測試」為目的的 stage 環境是很重要的~

把 region 抽成變數後,我們到另一個 region 用 terraform 建立 infrastructure 吧!

到首爾建立 resource 以驗證 configuration 的正確性

開始前,我們要先做個小小的手腳。因為現在儲存 state 的 backend 依然是 local,也就是本地檔案,它們記錄著東京 region 的 resource,可以用 terraform state list 列出目前的 resource 們。我們無法在這個情況下到另一個 region 建立 resource,因為 state 覺得你已經建好了,除非多寫 resource block 才會有新的 resource(但這樣不對啊…)。我們這邊簡單處理:建立一個新的資料夾,把 terraform.tfstateterraform.tfstate.backend 移進去(有興趣的朋友可以嘗試使用 workspace 功能):

$ mkdir origin-state
$ mv terraform.tfstate terraform.tfstate.backup origin-state

這樣我們又得到一個乾淨的 state 了~

接下來我們把 terraform.tfvars 裡的 region 變數值改成 ap-northeast-2 (首爾),接著 terraform apply

然後遇到一堆 error,兵來將擋水來土淹,來 error 就解吧~~

IAM Role 相關 error

https://ithelp.ithome.com.tw/upload/images/20230928/20160671uuWDCxPNqm.png

https://ithelp.ithome.com.tw/upload/images/20230928/20160671CaL0J8KLlp.png

這兩個是說 IAM role 已經存在,不能再建立相同名稱的 role。雖然我們把 resource 建立在 ap-northeast-2 region,但 IAM role 是 global level 的、所有 region 都是用同樣的 IAM role 們,我們當然不能再建立同樣名稱的 role。

簡單解法:把名字抽成變數後指定另一個名稱。

可能會想問為什麼不是把 resource 拿掉或者 import 現有的 IAM role 而是換名稱?因為我們現在是在驗證整份 configuration 的正確與完整性,它需要擁有在一個全新 AWS account 環境中建立起我們要的 resource 的能力,我們要模擬這個狀況才算是驗證。真正使用如果是在相同 AWS account 下建立 resource,依據情境有可能會希望共用 resource,那時候再用 import 或其他方式處理。

抽出兩個 variable:

variable "codebuild_project_role_name" {
    type = string
    default = "codebuild-my-app-service-role"
}

variable "ec2_inst_role_name" {
    type = string
    default = "ecsInstanceRole"
}

並且在 terraform.tfvars 指定:

codebuild_project_role_name = "codebuild-my-app-service-role-ap-northeast-2"
ec2_inst_role_name = "ecsInstanceRole-ap-northeast-2"

resource 設定名稱的地方也要改用 var.codebuild_project_role_namevar.ec2_inst_role_name

接下來跟前面 IAM role 也是相同問題――同樣名稱的 instance profile 已經存在,我們一樣用變數幫 instance profile 換個名字。

https://ithelp.ithome.com.tw/upload/images/20230928/20160671c5xZgnNIFZ.png

variable "ec2_inst_profile_name" {
    type = string
    default = "ecsInstanceRole"
}

並且指定:

ec2_inst_profile_name = "ecsInstanceRole-ap-northeast-2"

Security Group 相關 error

https://ithelp.ithome.com.tw/upload/images/20230928/201606715jnf7nIux6.png

不能用 default 這個保留字當作 security group 的名稱,它對應的是我們 aws_security_group.default resource。這邊要改用 aws_default_security_group resource 來管理預設 security group:

resource "aws_default_security_group" "default" {
  egress = [
    {
      cidr_blocks      = ["0.0.0.0/0"]
      description      = ""
      from_port        = 0
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      protocol         = "-1"
      security_groups  = []
      self             = false
      to_port          = 0
    }
  ]
  ingress = [
    {
      cidr_blocks      = []
      description      = ""
      from_port        = 0
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      protocol         = "-1"
      security_groups  = []
      self             = true
      to_port          = 0
    }
  ]
  revoke_rules_on_delete = null
  tags                   = {}
  tags_all               = {}
  vpc_id                 = aws_vpc.vpc.id
}

Subnet 相關 error

https://ithelp.ithome.com.tw/upload/images/20230928/201606716aWnPD0Z2K.png

這裡它說找不到 vpc,但從 AWS web console 是有看到建立的 vpc 的,那我們來看看這幾個 resource:

resource "aws_subnet" "public_1a" {
  assign_ipv6_address_on_creation                = false
  availability_zone                              = "${var.region}a"
  cidr_block                                     = "172.16.0.0/24"
  customer_owned_ipv4_pool                       = null
  enable_dns64                                   = false
  enable_resource_name_dns_a_record_on_launch    = false
  enable_resource_name_dns_aaaa_record_on_launch = false
  ipv6_cidr_block                                = null
  ipv6_native                                    = false
  map_public_ip_on_launch                        = true
  outpost_arn                                    = null
  private_dns_hostname_type_on_launch            = "ip-name"
  tags = {
    Name = "my-app-public-1a"
  }
  tags_all = {
    Name = "my-app-public-1a"
  }
  vpc_id = "vpc-04828d35609756ab9"
}

噢,原來是忘記用 vpc resource 指定 vpc_id ,既然看到了就把在 network.tf 所有類似問題通通修正(commit)。

Route Table 相關 error

https://ithelp.ithome.com.tw/upload/images/20230928/201606715bTsCw3Eav.png

這個 error 是說我們不能用 terraform 建立 target 是 local 的 route,要用 terraform 管理這種 route 的話要 import。這邊我們改用 terraform import 這個指令來 import resource,這是在 terraform v1.5 以前、沒有 import block 時 import resource 的方式。

參考 aws_route 文件 的 import 部份好知道要以 AWS cloud 上的什麼資訊作為識別來 import:

$ terraform import aws_route.internal rtb-044d756cdd33184bf_172.16.0.0/16

import 正常的話會出現這樣的畫面:

https://ithelp.ithome.com.tw/upload/images/20230928/20160671l0plpnpvTY.png

Auto Scaling Group 相關 error

https://ithelp.ithome.com.tw/upload/images/20230928/20160671WlJekPiWPv.png

因為 AMI 不是 global level 的,相同 AMI 在不同 region 會有不同 id,所以我們先在東京(ap-northeast-1)找到 ami-0f386dc4885ec169f AMI 的名稱是 amzn2-ami-ecs-hvm-2.0.20230606-x86_64-ebs 。接著到首爾(ap-northeast-2)用名稱找到對應的 AMI id 是 ami-0063312c13bc1e1ad 。一樣,把 launch template 內指定 ami id 的參數值抽成變數,然後在 terraform.tfvars 指定 ami-0063312c13bc1e1ad

除了 AMI id 之外,我們也把 launch template 的名稱抽成變數並且指定另一個名稱給首爾使用。

除了 launch template 的 AMI id,也要修正 auto scaling group 寫死的 launch template name:

  launch_template {
    name    = "ECSLaunchTemplate_BuALY8Zexo9F"
    version = "1"
  }

改成:

  launch_template {
    name    = aws_launch_template.template.name
    version = "1"
  }

接著 error 就變成了:

https://ithelp.ithome.com.tw/upload/images/20230928/20160671zrZFbBiDrQ.png

因為 key pair 也不是跨 region 的,所以我們要在首爾 region 建立一個 key pair。名稱可以跟東京的相同也可以不同,如果名稱不同就要改用變數表示 key pair 名稱。

ALB 相關 error

https://ithelp.ithome.com.tw/upload/images/20230928/20160671x9deT0k1np.png

我們不能同時在 ALB 指定 subnet_mappingsubnets ,刪掉其中一個吧~

ECS 相關 error

https://ithelp.ithome.com.tw/upload/images/20230928/20160671gTspJHjfjq.png

我們在 ECS service resource 裡直接寫死 task definition 的名稱,改成:

task_definition                    = aws_ecs_task_definition.td.family

CodePipeline 相關 error

https://ithelp.ithome.com.tw/upload/images/20230928/20160671zxPyfzw3CF.png

看起來我們前面 import resource 的時候少 import codepipeline 需要的 s3 bucket(所以說要驗證…真的會漏啊…),這邊我們直接加上 s3 bucket resource:

resource "aws_s3_bucket" "codepipeline_artifact" {
    bucket = "my-app-codepipeline-artifact-ap-northeast-2"
}

並且修正 aws_codepipeline.pipelineartifact_store

artifact_store {
  location = aws_s3_bucket.codepipeline_artifact.bucket
  region   = null
  type     = "S3"
}

好~!總算把 error 都解完、順利 apply 到首爾 region 了!


上一篇
Day 17 Terraform import container & deployment resource
下一篇
Day 19 完整驗證 & EventBridge
系列文
AWS ECS + Gitlab + Laravel + Terraform 從入門到摔坑30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言