대규모 Ansible 배포 최적화를 위한 모범 사례
forks, 전략 플러그인, 팩트 캐싱, SSH 재사용 및 더 나은 플레이북 설계를 통해 대규모 Ansible 실행 속도를 높이는 실용적인 방법.
대규모 Ansible 배포 최적화를 위한 모범 사례
대규모 Ansible 배포는 일반적으로 너무 많은 SSH 핸드셰이크, 과도한 팩트 수집, CPU가 부족한 컨트롤러, 또는 모든 호스트가 가장 느린 호스트를 기다리게 만드는 플레이북 때문에 느려집니다. 해결책은 단일 설정에 있는 경우가 드뭅니다. 연결 오버헤드를 줄이고, 동시성을 조정하며, 호스트당 수행하는 작업을 줄이는 플레이를 작성함으로써 최상의 결과를 얻을 수 있습니다.
저는 "대규모"를 엄격한 호스트 수로 정의하지 않습니다. 300개 호스트 인벤토리도 각 작업이 느린 링크를 통해 패키지를 설치하면 크게 느껴질 수 있습니다. 3,000개 호스트 인벤토리도 컨트롤러가 적절히 구성되고 플레이북이 간결하면 관리할 수 있습니다. 아래 숫자를 시작점으로 삼고, 자신의 인벤토리와 모듈로 측정하세요.
Ansible을 탓하기 전에 병렬 처리 조정
병렬 처리는 일반적으로 가장 먼저 테스트할 레버입니다. Ansible은 원격 호스트에서 대기하는 데 많은 시간을 소비하기 때문입니다. 목표는 "가장 높은 forks가 승리"하는 것이 아닙니다. 목표는 SSH, 권한 상승, 패키지 저장소 또는 대상 자체를 압도하지 않으면서 컨트롤러를 바쁘게 유지할 수 있는 충분한 동시성입니다.
forks로 동시성 제어
forks 매개변수는 Ansible 컨트롤러가 생성할 수 있는 병렬 프로세스 워커의 수를 정의합니다. 최적의 수를 찾으려면 컨트롤러 리소스(CPU 및 메모리)와 대상 환경의 연결 제한 간의 균형을 맞춰야 합니다.
ansible.cfg 또는 명령줄(-f 또는 --forks)에서 forks를 설정하세요.
[defaults]
forks = 100
필요하다고 생각하는 것보다 낮게 시작하세요. CPU, 메모리, SSH 실패 및 런타임을 모니터링하면서 25, 50, 100, 200 forks로 동일한 호스트 그룹에 대해 동일한 플레이를 실행하세요. CPU가 대부분 유휴 상태이고 호스트가 대기하는 데 시간을 소비하면 forks를 늘리세요. 컨트롤러가 스와핑을 시작하거나, Python 프로세스가 쌓이거나, 대상이 연결을 거부하면 낮추세요.
올바른 전략 플러그인 선택
Ansible의 기본 실행 전략은 linear입니다. 즉, 플레이북의 다음 작업으로 이동하기 전에 모든 대상 호스트에서 작업이 완료되어야 합니다. 수천 개의 노드의 경우 단일 느린 호스트가 전체 실행의 병목 현상이 될 수 있습니다.
일부 대규모 배포의 경우 free 전략을 사용하세요.
Free 전략 (strategy = free):
free를 사용하면 호스트가 느린 호스트를 기다리지 않고 작업을 완료하는 즉시 플레이북을 통해 독립적으로 진행할 수 있습니다. 작업이 독립적일 때 처리량을 향상시킬 수 있습니다. 롤링 배포, 공유 마이그레이션 또는 플릿 전체의 작업 순서가 중요한 플레이에는 맹목적으로 사용하지 마세요.
# 플레이북 정의 예시
---
- hosts: all
strategy: free
tasks:
- name: 서비스가 실행 중인지 확인
ansible.builtin.service:
name: httpd
state: started
팩트를 재사용할 때 캐싱
팩트 수집은 유용하지만 반복해서 비용을 지불하기 쉽습니다. 플레이북이 여러 실행에서 팩트를 사용하는 경우 캐시하세요. 플레이에 호스트 팩트가 전혀 필요하지 않은 경우 해당 플레이에 대해 수집을 비활성화하세요.
외부 캐시 사용 (Redis 또는 Memcached)
단일 컨트롤러의 경우 JSON 파일 캐싱으로 충분할 수 있습니다. 여러 컨트롤러 또는 자동화 워커의 경우 Redis 또는 Memcached와 같은 외부 캐시를 사용하여 모든 워커가 동일한 팩트 캐시를 볼 수 있도록 하세요.
ansible.cfg의 실행 가능한 구성:
[defaults]
gathering = smart
fact_caching = redis
fact_caching_timeout = 7200 ; 팩트를 2시간 동안 캐시 (초 단위)
fact_caching_prefix = ansible_facts
; Redis를 사용하는 경우
fact_caching_connection = localhost:6379:0
캐시된 팩트가 워크플로의 일부인 경우 gathering = smart로 설정하세요. 호스트 데이터의 작은 부분만 필요한 경우 모든 것을 수집하는 대신 gather_subset을 사용하세요.
3. 연결 및 전송 최적화
수천 개의 동시 SSH 세션을 처리할 때 연결 설정과 관련된 오버헤드를 줄이는 것이 가장 중요합니다.
SSH 파이프라이닝
파이프라이닝은 Ansible이 많은 모듈 실행에 사용하는 SSH 왕복 횟수를 줄입니다. 활성화할 가치가 있는 경우가 많지만 권한 상승 규칙과 함께 테스트하세요.
SSH 연결 재사용 (ControlPersist)
Unix 계열 대상의 경우 ControlMaster 및 ControlPersist 설정은 Ansible이 모든 단일 작업에 대해 새로운 SSH 세션을 시작하는 것을 방지합니다. 지정된 기간 동안 제어 소켓을 열어 두어 후속 작업이 기존 연결을 사용할 수 있도록 합니다.
ansible.cfg의 실행 가능한 구성:
[ssh_connection]
pipelining = True
; 공격적인 연결 재사용 사용 (예: 30분)
ssh_args = -C -o ControlMaster=auto -o ControlPersist=30m -o ServerAliveInterval=15
파이프라이닝은 TTY가 필요한 sudo 구성과 충돌할 수 있습니다. sudoers에 여전히 Defaults requiretty가 있는 경우 자동화 사용자에 대해 제거하거나 해당 호스트에 대해 파이프라이닝을 비활성화된 상태로 유지하세요.
Windows 최적화 (WinRM)
Windows 노드를 대상으로 할 때는 WinRM을 별도로 조정하세요. Kerberos는 일반적으로 기본 인증보다 생산에 더 나은 선택이며, 많은 작업이 동시에 연결되는 경우 WinRM 서비스 제한을 검토해야 할 수 있습니다.
4. 확장을 위한 인벤토리 관리
호스트가 자주 생성되고 삭제되는 경우 정적 인벤토리 파일은 관리하기 어려워집니다. 동적 인벤토리가 모든 대규모 환경에 필수적인 것은 아니지만 클라우드 플릿, 오토스케일링 그룹 및 CMDB 기반 인프라의 경우 올바른 기본값입니다.
동적 인벤토리 소스
클라우드 공급자(AWS EC2, Azure, Google Cloud) 또는 CMDB 시스템에 대한 인벤토리 플러그인을 활용하세요. 동적 인벤토리를 사용하면 Ansible이 최신 데이터로 활성 호스트만 대상으로 지정할 수 있습니다.
# 예시: 동적으로 필터링된 AWS 인벤토리에 대해 실행
ansible-playbook -i aws_ec2.yml site.yml --limit 'tag_Environment_production'
스마트 타겟팅 및 필터링
절대적으로 필요하지 않은 경우 전체 인벤토리(hosts: all)에 대해 플레이북을 실행하지 마세요. 세분화된 그룹, 제한(--limit) 및 태그(--tags)를 사용하여 실행 대상 세트를 최소화하세요.
5. 아키텍처 고려 사항 및 컨트롤러 크기 조정
대규모 배포의 경우 Ansible이 실행되는 환경을 적절히 프로비저닝해야 합니다.
컨트롤러 크기 조정
Ansible은 병렬 실행을 위해 프로세스를 포크해야 하기 때문에 컨트롤러의 리소스, 주로 CPU 및 RAM에 크게 의존합니다.
- CPU: forks가 많을수록 일반적으로 컨트롤러에서 더 많은 Python 작업이 필요합니다. 실제 플레이북 실행 중 로드 평균 및 코어당 포화 상태를 확인하세요.
- RAM: 각 포크는 메모리를 소비합니다. 대규모 템플릿, 큰 변수 및 많은 양의 콜백 플러그인은 메모리 사용량을 빠르게 증가시킬 수 있습니다.
- 스토리지 I/O: 빠른 로컬 스토리지는 컨트롤러가 많은 임시 파일, 로그, 아티팩트 또는 파일 기반 팩트 캐시 항목을 쓸 때 도움이 됩니다.
자동화 플랫폼 활용
일정, RBAC, 감사 추적 및 여러 실행 워커가 필요한 팀의 경우 단일 제어 노드에서 하나의 장기 실행 셸 세션 대신 Ansible Automation Platform 또는 AWX를 사용하세요.
AAP는 다음을 제공합니다:
- 작업 일정 및 기록: 중앙 집중식 로깅 및 감사.
- 실행 환경: 일관되고 재현 가능한 런타임 환경.
- 클러스터링 및 확장: 여러 워커 노드에 실행을 분산하여 단일 컨트롤러에 과부하를 주지 않고 대규모 동시성 요구를 처리합니다.
- 자격 증명 관리: 대규모로 비밀을 안전하게 처리합니다.
6. 효율성을 위한 플레이북 설계
인프라가 최적화되어 있어도 잘못 작성된 플레이북은 성능 향상을 무효화할 수 있습니다.
팩트 수집 최소화
캐시된 팩트(섹션 2)를 사용하는 경우 가능한 중복 팩트 수집을 적극적으로 비활성화하세요:
- hosts: web_servers
gather_facts: no # 이 플레이에 대한 팩트 수집 비활성화
tasks:
# ... 수집된 시스템 팩트에 의존하지 않는 작업만 실행
run_once 및 delegate_to를 드물게 사용
순차적으로 또는 중앙에서 실행해야 하는 작업(예: 롤링 배포 시작, 로드 밸런서 업데이트)은 run_once: true 및 delegate_to: management_node를 통해 처리해야 합니다. 이렇게 하면 하나의 호스트만 작업을 수행해야 할 때 불필요한 병렬 처리를 방지할 수 있습니다.
배치 작업 선호
가능하면 기본적으로 배치 작업을 처리하는 모듈(예: 패키지 목록을 허용하는 apt 또는 yum과 같은 패키지 관리자)을 loop 또는 with_items를 사용하여 별도의 package 작업을 통해 대규모 목록을 반복하는 대신 사용하세요.
# 더 나은 방법: 목록이 있는 하나의 패키지 작업
- name: 필요한 종속성 설치
ansible.builtin.package:
name:
- nginx
- python3-pip
- firewall
state: present
7. 호스트 수뿐만 아니라 플레이북 측정
Ansible 실행이 느린 경우 더 많은 설정을 변경하기 전에 타이밍을 추가하세요. 내장된 profile_tasks 콜백이 좋은 첫 번째 단계입니다:
[defaults]
callbacks_enabled = profile_tasks, timer
대표적인 호스트 그룹에 대해 플레이북을 한 번 실행하고 가장 느린 작업을 확인하세요. 대부분의 시간이 하나의 패키지 설치, 하나의 템플릿 렌더링 단계 또는 외부 서비스를 기다리는 하나의 명령에 소요된다는 것을 알 수 있습니다. 이 경우 forks를 늘리면 동일한 병목 현상에 더 많은 압력이 가해집니다.
반복 가능한 테스트를 위해 인벤토리 슬라이스를 안정적으로 유지하세요:
ansible-playbook -i inventory site.yml --limit 'web:&production' -f 50
ansible-playbook -i inventory site.yml --limit 'web:&production' -f 100
ansible-playbook -i inventory site.yml --limit 'web:&production' -f 200
총 런타임, 실패한 호스트, 컨트롤러 CPU, 컨트롤러 메모리 및 대상 측 SSH 또는 sudo 오류를 기록하세요. 또한 테스트 중에 패키지 저장소 또는 아티팩트 서버를 확인하세요. 실제 문제가 모든 호스트가 과부하된 내부 미러에서 동시에 동일한 패키지를 다운로드하는 것일 때 플레이북이 Ansible 문제처럼 보일 수 있습니다.
8. 동시성을 높이기 전에 작업 줄이기
대규모 Ansible 실행은 더 빠르게 수행하는 것보다 덜 수행함으로써 더 많이 개선되는 경우가 많습니다. 몇 가지 예가 반복해서 나타납니다:
- 템플릿 작업은 작은 포함만 변경되었음에도 모든 실행에서 대규모 구성 파일을 렌더링합니다.
- 셸 작업은 값이 이미 인벤토리에 있음에도 모든 호스트에서 검색 명령을 실행합니다.
- 역할은 루프에서 패키지를 하나씩 설치합니다.
- 핸들러는 하나의 리로드로 충분할 때 여러 관련 없는 템플릿 변경 후 서비스를 다시 시작합니다.
가능하면 shell 대신 모듈 멱등성을 사용하세요. 항상 변경되었다고 보고하는 셸 명령은 수백 대의 호스트에서 핸들러를 트리거하고 무해한 검사를 롤링 재시작으로 바꿀 수 있습니다. command 또는 shell을 사용해야 하는 경우 changed_when 및 creates 또는 removes를 신중하게 설정하세요.
- name: 애플리케이션 디렉토리를 한 번 초기화
ansible.builtin.command: /usr/local/bin/app-init /srv/app
args:
creates: /srv/app/.initialized
이 작은 가드는 반복 작업을 방지하고 잘못된 변경 보고를 피합니다.
9. 위험 제어를 위해 배치 사용
성능만이 대규모에서의 유일한 관심사는 아닙니다. 때로는 가장 빠른 플레이북이 운영상 위험할 수 있습니다. 서비스 플릿의 경우 serial을 사용하여 블래스트 반경을 제어하세요:
- hosts: app_servers
serial: 10%
max_fail_percentage: 5
tasks:
- name: 애플리케이션 패키지 배포
ansible.builtin.package:
name: myapp
state: latest
serial은 모든 호스트를 한 번에 실행하는 것보다 실행 시간을 길게 만들지만 로드 밸런서, 모니터링 및 사람이 대응할 시간을 제공합니다. 또한 공유 종속성을 보호합니다. 패키지 미러, 데이터베이스 마이그레이션 엔드포인트 또는 비밀 관리자는 수천 개의 동시 요청을 견디지 못할 수 있습니다.
대규모 Ansible 배포는 잊기 쉬운 시스템에 스트레스를 줍니다: DNS 확인자, 패키지 저장소, 비밀 저장소, 로깅 파이프라인 및 모니터링 엔드포인트. 플레이북이 더 높은 fork 수에서만 느려지는 경우 Ansible을 탓하기 전에 이러한 공유 서비스를 확인하세요.
또한 콜백 출력을 제어하세요. 매우 자세한 로그는 디버깅 중에 유용하지만 대규모 실행을 느리게 하고 실제 실패를 묻을 수 있습니다. 좁은 호스트 슬라이스에 대해 높은 상세 수준을 사용한 다음 전체 플릿 실행을 위해 일반 출력으로 돌아가세요.
10. 장애 도메인별로 플레이 분할
간과되는 확장 트릭 중 하나는 전체 에스테이트를 하나의 배포 단위로 취급하는 것을 중단하는 것입니다. 데이터베이스 호스트, 웹 호스트, 큐 및 캐시 노드가 모두 동일한 거대한 플레이에 있는 경우 느리거나 중단된 그룹 하나가 관련 없는 작업을 지연시킬 수 있습니다. 장애 도메인과 종속성 순서별로 플레이를 분리하세요.
예를 들어, 기본 OS 구성을 광범위하게 실행하지만 서비스 계층별로 애플리케이션 코드를 배포하세요. 자체 플레이에서 캐시 노드를 업데이트하세요. 웹 노드를 배치로 드레이닝하고 다시 시작하세요. 추가 검사와 더 작은 동시성으로 데이터베이스 구성을 적용하세요. 이렇게 하면 실패한 부분을 모든 호스트에서 작업을 반복하지 않고 다시 실행할 수 있으므로 재시도가 더 안전해집니다.
또한 소유권이 더 명확해집니다. 서비스를 담당하는 팀은 전역 자동화 기본값을 변경하지 않고도 배치 크기, 상태 확인 및 롤백 동작을 조정할 수 있습니다. 플레이북 구조가 실제 인시던트 및 유지 관리 기간 동안 인프라가 실제로 실패하는 방식과 일치할 때 대규모 Ansible은 유지 관리 가능하게 유지됩니다.
영향력이 가장 큰 Ansible 성능 작업은 일반적으로 간단합니다: SSH 연결 재사용, 불필요한 팩트 수집 방지, forks 크기 조정, 플레이북이 반복적인 소규모 작업을 수행하지 않도록 중지. 그런 다음 아키텍처를 살펴보세요. 하나의 컨트롤러가 깔끔하게 따라잡을 수 없는 경우 실행 노드 간에 작업을 분할하고 인벤토리, 자격 증명 및 로깅을 반복 가능하게 만드세요.