---
title: "Ansible로 노드 자동화를 완벽하게 마스터하는 방법: 설치부터 첫 Playbook 실행까지"
description: "Ansible은 에이전트리스 IT 자동화 도구로, SSH와 Python을 기반으로 하여 서버 설정 및 애플리케이션 배포를 관리한다. Ubuntu에서 Ansible을 설치하는 방법, Inventory 파일 구성, Ad-hoc 명령어 사용법, 그리고 첫 번째 Playbook 작성 및 실행 방법을 다룬다. 멱등성을 강조하며, SSH 키 설정과 보안 주의사항도 포함되어 있다."
date: "2025-04-03"
last_modified: "2026-05-15T08:36:00.000Z"
type: "Post"
tags:
  - "Docs"
  - "Automation"
  - "DevOps"
  - "ssh"
  - "install"
categories:
  - "📗 Docs"
canonical_url: "https://blog.pieroot.xyz/ansible-node-master"
markdown_url: "https://blog.pieroot.xyz/ansible-node-master.md"
---

# Ansible로 노드 자동화를 완벽하게 마스터하는 방법: 설치부터 첫 Playbook 실행까지

Ansible은 에이전트리스 IT 자동화 도구로, SSH와 Python을 기반으로 하여 서버 설정 및 애플리케이션 배포를 관리한다. Ubuntu에서 Ansible을 설치하는 방법, Inventory 파일 구성, Ad-hoc 명령어 사용법, 그리고 첫 번째 Playbook 작성 및 실행 방법을 다룬다. 멱등성을 강조하며, SSH 키 설정과 보안 주의사항도 포함되어 있다.

노드 확장을 할 때 너무나도 기본 세팅이 하기 귀찮다. 네트워크 스펙을 NIC를 자동 감지하여 세팅하지 못하더라도 OVS만 연결하면 되기에 일단 Ansible 구성을 해보려고 한다.

이번 글에서는 **Ansible을 설치하고, 기본적인 사용법을 익혀보는 과정**을 정리한다. Inventory 구성부터 Ad-hoc 명령어, 그리고 첫 번째 Playbook까지 다뤄본다.

> **이 글에서 다루는 내용**
> 
> - Ansible이란 무엇인가, 왜 쓰는가
> 
> - Ubuntu에서 Ansible 설치 (apt)
> 
> - Inventory 파일 구성
> 
> - Ad-hoc 명령어로 간단한 작업 실행
> 
> - 첫 번째 Playbook 작성 및 실행
> 
> - 실전 팁과 주의사항

---

### 🤔 Ansible이란?

Ansible은 **에이전트리스(agentless) IT 자동화 도구**다. 서버 설정, 애플리케이션 배포, 오케스트레이션 등을 코드로 관리할 수 있게 해준다.

핵심은 **SSH와 Python으로 동작**한다는 점이다. 그 말은 즉, 최신 Ubuntu 서버뿐만 아니라 대부분의 Unix 서버들은 기본 세팅만으로도 사용이 가능하다는 것이다. 대상 서버에 별도의 에이전트를 설치할 필요가 없으니 관리 포인트가 확 줄어든다.

> **Ansible의 핵심 구성 요소**
> 
> - **Control Node**: Ansible이 설치된 관리 서버. 여기서 명령을 내린다
> 
> - **Managed Node**: Ansible이 관리하는 대상 서버. SSH만 열려있으면 된다
> 
> - **Inventory**: 관리 대상 서버 목록. IP나 호스트명을 그룹별로 정리한 파일
> 
> - **Playbook**: 자동화 작업을 정의한 YAML 파일. "어떤 서버에 무슨 작업을 할지" 선언한다
> 
> - **Module**: 실제 작업을 수행하는 단위. `apt`, `copy`, `service` 등 수백 개가 내장되어 있다

~~사실 노드 3대 초기 세팅하는 데 Ansible까지 필요한가 싶긴 하지만, 한 번 만들어두면 다음번에는 명령어 한 줄이면 끝이니까.~~

---

### 📦 Ansible 설치 (Ubuntu)

Python으로 동작하기에 `pip`로 설치를 진행해도 괜찮지만, 어느 정도 서버를 사용해본 유저라면 **시스템의 Python은 가능하면 건드리지 않는 게 좋다**는 걸 알 것이다. `pip`로 시스템 Python에 이것저것 설치하다가 의존성 꼬이면... ~~그날 하루가 통째로 날아간다 😭~~

Ubuntu로 설치할 것이기에 **apt 패키지**를 이용해 진행하겠다.

#### 1단계: 시스템 업데이트

```shell
sudo apt update
```

이 명령어는 마치 시스템을 설정할 때 의식을 치루는 것과 같다. 마음을 경건하게 하는 과정과 같다 이말이다.

#### 2단계: PPA 추가 및 설치

