4주차 Provider & State

본 스터디 내용 요약은 @CloudNet의 가시다님의 강의를 요약한 내용입니다.

1. Provider

  • 프로바이더는 다른 프로비져닝 API(AWS, Google Cloud, VSphere)을 동일한 선언적 형식으로 호출 할 수 있는 translator라고 생각하면 된다.

  • 각 프로바이더는 terraform의 형식에 맞춰 API 구현을 맞춰서 각 환경에 맞는 구성을 해준다.

  • 우리는 연동하는 대상의 프로비져닝 API에 사용될 인증만 처리해주면 쉽게 사용할 수 있다.

1.1 프로바이더 구성

  • 프로바이더에는 다음과 같이 유지보수 및 권한을 가진 티어에 따라 식별이 가능합니다.

  • 오른쪽 위에 다음과 같은 창을 사용해서 프로바이더를 설정 해 줄 수 있습니다.

  • 로컬 이름과 프로바이더 지정

    • required_providers 블록을 사용해서 여러개의 프로바이더를 설정할 수 있습니다. 이름들이 동일 접두사로 시작하거나 동일할 경우 다음과 같이 provider를 명시해서 사용할 수 있습니다.

terraform {
  required_providers {
    http = {
      source = "hashicorp/http"
    }
    aws-http = {
      source = "terraform-aws-modules/http" 
    }
  }
}

data "http" "example" { // http 프로바이더 사용
  provider = aws-http  // 명시적 표기사용
  url = "https://checkpoint-api.hashicorp.com/v1/check/terraform"

}
  • 단일 프로바이더와 다중 정의

    • 동일 프로바이더를 사용하지만 다른 조건을 사용하는경우

      • 다른 IAM 권한

      • 다른 리전, ACCESS ID 일 경우

    • Alias 명시하고 provider 메타인수를 이용해 provider를 지정할 수 있습니다.


provider "aws" {
  alias = "seoul"  // alias 지정
  region = "ap-northeast-2"
}

resource "aws_instance" "app_server2" {
  provider      = aws.seoul   // provider 지정
  ami           = "ami-0ea4d4b8dc1e46212"
  instance_type = "t2.micro"
}
  • 프로바이더 요구사항 정의

    • 프로바이더에 대한 source에 다운로드 경로 정의하고 version에 프로바이더 버전을 명시 할 수 있습니다.

terraform {
  required_providers {
    <프로바이더 로컬 이름> = {
      source = [<호스트 주소>/]<네임스페이스>/<유형> (다운로드 주소)
      version = 1.1.0 // 버전  (값 생략시 최신버전)
    }
  }
}
  • 프로바이더간 전환 여부

    • 다른 클라우드간 프로바이더만 바꿔서 바로 전환이 가능할까?

      • 답은 안된다. -> 다른 API 로 구현된 프로바이더이다.

1.2 프로바이더 에코시스템

  • 테라폼 에코시스템은 사용자가 원하는 구조에 따라 설계할 수 있게끔 사용됩니다.

    • 퍼블릭 클라우드

    • 컨테이너 관리 Iaas

    • 보안 & 인증

    • CI/CD

    • 네트워크

    • VCS

1.3 프로바이더 실습

  • [실습] 멀티 리젼에 EC2를 배포해보자

// ec2.tf

```
resource "aws_instance" "region_1" {
  provider = aws.region_1   // 첫번째 region 에 ec2를 생성하게 끔 region을 설정합니다.

  ami           = data.aws_ami.ubuntu_region_1.id
  instance_type = "t2.micro"
}

resource "aws_instance" "region_2" {
  provider = aws.region_2  // 두번째 region 에 ec2를 생성하게 끔 region을 설정합니다.


  ami           = data.aws_ami.ubuntu_region_2.id
  instance_type = "t2.micro"
}
```
// provider_data.tf 

