Ansible을 활용한 IaC 기반 서버 구성 자동화

Ansible을 활용한 IaC 기반 서버 구성 자동화

개요

여러 서버를 직접 관리하다 보면 같은 패키지 설치, 설정 파일 배포, 서비스 재시작 작업이 반복된다. 서버 수가 늘어날수록 수동 작업은 누락, 순서 오류, 환경 차이, 작업 이력 부재 같은 문제를 만들기 쉽다.

이 때 Ansible이라는 자동화 도구를 사용하면 반복 작업을 코드로 정의하여 여러 서버에 동일한 구성을 한 번에 적용가능하며, 이를 통해 작업 시간을 단축하고 작업 공수를 줄일 수 있다.


1. Ansible 개념과 특징

Ansible은 Python 기반의 오픈소스 Infrastructure as Code(IaC) 도구이다. 서버 구성 관리, 애플리케이션 배포, 파일 배포, 서비스 제어, 반복 운영 작업을 코드로 정의해 여러 대상에 일관되게 적용할 수 있다.

Ansible의 주요 특징은 다음과 같다.

  • Idempotency(멱등성): 같은 작업을 반복 실행해도 최종 상태가 같도록 설계한다.
  • 모듈 기반 구조: Shell Script에만 의존하지 않고 목적별 모듈로 작업을 표현한다.
  • YAML 형식: 플레이북을 사람이 읽기 쉬운 선언형 문법으로 작성한다.
  • 대규모 서버 작업에 적합: 인벤토리 그룹을 통해 여러 서버에 같은 구성을 적용할 수 있다.
  • Agentless: 대상 Linux 서버에 별도 에이전트를 설치하지 않고 주로 SSH로 통신한다.

1.1 Ansible의 동작 방식

Ansible은 관리 노드에만 도구를 설치하고 대상 서버에는 별도 에이전트를 설치하지 않는 방식으로 동작한다. Linux 서버는 보통 SSH로 접속하고, Windows 서버는 WinRM을 사용할 수 있다.

기존 Shell Script도 자동화에 사용할 수 있지만, Ansible은 yum, apt, copy, file, lineinfile, systemd 같은 Module을 제공해 작업 의도를 더 명확히 표현한다. 같은 작업을 여러 번 실행해도 이미 원하는 상태라면 변경하지 않는 멱등성을 지향하기 때문에, 서버 초기 구성과 반복 배포 작업에 적합하다.

1.2 수동 관리에서 발생하는 문제

여러 서버를 수동으로 관리하면 다음 문제가 자주 발생한다.

  • 서버마다 설치된 패키지 버전이나 설정 파일 내용이 달라진다.
  • 작업자가 명령 순서를 다르게 실행해 결과가 달라질 수 있다.
  • 장애 시 어떤 명령을 어느 서버에 적용했는지 추적하기 어렵다.
  • 신규 서버가 추가될 때 기존 서버와 같은 상태로 맞추는 데 시간이 오래 걸린다.

Ansible은 인벤토리(Inventory)에 대상 서버를 그룹화하고, 플레이북(Playbook)에 필요한 작업을 순서대로 정의해 이런 문제를 줄인다.

1.3 Terraform과 Ansible 간의 차이점

Ansible과 Terraform은 모두 IaC 도구이며, 두 도구 모두 리소스 배포와 서버 내 작업 실행이 가능하다.

그러나 개인적인 사용 경험으로는 각 도구별로 더 적합한 용도가 다르므로 구분해서 사용하는 것이 좋다.

  • Ansible : 다수의 서버 설정 변경, 패키지 설치, 서비스 제어, 명령 실행 등의 서버 구성 관리 
  • Terraform : 주로 클라우드 내에서 VM, Network, LB, DB 등 인프라 리소스를 생성 및 관리 

이렇게 구분해서 사용하는 이유는 다음과 같다.

