SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-07

Ansible 첫 플레이북 작성 및 Ubuntu VPS 보안 설정

Ubuntu 24.04 환경에서 pipx로 Ansible을 설치하고 VPS 보안을 강화하는 첫 플레이북을 작성합니다. Permission denied 및 sudo 권한 오류 해결법을 포함하여 서버 프로비저닝 자동화의 핵심인 멱등성을 실습합니다.

구축할 환경

Ansible이 설치된 제어용 머신 1대와, 기본 이미지 외에는 아무것도 설치되지 않은 Ubuntu 24.04 VPS 1대 이상이 필요합니다. 이 과정을 마치면 서버 이름을 정의한 인벤토리 파일, 인증이 정상적으로 작동함을 확인하는 ad-hoc ping, 그리고 새로운 VPS를 위한 체크리스트를 코드로 자동화한 플레이북을 갖게 됩니다. 이 플레이북에는 SSH 키를 포함한 배포용 사용자 생성, sshd 보안 강화, fail2ban 설치, 자동 보안 업데이트, 그리고 OpenSSH를 제외한 모든 트래픽을 차단하는 방화벽 설정이 포함됩니다. 서버 1대든 20대든 상관없이 적용할 수 있습니다. 두 번 실행해도 두 번째 실행 시에는 아무것도 변경되지 않아야 하며, 이것이 자동화의 핵심입니다.

15년 동안 VPS를 프로비저닝해 온 경험에 비추어 볼 때, 솔직한 패턴은 다음과 같습니다. 처음 5대의 서버는 수동으로 설정하지만, 6번째 서버를 설정할 때는 앞선 5대에 무엇을 했는지 기억하지 못해 주말을 허비하게 됩니다. 이 가이드는 여러 Linux 서버 관리하기에서 다룬 내용을 심화한 것이며, 같은 apt install 명령어를 3개의 터미널에 입력하고 있는 자신을 발견하는 날 이 가이드를 시작하십시오.

Ansible의 정의

Ansible은 에이전트가 없는 방식입니다. 관리 대상 서버에 설치해야 할 데몬이 없습니다. 제어 노드는 일반적인 SSH로 연결하여 작은 Python 모듈을 대상 서버로 복사하고, 이를 실행한 뒤 출력되는 JSON을 읽고 삭제합니다. 대상 서버에 필요한 유일한 조건은 python3이며, 이는 모든 기본 Ubuntu 이미지에 이미 포함되어 있습니다. 여기서 중요한 단어는 멱등성(idempotent)입니다. 이는 작업이 '동작'이 아닌 '상태'를 기술한다는 단순한 의미입니다. 패키지에 대한 state: present는 "설치 프로그램을 실행하라"는 뜻이 아니라 "이 패키지가 설치되어 있는지 확인하라"는 뜻입니다. 만약 해당 상태가 이미 충족되어 있다면, Ansible은 아무것도 변경하지 않고 changed 대신 ok으로 보고합니다. 이 속성이 제품의 핵심이며, 플레이북을 안전하게 재실행할 수 있게 만드는 원동력입니다. 이러한 안전한 재실행이야말로 셸 스크립트를 인프라로 격상시키는 요소입니다.

사전 요구 사항 및 주의 사항

  • 제어용 머신: 노트북 또는 소규모 VPS를 사용합니다. Ubuntu 24.04를 기준으로 설명하지만, Homebrew를 통해 pipx를 설치하면 macOS에서도 동일하게 작동합니다.
  • 하나 이상의 대상 VPS: KVM 기반의 Ubuntu 24.04가 실행 중이어야 하며, root 계정으로 접근 가능해야 합니다. 대상 서버에는 별도의 소프트웨어를 설치하지 않습니다.
  • 모든 대상에 대한 SSH key 인증: Ansible의 인증 권한은 사용자의 ssh 명령과 동일합니다. 만약 ssh root@host 명령 실행 시 비밀번호를 묻는다면 Ansible은 실패합니다.
  • Ubuntu 24.04에서 pip install ansible 명령은 error: externally-managed-environment 오류와 함께 종료됩니다. 이는 배포판의 의도된 정책이며 고장 난 것이 아닙니다. pipx를 사용하십시오.
  • YAML에서 공백은 문법의 일부입니다. 들여쓰기가 잘못되면 mapping values are not allowed in this context 오류가 발생하며, 탭 문자가 포함되면 즉시 실패합니다.
  • playbook으로 sshd를 강화하는 동안 각 대상 서버의 SSH 세션을 열어두십시오. 제가 고객의 접속 차단 문제를 해결했던 사례들은 모두 "깨끗한 상태에서 테스트하기 위해" 마지막 세션을 종료했을 때 발생했습니다.