```
provider "aws" {
  region = "ap-northeast-2"
  alias  = "region_1"
}

provider "aws" {
  region = "ap-southeast-1"
  alias  = "region_2"
}

data "aws_region" "region_1" {
  provider = aws.region_1
}

data "aws_region" "region_2" {
  provider = aws.region_2
}

data "aws_ami" "ubuntu_region_1" {
  provider = aws.region_1  // 다음과 같이 region 1에서 사용되는 provider를 사용합니다.

  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
}

data "aws_ami" "ubuntu_region_2" {
  provider = aws.region_2 // 다음과 같이 region 2에서 사용되는 provider를 사용합니다.

  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
}
```
// 다음 코드를 실행하면 ec2가 각각의 region 에 생성되있는것을 확인 할 수 있다.
aws ec2 describe-instances --region ap-northeast-2 --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text

  • 2가지 주의사항

    • 첫번째 주의사항: 프로덕션 수준의 멀티 리전은 어렵다

      • 지역간 지연시간, 고유ID, 최종 일관성 등 고려사항이 많아서 어렵다.

    • 두번째 주의사항: Alias를 빈번하게 사용하지 말자

      • 별칭 사용하여 두 리전에 배포하는 모듈은 한 리전이 다운 시에 Apply가 실패한다.

      • workspace나 디렉토리를 이용해서 완전 분리하는것이 별칭으로 분리하는것보다 나은 선택이다.

2. State

테라폼 상태에 대한 카톡
  • 테라폼 상태관리의 중요성에 대한 카톡

    • 상태관리를 어떻게 해야할지 정확히 알아야합니다.

    • s3 버저닝과 import로 복구하는 면이 인상적입니다.

2.1 State의 목적과 의미

  • [실습] 상태 파일 확인

provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"

  tags = {
    Name = "t101-study"  // 해당 값을 변경할 예정
  }
}
// tf 파일을 프로비젼이후에 serial 값이 1인것을 확인할 수 있습니다.
provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"

  tags = {
    Name = "tf-state"  // 해당값을 바꾸고 다시 apply 하게되면 serial 값은 증가 할 것입니다.
  }
}
// serial 값이 3으로 변경되었습니다.

  • 상태 파일은 terraform에서 내부 API로 변경되는 상태를 저장하기 위한 파일로 직접 편집하거나 코드로 작성해서는 안됩니다.

    • 상태값에 따라 여러번 실행해도 결과가 동일하게끔 멱등성을 보장하게끔 실행됩니다.

  • 팀 단위에서 테라폼 운영시 문제사항

    • 파일을 저장하는 공유 스토리지 필요

    • 상태 파일 잠금

      • 동시에 작업될 시에 충돌 가능

    • 상태 파일 격리

      • 환경별 상태파일 격리 필요

  • 지원되는 원격 백엔드

    • AWS, Azure Blob, Google Cloud Storage, Postgresql, k8s secret 등 원격으로 apply 이후에 저장이 가능하다.

      • 수동 오류 해결: plan/ apply 실행 시마다 파일을 로드해서 apply후에 자동으로 저장한다. (버전관리시스템은 수동으로 update를 해줘야한다.)

      • 잠금: 버전 관리시스템에서 여러명의 구성원들이 동시에 하나의 상태파일에 대해서 apply 못하게 잠금기능을 제공합니다. -lock-timeout=30s (상태파일 동시 업데이트로 충돌이 가능하다.)

      • 시크릿: 테라폼 상태의 모든 데이터는 평문으로 tf.state에 저장될때, 암호화하는 기능을 지원 (VCS 는 모든 데이터 평문 저장함)

  • [실습] S3 + DynamoDB 동작

    • [사전준비] aws s3 생성

```
resource "aws_s3_bucket" "main" {
  bucket = var.bucket_name

  tags = {
    Name = "terraform test"
  }
}

```
// s3 버킷을 생성합니다.
```
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.4.0"
    }
  }
  backend "s3" {
  bucket         = "hello-tf102-remote-backend"
  key            = "terraform/state-test/terraform.tfstate"
  region         = "ap-northeast-2"
  #dynamodb_table = "terraform-lock" 주석처리
  }
  required_version = ">= 1.4"
}

provider "aws" {}
```

// s3를 백엔드로 설정합니다.

  • [사전 준비] Locking을 위한 DynamoDB 생성

```
resource "aws_dynamodb_table" "terraform_state_lock" {
  name         = "terraform-lock" # table이름
  hash_key     = "LockID"         # key 이름
  billing_mode = "PAY_PER_REQUEST"

  attribute {
    name = "LockID"
    type = "S" # key 타입
  }
}
```


aws dynamodb list-tables --output text

// dynamodb 생성확인

  • [확인] S3 버저닝 확인

