🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

실전 인프라 자동화 프로젝트 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2026. 2. 3. · 4 Views

실전 인프라 자동화 프로젝트 완벽 가이드

Ansible을 활용하여 멀티 티어 웹 애플리케이션 인프라를 자동으로 구축하는 실전 프로젝트입니다. 웹 서버, 데이터베이스, 로드 밸런서를 코드로 관리하며 반복 가능한 인프라 배포를 경험합니다.


목차

  1. 프로젝트_요구사항_분석
  2. 멀티_티어_아키텍처_설계
  3. 웹_서버_자동_프로비저닝
  4. 데이터베이스_설정_자동화
  5. 로드_밸런서_구성
  6. 전체_스택_배포_및_테스트

1. 프로젝트 요구사항 분석

스타트업 A사의 인프라 엔지니어 김개발 씨는 오늘도 야근 중입니다. 서버 10대에 일일이 접속해서 같은 설정을 반복하고 있었거든요.

"이걸 매번 수동으로 해야 하나요?" 선배 박시니어 씨가 웃으며 다가왔습니다. "인프라 자동화라는 게 있어요.

한번 배워볼래요?"

인프라 자동화 프로젝트의 첫 단계는 요구사항을 명확히 분석하는 것입니다. 마치 건축가가 집을 짓기 전에 설계도를 그리는 것과 같습니다.

어떤 서버가 필요하고, 어떤 소프트웨어를 설치해야 하며, 서버들이 어떻게 통신해야 하는지를 정의합니다.

다음 코드를 살펴봅시다.

# requirements.yml - 프로젝트 요구사항 정의
---
project:
  name: "ecommerce-platform"
  environment: "production"

# 필요한 서버 구성 정의
infrastructure:
  web_servers:
    count: 3
    os: "Ubuntu 22.04"
    specs: { cpu: 2, memory: "4GB" }

  database:
    type: "MySQL 8.0"
    replication: "master-slave"

  load_balancer:
    type: "HAProxy"
    algorithm: "roundrobin"

# 네트워크 요구사항
network:
  vpc_cidr: "10.0.0.0/16"
  public_subnet: "10.0.1.0/24"
  private_subnet: "10.0.2.0/24"

김개발 씨는 입사 6개월 차 주니어 인프라 엔지니어입니다. 매주 금요일마다 반복되는 배포 작업이 너무 힘들었습니다.

서버에 SSH로 접속하고, 패키지를 설치하고, 설정 파일을 수정하는 작업을 10대의 서버에 일일이 해야 했거든요. 어느 날 실수로 한 서버의 설정을 잘못 변경했습니다.

서비스 장애가 발생했고, 원인을 찾는 데만 3시간이 걸렸습니다. 박시니어 씨가 조용히 다가왔습니다.

"김개발 씨, 인프라도 코드로 관리할 수 있다는 거 알아요?" 그렇다면 인프라 자동화란 정확히 무엇일까요? 쉽게 비유하자면, 인프라 자동화는 마치 요리 레시피와 같습니다.

요리사가 매번 감으로 요리하면 맛이 일정하지 않습니다. 하지만 정확한 레시피가 있다면 누가 만들어도 같은 맛을 낼 수 있습니다.

인프라 자동화도 마찬가지입니다. 서버 설정을 코드로 정의해 두면, 누가 실행해도 동일한 인프라를 구축할 수 있습니다.

인프라 자동화가 없던 시절에는 어땠을까요? 개발자들은 서버 한 대 한 대에 직접 접속해서 명령어를 입력했습니다.

문서에 적힌 대로 따라 하다가 오타를 내기도 하고, 순서를 잘못 지키기도 했습니다. 더 큰 문제는 설정 드리프트였습니다.

시간이 지나면서 각 서버의 설정이 조금씩 달라지는 현상이죠. 어떤 서버에는 패치가 적용되어 있고, 어떤 서버에는 적용되지 않은 상태가 되어버립니다.

바로 이런 문제를 해결하기 위해 Infrastructure as Code, 즉 IaC가 등장했습니다. IaC를 사용하면 인프라 상태를 버전 관리할 수 있습니다.

Git으로 코드를 관리하듯이, 인프라 설정도 히스토리를 추적할 수 있게 됩니다. 또한 동일한 설정을 여러 환경에 반복 적용할 수 있습니다.

개발 환경과 운영 환경을 완전히 동일하게 만들 수 있는 것이죠. 위의 코드를 살펴보겠습니다.

먼저 project 섹션에서 프로젝트 이름과 환경을 정의합니다. 이것은 나중에 여러 프로젝트를 관리할 때 구분자 역할을 합니다.

infrastructure 섹션에서는 필요한 서버들을 정의합니다. 웹 서버 3대, 데이터베이스, 로드 밸런서의 사양을 명시합니다.

network 섹션에서는 VPC와 서브넷 구성을 정의합니다. 실제 현업에서는 이 요구사항 문서가 프로젝트의 나침반 역할을 합니다.

