Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

Terraform workspace의 활용

Terraform으로 인프라스트럭처를 관리하면 기존보다 훨씬 체계적으로 관리할 수 있고 히스토리를 추적하기도 좋아지지만, 실제 인프라스트럭처를 직접 다루기 때문에 실수에 대한 걱정이 있는 것이 사실이다. 물론 이를 방지하기 위해 plan으로 미리 확인해 볼 수 있지만 그래도 잘못 생각하고 apply를 해서 리소스가 제거된다거나 잘못 변경되어 장애가 난다거나 하는 가능성이 충분히 있다. 그리고 이게 애플리케이션이 아니라 인프라스트럭처의 설정이므로 일부 설정은 적용을 해봐야 동작 여부를 확인 가능한 것들이 있다. 그래서 항상 프로덕션에서 apply를 할 때는 두려움이 생겨서 2~3번씩 확인해 보게 된다.

특히 인프라스터럭처의 경우 소소한 변경은 다시 적용하면 되지만 리소스를 잘못 제거하면 장애가 크게 발생하거나 최악으로는 원래 상태로 롤백하기 어려운(혹은 오래 걸리는) 상황이 발생할 수 있다. 100% Terraform으로 관리하고 있다면 롤백 걱정이 줄어들겠지만 복잡한 회사 인프라스트럭처에서 100% 관리하에 두는 것은 쉽지 않다.

이렇게 Terraform을 사용하다 보니 인프라에서 프로덕션 외에 스테이징이나 테스트환경이 있으면 좋겠다는 생각이 자연히 들게 되었다. Infrastructure as code를 하다 보면 소프트웨어 개발에서 오랫동안 쌓아온 패턴을 거의 비슷하게 활용할 수 있다는 것을 알게 되는데 서버 애플리케이션을 배포할 때 코드를 배포판으로 만들어서 테스트 서버나 스테이징 서버에서 테스트하고 이상이 없으면 똑같은 코드를 프로덕션에도 배포하도록 배포 절차를 만드는 것이 일반적이다. 이를 통해 테스트한 소프트웨어가 프로덕션에 배포될 수 있게 보장하는 것인데 Terraform으로도 똑같이 하고 싶어졌다.

프로덕션 인프라스트럭처를 직접 조작하는 것이 부담되고 실수 가능성이 있으므로(특히, 민감한 설정 부분이라면 더욱...) 아예 VPC 레벨이나 계정 차원에서 테스트나 스테이징용 인프라스트럭처가 있고 이쪽에서 설정하고 설정이 이상 없음을 확인하면 같은 설정을 그대로 프로덕션 인프라스트럭처에 적용해서 안심하고 작업할 수 있게 하고 싶었다.(물론 제대로 되려면 여기서 프로덕션 적용은 자동화가 이루어져야 할 것이다.)

워크스페이스

Terraform에는 워크스페이스라는 기능이 있다. 이 기능은 Terraform 0.9 버전에서는 environment라는 이름으로 존재했는데 environment라는 단어의 의미가 모호하다는 이유로 0.10부터는 workspace로 바뀌었다.

이 기능이 존재를 알았을 때부터 위에서 말한 문제를 해결할 수 있을지 확인해 보고 싶었다. 사실 테스트는 지난 9월 정도에 했지만, 블로그에 정리는 이제야 하게 된다.(어느새 2달이!)

Terraform을 사용할 때 인지할 수는 없지만 이미 워크스페이스 내에서 작업을 하고 있다. 기본 워크스페이스는 default라는 이름을 가지는데 이 워크스페이스가 기본 이름이고 제거할 수 없다. 그래서 명시적으로 워크스페이스를 지정하지 않으면 default 워크스페이스를 사용하게 된다.

사용방법

terraform workspace 명령어를 사용하면 관련 명령어를 볼 수 있다.(여기서 사용하는 버전은 Terraform 0.11.0이다.)

$ terraform workspace
Usage: terraform workspace

  Create, change and delete Terraform workspaces.


Subcommands:

    show      Show the current workspace name.
    list      List workspaces.
    select    Select a workspace.
    new       Create a new workspace.
    delete    Delete an existing workspace.

terraform workspace show로 현재 워크스페이스가 default임을 볼 수 있다.

$ terraform workspace show
default

terraform workspace list를 입력하면 전체 워크스페이스의 목록을 볼 수 있고 현재 사용 중인 워크스페이스에 * 표시가 있음을 볼 수 있다.

$ terraform workspace list
* default

terraform workspace new WORKSPACE_NAME으로 새로운 워크스페이스를 생성할 수 있다.

$ terraform workspace new demo
Created and switched to workspace "demo"!

You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.

$ terraform workspace list
  default
* demo

여기에 나온 대로 워크스페이스를 새로 생성하면 상태 파일(.tfstate)이 분리된다. 같은 구성 파일(.tf)을 사용하지만 상태 파일은 분리가 되므로 별도로 관리할 수 있게 된다.

