🤖

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

⚠️

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

A

AI Generated

2025. 12. 10. · 55 Views

설치 후 초기 설정 자동화 완벽 가이드

Ansible을 활용하여 새로운 서버의 timezone, locale, 필수 패키지, 보안 설정을 자동으로 구성하는 방법을 배웁니다. 점프 투 자바 스타일로 술술 읽히는 DevOps 입문서입니다.


목차

  1. Ansible 기본 구조와 인벤토리
  2. timezone과 locale 설정 자동화
  3. 필수 패키지 설치 playbook
  4. sudo 권한과 사용자 관리
  5. firewalld/ufw 기본 규칙 설정
  6. SSH key-only 인증 강제화
  7. os-init.yml playbook 완성

1. Ansible 기본 구조와 인벤토리

김개발 씨는 오늘도 새로운 서버 3대를 셋업해야 합니다. 지난주에도 5대, 그 전주에도 7대를 일일이 손으로 설정했습니다.

선배 박시니어 씨가 다가와 물었습니다. "아직도 손으로 하나씩 설정하고 있어요?"

Ansible은 서버 설정을 자동화하는 도구입니다. 마치 요리 레시피를 한 번 작성해두면 똑같은 요리를 계속 만들 수 있는 것처럼, 서버 설정 스크립트를 한 번 작성하면 수백 대의 서버를 동일하게 구성할 수 있습니다.

인벤토리는 관리할 서버 목록을 담은 파일이며, 플레이북은 실제 작업 내용을 담은 설정 파일입니다.

다음 코드를 살펴봅시다.

# inventory/hosts.ini
[webservers]
web1 ansible_host=192.168.1.10 ansible_user=admin
web2 ansible_host=192.168.1.11 ansible_user=admin

[dbservers]
db1 ansible_host=192.168.1.20 ansible_user=admin

# 연결 테스트
# ansible -i inventory/hosts.ini all -m ping

김개발 씨는 입사 2개월 차 주니어 DevOps 엔지니어입니다. 오늘도 팀장님으로부터 "새로운 웹서버 3대 셋업 부탁해요"라는 요청을 받았습니다.

지난 2주 동안 이미 12대의 서버를 손으로 하나씩 설정했습니다. 각 서버에 SSH로 접속해서 timezone을 설정하고, 필수 패키지를 설치하고, 방화벽을 구성하고, SSH 보안 설정을 변경하는 작업을 반복했습니다.

한 대당 평균 30분이 걸렸고, 실수로 설정을 빠뜨려서 나중에 문제가 발생한 적도 있었습니다. 선배 박시니어 씨가 김개발 씨의 모니터를 보더니 고개를 가로저었습니다.

"아직도 손으로 하나씩 설정하고 있어요? Ansible을 배워보세요.

설정 파일 하나로 수백 대를 한 번에 구성할 수 있어요." 그렇다면 Ansible이란 정확히 무엇일까요? 쉽게 비유하자면, Ansible은 마치 요리 레시피와 같습니다.

김치찌개를 만드는 레시피를 한 번 작성해두면, 그 레시피대로 계속해서 똑같은 맛의 김치찌개를 만들 수 있습니다. 재료의 양도 정확하고, 순서도 명확하며, 누가 만들어도 비슷한 결과가 나옵니다.

Ansible도 마찬가지입니다. 서버 설정 방법을 한 번 플레이북이라는 파일에 작성해두면, 그 플레이북을 실행할 때마다 동일한 설정이 적용됩니다.

1대든 100대든 상관없이 말이죠. Ansible이 없던 시절에는 어땠을까요?

시스템 관리자들은 서버마다 일일이 접속해서 명령어를 입력해야 했습니다. 서버가 3대라면 같은 작업을 3번 반복해야 했고, 100대라면 100번을 반복해야 했습니다.

더 큰 문제는 사람이 하는 작업이다 보니 실수가 발생한다는 점이었습니다. 예를 들어 서버 10대 중 7대는 timezone을 Asia/Seoul로 설정했는데, 깜빡하고 3대는 UTC로 남겨둔 경우를 생각해봅시다.

나중에 로그 시간을 비교할 때 혼란이 발생합니다. 또한 보안 패치를 적용할 때도 일부 서버를 빠뜨릴 수 있습니다.

바로 이런 문제를 해결하기 위해 Ansible이 등장했습니다. Ansible을 사용하면 일관성이 보장됩니다.

플레이북에 작성된 내용이 모든 서버에 동일하게 적용되므로, 어떤 서버는 설정이 있고 어떤 서버는 없는 상황이 발생하지 않습니다. 또한 재사용성도 뛰어납니다.

한 번 작성한 플레이북은 계속해서 사용할 수 있습니다. 무엇보다 시간 절약이라는 큰 이점이 있습니다.

김개발 씨처럼 서버 한 대당 30분씩 걸리던 작업이 플레이북 실행 한 번으로 끝납니다. Ansible의 기본 구조를 이해하려면 두 가지 핵심 개념을 알아야 합니다.

첫 번째는 인벤토리입니다. 인벤토리는 관리할 서버 목록을 담은 파일입니다.

마치 전화번호부처럼 "webservers 그룹에는 web1, web2가 있고, dbservers 그룹에는 db1이 있다"는 정보를 담고 있습니다. 위의 코드를 살펴보면, [webservers]라는 그룹 아래에 web1과 web2 서버가 정의되어 있습니다.

각 서버마다 ansible_host로 IP 주소를, ansible_user로 접속할 사용자 계정을 지정합니다. 두 번째는 플레이북입니다.

플레이북은 실제로 수행할 작업을 YAML 형식으로 작성한 파일입니다. "이 서버들에 nginx를 설치하고, 설정 파일을 복사하고, 서비스를 시작하라"는 식의 지시사항이 담깁니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 스타트업에서 갑자기 트래픽이 10배 증가했다고 가정해봅시다.

급하게 웹서버를 20대 추가로 구성해야 합니다. Ansible이 없다면 김개발 씨는 밤을 새워야 할 것입니다.

하지만 Ansible이 있다면 인벤토리에 새로운 서버 20대를 추가하고, 플레이북을 실행하면 끝입니다. 많은 기업에서 이런 패턴을 적극적으로 사용하고 있습니다.

Netflix, NASA, Red Hat 등 대규모 인프라를 운영하는 곳에서는 Ansible 없이는 상상할 수 없을 정도입니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 인벤토리 파일의 IP 주소를 잘못 입력하는 것입니다. 오타 하나로 엉뚱한 서버에 작업이 적용되거나, 연결이 안 되어 전체 작업이 실패할 수 있습니다.

따라서 먼저 ansible -m ping 명령으로 연결을 테스트한 후 본격적인 작업을 진행해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 Ansible이라는 도구를 사용하는 거군요!" Ansible의 기본 구조를 제대로 이해하면 반복적인 서버 설정 작업에서 해방될 수 있습니다.