예를 들어 이커머스 플랫폼을 구축한다고 가정해봅시다. 블랙프라이데이 시즌에는 트래픽이 10배로 증가합니다.

이때 요구사항 문서에 auto_scaling 설정이 정의되어 있다면, 자동으로 서버를 늘릴 수 있습니다. 많은 기업에서 이런 방식으로 탄력적인 인프라를 운영하고 있습니다.

하지만 주의할 점도 있습니다. 초보 엔지니어들이 흔히 하는 실수 중 하나는 요구사항을 너무 막연하게 정의하는 것입니다.

"서버 몇 대 필요함"이라고 적어두면 나중에 혼란이 생깁니다. 따라서 CPU 코어 수, 메모리 용량, 디스크 크기까지 구체적으로 명시해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언을 듣고 김개발 씨는 먼저 현재 인프라의 요구사항을 YAML 파일로 정리하기 시작했습니다.

"아, 이렇게 문서화하니까 전체 구조가 한눈에 보이네요!" 요구사항 분석을 제대로 하면 프로젝트의 절반은 성공한 것입니다. 여러분도 인프라 자동화 프로젝트를 시작할 때 이 단계를 절대 건너뛰지 마세요.

실전 팁

💡 - 요구사항은 YAML이나 JSON 같은 구조화된 형식으로 작성하여 나중에 코드에서 파싱할 수 있게 합니다

  • 개발, 스테이징, 운영 환경별로 다른 사양을 정의하되, 구조는 동일하게 유지합니다
  • 팀원들과 함께 요구사항을 리뷰하여 빠진 부분이 없는지 확인합니다

2. 멀티 티어 아키텍처 설계

요구사항 분석을 마친 김개발 씨에게 박시니어 씨가 질문을 던졌습니다. "그런데 이 서버들을 어떻게 배치할 건가요?

웹 서버와 데이터베이스를 같은 네트워크에 두면 보안상 문제가 있어요." 김개발 씨는 고개를 갸웃했습니다. "그럼 어떻게 해야 하죠?"

멀티 티어 아키텍처는 애플리케이션을 여러 계층으로 분리하여 배치하는 설계 방식입니다. 마치 아파트에서 상가, 주거 공간, 주차장을 층별로 분리하는 것과 같습니다.

이렇게 하면 보안이 강화되고, 각 계층을 독립적으로 확장할 수 있습니다.

다음 코드를 살펴봅시다.

# inventory/production/hosts.yml - 멀티 티어 인벤토리
---
all:
  children:
    # 프레젠테이션 티어 (Public Subnet)
    web_tier:
      hosts:
        web1: { ansible_host: 10.0.1.11 }
        web2: { ansible_host: 10.0.1.12 }
        web3: { ansible_host: 10.0.1.13 }
      vars:
        tier: "presentation"
        subnet: "public"

    # 애플리케이션 티어 (Private Subnet)
    app_tier:
      hosts:
        app1: { ansible_host: 10.0.2.21 }
        app2: { ansible_host: 10.0.2.22 }
      vars:
        tier: "application"
        subnet: "private"

    # 데이터 티어 (Private Subnet - Isolated)
    db_tier:
      hosts:
        db_master: { ansible_host: 10.0.3.31, role: master }
        db_slave: { ansible_host: 10.0.3.32, role: slave }
      vars:
        tier: "data"
        subnet: "isolated"

박시니어 씨가 화이트보드 앞으로 김개발 씨를 데려갔습니다. "인프라 설계는 집을 짓는 것과 비슷해요.

무작정 벽돌부터 쌓으면 안 되고, 먼저 설계도가 필요하죠." 박시니어 씨가 마커를 들고 세 개의 상자를 그렸습니다. "이게 바로 3-티어 아키텍처예요." 그렇다면 멀티 티어 아키텍처란 정확히 무엇일까요?

쉽게 비유하자면, 멀티 티어 아키텍처는 마치 레스토랑의 구조와 같습니다. 손님이 앉는 홀이 있고, 요리사가 일하는 주방이 있고, 식재료를 보관하는 창고가 있습니다.

손님은 주방에 직접 들어갈 수 없고, 요리사만 창고에 접근할 수 있습니다. 이렇게 역할별로 공간을 분리하면 효율적이고 안전합니다.

모든 것을 한 서버에 넣으면 어떤 문제가 생길까요? 첫째, 보안 위험이 커집니다.

웹 서버가 해킹당하면 데이터베이스까지 한꺼번에 노출됩니다. 둘째, 확장이 어렵습니다.

웹 트래픽만 늘었는데 데이터베이스 서버까지 함께 늘려야 합니다. 셋째, 장애 범위가 넓어집니다.

한 부분의 문제가 전체 시스템을 마비시킵니다. 바로 이런 문제를 해결하기 위해 티어를 분리합니다.

프레젠테이션 티어는 사용자와 직접 만나는 층입니다. 웹 서버나 로드 밸런서가 여기에 위치합니다.