1단계: pip가 아닌 pipx로 제어 머신에 Ansible 설치하기

가장 먼저 떠오르는 방법은 pip3 install ansible입니다. 완전히 새로 설치한 24.04 이미지에서는 이 단계가 먼저 실패하며, Command 'pip3' not found, but can be installed with: sudo apt install python3-pip을 실행하여 pip를 설치하더라도 실제로는 더 큰 문제에 직면하게 됩니다.

pip3 install ansible
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

Ubuntu 24.04는 시스템 Python을 외부 관리형(PEP 668)으로 지정하므로, pip가 apt와 동일한 파일을 두고 충돌을 일으킬 수 없습니다. --break-system-packages를 사용하지 마십시오. 플래그 이름이 의미하는 바를 그대로 따라야 합니다. 깔끔한 해결책은 pipx를 사용하는 것입니다. pipx는 Ansible을 위한 독립적인 virtualenv를 생성하고 바이너리를 PATH에 추가합니다.

sudo apt update && sudo apt install -y pipx
pipx ensurepath
pipx install --include-deps ansible

pipx ensurepath을 실행한 후에는 PATH 변경 사항이 적용되도록 새로운 셸을 여십시오. --include-deps는 장식이 아닙니다. ansible 패키지 자체에는 콘솔 스크립트가 포함되어 있지 않으며, ansible, ansible-playbook 및 기타 항목들은 ansible-core 의존성의 진입점입니다. 따라서 해당 플래그가 없으면 pipx는 No apps associated with package ansible or its dependencies 오류를 발생시키며 설치를 거부합니다. 또한 단순히 ansible-core이 아닌 ansible 패키지를 설치하십시오. 전체 패키지에는 커뮤니티 컬렉션이 포함되어 있으며, 이 플레이북은 그중 두 가지(ansible.posixcommunity.general) 모듈을 사용합니다.

ansible --version

올바르게 설치되었다면 ansible [core 2.19.x]와 같은 줄로 시작하며 실행 중인 Python 버전이 표시됩니다. 현재의 모든 코어 릴리스는 이 가이드의 모든 작업에 적합합니다. 대신 ansible: command not found가 출력된다면 ~/.local/bin이 아직 PATH에 없거나, 새로운 셸을 열지 않았거나, 혹은 source ~/.bashrc 상태임을 의미합니다.

설치 과정은 이것으로 끝입니다. 대상 서버에는 아무것도 설치할 필요가 없습니다.

2단계: 모든 대상 서버에 대한 SSH 키 접근 설정

ssh-keygen -t ed25519 -C "ansible control"
ssh-copy-id root@10.0.0.10
ssh-copy-id root@10.0.0.20

그런 다음 각 호스트별로 아래 명령을 실행하여 연결을 확인합니다.

ssh root@10.0.0.10 true && echo ok

이 한 줄의 명령은 두 가지 역할을 수행합니다. 비밀번호 없이 키 인증이 정상적으로 작동하는지 확인하고, 해당 호스트 키를 known_hosts에 기록합니다. 이 과정을 지금 수행하십시오. Ansible은 호스트 키가 기록되지 않았을 경우 실행 도중 대화형 프롬프트를 띄우는데, 이 상태가 마치 프로그램이 멈춘 것처럼 보일 수 있기 때문입니다.

3단계: 인벤토리, 처음에는 INI로 시작하고 규모가 커지면 YAML 사용

인벤토리는 Ansible이 접근할 수 있는 서버 목록을 담은 텍스트 파일입니다. 새로운 프로젝트 디렉터리에 inventory.ini 파일을 생성하십시오:

[vps]
web1 ansible_host=10.0.0.10
web2 ansible_host=10.0.0.20

[vps:vars]
ansible_user=root

web1은 사용자가 선택하는 별칭이며, 출력 결과에 표시되고 --limit web1로 대상을 지정할 때 사용됩니다. ansible_host는 실제 주소입니다. [vps]은 그룹이며, [vps:vars]는 해당 그룹의 모든 호스트에 변수를 설정합니다. ansible_user는 Ansible이 로그인할 때 사용하는 계정입니다. 그 옆에는 ansible.cfg을 추가하여 더 이상 -i을 입력할 필요가 없도록 합니다:

[defaults]
inventory = inventory.ini

Ansible은 현재 디렉터리에서 ansible.cfg을 읽어 들입니다. 호스트마다 여러 변수를 관리해야 하는 상황이 오면, 동일한 인벤토리를 YAML 형식으로 작성하여 inventory.yml로 저장하고 ansible.cfg을 통해 해당 파일을 지정하는 방식을 선호하게 될 것입니다:

vps:
  hosts:
    web1:
      ansible_host: 10.0.0.10
    web2:
      ansible_host: 10.0.0.20
  vars:
    ansible_user: root

두 형식은 동일하게 작동합니다. 서버가 2대일 때는 INI 형식이 한눈에 들어오기 쉽고, 20대가 넘어가면 YAML 형식이 관리하기에 더 좋습니다. 하나를 선택한 뒤에는 고민하지 마십시오.

4단계: 임시(ad-hoc) 명령 실행, 모든 것이 정상임을 확인하는 녹색 응답

ansible all -m ping

이것은 ICMP가 아닙니다. ping 모듈은 SSH 로그인, 모듈 복사, 대상 서버에서의 Python 실행, 정리 작업까지 포함하는 전체 예행연습입니다. 올바른 결과는 호스트당 하나의 블록으로 녹색이 표시되는 것입니다.

web1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

녹색 SUCCESS는 인증, Python 인터프리터, 전송 계층이 모두 정상 작동함을 의미하며, 플레이북도 정상적으로 실행될 것임을 나타냅니다. 적색 UNREACHABLE!은 모듈이 실행되기 전에 전송 단계에서 실패했음을 의미합니다. 정확한 오류 메시지와 해결 방법은 아래의 실패 유형 섹션에서 확인할 수 있습니다. 알아두어야 할 두 가지 임시 명령은 다음과 같습니다.

ansible all -a "uptime"
ansible all -m apt -a "update_cache=true upgrade=dist" --become

임시 명령은 일회성 작업이나 확인 용도로 사용합니다. 두 번 이상 실행해야 하는 모든 작업은 플레이북으로 작성해야 합니다.

5단계: 첫 번째 플레이북, 코드로 작성하는 신규 VPS 체크리스트

새 서버를 구축할 때 처음 10분 동안 수동으로 수행해야 하는 모든 작업을 자동화합니다. 이 내용을 site.yml로 저장하십시오.

---
- name: Baseline a fresh Ubuntu VPS
  hosts: vps
  become: true

  vars:
    deploy_user: deploy
    deploy_pubkey: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
    baseline_packages:
      - fail2ban
      - unattended-upgrades
      - ufw
    baseline_services:
      - fail2ban
      - unattended-upgrades

  tasks:
    - name: Create the deploy user
      ansible.builtin.user:
        name: "{{ deploy_user }}"
        groups: sudo
        append: true
        shell: /bin/bash

    - name: Install the deploy user's SSH key
      ansible.posix.authorized_key:
        user: "{{ deploy_user }}"
        key: "{{ deploy_pubkey }}"

    - name: Passwordless sudo for the deploy user
      ansible.builtin.copy:
        dest: /etc/sudoers.d/deploy
        content: "{{ deploy_user }} ALL=(ALL) NOPASSWD:ALL\n"
        mode: "0440"
        validate: /usr/sbin/visudo -cf %s

    - name: Install baseline packages
      ansible.builtin.apt:
        name: "{{ baseline_packages }}"
        state: present
        update_cache: true

    - name: Enable and start baseline services
      ansible.builtin.service:
        name: "{{ item }}"
        state: started
        enabled: true
      loop: "{{ baseline_services }}"

    - name: Harden sshd with a drop-in
      ansible.builtin.copy:
        dest: /etc/ssh/sshd_config.d/00-hardening.conf
        content: |
          PasswordAuthentication no
          KbdInteractiveAuthentication no
          PermitRootLogin prohibit-password
          X11Forwarding no
        mode: "0644"
        validate: /usr/sbin/sshd -t -f %s
      notify: Restart ssh

    - name: Allow OpenSSH through ufw
      community.general.ufw:
        rule: allow
        name: OpenSSH

    - name: Enable ufw with default deny
      community.general.ufw:
        state: enabled
        policy: deny

  handlers:
    - name: Restart ssh
      ansible.builtin.service:
        name: ssh
        state: restarted

단순히 복사하는 것보다 이해하고 넘어가야 할 줄은 다음과 같습니다.