여러분도 오늘 배운 인벤토리와 플레이북 개념을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 인벤토리 파일에 서버를 추가한 후 반드시 ansible -m ping으로 연결을 테스트하세요

  • 서버를 용도별로 그룹화하면 관리가 훨씬 편리합니다 (webservers, dbservers, cacheservers 등)
  • 민감한 정보(비밀번호, API 키)는 Ansible Vault로 암호화하여 저장하세요

2. timezone과 locale 설정 자동화

김개발 씨가 새로 구성한 서버에서 로그를 확인하던 중 이상한 점을 발견했습니다. 분명 오후 3시에 발생한 에러인데 로그에는 오전 6시로 기록되어 있었습니다.

박시니어 씨가 웃으며 말했습니다. "timezone 설정 안 했죠?"

Timezone은 서버가 사용할 시간대를 설정하는 것이며, locale은 언어와 지역 설정을 의미합니다. 서버의 시간대가 잘못 설정되면 로그 시간이 뒤죽박죽이 되고, locale이 잘못되면 한글이 깨지는 문제가 발생합니다.

Ansible의 timezone 모듈과 locale_gen 모듈을 사용하면 이 설정을 자동화할 수 있습니다.

다음 코드를 살펴봅시다.

# playbooks/set-timezone-locale.yml
---
- name: Configure timezone and locale
  hosts: all
  become: yes
  tasks:
    - name: Set timezone to Asia/Seoul
      community.general.timezone:
        name: Asia/Seoul

    - name: Generate ko_KR.UTF-8 locale
      community.general.locale_gen:
        name: ko_KR.UTF-8
        state: present

    - name: Set default locale
      lineinfile:
        path: /etc/default/locale
        regexp: '^LANG='
        line: 'LANG=ko_KR.UTF-8'

김개발 씨는 어제 구성한 서버 5대에서 애플리케이션 에러를 추적하고 있었습니다. 각 서버의 로그를 시간순으로 정렬해서 문제가 어디서 시작되었는지 파악하려는 중이었습니다.

그런데 이상한 일이 발생했습니다. web1 서버의 로그는 "15:23:45"에 에러가 발생했다고 나오는데, web2 서버는 "06:23:45"로 기록되어 있었습니다.

분명 같은 시각에 발생한 에러인데 시간이 9시간 차이가 났습니다. 박시니어 씨가 김개발 씨의 화면을 보더니 바로 원인을 찾아냈습니다.

"timezone 설정 안 했죠? web1은 Asia/Seoul로 설정되어 있는데 web2는 UTC로 되어 있나 봐요." 그렇다면 timezone과 locale 설정은 왜 중요할까요?

쉽게 비유하자면, timezone은 마치 시계를 어느 나라 시간에 맞출지 정하는 것과 같습니다. 한국에 사는 사람이 시계를 미국 시간으로 맞춰두면 약속 시간을 헷갈리게 됩니다.

서버도 마찬가지입니다. 서버가 기록하는 모든 로그, 모든 타임스탬프는 timezone 설정에 따라 달라집니다.

locale은 언어와 문자 인코딩 설정입니다. 한글을 제대로 표시하려면 UTF-8 인코딩을 지원하는 locale이 필요합니다.

잘못 설정하면 "안녕하세요"가 "¾È³çÇϼ¼¿ä" 같은 깨진 글자로 나타납니다. timezone과 locale을 손으로 설정하던 시절에는 어땠을까요?

관리자는 각 서버에 접속해서 timedatectl set-timezone Asia/Seoul 명령을 입력해야 했습니다. locale 설정도 마찬가지로 /etc/default/locale 파일을 직접 편집해야 했습니다.

서버가 많아질수록 이 작업은 지루하고 실수하기 쉬웠습니다. 더 큰 문제는 나중에 확인하기 어렵다는 점이었습니다.

"이 서버들 timezone 제대로 설정되어 있나?" 하고 의심이 들면 일일이 접속해서 확인해야 했습니다. 100대의 서버가 있다면 100번 확인해야 했죠.

바로 이런 문제를 해결하기 위해 Ansible로 자동화하는 것입니다. Ansible을 사용하면 플레이북 한 번 실행으로 모든 서버의 timezone과 locale이 일관되게 설정됩니다.

나중에 새로운 서버를 추가할 때도 같은 플레이북을 실행하면 되므로 설정을 빠뜨릴 일이 없습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 hosts: all은 인벤토리의 모든 서버에 이 작업을 적용한다는 의미입니다. become: yes는 sudo 권한으로 실행한다는 뜻입니다.

timezone 같은 시스템 설정은 관리자 권한이 필요하기 때문입니다. 첫 번째 task는 community.general.timezone 모듈을 사용하여 시간대를 Asia/Seoul로 설정합니다.

이 한 줄이 timedatectl set-timezone Asia/Seoul 명령을 대신합니다. 두 번째 task는 community.general.locale_gen 모듈로 ko_KR.UTF-8 locale을 생성합니다.

Ubuntu 같은 데비안 계열에서는 locale을 먼저 생성해야 사용할 수 있습니다. 세 번째 task는 lineinfile 모듈을 사용하여 /etc/default/locale 파일에 기본 언어를 설정합니다.

파일에서 LANG=로 시작하는 줄을 찾아서 LANG=ko_KR.UTF-8로 바꿉니다. 실제 현업에서는 어떻게 활용할까요?

글로벌 서비스를 운영하는 회사를 생각해봅시다. 한국 리전의 서버는 Asia/Seoul을, 미국 리전의 서버는 America/New_York을 사용해야 합니다.

인벤토리에서 서버를 리전별로 그룹화하고, 각 그룹에 맞는 timezone을 변수로 지정하면 됩니다. 많은 기업에서 서버 초기 설정 자동화의 첫 단계로 timezone과 locale 설정을 진행합니다.

이것이 제대로 되어 있어야 로그 분석, 스케줄링, 데이터베이스 타임스탬프 등 모든 것이 정확하게 작동하기 때문입니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 timezone만 변경하고 시스템을 재시작하지 않아서 일부 서비스가 여전히 이전 시간대로 동작하는 경우입니다. 특히 Java 애플리케이션은 JVM이 시작할 때 timezone을 읽어오므로, timezone을 변경한 후에는 애플리케이션을 재시작해야 합니다.

또 다른 실수는 locale 설정 후 환경변수를 제대로 적용하지 않는 것입니다. /etc/default/locale 파일을 수정한 후에는 새로운 SSH 세션에서 적용되므로, 현재 세션에서 바로 확인하려면 source /etc/default/locale 명령이 필요합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨가 작성해준 플레이북을 실행한 김개발 씨는 모든 서버의 로그 시간이 일치하는 것을 확인했습니다.