구분AnsibleTerraform
인프라 배포 – Cloud 모듈을 이용해 VM, Network 등 리소스 생성 가능. 
– 기존 Playbook과 서버 설정 작업을 한 흐름으로 구성 가능 
– Terraform처럼 인프라 상태를 지속적으로 관리하는 state 개념 부재 
– Terraform Plan 보다 리소스 변경 예측이 어려움
– 주요 Cloud Provider가 공식 Provider 및 다양한 리소스 모듈 지원 
– State 기반으로 인프라 상태를 지속적으로 관리 가능 
– plan으로 변경 내용을 사전에 확인 가능.
서버 구성 관리 – 패키지 설치, 서비스 제어, 파일 배포, 명령 실행 등
다양한 서버 구성 관련 모듈이 존재
– Task 단위로 실행 결과를 보여주고 조건 분기가 비교적 용이함
– 인프라 생성 과정에서 간단한 초기 설정이나 스크립트 실행 정도는 가능함.
– remote-exec, local-exec 등을 통해 서버 내부 작업도 가능하지만
스크립트 의존도가 높고 반복적인 서버 설정 관리에는 적합하지 않음.

따라서 두 도구 모두 일부 기능을 상호 대체할 수 있지만, 인프라 리소스의 배포와 상태 관리는 Terraform, 서버 내부의 구성 관리와 작업 자동화는 Ansible을 사용하는 것이 각각의 장점을 가장 잘 활용하는 방식이라 생각된다.

ex) Terraform으로 인프라를 생성한 뒤, 생성된 서버의 내부 구성을 Ansible로 자동화 

1.4 멱등성(Idempotency)

멱등성(Idempotency)이란 동일한 작업을 여러 번 실행하더라도 최종 시스템 상태가 동일하게 유지되는 특성을 의미한다.

Ansible은 대부분의 Module에서 대상 서버의 현재 상태를 확인하고, Playbook에 정의된 상태와 다른 경우에만 변경 작업을 수행한다. 따라서 이미 원하는 상태로 구성되어 있다면 동일한 Playbook을 다시 실행하더라도 불필요한 변경을 수행하지 않는다.

예를 들어 다음 Task를 여러 번 실행하더라도 nginx 패키지가 이미 설치되어 있다면 다시 설치하지 않는다.

[ nginx 패키지 설치용 Task]

최초 실행 시 패키지가 설치되지 않은 상태라면 결과 출력 시 다음과 같이 changed로 표기된다.

이후 동일한 Task를 다시 실행하면 이미 해당 Package가 설치된 상태이므로 다시 설치하지 않고 Task가 완료되며, 결과출력 시 ok로 표기된다.

이러한 멱등성을 활용하면 동일한 Playbook을 여러 서버에 반복 실행하거나, 기존 서버와 신규 서버에 동일하게 적용하더라도 필요한 작업만 수행하도록 구성할 수 있다. 이를 통해 서버별 설정 차이를 줄이고 반복적인 운영 작업을 보다 안정적으로 수행할 수 있다.

다만 모든 Task가 자동으로 멱등성을 보장하는 것은 아니다. 특히 shell이나 command 모듈과 같이 명령 자체를 실행하는 Module들은 기본적으로 실행 자체를 변경 작업으로 판단하는 경우가 많다. 

따라서 동일한 기능을 제공하는 전용 Module이 있다면 이를 우선 사용하며, shell/command 등을 사용하는 경우에는 when 등의 실행 조건을 이용하여 불필요한 실행을 방지하고, changed_when을 사용해 실제 변경 여부가 정확하게 표기되도록 구성하는 것이 좋다.

[shell 모듈의 changed_when 적용 예시]

-> “updated” 라는 결과값이 나오면 변경이 일어난 것으로 간주함


2. Ansible 사용방법

Ansible은 일반적으로 SSH를 통해 대상 서버에 연결하므로 Linux 서버 관리에 주로 사용되며, Windows 서버의 경우 WinRM을 통해 관리할 수 있다. 본 문서에서는 Linux 환경에서 Ansible을 사용하는 방법을 중심으로 설명한다.

2.1 리눅스 환경 내 Ansible 설치

운영 환경에서는 OS별 패키지 관리자를 사용해 Ansible을 설치하고, ansible –version으로 설치 상태를 확인한다.

[각 OS별 Ansible 설치 방법]

2.2 Ansible Inventory

