Red Hat Certified Engineer (RHCE) #16 RHCSA 자동화 3: 스토리지(LVM), 파일시스템(NFS)

8 분 소요

#15 RHCSA 자동화 2: 서비스, chronyd, log에서 서비스와 시간 동기화, 로그를 플레이북으로 다뤘다면, 이번에는 RHCSA에서 가장 손이 많이 가던 영역인 스토리지를 Ansible로 자동화하겠습니다. 디스크에 파티션을 만들고, LVM으로 VG와 LV를 구성하고, 파일시스템을 올린 다음 영구 마운트까지 잇는 일련의 작업을 멱등성 있는 task로 풀어내는 것이 목표입니다.

스토리지는 RHCE 실기의 단골 출제 영역입니다. RHCSA에서 LVM 시리즈NFS/autofs 시리즈로 손으로 익힌 작업을, 이번 글에서는 같은 결과를 내는 플레이북으로 옮기겠습니다.

스토리지 자동화의 전체 그림 #

손으로 하던 스토리지 작업을 떠올리면 다음 순서였습니다. 디스크에 파티션을 만들고(parted), 그 파티션을 PV로 삼아 VG를 만들고(vgcreate), VG에서 LV를 잘라내고(lvcreate), LV를 포맷하고(mkfs), 마운트 지점을 만든 뒤 /etc/fstab에 등록하고 실제로 마운트하는 흐름이었습니다.

Ansible은 이 단계마다 전용 모듈을 둡니다.

손작업Ansible 모듈
parted 파티션community.general.parted
vgcreate (VG)community.general.lvg
lvcreate (LV)community.general.lvol
mkfs 포맷community.general.filesystem
/etc/fstab + mountansible.posix.mount

여기서 핵심은 모듈마다 멱등성이 보장되는 방식이 다르다는 점입니다. lvgfilesystem은 이미 존재하면 그냥 넘어가지만, partedlvol은 옵션을 잘못 주면 매 실행마다 changed로 잡히거나 크기를 다시 건드릴 수 있으므로 주의가 필요합니다.

parted: 파티션 만들기 #

community.general.parted는 디스크에 파티션 테이블과 파티션을 만듭니다. 멱등성을 위해 number(파티션 번호)와 시작,끝 위치를 명시하는 것이 안전합니다.

파티션 생성
- name: 디스크에 파티션 1개 생성
  community.general.parted:
    device: /dev/vdb
    number: 1
    state: present
    part_start: 1MiB
    part_end: 1024MiB

같은 number로 같은 part_start/part_end를 주면 두 번째 실행에서는 changed가 발생하지 않습니다. 반대로 끝 위치를 100%처럼 상대값으로 주면 환경에 따라 매 실행마다 재계산되어 멱등성이 흔들릴 수 있으므로, 명시적 크기를 권장합니다.

LVM에 쓸 파티션이라면 파티션 플래그를 LVM으로 설정하기도 합니다.

LVM용 파티션 생성
- name: LVM용 파티션 생성
  community.general.parted:
    device: /dev/vdb
    number: 1
    state: present
    part_start: 1MiB
    part_end: 2048MiB
    flags: [ lvm ]

다만 시험에서 제공하는 디스크를 통째로 PV로 쓰는 경우라면 파티션 없이 /dev/vdb 자체를 lvg에 넘길 수도 있습니다. 문제 지문이 “파티션을 만들어"라고 명시하면 parted를 쓰고, 그렇지 않으면 디스크 전체를 PV로 쓰는 쪽이 단순합니다.

lvg: VG 만들기 #

community.general.lvg는 PV를 묶어 VG를 만듭니다. pvs에 디스크나 파티션을 넘기면 PV 생성과 VG 생성을 한 번에 처리합니다.

VG 생성
- name: VG 생성
  community.general.lvg:
    vg: data_vg
    pvs: /dev/vdb1
    state: present

VG가 이미 존재하고 동일한 PV로 구성되어 있으면 changed가 발생하지 않습니다. 여러 PV를 묶으려면 리스트로 넘깁니다.

여러 PV로 VG 생성
- name: 여러 PV로 VG 생성
  community.general.lvg:
    vg: data_vg
    pvs:
      - /dev/vdb1
      - /dev/vdc1

lvol: LV 만들기 #

community.general.lvol은 VG에서 LV를 잘라냅니다. size로 크기를 지정하며, 절대 크기(2g, 500m)와 상대 비율(50%VG, 100%FREE)을 모두 받습니다.

LV 생성
- name: LV 생성
  community.general.lvol:
    vg: data_vg
    lv: data_lv
    size: 1g
    state: present