"이제 에러 추적이 훨씬 쉬워졌네요!" timezone과 locale을 Ansible로 자동화하면 일관된 환경을 유지할 수 있고, 로그 분석과 디버깅이 훨씬 편리해집니다. 여러분도 서버를 새로 구성할 때 가장 먼저 이 설정을 적용해 보세요.

실전 팁

💡 - timezone 변경 후에는 실행 중인 애플리케이션을 재시작해야 적용됩니다

  • locale 설정 시 UTF-8을 사용하면 한글뿐 아니라 모든 언어를 지원할 수 있습니다
  • 글로벌 서비스라면 서버 리전별로 다른 timezone을 변수로 관리하세요

3. 필수 패키지 설치 playbook

새로운 서버를 받은 김개발 씨는 설치해야 할 패키지 목록을 적은 메모를 꺼냈습니다. vim, git, curl, htop, net-tools...

목록이 20개가 넘습니다. 박시니어 씨가 말했습니다.

"그거 플레이북으로 만들어두면 매번 편하게 쓸 수 있어요."

서버를 새로 구성할 때마다 반복적으로 설치하는 패키지들이 있습니다. vim, git, curl 같은 기본 도구부터 모니터링 도구인 htop, 네트워크 도구인 net-tools까지 다양합니다.

Ansible의 apt 모듈이나 yum 모듈을 사용하면 패키지 목록을 플레이북에 작성해두고 한 번에 설치할 수 있습니다.

다음 코드를 살펴봅시다.

# playbooks/install-packages.yml
---
- name: Install essential packages
  hosts: all
  become: yes
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_os_family == "Debian"

    - name: Install essential packages
      apt:
        name:
          - vim
          - git
          - curl
          - htop
          - net-tools
          - unzip
          - wget
        state: present
      when: ansible_os_family == "Debian"

김개발 씨는 오늘 새로 프로비저닝된 Ubuntu 서버 10대를 받았습니다. 각 서버에는 운영체제만 깔려 있을 뿐, 개발과 운영에 필요한 도구는 아무것도 설치되어 있지 않았습니다.

김개발 씨는 노트북 메모장에 저장해둔 "신규 서버 설치 목록"을 열었습니다. vim, git, curl, wget, htop, net-tools, unzip...

목록이 20개가 넘었습니다. 한 대당 apt install 명령을 20번씩 입력해야 한다면, 10대는 200번입니다.

박시니어 씨가 김개발 씨의 화면을 보더니 한숨을 쉬었습니다. "아직도 손으로 하나씩 설치하려고요?

그거 플레이북으로 만들어두면 앞으로 계속 쓸 수 있어요." 그렇다면 패키지 설치를 플레이북으로 관리하면 무엇이 좋을까요? 쉽게 비유하자면, 마치 이사 갈 때 체크리스트를 만들어두는 것과 같습니다.

"전입신고, 인터넷 설치, 가스 개통, 우편물 전달 신청..." 이런 목록을 만들어두면 이사할 때마다 빠뜨리지 않고 처리할 수 있습니다. 서버 패키지 설치도 마찬가지입니다.

"vim, git, curl, htop..." 이런 목록을 플레이북에 작성해두면, 새로운 서버를 받을 때마다 플레이북 한 번 실행으로 모든 패키지가 설치됩니다. 10대든 100대든 시간은 동일합니다.

패키지 설치를 손으로 하던 시절에는 어땠을까요? 시스템 관리자는 각 서버에 접속해서 apt install vim 같은 명령을 반복해서 입력해야 했습니다.

특히 패키지가 많을 때는 한 줄로 여러 개를 설치하려고 apt install vim git curl wget htop... 같은 긴 명령을 타이핑했습니다. 더 큰 문제는 나중에 추가 패키지가 필요할 때였습니다.

"이 서버들에 jq 패키지가 설치되어 있나?" 하고 의심이 들면 일일이 확인해야 했고, 없으면 다시 설치해야 했습니다. 서버마다 설치된 패키지가 조금씩 달라지는 것도 흔한 일이었습니다.

바로 이런 문제를 해결하기 위해 Ansible로 패키지 설치를 자동화하는 것입니다. Ansible을 사용하면 멱등성이 보장됩니다.

플레이북을 여러 번 실행해도 결과는 같습니다. 이미 설치된 패키지는 건너뛰고, 없는 패키지만 설치합니다.

따라서 안심하고 플레이북을 반복 실행할 수 있습니다. 또한 문서화 효과도 있습니다.

플레이북을 보면 "우리 서버에는 이런 패키지들이 설치되어 있구나"를 한눈에 알 수 있습니다. 신입 직원이 입사했을 때도 플레이북을 보여주면 됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. 첫 번째 task는 apt 모듈의 update_cache 옵션으로 패키지 목록을 업데이트합니다.

이것은 apt update 명령과 동일합니다. cache_valid_time: 3600은 캐시가 1시간(3600초) 이내에 업데이트되었다면 건너뛴다는 의미입니다.

불필요한 네트워크 트래픽을 줄여줍니다. when: ansible_os_family == "Debian"은 조건문입니다.

Ubuntu나 Debian 같은 데비안 계열 OS에서만 이 task를 실행합니다. 만약 CentOS 같은 Red Hat 계열이 섞여 있다면 이 task는 건너뜁니다.

두 번째 task는 실제로 패키지를 설치합니다. name 항목에 패키지 이름을 리스트로 나열하면 한 번에 여러 개를 설치할 수 있습니다.

state: present는 "이 패키지가 설치되어 있는 상태"를 의미합니다. 없으면 설치하고, 있으면 건너뜁니다.

실제 현업에서는 어떻게 활용할까요? 스타트업에서 새로운 마이크로서비스를 런칭한다고 가정해봅시다.

개발 환경, 스테이징 환경, 프로덕션 환경에 각각 서버를 구성해야 합니다. 총 15대의 서버가 필요합니다.

과거에는 김개발 씨가 하루 종일 서버에 접속해서 패키지를 설치했을 것입니다. 하지만 Ansible 플레이북이 있다면 15대 모두에 5분 만에 동일한 패키지를 설치할 수 있습니다.

많은 기업에서 "골든 이미지" 대신 Ansible 플레이북을 사용합니다. 골든 이미지는 모든 것이 설치된 서버 이미지를 말하는데, 시간이 지나면 오래되고 관리가 어렵습니다.

반면 플레이북은 항상 최신 상태로 패키지를 설치하므로 유지보수가 쉽습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 update_cache를 생략하는 것입니다. 패키지 목록이 오래되면 "패키지를 찾을 수 없다"는 에러가 발생할 수 있습니다.

특히 새로운 버전의 패키지를 설치하려고 할 때 문제가 됩니다. 또 다른 실수는 OS에 따른 분기 처리를 안 하는 것입니다.

Ubuntu는 apt를, CentOS는 yum을 사용합니다. 인벤토리에 여러 OS가 섞여 있다면 when 조건문으로 분기해야 합니다.