퍼블릭 서브넷에 배치되어 인터넷에서 접근 가능합니다. 애플리케이션 티어는 비즈니스 로직을 처리합니다.

프라이빗 서브넷에 있어서 외부에서 직접 접근할 수 없습니다. 데이터 티어는 가장 중요한 데이터를 보관합니다.

격리된 서브넷에 배치하여 오직 애플리케이션 티어에서만 접근 가능합니다. 위의 인벤토리 파일을 살펴보겠습니다.

Ansible에서 인벤토리는 관리할 서버 목록을 정의하는 파일입니다. children 키워드로 그룹을 계층적으로 구성할 수 있습니다.

각 티어마다 vars 섹션에서 공통 변수를 정의합니다. 이렇게 하면 나중에 플레이북에서 티어별로 다른 작업을 수행할 수 있습니다.

실제 현업에서 이 아키텍처는 어떻게 활용될까요? 대형 쇼핑몰을 생각해 봅시다.

명절 대목에는 웹 서버만 5배로 늘립니다. 주문 처리 로직이 복잡해지면 애플리케이션 서버만 업그레이드합니다.

데이터베이스는 안정성이 중요하므로 거의 건드리지 않습니다. 티어가 분리되어 있기 때문에 이런 유연한 운영이 가능합니다.

하지만 주의할 점도 있습니다. 티어를 너무 세분화하면 복잡도가 높아집니다.

소규모 서비스에서 5티어 아키텍처를 도입하는 것은 과도한 설계입니다. 또한 티어 간 통신 지연도 고려해야 합니다.

네트워크를 거칠 때마다 몇 밀리초씩 지연이 발생하기 때문입니다. 박시니어 씨의 설명을 들은 김개발 씨가 물었습니다.

"그러면 우리 서비스는 3티어가 적당하겠네요?" 박시니어 씨가 고개를 끄덕였습니다. "맞아요.

서비스 규모에 맞는 적절한 설계가 중요해요." 멀티 티어 아키텍처를 제대로 이해하면 보안과 확장성을 모두 잡을 수 있습니다. 이제 이 설계를 바탕으로 실제 서버를 프로비저닝해 봅시다.

실전 팁

💡 - 티어 간 통신은 반드시 정해진 포트만 허용하도록 보안 그룹을 설정합니다

  • 인벤토리 파일은 환경별로 분리하여 관리합니다 (inventory/dev, inventory/prod)
  • 호스트 변수와 그룹 변수를 적절히 활용하면 중복 설정을 줄일 수 있습니다

3. 웹 서버 자동 프로비저닝

아키텍처 설계를 마친 김개발 씨가 본격적으로 서버 구축에 들어갔습니다. 예전 같으면 SSH로 접속해서 apt install nginx를 세 번 입력했겠지만, 이제는 다릅니다.

"이제 플레이북 하나로 웹 서버 세 대를 동시에 설정할 수 있어요." 박시니어 씨가 자신 있게 말했습니다.

웹 서버 프로비저닝은 빈 서버에 웹 서비스를 제공할 수 있는 상태로 만드는 과정입니다. 마치 빈 가게에 인테리어를 하고, 물건을 채워 넣어 영업 준비를 완료하는 것과 같습니다.

Ansible 플레이북을 사용하면 이 과정을 자동화하여 여러 서버에 동시에 적용할 수 있습니다.

다음 코드를 살펴봅시다.

# playbooks/web_servers.yml - 웹 서버 프로비저닝
---
- name: Configure Web Servers
  hosts: web_tier
  become: true
  vars:
    nginx_worker_processes: "auto"
    nginx_worker_connections: 4096

  tasks:
    # Nginx 설치
    - name: Install Nginx
      apt:
        name: nginx
        state: present
        update_cache: true

    # Nginx 설정 파일 배포
    - name: Deploy Nginx configuration
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        validate: nginx -t -c %s
      notify: Reload Nginx

    # 가상 호스트 설정
    - name: Configure virtual host
      template:
        src: templates/vhost.conf.j2
        dest: /etc/nginx/sites-available/{{ app_name }}
      notify: Reload Nginx

    # 사이트 활성화
    - name: Enable site
      file:
        src: /etc/nginx/sites-available/{{ app_name }}
        dest: /etc/nginx/sites-enabled/{{ app_name }}
        state: link

    # 서비스 시작 및 자동 시작 설정
    - name: Ensure Nginx is running
      service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded

김개발 씨가 터미널을 열었습니다. 예전에는 서버마다 접속해서 명령어를 입력했지만, 오늘은 다릅니다.

플레이북 파일 하나를 작성하면 됩니다. 박시니어 씨가 옆에서 지켜보며 설명했습니다.

"Ansible 플레이북은 서버 설정의 레시피예요. 한 번 작성해 두면 몇 대의 서버든 동일하게 설정할 수 있죠." 그렇다면 플레이북이란 정확히 무엇일까요?