인벤토리는 Ansible이 어떤 서버를 대상으로 작업할지 정의하는 목록을 정의한 파일을 말한다. 인벤토리 파일 내에서는 그룹 단위로 대상 서버를 관리할 수 있으며, 필요한 작업별로 서버 그룹을 지정해서 사용이 가능하다. 작업 시 Inventory 내의 서버에 SSH를 통해 접근하므로, 가급적 키 파일을 사용하여 접근이 가능하도록 설정하는 것이 권장된다. 

만약 별도 키 파일을 사용하는 경우 기본 설정 파일인 /etc/ansible/ansible.cfg 에서 지정하거나 Inventory 구성 시 별도 명시가 필요하다. 인벤토리 파일의 기본 경로는 /etc/ansible/hosts이지만, 작업 단위별로 inventory.ini 같은 파일을 따로 두고 -i 옵션으로 지정하는 방식도 가능하다.

[Inventory 파일 구성 예시]

인벤토리는 ansible-inventory 명령으로 구조를 확인할 수 있다. 실제 실행 전 어떤 그룹과 호스트가 잡히는지 보는 데 유용하다.

2.3 Ansible 명령 기본 사용법

플레이북을 작성하지 않고도 ansible 명령을 통해  간단한 일회성 작업이 가능하다.

  • -m : 사용할 모듈을 지정하는 옵션
  • -i : 인벤토리 파일을 별도로 사용하는 경우 사용 
  • -a : 모듈에 전달할 인자를 지정 

3. Ansible-Playbook 

하나의 모듈만 사용하는 경우라면 ansible 명령으로 충분하지만, 대부분의 서버 구성 및 관리 작업은 여러 작업이 동시에 수반되는 경우가 많다. 이때 사용하는 것이 Ansible-Playbook이다. Ansible-Playbook이란 여러 Task를 명시해둔 YAML파일을 뜻하며, 작업 대상 그룹, 변수 지정, 유저 설정 등의 여러 옵션을 하나의 파일에 명시해서 사용한다.

Ansible-Playbook YAML파일에 명시된 Task는 위에서 아래로 순서대로 실행된다.

또한 YAML파일 형식을 사용하므로 들여쓰기에 민감해서 구조를 일관되게 작성해야 한다. 작성 시에는 탭이 아닌 Space로 공백을 사용해야 하며, 탭을 사용하는 경우 Syntax 오류가 발생하므로 주의해야 한다.

[Playbook YAML파일 예시 – nginx 패키지 설치]

[Playbook  기본 옵션]

옵션설명
hosts인벤토리에 정의된 대상 그룹 또는 호스트
remote_user원격 접속에 사용할 계정
becomesudo 같은 권한 상승 사용 여부
gather_facts대상 서버의 OS, CPU, 메모리 등 fact 수집 여부
varsplay 안에서 사용할 변수
tasks대상 서버에서 수행할 작업 목록

작성된 ansible-playbook YAML파일의 Task를 실행하려면 ansible-playbook 명령을 사용한다.

기존 ansible 명령과 동일하게 -i 옵션을 통해 인벤토리 파일을 지정 가능하다.


[ansible-playbook 명령의 주요 옵션]

옵션용도
–syntax-check실제 실행 없이 YAML 문법과 플레이북 구조 확인
–check실제 변경 없이 변경 예상 결과 확인 (Dry run)
–diff파일 변경 전후 차이 확인, 보통 –check와 함께 사용
–list-hosts플레이북 실행 대상 호스트 목록 확인
–list-tasks실행될 태스크 목록 확인
–start-at-task [TASK]특정 태스크부터 실행 재개
–step태스크별 실행 여부를 물어보며 진행
–skip-tags [tag]특정 태그가 붙은 태스크 제외

3.1 Ansible-Playbook 작업 결과 표기

Ansible-Playbook의 Task 별 실행 결과는 각 Task 별 실행 결과에 따라 별도로 표기되며, 다음과 같은 상태로 표기한다.

결과의미
okTask가 정상 완료 되었으며, 대상 상태 변경 없음
changedTask가 정상 완료 되었으며, 대상 상태 변경이 발생 
failedTask 수행 도중 오류 발생
skipped조건문이나 Tag 조건에 따라 Task 실행을 생략

[실행 결과 출력 예시]

3.2 Module과 Task

