✅ 이 블로깅은 테라폼으로 시작하는 IaC 책을 기반으로 작성한다.


테라폼은 Stateful 애플리케이션이다. State 파일에 상태를 저장하고 프로비저닝한 모든 내용을 저장된 상태로 추적하기 때문이다.

테라폼은 Serial 넘버를 기준으로 State backup을 관리하고 있다.

State의 목적과 의미

아래는 State의 역할이다

  • State에는 테라폼 구성과 실제를 동기화하고 각 리소스에 고유한 아이디(리소스 주소)로 맵핑
  • 리소스 종속성과 같은 메타데이터를 저장하고 추적
  • 테라폼 구성으로 프로비저닝 결과를 캐싱하는 역할을 수행

상태 파일 확인 실습

resource "random_password" "mypw" {
  length           = 16
  special          = true
  override_special = "!#$%"
}

random 프로바이더의 random_password 리소스를 활용해서 랜덤으로 패스워드 생성이 가능하다.

참고 ) Terraform Registry - random_password

리소스를 생성할 때 민감 정보를 sensitive value로 가려진 채로 출력된다.

  1. terraform plan 및 apply 시

     + result           = (sensitive value)
    
  2. terraform state로 확인 시

     $ 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
     	}
    
  3. terraform console로 확인 시

     $ echo "random_password.mypw.result" | terraform console
     	(sensitive)
    

하지만 terraform.tfstate 파일에는 그대로 노출되어 있다.

$ cat terraform.tfstate | jq | grep result
            "result": "4#FZ3rNGBJc$%zk0",

이처럼 state 파일에는 민감한 정보가 그대로 노출될 수 있어 관리에 유의해야 한다.

State 동기화

Untitled

그림 출처 ) https://kschoi728.tistory.com/135

테라폼은 다음과 같은 과정을 거치게 된다.

  1. state 파일과 테라폼 코드를 비교
  2. plan 진행 시 Refresh 과정을 거침. 실제로 외부 통신을 하여 실제 리소스(Real)와 코드를 비교하는 과정을 거치게 된다
  3. create / replace / update / destroy 진행
  4. apply 진행 시 실제 리소스(Real)에 적용하고 State를 저장

여기서 2번 과정 Refresh는 실제 생성된 리소스와 비교하는 과정인데, 리소스가 많을수록 시간이 오래 소요된다. 이 때, -refresh=false를 사용하면 Plan 진행 시에 skip이 가능하다.

테라폼 구성에 추가된 리소스와 State에 따라 발생하는 동작 확인

