Terraform Study #4

Dongmin Hanยท2023๋…„ 9์›” 21์ผ
0

Terraform-Study

๋ชฉ๋ก ๋ณด๊ธฐ
4/5


๐Ÿ’ก โ€˜ํ…Œ๋ผํผ์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” IaCโ€™ ์ฑ…์œผ๋กœ ์ง„ํ–‰ํ•˜๋Š” Terraform ์Šคํ„ฐ๋””[T101] 4์ฃผ์ฐจ ์ •๋ฆฌ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

4์ฃผ์ฐจ

์ด๋ฒˆ ์ฃผ์ฐจ์—์„œ๋Š” ๊ธฐ๋ณธ ๋ฌธ๋ฒ•์„ ๋„˜์–ด, ์ฝ”๋“œ๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๊ณ  ํ˜‘์—…ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋ฐฐ์šด๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ๋Š” module๊ณผ state์— ๋Œ€ํ•ด ํ•™์Šตํ•˜๋ฉฐ, ํ˜‘์—…๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์€ 5์ฃผ์ฐจ์—์„œ ๋” ์ž์„ธํ•˜๊ฒŒ ๋‹ค๋ฃฌ๋‹ค.

State

์•„๋ž˜๋Š” 1์ฃผ์ฐจ ์ •๋ฆฌ๋‚ด์šฉ์ด๋‹ค. ํ…Œ๋ผํผ์—์„œ๋Š” State ํŒŒ์ผ์„ Serial์„ ๊ธฐ์ค€์œผ๋กœ backup ๊ด€๋ฆฌํ•œ๋‹ค.

Terraform์˜ .tfstate ํŒŒ์ผ ๋‚ด์˜ serial ๊ฐ’์€ ์ƒํƒœ ํŒŒ์ผ์˜ ๋ฒ„์ „์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ๋™์‹œ์„ฑ ์ œ์–ด์™€ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ํ™•์ธ์—๋„ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ Terraform ๋ช…๋ น์ด ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ์ฆ๊ฐ€ํ•˜์—ฌ, ์ƒํƒœ ํŒŒ์ผ์˜ ์ตœ์‹ ์„ฑ๊ณผ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— backup ํŒŒ์ผ์ด ํ˜„์žฌ state ํŒŒ์ผ๋ณด๋‹ค serial ๋ฒˆํ˜ธ๊ฐ€ ๋‚ฎ๋‹ค.

์ด๋ก ์ ์ธ ๋‚ด์šฉ

์ƒํƒœ ํŒŒ์ผ์€ ๋ฐฐํฌํ•  ๋•Œ๋งˆ๋‹ค ๋ณ€๊ฒฝ๋˜๋Š” ํ”„๋ผ์ด๋น— API์ด๋ฉฐ, ์˜ค์ง ํ…Œ๋ผํผ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉ์šฉ๋„์ด๋‹ˆ ์ง์ ‘ ํŽธ์ง‘ํ•˜๊ฑฐ๋‚˜ ์ž‘์„ฑํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค. (ํŒŒ์ผ ๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ํ†ตํ•ด, API๋ฅผ ์š”์ฒญํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.)

๋งŒ์•ฝ, ํ…Œ๋ผํผ์„ ํ†ตํ•ด ํ˜‘์—…์„ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค๋ฉด state ํŒŒ์ผ์„ ๊ด€๋ฆฌํ•ด์•ผ ํ•œ๋‹ค. ์ด๋•Œ๋Š” 1์ฃผ์ฐจ๋•Œ ์ง„ํ–‰ํ–ˆ๋˜ ์›๊ฒฉ ๋ฐฑ์—”๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

ํŒ€ ๋‹จ์œ„ ์šด์˜์‹œ ํ•„์š”ํ•œ ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • state ํŒŒ์ผ์˜ ๊ณต์œ  ์Šคํ† ๋ฆฌ์ง€
  • Locking(ํ•œ๋ช…์— ํ•œ๋ช…์”ฉ)
  • ํŒŒ์ผ ๊ฒฉ๋ฆฌ(dev, stage ๋“ฑ ํ™˜๊ฒฝ ๋ณ„ ๊ฒฉ๋ฆฌ๊ฐ€ ํ•„์š”)

์•„๋ž˜๋Š” VCS๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ ์ด๋‹ค.

  • VCS: ์ˆ˜๋™์œผ๋กœ ์ƒํƒœํŒŒ์ผ์„ push, pull ํ•ด์•ผ ํ•˜๋‹ˆ ํœด๋จผ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.++ Lock ๊ธฐ๋Šฅ์ด ์—†๋‹ค.