```
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr

  tags = {
    Name = "terraform VPC 2"  //2로 변경이후에 lock되는지 확인
  }
}

```
terraform apply -> 인풋값을 입력하지 않으면 lock이 생깁니다.
// "Path":"hello-tf102-remote-backend/terraform/state-test/terraform.tfstate"}
  • 다음 화면에서 LockId를 확인할 수 있습니다.

  • s3 에서는 다음과 같이 버전이 명시되는것을 확인 할 수 있습니다.

2.2 State 동기화

  • 테라폼 구성 파일을 기존 존재하는 State 파일과 비교해서 실행계획을 세워서 생성, 수정, 삭제를 하게 됩니다.

  • 테라폼 state 흐름

    • 각 리소스에서 발생할 수 있는 상태변화

  • [실습] 유형별 상태변화 및 복구 방법

유형

구성 리소스 정의(*.tf)

State 구성 데이터

실제 리소스

기본 예상 동작

1

있음

리소스 생성

2

있음

있음

리소스 생성

3

있음

있음

있음

동작 없음

4

있음

있음

리소스 삭제

5

있음

동작 없음

  • 유형1 : 신규 리소스 정의 → Apply ⇒ 리소스 생성

// main.tf

```
locals {
  name = "mytest"
}

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

resource "aws_iam_user" "myiamuser2" {
  name = "${local.name}2"
}
```
// 두번 실행시에도 멱등성을 유지하는것을 확인 할 수 있습니다. (추가 수정, 실행 없음)
  • 유형2 : 실제 리소스 수동 제거 → Apply ⇒ 리소스 생성

// 실제 리소스 수동 제거
aws iam delete-user --user-name mytest1
aws iam delete-user --user-name mytest2

terraform plan
// 삭제이후에는 다시 생성해야 되는것을 명시해 줍니다.
terraform plan
terraform plan -refresh=false  // 해당 refresh는 aws 와 같은
                               // 인프러스트럭쳐에 실제로 리소스가 프로비져닝 되있는지 확인합니다.
cat terraform.tfstate | jq .serial

  • 유형3 : Apply → Apply ← 코드, State, 형상 모두 일치한 경우

// terraform apply -auto-approve

// terraform apply -auto-approve

두번 apply를 하게되면 형상이 모두 일치할 때, 다음과 같은 결과가 나오게 됩니다.

  • 유형4 : 코드에서 일부 리소스 삭제 → Apply

locals {
  name = "mytest"
}

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

// myiamuser2을 명시하지 않았을때 다음과 같은 값이 나옵니다.
  • 유형6 : 실수로 tfstate 파일 삭제 → plan/apply

rm -rf terraform.tfstate*  /tfstate 파일들 모두 삭제

terraform plan -refresh=false  // tfstate만 보고 판별할 경우
terraform plan              // 두개다 새로운 유저를 만드려고합니다.
//  위 상황에서 복구하는 방법은? import가 있습니다.
terraform import aws_iam_user.myiamuser1 mytest1

2.3 워크 스페이스

  • 테라폼 상태를 격리하지 않을때, 생기는 문제점

  • 환경을 격리하는 방법들

    • 작업 공간을 통한 격리

    • 파일 레이아웃을 통한 격리

  • 워크스페이스

    • 서로 다른 state를 갖는 실제 대상을 각 워크스페이스 별 프로비저닝을 할 수 있다.

    • 장점

      • 하나의 루트 모듈로 동일 구성 프로비저닝 관리 가능하다.

      • 깃의 브랜치 전략처럼 동일한 구성에서 리소스 결과 관리가능하다

    • 단점

      • State가 동일한 저장소에 저장되어 접근 권한 관리 불가능

      • 모든 환경에 동일한 리소스가 요구되지 않아 분기처리 발생가능

      • 완벽한 격리가 불가능

      • 해결하기 위해 별도로 구성하는 디렉터리 기반의 레이아웃을 사용해야한다.

  • [실습] 워크 스페이스를 생성해서 프로비저닝 하기

// main.tf

```
resource "aws_instance" "mysrv1" {
  ami           = "ami-0ea4d4b8dc1e46212"
  instance_type = "t2.micro"
  tags = {
    Name = "t101-study"
  }
}
```
// terraform workspace list  으로 terraform의 workspace 확인

- default 인 것을 확인 할 수 있다.
// terraform workspace list  으로 terraform의 workspace 생성


- mywork2 가 생성된 것을 확인
- terraform apply 로 해당 워크스페이스에서 리소스들이 생성되는 것을 확인 할 수 있다.

Last updated