그렇지 않으면 잘못된 패키지 매니저를 사용해서 에러가 발생합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨가 알려준 플레이북을 실행한 김개발 씨는 10대의 서버에 패키지가 일괄 설치되는 것을 지켜봤습니다. "5분 만에 끝났네요!

손으로 했으면 한 시간은 걸렸을 텐데..." 필수 패키지 설치를 플레이북으로 관리하면 시간을 절약할 수 있고, 모든 서버가 동일한 환경을 가지도록 보장할 수 있습니다. 여러분도 자주 사용하는 패키지 목록을 플레이북으로 만들어 보세요.

실전 팁

💡 - apt update를 먼저 실행하지 않으면 패키지를 찾지 못할 수 있습니다

  • 개발 서버와 운영 서버에 설치할 패키지가 다르다면 인벤토리 그룹별로 별도의 task를 작성하세요
  • state를 latest로 설정하면 항상 최신 버전으로 업데이트되지만, 의도치 않은 버전 변경이 발생할 수 있으므로 주의하세요

4. sudo 권한과 사용자 관리

김개발 씨는 새로운 팀원이 입사할 때마다 서버에 계정을 만들고 sudo 권한을 부여해야 했습니다. 이번 달에만 3명이 입사해서 15대 서버에 각각 계정을 만들었습니다.

박시니어 씨가 물었습니다. "이것도 자동화하지 그래요?"

서버에 새로운 사용자를 추가하고 sudo 권한을 부여하는 작업은 보안상 매우 중요합니다. Ansible의 user 모듈로 계정을 생성하고, authorized_key 모듈로 SSH 공개키를 등록하며, lineinfile 모듈로 sudoers 파일을 수정할 수 있습니다.

이렇게 하면 신규 직원 입사 시 모든 서버에 일관된 계정을 자동으로 생성할 수 있습니다.

다음 코드를 살펴봅시다.

# playbooks/manage-users.yml
---
- name: Manage users and sudo access
  hosts: all
  become: yes
  vars:
    admin_users:
      - name: devops1
        ssh_key: "ssh-rsa AAAAB3... user@example.com"
  tasks:
    - name: Create admin users
      user:
        name: "{{ item.name }}"
        shell: /bin/bash
        groups: sudo
        append: yes
      loop: "{{ admin_users }}"

    - name: Add SSH keys for admin users
      authorized_key:
        user: "{{ item.name }}"
        key: "{{ item.ssh_key }}"
      loop: "{{ admin_users }}"

    - name: Allow sudo group to run sudo without password
      lineinfile:
        path: /etc/sudoers.d/90-cloud-init-users
        line: '%sudo ALL=(ALL) NOPASSWD:ALL'
        create: yes
        validate: 'visudo -cf %s'

김개발 씨는 오늘 아침 인사팀으로부터 메일을 받았습니다. "이번 주에 DevOps 엔지니어 3명이 입사합니다.

서버 접근 권한 설정 부탁드립니다." 회사에는 개발 서버 10대, 스테이징 서버 3대, 운영 서버 5대가 있습니다. 김개발 씨는 한숨을 쉬었습니다.

3명 × 18대 = 54번의 계정 생성 작업이 기다리고 있었습니다. 각 서버에 접속해서 useradd 명령으로 계정을 만들고, SSH 공개키를 ~/.ssh/authorized_keys에 추가하고, sudoers 파일을 수정해야 합니다.

박시니어 씨가 김개발 씨의 고민을 듣더니 말했습니다. "사용자 관리도 Ansible로 자동화하면 5분 만에 끝나요." 그렇다면 사용자 관리를 왜 자동화해야 할까요?

쉽게 비유하자면, 마치 회사의 출입증을 발급하는 시스템과 같습니다. 신입사원이 입사할 때마다 수동으로 출입증을 만들고, 각 층의 출입문마다 일일이 등록하는 것은 비효율적입니다.

대신 중앙 시스템에서 한 번 등록하면 모든 출입문에서 자동으로 인식되도록 하는 것이 좋습니다. 서버 사용자 관리도 마찬가지입니다.

플레이북에 신규 직원 정보를 추가하고 실행하면, 모든 서버에 동일한 계정이 생성되고 동일한 권한이 부여됩니다. 실수로 일부 서버에만 계정을 만들거나, 권한 설정을 빠뜨리는 일이 없습니다.

사용자 관리를 손으로 하던 시절에는 어땠을까요? 시스템 관리자는 각 서버에 접속해서 useradd 사용자명 명령을 입력하고, usermod -aG sudo 사용자명으로 sudo 그룹에 추가했습니다.

그 다음 사용자의 홈 디렉토리에 .ssh 폴더를 만들고, authorized_keys 파일에 공개키를 복사했습니다. 권한도 정확히 설정해야 했습니다.

(.ssh는 700, authorized_keys는 600) 더 큰 문제는 퇴사자 관리였습니다. 누군가 퇴사하면 모든 서버에서 일일이 계정을 삭제하거나 비활성화해야 했습니다.

하나라도 빠뜨리면 보안 위험이 발생합니다. 바로 이런 문제를 해결하기 위해 Ansible로 사용자 관리를 자동화하는 것입니다.

Ansible을 사용하면 일관성이 보장됩니다. 플레이북에 정의된 사용자는 모든 서버에 동일하게 생성됩니다.

또한 플레이북이 문서 역할을 합니다. "현재 우리 서버에 접근 권한이 있는 사람은 누구인가?"라는 질문에 플레이북 파일을 보여주면 됩니다.

무엇보다 보안 감사가 쉬워집니다. Git으로 플레이북을 관리하면 누가 언제 어떤 사용자를 추가했는지 모든 이력이 남습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 vars 섹션에서 admin_users 변수를 정의합니다.

이 리스트에 관리자 계정 정보를 담습니다. 새로운 직원이 입사하면 이 리스트에 항목을 추가하기만 하면 됩니다.

첫 번째 task는 user 모듈로 계정을 생성합니다. shell: /bin/bash는 기본 쉘을 bash로 설정하는 것이고, groups: sudo는 사용자를 sudo 그룹에 추가합니다.

append: yes는 기존 그룹을 유지하면서 sudo 그룹을 추가한다는 의미입니다. loop을 사용하여 리스트의 각 사용자에 대해 반복합니다.

두 번째 task는 authorized_key 모듈로 SSH 공개키를 등록합니다. 이렇게 하면 사용자가 비밀번호 없이 SSH 키로 로그인할 수 있습니다.

보안을 위해 비밀번호 로그인은 비활성화하고 키 기반 인증만 허용하는 것이 모범 사례입니다. 세 번째 task는 lineinfile 모듈로 sudoers 파일을 수정합니다.

%sudo ALL=(ALL) NOPASSWD:ALL이라는 줄을 추가하면 sudo 그룹의 모든 사용자가 비밀번호 없이 sudo를 사용할 수 있습니다. validate: 'visudo -cf %s'는 중요한 옵션입니다.