쉽게 비유하자면, 플레이북은 마치 조립 설명서와 같습니다. 레고 블록을 조립할 때 설명서를 보고 순서대로 따라 하면 누구나 같은 모양을 만들 수 있습니다.

플레이북도 마찬가지입니다. 작업 순서와 내용을 YAML로 정의해 두면, Ansible이 설명서대로 서버를 설정합니다.

수동 설정의 문제점은 무엇일까요? 서버가 3대일 때는 수동으로 해도 괜찮습니다.

하지만 30대, 300대가 되면 어떨까요? 실수가 생기기 마련입니다.

어떤 서버에는 패키지를 설치했고, 어떤 서버에는 빠뜨렸습니다. 나중에 문제가 생겼을 때 어디서부터 확인해야 할지 막막해집니다.

이것을 설정 드리프트라고 합니다. 플레이북을 사용하면 이 문제가 해결됩니다.

hosts: web_tier는 이 플레이북이 웹 티어의 모든 서버에 적용된다는 뜻입니다. 인벤토리에 정의된 web1, web2, web3 서버에 동시에 실행됩니다.

become: true는 sudo 권한으로 실행한다는 의미입니다. 패키지 설치나 시스템 설정 변경에는 관리자 권한이 필요하기 때문입니다.

각 태스크를 하나씩 살펴보겠습니다. 첫 번째 태스크는 apt 모듈로 Nginx를 설치합니다.

state: present는 패키지가 설치되어 있어야 한다는 뜻입니다. 이미 설치되어 있다면 아무 작업도 하지 않습니다.

이것이 바로 멱등성입니다. 같은 플레이북을 여러 번 실행해도 결과는 동일합니다.

두 번째 태스크는 template 모듈로 설정 파일을 배포합니다. Jinja2 템플릿을 사용하면 변수를 동적으로 삽입할 수 있습니다.

validate 옵션은 설정 파일을 적용하기 전에 문법을 검사합니다. 잘못된 설정으로 서비스가 죽는 것을 방지합니다.

notifyhandlers의 관계도 중요합니다. 설정 파일이 변경되면 Nginx를 재시작해야 합니다.

하지만 변경이 없는데 매번 재시작하면 불필요한 다운타임이 발생합니다. notify는 태스크가 변경을 일으켰을 때만 핸들러를 호출합니다.

핸들러는 플레이북 끝에 한 번만 실행됩니다. 여러 태스크가 같은 핸들러를 호출해도 중복 실행되지 않습니다.

실제 현업에서는 이런 플레이북을 어떻게 관리할까요? 대부분의 팀은 Git 저장소에 플레이북을 저장합니다.

변경 이력을 추적하고, 코드 리뷰를 거쳐 안전하게 적용합니다. CI/CD 파이프라인과 연동하면 코드가 머지될 때 자동으로 서버에 적용됩니다.

하지만 주의할 점도 있습니다. 플레이북에 비밀번호나 API 키를 직접 작성하면 안 됩니다.

Git에 커밋되면 누구나 볼 수 있기 때문입니다. Ansible Vault를 사용하여 민감한 정보를 암호화해야 합니다.

김개발 씨가 플레이북을 실행했습니다. 화면에 초록색 "ok"와 노란색 "changed" 메시지가 쏟아졌습니다.

불과 2분 만에 웹 서버 3대의 설정이 완료되었습니다. "예전에는 서버당 30분씩 걸렸는데..." 김개발 씨의 눈이 반짝였습니다.

웹 서버 프로비저닝을 자동화하면 시간도 절약되고 실수도 줄어듭니다. 이제 데이터베이스 설정으로 넘어가 봅시다.

실전 팁

💡 - 플레이북은 작은 단위로 나누어 역할(role)로 구성하면 재사용성이 높아집니다

  • validate 옵션을 적극 활용하여 잘못된 설정이 적용되는 것을 방지합니다
  • 프로덕션 적용 전에 반드시 스테이징 환경에서 테스트합니다

4. 데이터베이스 설정 자동화

웹 서버 설정을 마친 김개발 씨 앞에 더 까다로운 과제가 놓였습니다. 바로 데이터베이스 설정입니다.

"데이터베이스는 좀 무섭지 않아요? 잘못 건드리면 데이터가 날아갈 수도 있잖아요." 박시니어 씨가 고개를 끄덕였습니다.

"맞아요. 그래서 더욱 자동화가 필요한 거예요."

데이터베이스 설정 자동화는 MySQL이나 PostgreSQL 같은 데이터베이스를 일관되게 설치하고 구성하는 과정입니다. 마치 금고를 설치할 때 전문가의 매뉴얼대로 정확하게 따라 해야 하는 것과 같습니다.

한 번 작성한 플레이북으로 마스터-슬레이브 복제 구성까지 자동화할 수 있습니다.

다음 코드를 살펴봅시다.