Module은 패키지 설치, 파일 복사, 서비스 제어, 명령 실행 등 실제 작업을 수행하는 기능 단위를 뜻하며, Ansible은 다양한 기본 모듈을 제공한다.  기본 모듈들은 ansible.builtin.[모듈] 형태로 사용하며, 대부분의 기본 모듈들은 ‘ansible.builtin.’ 을 생략하고 모듈명만 사용할 수도 있다.

Task는 플레이북 안에서 하나의 모듈 호출을 설명하는 단위를 뜻하며, 일반적으로 하나의 Task에서는 하나의 모듈만 실행한다. 각 Task 안에서는  작업 이름(name) , Module, 전달할 옵션, 조건문 (when) 등을 함께 지정 가능하다.

[주로 사용되는 builtin 모듈 목록]

  • Shell 모듈은 Shell을 통해 대부분의 명령을 실행할 수 있어 활용 범위가 넓지만, 명령 실행 자체를 변경 작업으로 판단하는 경우가 많고 명령의 멱등성을 Ansible이 자동으로 보장하지 않는다. 따라서 동일한 기능을 제공하는 전용 Module이 있다면 이를 우선 사용하고, Shell 기능이 필요한 경우에만 사용하는 것이 좋다.
모듈주요 용도
ping대상 호스트와 Ansible 통신 가능 여부 확인
copy관리 노드의 파일을 대상 서버로 전송
fetch대상 서버의 파일을 관리 노드로 가져오기
file파일, 디렉터리, 권한, 소유자, 심볼릭 링크 관리
lineinfile텍스트 파일의 특정 라인 추가 또는 치환
blockinfile여러 줄로 된 텍스트 블록 추가 또는 관리
debug변수 값이나 실행 결과 출력
shellShell 명령 실행, 단 멱등성 보완 필요
service / systemd서비스 시작, 중지, 재시작, enable 관리
packageOS 패키지 설치를 공통 인터페이스로 처리
apt / dnfDebian/Ubuntu 또는 RHEL 계열 패키지 관리
get_urlURL에서 파일 다운로드
stat파일 존재 여부, 속성, 상태 확인
set_fact플레이 실행 중 동적 변수 생성

[여러 모듈을 사용한 Playbook YAML 작성 예시  – Bash 스크립트 배포 및 실행]

3.3 변수와 vars_files

변수는 환경별 차이를 코드 밖으로 분리한다. 간단한 값은 play 안의 vars에 둘 수 있고, 반복 사용하거나 환경별로 나눌 값은 vars_files로 분리할 수 있다. 다만, vars와 vars_files에 동일 변수가 있는 경우 vars_files의 값이 우선된다.

패키지명, 서비스명, 설치 경로처럼 환경에 따라 달라지는 값은 변수 파일로 분리하면 플레이북 본문을 더 단순하게 유지할 수 있으며, 재사용도 용이하다.

[vars_file 구성]

[Playbook 구성]

[실행 결과]

3.4 register와 set_fact

Ansible-Playbook 실행 도중 이전 Task의 실행 결과를 다음 Task에서 사용하거나 실행 중 확인된 값을 변수로 사용해야 하는 경우가 발생하는데, 이때 register 혹은 set_fact를 사용한다.

register는 특정 Task의 실행 결과를 변수에 저장하는 기능으로, 저장된 변수에는 모듈에 따라 stdout, stderr, rc 등의 실행 결과가 포함된다. 이는 이후 Task의 debug, when, set_fact 등에서 활용 가능하다.

set_fact는 Playbook 실행 도중 새로운 변수를 생성하거나 기존 변수값을 업데이트하는 경우에 주로 사용된다.

[register와 set_fact 사용 예시 – Nginx 설정 상태 확인]

다음과 같이 register를 사용해서 실행 결과를 저장하고, 이를 set_fact로 변수화해서 사용하는 등의 활용이 가능하다.

3.5 when 조건문 

when은 조건에 따라 태스크 실행 여부를 결정한다. 
ex) OS가 debian 인지 아니면 Redhat 계열인지 확인 후 Task 수행

[register/set_fact/when 조건을 사용한 예시 – apache2(httpd) 패키지 설치]