sudoers 파일은 문법 오류가 있으면 시스템에 접근할 수 없게 되므로, 적용 전에 검증합니다. 실제 현업에서는 어떻게 활용할까요?

대규모 조직에서는 LDAP이나 Active Directory 같은 중앙 인증 시스템을 사용하기도 하지만, 중소기업이나 스타트업에서는 Ansible로 사용자를 관리하는 것이 더 간단합니다. 예를 들어 신규 DevOps 엔지니어가 3명 입사했다면, 플레이북의 admin_users 리스트에 3개 항목을 추가하고 플레이북을 실행하면 됩니다.

몇 분 안에 모든 서버에 계정이 생성됩니다. 퇴사자가 발생하면 리스트에서 제거하고, state: absent 옵션으로 계정을 삭제하는 플레이북을 실행합니다.

많은 기업에서 사용자 관리 플레이북을 Git 저장소로 관리하고, 변경 시 코드 리뷰를 거칩니다. "김개발 씨가 퇴사했으니 계정을 삭제해주세요"라는 요청이 오면, Pull Request를 만들고 리뷰를 받은 후 머지하는 식입니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 sudoers 파일을 직접 /etc/sudoers로 수정하는 것입니다.

이 파일은 매우 중요하므로, /etc/sudoers.d/ 디렉토리에 별도 파일을 만드는 것이 안전합니다. 위 예제처럼 /etc/sudoers.d/90-cloud-init-users 같은 파일을 만들면 됩니다.

또 다른 실수는 validate 옵션을 생략하는 것입니다. sudoers 파일에 문법 오류가 있으면 sudo를 사용할 수 없게 되고, 서버에 접근할 수 없게 될 수도 있습니다.

반드시 validate로 검증해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨가 알려준 플레이북으로 신규 직원 3명의 계정을 18대 서버에 일괄 생성한 김개발 씨는 놀라움을 감추지 못했습니다. "54번 작업할 것을 5분 만에 끝냈어요!" 사용자 관리를 Ansible로 자동화하면 일관성 있고 안전하게 계정을 관리할 수 있습니다.

여러분도 팀원 변동이 있을 때 플레이북으로 처리해 보세요.

실전 팁

💡 - SSH 키는 사용자별로 다르게 관리하고, 퇴사 시 즉시 제거하세요

  • sudoers 파일 수정 시 반드시 validate 옵션을 사용하여 문법 오류를 방지하세요
  • 사용자 목록을 Git으로 관리하고 변경 시 코드 리뷰를 거치면 보안 감사가 쉬워집니다

5. firewalld/ufw 기본 규칙 설정

김개발 씨가 구성한 서버가 갑자기 외부에서 접근이 안 되었습니다. 애플리케이션은 정상인데 브라우저에서 연결할 수 없었습니다.

박시니어 씨가 확인해보니 방화벽이 모든 포트를 막고 있었습니다. "방화벽 설정 안 했네요?"

방화벽은 서버의 네트워크 트래픽을 제어하여 보안을 강화하는 도구입니다. Ubuntu는 ufw를, CentOS/RHEL은 firewalld를 주로 사용합니다.

Ansible의 ufw 모듈이나 firewalld 모듈로 필요한 포트(SSH 22, HTTP 80, HTTPS 443 등)만 열고 나머지는 차단하는 규칙을 자동으로 설정할 수 있습니다.

다음 코드를 살펴봅시다.

# playbooks/configure-firewall.yml
---
- name: Configure firewall rules
  hosts: all
  become: yes
  tasks:
    - name: Install and enable ufw (Ubuntu/Debian)
      apt:
        name: ufw
        state: present
      when: ansible_os_family == "Debian"

    - name: Allow SSH port 22
      ufw:
        rule: allow
        port: '22'
        proto: tcp
      when: ansible_os_family == "Debian"

    - name: Allow HTTP and HTTPS
      ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - '80'
        - '443'
      when: ansible_os_family == "Debian"

    - name: Enable ufw
      ufw:
        state: enabled
      when: ansible_os_family == "Debian"

김개발 씨는 새로운 웹서버를 구성하고 Nginx를 설치한 후 애플리케이션을 배포했습니다. 로컬에서 curl로 테스트하니 정상적으로 응답이 왔습니다.

"좋아, 이제 서비스 오픈이다!" 김개발 씨는 팀원들에게 서버 주소를 공유했습니다. 그런데 5분 후 팀원들로부터 메시지가 쏟아졌습니다.

"접속이 안 되는데요?" "연결 시간 초과 에러가 나요." 김개발 씨는 당황해서 서버에 접속해 Nginx 상태를 확인했습니다. 정상 작동 중이었습니다.

박시니어 씨가 김개발 씨의 화면을 보더니 바로 원인을 찾았습니다. "방화벽 확인해 봤어요?

ufw status 쳐보세요." 확인해보니 방화벽이 활성화되어 있었고, SSH 포트 22번만 열려 있었습니다. HTTP 포트 80번과 HTTPS 포트 443번이 막혀 있었습니다.

그렇다면 방화벽 설정은 왜 중요할까요? 쉽게 비유하자면, 방화벽은 마치 건물의 경비원과 같습니다.

모든 사람이 무분별하게 건물에 들어오면 위험하므로, 경비원은 출입증이 있는 사람만 통과시킵니다. 특정 층은 특정 권한이 있는 사람만 출입할 수 있도록 제한합니다.

서버의 방화벽도 마찬가지입니다. 모든 포트를 열어두면 해커가 취약한 서비스를 공격할 수 있습니다.

반대로 모든 포트를 막으면 정상적인 서비스도 동작하지 않습니다. 따라서 필요한 포트만 선택적으로 열어야 합니다.

방화벽 설정을 손으로 하던 시절에는 어땠을까요? 시스템 관리자는 각 서버에 접속해서 ufw allow 22/tcp, ufw allow 80/tcp 같은 명령을 입력했습니다.

웹서버라면 80과 443을 열고, 데이터베이스 서버라면 3306(MySQL) 또는 5432(PostgreSQL)를 열었습니다. 더 큰 문제는 일관성이었습니다.

서버 A는 관리자가 꼼꼼하게 필요한 포트만 열었는데, 서버 B는 귀찮아서 방화벽을 아예 비활성화했다고 가정해봅시다. 서버 B는 보안 취약점이 됩니다.

실제로 많은 해킹 사고가 방화벽 미설정으로 인해 발생합니다. 바로 이런 문제를 해결하기 위해 Ansible로 방화벽 설정을 자동화하는 것입니다.

Ansible을 사용하면 모든 서버에 동일한 방화벽 규칙이 적용됩니다. 웹서버 그룹에는 80과 443을 열고, 데이터베이스 그룹에는 내부 네트워크에서만 3306을 열도록 설정할 수 있습니다.

한 번 작성한 규칙은 계속 재사용됩니다. 또한 검증이 쉬워집니다.