변수vars: 아래에 위치하며 "{{ deploy_user }}"으로 참조합니다. 값이 중괄호로 시작하면 YAML 파서가 오독할 수 있으므로 전체 표현식을 따옴표로 묶어야 합니다. lookup('file', ...)은 런타임에 제어 머신에서 공개 키를 읽어오므로, 플레이북에 키 정보가 포함되지 않습니다.

루프. loop: "{{ baseline_services }}"은 항목마다 서비스 작업을 한 번씩 실행하며, 출력 결과는 각 항목을 별도의 줄에 표시합니다. apt 작업은 패키지 목록 전체를 한 번에 처리한다는 점에 유의하십시오. 한 번의 apt 트랜잭션이 더 빠르며 패키지 설치 시 권장되는 방식입니다. 루프는 한 번에 하나씩 처리해야 하는 모듈에 적합합니다.

핸들러는 반드시 숙지해야 할 개념입니다. notify: Restart ssh는 "지금 바로 ssh를 재시작하라"는 의미가 아닙니다. 핸들러를 큐에 넣고 플레이가 끝날 때 한 번만 실행하며, 알림을 보낸 작업이 실제로 changed을 보고했을 때만 동작합니다. 내일 플레이북을 다시 실행하면 드롭인 파일이 이미 올바르므로 복사 작업은 ok을 보고하고, sshd는 재시작되지 않습니다. validate: 줄은 트리거의 안전장치입니다. sshd는 파일을 교체하기 전에 먼저 검사하므로, 오타가 있으면 데몬이 중단되는 대신 작업이 실패합니다.

no가 아닌 PermitRootLogin prohibit-password을 의도적으로 사용합니다. 이 플레이북은 키를 사용하여 root로 로그인합니다. prohibit-password는 사용자의 로그인은 유지하면서 root의 비밀번호 로그인을 차단합니다. 배포용 사용자가 검증되면(web1은 Ansible만 아는 별칭이므로 실제 주소인 ssh deploy@10.0.0.10 sudo true 사용), 인벤토리에서 ansible_user=deploy로 전환하고 이후 실행에서 no로 설정을 강화하십시오. 서버에서 고립되지 않도록 순차적으로 보안을 강화해야 합니다.