```shell
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
```

> **왜 PPA를 추가하는가?**
> 
> Ubuntu 기본 저장소에도 Ansible이 있지만, **버전이 꽤 오래된 경우**가 많다. Ansible 공식 PPA를 추가하면 최신 안정 버전을 받을 수 있다. 특히 최신 모듈이나 기능이 필요한 경우 PPA 사용을 강력 권장한다.

> **`ansible`**** vs ****`ansible-core`**** 차이**
> 
> - `ansible-core`: 최소한의 런타임 + 내장 모듈/플러그인만 포함. 가벼움
> 
> - `ansible`: "batteries included" 패키지. 커뮤니티가 선별한 다양한 Collection이 포함되어 있어 바로 사용하기 편함
> 
> 처음 시작한다면 `ansible` 패키지를 설치하는 게 편하다. 나중에 필요한 Collection만 골라서 쓰고 싶다면 `ansible-core`로 갈아타면 된다.

#### 3단계: 설치 확인

```shell
ansible --version
```

```shell
ansible [core 2.17.x]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ...
```

이렇게 버전 정보가 출력되면 설치 완료다. ✅

---

### 📋 Inventory 구성

Ansible은 **어떤 서버에 작업을 할 것인지**를 Inventory 파일에서 읽어온다. 설치하면 기본적으로 `/etc/ansible/hosts` 파일이 생성되지만, 프로젝트별로 따로 관리하는 게 훨씬 깔끔하다.

#### 기본 Inventory 파일 (INI 형식)

프로젝트 디렉토리를 만들고 Inventory 파일을 작성한다:

```shell
mkdir ~/ansible-lab && cd ~/ansible-lab
```

```
# inventory.ini

[control]
controller ansible_host=172.30.0.11

[compute]
compute01 ansible_host=172.30.0.21
compute02 ansible_host=172.30.0.22
compute03 ansible_host=172.30.0.23

[storage]
storage01 ansible_host=172.30.2.101
storage02 ansible_host=172.30.2.102
storage03 ansible_host=172.30.2.103

[all:vars]
ansible_user=root
ansible_python_interpreter=/usr/bin/python3
```

> **Inventory 파일 해설**
> 
> - `[compute]`, `[storage]`: 호스트 그룹. 그룹 단위로 작업을 실행할 수 있다
> 
> - `ansible_host`: 실제 접속할 IP 주소
> 
> - `[all:vars]`: 모든 호스트에 적용되는 변수. `ansible_user`는 SSH 접속 유저, `ansible_python_interpreter`는 대상 서버의 Python 경로

#### YAML 형식 Inventory

YAML이 더 익숙하다면 이렇게도 작성할 수 있다:

```yaml
# inventory.yml
all:
  vars:
    ansible_user: root
    ansible_python_interpreter: /usr/bin/python3
  children:
    compute:
      hosts:
        compute01:
          ansible_host: 172.30.0.21
        compute02:
          ansible_host: 172.30.0.22
        compute03:
          ansible_host: 172.30.0.23
    storage:
      hosts:
        storage01:
          ansible_host: 172.30.2.101
        storage02:
          ansible_host: 172.30.2.102
        storage03:
          ansible_host: 172.30.2.103
```

두 형식 모두 동작은 동일하니 취향에 맞게 선택하면 된다.

#### Inventory 확인

작성한 Inventory가 제대로 파싱되는지 확인한다:

```shell
# 호스트 목록 확인
ansible-inventory -i inventory.ini --list

# 그래프 형태로 확인 (그룹 구조 파악에 유용)
ansible-inventory -i inventory.ini --graph
```

```shell
@all:
  |--@control:
  |  |--controller
  |--@compute:
  |  |--compute01
  |  |--compute02
  |  |--compute03
  |--@storage:
  |  |--storage01
  |  |--storage02
  |  |--storage03
  |--@ungrouped:
```

---

### 🔑 SSH 키 설정

Ansible은 SSH로 통신하기 때문에, **비밀번호 없이 접속할 수 있도록 SSH 키를 배포**해야 한다. 이미 키가 있다면 이 단계는 건너뛰어도 좋다.

```shell
# SSH 키 생성 (이미 있으면 스킵)
ssh-keygen -t ed25519 -C "ansible-control"

# 대상 서버에 키 배포
ssh-copy-id root@172.30.0.21
ssh-copy-id root@172.30.0.22
ssh-copy-id root@172.30.0.23
```

> **비밀번호 방식으로도 가능하지만...**
> 
> `--ask-pass` (`-k`) 옵션을 사용하면 비밀번호 입력 방식으로도 접속할 수 있다. 하지만 서버가 10대, 20대 늘어나면 매번 비밀번호 입력하는 건 자동화의 의미가 없어진다. SSH 키 한 번 세팅해두면 편하다.

#### 연결 테스트

