본문 바로가기
개발/Terraform on NaverCloud

Terraform 프로바이더 개발 세미나 2

by 컴쏘 2023. 7. 22.

Resource 개발하기 (VPC)

ValidateFunc

  • attribute의 값들을 사용자가 terraform config를 통해서 원하는 값을 입력할 때, 유효한 값들이 정해져 있는 attribute들은 ValidateFunc을 통해서 사전의 terraform core에서 이러한 값들이 유효한지 검증할 수 있음
  • ValidateFunc 같은 경우에, terraform SDK에서 제공하는 것을 사용할 수 있음, 만약 제공해주는 것이 없다면 직접 정의해서 사용

위의 예시에 대한 구현

  • 사용자가 적절하지 않은 입력값을 주면, terraform plan에서 error를 발생시킨다.

 

CRUD Func 구현

  • Terraform core가 해당 구현체를 통해 인프라를 생성/수정/삭제를 함
  • Read operation을 통해 state(.tfstate)를 refresh 함

api 연결

  • 가장 중요한 것은 CRUD Func을 구현하는 것
  • 사용자가 작성한 config 파일을 terraform core가 state와 비교해서 생성을 하거나 삭제하는 명령들을 rpc를 통해서 terraform provider에게 전달하게 됨
  • 해당 명령을 받으면, 각각의 리소스에 있는 Create, Read, Update, Delete 함수를 통해서 NAVER Cloud API를 호출해서 수행
  • 해당 함수를 구현하고, resource 타입에서 해당 함수를 연결해주면 동작을 수행할 수 있게 된다.
  • 최근에 구현되는 api에는 Context를 많이 사용하게 되어있다.
  • 오래된 api에는 Context를 쓰지 않는 경우도 있다.

 

CRUD Func Parameters - meta

사용자 입력 설정값 참고 예시 (meta)

  • ConfigureFunc(Provider)에서 정의 한 meta 정보를 사용할 수 있다. (리젼 코드)
  • Context
  • ResourceData
  • meta
    • config에서 사용자가 입력한 설정 관련 값들을 meta를 이용해서 가져올 수 있음
    • vpc 생성할 때, RegionCode가 필요한데, 이것은 사용자가 설정한 region과 관련한 정보에서 얻어올 수 있음
config := meta.(*ProviderConfig) 
  • meta를 ProviderConfig 타입으로 변환하고 가져옴

 

CRUD Func Parameters - ResourceData

사용자 입력 설정값 참고 예시 (ResourceData)

  • 사용자가 config 파일에서 vpc를 선언했을 때, name에 대한 값, cidr_block에 대한 값을 정의함
  • 이러한 사용자가 입력한 값을 가져올 때, ResourceData 타입을 활용