00- 접두사는 중요합니다. 대부분의 키워드에 대해 sshd는 파싱되는 첫 번째 항목을 우선시하며, Ubuntu의 sshd_config은 사전순으로 sshd_config.d/*.conf를 본문보다 먼저 포함합니다. Ubuntu 24.04 클라우드 이미지에는 이미 해당 디렉터리에 60-cloudimg-settings.conf이 포함되어 있고, cloud-init을 통해 비밀번호 로그인을 활성화하는 공급자는 PasswordAuthentication yes가 포함된 50-cloud-init.conf를 추가합니다. 따라서 이름을 00-hardening.conf으로 지정하면 정렬 순서상 가장 먼저 처리되어 두 설정보다 우선하게 됩니다.

작업 순서는 방화벽 안전과 직결됩니다. Allow OpenSSH은 거부 정책을 가진 Enable ufw보다 먼저 실행됩니다. Ansible은 나열된 순서대로 작업을 엄격하게 실행하므로, 방화벽이 올라오기 전에 포트가 먼저 열려 있어야 합니다. fail2ban은 여기서 별도의 설정 없이도 유용하게 작동합니다. Ubuntu 기본 설정은 즉시 sshd를 감시하며, jail의 구체적인 동작과 튜닝 방법은 Ubuntu 24.04의 fail2ban 가이드에서 다룹니다.

6단계: --check 플래그로 드라이 런 수행 후 실제 실행

ansible-playbook site.yml --check

체크 모드는 연결을 수행하고 수행할 작업을 계산하지만, 실제 변경 사항은 적용하지 않습니다. 하단 PLAY RECAP에 표시된 changed= 개수를 확인하십시오. 이는 각 호스트에서 수정될 작업의 수입니다. 한 가지 주의할 점은, 이후 작업이 이전 작업의 변경 사항에 의존하는 경우 체크 모드에는 구조적인 한계가 있다는 것입니다. Ubuntu 표준 서버 이미지에는 ufw가 기본으로 포함되어 있어 이 플레이북은 체크 모드에서 문제없이 실행되지만, ufw가 없는 최소 설치 이미지에서는 패키지가 실제로 설치되지 않아 모듈이 호출할 대상이 없으므로 ufw 작업이 체크 모드에서 실패합니다. 이는 플레이북의 버그가 아니라 드라이 런의 한계입니다. 계획이 올바르게 보이면 다음을 실행하십시오.

ansible-playbook site.yml

각 작업은 호스트당 한 줄씩 출력하며, 노란색 changed, 녹색 ok로 표시됩니다. 요약 결과는 다음과 같아야 합니다.

PLAY RECAP *********************************************************************
web1 : ok=10  changed=9  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2 : ok=10  changed=9  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

10개의 ok은 팩트 수집, 8개의 작업, 그리고 핸들러로 구성됩니다. 사용자의 changed는 제 결과와 1~2개 정도 차이가 날 수 있습니다. Ubuntu 표준 이미지에는 ufwunattended-upgrades이 기본으로 포함되어 있으며, fail2ban은 apt로 설치되는 즉시 스스로 시작되므로 첫 실행 시 작업이 ok을 보고하는 것은 정상입니다. 이는 선언된 상태가 이미 유지되고 있음을 의미합니다. 반드시 0이어야 하는 수치는 unreachablefailed입니다. become: true에 관한 참고 사항입니다. root로 접속하는 동안에는 형식적인 절차이지만, ansible_userdeploy로 변경하는 순간 sudo는 실제 권한이 되며, 이 플레이북이 설치하는 NOPASSWD sudoers 파일은 -K을 명령줄에서 배제하는 핵심 요소입니다. 이 설정이 없으면 아래에서 다룰 Missing sudo password 오류가 발생합니다.

7단계: 두 번 실행하기, 멱등성이란 무엇인가

즉시 동일한 명령을 다시 실행합니다.

web1 : ok=9  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

changed=0가 발생하며, 알림을 받지 않은 핸들러는 실행되지 않았으므로 ok은 1만큼 감소합니다. 아무것도 재설치되지 않았고, sshd는 재시작되지 않았으며, ufw는 변경되지 않았습니다. 이것이 바로 플레이북이 프로비저닝 도구인 동시에 감사 도구로 기능하는 이유입니다. 다음 달 인벤토리에 web3을 추가하고 다시 실행하면, 새로운 서버는 구축되고 기존 서버들은 검증됩니다. 건드리지 않은 서버에서 0이 아닌 changed이 발생했다면 이는 설정 드리프트(drift)를 의미하며, 누군가 플레이북으로 관리해야 할 설정을 수동으로 수정했음을 알려줍니다.

이 지점부터 패턴이 복합적으로 적용됩니다. 다음에 작성할 가치가 있는 플레이북은 동일한 VPS에 WireGuard VPN을 구성하고 ufw 규칙을 강화하여 SSH가 터널을 통해서만 응답하도록 하는 것입니다. 그 후에는 모든 애플리케이션 서버에 Docker 및 Compose를 설치하는 플레이북을 작성합니다. site.yml의 내용이 화면 세 개 분량을 넘어가면 그때 역할을(roles) 분리하십시오. 그전에는 분리할 필요가 없습니다.

실패 유형 및 확인되는 메시지

UNREACHABLE with Permission denied.

web1 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: root@10.0.0.10: Permission denied (publickey).",
    "unreachable": true
}

모듈이 실행되기 전에 SSH 전송 단계에서 실패한 경우입니다. ansible_user 설정이 잘못되었거나, 해당 호스트에 키가 복사되지 않았거나, 잘못된 키가 제공되고 있습니다. 일반 ssh root@10.0.0.10 명령으로 재현한 뒤 ssh -v 명령을 사용하여 어떤 키가 제공되는지 확인하십시오. 비밀번호를 이용한 SSH 접속은 성공하지만 Ansible만 실패한다면 ssh-copy-id 단계를 건너뛴 것입니다.

Missing sudo password.

web1 | FAILED! => {
    "msg": "Missing sudo password"
}

become: true 설정을 사용하고 비루트(non-root) 사용자로 접속했으나, 해당 사용자가 sudo를 위해 비밀번호를 요구하는 경우입니다. 명령행에 -K (--ask-become-pass) 옵션을 추가하거나, 해당 사용자에게 NOPASSWD가 설정된 sudoers 항목을 부여하십시오. 플레이북이 사용자를 전환하기 전에 deploy 사용자를 위해 NOPASSWD 설정을 수행하는 이유가 바로 이것입니다.

error: externally-managed-environment. Ubuntu 24.04의 시스템 Python에 대해 pip를 실행한 경우입니다. 1단계에서 다룬 내용과 같이 pip가 아닌 pipx를 사용해야 하며, --break-system-packages 옵션도 사용해서는 안 됩니다.

mapping values are not allowed in this context.

ERROR! Syntax Error while loading YAML.
  mapping values are not allowed in this context

거의 항상 들여쓰기 문제입니다. 키의 깊이가 잘못되었거나 콜론 뒤에 공백이 누락된 경우입니다. 보고된 줄 번호는 오류 지점 '근처'를 가리키므로, 바로 윗줄도 함께 확인하십시오. 이와 유사한 found character '\t' that cannot start any token 오류는 탭 문자가 포함되었음을 의미합니다. YAML은 탭 사용을 금지합니다. 실행 전 항상 ansible-playbook site.yml --syntax-check 명령을 습관화하고, 편집기의 YAML 들여쓰기를 공백 2칸으로 설정하십시오.

/usr/bin/python3: not found. 표준 Ubuntu 24.04 이미지에서는 드물지만, 최소 설치(minimal) 또는 네트워크 부팅(netboot) 이미지에서는 흔히 발생합니다. 대상 시스템에 Python이 없어 모듈 실행이 실패하는 경우입니다. 원격지에 아무것도 필요로 하지 않는 유일한 모듈인 raw 모듈을 사용하여 ansible all -m raw -a "apt-get update && apt-get install -y python3" --become 명령으로 부트스트랩을 수행한 뒤, 플레이북을 다시 실행하십시오.

FAQ

관리 대상 서버에 Ansible을 설치해야 합니까?

아니요. Ansible은 에이전트가 필요 없습니다. 제어 노드가 SSH를 통해 작은 Python 모듈을 전송하여 실행한 뒤 삭제하는 방식입니다. 대상 서버에는 python3과 SSH 접근 권한만 있으면 되며, 기본 Ubuntu 이미지에는 이미 이 두 가지가 포함되어 있습니다. 이 가이드에서 수행하는 모든 설치 과정은 제어 노드에서만 진행됩니다.

Ansible에서 "Permission denied (publickey)" 오류가 발생하는 이유는 무엇입니까?

Permission denied (publickey)가 포함된 UNREACHABLE! 블록은 Ansible이 작업을 시작하기 전에 SSH 인증이 실패했음을 의미합니다. 인벤토리의 ansible_user이 실제로 설정한 계정과 일치하는지, 해당 호스트로 ssh-copy-id을 실행했는지, 그리고 일반 ssh user@host 명령으로 비밀번호 없이 로그인되는지 확인하십시오. 일반 ssh 명령을 수정하면 Ansible 문제도 해결됩니다. 두 도구 모두 동일한 전송 계층을 사용하기 때문입니다.

Ansible에서 멱등성(idempotency)이란 무엇입니까?

작업은 수행할 동작이 아니라 "이 패키지가 설치되어 있어야 함", "이 파일에 이 줄이 포함되어야 함"과 같은 원하는 상태를 선언합니다. 이미 해당 상태라면 Ansible은 아무 작업도 하지 않고 changed 대신 ok를 보고합니다. 이것이 플레이북을 두 번 실행했을 때 두 번째에는 changed=0이 표시되는 이유이며, 재실행이 위험한 재설치가 아닌 안전한 감사 작업이 되는 이유입니다.

Ubuntu 24.04에서 Ansible을 설치할 때 pip와 pipx 중 무엇을 사용해야 합니까?

pipx를 사용하십시오. Ubuntu 24.04는 시스템 Python을 외부 관리형으로 지정하므로, 설계상 pip install ansible를 실행하면 error: externally-managed-environment 오류가 발생합니다. pipx install --include-deps ansible는 Ansible을 격리된 virtualenv에 설치하고 ansible, ansible-playbook 및 기타 실행 파일을 PATH에 깔끔하게 노출합니다.

ansible 패키지와 ansible-core 패키지의 차이점은 무엇입니까?

ansible-core은 엔진과 ansible.builtin 모듈만 포함합니다. ansible 패키지는 core에 선별된 커뮤니티 컬렉션을 묶은 것으로, 이 가이드에서 사용하는 ansible.posix(authorized_key 모듈)과 community.general(ufw 모듈)가 포함되어 있습니다. 전체 패키지로 시작하고, 명확한 이유가 있을 때만 core와 필요한 컬렉션만 선택하여 경량화하십시오.