본문 바로가기
DevOps/Terraform

[Terraform] 프로덕션 수준의 테라폼 코드

by BenKangKang 2023. 4. 2.

서론

  • 프로덕션 수준 인프라 구축은 어렵다. 여기서 말하는 인프라란 서버, 데이터 저장소, 로드 밸런서, 보안 기능, 모니터링 및 경고 도구, 파이프라인 구축 및 비즈니스 운영에 필요한 기타 모든 기술을 의미한다.
  • 매니지드 서비스를 사용하더라도 작업은 꽤 오래걸리며 깊은 전문성을 갖춘 인력이 팀에 없거나, 끌려다녀 집중할 시간을 갖지 못한다면 시간은 훨씬 더 오래 걸릴 수 있다.

1. 프로덕션 수준 인프라 구축에 오랜 시간이 걸리는 이유

호프스태터의 법칙

  • “호프스태터의 법칙을 계산에 넣어도 항상 예상한 것보다 더 오래 걸린다.”
  • SW 프로젝트에 걸리는 시간 추정치는 아주 부정확하다.
  • 데브옵스 프로젝트에는 예상 시간보다 2배가 더 소요될 수 있다.

1.1. 데브옵스 산업이 아직 초기 단계이다

  • 테라폼, 도커, 패커 및 쿠버네티스와 같은 도구가 모두 2010년 중반 혹은 중반에 출시되었으며 계속 빠르게 변하고 있다.

1.2 야크 털 깎기에 취약하다

  • 야크 털 깎기
    • 원래 하고 싶었던 작업을 수행하기 전 해야하는 하찮고, 겉으로 볼때는 관련 없는 작업들.
      • 앱을 배포하는데 갑자기 TLS 인증서 문제가 발생하는 경우

1.3 본질적인 복잡성

  • 우발적인 복잡성
    • 알고리즘 작성하는데 메모리 할당 버그를 처리하는 것
  • 본질적인 복잡성
    • 본질적인 문제 (언어, 환경과 상관 없음)

본질적인 복잡성을 평가할 때 시간이 많이 걸리는 세부 사항을 잊어먹는다.

2. 프로덕션 수준 인프라 체크 리스트

  • 모든 항목이 인프라에 필요하지는 않지만 어떤 항목을 구현했는지, 어떤 항목을 건너 뛰기로 결정했는지, 그 이유는 무엇인지를 의식적으로 명시적으로 문서화해야한다

3. 프로덕션 수준 인프라 모듈

3.1 소형 모듈

모든 인프라를 단일 파일 또는 단일 모듈러 정의하는 대형 모듈의 단점

  1. 속도가 느림
    1. plan 이 5~6분이상 걸리는 등 명령 실행에 걸리는 시간이 오래 걸린다.
  2. 안전하지 않음
    1. 취소 권한 원칙에 위배.
  3. 위험성이 높음
    1. 달걀을 한 바구니에 담는 행위임으로 전부 깨질 수 있다.
    2. 스테이징 변경사항이 프로덕션에 반영되는 건 이상핟,
  4. 이해하기 어려움
    1. 코드가 한 곳에 뭉쳐있으면 이해하기 어렵다.
  5. 리뷰하기 어려움
    1. 4번과 연결되는데 수천 글의 코드로 구성된 모듈 리뷰는 거의 불가능함
  6. 테스트하기 어려움
    1. 많은 양의 인프라 코드를 테스트하는 것은 거의 불가능

로버트 C. 마틴 “함수는 작게” → 인프라도 모두 모듈로 나누자

로버트 C.마틴 “함수의 첫 번째 규칙은 작아야 한다는 것입니다. 함수의 두 번째 규칙은 그보다 더 작아야 한다는 것입니다.”

3.2 합성 가능한 모듈

  • 재사용 가능하고 합성 가능한 모듈(composable module)을 구축하자
  • 유닉스 도구 개발자 더그 매클로이 “한 가지 일을 잘 해내는 프로그램을 여러 개 작성하세요. 그리고 그 프로그램이 함께 작동하게 하세요. 이것이 바로 유닉스 철학입니다.
  • 함수 합성 방식을 적용
    • add, sub 을 구현해 calculation 내부에서 사용하는 것
    • 모듈 2개로 구분한다
      • Generic module
        • alb, asg-rolling-deploy 같은 코드의 기본 구성 요소며 재사용할 수 있는 것
      • 사용 사례별 모듈
        • 특정 앱같이 여러 개의 일반 모듈을 결합하여 앱을 배포하는 경우