d.Get("name").(string) // string 타입 변환
d.Get(String("ipv4_cidr_block").(string) // string 타입 변환
  • 위의 코드와 같은 방식으로 사용자 입력 값을 가져올 수 있음
  • 코드를 작성하면서 많이 쓰이는 부분 중 하나이다.

 

Resource 구현 방식

Create 구현

Create 구현 예시

  • 인프라를 생성하고, ID를 설정
  • 마지막으로 Read 함수를 호출해서 state(.tfstate)를 업데이트 할 수 있도록 한다.
  • Client를 통해서 NAVER Cloud에 Create API를 사용해서 vpc를 실제로 생성하는 부분
    • 해당 결과 값을 resp(response)로 받게 된다.
    • 해당 resp에는 VpcNo(id)가 있는데, 이를 이용해서 ResourceData에서 값 설정 가능
  • ResourceData에서 SetId로 설정을 해주면, 최종적으로 결과물이 state에 저장된다.
  • 끝났을 때, Read함수를 호출

 

Read 구현

Read 구현 예시

  • state(.tfstate)를 sync 하는데 사용
  • ID는 Unique한 값이어야 함
  • 실제 생성된 vpc에 대한 값을 읽어오는 API를 호출하고, 그 vpc에 대한 값을 instance로 가져온다.
    • instance가 없으면 예외 처리
    • 있으면 d(ResourceData)에 값들을 설정해줌
    • 설정을 해주면, 실제로 terraform SDK에서 terraform으로 전달이 되고, terraform core에서 값들을 저장

 

Update 구현

Update 구현 예시

  • d(ResourceData)로 update 유무를 판단
  • 예제에서는 vpc의 이름을 다른 이름으로 변경했을 때이다.
  • HasChange에서 name이 변경되었는지 확인 가능
    • name을 변경했다면, return 값이 true
  • UpdateVpc API를 호출해서 변경 가능
  • 여기서도 마찬가지로, 변경 사항을 state에 저장하기 위해 Read 함수 호출

 

Delete 구현

Delete API 호출

  • terraform destroy 명령으로 삭제 또는 config에 주석 처리를 해서 terraform core에서 변경사항을 확인했을 때, resource가 삭제되었다고 판단되었을 때 Delete 함수 호출
  • Terraform destroy 또는 Diff 결과 삭제 될 경우 호출
  • 속성이 ForceNew인 경우, 삭제 후 생성 하기 위해 사용
  • Delete는 id 정보만 있으면 된다.
    • 해당 리소스의 id 정보를 얻어와서 Delete API를 호출하고 끝난다.

컨트리뷰션을 진행하면서 어느 정도 익숙해지면, 이러한 Resource 구현도 참여할 수 있다. 새로운 resource를 구현하지 않더라도, 기존의 resource 구현된 것에서 동작 방식을 수정해볼 수도 있다.

 

Terraform provider 만들기

Testing

Terraform SDK에서 test 해볼 수 있는 여러가지 구현체들을 제공해준다. 크게 보았을 때는 다음의 4가지 단계로 진행된다.

Testing

  • PreChecking : provider 리젼 설정 여부, 입력 Validation
    • 테스트 수행 전 provider가 동작할 수 있게 하는 환경 설정
  • TestSteps : 실제 인프라 생성 또는 변경 테스트
    • 실제 테스트가 동작하는 부분
  • Destroy : 테스트가 종료된 후, 제거하는 역할
    • test에서 생성된 vpc 리소스 삭제
    • 이 과정은 terraform SDK에서 자동 수행
  • CheckDestory : 잘 삭제가 되었는지 검증
    • Delete 함수를 잘못 작성하면 리소스 삭제가 잘 안된다.
    • 의존성에 문제가 있어서 삭제가 안되는 경우도 있음
    • 이렇게 여러 가지 상황을 체크해볼 수 있다.

 

Test 코드 구현 예시

test 코드 구현 예시

  • 함수가 Test로 시작한다.
  • random 함수를 통해서 cidr 블럭 값 정의, 이름도 정의
  • test 코드 작성에서
    • PreCheck, Providers, CheckDestroy는 정의된 것 사용
    • Steps는 직접 구현하기
      • Config에서는 실제 terraform config 파일 작성하는 것처럼 작성
      • name과 cidr은 앞에서 정의한 값 사용
      • 해당 Config가 작성이 완료되면, 잘 작성되었는지 Check에서 확인

 

Testing

  • resource.ParalleTest와 같은 병렬 테스트도 지원 (하지만, 동시성을 고려해야 함)
  • TF_ACC=true 환경 변수를 통해, 실제 인프라에 반영되는 Acceptance Tests 수행
    • 작성한 test 코드를 수행하기 위해서는 TF_ACC=true를 설정해줘야 test 수행 가능
    • Acceptance Test가 통과했다는 것은 최소한으로 어느 정도의 기준을 통과했다는 것
  • dev_overrides를 사용하면, required_providers 설정하고 사용 시 Binary 파일 SUM 체크 무시
    • dev_overrides는 ****terraform command를 사용해서 구현한 내용을 terraform config 파일을 가지고 plan, apply를 수행한다고 했을 때 설정할 수 있는 값

 

한가지 더 참고해야할 점

  • NAVER Cloud 리소스 중에서 생성할 수 있는 개수가 제한되어있는 것들이 있다.
  • 따라서 테스트 내용과 무관하게 테스트가 실패하는 경우도 있다. 이런 부분들은 주의할 필요가 있다.

Debugging

  • TF_LOG=DEBUG를 통해 로깅 하자
  • 디버깅을 위해 각 operation 마다 Logging 하는 것이 중요
    • 로그를 잘 남겨두는 것도 개발에 도움이 된다.
  • 에러 발생 시 request와 response가 잘 나오도록

 

Documentation

  • /docs 내의 Markdown 파일 수정
    • /docs/resources
  • Doc Preview Tool 제공
  • registry.terraform.io/tools/doc-preview
    • 작성한 Markdown이 잘 작성되어있는지 확인할 수 있는 페이지
  • tfplugindocs을 사용하여 자동화 가능

 

Release

  • Travis CI/CD 적용
  • gorelease를 통해 바이너리 배포

 

Appendix & Tips

비동기 API 및 상태 처리

 

Resource graph

Terraform core에서는 Resource 간의 종속성 그래프를 생성

의존성 관계

  • vpc는 vpc 정보만 있으면 되므로 의존성 없음
  • subnet은 vpc 정보가 필요하기 때문에 의존성을 가짐
  • server는 subnet 정보가 필요하기 때문에 의존성을 가짐

resource graph

  • 만약 의존성을 가시적으로 보고 싶다면 graph를 확인하면 된다.

 

StateChangeConf

Terraform SDK에서 제공하는 구현체이다. Pending에서 Target 상태가 될 때 까지 대기

 

Q. 언제 생성하는가?

A. VPC 생성을 예시로 들어보자.

StateChangeConf

  • vpc를 생성하게 되면 처음에는 init 상태가 되고, run 상태가 될 때까지 시간이 좀 걸린다.
  • vpc상태가 run 상태가 될 때 까지 대기를 하기 위해, StateChangeConf를 사용한다.
  • run 상태가 될 때 까지 기다리는 이유는 vpc가 의존성이 있는 subnet을 생성한다고 했을 때, subnet이 해당 vpc 정보를 사용해야한다. 하지만, vpc가 아직 생성이 완료가 안된 상태에서 subnet을 생성하고자 하면, error 발생한다.
    • 따라서, run 상태가 되어서 vpc를 쓸 수 있는 상태인지 확인하고 그 다음 단계를 수행하기 위해서 상태를 체크하면 된다.

 

실제 구현

실제 구현

  • Pending : 기다릴 상태
    • 예시로 설명하자면, INIT, CREATING 상태라면 기다리겠다는 의미
  • Target : 최종적으로 기다리는 상태
    • 예시로 설명하면, RUN 상태가 될 때까지 기다리겠다는 의미
  • Refresh : 구현체를 어떻게 가져오는지
  • Timeout : 최대 대기 시간
  • Delay : 몇 초 주기로 확인?
    • 이런 시간들은 리소스의 특성에 따라서 다르게 구성된다.

 

동시성 이슈 처리

Parallel walk

병렬 처리 예시

  • Terraform은 최대 10개의 노드를 병렬로 사용
    • -parallelism=n 옵션을 통해 동시 노드 조절 가능 plan, apply, destroy
  • 하나의 인프라의 여러 요청이 동시에 들어올 때
    • 예) 하나의 ACG 또는 Network ACL에 여러 룰 들이 추가 될 때

 

