ControlPersist 및 Pipelining으로 Ansible 성능 극대화

ControlPersist로 SSH 연결을 재사용하고 Pipelining으로 모듈 실행을 간소화하여 Ansible 플레이북 성능을 크게 향상시키세요. 이 가이드는 특히 대규모 환경에서 실행 시간을 단축하는 필수 통찰력과 실용적인 구성을 제공합니다. 더 빠르고 효율적인 IT 자동화를 위해 `ansible.cfg`를 조정하는 방법을 알아보세요.

ControlPersist 및 Pipelining으로 Ansible 성능 극대화

느린 Ansible 실행은 일반적으로 네트워크에서 무슨 일이 일어나고 있는지 확인하기 전까지는 미스터리로 느껴집니다. 100개 호스트를 대상으로 하는 20개의 작은 작업이 포함된 플레이북은 SSH 세션 열기, 임시 모듈 파일 복사, Python 실행, 출력 수집, 연결 종료에 놀라울 정도로 많은 시간을 소비할 수 있습니다. 원격 호스트에서의 작업은 밀리초가 걸릴 수 있지만 연결 오버헤드는 계속해서 반복됩니다.

두 가지 설정이 종종 도움이 됩니다: ControlPersist를 통한 SSH 연결 재사용과 Ansible Pipelining입니다. 이것들은 마법 같은 스위치가 아니며, 느린 패키지 미러, 과부하된 데이터베이스 또는 무거운 작업을 수행하는 작업을 해결하지 못합니다. 이는 피할 수 있는 통신 오버헤드를 줄여주며, 이것이 바로 많은 소규모 작업 플레이북이 시간을 낭비하는 부분입니다.

먼저, 현재의 문제점을 측정하세요

설정을 변경하기 전에 타이밍을 활성화하여 플레이북을 한 번 실행하세요:

ANSIBLE_CALLBACKS_ENABLED=ansible.posix.profile_tasks ansible-playbook site.yml

해당 콜백이 설치되지 않은 경우, CI 시스템의 더 간단한 내장 타이밍 출력을 사용하거나 time 명령어로 래핑하세요. 목표는 완벽한 벤치마크가 아닙니다. 기준을 설정하고 느린 작업이 실제 작업인지 아니면 여러 호스트에 걸쳐 반복되는 작은 작업인지 파악하는 것입니다.

유용한 스모크 테스트는 대표 인벤토리에 대한 임시 ping입니다:

time ansible all -m ping

두 번 실행하세요. 연결 재사용이 구성된 후 두 번째 실행이 훨씬 빠르다면 SSH 설정 비용이 문제의 일부였음을 확인한 것입니다.

ControlPersist가 변경하는 사항

ControlPersist는 OpenSSH 기능입니다. 마스터 SSH 연결을 일정 시간 동안 열어 두어 나중에 동일한 호스트, 사용자 및 포트에 대한 SSH 명령이 이를 재사용할 수 있도록 합니다. Ansible은 일반적으로 각 작업에 대해 SSH를 사용하므로 연결 다중화는 반복적인 핸드셰이크를 제거합니다.

실용적인 프로젝트 수준의 ansible.cfg는 다음과 같습니다:

[defaults]
forks = 20

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r

소켓 디렉토리를 개인 권한으로 생성하세요:

mkdir -p ~/.ansible/cp
chmod 700 ~/.ansible ~/.ansible/cp

ControlMaster=auto는 SSH에 사용 가능한 마스터 연결이 있으면 사용하고, 없으면 새로 생성하도록 지시합니다. ControlPersist=600s는 마지막 세션이 종료된 후 10분 동안 마스터를 유지합니다. 이 값은 고정된 것이 아닙니다. 짧은 CI 작업의 경우 몇 분이면 충분할 수 있습니다. 유지 관리 기간 동안 플레이북을 반복적으로 실행하는 운영자의 경우 10분 또는 15분이 적당할 수 있습니다.

ControlPath는 사람들이 예상하는 것보다 더 중요합니다. 많은 시스템에서 Unix 소켓 경로에는 길이 제한이 있습니다. 깊은 작업 공간 경로 내의 긴 인벤토리 호스트 이름은 혼란스러운 오류와 함께 다중화를 중단시킬 수 있습니다. ~/.ansible/cp와 같이 경로를 짧게 유지하면 이러한 종류의 오류를 피할 수 있습니다.

최근 Ansible 버전은 이미 많은 일반 구성에서 SSH 다중화를 기본적으로 활성화하지만, 프로젝트 ansible.cfg의 명시적 설정은 동작을 감사하기 쉽게 만듭니다. 다음 명령으로 활성 구성을 확인하세요:

ansible-config dump --only-changed

Pipelining이 변경하는 사항

Pipelining이 없으면 Ansible은 종종 모듈을 원격 호스트의 임시 디렉토리에 복사하고, 실행한 다음 정리합니다. Pipelining이 활성화되면 Ansible은 SSH 연결을 통해 모듈 코드를 전달하여 임시 파일을 덜 작성할 수 있습니다. 이렇게 하면 왕복 시간과 원격 파일 시스템 작업이 절약됩니다.

동일한 파일에서 활성화하세요:

[ssh_connection]
pipelining = True

또는 한 번 실행해 보세요:

ANSIBLE_PIPELINING=True ansible-playbook site.yml

이 설정은 플레이북에 file, lineinfile, template, user, service 및 짧은 명령 작업과 같은 많은 소규모 모듈이 있을 때 가장 두드러집니다. 작업이 패키지 설치, 소프트웨어 빌드, 대규모 아티팩트 전송 또는 외부 서비스 대기와 같은 작업에 대부분의 시간을 소비할 때는 덜 중요합니다.

sudo requiretty 함정

고전적인 Pipelining 문제는 sudoers의 requiretty입니다. 일부 구형 엔터프라이즈 Linux 구성에서는 sudo에 TTY가 필요했습니다. Pipelining은 Ansible이 비대화식으로 SSH를 통해 작업을 스트리밍하려고 하기 때문에 해당 요구 사항과 잘 작동하지 않습니다.

sudoers를 주의 깊게 확인하세요. 일반 편집기로 /etc/sudoers를 편집하지 말고 visudo를 사용하세요:

sudo visudo

다음과 같은 전역 줄이 표시되면:

Defaults requiretty

제거하거나 Ansible 자동화 사용자에 대해 재정의해야 할 수 있습니다:

Defaults:ansible !requiretty

조직의 보안 정책에 부합하는 경우에만 이 변경을 수행하세요. 많은 최신 배포판에서 requiretty는 기본적으로 활성화되어 있지 않습니다.

더 안전한 결합 구성

많은 팀에게 이것은 합리적인 시작점입니다:

[defaults]
forks = 20
gathering = smart
fact_caching = jsonfile
fact_caching_connection = .ansible_facts
fact_caching_timeout = 86400

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
pipelining = True

forks는 병렬 처리를 제어합니다. 성능과 관련이 있지만 동일한 최적화는 아닙니다. forks를 5에서 20으로 높이면 제어 노드와 네트워크가 처리할 수 있는 경우 도움이 될 수 있습니다. 너무 높이면 배스천 호스트, 패키지 리포지토리 또는 관리 노드 자체에 과부하가 걸릴 수 있습니다.

Fact 캐싱은 다른 종류의 느린 속도에 도움이 됩니다. 플레이북이 실행될 때마다 사실을 수집하고 사실이 매분 새로울 필요가 없는 경우 캐싱은 반복적인 설정 작업을 제거할 수 있습니다. 의도적으로 사용하세요. 오래된 사실은 현재 메모리, 디스크 또는 인터페이스 데이터에 의존하는 경우 혼란을 줄 수 있습니다.

자신을 속이지 않고 테스트하는 방법

프로덕션처럼 보이는 하위 집합에서 테스트하세요. 동일한 서브넷에 있는 5개의 유휴 테스트 VM은 배스천 뒤에 있는 100개의 혼합 호스트에 대해 많은 것을 알려주지 않습니다.

변경 전후에 동일한 플레이북을 실행하세요. 콜드 비교가 필요한 경우 테스트 실행 사이에 기존 제어 소켓을 지우세요:

rm -f ~/.ansible/cp/*
time ansible-playbook site.yml --limit web

그런 다음 웜 비교를 실행하세요:

time ansible-playbook site.yml --limit web

작고 반복되는 작업에 소요되는 시간이 줄어드는지 확인하세요. 또한 실패 모드를 관찰하세요. become을 사용하는 작업이 실패하기 시작하면 Pipelining 자체를 비난하기 전에 sudo 구성을 조사하세요.

이러한 설정이 별로 도움이 되지 않는 경우

ControlPersist와 Pipelining은 느린 원격 작업을 빠르게 만들지 않습니다. apt update가 미러를 기다리는 경우 연결 재사용이 도움이 되지 않습니다. 서비스 재시작이 앱이 연결을 드레이닝하기 때문에 30초를 기다리는 경우 Pipelining은 관련이 없습니다. 플레이북이 모든 호스트에 2GB 아티팩트를 복사하는 경우 아티팩트 배포, 캐싱 또는 로컬 패키지 리포지토리에 집중하세요.

또한 멱등적인 플레이북 디자인을 대체하지 않습니다. 조건 없이 셸 명령을 실행하는 플레이북은 여전히 시간을 낭비합니다. 현재 상태를 감지할 수 있는 모듈을 사용하고, 적절한 경우 명령 작업에 creates 또는 removes를 추가하고, 플레이에서 사용하지 않을 때는 사실 수집을 피하세요.

실용적인 접근 방식은 간단합니다: 짧고 개인적인 제어 경로로 ControlPersist를 활성화하고, sudo 정책으로 Pipelining을 테스트하고, forks를 점진적으로 조정하고, 매번 동일한 인벤토리와 워크로드로 측정하세요. 많은 실제 Ansible 환경에서 이러한 변경은 반복적인 오버헤드를 숨기는 것이 아니라 제거하기 때문에 느린 실행을 견딜 수 있는 수준으로 만듭니다.

배스천 호스트 및 점프 호스트

많은 인벤토리가 관리 노드에 직접 연결되지 않습니다. 배스천을 통해 연결됩니다. ControlPersist는 여전히 도움이 되지만 두 연결(제어 노드에서 배스천, 배스천에서 대상)을 모두 고려해야 합니다.

일반적인 인벤토리 변수는 다음과 같습니다:

[private]
app01 ansible_host=10.0.10.11
app02 ansible_host=10.0.10.12

[private:vars]
ansible_user=ansible
ansible_ssh_common_args='-o ProxyJump=bastion.example.com'

모든 작업이 반복적으로 점프 연결을 구축하면 배스천이 오버헤드의 일부가 됩니다. 제어 경로를 짧고 개인적으로 유지하고 배스천의 SSH 연결 제한을 주시하세요. 10개 호스트에서 잘 실행되는 플레이북은 forks가 50으로 높아지면 소규모 배스천에 과부하를 줄 수 있습니다.

이전 SSH 클라이언트의 경우 ProxyJump 대신 ProxyCommand가 표시될 수 있습니다:

ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p bastion.example.com"'

Ansible을 비난하기 전에 일반 SSH 연결을 테스트하세요:

ssh -o ProxyJump=bastion.example.com [email protected] hostname

느리면 Ansible도 느릴 것입니다.

실제 플레이북에서의 Pipelining과 become

Pipelining이 권한 에스컬레이션과 작동하지 않는다는 오래된 조언은 너무 광범위합니다. Pipelining은 become과 함께 작동할 수 있습니다. 일반적인 차단 요소는 TTY를 요구하는 sudo 또는 비대화식 실행을 방해하는 정책입니다. 유일한 신뢰할 수 있는 답변은 플레이북에서 사용하는 것과 동일한 become 설정으로 테스트하는 것입니다.

작은 테스트 플레이로 충분합니다:

- hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: 권한 있는 명령이 작동하는지 확인
      ansible.builtin.command: id
      changed_when: false

Pipelining을 활성화하고 실행하세요. 실패하면 sudoers, 인증 프롬프트 및 자동화 사용자가 암호 프롬프트 없이 필요한 명령을 실행할 수 있는지 확인하세요. 대규모 자동화 실행에서 암호 프롬프트는 취약합니다. 또한 타이밍 측정을 노이즈하게 만듭니다.

종속성을 고려한 forks 조정

forks를 늘리는 것은 즉각적인 가시적인 변화를 생성하기 때문에 유혹적입니다. 또한 새로운 병목 현상을 만들 수 있습니다. 하나의 플레이가 모든 호스트에서 동시에 패키지 캐시를 업데이트하면 높은 fork 수는 패키지 미러에 부담을 줄 수 있습니다. 모든 호스트가 동시에 재시작되고 동일한 데이터베이스에 다시 연결되면 데이터베이스는 폭발적인 부하를 보게 됩니다.

측정된 접근 방식이 더 좋습니다:

[defaults]
forks = 10

플레이를 실행하세요. 20을 시도해 보세요. 그런 다음 30을 시도해 보세요. 제어 노드 CPU, SSH 프로세스 수, 배스천 부하, 네트워크 포화, 패키지 리포지토리 및 변경 중인 서비스를 모니터링하세요. 가장 빠른 설정이 항상 가장 높은 병렬 처리를 가진 설정은 아닙니다. 롤링 애플리케이션 배포의 경우 가용성을 보호하기 위해 플레이북에서 serial을 원할 수도 있습니다:

- hosts: web
  serial: 10

forks는 Ansible이 한 번에 얼마나 많은 작업을 수행할 수 있는지 제어합니다. serial은 플레이가 한 번에 처리해야 하는 호스트 수를 제어합니다. 서로 다른 문제를 해결합니다.

오래된 제어 소켓 정리

제어 소켓은 일반적으로 자체 정리되지만 랩톱은 절전 모드로 전환되고, CI 작업은 종료되며, 네트워크 경로는 변경됩니다. SSH가 다중화 오류를 보고하기 시작하면 오래된 소켓을 제거하세요:

rm -f ~/.ansible/cp/*

해당 소켓에 대해 활성 Ansible 실행이 없을 때 안전합니다. 공유 자동화 실행기에서는 제어 소켓을 공유 쓰기 가능 디렉토리에 배치하지 마세요. 각 자동화 사용자는 자체 개인 경로를 가져야 합니다.

인벤토리 디자인은 성능 향상을 무효화할 수 있습니다

연결 튜닝은 도움이 되지만 인벤토리 디자인은 여전히 모든 것을 느리게 만들 수 있습니다. 느리게 클라우드 API를 호출하는 동적 인벤토리 스크립트, 비용이 많이 드는 조회를 실행하는 그룹 변수 및 건드리지 않는 호스트에 대한 사실을 수집하는 플레이북은 SSH가 시작되기 전에 지연을 추가할 수 있습니다.

첫 번째 작업 전에 실행이 일시 중지되면 인벤토리 로딩을 프로파일링하세요. 플러그인이 지원하는 경우 동적 인벤토리를 캐시하세요. 구문 분석 시간에 비용이 많이 드는 변수 조회를 피하세요. 호스트 패턴을 좁게 유지하세요:

ansible-playbook site.yml --limit web:&prod

이 명령은 webprod 모두에 있는 호스트를 대상으로 합니다. all에 대해 광범위한 플레이를 실행한 다음 when 조건으로 대부분의 작업을 건너뛰면 시간이 낭비되고 출력을 읽기가 더 어려워집니다.

상태가 관련된 경우 더 적고 명확한 작업 선호

Ansible 가독성은 중요하지만 지나치게 작은 작업은 연결 오버헤드를 더 눈에 띄게 만들 수 있습니다. 하나의 구성 파일에서 10개의 관련 줄을 10개의 개별 lineinfile 작업으로 설정하면 작업 오버헤드를 10번 지불하고 롤백 추론을 더 어렵게 만듭니다. 템플릿이 더 명확하고 빠를 수 있습니다:

- name: 애플리케이션 구성 렌더링
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf
    mode: '0644'
  notify: restart app

이것은 Ansible 내에서 거대한 셸 스크립트를 위한 주장이 아닙니다. 올바른 수준에서 원하는 상태를 모델링하라는 알림입니다. 하나의 구성 파일에 대한 하나의 템플릿은 많은 마이크로 편집보다 종종 더 좋습니다.

조기 글로벌 변경 피하기

가능하면 프로젝트 ansible.cfg에 성능 설정을 넣으세요. 공유 제어 노드에서 /etc/ansible/ansible.cfg를 변경하면 다른 팀을 놀라게 할 수 있습니다. 프로젝트 수준 파일은 동작을 리포지토리와 함께 이동시키고 CI, 랩톱 및 자동화 실행기를 동일한 구성에 더 가깝게 유지합니다.

Ansible이 사용 중인 구성 파일을 확인하세요:

ansible --version

출력에는 활성 구성 경로가 포함됩니다. 이는 일반적인 실수(Ansible이 다른 ansible.cfg를 읽는 동안 하나를 편집하는 것)를 잡아냅니다.

Ansible 튜닝을 중단해야 할 때 알기

연결 오버헤드가 더 이상 런타임의 주요 부분이 아닌 경우 SSH 튜닝을 중단하고 작업을 살펴보세요. 패키지 캐시 업데이트, 컨테이너 풀, 데이터베이스 마이그레이션, 서비스 상태 확인 및 클라우드 API 호출은 종종 성숙한 플레이북을 지배합니다. 이 시점에서 더 나은 최적화는 로컬 패키지 미러, 아티팩트 캐싱, 더 작은 배포 배치 또는 모든 배포 실행에서 일회성 프로비저닝을 제거하는 것일 수 있습니다.