SSH 키 배포가 끝났으면 Ansible로 연결을 테스트한다:

```shell
ansible all -i inventory.ini -m ping
```

```shell
compute01 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
compute02 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
compute03 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
```

모든 호스트에서 `pong`이 돌아오면 성공이다! 🎉

> **`ping`**** 모듈은 ICMP ping이 아니다!**
> 
> Ansible의 `ping` 모듈은 실제로 SSH 접속 → Python 실행 → 응답 반환의 과정을 거친다. 즉, SSH 연결과 Python 환경이 모두 정상인지 한 번에 확인할 수 있는 모듈이다.

---

### ⚡ Ad-hoc 명령어

Playbook까지 작성하기엔 좀 과한, **간단한 일회성 작업**은 Ad-hoc 명령어로 처리할 수 있다.

#### 기본 문법

```shell
ansible <대상> -i <inventory> -m <모듈> -a "<인자>"
```

#### 자주 쓰는 예시들

```shell
# 모든 compute 노드의 업타임 확인
ansible compute -i inventory.ini -m command -a "uptime"

# 모든 서버의 디스크 용량 확인
ansible all -i inventory.ini -m command -a "df -h"

# compute 그룹에 패키지 설치
ansible compute -i inventory.ini -m apt -a "name=htop state=present" --become

# 특정 서비스 재시작
ansible compute -i inventory.ini -m service -a "name=ssh state=restarted" --become

# 파일 복사
ansible all -i inventory.ini -m copy -a "src=./config.txt dest=/tmp/config.txt"
```

> **자주 쓰는 모듈 정리**

> **`command`**** vs ****`shell`**** 차이**
> 
> - `command`: 단순 명령어 실행. 파이프(`|`), 리다이렉션(`>`) 등을 **사용할 수 없다**
> 
> - `shell`: `/bin/sh`를 통해 실행. 파이프, 리다이렉션, 환경변수 등 **모두 사용 가능**
> 
> 보안상 `command`가 더 안전하므로, 파이프가 필요 없다면 `command`를 쓰자.

---

### 📝 첫 번째 Playbook 작성

Ad-hoc으로 해결할 수 없는 **복잡한 작업의 조합**은 Playbook으로 정의한다. Playbook은 YAML 형식으로 작성하며, "어떤 호스트에, 어떤 순서로, 무슨 작업을 할지"를 선언한다.

#### 기본 구조

```yaml
# setup-base.yml
---
- name: 기본 서버 세팅
  hosts: compute
  become: yes

  tasks:
    - name: apt 캐시 업데이트
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: 기본 패키지 설치
      apt:
        name:
          - vim
          - htop
          - curl
          - wget
          - net-tools
          - git
        state: present

    - name: 타임존 설정
      timezone:
        name: Asia/Seoul

    - name: NTP 동기화 활성화
      service:
        name: systemd-timesyncd
        state: started
        enabled: yes
```

> **Playbook 구조 해설**
> 
> - `name`: 이 Play의 이름. 실행 시 로그에 표시된다
> 
> - `hosts`: 작업 대상 그룹. Inventory의 그룹명을 지정
> 
> - `become: yes`: root 권한으로 실행 (= `sudo`)
> 
> - `tasks`: 실행할 작업 목록. **위에서 아래 순서대로** 실행된다
> 
> - 각 task의 `name`은 실행 로그에 표시되므로, 알아보기 쉽게 작성하는 게 좋다

#### Playbook 실행

```shell
# 문법 검사 (실행 전 반드시!)
ansible-playbook -i inventory.ini setup-base.yml --syntax-check

# 드라이 런 (실제 변경 없이 시뮬레이션)
ansible-playbook -i inventory.ini setup-base.yml --check

# 실제 실행
ansible-playbook -i inventory.ini setup-base.yml
```

```shell
PLAY [기본 서버 세팅] ***********************************

TASK [Gathering Facts] *********************************
ok: [compute01]
ok: [compute02]
ok: [compute03]

TASK [apt 캐시 업데이트] *********************************
changed: [compute01]
changed: [compute02]
changed: [compute03]

TASK [기본 패키지 설치] **********************************
changed: [compute01]
changed: [compute02]
changed: [compute03]

TASK [타임존 설정] **************************************
changed: [compute01]
changed: [compute02]
changed: [compute03]

TASK [NTP 동기화 활성화] *********************************
ok: [compute01]
ok: [compute02]
ok: [compute03]

PLAY RECAP ********************************************
compute01   : ok=5  changed=3  unreachable=0  failed=0
compute02   : ok=5  changed=3  unreachable=0  failed=0
compute03   : ok=5  changed=3  unreachable=0  failed=0
```

`failed=0`이면 모든 작업이 성공한 것이다! ✅