$ terraform workspace select default
Switched to workspace "default".

terraform workspace select WORKSPACE_NAME로 git에서 브랜치를 바꾸듯이 워크스페이스를 변경할 수 있다.

워크스페이스의 활용

앞에서 말한 문제를 해결하려고 어떻게 사용 가능한지를 간단히 테스트해보자.

resource "aws_s3_bucket" "main" {
  bucket = "outsider-test${terraform.workspace == "default" ? "-stage" : "-${terraform.workspace}"}"
  acl    = "private"

  versioning = {
    enabled = true
  }
}

위는 S3 버킷을 만드는 간단한 Terraform 구성 파일이다. 워크스페이스 개념 자체는 간단하므로 예시로 너무 복잡한 구성을 사용하면 이해하기가 어려울 것 같아서 다른 의존성이 별로 없는 S3 버킷을 예제로 선택했다. VPC나 EC2 등에서 사용하려면 Terraform 구성파일을 잘 작성하는 것만이 남은 작업이 될 것이다.

여기서는 S3 버킷을 만드는데 중요한 부분은 다음 부분이다.

outsider-tf-demo${terraform.workspace == "default" ? "-test" : "-${terraform.workspace}"}

terraform.workspace는 현재의 워크스페이스 이름을 담고 있는 변수다. 이 값을 비교해서 여기서처럼 워크스페이스에 따라 다른 이름을 줄 수 있다. 다른 경우라면 워크스페이스 이름을 보고 다른 Security Group을 지정하거나 VPC를 다르게 지정하거나 할 수 있다.

아무런 작업을 하지 않으면 default 워크스페이스를 사용하므로 실수를 방지하기 위해서 default를 테스트 용도로 사용하고 프로덕션 등 다른 환경을 위해서는 워크스페이스를 명시적으로 변경해서 하도록 했다. 그래서 여기서 버킷 이름이 outsider-tf-demo를 접두사로 쓰는데 기본 워크스페이스에서는 outsider-tf-demo-test가 버킷 이름이 되고 production 워크스페이스라면 outsider-tf-demo-production이 버킷 이름이 된다.

$ terraform workspace list
* default
  demo

$ terraform plan
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_s3_bucket.main
      id:                      <computed>
      acceleration_status:     <computed>
      acl:                     "private"
      arn:                     <computed>
      bucket:                  "outsider-test-stage"
      bucket_domain_name:      <computed>
      force_destroy:           "false"
      hosted_zone_id:          <computed>
      region:                  <computed>
      request_payer:           <computed>
      versioning.#:            "1"
      versioning.0.enabled:    "true"
      versioning.0.mfa_delete: "false"
      website_domain:          <computed>
      website_endpoint:        <computed>


Plan: 1 to add, 0 to change, 0 to destroy.

default 워크스페이스인 상태에서 terrafom plan을 하면 S3 버킷 이름이 outsider-test-stage가 된 것을 볼 수 있다. 당연히 apply하면 이 이름 그래도 적용이 되므로 여기서는 plan까지만 했다.

$ terraform workspace new production
Created and switched to workspace "production"!

You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.

$ terraform workspace list
  default
  demo
* production

$ An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_s3_bucket.main
      id:                      <computed>
      acceleration_status:     <computed>
      acl:                     "private"
      arn:                     <computed>
      bucket:                  "outsider-test-production"
      bucket_domain_name:      <computed>
      force_destroy:           "false"
      hosted_zone_id:          <computed>
      region:                  <computed>
      request_payer:           <computed>
      versioning.#:            "1"
      versioning.0.enabled:    "true"
      versioning.0.mfa_delete: "false"
      website_domain:          <computed>
      website_endpoint:        <computed>


Plan: 1 to add, 0 to change, 0 to destroy.

이번에는 production이라는 워크스페이스를 만든 뒤에 plan을 하면 S3 버킷 이름이 outsider-test-production로 바뀐 것을 볼 수 있다.

구성 파일은 하나도 바꾸지 않은 상태에서 워크스페이스의 변경만으로 상태를 별도로 관리하면서 워크스페이스에 따라 달라지는 값을 다르게 줄 수 있게 되었다. 하나의 구성파일을 잘 만들어 놓고 S3 버킷에 원하는 정책이랑 설정을 하고 스페이징에서 테스트를 마친 뒤에 이상이 없다는 게 확인되면 워크스페이스를 production으로 변경해서 프로덕션 인프라스트럭처에 적용할 수 있다.

이렇게 하면 구성파일을 하나로 관리할 수 있으니 스테이징과 프로덕션의 인프라를 완전히 같게 맞춰놓을 수 있고 스테이징에서 테스트를 마친 뒤에 프로덕션에 적용할 수 있으므로 좀 더 적극적으로 인프라스트럭처를 개선하면서도 프로덕션에서 장애가 발생할 확률을 줄일 수 있다.