# playbooks/database.yml - 데이터베이스 설정 자동화
---
- name: Configure MySQL Database
  hosts: db_tier
  become: true
  vars:
    mysql_root_password: "{{ vault_mysql_root_password }}"
    mysql_replication_user: "repl_user"
    mysql_databases:
      - name: ecommerce_db
        encoding: utf8mb4
        collation: utf8mb4_unicode_ci

  tasks:
    # MySQL 설치
    - name: Install MySQL Server
      apt:
        name:
          - mysql-server
          - mysql-client
          - python3-mysqldb
        state: present

    # MySQL 서비스 시작
    - name: Start MySQL service
      service:
        name: mysql
        state: started
        enabled: true

    # root 비밀번호 설정
    - name: Set MySQL root password
      mysql_user:
        name: root
        password: "{{ mysql_root_password }}"
        host: localhost
        login_unix_socket: /var/run/mysqld/mysqld.sock

    # 데이터베이스 생성
    - name: Create application database
      mysql_db:
        name: "{{ item.name }}"
        encoding: "{{ item.encoding }}"
        collation: "{{ item.collation }}"
        state: present
      loop: "{{ mysql_databases }}"

    # 마스터 서버 설정 (조건부 실행)
    - name: Configure MySQL Master
      template:
        src: templates/mysql_master.cnf.j2
        dest: /etc/mysql/mysql.conf.d/master.cnf
      when: role == 'master'
      notify: Restart MySQL

    # 복제 사용자 생성 (마스터에서만)
    - name: Create replication user
      mysql_user:
        name: "{{ mysql_replication_user }}"
        password: "{{ vault_replication_password }}"
        host: "%"
        priv: "*.*:REPLICATION SLAVE"
        state: present
      when: role == 'master'

  handlers:
    - name: Restart MySQL
      service:
        name: mysql
        state: restarted

김개발 씨는 예전에 데이터베이스 마이그레이션 작업 중 실수로 프로덕션 테이블을 삭제한 적이 있습니다. 다행히 백업이 있어서 복구했지만, 그 이후로 데이터베이스 작업만 하면 손이 떨립니다.

박시니어 씨가 김개발 씨를 안심시켰습니다. "자동화하면 오히려 더 안전해요.

사람이 직접 명령어를 입력하는 것보다 검증된 플레이북을 실행하는 게 실수가 적거든요." 그렇다면 데이터베이스 자동화에서 가장 중요한 것은 무엇일까요? 바로 보안일관성입니다.

데이터베이스는 서비스의 핵심 자산을 보관합니다. 비밀번호가 노출되거나 권한 설정이 잘못되면 큰 사고로 이어집니다.

자동화하면 검증된 설정을 일관되게 적용할 수 있어서 오히려 보안이 강화됩니다. 위 플레이북에서 눈여겨볼 부분이 있습니다.

vault_mysql_root_password라는 변수를 보세요. 앞에 vault_ 접두사가 붙어 있습니다.

이것은 Ansible Vault로 암호화된 변수라는 컨벤션입니다. 비밀번호를 플레이북에 직접 작성하지 않고, 별도의 암호화된 파일에서 불러옵니다.

이렇게 하면 Git에 커밋해도 비밀번호가 노출되지 않습니다. loop 키워드도 중요합니다.

mysql_databases 변수에 여러 데이터베이스를 정의해 두면, loop가 자동으로 반복합니다. 나중에 새 데이터베이스가 필요하면 변수만 추가하면 됩니다.

플레이북 자체를 수정할 필요가 없습니다. 이것이 데이터와 로직의 분리입니다.

when 조건문은 더욱 강력합니다. 인벤토리에서 db_master의 role을 'master'로, db_slave의 role을 'slave'로 정의했습니다.

when: role == 'master' 조건을 사용하면 마스터 서버에서만 해당 태스크가 실행됩니다. 하나의 플레이북으로 마스터와 슬레이브를 동시에, 그러나 다르게 설정할 수 있습니다.

마스터-슬레이브 복제는 왜 필요할까요? 데이터베이스 하나에 모든 읽기와 쓰기가 집중되면 병목이 생깁니다.

마스터는 쓰기를 담당하고, 슬레이브는 읽기를 담당하면 부하를 분산할 수 있습니다. 또한 마스터에 장애가 생겼을 때 슬레이브를 마스터로 승격시켜 서비스를 지속할 수 있습니다.

하지만 주의할 점도 있습니다. 복제 설정이 잘못되면 데이터 불일치가 발생합니다.

마스터에는 있는 데이터가 슬레이브에는 없을 수 있습니다. 따라서 복제 상태를 모니터링하는 태스크도 추가해야 합니다.

또한 슬레이브에서 직접 쓰기 작업을 하면 안 됩니다. 읽기 전용으로 설정하는 것이 좋습니다.

김개발 씨가 플레이북을 실행했습니다. 마스터와 슬레이브 서버에 각각 다른 설정이 적용되는 것을 확인하고 감탄했습니다.

"하나의 플레이북인데 서버마다 다르게 동작하네요!" 데이터베이스 자동화를 마스터하면 인프라 엔지니어로서 한 단계 성장한 것입니다. 이제 로드 밸런서로 이 모든 것을 연결해 봅시다.