lvol에서 멱등성이 가장 잘 깨지는 지점이 크기 변경입니다. 이미 만들어진 LV에 다른 size를 주면 모듈이 크기를 줄이려고 시도할 수 있고, 축소는 데이터 손실로 이어집니다. 이를 막는 옵션이 두 가지입니다.

  • shrink: false. 축소 동작을 막아 실수로 LV가 작아지는 일을 방지합니다.
  • resizefs: true. LV 크기를 바꿀 때 그 위의 파일시스템도 함께 키워 정합성을 맞춥니다.
LV 크기 안전 관리
- name: LV 크기를 안전하게 관리
  community.general.lvol:
    vg: data_vg
    lv: data_lv
    size: 2g
    shrink: false
    resizefs: true

시험에서 “LV를 N기가로 만들어라"는 한 번 만들면 끝이지만, “기존 LV를 확장하라"가 나오면 resizefs: true로 파일시스템까지 함께 키우는 패턴을 떠올리겠습니다.

filesystem: 포맷 #

community.general.filesystem은 LV나 파티션에 파일시스템을 올립니다. fstype과 대상 장치(dev)를 지정합니다.

xfs 포맷
- name: LV를 xfs로 포맷
  community.general.filesystem:
    fstype: xfs
    dev: /dev/data_vg/data_lv

이미 해당 파일시스템이 있으면 changed가 발생하지 않습니다. 강제로 다시 만들려면 force: true를 줄 수 있지만, 기존 데이터를 지우므로 시험에서는 거의 쓰지 않습니다. 대상 장치 경로는 /dev/<vg>/<lv> 또는 /dev/mapper/<vg>-<lv> 형식 모두 동작합니다.

mount: fstab 등록과 실제 마운트 #

ansible.posix.mount/etc/fstab 항목과 실제 마운트를 함께 다룹니다. 핵심은 state 값입니다.

state동작
mountedfstab에 등록하고 지금 즉시 마운트
presentfstab에만 등록(지금 마운트하지 않음)
unmounted지금 언마운트(fstab 항목은 유지)
absent언마운트하고 fstab 항목도 제거

영구 마운트가 목표라면 거의 항상 state: mounted입니다. fstab 등록과 실제 마운트를 한 번에 해 주므로, 재부팅 후에도 살아남으면서 지금 당장도 쓸 수 있습니다.

마운트 지점 생성과 영구 마운트
- name: 마운트 지점 디렉터리 생성
  ansible.builtin.file:
    path: /data
    state: directory
    mode: '0755'

- name: LV를 영구 마운트
  ansible.posix.mount:
    path: /data
    src: /dev/data_vg/data_lv
    fstype: xfs
    state: mounted

src는 장치 경로 대신 UUID=...LABEL=...로 줄 수도 있습니다. 장치 이름이 바뀌어도 안전하게 마운트하려면 UUID를 쓰는 편이 견고합니다.

swap 추가 #

swap도 같은 모듈 조합으로 다룹니다. filesystem에서 fstype: swap으로 swap 시그니처를 만들고, mount 모듈로 fstab에 등록합니다. swap은 디렉터리에 마운트하는 것이 아니므로 path: none, fstype: swap, state: present로 fstab에만 등록한 뒤 활성화하는 것이 일반적입니다.

swap 포맷과 fstab 등록
- name: swap LV 포맷
  community.general.filesystem:
    fstype: swap
    dev: /dev/data_vg/swap_lv

- name: swap을 fstab에 등록하고 활성화
  ansible.posix.mount:
    path: none
    src: /dev/data_vg/swap_lv
    fstype: swap
    opts: sw
    state: present

state: present로 fstab에 등록한 다음, 실제 활성화는 command: swapon -a로 보완하거나 swap을 다루는 storage role에 맡기는 방식을 쓰겠습니다.

NFS 원격 마운트 #

NFS 마운트도 동일한 ansible.posix.mount 모듈로 처리합니다. 로컬 파일시스템과 다른 점은 fstype: nfs이고 src서버:내보낸경로 형식이라는 것뿐입니다.

NFS 영구 마운트
- name: NFS export를 영구 마운트
  ansible.posix.mount:
    path: /mnt/share
    src: nfs-server.example.com:/exports/share
    fstype: nfs
    opts: defaults,_netdev
    state: mounted

네트워크가 올라온 뒤 마운트되도록 opts_netdev를 넣는 습관을 들이겠습니다. NFS 클라이언트 패키지(nfs-utils)가 없으면 마운트가 실패하므로, 그 앞에 패키지 설치 작업을 두는 것이 안전합니다.

