🔧

Ansible 실습 환경 구성과 첫 Playbook

최민석·2026-03-10

Ansible 실습 환경 구성과 첫 Playbook

🔧 Ansible 실습 시리즈

Ansible은 에이전트 없이 SSH만으로 서버를 관리하는 자동화 도구입니다. 이번 포스팅에서는 Docker 기반 실습 환경을 구성하고, 인벤토리와 설정 파일의 역할을 이해한 뒤, 첫 Playbook으로 nginx를 설치해봅니다.


실습 환경 아키텍처

Docker Compose로 control 노드 1대 + managed 노드 3대를 구성합니다.

Ansible은 에이전트리스(agentless) 구조입니다. managed 노드에 별도 소프트웨어를 설치하지 않고, control 노드에서 SSH로 접속하여 작업을 수행합니다.

쿠버네티스에서 kubelet이 각 노드에 설치되어야 하는 것과 달리, Ansible은 SSH 접속만 가능하면 됩니다.

컨테이너 역할 베이스 이미지
ansible-control Ansible 명령 실행 (control 노드) Python 3.12-slim + ansible-core
node1 ~ node3 관리 대상 (managed 노드) Ubuntu 24.04 + SSH 서버

4개 컨테이너는 ansible-net 브릿지 네트워크로 연결되며, control 노드에서 managed 노드로 SSH(user: ansible, password: ansible)로 접속합니다.

docker-compose.yml

services:
  ansible-control:
    build:
      context: ./control
    container_name: ansible-control
    tty: true
    stdin_open: true
    volumes:
      - ./ansible:/workspace/ansible
    working_dir: /workspace/ansible
    networks:
      - ansible-net

  node1:
    build:
      context: ./managed
    container_name: node1
    hostname: node1
    volumes:
      - ./managed/sshd_config:/etc/ssh/sshd_config:ro
    networks:
      - ansible-net

  # node2, node3도 동일 구조

핵심 포인트:

  • ./ansible 디렉토리가 control 컨테이너의 /workspace/ansible볼륨 마운트됩니다. 호스트에서 파일을 편집하면 컨테이너에 즉시 반영되고, 그 반대도 마찬가지입니다.
  • managed 노드의 sshd_config도 볼륨 마운트(:ro)하여, SSH 설정 변경 시 이미지 재빌드 없이 컨테이너 재시작만으로 반영됩니다.

managed 노드의 Dockerfile

FROM ubuntu:24.04

RUN apt-get update && apt-get install -y --no-install-recommends \
    openssh-server python3 sudo iputils-ping vim \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir /var/run/sshd

# 실습용 사용자 생성
RUN useradd -m -s /bin/bash ansible \
    && echo 'ansible:ansible' | chpasswd \
    && usermod -aG sudo ansible \
    && echo 'ansible ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

COPY sshd_config /etc/ssh/sshd_config
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 22
CMD ["/entrypoint.sh"]
  • ansible 유저를 생성하고, NOPASSWD sudo 권한을 부여합니다. 이후 Ansible의 become(권한 상승) 실습에 사용됩니다.
  • entrypoint.sh에서 SSH host key를 생성하고 sshd를 포그라운드로 실행합니다.

환경 실행

docker compose up -d --build
docker exec -it ansible-control bash

ansible.cfg — Ansible 설정 파일

[defaults]
inventory = ./inventory.ini
host_key_checking = False
interpreter_python = auto_silent
stdout_callback = default
result_format = yaml

[ssh_connection]
pipelining = True

ansible.cfg는 INI 형식으로, 섹션 이름은 Ansible이 정한 고정 키워드입니다.

섹션 용도
[defaults] 일반 설정 (인벤토리 경로, 출력 형식 등)
[ssh_connection] SSH 연결 관련 설정
[privilege_escalation] become(sudo) 관련 설정

주요 설정:

  • inventory — 인벤토리 파일 경로 (상대 경로는 ansible.cfg 기준)
  • host_key_checking = False — SSH 첫 접속 시 fingerprint 확인을 생략
  • pipelining = True — SSH 연결 시 임시 파일 대신 파이프로 모듈을 전송하여 성능 향상

ansible.cfg 탐색 순서

Ansible은 다음 순서로 설정 파일을 찾습니다:

  1. ANSIBLE_CONFIG 환경변수
  2. 현재 디렉토리ansible.cfg
  3. ~/.ansible.cfg
  4. /etc/ansible/ansible.cfg

따라서 playbook 실행 시 ansible.cfg가 있는 디렉토리에서 실행하는 것이 편합니다.


인벤토리 — 관리 대상 정의

인벤토리는 Ansible이 관리할 대상 호스트 목록입니다. "어떤 서버에, 어떻게 접속할 것인가"를 정의합니다.