실전 팁

💡 - 데이터베이스 비밀번호는 반드시 Ansible Vault로 암호화하여 관리합니다

  • 프로덕션 적용 전에 반드시 백업을 수행하고, 복원 테스트까지 완료합니다
  • 복제 지연(replication lag)을 모니터링하는 태스크를 추가하는 것이 좋습니다

5. 로드 밸런서 구성

웹 서버 3대와 데이터베이스가 준비되었습니다. 김개발 씨가 물었습니다.

"그런데 사용자 요청이 들어오면 어떤 웹 서버로 보내야 하죠?" 박시니어 씨가 웃으며 대답했습니다. "그걸 결정해 주는 게 바로 로드 밸런서예요.

교통 경찰 같은 역할이죠."

로드 밸런서는 들어오는 트래픽을 여러 서버에 분산시키는 역할을 합니다. 마치 은행에서 번호표를 뽑으면 비어 있는 창구로 안내하는 것과 같습니다.

HAProxy는 가장 널리 사용되는 오픈소스 로드 밸런서로, Ansible로 쉽게 자동화할 수 있습니다.

다음 코드를 살펴봅시다.

# playbooks/load_balancer.yml - HAProxy 로드 밸런서 구성
---
- name: Configure HAProxy Load Balancer
  hosts: load_balancer
  become: true
  vars:
    haproxy_frontend_port: 80
    haproxy_stats_port: 8404
    backend_servers: "{{ groups['web_tier'] }}"

  tasks:
    # HAProxy 설치
    - name: Install HAProxy
      apt:
        name: haproxy
        state: present
        update_cache: true

    # HAProxy 설정 파일 배포
    - name: Deploy HAProxy configuration
      template:
        src: templates/haproxy.cfg.j2
        dest: /etc/haproxy/haproxy.cfg
        validate: haproxy -c -f %s
      notify: Reload HAProxy

    # 서비스 시작
    - name: Ensure HAProxy is running
      service:
        name: haproxy
        state: started
        enabled: true

  handlers:
    - name: Reload HAProxy
      service:
        name: haproxy
        state: reloaded

# templates/haproxy.cfg.j2 - HAProxy 설정 템플릿
# global
#   log /dev/log local0
#   maxconn 4096
#
# defaults
#   mode http
#   timeout connect 5s
#   timeout client 50s
#   timeout server 50s
#
# frontend http_front
#   bind *:{{ haproxy_frontend_port }}
#   default_backend web_servers
#
# backend web_servers
#   balance roundrobin
#   option httpchk GET /health
# {% for host in backend_servers %}
#   server {{ host }} {{ hostvars[host].ansible_host }}:80 check
# {% endfor %}
#
# listen stats
#   bind *:{{ haproxy_stats_port }}
#   stats enable
#   stats uri /stats

김개발 씨는 궁금했습니다. 로드 밸런서 없이 웹 서버 IP를 직접 사용자에게 알려주면 안 되는 걸까요?

박시니어 씨가 설명했습니다. "만약 서버 하나가 고장 나면 어떻게 될까요?

사용자는 접속이 안 되는 서버에 계속 요청을 보내겠죠. 로드 밸런서가 있으면 고장 난 서버를 자동으로 제외합니다." 그렇다면 로드 밸런서는 어떻게 동작할까요?

쉽게 비유하자면, 로드 밸런서는 마치 콜센터의 교환원과 같습니다. 고객이 전화를 걸면 교환원이 현재 통화 중이지 않은 상담사에게 연결해 줍니다.

모든 고객이 한 명의 상담사에게만 전화하면 그 상담사는 과부하에 걸리고, 다른 상담사들은 놀게 됩니다. 교환원이 골고루 분배해 주면 모두가 효율적으로 일할 수 있습니다.

위 플레이북에서 핵심은 동적 설정 생성입니다. backend_servers 변수를 보세요.

**groups['web_tier']**는 인벤토리에서 web_tier 그룹에 속한 모든 호스트 이름을 가져옵니다. 템플릿에서 for 루프를 돌면서 각 서버를 백엔드에 추가합니다.

나중에 웹 서버를 추가하면 인벤토리만 수정하면 됩니다. HAProxy 설정은 자동으로 업데이트됩니다.

balance roundrobin은 가장 기본적인 분산 알고리즘입니다. 라운드 로빈은 서버에 순서대로 요청을 분배합니다.

첫 번째 요청은 web1, 두 번째는 web2, 세 번째는 web3, 네 번째는 다시 web1로 갑니다. 단순하지만 효과적입니다.

서버 성능이 다르다면 leastconn 알고리즘을 사용할 수 있습니다. 현재 연결 수가 가장 적은 서버로 요청을 보냅니다.

헬스체크는 로드 밸런서의 핵심 기능입니다. option httpchk GET /health는 주기적으로 각 서버의 /health 엔드포인트에 요청을 보냅니다.