nfs-utils 설치
- name: NFS 클라이언트 패키지 설치
  ansible.builtin.dnf:
    name: nfs-utils
    state: present

전체 LVM 플레이북 예제 #

지금까지의 작업을 하나의 흐름으로 묶으면 다음과 같습니다. 시험에서 “디스크로 VG/LV를 만들어 xfs로 포맷하고 /data에 영구 마운트하라"는 유형의 표준 답안입니다.

LVM 전체 플레이북
---
- name: LVM 스토리지 구성
  hosts: storage
  become: true
  tasks:
    - name: VG 생성
      community.general.lvg:
        vg: data_vg
        pvs: /dev/vdb

    - name: LV 생성
      community.general.lvol:
        vg: data_vg
        lv: data_lv
        size: 1g
        shrink: false

    - name: 파일시스템 생성
      community.general.filesystem:
        fstype: xfs
        dev: /dev/data_vg/data_lv

    - name: 마운트 지점 생성
      ansible.builtin.file:
        path: /data
        state: directory
        mode: '0755'

    - name: 영구 마운트
      ansible.posix.mount:
        path: /data
        src: /dev/data_vg/data_lv
        fstype: xfs
        state: mounted

이 플레이북을 두 번 돌려 두 번째 실행에서 changed가 0이 나오는지 반드시 확인하겠습니다. lvg/lvol/filesystem/mount는 모두 멱등성을 지원하므로, 제대로 작성했다면 재실행 시 모든 작업이 ok로 잡힙니다.

storage system role 대안 #

위 작업을 한 번에 묶어 주는 공식 역할이 rhel-system-roles의 storage role입니다. #13 system roles에서 다룬 system role 계열로, PV,VG,LV,파일시스템,마운트를 변수 하나로 선언합니다.

storage role 플레이북
---
- name: storage role로 스토리지 구성
  hosts: storage
  become: true
  roles:
    - rhel-system-roles.storage
  vars:
    storage_pools:
      - name: data_vg
        disks:
          - vdb
        volumes:
          - name: data_lv
            size: 1g
            fs_type: xfs
            mount_point: /data

storage_pools에 디스크와 볼륨을 선언하면 역할이 내부에서 VG/LV 생성, 포맷, fstab 등록, 마운트를 모두 처리합니다. swap도 같은 구조에서 fs_type: swap으로 선언할 수 있습니다. 개별 모듈을 직접 엮는 방식과 storage role 중 어느 쪽을 써도 시험에서는 인정되므로, 손에 익은 쪽을 쓰겠습니다. 다만 지문이 특정 모듈을 지정하면 그 모듈을 쓰는 것이 안전합니다.

시험 포인트 #

  • mount 모듈의 state: mounted가 fstab 등록과 실제 마운트를 한 번에 처리합니다. 영구 마운트 문제의 표준 답입니다. present는 fstab에만 등록하므로 지금 마운트가 빠집니다.
  • partedlvol은 멱등성이 잘 깨집니다. parted는 시작,끝 위치를 명시하고, lvolshrink: false로 축소를 막겠습니다.
  • LV 확장은 lvolresizefs: true를 주어 파일시스템까지 함께 키웁니다.
  • NFS는 ansible.posix.mountfstype: nfs서버:경로 형식 src, opts: _netdev로 처리하고, 앞에 nfs-utils 설치를 둡니다.
  • 스토리지 모듈은 대부분 community.general collection 소속이고 mountansible.posix 소속입니다. FQCN과 collection 설치 여부를 확인하겠습니다.

정리 #

이번 글에서 잡은 것:

  • 스토리지 자동화의 단계별 모듈. parted(파티션) → lvg(VG) → lvol(LV) → filesystem(포맷) → mount(fstab+마운트)
  • lvolshrink: falseresizefs: true로 축소 방지와 파일시스템 동시 확장
  • mount 모듈의 state의미. mounted가 영구 마운트의 정답
  • swap은 filesystemfstype: swapmountpath: none으로 처리
  • NFS 원격 마운트는 fstype: nfs_netdev 옵션, nfs-utils 설치
  • 개별 모듈 조합의 대안으로 rhel-system-roles의 storage role

다음: RHCSA 자동화 4 #

스토리지까지 자동화했습니다. RHCSA 자동화의 마지막 조각은 보안 영역입니다.

#17 RHCSA 자동화 4: firewall, SELinux, SSH 키에서는 firewalld를 ansible.posix.firewalld로 다루고, SELinux 모드와 불리언,포트 레이블을 ansible.posix.selinuxcommunity.general.sefcontext로 자동화하며, SSH 공개 키 배포까지 플레이북으로 묶어 정리하겠습니다.

X