일단 워크스페이스를 쓰면 내가 고민했던 문제를 해결할 수 있다는 것은 알았지만 실제 구성 파일은 S3보다 훨씬 더 복잡하므로 구성파일을 작성하는데 초기에는 좀 더 큰 노력이 들 것 같다. 그리고 프로덕션 적용을 자동화해야 더 의미가 있는데 이 부분까지 구축하는 것은 꽤 큰 작업이기는 하다.

2017/11/19 23:25 2017/11/19 23:25

Terraform의 원격 상태 위치 변경

Terraform을 사용하다 보면 하나의 Terrafom state 파일(.tfstate)로 모든 리소스를 관리하는 것을 권장하지 않으므로 자연히 여러 state 파일로 나누게 된다. 처음에도 많은 고민을 하고 구조를 나누어서 관리하게 되지만 리소스가 계획대로 늘어가는 것도 아니고 작업을 하다가 불편한 부분을 느끼고 개선을 하다 보면 이미 관리하는 상태 파일을 다른 위치로 이동하고 싶은 경우가 생기게 된다.

초기에 아주 잘 설계하면 당연히 좋겠지만 그런 일은 발생하지 않고 필요할 때 원하는 구조로 바꿀 수 있는 게 제일 좋은 구조가 된다. 로컬에서 .tfstate를 관리하고 있다면 A 폴더에서 관리하다가 B/A로 바꾸더라도 폴더 이동만 하면 되지만 원격으로 .tfstate를 관리하고 있다면 얘기가 달라진다.

terraform {
  backend "s3" {
    bucket     = "my-terraform-state"
    key        = "ec2/terraform.tfstate"
    region     = "ap-northeast-1"
    encrypt    = true
  }
}

위는 Terraform 원격 상태 파일을 AWS S3에 저장하도록 한 설정이고 여기서 키인 ec2/terraform.tfstate는 내 프로젝트의 폴더구조를 그대로 가져온 것이다. 상태마다 S3 버킷을 파는 것은 불편하므로 하나의 버킷에서 상태 파일을 관리하기 위해 폴더구조를 그대로 키값으로 사용한 것이다.

여기서 리전을 구분하기 위해서 ec2/terraform.tfstate로 관리하던 상태 파일을 ap-northeast-1/ec2/terraform.tfstate로 이동하고 싶다면 Terraform 설정 파일은 로컬에서 폴더구조를 바꿔주면 되지만 S3의 키값은 바뀌지 않으므로 위 설정을 바꾸어 주어야 한다.

처음에는 로컬에서 폴더구조를 바꾼 다음에 수동으로 S3에서 변경해야 한다고 생각했지만, 막상 이 작업을 해보니 Terraform에서 이 부분까지 지원하고 있었다.

먼저 원하는 경로에 맞춰서 키값을 아래처럼 변경한다.

terraform {
  backend "s3" {
    bucket     = "my-terraform-state"
    key        = "ap-northeast-1/ec2/terraform.tfstate"
    region     = "ap-northeast-1"
    encrypt    = true
  }
}

원격 상태 파일의 위치를 변경한 후에 다시 terraform init을 실행한다.

$ terraform init
Initializing modules...
- module.my_module

Initializing the backend...
Backend configuration changed!

Terraform has detected that the configuration specified for the backend
has changed. Terraform will now reconfigure for this backend. If you didn't
intend to reconfigure your backend please undo any changes to the "backend"
section in your Terraform configuration.


Do you want to copy the state from "s3"?
  Would you like to copy the state from your prior backend "s3" to the
  newly configured "s3" backend? If you're reconfiguring the same backend,
  answering "yes" or "no" shouldn't make a difference. Please answer exactly
  "yes" or "no".

  Enter a value: yes

Terraform has detected that the configuration specified for the backend has changed.라고 나온 것처럼 이전과 상태 파일의 위치가 달라진 것을 자동으로 감지하고 상태 파일을 S3에서 복사할 것인지를 붇는다. 의도가 아니라면 여기서 no를 입력하면 되지만 지금은 상태 파일을 바꾸는 것이 의도이므로 yes를 입력한다.

Do you want to copy state from "s3" to "s3"?
  Pre-existing state was found in "s3" while migrating to "s3". No existing
  state was found in "s3". Do you want to copy the state from "s3" to
  "s3"? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes

Releasing state lock. This may take a few moments...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

S3에서 이전에 있던 상태 파일을 새로운 위치로 복사할 것인지를 묻는다. 여기서는 단순히 이동하는 것이므로 yes를 입력하면 S3의 새 위치에 상태 파일을 복사해준다. 물론 복사이므로 이전 상태 파일을 알아서 지워주지는 않고 S3에 들어가서 직접 지워야 한다.

이 작업을 실제로 해보기 전에는 로컬에서 위치를 변경한 뒤에 S3에서 수동으로 위치를 변경하고 다시 plan, apply로 동기화해야 한다고 생각했는데 Terraform에서 이 부분까지 지원하고 있어서 쉽게 할 수 있었다.

2017/11/18 03:25 2017/11/18 03:25