응답이 없거나 에러를 반환하면 해당 서버를 풀에서 제외합니다. 서버가 복구되면 자동으로 다시 추가됩니다.

사용자는 장애를 전혀 느끼지 못합니다. stats 섹션도 중요합니다.

HAProxy는 기본적으로 통계 대시보드를 제공합니다. 8404 포트로 접속하면 각 서버의 상태, 요청 수, 응답 시간 등을 실시간으로 확인할 수 있습니다.

운영 중에 문제가 생겼을 때 빠르게 원인을 파악할 수 있습니다. 실제 현업에서는 로드 밸런서를 어떻게 운영할까요?

대부분의 서비스는 로드 밸런서도 이중화합니다. Active-Standby 구성으로 메인 로드 밸런서가 죽으면 백업이 즉시 대체합니다.

AWS의 ELB나 GCP의 Cloud Load Balancing 같은 관리형 서비스를 사용하기도 합니다. 하지만 원리를 이해하려면 HAProxy 같은 소프트웨어 로드 밸런서를 직접 다뤄보는 것이 좋습니다.

주의할 점도 있습니다. 로드 밸런서가 단일 장애점이 되면 안 됩니다.

아무리 백엔드 서버가 많아도 로드 밸런서 하나가 죽으면 전체 서비스가 중단됩니다. 또한 세션 관리에 주의해야 합니다.

로그인한 사용자가 다른 서버로 분배되면 세션이 끊어질 수 있습니다. 이를 위해 sticky session이나 외부 세션 저장소를 사용합니다.

김개발 씨가 HAProxy 통계 페이지를 열어봤습니다. 세 대의 웹 서버가 초록색으로 빛나고 있었습니다.

"와, 이제 서버 하나가 죽어도 서비스는 계속되겠네요!" 로드 밸런서 구성을 마치면 고가용성 아키텍처의 기반이 완성됩니다. 이제 전체 스택을 한 번에 배포하는 방법을 알아봅시다.

실전 팁

💡 - 헬스체크 엔드포인트는 데이터베이스 연결까지 확인하도록 구현하면 더 정확합니다

  • 프로덕션에서는 로드 밸런서도 이중화하여 단일 장애점을 제거합니다
  • SSL 종료(termination)는 로드 밸런서에서 처리하면 백엔드 서버의 부하를 줄일 수 있습니다

6. 전체 스택 배포 및 테스트

모든 플레이북이 준비되었습니다. 김개발 씨가 물었습니다.

"이걸 하나씩 실행해야 하나요?" 박시니어 씨가 고개를 저었습니다. "아니요, 전체를 한 번에 실행하는 마스터 플레이북을 만들 거예요.

그리고 제대로 동작하는지 테스트하는 것도 자동화할 수 있어요."

전체 스택 배포는 개별 플레이북을 올바른 순서로 실행하여 완전한 인프라를 구축하는 과정입니다. 마치 오케스트라 지휘자가 각 악기 파트를 조율하여 하나의 교향곡을 완성하는 것과 같습니다.

배포 후에는 자동화된 테스트로 모든 것이 정상인지 확인합니다.

다음 코드를 살펴봅시다.

# site.yml - 전체 스택 배포 마스터 플레이북
---
# 1단계: 기본 설정 (모든 서버)
- name: Apply common configuration
  hosts: all
  become: true
  roles:
    - common
    - security

# 2단계: 데이터베이스 설정 (먼저 실행)
- name: Configure Database Tier
  import_playbook: playbooks/database.yml

# 3단계: 웹 서버 설정
- name: Configure Web Tier
  import_playbook: playbooks/web_servers.yml

# 4단계: 로드 밸런서 설정
- name: Configure Load Balancer
  import_playbook: playbooks/load_balancer.yml

# 5단계: 배포 검증 테스트
- name: Verify Deployment
  hosts: localhost
  gather_facts: false
  tasks:
    # 로드 밸런서 응답 확인
    - name: Test load balancer endpoint
      uri:
        url: "http://{{ load_balancer_ip }}/"
        method: GET
        status_code: 200
      register: lb_result
      retries: 5
      delay: 10

    # 모든 웹 서버 헬스체크
    - name: Verify all web servers healthy
      uri:
        url: "http://{{ load_balancer_ip }}:8404/stats"
        method: GET
        status_code: 200

    # 데이터베이스 연결 테스트
    - name: Test database connectivity
      command: >
        mysql -h {{ db_master_ip }} -u app_user -p{{ vault_app_db_password }}
        -e "SELECT 1"
      delegate_to: "{{ groups['web_tier'][0] }}"
      no_log: true

    # 결과 출력
    - name: Display deployment status
      debug:
        msg: |
          ========================================
          Deployment Completed Successfully!
          ----------------------------------------
          Load Balancer: http://{{ load_balancer_ip }}
          HAProxy Stats: http://{{ load_balancer_ip }}:8404/stats
          Web Servers: {{ groups['web_tier'] | length }} nodes
          Database: Master-Slave replication active
          ========================================