"모든 서버에 방화벽이 제대로 설정되어 있나?" 하고 의심이 들면 플레이북을 다시 실행하면 됩니다. 이미 올바르게 설정된 서버는 건너뛰고, 잘못된 서버만 수정됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. 첫 번째 task는 ufw 패키지를 설치합니다.

일부 최소 설치 Ubuntu에는 ufw가 없을 수 있으므로 먼저 설치합니다. when: ansible_os_family == "Debian"으로 데비안 계열에서만 실행됩니다.

두 번째 task는 SSH 포트 22번을 허용합니다. 이것은 매우 중요합니다.

만약 SSH 포트를 열지 않은 채 방화벽을 활성화하면, 서버에 접속할 수 없게 됩니다. 원격에서 작업할 때는 반드시 SSH 포트를 먼저 열어야 합니다.

세 번째 task는 HTTP 포트 80과 HTTPS 포트 443을 허용합니다. loop을 사용하여 여러 포트를 한 번에 처리합니다.

웹서버라면 이 두 포트가 필수입니다. 네 번째 task는 방화벽을 활성화합니다.

state: enabled로 설정하면 ufw가 시작되고, 시스템 부팅 시 자동으로 활성화됩니다. 실제 현업에서는 어떻게 활용할까요?

서버 역할에 따라 다른 방화벽 규칙을 적용합니다. 웹서버는 80/443을 열고, 애플리케이션 서버는 8080을 열고, 데이터베이스 서버는 특정 IP 대역에서만 3306을 열도록 설정합니다.

예를 들어 데이터베이스 서버는 공개 인터넷에 노출되면 안 됩니다. 오직 애플리케이션 서버의 내부 IP(예: 10.0.1.0/24)에서만 접근할 수 있도록 제한합니다.

ufw에서는 from 10.0.1.0/24 to any port 3306 같은 규칙으로 구현합니다. 많은 기업에서 기본 방화벽 규칙을 플레이북으로 관리하고, 특수한 경우에만 수동으로 규칙을 추가합니다.

모든 변경 사항은 Git 커밋으로 기록되어 감사 추적이 가능합니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 SSH 포트를 열지 않은 채 방화벽을 활성화하는 것입니다. 원격 서버에서 이렇게 하면 접속이 끊기고 다시 연결할 수 없게 됩니다.

데이터센터에 직접 가서 콘솔로 접속해야 하는 상황이 발생할 수 있습니다. 또 다른 실수는 방화벽 규칙의 순서를 고려하지 않는 것입니다.

ufw는 비교적 단순하지만, iptables나 firewalld 같은 도구는 규칙 순서가 중요합니다. 먼저 매치되는 규칙이 적용되므로, "모든 트래픽 차단" 규칙이 위에 있으면 아래의 허용 규칙이 무시됩니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 도움으로 방화벽에 80과 443 포트를 추가한 김개발 씨는 정상적으로 외부에서 접속되는 것을 확인했습니다.

"앞으로는 플레이북에 방화벽 설정을 포함시켜야겠어요." 방화벽 설정을 Ansible로 자동화하면 보안을 강화하면서도 일관성을 유지할 수 있습니다. 여러분도 서버 초기 설정에 방화벽 규칙을 포함시켜 보세요.

실전 팁

💡 - 원격 서버에서 방화벽을 설정할 때는 반드시 SSH 포트를 먼저 허용하세요

  • 데이터베이스 같은 민감한 서비스는 특정 IP 대역에서만 접근하도록 제한하세요
  • 방화벽 규칙 변경 후에는 실제로 외부에서 접근 테스트를 해보세요

6. SSH key-only 인증 강제화

김개발 씨는 서버 로그를 확인하다가 이상한 로그인 시도가 수천 건 기록된 것을 발견했습니다. 중국, 러시아 등 다양한 IP에서 무차별 대입 공격을 시도하고 있었습니다.

박시니어 씨가 말했습니다. "비밀번호 인증을 비활성화하고 SSH 키만 사용하도록 설정해야 해요."

SSH 키 기반 인증은 비밀번호 대신 공개키와 개인키 쌍으로 서버에 접속하는 방식입니다. 비밀번호는 무차별 대입 공격에 취약하지만, SSH 키는 사실상 뚫을 수 없습니다.

Ansible의 lineinfile 모듈로 /etc/ssh/sshd_config 파일을 수정하여 비밀번호 인증을 비활성화하고 키 인증만 허용할 수 있습니다.

다음 코드를 살펴봅시다.

# playbooks/ssh-hardening.yml
---
- name: Harden SSH configuration
  hosts: all
  become: yes
  tasks:
    - name: Disable password authentication
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PasswordAuthentication'
        line: 'PasswordAuthentication no'
        state: present
      notify: restart sshd

    - name: Disable root login
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PermitRootLogin'
        line: 'PermitRootLogin no'
        state: present
      notify: restart sshd

    - name: Enable public key authentication
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PubkeyAuthentication'
        line: 'PubkeyAuthentication yes'
        state: present
      notify: restart sshd

  handlers:
    - name: restart sshd
      service:
        name: sshd
        state: restarted

김개발 씨는 월요일 아침 출근해서 서버 모니터링 대시보드를 확인했습니다. 평소보다 CPU 사용률이 약간 높아 보였습니다.

이상하다는 생각에 SSH 로그를 열어봤습니다. 로그 파일에는 수천 개의 로그인 실패 기록이 있었습니다.

"Failed password for root from 123.45.67.89", "Failed password for admin from 98.76.54.32"... 전 세계의 다양한 IP 주소에서 root, admin, test 같은 흔한 계정명으로 로그인을 시도하고 있었습니다.

박시니어 씨가 김개발 씨의 화면을 보더니 심각한 표정을 지었습니다. "무차별 대입 공격이네요.

비밀번호 인증을 비활성화하고 SSH 키만 사용하도록 설정해야 해요. 지금 당장요." 그렇다면 SSH 키 기반 인증은 왜 안전할까요?

쉽게 비유하자면, 비밀번호는 마치 4자리 자물쇠 번호와 같습니다. 0000부터 9999까지 1만 가지 조합을 모두 시도하면 언젠가는 열립니다.

특히 "1234"나 "0000" 같은 흔한 번호를 사용하면 금방 뚫립니다. 반면 SSH 키는 마치 은행 금고의 열쇠와 같습니다.

열쇠가 없으면 아무리 시간을 들여도 열 수 없습니다. SSH 키는 2048비트 또는 4096비트 암호화를 사용하므로, 무차별 대입으로 뚫는 것은 사실상 불가능합니다.

SSH 비밀번호 인증을 사용하던 시절에는 어땠을까요? 시스템 관리자들은 복잡한 비밀번호를 설정하고, 주기적으로 변경했습니다.

"Xk9@mP2$qL4!" 같은 비밀번호를 메모장에 적어두고 복사해서 사용했습니다. 하지만 비밀번호는 본질적으로 취약합니다.