๊ฒฐ๊ตญ, ํ…Œ๋ผํผ์„ ์ง€์›ํ•˜๋Š” ์›๊ฒฉ ๋ฐฑ์—”๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. S3, Terraform Cloud ๋“ฑ์ด ์žˆ๋‹ค.

๊ด€๋ จ ์‹ค์Šต

State ํŒŒ์ผ์—๋Š” ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ๋ชจ๋“  ๊ฒƒ์ด ๋‹ด๊ฒจ์žˆ๋‹ค. ์•„๋ž˜์˜ ์‹ค์Šต์„ ํ†ตํ•ด, ์ƒํƒœ ํŒŒ์ผ ๊ด€๋ฆฌ์˜ ์ค‘์š”์„ฑ์„ ํ™•์ธํ•ด๋ณธ๋‹ค.

  • ํŒจ์Šค์›Œ๋“œ ๋ฆฌ์†Œ์Šค ์ฝ”๋“œ
resource "random_password" "mypw" {
  length           = 16
  special          = true
  override_special = "!#$%"
}

์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ

terraform init && terraform plan && terraform apply -auto-approve

ํ…Œ๋ผํผ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ๋ฆฌ์†Œ์Šค๋ฅผ ํ™•์ธํ•˜๋ฉด, ์‹œํฌ๋ฆฟ ์ •๋ณด๋Š” ์•Œ๋ ค์ฃผ์ง€ ์•Š๋Š”๋‹ค.

โฏ terraform state show random_password.mypw

# random_password.mypw:
resource "random_password" "mypw" {
    bcrypt_hash      = (sensitive value)
    id               = "none"
    length           = 16
    lower            = true
    min_lower        = 0
    min_numeric      = 0
    min_special      = 0
    min_upper        = 0
    number           = true
    numeric          = true
    override_special = "!#$%"
    result           = (sensitive value)
    special          = true
    upper            = true
}

ํ•˜์ง€๋งŒ, state ํŒŒ์ผ์„ ํ™•์ธํ•ด๋ณด๋ฉด ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ์ „๋ถ€ ์กด์žฌํ•œ๋‹ค.

{
  ...
  "resources": [
    {
      "mode": "managed",
      "type": "random_password",
     ...
      "instances": [
        {
          "schema_version": 3,
          "attributes": {
            "bcrypt_hash": "$2a$10$pLMnmRKSY52ageoVumPlpuP5dyo2GZpomOxo6MsQetO/F28dR2ge2",
            "id": "none",
          ...
            "result": "CLTOsYB9zWifY9WT",
            "special": true,
            "upper": true
          },
          "sensitive_attributes": []
        }
      ]
    }
  ],
  "check_results": null
}

ํ…Œ๋ผํผ ์ฝ˜์†”์—์„œ๋„ sensitive value๋Š” ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค.

echo "random_password.mypw" | terraform console
{
  "bcrypt_hash" = (sensitive value)
  "id" = "none"
  "keepers" = tomap(null) /* of string */
  "length" = 16
  ...
  "override_special" = "!#$%"
  "result" = (sensitive value)
  "special" = true
  "upper" = true
}

.tfstate

Terraform์˜ .tfstate ํŒŒ์ผ ๋‚ด์˜ serial ๊ฐ’์€ ์ƒํƒœ ํŒŒ์ผ์˜ ๋ฒ„์ „์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ๋™์‹œ์„ฑ ์ œ์–ด์™€ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ํ™•์ธ์—๋„ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ Terraform ๋ช…๋ น์ด ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ์ฆ๊ฐ€ํ•˜์—ฌ, ์ƒํƒœ ํŒŒ์ผ์˜ ์ตœ์‹ ์„ฑ๊ณผ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— backup ํŒŒ์ผ์ด ํ˜„์žฌ state ํŒŒ์ผ๋ณด๋‹ค serial ๋ฒˆํ˜ธ๊ฐ€ ๋‚ฎ๋‹ค.

