AWX의 sudo 오류 해결: delegate_to localhost와 권한 상승 변수
AI_Manager
대상 서버의 정보를 수집한 다음 NetBox API에 등록하는 단계에서 플레이북이 실패했다. 오류는 sudo: command not found였지만, 대상 Rocky Linux에 sudo가 없는 문제는 아니었다. API 요청을 위임한 AWX 실행환경에서 불필요하게 권한 상승을 시도한 것이 원인이었다.
사용 기술·서비스: AWX 24.6.1 · Ansible · Execution Environment · NetBox REST API
이 글의 순서- 1. 오류가 난 위치부터 구분한다
- 2. become: false만으로 끝나지 않은 이유
- 3. 수정: 로컬 작업의 권한과 Python을 명시한다
- 4. 온라인망과 폐쇄망 모두 같은 경계를 적용한다
- 5. 수정 후 확인 결과
1. 오류가 난 위치부터 구분한다
대상 서버의 패키지 설치와 정보 수집은 SSH를 통해 수행하고, NetBox·Zabbix API 요청은 AWX 실행환경에서 수행했다. API 작업에는 delegate_to: localhost를 사용했다. 이때 localhost는 관리 대상 서버가 아니라 해당 작업을 실행하는 컨테이너다.
실제 실패한 작업은 자산 정보를 NetBox에 제출하는 로컬 단계였고, 다음 메시지를 남겼다.
/bin/sh: line 1: sudo: command not found
MODULE FAILURE
API 요청 자체가 거부된 것이 아니라 모듈을 실행하기 전에 실패한 상태였다. 따라서 이 오류를 NetBox 토큰 문제나 이미 생성된 자산의 중복 등록 문제로 처리하지 않았다.
2. become: false만으로 끝나지 않은 이유
대상 서버의 관리 작업에는 권한 상승이 필요해 인벤토리에 ansible_become 변수를 사용했다. 하지만 로컬 API 작업에는 이 권한이 필요하지 않았다. 이 실습에서는 task의 become: false만 적용한 뒤에도 인벤토리 변수의 영향으로 sudo 호출이 남았다.
Ansible은 playbook 키워드와 같은 목적의 연결 변수가 함께 있으면 우선순위에 따라 적용한다. 위임 작업의 실행 위치와 변수 범위를 함께 확인해야 한다. 이 동작은 Ansible 우선순위 문서와 작업 위임 문서를 참고했다.
3. 수정: 로컬 작업의 권한과 Python을 명시한다
로컬 API 작업에 ansible_become: false를 명시하고 Python도 Ansible이 실행되는 환경의 경로를 사용하게 했다. 다음 코드는 핵심 설정을 확인하기 위한 예제이며, 실제 자산 등록 로직은 기존 API 작업에 이 설정을 적용했다.
- name: Check Python used by the local API task
ansible.builtin.command:
argv:
- "{{ ansible_playbook_python }}"
- -c
- "import sys; print(sys.executable)"
delegate_to: localhost
become: false
vars:
ansible_become: false
ansible_python_interpreter: "{{ ansible_playbook_python }}"
changed_when: false
실제 API 토큰을 사용하는 작업에는 no_log: true를 유지했다. 반대로 토큰이 없는 실행환경 경로 확인 예제에는 결과를 볼 수 있게 했다. 단순히 실행환경에 sudo를 추가하거나 컨테이너를 root로 바꾸는 대신, API 작업에 필요한 권한 범위를 바로잡았다.
Job Template나 실행 요청의 Extra Variables에서 같은 변수를 다시 덮어쓰는지도 확인해야 한다. 위 코드를 복사했다는 사실만으로 모든 변수 충돌이 해소되는 것은 아니다.
4. 온라인망과 폐쇄망 모두 같은 경계를 적용한다
이 문제는 인터넷 연결 여부와 관계없이 발생한다. 온라인망에서도 대상 서버와 실행환경은 별개이며, 폐쇄망에서도 반입한 이미지 안의 Python·라이브러리·권한 설정을 확인해야 한다. API 자격 증명은 AWX Credential에서 실행환경에 주입하고 대상 서버나 Bastion에 복사하지 않았다.
5. 수정 후 확인 결과
| 단계 | 확인 결과 |
|---|---|
| 초기 로컬 API 실행 | sudo 호출로 실패 |
| task 키워드만 조정 | 변수 영향이 남아 재실패 |
| task 변수와 Python 경로 명시 | API 작업 실행 성공 |
| 전체 Workflow | 자산 등록·인벤토리 갱신·수집 확인 성공 |
| 같은 대상 재실행 | 불필요한 중복 생성 없이 확인 |
실패한 작업 결과와 수정 후 성공 결과를 함께 남겼다. 이 사례에서 먼저 물어야 할 것은 “어느 서버에 sudo를 설치할까”가 아니라 “지금 실패한 모듈이 어디에서, 어떤 변수와 권한으로 실행되는가”였다.