더 큰 문제는 인터넷에 연결된 서버는 하루에도 수천 번의 로그인 시도를 받는다는 것입니다. 봇들이 자동으로 흔한 비밀번호 목록을 시도합니다.

"password", "123456", "admin123" 같은 약한 비밀번호는 몇 분 안에 뚫립니다. 바로 이런 문제를 해결하기 위해 SSH 키 기반 인증을 강제화하는 것입니다.

SSH 키를 사용하면 비밀번호 입력 자체가 불가능합니다. 해커가 아무리 비밀번호를 시도해도 서버는 응답조차 하지 않습니다.

오직 올바른 개인키를 가진 사람만 접속할 수 있습니다. 또한 편리성도 좋아집니다.

비밀번호를 외울 필요가 없고, 매번 입력할 필요도 없습니다. SSH 에이전트를 사용하면 개인키를 한 번 로드해두고 계속 사용할 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 첫 번째 task는 /etc/ssh/sshd_config 파일에서 PasswordAuthentication 설정을 찾아서 no로 변경합니다.

regexp: '^#?PasswordAuthentication'은 주석 처리된 줄도 매치하여 교체합니다. 이렇게 하면 비밀번호로는 로그인할 수 없습니다.

두 번째 task는 root 계정의 로그인을 비활성화합니다. root는 모든 권한을 가진 계정이므로, 직접 로그인을 막고 일반 계정으로 로그인한 후 sudo를 사용하도록 강제하는 것이 안전합니다.

세 번째 task는 공개키 인증을 명시적으로 활성화합니다. 대부분의 시스템에서 기본값이지만, 명확하게 설정해두는 것이 좋습니다.

각 task마다 notify: restart sshd가 있습니다. SSH 설정을 변경한 후에는 sshd 서비스를 재시작해야 적용됩니다.

handlers 섹션에 정의된 restart sshd 핸들러가 호출됩니다. 핸들러의 중요한 특징은 여러 task가 notify해도 한 번만 실행된다는 것입니다.

세 개의 task가 모두 설정을 변경했다면, sshd는 마지막에 딱 한 번만 재시작됩니다. 효율적입니다.

실제 현업에서는 어떻게 활용할까요? 보안이 중요한 서비스를 운영하는 기업에서는 SSH 키 인증을 필수로 설정합니다.

금융, 의료, 전자상거래 등 민감한 데이터를 다루는 곳에서는 비밀번호 인증을 절대 허용하지 않습니다. 또한 PCI-DSS, ISO 27001 같은 보안 인증을 받으려면 SSH 키 인증 사용이 요구 사항에 포함되는 경우가 많습니다.

많은 기업에서 신규 서버를 프로비저닝할 때 이 플레이북을 자동으로 실행합니다. 서버가 생성되고 몇 분 안에 SSH 키 인증만 허용하도록 설정되어, 무차별 대입 공격의 위험에서 벗어납니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 SSH 키를 제대로 등록하지 않은 채 비밀번호 인증을 비활성화하는 것입니다.

이렇게 하면 자기 자신도 서버에 접속할 수 없게 됩니다. 반드시 먼저 SSH 키를 등록하고, 새 터미널에서 키로 로그인이 되는지 확인한 후에 비밀번호 인증을 비활성화해야 합니다.

또 다른 실수는 개인키를 안전하게 관리하지 않는 것입니다. 개인키는 절대로 다른 사람과 공유하면 안 되고, Git 저장소에 커밋해서도 안 됩니다.

개인키에는 반드시 암호를 설정하여 이중 보안을 적용하세요. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 도움으로 모든 서버에 SSH 키 인증을 강제화한 김개발 씨는 다음 날 로그를 확인했습니다. 로그인 시도는 여전히 수천 건이 있었지만, 모두 실패했고 서버는 안전했습니다.

"이제 마음 놓고 잘 수 있겠어요." SSH 키 기반 인증을 강제화하면 서버 보안이 비약적으로 향상됩니다. 여러분도 모든 서버에 이 설정을 적용해 보세요.

실전 팁

💡 - 비밀번호 인증을 비활성화하기 전에 반드시 SSH 키로 로그인이 되는지 새 터미널에서 테스트하세요

  • 개인키에는 암호를 설정하여 이중 보안을 적용하세요
  • root 직접 로그인도 함께 비활성화하여 보안을 강화하세요

7. os-init.yml playbook 완성

김개발 씨는 지난 일주일간 배운 내용을 하나의 플레이북으로 정리하고 있었습니다. timezone, locale, 패키지 설치, 사용자 관리, 방화벽, SSH 보안...

각각 따로 실행하던 작업들을 하나로 통합하고 싶었습니다. 박시니어 씨가 말했습니다.

"잘 생각했어요. 이제 완전한 os-init.yml을 만들어 봅시다."

지금까지 배운 모든 설정을 하나의 통합 플레이북으로 만들면 서버 초기 설정 전체를 자동화할 수 있습니다. role이나 include_tasks로 작업을 모듈화하면 재사용성이 높아지고 유지보수가 쉬워집니다.

한 번의 플레이북 실행으로 안전하고 일관된 서버 환경을 구축할 수 있습니다.

다음 코드를 살펴봅시다.

# playbooks/os-init.yml
---
- name: Complete OS initialization
  hosts: all
  become: yes
  vars:
    admin_users:
      - name: devops1
        ssh_key: "ssh-rsa AAAAB3... user@example.com"

  tasks:
    - name: Set timezone to Asia/Seoul
      community.general.timezone:
        name: Asia/Seoul

    - name: Generate and set locale
      block:
        - community.general.locale_gen:
            name: ko_KR.UTF-8
            state: present
        - lineinfile:
            path: /etc/default/locale
            regexp: '^LANG='
            line: 'LANG=ko_KR.UTF-8'

    - name: Update apt cache and install packages
      apt:
        update_cache: yes
        name:
          - vim
          - git
          - curl
          - htop
          - ufw
        state: present
      when: ansible_os_family == "Debian"

    - name: Configure users and SSH keys
      block:
        - user:
            name: "{{ item.name }}"
            shell: /bin/bash
            groups: sudo
            append: yes
          loop: "{{ admin_users }}"
        - authorized_key:
            user: "{{ item.name }}"
            key: "{{ item.ssh_key }}"
          loop: "{{ admin_users }}"

    - name: Configure firewall
      block:
        - ufw:
            rule: allow
            port: '22'
            proto: tcp
        - ufw:
            rule: allow
            port: "{{ item }}"
            proto: tcp
          loop: ['80', '443']
        - ufw:
            state: enabled
      when: ansible_os_family == "Debian"

    - name: Harden SSH
      block:
        - lineinfile:
            path: /etc/ssh/sshd_config
            regexp: '^#?PasswordAuthentication'
            line: 'PasswordAuthentication no'
        - lineinfile:
            path: /etc/ssh/sshd_config
            regexp: '^#?PermitRootLogin'
            line: 'PermitRootLogin no'
      notify: restart sshd

  handlers:
    - name: restart sshd
      service:
        name: sshd
        state: restarted