3.6 Handler

Playbook 내에 여러 Task가 존재하고 특정 Task가 실행될 때만 추가적인 Task가 필요하다면 Handler를 사용할 수 있다. 예를 들면 설정 파일 변경 후 서비스를 재시작해줘야 하는 경우에 사용할 수 있다.                                                                                                              

대상 Task에서 Notify를 통해 Handler를 호출 가능하며, 대상 Task가 Changed 인 경우에만 Handler가 실행된다. 기본적으로 Handler는 해당 Play의 일반 Task들이 끝난 뒤에 실행되며, 만약 동일 Handler가 여러 Task에서 호출되더라도 한 번만 실행된다.

When과 handler 모두 조건에 따른 Task 실행을 제어하나 사용하는 목적이 다르므로 구분하여 사용해야한다. 

  • When
    • Task 실행 전 현재 태스크의 실행 여부를 판단하는 조건문
    • 조건이 참이면 해당 태스크를 실행하고, 거짓이면 skipped로 생략
    • 주로 이전 태스크 결과 혹은 변수값에 따라 작업을 분기할 때 사용
  • Handler
    • 모든 Task가 완료되고 나서 특정 Task가 Changed 상태인 경우, 추가로 동작하는 별도 Task
    • 변경이 발생했을 때 후처리 작업을 위해 주로 사용됨

[Handler 사용 예시 – 설정 파일이 변경되면 httpd 서비스 재기동]

-> Copy Task A, B가 모두 Changed라도 ‘Restart httpd’ Handler는 두 Task 완료 후 한 번만 실행

3.7 loop와 with_items

반복문은 여러 값에 같은 Task를 반복 적용할 때 사용한다. 변수를 리스트 형태로 작성하고 이를 loop에 전달하면 Task 동작 시 각 값이 순서대로 item 변수에 저장되면서 Task가 실행된다.

[리스트 변수와 loop를 사용한 예시 – 한 Task 내에서 여러 패키지를 한 번에 설치]

만약 리스트가 단순 문자열이 아니라 Dictionary 형태라면 item.name, item.path처럼 필드를 참조 가능하다.

[ Dictionary 변수와 loop를 사용한 예시 – 다수의 유저 생성]

with_items는 기존 Ansible에서 사용하던 반복 방식으로, 단순 리스트 반복에서는 loop와 유사하게 사용할 수 있다. 다만, with_items는 lookup 기반으로 동작하며 리스트를 한 단계 자동으로 펼치는(flatten) 등 약간의 차이가 존재한다.

신규 Playbook에서는 일반적으로 loop 사용을 권장하므로 loop를 사용하는 것이 좋다.

[with_items를 사용한 리스트 변수 처리 예시 – 여러 패키지 동시 설치]

3.8 Tag

만약 Playbook 내의 모든 Task를 실행하는 것이 아닌 특정 작업만을 실행하거나, 특정 작업을 제외하고 실행해야하는 경우, Tag를 활용할 수 있다. 전체 Playbook을 다시 실행하지 않고 특정 작업만 다시 실행해야하는 경우 유용하게 사용된다.

Playbook의 특정 Task 등에 별도의 Tag를 지정해두면 해당 태그가 지정된 Task만 실행하거나 제외해서 실행할 수 있다. 또한 하나의 Task에 여러 태그를 지정하는 것도 가능하다.

이를 활용하면 install, config, deploy 와 같이 작업 목적별로 태그를 구분해서 선택적으로 실행하는 구조를 만들 수 있다.

[ tag 활용 Task 구성 예시 – httpd 설치 playbook]

[tag 활용 Task 구성 예시 – tag 별 실행 방식]


4. Playbook 구조화 및 재사용 

4.1 include_tasks 활용

하나의 Playbook 내에서 수행되는 작업이 많아질수록, 관리 및 수정이 점점 어려워진다. 따라서 모든 Task를 하나의 파일에 작성해서 사용하기보다, 각 기능 혹은 역할별로 YAML파일을 분리해서 사용하는 것이 관리하기 좋다. 