์œ ํ˜•๊ตฌ์„ฑ ๋ฆฌ์†Œ์Šค ์ •์˜State ๊ตฌ์„ฑ ๋ฐ์ดํ„ฐ์‹ค์ œ ๋ฆฌ์†Œ์Šค๊ธฐ๋ณธ ์˜ˆ์ƒ ๋™์ž‘
1์žˆ์Œ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ
2์žˆ์Œ์žˆ์Œ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ
3์žˆ์Œ์žˆ์Œ์žˆ์Œ๋™์ž‘ ์—†์Œ
4์žˆ์Œ์žˆ์Œ๋ฆฌ์†Œ์Šค ์‚ญ์ œ
5์žˆ์Œ๋™์ž‘ ์—†์Œ
6์žˆ์Œ์žˆ์Œ
  • -refresh=false ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด, ํ˜„์žฌ์˜ stateํŒŒ์ผ๊ณผ ํ…Œ๋ผํผ์ฝ”๋“œ๋ฅผ ๋น„๊ตํ•˜์—ฌ ๊ทธ๋Œ€๋กœ ์ ์šฉํ•œ๋‹ค. ์›๊ฒฉ ๋ฆฌ์†Œ์Šค์˜ ์‹ค์ œ ์ƒํƒœ๋Š” ํ™•์ธํ•˜์ง€ ์•Š์Œ. ๊ทธ๋ ‡๊ธฐ์— ๋งŒ์•ฝ ์›๊ฒฉ๋ฆฌ์†Œ์Šค๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์–ด๋„ ๋‹ค์‹œ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค.

์œ ํ˜•6๋ฒˆ ์‹ค์Šต์ง„ํ–‰

  • IAM user๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฆฌ์†Œ์Šค ํ™•์ธ
locals {
  name = "mytest"
}

resource "aws_iam_user" "myiamuser1" {
  name = "${local.name}1"
}

resource "aws_iam_user" "myiamuser2" {
  name = "${local.name}2"
}
  • ๋ฐฐํฌ์ง„ํ–‰
terraform apply -auto-approve
  • ๋ฐฐํฌ ์ƒํƒœ ํ™•์ธ
aws iam list-users | jq '.Users[] | .UserName'
"admin"
"mytest1"
"mytest2"
  • tfstate ํŒŒ์ผ ์‚ญ์ œ
rm -rf terraform.tfstate*
โฏ ls terraform.tfstate*
zsh: no matches found: terraform.tfstate*
  • Plan ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ํŒŒ์•…ํ•˜์ง€ ๋ชปํ•˜๊ณ  ์ƒˆ๋กญ๊ฒŒ ์ƒ์„ฑํ•˜๋ ค๊ณ  ํ•จ.
$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_iam_user.myiamuser1 will be created
  + resource "aws_iam_user" "myiamuser1" {
      + arn           = (known after apply)
      + force_destroy = false
      + id            = (known after apply)
      + name          = "mytest1"
      + path          = "/"
      + tags_all      = (known after apply)
      + unique_id     = (known after apply)
    }
  • apply ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, EntityAlreadyExists ์—๋Ÿฌ ๋ฐœ์ƒ
$ terraform apply
...
Plan: 2 to add, 0 to change, 0 to destroy.
aws_iam_user.myiamuser2: Creating...
aws_iam_user.myiamuser1: Creating...
โ•ท
โ”‚ Error: creating IAM User (mytest1): **EntityAlreadyExists: User with name mytest1 already exists.**
โ”‚       status code: 409, request id: e32ae858-e9eb-4c3a-a6ab-d7dba9f8bbd8
โ”‚ 
โ”‚   with aws_iam_user.myiamuser1,
โ”‚   on main.tf line 5, in resource "aws_iam_user" "myiamuser1":
โ”‚    5: resource "aws_iam_user" "myiamuser1" {
โ”‚ 
โ•ต
  • ์ด๋Ÿด๋•Œ๋Š” import ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค, IAM user์˜ ID๋Š” ์œ ์ €์˜ ์ด๋ฆ„์ด๋ฏ€๋กœ ์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
    • terraform import [options] ADDRESS ID
terraform import aws_iam_user.myiamuser1 mytest1                     
aws_iam_user.myiamuser1: Importing from ID "mytest1"...
aws_iam_user.myiamuser1: Import prepared!
  Prepared aws_iam_user for import
aws_iam_user.myiamuser1: Refreshing state... [id=mytest1]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
  • tfstate ํ™•์ธ, myiamuser1์˜ ์ƒํƒœํŒŒ์ผ์ด ์ถ”๊ฐ€๋˜์—ˆ๋‹ค.
{
  "version": 4,
  "terraform_version": "1.5.6",
  "serial": 4,
  "lineage": "0e52f56e-fe1e-d6e7-acb3-f521a3c2f365",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_iam_user",
      "name": "myiamuser1",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
						...
            "id": "mytest1",
            "name": "mytest1",
            
          },
          "sensitive_attributes": [],
...
}

์›Œํฌ์ŠคํŽ˜์ด์Šค

State๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋…ผ๋ฆฌ์ ์ธ ๊ฐ€์ƒ ๊ณต๊ฐ„์„ ์›Œํฌ์ŠคํŽ˜์ด์Šค๋ผ๊ณ ํ•œ๋‹ค.

๊ฐœ๋ฐœ์šฉ ํ™˜๊ฒฝ, ์Šคํ…Œ์ด์ง• ํ™˜๊ฒฝ, ์šด์˜ํ™˜๊ฒฝ์€ ๋Œ€๋ถ€๋ถ„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ๊ฑฐ์˜ ์œ ์‚ฌํ•œ ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•œ๋‹ค๊ณ  ํ•˜๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ํ†ตํ•ด ์šด์˜ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋™์ผํ•œ ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•œ๋‹ค๋ฉด ์ด๋Š” ์ผ๊ด€์„ฑ ์œ ์ง€์— ์ข‹์ง€ ์•Š๋‹ค. ํ…Œ๋ผํผ์—์„œ๋Š” ์ด๋ฅผ ์œ„ํ•ด workspace๋ฅผ ์ง€์›ํ•œ๋‹ค. ํ•˜๋‚˜์˜ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ terraform.workspace ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ํ™˜๊ฒฝ๋งˆ๋‹ค ๋ฆฌ์†Œ์Šค๋ฅผ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

vs ์•„๋ž˜๋Š” ์›Œํฌ์ŠคํŽ˜์ด์Šค๊ฐ€ ์•„๋‹Œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ”„๋กœ์ ํŠธ๋กœ ๊ตฌ์„ฑํ•œ ๋ชจ์Šต์ด๋‹ค.

๐Ÿ’ก ์„ค๋ช…ํ•ด์ฃผ์‹  ์›Œํฌ์ŠคํŽ˜์ด์Šค์˜ ์žฅ๋‹จ์ 
  • ์žฅ์ 
    • ํ•˜๋‚˜์˜ ๋ฃจํŠธ ๋ชจ๋“ˆ์—์„œ ๋‹ค๋ฅธ ํ™˜๊ฒฝ์„ ์œ„ํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋™์ผํ•œ ํ…Œ๋ผํผ ๊ตฌ์„ฑ์œผ๋กœ ํ”„๋กœ๋น„์ €๋‹ํ•˜๊ณ  ๊ด€๋ฆฌ
    • ๊ธฐ์กด ํ”„๋กœ๋น„์ €๋‹๋œ ํ™˜๊ฒฝ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝ ์‚ฌํ•ญ ์‹คํ—˜ ๊ฐ€๋Šฅ
    • ๊นƒ์˜ ๋ธŒ๋žœ์น˜ ์ „๋žต์ฒ˜๋Ÿผ ๋™์ผํ•œ ๊ตฌ์„ฑ์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฆฌ์†Œ์Šค ๊ฒฐ๊ณผ ๊ด€๋ฆฌ - [์ฐธ๊ณ  : ํ™”ํ•ด - Git ๋ธŒ๋žœ์น˜ ์ „๋žต ์ˆ˜๋ฆฝ์„ ์œ„ํ•œ ์ „๋ฌธ๊ฐ€์˜ ์กฐ์–ธ๋“ค]
  • ๋‹จ์ 
    • State๊ฐ€ ๋™์ผํ•œ ์ €์žฅ์†Œ(๋กœ์ปฌ ๋˜๋Š” ๋ฐฑ์—”๋“œ)์— ์ €์žฅ๋˜์–ด State ์ ‘๊ทผ ๊ถŒํ•œ ๊ด€๋ฆฌ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅ(์–ด๋ ค์›€)
    • ๋ชจ๋“  ํ™˜๊ฒฝ์ด ๋™์ผํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ…Œ๋ผํผ ๊ตฌ์„ฑ์— ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ๋‹ค์ˆ˜ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
    • ํ”„๋กœ๋น„์ €๋‹ ๋Œ€์ƒ์— ๋Œ€ํ•œ ์ธ์ฆ ์š”์†Œ๋ฅผ ์™„๋ฒฝํžˆ ๋ถ„๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์›€ โ†’ ๊ฐ€์žฅ ํฐ ๋‹จ์ ์€ ์™„๋ฒฝํ•œ ๊ฒฉ๋ฆฌ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅ โ‡’ ํ•ด๊ฒฐ๋ฐฉ์•ˆ 1. ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ฃจํŠธ ๋ชจ๋“ˆ์„ ๋ณ„๋„๋กœ ๊ตฌ์„ฑํ•˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ธฐ๋ฐ˜์˜ ๋ ˆ์ด์•„์›ƒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
      โ‡’ ํ•ด๊ฒฐ๋ฐฉ์•ˆ 2. Terraform Cloud ํ™˜๊ฒฝ์˜ ์›Œํฌ์ŠคํŽ˜์ด์Šค๋ฅผ ํ™œ์šฉ

