LabHub
배우기 러닝패스 코스

Terraform in Practice

Checking by Hand After Every Module Change, Until One Slipped Through

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

시험할 수 있는 모양의 모듈을 만들고, 계획만 세우는 값싼 시험과 실제로 만드는 비싼 시험을 각각 쓰고, run 을 이어 붙이고, 막혀야 할 것이 막히는지 시험하고, 실패가 어떤 메시지를 내는지 보고, mock 으로 값만 확인한 뒤, CI 가 부를 한 줄로 묶습니다.

왜 중요한가

재사용하는 모듈은 부르는 곳이 늘어날수록 고치기 무서워집니다. '이 값을 바꾸면 어디가 깨지나' 를 사람이 매번 확인하기 때문입니다. 시험을 붙여 두면 그 확인이 명령 한 줄이 되고, 그때부터 모듈을 고칠 수 있게 됩니다. 중요한 것은 시험을 두 종류로 나눠 쓰는 감각입니다 — 계획만 세우는 시험은 빠르고 값싸서 이름 규칙·조건 분기·기본값처럼 값만 보면 되는 것을 다 덮을 수 있고, 실제로 만드는 시험은 느리지만 '정말 그것이 그렇게 생기는가' 를 확인하는 유일한 방법입니다. 여기에 '막혀야 하는 것이 막히는가' 를 확인하는 시험을 더하면, validation 을 잘못 고쳤을 때 조용히 통과하는 일이 없어집니다. 마지막으로 이 모든 것이 CI 의 종료 코드 하나로 이어져야 값어치가 생깁니다.

단계

  1. /root/tfa-test/main.tfvar.env(dev·stage·prod 만 허용하는 validation)와 var.replicas(기본 1, 1~10 만 허용)를 받아 out/app-<env>.confname=replicas= 두 줄을 쓰는 local_file.conf 를 두고, 출력 name·conf_path·replicas 세 개를 선언하세요. init 하고 tofu validate 로 확인합니다.
  2. /root/tfa-test/tests/basic.tftest.hcl 에 파일 수준 variablesenv = "dev" 를 두고, command = planrun "name_is_prefixed" 에서 출력 nameapp-dev 인지 단언하세요. tofu test 를 이 파일만 걸러 돌리고 출력을 /root/tfa-test/results/basic.txt 에 저장합니다.
  3. /root/tfa-test/tests/apply.tftest.hclenv = "stage", replicas = 3 으로 command = applyrun "file_is_written" 을 두고, file(output.conf_path) 의 내용이 정확히 name=app-stagereplicas=3 두 줄인지 단언하세요. 이 파일만 걸러 돌린 출력을 /root/tfa-test/results/apply.txt 에 저장합니다.
  4. /root/tfa-test/tests/chain.tftest.hclcommand = applyrun "make_dev" 와, 그 run 의 출력을 참조해 값을 정하는 command = planrun "reuse_previous_output" 을 두세요. 두 번째 run 은 출력 nameapp-prod 인지 단언합니다. 이 파일만 걸러 돌린 출력을 /root/tfa-test/results/chain.txt 에 저장하세요.
  5. /root/tfa-test/tests/validation.tftest.hclenv = "qa" 로 도는 run "bad_env_is_rejected"replicas = 99 로 도는 run "too_many_replicas_is_rejected" 를 두고, 각각 expect_failures 로 해당 변수의 validation 이 막는 것이 정상임을 선언하세요. 이 파일만 걸러 돌린 출력을 /root/tfa-test/results/validation.txt 에 저장합니다.
  6. /root/tfa-test/tests/fail.tftest.hcl 에 일부러 틀리는 단언(출력 nameapp-nope 인지)을 두고 tofu test 를 돌리세요. 출력은 /root/tfa-test/results/fail.txt 에, 종료 코드는 /root/tfa-test/results/fail.rc 에 한 줄로 저장합니다. 그다음 그 파일을 /root/tfa-test/broken/fail.tftest.hcl 로 옮겨 시험 묶음이 다시 통과하게 만드세요.
  7. /root/tfa-test/tests/mock.tftest.hclmock_provider "local" {} 를 두고 env = "prod"command = applyrun "mocked_apply" 에서 출력 nameapp-prod 인지 단언하세요. 이 파일만 걸러 돌린 출력을 /root/tfa-test/results/mock.txt 에 저장합니다.
  8. /root/tfa-test/ci.sh 를 만드세요. 자기가 놓인 디렉터리로 옮겨 tofu init -input=false 를 돌리고(실패하면 2로 끝남), 이어서 tofu test 를 돌려 출력을 /root/tfa-test/results/ci.txt 에 남기고 시험의 종료 코드를 그대로 돌려줘야 합니다. 만들고 나서 한 번 돌려 0 으로 끝나는지 확인하세요.

참고

시험을 붙일 대상을 만든다

/root/tfa-test/main.tfvar.env(dev·stage·prod 만 허용하는 validation)와 var.replicas(기본 1, 1~10 만 허용)를 받아 out/app-<env>.confname=replicas= 두 줄을 쓰는 local_file.conf 를 두고, 출력 name·conf_path·replicas 세 개를 선언하세요. init 하고 tofu validate 로 확인합니다.

시험을 붙이려면 먼저 시험할 수 있는 모양이어야 합니다 — 바깥에서 값을 넣을 자리(변수)와 결과를 꺼낼 자리(출력)가 있어야 단언을 쓸 수 있습니다. validation 은 뒤에서 expect_failures 로 시험할 대상이 됩니다.