Include_tasks를 사용하면 별도 파일에 정의된 Task 목록을 Playbook 실행 중 불러와 실행할 수 있다. 또한 when 등의 조건을 사용하여 환경이나 조건에 따라 필요한 Task파일만 선택적으로 실행하도록 구성도 가능하다.

일반적으로 메인 Playbook에서는 전체 실행 흐름과 공통 변수를 관리하고, 실제 작업은 별도 Task 파일로 분리하여 구성한다.

[include_tasks를 활용한 기능별 Task 분리 구성 예시]

  • Main.yaml
    •  공통 변수와 Task의  실행 순서를 관리한다.
  • tasks/packages.yaml
    • 패키지 설치만 담당
  • tasks/users.yaml
    • 계정과 디렉터리 구성
  • tasks/service.yaml
    • 서비스 상태를 관리

이러한 방식을 사용하면 각 기능별 실행 흐름 파악이 용이하며, 필요한 부분만 수정하거나 재사용하기 쉬운 구조로 Playbook을 관리할 수 있다.

4.2 Template

Template은 Jinja2 문법으로 동적 파일이나 메시지를 만든다. OS, 포트, 경로, 서비스명처럼 환경마다 달라지는 값을 변수로 받아 설정 파일을 생성할 때 유용하다.
예를 들어 Nginx 설정 파일을 서버별 변수로 렌더링하려면 templates/nginx.conf.j2를 다음처럼 작성할 수 있다.

[ Nginx 용 Jinja2 템플릿 예시]

Template에서 서버별로 다른 변수를 사용 가능하도록 Inventory 파일 내에 변수를 설정한다.

이후 template 파일을 통해 nginx의 config 파일을 배포하고 Handler로 서비스를 재시작한다.

각 Host 별로 설정된 변수값에 따라 Template으로 배포되는 파일의 내용이 달라진다.

[web-1 – /etc/nginx/conf.d/app.conf ]

[web-2 – /etc/nginx/conf.d/app.conf ]

이와 같이 파일 구조는 동일하지만 서버나 환경에 따라 일부 설정값이 달라지는 경우, Template을 사용하여 제어가 가능하다.

4.3 Role 구조

Role은 반복적으로 사용하는 Playbook 구성을 정해진 디렉토리 구조로 묶어 재사용하기 위한 기능이다.  기존 include_tasks는 하나의 Playbook이 커졌을 때 Task를 여러 파일로 분리해서 관리하기 위한 목적이라면, Role은 Task, 변수, Template, Handler 등을 하나의 디렉토리 구조로 묶어 특정 서버 역할에 필요한 전체 구성을 하나의 단위로서 관리한다.

예를 들어 Nginx 서버 구성을 Role로 만들면 패키지 설치, 설정 파일 배포, 서비스 실행 등의 작업을 Nginx Role 내부에서 관리하고 메인 Playbook에서는 해당 Role만 호출해서 사용이 가능하다.

[Nginx 서버 구성을 위한 Role 구성 예시]

다음과 같이 프로젝트 상위의 main.yaml은 필요한 Role을 호출하여 사용이 가능하다.

위와 같이 nginx Role을 지정하면 기본적으로 디렉토리 내의 roles/nginx/tasks/main.yml을 실행하게 된다. 이 파일 내에는 실제 Task를 작성하거나 필요한 경우 다른 Task 파일을 호출할 수 있다.

[roles/nginx/tasks/main.yml 예시]

  • OS 계열에 따라 RedHat.yml, Debian.yml 등의 Task 파일을 별도로 실행하도록 구성

즉 Role을 호출했을 때의 실행 흐름은 다음과 같다.

Role은 반복적으로 사용하는 서버 구성이나 표준화된 설정을 여러 Playbook에서 재사용하는 경우에 유용하다. 다만  단순한 서버 구성 작업이나 동일 구성을 반복해서 적용할 일이 많지 않다면 Role 구조를 적용하거나 Task 분리 작업이 오히려 관리 복잡도를 높일 수 있다.

따라서 Playbook 규모와 반복 사용 여부를 고려하여 필요한 경우에만 구조를 분리하여 사용하는 것이 좋다.


5. 실제 적용 예시: Docker 설치 및 WEB/WAS 구성