김개발 씨는 지난 일주일간 Ansible을 배우면서 여러 개의 플레이북을 작성했습니다. set-timezone-locale.yml, install-packages.yml, manage-users.yml, configure-firewall.yml, ssh-hardening.yml...

각각은 잘 작동했지만, 새로운 서버를 구성할 때마다 5개의 플레이북을 순서대로 실행해야 했습니다. "이것들을 하나로 합칠 수 없을까?" 김개발 씨는 박시니어 씨에게 물었습니다.

박시니어 씨가 웃으며 대답했습니다. "당연하죠.

실제로 대부분의 조직에서는 os-init.yml 같은 이름으로 통합 플레이북을 만들어 사용해요." 그렇다면 통합 플레이북을 만들면 무엇이 좋을까요? 쉽게 비유하자면, 마치 스마트폰의 초기 설정 마법사와 같습니다.

새 휴대폰을 사면 언어 선택, Wi-Fi 연결, 계정 로그인, 앱 설치 등을 단계별로 안내받습니다. 각 단계를 따로따로 수동으로 하는 것보다 훨씬 편리하고 빠릅니다.

서버 초기 설정도 마찬가지입니다. timezone 설정, 패키지 설치, 사용자 생성, 방화벽 구성, SSH 보안 강화를 하나의 플레이북에 담으면, 플레이북 한 번 실행으로 완전히 구성된 서버를 얻을 수 있습니다.

통합 플레이북 없이 여러 플레이북을 실행하던 시절에는 어땠을까요? 시스템 관리자는 체크리스트를 보면서 하나씩 실행했습니다.

"timezone 설정 완료, 패키지 설치 완료, 사용자 생성... 어?

방화벽 설정 깜빡했네!" 실수로 빠뜨리는 일이 종종 발생했습니다. 더 큰 문제는 새로운 팀원이 입사했을 때였습니다.

"서버 초기 설정 어떻게 해요?" 하고 물으면, "여기 README 보고 플레이북 5개를 순서대로 실행하세요"라고 알려줘야 했습니다. 문서가 오래되면 내용이 실제와 달라지기도 했습니다.

바로 이런 문제를 해결하기 위해 통합 플레이북을 만드는 것입니다. 통합 플레이북을 사용하면 일관성이 극대화됩니다.

모든 설정이 한 번에 적용되므로, 일부는 설정하고 일부는 빠뜨리는 일이 없습니다. 또한 신규 인력 온보딩이 쉬워집니다.

"이 플레이북 하나만 실행하면 돼요"라고 말하면 끝입니다. 무엇보다 검증이 간편합니다.

플레이북을 읽으면 "우리 서버는 이런 식으로 구성되어 있구나"를 한눈에 파악할 수 있습니다. 보안 감사를 받을 때도 이 플레이북을 제출하면 됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. 전체 구조는 하나의 플레이북 파일에 여러 개의 task가 포함된 형태입니다.

각 task는 논리적으로 관련된 작업들을 block으로 묶었습니다. block을 사용하면 여러 task를 하나의 단위로 관리할 수 있고, 에러 처리도 쉬워집니다.

첫 번째 task는 timezone 설정입니다. 이전에 배운 내용과 동일합니다.

두 번째 task는 locale 설정을 block으로 묶었습니다. locale 생성과 기본값 설정이 논리적으로 하나의 작업이므로 함께 묶은 것입니다.

세 번째 task는 패키지 설치입니다. ufw도 함께 설치하여 다음 단계에서 방화벽 설정을 할 수 있도록 준비합니다.

네 번째 task는 사용자 관리를 block으로 묶었습니다. 계정 생성과 SSH 키 등록이 함께 이루어져야 의미가 있으므로 하나로 묶었습니다.

다섯 번째 task는 방화벽 설정입니다. SSH 포트를 먼저 열고, 그 다음 HTTP/HTTPS를 열고, 마지막에 방화벽을 활성화합니다.

순서가 중요합니다. 여섯 번째 task는 SSH 보안 강화입니다.

비밀번호 인증 비활성화와 root 로그인 차단을 함께 수행합니다. 변경 사항이 있으면 sshd 재시작 핸들러를 호출합니다.

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

인프라 팀은 계속해서 플레이북을 개선하고, 새로운 보안 요구사항이 생기면 플레이북에 추가합니다. 예를 들어 회사 정책이 바뀌어서 모든 서버에 fail2ban을 설치해야 한다면, 플레이북의 패키지 목록에 fail2ban을 추가하고 설정 task를 작성합니다.

그 다음 모든 서버에 플레이북을 실행하면 일괄 적용됩니다. 또한 CI/CD 파이프라인과 통합하기도 합니다.

새로운 서버가 클라우드에서 프로비저닝되면, 자동으로 이 플레이북이 실행되어 몇 분 안에 완전히 구성된 서버가 준비됩니다. 많은 기업에서 환경별로 변수를 다르게 설정합니다.

개발 환경은 방화벽을 느슨하게, 운영 환경은 엄격하게 설정하는 식입니다. Ansible의 변수 시스템을 활용하면 같은 플레이북으로 다양한 환경을 관리할 수 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 플레이북을 너무 복잡하게 만드는 것입니다.

모든 경우의 수를 처리하려다 보면 플레이북이 수백 줄이 되고, 유지보수가 어려워집니다. 처음에는 단순하게 시작하고, 필요할 때 점진적으로 개선하는 것이 좋습니다.

또 다른 실수는 테스트 없이 프로덕션에 바로 적용하는 것입니다. 플레이북을 수정했다면, 먼저 테스트 서버나 로컬 가상머신에서 실행해보고 문제가 없는지 확인해야 합니다.

Vagrant나 Docker로 테스트 환경을 만들 수 있습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 도움으로 완성한 os-init.yml을 실행한 김개발 씨는 새로운 서버 10대가 5분 만에 완전히 구성되는 것을 지켜봤습니다. "앞으로 서버 구성이 정말 편해지겠어요!" 통합 플레이북을 만들면 서버 초기 설정 전체를 자동화할 수 있고, 일관성과 보안을 보장할 수 있습니다.

여러분도 조직의 표준 os-init.yml 플레이북을 만들어 보세요.

실전 팁

💡 - block을 사용하여 관련된 작업들을 논리적으로 그룹화하세요

  • Git으로 플레이북을 버전 관리하고 변경 시 코드 리뷰를 거치세요
  • 테스트 환경에서 먼저 검증한 후 프로덕션에 적용하세요

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

#Ansible#Playbook#Linux#Automation#DevOps#DevOps,Ansible,Linux

댓글 (0)

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

함께 보면 좋은 카드 뉴스