[managed]
node1 ansible_host=node1
node2 ansible_host=node2
node3 ansible_host=node3

[managed:vars]
ansible_connection=ssh
ansible_user=ansible
ansible_password=ansible

인벤토리 문법

인벤토리도 INI 형식이지만, ansible.cfg와는 섹션 이름 규칙이 다릅니다.

파일 섹션 이름 규칙
ansible.cfg [defaults], [ssh_connection] Ansible이 정한 고정 이름
inventory.ini [managed], [web] 사용자가 자유롭게 지정
inventory.ini [managed:vars] 그룹명:접미사 형식, 접미사(vars, children)는 고정

그룹 변수 ([그룹명:vars])

그룹 내 호스트에 공통 적용할 변수를 한 곳에서 관리합니다. 호스트가 많아질수록 효과가 큽니다. 비밀번호를 바꿀 때도 한 곳만 수정하면 됩니다.

인벤토리 활용 — ad-hoc 명령

인벤토리가 정의되면 ad-hoc 명령으로 바로 테스트할 수 있습니다. ad-hoc 명령은 playbook 파일 없이 CLI에서 모듈을 직접 실행하는 방식입니다.

# ad-hoc 명령 형식: ansible <대상> -m <모듈> [-a <인자>]
ansible managed -m ping       # managed 그룹 전체
ansible node1 -m ping         # node1에만
ansible all -m ping           # 모든 호스트

쿠버네티스로 비유하면, ad-hoc은 kubectl run --rm -it으로 일회성 작업을 하는 것이고, playbook은 매니페스트 YAML을 kubectl apply하는 것에 해당합니다.

💡 -m 옵션과 ping

-mmodule의 약자로, 실행할 모듈을 지정하는 옵션입니다. ping은 예약어가 아니라 ansible.builtin.ping 모듈의 축약 이름입니다. ad-hoc 명령에서는 FQCN 없이 모듈명만 써도 동작합니다.

ansible managed -m ping              # 축약 이름
ansible managed -m ansible.builtin.ping  # FQCN (동일한 동작)
ansible managed -m shell -a "whoami"     # shell 모듈에 인자 전달
ansible managed -m apt -a "name=nginx state=present" --become  # apt 모듈

-m을 생략하면 기본 모듈인 command가 사용됩니다.


Playbook — 작업 자동화

ad-hoc vs playbook

ad-hoc 명령은 한 번에 하나씩 실행하지만, playbook은 여러 작업을 파일에 묶어 순서대로 실행합니다.

ad-hoc playbook
용도 간단한 일회성 작업 반복 가능한 자동화
형식 CLI 명령어 YAML 파일
작업 수 1개 여러 개를 순서대로

첫 Playbook — ping.yml

---
- name: First Ansible connectivity test
  hosts: managed
  gather_facts: false

  tasks:
    - name: Ping managed nodes
      ansible.builtin.ping:

각 라인의 역할:

  • --- — YAML 문서 시작 표시
  • name — play의 설명
  • hosts — 대상 그룹 (inventory의 [managed])
  • gather_facts — 대상 서버의 OS/네트워크 등 시스템 정보 수집 여부. false로 하면 수집을 건너뛰어 실행이 빨라짐
  • tasks — 실행할 task 목록
  • ansible.builtin.ping: — 사용할 모듈의 FQCN(Fully Qualified Collection Name)

참고: ansible.builtin.ping은 네트워크 ICMP ping이 아니라, SSH 접속 후 Python이 정상 동작하는지 확인하는 Ansible 전용 모듈입니다.

FQCN이란?

ansible.builtin.ping에서 ansible.builtin은 내장 컬렉션, ping은 모듈명입니다. 그냥 ping:으로도 쓸 수 있지만, 서드파티 컬렉션에 동일한 이름의 모듈이 있을 수 있으므로 FQCN이 권장됩니다.

쿠버네티스에서 다른 네임스페이스의 서비스를 호출할 때 서비스명.네임스페이스.svc.cluster.local FQDN을 쓰는 것과 같은 이유입니다.

Playbook의 선언형과 절차형

Ansible은 모듈 레벨에서는 선언형, playbook 레벨에서는 절차형입니다.

# 선언형: "nginx가 설치되어 있는 상태"를 선언
- ansible.builtin.apt:
    name: nginx
    state: present    # 이미 설치되어 있으면 아무것도 안 함

state: present처럼 원하는 상태를 선언하면, 모듈이 알아서 현재 상태를 확인하고 필요한 경우에만 변경합니다. 이것을 **멱등성(idempotency)**이라고 합니다 — 몇 번을 실행해도 결과가 같습니다.