Module

๋ชจ๋“ˆ์€ ๋Œ€๋ถ€๋ถ„์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ ์“ฐ์ด๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ํŒจํ‚ค์ง€์™€ ์—ญํ• ์ด ๋น„์Šทํ•˜๋‹ค.

์ค‘๋ณต๋˜๊ฑฐ๋‚˜, ์ž์ฃผ์“ฐ๋Š” ์ฝ”๋“œ๋ฅผ ๋ชจ๋“ˆํ™”ํ•ด์„œ ํŽธํ•˜๊ฒŒ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ชจ๋“ˆ ๋””๋ ‰ํ„ฐ๋ฆฌ ํ˜•์‹์€ terraform-<ํ”„๋กœ๋ฐ”์ด๋” ์ด๋ฆ„>-<๋ชจ๋“ˆ ์ด๋ฆ„> ํ˜•์‹์„ ์ œ์•ˆํ•œ๋‹ค.

์ด ํ˜•์‹์€ Terraform Cloud, Terraform Enterprise์—์„œ๋„ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ์‹์œผ๋กœ
1) ๋””๋ ‰ํ„ฐ๋ฆฌ ๋˜๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ด๋ฆ„์ด ํ…Œ๋ผํผ์„ ์œ„ํ•œ ๊ฒƒ์ด๊ณ , 2) ์–ด๋–ค ํ”„๋กœ๋ฐ”์ด๋”์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, 3) ๋ถ€์—ฌ๋œ ์ด๋ฆ„์ด ๋ฌด์—‡์ธ์ง€ ํŒ๋ณ„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

๊ตฌ์กฐ

์•„๋ž˜์™€ ๊ฐ™์ด ๋ฃจํŠธ ๋ชจ๋“ˆ์—์„œ ์ž์‹ ๋ชจ๋“ˆ์„ ์ฐธ์กฐํ•œ๋‹ค. ์ž์‹๋ชจ๋“ˆ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ๋ฃจํŠธ ๋ชจ๋“ˆ์ด main ํ•จ์ˆ˜์ด๋‹ค. ์ž์‹ ๋ชจ๋“ˆ์„ ํ˜ธ์ถœํ•  ๋•Œ, ๋ณ€์ˆ˜๋„ ๋งž๊ฒŒ ๋Œ€์ž…ํ•œ๋‹ค.

์ถœ์ฒ˜: https://jloudon.com/cloud/Azure-Policy-as-Code-with-Terraform-Part-1/

๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด, ์•„๋ž˜์˜ ์ž์‹ ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. isDB๋ผ๋Š” ๋ณ€์ˆ˜์˜ ๊ฐ’์„ ๋Œ€์ž…ํ•ด์ค˜์•ผํ•˜๊ณ , id์™€ pw๋ฅผ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.

# main.tf
resource "random_pet" "name" {
  keepers = {
    ami_id = timestamp()
  }
}

# DB์ผ ๊ฒฝ์šฐ Password ์ƒ์„ฑ ๊ทœ์น™์„ ๋‹ค๋ฅด๊ฒŒ ๋ฐ˜์˜ 
resource "random_password" "password" {
  length           = var.isDB ? 16 : 10
  special          = var.isDB ? true : false
  override_special = "!#$%*?"
}
# variable.tf
variable "isDB" {
  type        = bool
  default     = false
  description = "ํŒจ์Šค์›Œ๋“œ ๋Œ€์ƒ์˜ DB ์—ฌ๋ถ€"
}
# output.tf
output "id" {
  value = random_pet.name.id
}

output "pw" {
  value = nonsensitive(random_password.password.result) 
}

๋ฃจํŠธ ๋ชจ๋“ˆ

"mypw1"์˜ ๊ฒฝ์šฐ๋Š” ๋ณ€์ˆ˜๋ฅผ ๋Œ€์ž…ํ•˜์ง€ ์•Š์•˜์œผ๋‹ˆ, isDB์˜ ๊ธฐ๋ณธ๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ชจ๋“ˆ์„ ํ†ตํ•ด ์ฝ”๋“œ๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๊ณ  ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

module "mypw1" {
  source = "../modules/terraform-random-pwgen"
}