다음은 Ansible-playbook을 활용하여 모든 서버에 docker를 설치한 뒤, 각 역할에 맞게 WEB/WAS 서버 구성을 진행하는 예시이다.

5.1 서버 및 아키텍처 구성

[서버 구성]

  • WEB
    • OS : Rocky Linux 10 
    • Nginx 컨테이너를 통한 Web 서버 구성 (Port : 80)
    • Nginx Reverse Proxy를 구성하여 WAS 서버로 트래픽 전달
  • WAS
    • OS : Ubuntu 24.04 LTS
    • FastAPI 컨테이너를 통한 WAS 서버 구성 (Port : 8080)
  • 방화벽 설정
    • Ansible Control Node → WEB/WAS : TCP 22 접근 허용 
    • Client → WEB : TCP 80 접근 허용 
    • WEB → WAS : TCP 8080 접근 허용 

전체 구성은 Docker 설치, FastAPI 배포, Nginx Reverse Proxy 구성의 세 단계로 나누며 각각 별도의 Role로 관리한다. 

[ 아키텍처 구성도]

5.2 Ansible-Playbook 구성

Playbook은 하나의 파일에 모든 작업을 작성하지 않고 기능별 Role과 환경별 변수를 분리하여 구성한다. 

[Ansible-Playbook 구성]

  • 각 기능별로 별도 Role을 구성
    • docker_engine :  WEB/WAS 서버 공통 Docker 설치
    • fastapi_app : WAS 서버의 FastAPI 이미지 Build 및 컨테이너 실행
    • nginx_proxy :  WEB 서버에서 Nginx 컨테이너를 실행하고 WAS 서버로 Reverse Proxy하도록 설정

변수는 Role의 defaults/main.yml에 정의된 기본값과 Inventory의 group_vars에 정의된 공통 및 그룹별 변수 파일을 조합하여 사용한다.

5.3 Inventory 구성

WEB과 WAS 서버를 각각 web, app 그룹으로 구분하고 두 그룹을 docker_targets의 하위 그룹으로 구성한다.

[inventories/example/hosts.ini]

5.4 Main Playbook 구성

실제 실행용 main.yaml에서는 Docker 설치, WAS 구성, WEB 구성 순서로 Role을 호출하면 된다.

[main.yaml]

각 Play 내에서 실행되는 동작은 다음과 같다.

각 Play에 any_errors_fatal: true를 설정하여 하나의 대상 서버에서 작업이 실패하면 다음 단계로 진행하지 않도록 구성한다. 예를 들어 FastAPI 상태 확인이 실패하면 WEB 구성이 진행되지 않고 끝난다.

5.5 Role – docker_engine

docker 설치를 위한 Role로 모든 WEB/WAS 서버에서 실행된다. Debian 계열과 Redhat 계열의 Docker 설치 방법이 다르므로 OS별 Task를 분리하고, 대상 서버의 OS에 맞는 Task를 실행하도록 구성한다.

설치 완료 후에는 Template을 사용하여 docker의 daemon.json 설정 파일을 배포하고, containerd 설정을 구성한다. 만약 설정 파일이 변경되었다면 Handler를 호출하여 containerd와 Docker 서비스를 재기동한다.

마지막으로 Docker Engine과 Containerd 설정 및 서비스 상태를 확인하여 정상적으로 구성되었는지 검증을 진행한다.

[ docker_engine Role 동작 방식]

5.6 Role – fastapi_app

FastAPI 기반 WAS 구성을 위한 Role로 app 그룹의 WAS 서버에서 실행된다.

먼저 Template을 사용하여 FastAPI 애플리케이션 실행에 필요한 Dockerfile, requirements.txt, main.py 등의 Build Context를 생성한다.

이후 기존 FastAPI Docker Image의 존재 여부와 Build Context의 변경 여부를 확인하며, 소스가 변경되었거나 이미지가 존재하지 않는 경우에만 Docker Image를 새로 Build한다.

이미지 준비가 완료되면 FastAPI 컨테이너를 실행하고, /health API를 호출하여 애플리케이션이 정상적으로 동작하는지 확인한다. 상태 확인에 실패할 경우 이후 WEB 서버 구성은 진행하지 않는다.

[fastapi_app Role 동작 방식]