반면 task는 위에서 아래로 순서대로 실행됩니다. 쿠버네티스는 컨트롤러가 알아서 원하는 상태로 수렴시키지만, Ansible은 작업 순서를 사용자가 직접 제어합니다.

Ansible Kubernetes
개별 작업 선언형 (state: present) 선언형 (replicas: 3)
전체 흐름 절차형 (순서대로 실행) 선언형 (컨트롤러가 수렴)
지속성 실행 시점에만 적용 컨트롤러가 상태를 계속 유지

모듈 문서 확인 — ansible-doc

ansible-doc ansible.builtin.apt       # apt 모듈의 파라미터 확인
ansible-doc ansible.builtin.service   # service 모듈의 파라미터 확인
ansible-doc -l                        # 전체 모듈 목록

쿠버네티스의 kubectl explain과 비슷한 역할입니다. 다만 kubectl explain pod.spec.containers처럼 계층적 탐색은 지원하지 않고, 모듈 단위로만 조회할 수 있습니다.


nginx 설치 Playbook

실행 전 상태 확인

root@control:/workspace/ansible# ansible managed -m shell -a "which nginx || echo 'nginx not found'"
node1 | CHANGED | rc=0 >> nginx not found
node2 | CHANGED | rc=0 >> nginx not found
node3 | CHANGED | rc=0 >> nginx not found

참고: shell 모듈은 명령이 성공하면 항상 CHANGED를 반환합니다. 실제 변경 여부를 판단할 수 없기 때문입니다.

playbook/nginx.yml

---
- name: Ansible nginx install & execute
  hosts: managed
  become: true
  gather_facts: false

  tasks:
    - name: apt install nginx
      ansible.builtin.apt:
        name: nginx
        state: present
    - name: nginx service start
      ansible.builtin.service:
        name: nginx
        state: started

💡 become: true란?

Ansible은 inventory에 정의된 유저(여기서는 ansible)로 SSH 접속합니다. 하지만 패키지 설치나 서비스 관리는 root 권한이 필요합니다. become: true를 설정하면 내부적으로 sudo를 실행하여 권한을 상승시킵니다.

쿠버네티스의 securityContext.runAsUser: 0으로 root 권한을 부여하는 것과 비슷한 개념입니다.

  • play 레벨에 설정하면 해당 play의 모든 task에 적용
  • task 레벨에 설정하면 해당 task에만 적용
tasks:
  - name: sudo 필요한 작업
    ansible.builtin.apt:
      name: nginx
      state: present
    become: true           # 이 task만 sudo

  - name: sudo 불필요한 작업
    ansible.builtin.ping:  # 일반 유저로 실행

현재 managed 노드의 ansible 유저에 NOPASSWD:ALL sudo 권한이 있으므로, 비밀번호 입력 없이 become을 사용할 수 있습니다.

  • ansible.builtin.aptname에 패키지명, state: present로 설치 상태를 선언
  • ansible.builtin.servicestate: started로 서비스 실행 상태를 선언

실행 결과

PLAY [Ansible nginx install & execute] **********************

TASK [apt install nginx] ************************************
changed: [node1]
changed: [node3]
changed: [node2]

TASK [nginx service start] **********************************
changed: [node3]
changed: [node2]
changed: [node1]

PLAY RECAP **************************************************
node1 : ok=2  changed=2  unreachable=0  failed=0  skipped=0
node2 : ok=2  changed=2  unreachable=0  failed=0  skipped=0
node3 : ok=2  changed=2  unreachable=0  failed=0  skipped=0
  • ok=2 — 2개 task 모두 성공
  • changed=2 — 서버 상태가 실제로 변경됨 (nginx 설치 + 서비스 시작)

실행 후 확인

root@control:/workspace/ansible# ansible managed -m shell -a "which nginx || echo 'nginx not found'"
node1 | CHANGED | rc=0 >> /usr/sbin/nginx
node2 | CHANGED | rc=0 >> /usr/sbin/nginx
node3 | CHANGED | rc=0 >> /usr/sbin/nginx

3대 모두 nginx가 설치되었습니다.

💡 멱등성 확인: 같은 playbook을 한 번 더 실행하면, 이미 nginx가 설치되고 실행 중이므로 ok=2, changed=0이 나옵니다. 선언형 모듈이 현재 상태를 확인하고, 이미 원하는 상태이면 변경하지 않기 때문입니다.

문법 검사

playbook을 실행하기 전에 문법을 검사할 수 있습니다:

ansible-playbook playbook/nginx.yml --syntax-check   # 문법 검사
ansible-playbook playbook/nginx.yml --check           # dry-run (실제 변경 없음)

--check는 쿠버네티스의 kubectl apply --dry-run=server와 비슷합니다.