module "mypw2" {
  source = "../modules/terraform-random-pwgen"
  isDB   = true
}

output "mypw1" {
  value  = module.mypw1
}

output "mypw2" {
  value  = module.mypw2
}

ํ”„๋กœ๋ฐ”์ด๋” ์ •์˜

๋ฃจํŠธ๋ชจ๋“ˆ์—์„œ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. ๋งŒ์•ฝ, ์ž์‹๋ชจ๋“ˆ์—์„œ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ •์˜ํ•˜๋ฉด ๋ฃจํŠธ ๋ชจ๋“ˆ์— ๋ฒ„์ „์ด ๋‹ค๋ฅด๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ๋ชจ๋“ˆ์— ๋ฐ˜๋ชฉ๋ฌธ์„ ์“ธ ์ˆ˜ ์—†๋‹ค.

์•„๋ž˜์˜ module โ€œexampleโ€๊ณผ ๊ฐ™์ด ๋ฃจํŠธ ๋ชจ๋“ˆ์— ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์„ ์–ธํ•œ๋‹ค.

# The default "aws" configuration is used for AWS resources in the root
# module where no explicit provider instance is selected.
provider "aws" {
  region = "us-west-1"
}

# An alternate configuration is also defined for a different
# region, using the alias "usw2".
provider "aws" {
  alias  = "usw2"
  region = "us-west-2"
}

# An example child module is instantiated with the alternate configuration,
# so any AWS resources it defines will use the us-west-2 region.
module "example" {
  source    = "./example"
  providers = {
    aws = aws.usw2
  }
}

๋ชจ๋“ˆ์€ ์œ„์—์„œ ์ง„ํ–‰ํ•˜๋“ฏ์ด ๋กœ์ปฌํŒŒ์ผ๋กœ ๊ฐ€๋Šฅํ•˜๋ฉฐ ํ…Œ๋ผํผ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ, ๊นƒํ—ˆ๋ธŒ ๋“ฑ์—์„œ ๊ฐ€์ ธ์™€์„œ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

ex) Terraform registry์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ

module "consul" {
  source = "hashicorp/consul/aws"
  version = "0.1.0"
}

ํ˜‘์—…

S3๋ฅผ ํ†ตํ•ด, ๋ฐฑ์—”๋“œ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์€ 1์ฃผ์ฐจ๋•Œ ์ง„ํ–‰ํ–ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” Terraform Cloud๋ฅผ ์ด์šฉํ•˜์—ฌ TFC ๋ฐฑ์—”๋“œ๋ฅผ ๊ตฌ์„ฑํ•ด๋ณธ๋‹ค. ๋‹น์—ฐํžˆ Lock ๊ธฐ๋Šฅ๊ณผ ๋ฒ„์ „๊ด€๋ฆฌ๋„ ์ง€์›ํ•œ๋‹ค.

Terraform Cloud

state ๊ด€๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•˜๋Š” TFC๋Š” ๋ฌด์ƒ์ด๋ผ๊ณ  ํ•œ๋‹ค.

๐Ÿ’ก TFC
  • ์ œ๊ณต ๊ธฐ๋Šฅ : ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๋ฌด๋ฃŒ, State ํžˆ์Šคํ† ๋ฆฌ ๊ด€๋ฆฌ, State lock ๊ธฐ๋ณธ ์ œ๊ณต, State ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ๋น„๊ต ๊ธฐ๋Šฅ
  • Free Plan ์—…๋ฐ์ดํŠธ : ์‚ฌ์šฉ์ž 5๋ช… โ†’ ๋ฆฌ์†Œ์Šค 500๊ฐœ, ๋ณด์•ˆ ๊ธฐ๋Šฅ(SSO, Sentinel/OPA๋กœ Policy ์‚ฌ์šฉ) - ๋งํฌ

์›Œํฌ์ŠคํŽ˜์ด์Šค ์ƒ์„ฑ

  • https://app.terraform.io/ ๋งํฌ ์ ‘์† ํ›„ ๊ณ„์ • ์ƒ์„ฑ
  • workflow ์„ ํƒ ํ™”๋ฉด์—์„  Create a new organization ์„ ํƒ
  • Connect
  • GitHub์™€ ๊ฐ™์€ ๋ฒ„์ „๊ด€๋ฆฌ ์‹œ์Šคํ…œ์— ์—ฐ๊ฒฐํ• ๊ฑฐ๋ฉด VCS๋ฅผ ์„ ํƒํ•œ๋‹ค.
  • CLI-driven ์„ ํƒ