> **`--check`**** 옵션은 꼭 사용하자!**
> 
> 실제 실행 전에 `--check`(드라이 런)를 돌리면 **어떤 변경이 일어날지 미리 확인**할 수 있다. 프로덕션 서버에서 Playbook을 바로 실행했다가 예상치 못한 변경이 발생하면... ~~그날은 일찍 퇴근 못 한다~~

---

### 🚀 실전 예제: OVS 설치 Playbook

실제로 내가 노드 확장할 때 사용하려는 OVS(Open vSwitch) 설치 Playbook을 작성해보자.

```yaml
# setup-ovs.yml
---
- name: OVS 설치 및 기본 브릿지 구성
  hosts: compute
  become: yes

  vars:
    ovs_bridge_name: br-int

  tasks:
    - name: OVS 패키지 설치
      apt:
        name:
          - openvswitch-switch
        state: present
        update_cache: yes

    - name: OVS 서비스 시작 및 활성화
      service:
        name: openvswitch-switch
        state: started
        enabled: yes

    - name: OVS 브릿지 생성
      command: ovs-vsctl --may-exist add-br  ovs_bridge_name 

    - name: OVS 브릿지 확인
      command: ovs-vsctl show
      register: ovs_result

    - name: OVS 상태 출력
      debug:
        var: ovs_result.stdout_lines
```

> **새로운 개념들**
> 
> - `vars`: Playbook 내에서 사용할 변수를 정의. ` 변수명 ` 형태로 참조한다
> 
> - `register`: 명령어 실행 결과를 변수에 저장
> 
> - `debug`: 변수 내용을 콘솔에 출력. 디버깅할 때 매우 유용하다

---

### 🔧 유용한 팁들

#### ansible.cfg 설정

매번 `-i inventory.ini`를 입력하기 귀찮다면, 프로젝트 루트에 `ansible.cfg`를 만들자:

```
# ansible.cfg
[defaults]
inventory = inventory.ini
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
```

> **`host_key_checking = False`**** 주의!**
> 
> 이 설정은 SSH 접속 시 호스트 키 검증을 건너뛴다. **신뢰할 수 있는 내부 네트워크에서만** 사용하자. 공개 네트워크에서는 보안 위험이 있다.

#### 멱등성(Idempotency) 이해

Ansible의 가장 중요한 특성 중 하나가 **멱등성**이다. 같은 Playbook을 여러 번 실행해도 **결과가 동일**하다는 뜻이다.

- `apt`로 패키지를 설치할 때, 이미 설치되어 있으면 `ok`(변경 없음)

- `service`로 서비스를 시작할 때, 이미 실행 중이면 `ok`

- 즉, **"이 상태여야 한다"를 선언**하는 것이지, "이 명령을 실행해라"가 아니다

이 때문에 `command`나 `shell` 모듈은 멱등성이 보장되지 않는다. 가능하면 **전용 모듈(****`apt`****, ****`service`****, ****`copy`**** 등)을 사용**하는 게 좋다.

---

### 핵심 정리

✅ **Ansible**: SSH + Python 기반 에이전트리스 자동화 도구. 대상 서버에 아무것도 설치할 필요 없다

✅ **설치**: Ubuntu PPA를 통해 최신 안정 버전 설치. 시스템 Python은 건드리지 말자

✅ **Inventory**: 관리 대상 서버를 그룹별로 정리한 파일. INI, YAML 형식 모두 지원

✅ **Ad-hoc**: 간단한 일회성 작업에 사용. `ansible <대상> -m <모듈> -a "<인자>"` 형태

✅ **Playbook**: 복잡한 작업을 YAML로 선언. `--check`로 반드시 사전 검증

✅ **멱등성**: 같은 Playbook을 여러 번 실행해도 결과는 동일. 가능하면 전용 모듈을 사용하자

### 주의사항

⚠️ **시스템 Python**: `pip`로 Ansible을 설치할 때는 venv를 사용하거나, apt를 쓰자

⚠️ **SSH 키 관리**: 비밀번호 방식보다 SSH 키 방식이 자동화에 적합하다

⚠️ **`command`****/****`shell`**** 남용 금지**: 멱등성이 보장되지 않으므로, 전용 모듈이 있다면 전용 모듈을 쓰자

⚠️ **`--check`**** 생활화**: 프로덕션에서는 반드시 드라이 런 먼저. 실수는 한 순간이다

⚠️ **Inventory 보안**: IP, 비밀번호 등이 포함될 수 있으므로, **Git에 올릴 때 ****`.gitignore`**** 설정** 필수

### 참고 자료

- [Ansible 공식 설치 가이드](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html)

- [Ansible Getting Started](https://docs.ansible.com/ansible/latest/getting_started/index.html)

- [Ansible Inventory 구성 가이드](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html)

- [Ubuntu에서 Ansible PPA 설치](https://docs.ansible.com/ansible/latest/installation_guide/installation_distros.html)