3.3 테스트 가능한 모듈

  • 수동 테스트 장치
    • apply, destory 실행하여 반복적으로 배포, 최소함으로써 작동 테스트
  • 자동화된 테스트 장치
    • 모듈에 대한 자동화 테스트 작성
  • 실행 가능한 문서
    • README 등 팀의 다른 구성원이 이를 찾아서 모듈 작동 방식을 이해하여 모듈 고체할 수 있다.

디렉토리

  • modules/modules
  • modules/examples: 예제
  • modules/test: 테스트

버전 고정하자

  • 시멘틱 버전 특성이 그대로 나타난다. 공급자 버전을 고정하는 것을 권장한다
    • version = “~> 2.0”
  • 공급자에 따라 다르다. 신뢰할 수 있는 공급자라면 더 유연하게 관리?

3.4 릴리스 가능한 모듈

1. 깃을 이용

module "{module}" {

source = "github.com/brikis98/terraform-up-and-running-code//code/terraform/04-terraform-module/module-example/modules/services/webserver-cluster?ref=v0.1.0"

...
  • 모듈 url과 버전을 명시

2. 테라폼 레지스트리

  • 테라폼 엔터프라이즈면 개인 깃 레포르 사용할 수 있다.
  • 요구사항이 있으니 주의하자
    • 모듈이 공개 깃허브 리포지토리에 있어야 한다.
    • 리포지토리 이름은 terraform-<PROVIDER>-<NAME> 과 같은 형태로 부여해야 한다.
      • provider → AWS 와 같은 공급자 이름
      • name → vault 와 같은 모듈 이름
    • 모듈은 리포지토리의 루트에 테라폼 코드를 정의하고 README.md를 제공하고, main.tf, variables.tf, output.tf 규칙을 파일 이름으로 사용하는 것을 포함하는 지정된 파일 구조르 따라야 합니다.
    • 시멘틱 버전과 함께 깃 태그를 사용해야 합니다.

3.5 테라폼 모듈을 넘어서

테라폼 모듈 폴더에는 비테라폼 코드가 포함될 수 있다

  • 부팅에 필요한 배시 스크립트
  • 테라폼의 한계 극복할 수 있는 비상구

프로비저너

  • 테라폼 실행할 때 부트스트랩, 구성 관리 또는 정리 작업을 수행하기 위해 로컬 시스템이나 원격 시스템에서 스크립트를 실행하는데 사용된다
  • provisioner 블록을 사용한다.
  • 종류
    • local-exec: 로컬 시스템에서 실행
    • remote-exec: 원격 리소스에서 실행
    • 원격 리소스로 파일을 복사하는 file 등
  • 기타 키워드
    • 기본적으로는 creation-time 프로비저너이다.
    • when=”destory” 인수 설정하면 destory-time 프로비저너가 된다. (desyory 실행 후 리소스 삭제 전 실행)
    • on_failure 사용해 오류 처리할 수 있음 → continue: 오류 무시, abort: 중단

local-exec 예시

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  provisioner "local-exec" {
    command = "echo \\"Hello, World from $(uname -smp)\\""
  }
}

remote-exec 예시

resource "aws_instance" "example" {
  ami                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.instance.id]
  key_name               = aws_key_pair.generated_key.key_name

  provisioner "remote-exec" {
    inline = ["echo \\"Hello, World from $(uname -smp)\\""]
  }

  connection {
    type        = "ssh"
    host        = self.public_ip
    user        = "ubuntu"
    private_key = tls_private_key.example.private_key_pem
  }

}
  • 네트워크를 통해 EC2인스턴스와 통신하고 SSH 구성한다.

null_resource 이용해서 실제 리소스에 연결하지 않는 프로비저너

resource "null_resource" "example" {
  # Use UUID to force this null_resource to be recreated on every
  # call to 'terraform apply'
  triggers = {
    uuid = uuid()
  }

  provisioner "local-exec" {
    command = "echo \\"Hello, World from $(uname -smp)\\""
  }
}

외부 데이터 소스 사용하는 프로토콜

data "external" "echo" {
  program = ["bash", "-c", "cat /dev/stdin"]

  query = {
    foo = "bar"
  }
}

output "echo" {
  value = data.external.echo.result
}

output "echo_foo" {
  value = data.external.echo.result.foo
}
  • 배시에 의존하는 코드여서 조심해야함. → 윈도우에서 배포 불가능.

결론

  • 인프라 체크리스트를 통해 건너뛸 항목을 명시적으로 구분하자.
  • 예제 코드와 테스트 코드를 작성하자.
  • 버전은 최대한 고정하자.
  • external 데이터 소스, 다른 비상구를 사용하면 코드 이식성이 저하되고 코드를 불안정하게 만들기 때문에 신중하게 사용하자

댓글