terraform login์„ ์ง„ํ–‰ํ•œ๋‹ค.

$ terraform login
[ํ† ํฐ ์ž…๋ ฅ]

ํ† ํฐ ํ™•์ธ

cat ~/.terraform.d/credentials.tfrc.json | jq
{
  "credentials": {
    "app.terraform.io": {
      "token": "YMgr4VM...EWGuw"
    }
  }
}

provider.tf์—์„œ ํ…Œ๋ผํผ ํด๋ผ์šฐ๋“œ๋ฅผ ์ •์˜ํ•œ๋‹ค.

terraform {
  cloud {
    organization = "kane-org"         # ์ƒ์„ฑํ•œ ORG ์ด๋ฆ„ ์ง€์ •
    hostname     = "app.terraform.io" # default

    workspaces {
      name = "terraform-stduy" # ์—†์œผ๋ฉด ์ƒ์„ฑ๋จ
    }
  }
}

์ดํ›„, terraform init ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด .terraform ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ  ์•ˆ์— ์ƒํƒœํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค. ์•„๋ž˜๋Š” ์ƒํƒœํŒŒ์ผ ์„ธ๋ถ€๋‚ด์šฉ์ด๋‹ค.

{
    "version": 3,
    "serial": 1,
    ...
    "backend": {
        "type": "cloud",
        "config": {
            "hostname": "app.terraform.io",
            "organization": "kane-org",
            "token": null,
            "workspaces": {
                "name": "terraform-stduy",
                "tags": null
            }
        },

์ด์ œ init && plan ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํด๋ผ์šฐ๋“œ์—์„œ ๋™์ž‘ํ•œ๋‹ค. [terraform cloud local ์„ค์ •x]

์„ค์ •์„ ํ†ตํ•ด, terraform ์ž‘์—…์„ ๋ชจ๋‘ ๋กœ์ปฌ์—์„œ ๋Œ๋ฆฌ๊ณ  state ํŒŒ์ผ๋งŒ ์—…๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค.

Plan์ด ๋ชจ๋‘ ๋™์ž‘ํ•˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด UI๋กœ ๋ฐฐํฌ๋  ๋ฆฌ์†Œ์Šค๋ฅผ ์•Œ๋ ค์ค€๋‹ค.

ํ™•์‹คํžˆ GUI๋กœ ๋ณด๋‹ˆ ๊น”๋”ํ•œ ๊ฒƒ ๊ฐ™๋‹ค. ํŠนํžˆ ๋ฆฌ์†Œ์Šค๊ฐ€ ๋งŽ์•„์กŒ์„ ๋•Œ ๋ณด๊ธฐํŽธํ•  ๊ฒƒ ๊ฐ™๋‹ค.

๋„์ „๊ณผ์ œ3

๊ฐ์ž ์‚ฌ์šฉํ•˜๊ธฐ ํŽธ๋ฆฌํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ชจ๋“ˆํ™” ํ•ด๋ณด๊ณ , ํ•ด๋‹น ๋ชจ๋“ˆ์„ ํ™œ์šฉํ•ด์„œ ๋ฐ˜๋ณต ๋ฆฌ์†Œ์Šค๋“ค ๋ฐฐํฌํ•ด๋ณด์„ธ์š”!

VPC, Subnet ๋“ฑ EC2์— ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ชจ๋“ˆํ™”ํ•ด๋ดค๋‹ค. ๋ณด์•ˆ๊ทธ๋ฃน์˜ ํฌํŠธ๋Š” SSH, HTTP๋ฅผ ์—ด์–ด๋†จ๋‹ค.

  • module/main.tf
    locals {
      additional_tags = {
        Name = var.namespace
      }
    }
    
    resource "aws_vpc" "vpc" {
      cidr_block = "192.169.0.0/16"
      tags       = local.additional_tags
    }
    
    data "aws_availability_zones" "available" {
      state = "available"
    }
    
    resource "aws_subnet" "public_subnet" {
      vpc_id                  = aws_vpc.vpc.id
      cidr_block              = "192.169.1.0/24"
      availability_zone       = data.aws_availability_zones.available.names[0]
      map_public_ip_on_launch = true
      tags                    = local.additional_tags
    }
    
    resource "aws_internet_gateway" "igw" {
      vpc_id = aws_vpc.vpc.id
      tags   = local.additional_tags
    }
    resource "aws_route_table" "public_route_table" {
      vpc_id = aws_vpc.vpc.id
      route {
        cidr_block = "0.0.0.0/0"
        gateway_id = aws_internet_gateway.igw.id
      }
      tags = local.additional_tags
    }
    
    resource "aws_route_table_association" "public_rtb_assoc" {
    
      subnet_id      = aws_subnet.public_subnet.id
      route_table_id = aws_route_table.public_route_table.id
    }
    
    resource "aws_security_group" "web_sg" {
      name   = var.namespace
      vpc_id = aws_vpc.vpc.id
    
      ingress {
        from_port   = var.ssh_port
        to_port     = var.ssh_port
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
    
      ingress {
        from_port   = var.http_port
        to_port     = var.http_port
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
    
      egress {
        from_port   = 0
        to_port     = 0
        protocol    = "-1"
        cidr_blocks = ["0.0.0.0/0"]
      }
    
    }
    data "aws_ami" "default" {
      most_recent = true
      owners      = ["amazon"]
    
      filter {
        name   = "owner-alias"
        values = ["amazon"]
      }
    
      filter {
        name   = "name"
        values = ["amzn2-ami-hvm*"]
      }
    }
    
    resource "aws_instance" "app" {
      ami                    = data.aws_ami.default.id
      instance_type          = var.ec2_instance_type
      key_name               = var.key_name
      vpc_security_group_ids = [aws_security_group.web_sg.id]
      subnet_id              = aws_subnet.public_subnet.id
      tags                   = local.additional_tags
    }
  • module/output.tf
    output "instance_public_ip" {
      value       = aws_instance.app.public_ip
      description = "The public IP address of the App instance"
    }
  • module/variable.tf
    variable "ssh_port" {
      default     = 22
      type        = number
      description = "SSH port"
    }
    variable "http_port" {
      default     = 80
      type        = number
      description = "HTTP port"
    }
    variable "ec2_instance_type" {
      type        = string
      description = "The type of EC2 instance to launch"
    }
    variable "key_name" {
      type        = string
      description = "The key name to use for an EC2 instance"
    }
    variable "namespace" {
      type        = string
      description = "env namespace"
    }
  • root/main.tf
    locals {
      env = {
        dev = {
          instance_type = "t3.micro"
          key_name      = "m1"
          namespace     = "dev"
        }
        prod = {
          instance_type = "t3.medium"
          key_name      = "m1"
          namespace     = "prod"
        }
      }
    }
    
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    module "ec2_aws_amazone" {
      for_each          = local.env
      source            = "../../module/ec2"
      key_name          = each.value.key_name
      ec2_instance_type = each.value.instance_type
      namespace         = each.value.namespace
    }
    
    # output.tf
    output "module_output_instance_public_ip" {
      value = [
        for k in module.ec2_aws_amazone : k.instance_public_ip
      ]
    }

์ด์ œ, ํ…Œ๋ผํผ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐฐํฌํ•œ๋‹ค.

$ tf apply -auto-approve
module.ec2_aws_amazone["dev"].data.aws_availability_zones.available: Reading...
module.ec2_aws_amazone["prod"].data.aws_availability_zones.available: Reading...
module.ec2_aws_amazone["prod"].data.aws_ami.default: Reading...
module.ec2_aws_amazone["dev"].data.aws_ami.default: Reading...
module.ec2_aws_amazone["dev"].data.aws_availability_zones.available: Read complete after 1s [id=ap-northeast-2]
module.ec2_aws_amazone["prod"].data.aws_availability_zones.available: Read complete after 1s [id=ap-northeast-2]
module.ec2_aws_amazone["dev"].data.aws_ami.default: Read complete after 1s [id=ami-0ec77cfb1037681eb]
module.ec2_aws_amazone["prod"].data.aws_ami.default: Read complete after 1s [id=ami-0ec77cfb1037681eb]
Apply complete! Resources: 14 added, 0 changed, 0 destroyed.

Outputs:

module_output_instance_public_ip = [
  "3.35.235.54",
  "13.124.205.208",
]

AWS ์ฝ˜์†”์—์„œ ๋ฐฐํฌ๋œ ๋ชฉ๋ก์„ ํ™•์ธํ•œ๋‹ค.

  • vpc
  • EC2

ํ™˜๊ฒฝ ๋ณ„๋กœ, ๋ฐฐํฌ๊ฐ€ ์ž˜ ๋œ ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

profile
์ดˆ๋ณด ๋ฐ๋ธŒ์˜ต์Šค ์—”์ง€๋‹ˆ์–ด, ํ”ผ๋“œ๋ฐฑ์€ ์–ธ์ œ๋‚˜ ํ™˜์˜์ž…๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€