테스트 전략
테스트 전략 (Testing Strategies)
"Ansible 플레이북에 테스트를 가장 잘 통합하려면 어떻게 해야 할까요?"라는 질문을 많이 받아요. Ansible은 본질적으로 '빠르게 실패(fail-fast)'하고 순서가 있는 시스템이라서 테스트를 플레이북에 직접 심기 쉽게 설계되어 있어요. 이 글에서는 인프라 테스트를 통합하는 몇 가지 패턴과, 상황에 맞는 적절한 테스트 수준을 다뤄요.
출처: 문서
본문
Ansible 플레이북과 테스트 통합
배포 워크플로에 어느 정도 테스트를 넣으면, 코드가 프로덕션에 도달했을 때 예상치 못한 일이 줄어들어요. 그리고 많은 경우 테스트를 프로덕션에서 사용해 실패한 업데이트가 전체 설치로 옮겨가는 것을 막을 수도 있어요. push 기반이라서 로컬 호스트나 테스트 서버에서 단계를 실행하기도 매우 쉬워요. Ansible은 업그레이드 워크플로에 원하는 만큼 많은 검사와 균형(checks and balances)을 넣을 수 있게 해 줘요.
Note 이 장은 배포하는 애플리케이션을 테스트하는 법에 관한 것이에요. 개발 중에 Ansible 모듈을 테스트하는 법에 대해서는 Development 섹션을 참고하세요.
적절한 테스트 수준 (The Right Level of Testing)
Ansible 리소스는 원하는 상태(desired-state)의 모델이에요. 따라서 서비스가 시작되었는지, 패키지가 설치되었는지, 그런 것들을 테스트할 필요는 없어요. Ansible이 이러한 것들이 선언적으로 참임을 보장하는 시스템이니까요. 대신 플레이북에서 이러한 것들을 단언(assert)하세요.
tasks:
- ansible.builtin.service:
name: foo
state: started
enabled: true
서비스가 시작되지 않았을 것 같다면, 가장 좋은 방법은 시작을 요청하는 거예요. 서비스가 시작에 실패하면 Ansible이 적절히 알려줘요. (이것은 서비스가 기능적으로 무언가를 하는지와는 혼동하면 안 돼요 — 그것에 대해서는 나중에 더 다룰게요.)
드리프트 테스트로서의 체크 모드 (Check Mode As A Drift Test)
위 설정에서 Ansible의 --check 모드는 테스트의 한 층으로도 사용할 수 있어요. 기존 시스템에 배포 플레이북을 실행할 때 ansible 명령에 --check 플래그를 쓰면, Ansible이 시스템을 원하는 상태로 만들기 위해 변경을 해야 했을지 여부를 보고해요.
이를 통해 해당 시스템에 배포할 필요가 있는지 미리 알 수 있어요. 보통 스크립트와 명령은 체크 모드에서 실행되지 않으므로, --check 플래그를 사용해도 특정 단계(예: script 모듈 호출)를 일반 모드로 실행하고 싶다면 해당 작업에서 체크 모드를 비활성화하세요:
roles:
- webserver
tasks:
- ansible.builtin.script: verify.sh
check_mode: false
테스트에 유용한 모듈 (Modules That Are Useful for Testing)
특정 플레이북 모듈은 테스트에 특히 좋아요. 아래는 포트가 열려 있는지 확인하는 예시예요:
tasks:
- ansible.builtin.wait_for:
host: "{{ inventory_hostname }}"
port: 22
delegate_to: localhost
URI 모듈을 사용해 웹 서비스가 응답하는지 확인하는 예시예요:
tasks:
- action: uri url=https://www.example.com return_content=yes
register: webpage
- fail:
msg: 'service is not happy'
when: "'AWESOME' not in webpage.content"
임의의 스크립트(어떤 언어든)를 원격 호스트에 밀어 넣는 것은 쉬워요. 스크립트가 0이 아닌 반환 코드를 가지면 자동으로 실패해요:
tasks:
- ansible.builtin.script: test_script1
- ansible.builtin.script: test_script2 --parameter value --parameter2 value
역할을 사용한다면(사용해야 해요, 역할은 훌륭해요!), script 모듈로 밀어 넣은 스크립트는 역할의 files/ 디렉터리에 둘 수 있어요. 그리고 assert 모듈은 다양한 종류의 참(truth)을 검증하기 매우 쉽게 만들어 줘요:
tasks:
- ansible.builtin.shell: /usr/bin/some-command --parameter value
register: cmd_result
- ansible.builtin.assert:
that:
- "'not ready' not in cmd_result.stderr"
- "'gizmo enabled' in cmd_result.stdout"
Ansible 구성에 선언적으로 설정되지 않은 파일의 존재를 테스트해야 한다면, stat 모듈이 좋은 선택이에요:
tasks:
- ansible.builtin.stat:
path: /path/to/something
register: p
- ansible.builtin.assert:
that:
- p.stat.exists and p.stat.isdir
앞서 말했듯이 명령의 반환 코드 같은 것을 검사할 필요는 없어요. Ansible이 자동으로 확인하니까요. 사용자가 존재하는지 확인하는 대신, user 모듈로 그 사용자를 존재하게 만드는 것을 고려해 보세요. Ansible은 빠르게 실패하는 시스템이라서 사용자를 만드는 중 오류가 나면 플레이북 실행을 멈춰요. 뒤에서 확인할 필요가 없어요.
테스트 수명주기 (Testing Lifecycle)
플레이북에 애플리케이션의 기본 검증을 어느 정도 작성해 두면, 배포할 때마다 실행돼요. 따라서 로컬 개발 VM과 스테이징 환경에 배포하는 것이 모두, 프로덕션 배포에 앞서 계획대로인지 검증해 줘요.
워크플로는 대략 다음과 같을 수 있어요:
- 개발에서 임베드된 테스트가 있는 동일한 플레이북을 항상 사용
- 프로덕션을 시뮬레이션하는 스테이징 환경에 (동일한 플레이북으로) 배포
- QA 팀이 작성한 통합 테스트 배터리를 스테이징에 대해 실행
- 동일한 통합 테스트를 포함해 프로덕션에 배포
프로덕션 웹 서비스라면 통합 테스트 배터리 같은 것은 QA 팀이 작성해야 해요. 여기에는 Selenium 테스트나 자동화된 API 테스트 같은 것이 포함되며, 보통 Ansible 플레이북에 임베드하지는 않아요. 다만 플레이북에 기본적인 헬스 체크를 포함하는 것은 합리적이고, 어떤 경우에는 QA 배터리의 일부를 원격 노드에 대해 실행할 수도 있어요. 그것을 다음 섹션에서 다룰게요.
롤링 업데이트와 테스트 통합 (Integrating Testing With Rolling Updates)
"Controlling where tasks run: delegation and local actions" 문서를 읽었다면 롤링 업데이트 패턴이 확장될 수 있다는 것을 금방 알 수 있을 거예요. 플레이북 실행의 성공 또는 실패를 사용해 머신을 로드 밸런서에 추가할지 여부를 결정할 수 있어요. 이것이 임베드된 테스트의 훌륭한 정점이에요:
---
- hosts: webservers
serial: 5
pre_tasks:
- name: take out of load balancer pool
ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
delegate_to: 127.0.0.1
tasks:
- ansible.builtin.include_role:
name: "{{ item }}"
loop:
- common
- webserver
- name: run any notified handlers
ansible.builtin.meta: flush_handlers
- name: test the configuration
ansible.builtin.include_role:
name: apply_testing_checks
post_tasks:
- name: add back to load balancer pool
ansible.builtin.command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
delegate_to: 127.0.0.1
물론 위의 "pool에서 빼기"와 "다시 추가" 단계는 Ansible 로드 밸런서 모듈이나 적절한 셸 명령 호출로 대체될 거예요. 머신의 중단(outage) 창을 시작하고 끝내기 위해 모니터링 모듈을 사용하는 단계가 있을 수도 있어요.
하지만 위에서 볼 수 있듯이 테스트는 게이트(gate)로 사용돼요 — "apply_testing_checks" 단계가 수행되지 않으면 머신은 pool로 돌아가지 않아요. 위임에 관한 "max_fail_percentage" 장을 읽으면, 롤링 업데이트를 멈추게 하는 실패 테스트 수를 제어할 수도 있어요.
위 접근 방식은 테스트 머신에서 원격 머신에 대해 단계를 실행하도록 수정할 수도 있어요:
---
- hosts: webservers
serial: 5
pre_tasks:
- name: take out of load balancer pool
ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
delegate_to: 127.0.0.1
roles:
- common
- webserver
tasks:
- ansible.builtin.script: /srv/qa_team/app_testing_script.sh --server {{ inventory_hostname }}
delegate_to: testing_server
post_tasks:
- name: add back to load balancer pool
ansible.builtin.command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
delegate_to: 127.0.0.1
위 예시에서 테스트 서버에서 원격 노드에 대해 스크립트를 실행한 다음, 그 노드를 pool로 다시 가져와요. 문제가 발생하면, Ansible이 자동으로 생성한 retry 파일을 사용해 실패한 몇 대의 서버만 다시 배포하면 돼요.
지속적 배포 달성 (Achieving Continuous Deployment)
원한다면 위의 기법을 확장해 지속적 배포(continuous deployment)를 실현할 수 있어요. 워크플로는 다음과 같을 수 있어요:
- 자동화를 작성·사용해 로컬 개발 VM에 배포
- Jenkins 같은 CI 시스템이 코드 변경마다 스테이징 환경에 배포
- 배포 작업이 테스트 스크립트를 호출해 매 배포 시 빌드를 통과/실패로 판정
- 배포 작업이 성공하면 동일한 배포 플레이북을 프로덕션 인벤토리에 대해 실행
일부 Ansible 사용자는 위 접근 방식을 사용해 인프라 전체를 오프라인으로 만들지 않고 시간당 6~12회 배포해요. 이 수준에 도달하려면 자동화된 QA 문화가 필수적이에요. 아직 수동 QA가 많다면 수동으로 배포할지 결정해야 하지만, 이전 섹션의 롤링 업데이트 패턴을 적용하고 script, stat, uri, assert 같은 모듈로 기본 헬스 체크를 통합하는 것이 여전히 도움이 돼요.
결론 (Conclusion)
Ansible은 인프라의 기본적인 것들이 참인지 검증하기 위해 다른 프레임워크가 필요하지 않다고 생각해요. 그 이유는 Ansible이 순서 기반 시스템이라 호스트에 처리되지 않은 오류가 있으면 즉시 실패하고 그 호스트의 추가 구성이 진행되지 않기 때문이에요. 이는 오류를 위로 끌어올려 Ansible 실행이 끝날 때 요약으로 보여줘요.
또한 Ansible은 다중 계층 오케스트레이션 시스템으로 설계되어, 느슨한 작업이나 역할을 사용해 플레이북 실행 끝에 테스트를 통합하기 매우 쉽게 만들어요. 롤링 업데이트와 함께 사용하면 테스트 단계가 머신을 로드 밸런스 pool에 되돌릴지 여부를 결정할 수 있어요.
마지막으로, Ansible 오류는 Ansible 프로그램 자체의 반환 코드까지 모두 전파되고, Ansible은 기본적으로 쉬운 push 기반 모드로 실행되므로, 시스템을 지속적 통합/지속적 배포(CI/CD) 파이프라인의 일부로 배포하려는 경우 Ansible은 빌드 환경에 넣기에 훌륭한 단계예요.
초점은 인프라 테스트가 아니라 애플리케이션 테스트에 맞춰야 해요. 그래서 QA 팀과 함께 개발 VM을 배포할 때마다 어떤 테스트가 말이 되는지, 스테이징 환경에 매 배포마다 어떤 테스트를 실행하고 싶은지 물어보는 것을 강력히 권장해요. 개발 단계에서는 단위 테스트도 당연히 훌륭해요. 하지만 플레이북을 단위 테스트하지 마세요. Ansible은 리소스의 상태를 선언적으로 기술하므로 그럴 필요가 없어요. 그래도 확실히 하고 싶은 경우가 있다면 stat/assert 같은 것이 그 용도로 좋은 모듈이에요.
결국 테스트는 조직적이고 사이트별 특성이 매우 강해요. 모두가 해야 하지만, 환경에 가장 잘 맞는 것은 무엇을 배포하는지, 누가 사용하는지에 따라 달라져요 — 모두가 더 견고하고 안정적인 배포 시스템의 혜택을 받으니까요.
더 알아보기 (Learn more)
- Collection Index — 기존 컬렉션, 모듈, 플러그인 둘러보기
- Working with playbooks — 플레이북 소개
- Controlling where tasks run: delegation and local actions — 로드 밸런서, 클라우드, 로컬 실행 단계에 유용한 위임
- Communication — 질문, 도움, 아이디어 공유는 Ansible 커뮤니케이션 가이드 참고