Retry

retry

  • 해당 자원에 접근이 일시적으로 어려울 경우 재시도
  • 리소스 특성에 따라서 실패하였을 때, 재시도가 불가능한 경우가 있다.
  • 재시도가 가능한 경우도 있다.
  • 이 경우, retry를 통해 재시도를 해볼 수 있는 코드 구현 가능

 

순환 참조 (Circular dependency) 이슈

순환 참조 이슈

  • 두 Resource간 서로 참조하려고 할 때 Circular dependency 이슈 발생
  • 순환 참조하는 리소스는 생성 불가

 

해결 방법

순환 참조 해결 방법

  • 리소스를 분리해서 참조하기
  • 자주 발생하지는 않겠지만, 이러한 순환 참조는 만들면 안된다.

 

Tip 1. Filter 기능 제공

Tip 1. filter

  • datasource는 필터 기능 제공
  • API 단에서 지원하는 필터 기능은 제한적
  • 사용자에게 많은 속성의 필터링 제공

 

Tip 2. 공통 함수를 재사용 하자

재사용 하기 좋은 함수들

  • Marshall & Unmarshall functions
  • Flatten 함수들 (API Model → Resource schemas)
  • Get functions (Resource와 DataSource, StateChangeConf에서 사용)

 

Tip 3. Go Context

Context 구현

Tip 3. Context

  • Go에서 동시성을 처리할 때, Context 사용
  • context.Context를 GO SDK에 전달하여 User cancellation 발생 시 불필요한 요청을 중단
  • API를 호출할 때, Context를 같이 전달함

 

Q. 왜 Context를 같이 전달하는가?

A. 사용자가 요청을 수행하다가, 중간에 수행을 취소하고 싶은 경우가 생긴다.

Context를 사용하는 이유

  • Context가 없는 경우에는 vpc가 끝날 때 까지 기다려야 한다.
  • Context를 사용하면, 요청을 바로 처리할 수 있다.

Terraform에서 기본적으로 제공하는 API들이 Context가 같이 있음. 따라서 Context를 직접 다룰 일은 많지 않다.

 

주의해야 할 점

Framework vs SDK

  • Terraform Provider Plugin은 hashcorp에서 제공하는 SDK를 사용하여 작성
  • SDKv2 : Ncloud 및 대부분의 provider가 사용하는 SDK, 하지만 더 이상 추가 개발되지 않음
  • Framework : Hashcorp에서 기존 SDK의 단점을 개선해서 새롭게 작성한 SDK
    • AWS 등 여러 provider들이 새롭게 추가하는 리소스 및 기존 리소스를 Framework로 포팅 중
  • SDKv2와 Framework가 동작하는 방식은 동일하나, 제공하는 API 및 구현이 달라짐
    • SDKv2는 any 타입으로 정의된 인터페이스를 사용할 때마다 정확한 타입으로 변환해야 하는 부분이 많아서 개발하기 불편했으나, Framework는 명시적인 타입을 사용하여 개발 편의성 증가 (런타임 에러 → 컴파일 에러)
  • 컨트리뷰션 시 기존 리소스는 SDKv2로 개발되어 있어 개선시 SDKv2 내용을 참고, 신규 리소스 추가 작업 시 framework를 사용하여 개발 필요

따라서 이러한 부분에서도 컨트리뷰션 활동이 가능하다.


OSSCA Terraform on NAVER Cloud 
"이원철 멘토님"의 Terraform 프로바이더 개발 세미나 2 내용을
terraform provider 개발 시 참고하기 위해 공부한 내용을 정리한 글입니다.