드디어 마지막 단계입니다. 김개발 씨는 지금까지 작성한 플레이북들을 바라보며 뿌듯함을 느꼈습니다.

하지만 아직 한 가지 문제가 남아 있었습니다. 이 플레이북들을 어떤 순서로 실행해야 할까요?

박시니어 씨가 설명했습니다. "순서가 중요해요.

웹 서버가 데이터베이스에 연결하려면 데이터베이스가 먼저 준비되어 있어야 하거든요." 그렇다면 마스터 플레이북의 역할은 무엇일까요? 쉽게 비유하자면, 마스터 플레이북은 마치 요리 순서표와 같습니다.

스테이크를 만들 때 고기를 먼저 굽고, 소스를 나중에 만들면 고기가 식어버립니다. 소스를 먼저 준비하고, 고기를 구워야 따뜻한 상태로 완성됩니다.

인프라도 마찬가지입니다. 의존성 순서대로 배포해야 합니다.

위 플레이북에서 실행 순서를 살펴봅시다. 첫 번째로 commonsecurity 롤을 모든 서버에 적용합니다.

타임존 설정, SSH 키 배포, 방화벽 기본 설정 같은 공통 작업입니다. 두 번째로 데이터베이스를 설정합니다.

웹 서버보다 먼저 준비되어야 연결할 수 있습니다. 세 번째로 웹 서버를 설정합니다.

네 번째로 로드 밸런서를 설정합니다. 웹 서버가 먼저 준비되어야 백엔드로 등록할 수 있습니다.

import_playbookinclude_playbook의 차이도 알아야 합니다. import_playbook은 실행 전에 플레이북을 미리 로드합니다.

문법 오류가 있으면 실행 전에 알 수 있습니다. include_playbook은 실행 시점에 동적으로 로드합니다.

조건부 실행이 필요할 때 사용합니다. 일반적으로는 import_playbook이 더 안전합니다.

배포 검증 테스트는 왜 필요할까요? 모든 태스크가 성공했다고 해서 시스템이 정상인 것은 아닙니다.

Nginx가 설치되었지만 설정 오류로 시작되지 않았을 수 있습니다. MySQL이 실행 중이지만 복제가 깨져 있을 수 있습니다.

실제로 요청을 보내보고 응답을 확인해야 진짜 동작하는지 알 수 있습니다. uri 모듈은 HTTP 요청을 보내고 응답을 검증합니다.

로드 밸런서에 GET 요청을 보내고, 상태 코드가 200인지 확인합니다. retriesdelay를 설정하면 서비스가 완전히 시작될 때까지 기다립니다.

첫 번째 시도에서 실패해도 10초 후에 다시 시도하고, 최대 5번까지 반복합니다. no_log: true는 보안을 위한 설정입니다.

데이터베이스 비밀번호가 포함된 명령어는 로그에 남기면 안 됩니다. no_log: true를 설정하면 해당 태스크의 출력이 마스킹됩니다.

민감한 정보를 다루는 태스크에는 반드시 적용해야 합니다. 실제 현업에서는 이 테스트를 어떻게 확장할까요?

대부분의 팀은 Molecule이나 TestInfra 같은 도구를 사용합니다. Ansible 롤을 자동으로 테스트하고, 인프라 상태를 검증하는 테스트를 작성할 수 있습니다.

CI/CD 파이프라인에 통합하면 변경 사항이 푸시될 때마다 자동으로 테스트됩니다. 주의할 점도 있습니다.

테스트 환경에서 성공했다고 프로덕션에서도 성공하리라는 보장은 없습니다. 네트워크 설정, 보안 그룹, DNS 설정 등이 다를 수 있습니다.

프로덕션과 최대한 유사한 스테이징 환경을 만들어 테스트하는 것이 중요합니다. 김개발 씨가 터미널에 명령어를 입력했습니다.

ansible-playbook site.yml. 화면에 태스크들이 순서대로 실행되고, 마지막에 "Deployment Completed Successfully!" 메시지가 출력되었습니다.

박시니어 씨가 어깨를 두드려 주었습니다. "축하해요, 김개발 씨.

이제 서버 10대를 설정하는 데 2분이면 돼요. 예전에는 하루 종일 걸렸는데 말이에요." 김개발 씨는 뿌듯했습니다.

인프라 자동화의 세계에 첫발을 내디딘 것입니다. 이제 야근 없이도 안정적인 서비스를 운영할 수 있게 되었습니다.

실전 팁

💡 - 마스터 플레이북은 멱등성을 보장해야 합니다. 여러 번 실행해도 결과가 동일해야 합니다

  • 배포 실패 시 롤백 전략을 미리 준비해 두는 것이 좋습니다
  • CI/CD 파이프라인과 연동하여 코드 변경 시 자동으로 테스트하고 배포하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Ansible#Infrastructure as Code#DevOps#Automation#YAML#Ansible,IaC,DevOps

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스