※  실제 운영 환경에서 사용 시에는 이미 구성이 완료된 이미지를 Registry를 통해 Pull 하여 사용하는 방식을 사용하는 것이 낫다. Application 소스까지 Jinja2 Template으로 관리하면 실제 애플리케이션 코드와 Ansible Template을 이중으로 관리해야 하므로 관리 복잡도가 증가하기 때문이다.

5.7 Role – nginx_proxy

Nginx 기반 WEB 서버와 Reverse Proxy 구성을 위한 Role로 web 그룹의 WEB 서버에서 실행된다.

먼저 Inventory의 app 그룹에 등록된 WAS 서버를 확인하고, 각 서버의 내부 IP와 애플리케이션 Port를 이용하여 Nginx가 요청을 전달할 Backend 서버 정보를 구성한다. 이후 Template을 사용하여 해당 정보를 반영한 Nginx 설정 파일을 생성한다. WAS 서버가 여러 대인 경우에는 app 그룹에 등록된 서버들이 자동으로 Backend 대상에 포함되도록 구성한다.

설정 파일 배포가 완료되면 Nginx 컨테이너를 실행한다. 이후 Nginx 설정 파일이 변경된 경우에는 컨테이너를 재생성하여 변경된 설정을 반영한다.

마지막으로 WEB 서버의 80번 Port로 요청을 보내 Nginx Reverse Proxy를 거쳐 FastAPI WAS까지 요청이 정상적으로 전달되고 응답을 받을 수 있는지 확인한다. 정상 응답이 확인되면 WEB/WAS 구성이 완료된다.

[ nginx_proxy Role 동작 방식]

5.8 실제 구성 결과

[WAS 구성 결과]

[WEB 구성 결과]

  • Docker 설치 완료
  • Nginx 컨테이너 정상 실행

[브라우저 내 호출 확인]


마무리

서버가 몇 대 되지 않을 때는 직접 접속하여 패키지를 설치하고 설정 파일을 수정하는 방식도 충분히 사용할 수 있다. 하지만 관리 대상이 늘어나고 동일한 작업을 여러 서버에 반복해서 적용해야 하는 상황이 많아질수록, 수동 작업은 작업자의 실수나 서버별 설정 차이로 이어지기 쉽다.

특히 신규 서버를 구성하거나 기존 서버의 설정을 일괄 변경할 때마다 동일한 명령을 반복해서 수행하는 방식은 작업 시간뿐만 아니라 작업 결과의 일관성을 유지하는 측면에서도 한계가 있다. 이러한 반복적인 서버 구성과 운영 작업을 사람이 직접 수행하는 대신 코드로 정의하고, 동일한 절차를 필요한 대상에 반복해서 적용하기 위해 Ansible을 사용할 수 있다.

Ansible은 Inventory를 통해 작업 대상을 관리하고 Playbook을 통해 작업 순서를 코드로 남길 수 있기 때문에, 누가 작업하더라도 동일한 절차로 서버를 구성할 수 있다. 또한 멱등성을 고려하여 Playbook을 작성하면 이미 적용된 설정은 유지하면서 필요한 변경만 수행할 수 있어 동일한 작업을 여러 번 실행하는 데 대한 부담도 줄일 수 있다.

결국 Ansible을 사용하는 가장 큰 이유는 단순히 여러 서버에서 명령을 한 번에 실행하기 위해서라기보다, 반복적으로 발생하는 서버 구성과 운영 작업을 표준화하고, 수동 작업에서 발생할 수 있는 실수와 서버별 설정 차이를 줄이며, 동일한 작업을 필요할 때 다시 실행할 수 있는 형태로 관리하기 위해서라고 볼 수 있다.

인프라 자체의 생성과 상태 관리는 Terraform과 같은 도구를 사용하고, 생성된 서버 내부의 구성과 반복적인 운영 작업은 Ansible로 관리하는 식으로 역할을 나누면 각 도구의 장점을 활용하면서 인프라부터 서버 구성까지 일관된 자동화 체계를 구성할 수 있다.


출처

  • Ansible 공식 문서
    • https://docs.ansible.com
  • Terraform 공식 문서
    • https://developer.hashicorp.com/terraform/docs