계획만 세우는 값싼 시험

/root/tfa-test/tests/basic.tftest.hcl 에 파일 수준 variablesenv = "dev" 를 두고, command = planrun "name_is_prefixed" 에서 출력 nameapp-dev 인지 단언하세요. tofu test 를 이 파일만 걸러 돌리고 출력을 /root/tfa-test/results/basic.txt 에 저장합니다.

시험 파일은 현재 디렉터리나 tests 디렉터리에서 찾습니다. run 블록의 command 가 plan 이면 아무것도 만들지 않고 계획만 세워 단언하므로 빠릅니다 — 이름 규칙이나 조건 분기처럼 값만 보면 되는 것은 전부 이쪽으로 씁니다.

실제로 만들어 확인하는 비싼 시험

/root/tfa-test/tests/apply.tftest.hclenv = "stage", replicas = 3 으로 command = applyrun "file_is_written" 을 두고, file(output.conf_path) 의 내용이 정확히 name=app-stagereplicas=3 두 줄인지 단언하세요. 이 파일만 걸러 돌린 출력을 /root/tfa-test/results/apply.txt 에 저장합니다.

apply 시험은 진짜로 만들고, 끝나면 도구가 스스로 지웁니다. 그래서 값싸지 않지만 '정말 그 파일이 그 내용으로 생기는가' 는 이렇게만 확인할 수 있습니다. 시험이 끝난 뒤 out/ 을 열어 무엇이 남았는지 보세요.

앞 시험의 결과를 다음 시험이 쓴다

/root/tfa-test/tests/chain.tftest.hclcommand = applyrun "make_dev" 와, 그 run 의 출력을 참조해 값을 정하는 command = planrun "reuse_previous_output" 을 두세요. 두 번째 run 은 출력 nameapp-prod 인지 단언합니다. 이 파일만 걸러 돌린 출력을 /root/tfa-test/results/chain.txt 에 저장하세요.

run 이름으로 앞 run 의 출력을 참조할 수 있습니다. 실무에서는 '먼저 네트워크를 만들고, 그 아이디로 앱을 계획한다' 처럼 단계가 이어지는 시험을 이렇게 씁니다. 파일 안의 run 은 위에서 아래로 차례대로 돕니다.

막혀야 하는 것이 막히는지 시험한다

/root/tfa-test/tests/validation.tftest.hclenv = "qa" 로 도는 run "bad_env_is_rejected"replicas = 99 로 도는 run "too_many_replicas_is_rejected" 를 두고, 각각 expect_failures 로 해당 변수의 validation 이 막는 것이 정상임을 선언하세요. 이 파일만 걸러 돌린 출력을 /root/tfa-test/results/validation.txt 에 저장합니다.

expect_failures 는 '이 run 은 실패해야 통과' 라고 뒤집어 말하는 장치입니다. 막아 주는 장치를 만들어 놓고 정말 막히는지 확인하지 않으면, 어느 날 조건을 잘못 고쳐도 아무도 모릅니다. 목록에는 실패해야 하는 대상의 주소를 적습니다.

실패하는 시험이 무엇을 보여 주는지 본다

/root/tfa-test/tests/fail.tftest.hcl 에 일부러 틀리는 단언(출력 nameapp-nope 인지)을 두고 tofu test 를 돌리세요. 출력은 /root/tfa-test/results/fail.txt 에, 종료 코드는 /root/tfa-test/results/fail.rc 에 한 줄로 저장합니다. 그다음 그 파일을 /root/tfa-test/broken/fail.tftest.hcl 로 옮겨 시험 묶음이 다시 통과하게 만드세요.

실패 메시지가 무엇을 보여 주는지가 중요합니다 — 단언이 있던 줄과, 그때 값이 실제로 무엇이었는지가 함께 나옵니다. 그래서 error_message 에 값을 되풀이해 적을 필요가 없습니다. 종료 코드는 CI 가 읽는 유일한 신호입니다.

프로바이더를 흉내 내어 값만 시험한다

/root/tfa-test/tests/mock.tftest.hclmock_provider "local" {} 를 두고 env = "prod"command = applyrun "mocked_apply" 에서 출력 nameapp-prod 인지 단언하세요. 이 파일만 걸러 돌린 출력을 /root/tfa-test/results/mock.txt 에 저장합니다.

mock 프로바이더는 실제로 만들지 않고 그럴듯한 값을 돌려줍니다. 느리거나 돈이 드는 프로바이더를 시험할 때 값어치가 큽니다. 다만 흉내이므로 '정말 그 파일이 생기는가' 는 확인하지 못합니다 — 3단계의 진짜 apply 시험과 역할이 다릅니다.

CI 가 부를 한 줄로 묶는다

/root/tfa-test/ci.sh 를 만드세요. 자기가 놓인 디렉터리로 옮겨 tofu init -input=false 를 돌리고(실패하면 2로 끝남), 이어서 tofu test 를 돌려 출력을 /root/tfa-test/results/ci.txt 에 남기고 시험의 종료 코드를 그대로 돌려줘야 합니다. 만들고 나서 한 번 돌려 0 으로 끝나는지 확인하세요.

CI 가 읽는 신호는 종료 코드 하나뿐입니다. 시험이 실패했는데 스크립트가 0 으로 끝나면 파이프라인은 초록불로 지나갑니다 — 시험이 없는 것보다 나쁩니다. 채점기는 이 스크립트를 사본에서 두 번 돌립니다. 지금 그대로일 때와, 실패하는 시험을 하나 끼워 넣었을 때입니다.