유형 구성 리소스 정의 State 구성 데이터 실제 리소스 기본 예상 동작
1 있음     리소스 생성
2 있음 있음   리소스 생성
3 있음 있음 있음 동작 없음
4   있음 있음 리소스 삭제
5     있음 동작 없음
6 있음   있음  
  1. 유형1: 테라폼 코드만 있을 때 리소스 생성하기

     locals {
       name = "mytest"
     }
        
     resource "aws_iam_user" "myiamuser1" {
       name = "${local.name}1"
     }
        
     resource "aws_iam_user" "myiamuser2" {
       name = "${local.name}2"
     }
    

    AWS의 IAM 리소스에 test 이름을 가진 유저를 생성하였다.

     $ terraform state list
     	aws_iam_user.myiamuser1
     	aws_iam_user.myiamuser2
     $ terraform state show aws_iam_user.myiamuser1
     	# aws_iam_user.myiamuser1:
     	resource "aws_iam_user" "myiamuser1" {
     	    arn           = "arn:aws:iam::062168528481:user/mytest1"
     	    force_destroy = false
     	    id            = "mytest1"
     	    name          = "mytest1"
     	    path          = "/"
     	    tags_all      = {}
     	    unique_id     = "AIDAQ46MIOZQYTQCNYKRZ"
     	}
    

    state 파일을 확인해 보거나

    $ aws iam list-users | jq 또는 AWS 콘솔에서 생성된 유저를 확인해 보았다

    Untitled 1

    Untitled 2

  2. 유형2: 테라폼 코드와 state 파일만 있을 때 리소스 생성하기

     $ aws iam delete-user --user-name mytest1
     $ aws iam delete-user --user-name mytest2
    

    먼저 실제 리소스를 수동으로 지워 준다.

    Untitled 3

    그럼 state는 리소스가 생성됐다고 저장되어 있지만 Real 실제 리소스를 확인해 보면 없어져 있다

     $ terraform plan
     	aws_iam_user.myiamuser2: Refreshing state... [id=mytest2]
     	aws_iam_user.myiamuser1: Refreshing state... [id=mytest1]
        	
     	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)
     	    }
        	
     	  # aws_iam_user.myiamuser2 will be created
     	  + resource "aws_iam_user" "myiamuser2" {
     	      + arn           = (known after apply)
     	      + force_destroy = false
     	      + id            = (known after apply)
     	      + name          = "mytest2"
     	      + path          = "/"
     	      + tags_all      = (known after apply)
     	      + unique_id     = (known after apply)
     	    }
        	
     	Plan: 2 to add, 0 to change, 0 to destroy.
        	
     	──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
        	
     	Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions
     	if you run "terraform apply" now.
    

    그래서 terraform plan 시 위처럼 리소스를 다시 생성하게 된다.

     $ terraform plan -refresh=false
        
     	No changes. Your infrastructure matches the configuration.
        	
     	Terraform has compared your real infrastructure against your configuration and found no differences, so no changes
     	are needed.
    

    하지만 참고로 -refresh=false로 plan 실행하게 되면 실제 리소스와 비교하는 refresh 작업이 없기 때문에 No changes로 인식한다.

    그래서 실수로 리소스를 지워 버리는 상황이라면 안정성을 위해 -refresh=false를 사용한 plan 실행은 권장하지 않는다

  3. 유형3: 코드, State, 형상 모두 일치한 경우

     $ cat terraform.tfstate | jq .serial
     	6
    

    지금은 리소스가 생성되어 있는 상태고 시리얼 값은 3이다

    apply를 다시 적용하고 나서 시리얼 값을 확인해 보면

     $ terraform apply -auto-approve
     	aws_iam_user.myiamuser2: Refreshing state... [id=mytest2]
     	aws_iam_user.myiamuser1: Refreshing state... [id=mytest1]
        	
     	No changes. Your infrastructure matches the configuration.
        	
     	Terraform has compared your real infrastructure against your configuration and found no differences, so no changes
     	are needed.
        	
     	Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
     $ cat terraform.tfstate | jq .serial
     	9
     $ terraform apply -auto-approve
     	aws_iam_user.myiamuser1: Refreshing state... [id=mytest1]
     	aws_iam_user.myiamuser2: Refreshing state... [id=mytest2]
        	
     	No changes. Your infrastructure matches the configuration.
        	
     	Terraform has compared your real infrastructure against your configuration and found no differences, so no changes
     	are needed.
        	
     	Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
     $ cat terraform.tfstate | jq .serial
     	11
    

    어라… 원래는 시리얼 넘버가 동일해야 하는데… destroy하고 다시 생성해도 시리얼 넘버가 계속 바뀐다… 확인이 필요하다 ㅠ 일단 스킵..

  4. 유형4: 코드에서 일부 리소스 삭제

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

    main.tf에서 mytest2번 유저를 지운다.

     $ terraform apply -auto-approve
     	aws_iam_user.myiamuser2: Refreshing state... [id=mytest2]
     	aws_iam_user.myiamuser1: Refreshing state... [id=mytest1]
        	
     	Terraform used the selected providers to generate the following execution plan. Resource actions are indicated
     	with the following symbols:
     	  - destroy
        	
     	Terraform will perform the following actions:
        	
     	  # aws_iam_user.myiamuser2 will be destroyed
     	  # (because aws_iam_user.myiamuser2 is not in configuration)
     	  - resource "aws_iam_user" "myiamuser2" {
     	      - arn           = "arn:aws:iam::062168528481:user/mytest2" -> null
     	      - force_destroy = false -> null
     	      - id            = "mytest2" -> null
     	      - name          = "mytest2" -> null
     	      - path          = "/" -> null
     	      - tags          = {} -> null
     	      - tags_all      = {} -> null
     	      - unique_id     = "AIDAQ46MIOZQYAIBKE5FT" -> null
     	    }
        	
     	Plan: 0 to add, 0 to change, 1 to destroy.
     	aws_iam_user.myiamuser2: Destroying... [id=mytest2]
     	terraform state list
     	aws_iam_user.myiamuser2: Destruction complete after 1s
    

    그럼 mytest2 유저를 삭제하게 되고

     $ terraform state list
     	aws_iam_user.myiamuser1
     $ aws iam list-users | jq | grep mytest
           "UserName": "mytest1",
           "Arn": "arn:aws:iam::062168528481:user/mytest1",
    

    state를 확인하고 awscli로 iam 유저 리스트를 확인해 보면 유저1만 남은걸 확인할 수 있다.

  5. 유형5: 리소스만 있으며 코드, State는 없는 경우 → Import 또는 신규코드 작성
  6. 유형6: 실수로 tfstate 파일 삭제

     rm -rf terraform.tfstate*
    
     $ 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)
     	    }
        	
     	Plan: 1 to add, 0 to change, 0 to destroy.
        	
     	──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
        	
     	Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions
     	if you run "terraform apply" now.
     $ terraform plan -refresh=false
        
     	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)
     	    }
        	
     	Plan: 1 to add, 0 to change, 0 to destroy.
        	
     	──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
        	
     	Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions
     	if you run "terraform apply" now.
    

    terraform planterraform plan -refresh=false 간의 차이가 없다.

    왜냐하면 plan 시에 이미 state 파일이 없어, state 파일을 새로 만들기 때문에 refresh 과정에서도 새로 만들게 되는 것이다.

     $ terraform apply -auto-approve
        
     	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)
     	    }
        	
     	Plan: 1 to add, 0 to change, 0 to destroy.
     	aws_iam_user.myiamuser1: Creating...
     	╷
     	│ Error: creating IAM User (mytest1): EntityAlreadyExists: User with name mytest1 already exists.
     	│       status code: 409, request id: efdc00fe-93dd-4c37-8a5d-289dc67c3357
     	│ 
     	│   with aws_iam_user.myiamuser1,
     	│   on main.tf line 5, in resource "aws_iam_user" "myiamuser1":
     	│    5: resource "aws_iam_user" "myiamuser1" {
     	│ 
     	╵
    

    하지만 apply를 실행하게 되면 실제 리소스에는 이미 유저가 있기 때문에 에러가 발생하게 된다.

    위 상황에서는 import로 state를 수동복구하는 방법이 최선이라고 한다!

결론

  • state 파일은 민감 정보를 가지고 있어 관리가 중요하다
  • state 파일을 실수로 삭제하는 경우에 복구하는 방법은 import가 최선이므로 백업이 필요하다

Categories:

Updated:

Leave a comment