새벽 3시, PagerDuty 알림이 울립니다. /var/lib/mysql 파티션 사용률이 93%입니다. df -h로 확인하니 20GB 파티션이 꽉 찼습니다. 전통 파티션 방식이라면 MySQL을 멈추고, 데이터를 임시 디스크에 옮기고, 파티션을 지웠다 새로 만들고, 다시 복원해야 합니다. 예상 다운타임 2~3시간, 새벽에 서비스 중단 공지를 써야 하는 상황.
같은 서버가 LVM으로 구성되어 있었다면 lvextend -L +20G -r /dev/vg_data/lv_mysql 한 줄, 3분 안에 끝납니다.
LVM & 볼륨 관리
- 1전통 파티션의 한계와 LVM이 온라인 볼륨 확장으로 해결하는 문제를 설명할 수 있다
- 2PV·VG·LV 3계층 구조를 이해하고 각 계층의 명령어를 구분할 수 있다
- 3pvcreate → vgcreate → lvcreate → mount → fstab 전체 흐름을 실습할 수 있다
- 4운영 중단 없이 lvextend와 resize2fs/xfs_growfs로 볼륨을 확장할 수 있다
- 5LVM 스냅샷으로 일관성 있는 백업 포인트를 생성하고 롤백할 수 있다
1. 기존 파티션의 한계와 LVM의 등장
dnf install -y lvm2 || apt-get install -y lvm2lsblk -f && pvs && vgs && lvsdnf install -y mdadm || apt-get install -y mdadm왜 LVM이 필요한가?

새벽 3시, 데이터베이스 서버에서 PagerDuty 알림이 울렸습니다. /var/lib/mysql 파티션 사용률이 93%였고, df -h로 확인해보니 20GB짜리 파티션 하나가 꽉 차 있었습니다. 파티션을 늘리려면 MySQL을 멈추고 데이터를 임시 디스크에 옮겨두고, 파티션을 지웠다 새로 만든 뒤 데이터를 다시 복원해야 했습니다. 예상 다운타임은 2~3시간이었고, 새벽에 서비스 중단 공지를 써야 하는 상황이었습니다. 같은 서버가 LVM으로 구성되어 있었다면 lvextend -L +20G 한 줄로 온라인 확장이 가능했을 겁니다.
전통적인 디스크 파티션 방식은 1980년대 설계된 개념으로, 현대 클라우드 인프라의 유연한 스토리지 요구를 충족하기 어렵습니다.
전통 파티션 방식의 한계:
- 크기 변경 불가: 파티션을 생성하면 크기가 고정됩니다. 확장하려면 서비스를 중단해야 합니다.
- 단일 디스크 종속: 하나의 파티션은 하나의 물리 디스크에만 존재합니다. 디스크 용량 한계가 곧 파티션 한계입니다.
- 낭비 발생:
/home에 공간이 남아도/var가 꽉 차면 이동이 불가합니다. - 사전 계획 필수: 초기 설치 시 정확한 용량 예측이 필요합니다. 예측이 틀리면 대공사가 필요합니다.
LVM이 제공하는 해결책:
- 운영 중인 서비스를 중단하지 않고 볼륨 크기를 늘릴 수 있습니다.
- 여러 물리 디스크를 하나의 스토리지 풀로 통합합니다.
- 스냅샷 기능으로 특정 시점의 상태를 순간 포착합니다.
- 새 디스크를 기존 볼륨 그룹에 추가해 동적으로 용량을 확장합니다.
LVM이 실제로 쓰이는 환경 — 현장에서 LVM이 필요한 실제 시나리오들입니다:
데이터베이스 서버 → 데이터 증가에 따라 주기적으로 스토리지 확장 필요
로그 서버 → 예측 불가능한 로그 증가량
개발/스테이징 환경 → 다양한 크기의 볼륨을 유연하게 할당
백업 서버 → LVM 스냅샷으로 일관성 있는 백업 생성
실무 포인트: 대부분의 Linux 배포판은 기본 설치 시 LVM을 사용합니다.
df -h로 마운트 포인트를 확인하면/dev/mapper/...형태의 경로가 보이는데, 이것이 LVM 논리 볼륨입니다.
2. LVM 3계층 구조 이해
PV → VG → LV: LVM의 3단계 추상화

LVM 서버에서 /var/log가 꽉 차 로그 수집이 멈췄습니다. vgs로 확인하니 VG에 여유 공간이 충분했는데, 막상 어떤 명령어를 써야 하는지 감이 안 왔습니다. 검색하면 pvcreate, vgextend, lvextend가 동시에 나오는데 무엇이 먼저인지, PV·VG·LV 중 어디에 명령을 내려야 하는지 헷갈렸습니다. 이 세 계층의 역할을 한 번 정확히 구분해두면, 이후 LVM 명령어 전체가 자연스럽게 읽힙니다.
LVM은 물리 스토리지를 3단계로 추상화합니다. 이 계층 구조를 이해하면 LVM의 모든 명령어가 자연스럽게 이해됩니다.
처음 보면 PV, VG, LV라는 세 가지 약자가 낯설게 느껴집니다. 레고 블록에 비유하면 직관적으로 이해됩니다.
- PV(Physical Volume)는 레고 블록 한 조각입니다. 실제 물리 디스크나 파티션을 LVM이 인식할 수 있도록 등록하는 단계입니다.
pvcreate /dev/sdb1을 실행하기 전까지는 LVM 입장에서 그 디스크는 "존재하지 않는 것"과 같습니다. - VG(Volume Group)는 블록들을 담는 바구니입니다. 여러 PV를 하나의 스토리지 풀로 묶어줍니다. 500GB 디스크와 1TB 디스크를 묶어 1.5TB짜리 풀을 만드는 것이 VG 생성입니다. 이후 모든 용량 계산은 이 풀 기준으로 이루어집니다.
- LV(Logical Volume)는 바구니에서 꺼내 실제로 쓰는 조각입니다. VG 풀에서 원하는 크기만큼 떼어내 파일시스템을 올리는 공간입니다. MySQL 데이터용으로 15GB, 로그용으로 10GB처럼 필요에 따라 동적으로 생성하고 나중에 늘릴 수 있습니다.
이 3계층 구조 덕분에 LVM은 물리 경계를 넘어 볼륨을 자유롭게 늘리고 줄일 수 있습니다.

아래는 동일한 구조를 텍스트로 표현한 것입니다:
물리 계층 (Physical)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ /dev/sdb │ │ /dev/sdc │ │ /dev/sdd │
│ (500GB HDD) │ │ (1TB SSD) │ │ (1TB SSD) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
PV 계층 (Physical Volume)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ PV: sdb │ │ PV: sdc │ │ PV: sdd │
│ pvcreate │ │ pvcreate │ │ pvcreate │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└─────────────────┼─────────────────┘
│
▼
VG 계층 (Volume Group) — 스토리지 풀
┌────────────────────────────────────────────────────┐
│ vg_data (2.5TB 통합 풀) │
│ vgcreate │
└──────────────────────┬─────────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
LV 계층 (Logical Volume) — 실제 사용
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ lv_mysql │ │ lv_logs │ │ lv_backup │
│ (500GB) │ │ (800GB) │ │ (1.2TB) │
│ lvcreate │ │ lvcreate │ │ lvcreate │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
파일시스템 & 마운트 포인트
/dev/vg_data/lv_mysql /dev/vg_data/lv_logs /dev/vg_data/lv_backup
/var/lib/mysql /var/log /backup
각 계층의 역할 — PV → VG → LV 세 계층이 어떤 역할을 하는지 핵심 명령어와 함께 정리했습니다:
| 계층 | 이름 | 역할 | 핵심 명령어 |
|---|---|---|---|
| 1단계 | Physical Volume (PV) | 물리 디스크/파티션을 LVM에 등록 | pvcreate |
| 2단계 | Volume Group (VG) | PV들을 하나의 스토리지 풀로 통합 | vgcreate, vgextend |
| 3단계 | Logical Volume (LV) | VG에서 원하는 크기만큼 논리 볼륨 할당 | lvcreate, lvextend |
PE(Physical Extent)란?
LVM은 디스크를 **PE(Physical Extent)**라는 고정 크기 블록으로 나눕니다. 기본값은 4MB이며, VG 생성 시 변경할 수 있습니다. LV는 이 PE 단위로 할당됩니다. 예를 들어 PE 크기가 4MB이고 LV가 100GB라면, 해당 LV는 25,600개의 PE로 구성됩니다.
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part3/exam_15 && cd /tmp/linux/part3/exam_15
# PE 크기 확인
vgdisplay vg_data | grep "PE Size"
# PE Size 4.00 MiB
3. LVM 전체 구성 실습
실습 전 루프백 이미지 파일을 생성하여 실제 디스크 없이 LVM을 실습할 수 있도록 환경을 준비합니다.
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part3/exam_2
# 실습용 루프백 이미지 파일 생성 (실제 디스크 없이 LVM 실습)
dd if=/dev/zero of=/tmp/linux/part3/exam_2/disk1.img bs=1M count=500 2>/dev/null
dd if=/dev/zero of=/tmp/linux/part3/exam_2/disk2.img bs=1M count=500 2>/dev/null
LOOP1=$(sudo losetup -f --show /tmp/linux/part3/exam_2/disk1.img)
LOOP2=$(sudo losetup -f --show /tmp/linux/part3/exam_2/disk2.img)
echo "루프백 장치: $LOOP1, $LOOP2"
이제 실습을 진행합니다.
실습에서는 /dev/sdb, /dev/sdc 두 개의 디스크를 사용합니다. 가상 머신이나 클라우드 인스턴스에 추가 디스크를 연결한 후 시작합니다.
현재 디스크 상태 확인 — LVM 설정 전에 현재 디스크와 파티션 구성을 파악합니다:
# 연결된 모든 블록 장치 확인
lsblk
# 예시 출력:
# NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
# sda 8:0 0 50G 0 disk
# ├─sda1 8:1 0 1G 0 part /boot
# └─sda2 8:2 0 49G 0 part /
# sdb 8:16 0 20G 0 disk ← 새로 추가된 디스크
# sdc 8:32 0 20G 0 disk ← 새로 추가된 디스크
# 디스크 상세 정보 확인
fdisk -l /dev/sdb
fdisk -l /dev/sdc
파티션 생성 (선택 사항):
물리 디스크 전체를 PV로 사용할 수도 있지만, 파티션을 먼저 생성하는 것이 일반적입니다. 파티션 타입을 Linux LVM (8e)으로 설정해야 합니다.
# /dev/sdb에 파티션 생성
fdisk /dev/sdb
# fdisk 대화형 명령:
# n → 새 파티션 생성
# p → Primary 파티션
# 1 → 파티션 번호 1
# Enter → 기본 시작 섹터
# Enter → 기본 끝 섹터 (전체 사용)
# t → 파티션 타입 변경
# 8e → Linux LVM 타입 코드
# w → 변경사항 저장
# /dev/sdc도 동일하게 진행
fdisk /dev/sdc
# 파티션 테이블 재로드
partprobe /dev/sdb
partprobe /dev/sdc
# 결과 확인
lsblk
# sdb 8:16 0 20G 0 disk
# └─sdb1 8:17 0 20G 0 part
# sdc 8:32 0 20G 0 disk
# └─sdc1 8:33 0 20G 0 part
참고: 파티션 없이 디스크 전체(
/dev/sdb)를 PV로 사용할 수도 있습니다. 이 경우 파티션 생성 단계를 건너뛰어도 됩니다.
- lsblk 출력에서 파티셔닝 후 /dev/sdb1, /dev/sdc1 이 tree 형태로 부모 디스크 아래 나타난다
- partprobe 실행 후 즉시 lsblk 결과에 새 파티션이 반영된다 (재부팅 불필요)
- fdisk -l /dev/sdb 로 파티션 타입이 'Linux LVM'(8e)으로 설정되어 있는지 확인한다
- 파티션 없이 /dev/sdb 전체를 PV로 쓸 경우 lsblk에서 파티션 항목 없이 디스크가 바로 표시된다
pvcreate 명령으로 파티션(또는 디스크)을 LVM의 Physical Volume으로 초기화합니다.
# PV 생성
pvcreate /dev/sdb1
# Physical volume "/dev/sdb1" successfully created.
pvcreate /dev/sdc1
# Physical volume "/dev/sdc1" successfully created.
# PV 목록 확인 (간략)
pvs
# 예시 출력:
# PV VG Fmt Attr PSize PFree
# /dev/sdb1 lvm2 --- <20.00g <20.00g
# /dev/sdc1 lvm2 --- <20.00g <20.00g
# PV 상세 정보 확인
pvdisplay /dev/sdb1
# 예시 출력:
# "/dev/sdb1" is a new physical volume of "<20.00 GiB"
# --- NEW Physical volume ---
# PV Name /dev/sdb1
# VG Name
# PV Size <20.00 GiB
# Allocatable NO
# PE Size 0
# Total PE 0
# Free PE 0
# Allocated PE 0
# PV UUID abc123-...
PV 생성 시 유용한 옵션: — PE 크기 조정 등 PV 생성 시 자주 쓰는 옵션입니다:
# PE 크기를 8MB로 지정하여 PV 생성
pvcreate --physicalextentsize 8M /dev/sdb1
# 기존 데이터가 있는 디스크에 강제 생성 (주의: 데이터 손실 가능)
pvcreate --force /dev/sdb1
# 여러 디스크를 한번에 PV로 생성
pvcreate /dev/sdb1 /dev/sdc1
vgcreate로 PV들을 묶어 Volume Group을 만들고, lvcreate로 실제 사용할 Logical Volume을 생성합니다.
Volume Group 생성 — 여러 PV를 하나의 VG로 묶어 큰 스토리지 풀을 만듭니다:
# vg_data라는 이름의 VG 생성 (두 PV를 하나의 풀로 통합)
vgcreate vg_data /dev/sdb1 /dev/sdc1
# 출력:
# Volume group "vg_data" successfully created
# VG 상태 확인 (간략)
vgs
# 예시 출력:
# VG #PV #LV #SN Attr VSize VFree
# vg_data 2 0 0 wz--n- <39.99g <39.99g
# VG 상세 정보 확인
vgdisplay vg_data
# 예시 출력:
# --- Volume group ---
# VG Name vg_data
# System ID
# Format lvm2
# VG Access read/write
# VG Status resizable
# MAX LV 0
# Cur LV 0
# Open LV 0
# Max PV 0
# Cur PV 2
# Act PV 2
# VG Size <39.99 GiB
# PE Size 4.00 MiB
# Total PE 10238
# Alloc PE / Size 0 / 0
# Free PE / Size 10238 / <39.99 GiB
# VG UUID xyz789-...
Logical Volume 생성 — VG에서 필요한 크기만큼 LV를 잘라냅니다:
# 15GB 크기의 lv_mysql 생성
lvcreate -L 15G -n lv_mysql vg_data
# 출력:
# Logical volume "lv_mysql" created.
# 10GB 크기의 lv_logs 생성
lvcreate -L 10G -n lv_logs vg_data
# VG의 남은 공간 전부를 lv_backup으로 사용
lvcreate -l 100%FREE -n lv_backup vg_data
# LV 상태 확인
lvs
# 예시 출력:
# LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
# lv_backup vg_data -wi-a----- <14.99g
# lv_logs vg_data -wi-a----- 10.00g
# lv_mysql vg_data -wi-a----- 15.00g
# 상세 LV 정보
lvdisplay /dev/vg_data/lv_mysql
lvcreate 옵션 정리 — 용량, 이름, 스냅샷 등 lvcreate 주요 옵션과 예시입니다:
| 옵션 | 설명 | 예시 |
|---|---|---|
-L 크기 | 절대 크기 지정 | -L 15G |
-l PE수 | PE 개수로 지정 | -l 3840 |
-l %VG | VG 비율로 지정 | -l 50%VG |
-l %FREE | 남은 공간 비율 | -l 100%FREE |
-n 이름 | LV 이름 지정 | -n lv_mysql |
LV는 파일시스템이 없는 빈 블록 장치입니다. 사용하려면 파일시스템을 생성하고 마운트 포인트에 연결해야 합니다.
파일시스템 생성 — LV에 ext4 또는 XFS 파일시스템을 생성합니다:
# ext4 파일시스템 생성 (범용적, 축소 가능)
mkfs.ext4 /dev/vg_data/lv_mysql
# 출력 예시:
# mke2fs 1.46.5 (30-Dec-2021)
# Creating filesystem with 3932160 4k blocks and 983040 inodes
# Filesystem UUID: a1b2c3d4-...
# Writing inode tables: done
# Creating journal (16384 blocks): done
# Writing superblocks and filesystem accounting information: done
# xfs 파일시스템 생성 (고성능, 대용량 파일 최적화, 확장만 가능)
mkfs.xfs /dev/vg_data/lv_logs
# ext4로 lv_backup 생성
mkfs.ext4 /dev/vg_data/lv_backup
마운트 포인트 생성 및 마운트 — 디렉터리를 만들고 LV를 마운트합니다:
# 마운트 포인트 디렉토리 생성
mkdir -p /var/lib/mysql
mkdir -p /var/log/app
mkdir -p /backup
# 임시 마운트 (재부팅 시 사라짐)
mount /dev/vg_data/lv_mysql /var/lib/mysql
mount /dev/vg_data/lv_logs /var/log/app
mount /dev/vg_data/lv_backup /backup
# 마운트 확인
df -hT | grep -E "vg_data|Filesystem"
# 출력 예시:
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql ext4 15G 24K 14G 1% /var/lib/mysql
# /dev/mapper/vg_data-lv_logs xfs 10G 105M 9.9G 2% /var/log/app
# /dev/mapper/vg_data-lv_backup ext4 15G 24K 14G 1% /backup
UUID 확인 및 /etc/fstab 등록:
재부팅 후에도 자동으로 마운트되도록 /etc/fstab에 등록합니다. 장치 경로 대신 UUID를 사용하는 것이 안전합니다.
# UUID 확인
blkid /dev/vg_data/lv_mysql
# /dev/mapper/vg_data-lv_mysql: UUID="a1b2c3d4-e5f6-7890-abcd-ef1234567890" BLOCK_SIZE="4096" TYPE="ext4"
blkid /dev/vg_data/lv_logs
# /dev/mapper/vg_data-lv_logs: UUID="b2c3d4e5-f6a7-8901-bcde-f12345678901" BLOCK_SIZE="512" TYPE="xfs"
blkid /dev/vg_data/lv_backup
# /dev/mapper/vg_data-lv_backup: UUID="c3d4e5f6-a7b8-9012-cdef-123456789012" BLOCK_SIZE="4096" TYPE="ext4"
# /etc/fstab 편집
# 형식: UUID=<uuid> <마운트포인트> <파일시스템타입> <옵션> <dump> <pass>
cat >> /etc/fstab << 'EOF'
UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890 /var/lib/mysql ext4 defaults 0 2
UUID=b2c3d4e5-f6a7-8901-bcde-f12345678901 /var/log/app xfs defaults 0 2
UUID=c3d4e5f6-a7b8-9012-cdef-123456789012 /backup ext4 defaults 0 2
EOF
# fstab 문법 검증 (마운트하지 않고 검사만)
mount -a --fake
# 오류 없으면 아무 출력 없이 종료됨
# 실제 마운트 적용 (언마운트 후 fstab 기준으로 다시 마운트)
umount /var/lib/mysql
mount -a
df -h /var/lib/mysql
fstab 옵션 설명 — /etc/fstab의 각 필드 의미와 권장값입니다:
| 필드 | 설명 | 권장값 |
|---|---|---|
| dump | 백업 유틸리티 사용 여부 | 0 (비활성) |
| pass | fsck 검사 순서 | 1 (루트), 2 (나머지), 0 (비활성) |
4. LV 및 VG 확장
LVM의 가장 강력한 기능 중 하나는 서비스를 중단하지 않고 볼륨을 확장하는 것입니다. LV 확장 후 반드시 파일시스템도 확장해야 합니다.
VG 여유공간 확인 — 확장 전에 VG에 남은 PE 수량을 확인합니다:
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 2 3 0 wz--n- <39.99g 0 ← VFree가 0이면 먼저 VG에 디스크 추가 필요
# 여유공간이 있을 경우 진행
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 2 2 0 wz--n- <39.99g 14.99g
ext4 볼륨 확장 (온라인) — 서비스 중단 없이 LV와 파일시스템을 동시에 확장합니다:
# lv_mysql을 5GB 추가 확장 (현재 15G → 20G)
lvextend -L +5G /dev/vg_data/lv_mysql
# 출력:
# Size of logical volume vg_data/lv_mysql changed from 15.00 GiB (3840 extents)
# to 20.00 GiB (5120 extents).
# Logical volume vg_data/lv_mysql successfully resized.
# ext4 파일시스템 온라인 확장 (마운트된 상태에서 가능)
resize2fs /dev/vg_data/lv_mysql
# 출력:
# resize2fs 1.46.5 (30-Dec-2021)
# Filesystem at /dev/mapper/vg_data-lv_mysql is mounted on /var/lib/mysql;
# on-line resizing required
# old_desc_blocks = 2, new_desc_blocks = 3
# The filesystem on /dev/mapper/vg_data-lv_mysql is now 5242880 (4k) blocks long.
# 확장 결과 확인
df -h /var/lib/mysql
# Filesystem Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql 20G 24K 19G 1% /var/lib/mysql
xfs 볼륨 확장 (온라인):
xfs는 resize2fs가 아닌 xfs_growfs를 사용합니다. xfs는 온라인 확장만 가능하며 축소는 지원하지 않습니다.
# lv_logs를 3GB 추가 확장
lvextend -L +3G /dev/vg_data/lv_logs
# xfs 파일시스템 확장 (마운트 포인트를 인자로 사용)
xfs_growfs /var/log/app
# 출력:
# meta-data=/dev/mapper/vg_data-lv_logs isize=512 agcount=4, agsize=655360 blks
# = sectsz=512 attr=2, projid32bit=1
# data = bsize=4096 blocks=2621440, imaxpct=25
# = sunit=0 swidth=0 blks
# naming =version 2 bsize=4096 ascii-ci=0, ftype=1
# log =internal bsize=4096 blocks=2560, version=2
# realtime =none extsz=4096 blocks=0, rtextents=0
# data blocks changed from 2621440 to 3407872
# 확인
df -h /var/log/app
# Filesystem Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_logs 13G 105M 13G 1% /var/log/app
lvextend + 파일시스템 확장을 한 번에 — -r 옵션으로 LV 확장과 파일시스템 확장을 한 명령에 처리합니다:
# -r 옵션: lvextend 후 자동으로 resize2fs 또는 xfs_growfs 실행
lvextend -L +5G -r /dev/vg_data/lv_mysql
VG의 여유공간이 부족할 때 새 물리 디스크를 추가하여 스토리지 풀을 확장합니다. 이 과정도 서비스 중단 없이 진행됩니다.
새 디스크 인식 확인 — 추가한 디스크가 커널에 인식됐는지 확인합니다:
# 새로 추가된 디스크 확인 (예: /dev/sdd)
lsblk | grep sdd
# sdd 8:48 0 30G 0 disk
# 새 디스크에 파티션 생성 (또는 디스크 전체 사용)
fdisk /dev/sdd
# n → p → 1 → Enter → Enter → t → 8e → w
partprobe /dev/sdd
새 디스크를 PV로 등록 후 VG에 추가 — 새 PV를 기존 VG에 합쳐 스토리지 풀을 확장합니다:
# 새 파티션을 PV로 생성
pvcreate /dev/sdd1
# Physical volume "/dev/sdd1" successfully created.
# 기존 vg_data에 새 PV 추가
vgextend vg_data /dev/sdd1
# Volume group "vg_data" successfully extended
# VG 확장 결과 확인
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 3 3 0 wz--n- <69.98g <29.99g
# ↑ PV 3개, VFree가 30GB로 늘어남
# 전체 상태 한 번에 확인
pvs
# PV VG Fmt Attr PSize PFree
# /dev/sdb1 vg_data lvm2 a-- <20.00g 0
# /dev/sdc1 vg_data lvm2 a-- <20.00g 0
# /dev/sdd1 vg_data lvm2 a-- <30.00g <29.99g
vgs
# VG #PV #LV #SN Attr VSize VFree
# vg_data 3 3 0 wz--n- <69.98g <29.99g
lvs
# LV VG Attr LSize
# lv_backup vg_data -wi-ao---- <14.99g
# lv_logs vg_data -wi-ao---- 13.00g
# lv_mysql vg_data -wi-ao---- 20.00g
이제 lvextend로 기존 LV를 추가 확장하거나 새 LV를 생성할 수 있습니다.
5. LVM 스냅샷
LVM 스냅샷: 백업 전 일관성 있는 순간 포착

MySQL이 운영 중인 볼륨을 rsync로 백업하면 파일 절반은 백업 전 상태, 나머지 절반은 백업 중 변경된 상태가 섞인 깨진 백업본이 만들어집니다. 데이터베이스를 중단하지 않고 일관성 있는 백업을 만들려면, 특정 시점의 상태를 순간적으로 고정한 뒤 그 복사본을 백업해야 합니다. LVM 스냅샷이 바로 이 역할을 합니다. FLUSH TABLES WITH READ LOCK으로 MySQL을 잠깐 멈추는 순간 스냅샷을 찍고 즉시 잠금을 해제하면, 이후 백업 작업이 몇 시간 걸리더라도 서비스에는 영향이 없습니다.
LVM 스냅샷은 특정 시점의 LV 상태를 순간 포착합니다. 데이터베이스 백업 시 특히 유용합니다. 데이터베이스를 잠깐 flush하고 스냅샷을 생성한 뒤 백업하면, 일관성 있는 백업본을 얻을 수 있습니다.
스냅샷 동작 원리 (COW: Copy-on-Write):
스냅샷 생성 시점에는 실제 데이터를 복사하지 않습니다. 원본 데이터가 변경될 때만 변경 전 데이터를 스냅샷 공간에 복사합니다. 따라서 스냅샷 생성은 매우 빠르고, 스냅샷 크기는 원본 LV보다 훨씬 작아도 됩니다.
스냅샷 생성 직후:
lv_mysql (원본) ──── 실제 데이터 ────→ 파일시스템
lv_snap (스냅샷) → (비어 있음, 원본 참조)
데이터 변경 후:
lv_mysql (원본) ──── 새 데이터 ─────→ 파일시스템
lv_snap (스냅샷) → 변경 전 데이터 복사본
+ 변경되지 않은 데이터는 원본 참조
스냅샷 생성 및 활용 — LVM 스냅샷으로 배포 전 백업 시점을 만들고 롤백에 활용합니다:
# MySQL 데이터 flush (백업 일관성을 위해)
mysql -e "FLUSH TABLES WITH READ LOCK;"
# lv_mysql의 5GB 스냅샷 생성
lvcreate -L 5G -s -n lv_mysql_snap /dev/vg_data/lv_mysql
# 출력:
# Logical volume "lv_mysql_snap" created.
# MySQL 잠금 해제
mysql -e "UNLOCK TABLES;"
# 스냅샷 확인
lvs
# LV VG Attr LSize Origin Snap% Data%
# lv_mysql vg_data owi-aos--- 20.00g ← 원본
# lv_mysql_snap vg_data swi-a-s--- 5.00g lv_mysql 0.00 ← 스냅샷
# 스냅샷 마운트하여 백업
mkdir -p /mnt/snap_backup
mount -o ro /dev/vg_data/lv_mysql_snap /mnt/snap_backup
# rsync로 백업 진행 (서비스 영향 없음)
rsync -av /mnt/snap_backup/ /backup/mysql-$(date +%Y%m%d)/
# 백업 완료 후 스냅샷 제거
umount /mnt/snap_backup
lvremove /dev/vg_data/lv_mysql_snap
스냅샷 사용 시 주의사항:
- 스냅샷 공간이 가득 차면 스냅샷이 무효화됩니다. 원본 데이터 변경량을 고려하여 충분한 크기를 할당하세요.
lvs의Snap%컬럼으로 스냅샷 사용률을 모니터링하세요.- 스냅샷은 임시 목적으로만 사용하고, 장기 보관은 rsync 또는 tar로 실제 백업을 생성하세요.
6. 소프트웨어 RAID: mdadm
RAID 레벨 비교: 속도 vs 안정성 트레이드오프

새 서버에 디스크를 여러 개 달아야 할 때, "그냥 전부 하나로 묶으면 안 되나요?"라는 질문이 나옵니다. 묶는 방식에 따라 디스크 하나가 죽었을 때 서버가 살아남는지 여부가 달라지고, 같은 디스크 4개를 써도 실제로 쓸 수 있는 용량이 완전히 달라집니다. 데이터베이스 서버인지 로그 수집 서버인지, 성능이 중요한지 데이터 안전성이 중요한지에 따라 RAID 레벨 선택이 달라집니다. 하드웨어 RAID 컨트롤러 없이도 mdadm으로 소프트웨어 RAID를 구성할 수 있으며, 클라우드 환경에서도 같은 개념이 그대로 적용됩니다.
RAID(Redundant Array of Independent Disks)는 여러 디스크를 하나처럼 사용하여 성능 향상 또는 데이터 이중화를 제공합니다. 하드웨어 RAID 컨트롤러 없이도 Linux에서 소프트웨어 RAID를 구성할 수 있습니다.
RAID 레벨 비교표 — RAID 0/1/5/6/10 각 레벨의 특성과 최소 디스크 수를 비교합니다:
| RAID 레벨 | 최소 디스크 | 내고장성 | 읽기 성능 | 쓰기 성능 | 용량 효율 | 주요 용도 |
|---|---|---|---|---|---|---|
| RAID 0 (스트라이핑) | 2개 | 없음 | 매우 높음 | 매우 높음 | 100% | 임시 데이터, 캐시 |
| RAID 1 (미러링) | 2개 | 1개 디스크 | 높음 (읽기 분산) | 동일 | 50% | OS, 중요 데이터 |
| RAID 5 (패리티) | 3개 | 1개 디스크 | 높음 | 중간 | (N-1)/N | 파일서버, NAS |
| RAID 10 (1+0) | 4개 | 각 미러에서 1개 | 매우 높음 | 높음 | 50% | 데이터베이스 |
RAID 구조 시각화 — RAID 1과 RAID 5의 데이터 분산 구조를 시각적으로 표현합니다:
RAID 0 (스트라이핑) — 성능 ↑, 안전성 없음
디스크1: [A1][A3][A5][A7]
디스크2: [A2][A4][A6][A8]
→ 둘 중 하나 고장 시 전체 데이터 손실
RAID 1 (미러링) — 안전성 ↑, 용량 50%
디스크1: [A1][A2][A3][A4]
디스크2: [A1][A2][A3][A4] ← 완전 복사본
→ 한 쪽 고장해도 데이터 보존
RAID 5 (패리티) — 균형잡힌 선택
디스크1: [A1][B1][C1][P4]
디스크2: [A2][B2][P3][C2]
디스크3: [A3][P2][B3][C3]
→ 1개 고장 허용, 용량 효율 66% (3디스크 기준)
RAID 10 (미러+스트라이핑) — 최고 성능+안전성
디스크1: [A1][A3] ─┐ RAID 1 쌍
디스크2: [A1][A3] ─┘
디스크3: [A2][A4] ─┐ RAID 1 쌍
디스크4: [A2][A4] ─┘
→ 각 미러 쌍에서 1개씩 고장 허용
실무에서의 선택 기준:
- RAID 0: 재생성 가능한 임시 데이터 (빌드 캐시, 임시 파일 등)
- RAID 1: 부팅 디스크, 설정 파일, 소규모 중요 데이터
- RAID 5: 4TB 이상의 파일 서버 (단, 고장 시 rebuild 중 추가 고장 위험)
- RAID 10: 데이터베이스 서버 — 성능과 안전성 모두 중요
mdadm은 Linux 소프트웨어 RAID를 관리하는 표준 도구입니다. RAID 1(미러링) 구성을 실습합니다.
mdadm 설치 및 준비 — mdadm을 설치하고 실습용 루프백 디바이스를 준비합니다:
# RHEL/CentOS/Rocky Linux
dnf install -y mdadm
# Ubuntu/Debian
apt-get install -y mdadm
# 사용할 디스크 확인 (실습: /dev/sde, /dev/sdf)
lsblk | grep -E "sde|sdf"
RAID 1 어레이 생성 — 두 디스크를 미러링하는 RAID 1 어레이를 생성합니다:
# /dev/md0 이름으로 RAID 1 어레이 생성
mdadm --create /dev/md0 \
--level=1 \
--raid-devices=2 \
/dev/sde /dev/sdf
# 생성 시 확인 메시지:
# mdadm: Note: this array has metadata at the start and
# may not be suitable as a boot device. If you plan to
# store '/boot' on this device please ensure that
# your boot-loader understands md/v1.x metadata, or use
# --metadata=0.90
# Continue creating array? y
# mdadm: Increasingly creating RAID1 array /dev/md0
# RAID 동기화 진행 상황 확인 (초기 동기화 필요)
cat /proc/mdstat
# 출력 예시 (동기화 중):
# Personalities : [raid1]
# md0 : active raid1 sde[0] sdf[1]
# 20955136 blocks super 1.2 [2/2] [UU]
# [=========>...........] resync = 47.5% (9952640/20955136) finish=1.1min speed=168M/s
# 동기화 완료 후:
# md0 : active raid1 sde[0] sdf[1]
# 20955136 blocks super 1.2 [2/2] [UU]
# ← [UU]는 두 디스크 모두 정상 (Up)
# 어레이 상세 정보
mdadm --detail /dev/md0
# 출력:
# /dev/md0:
# Version : 1.2
# Creation Time : Thu Mar 26 10:00:00 2026
# Raid Level : raid1
# Array Size : 20955136 (19.99 GiB 21.46 GB)
# Used Dev Size : 20955136 (19.99 GiB 21.46 GB)
# Raid Devices : 2
# Total Devices : 2
# Persistence : Superblock is persistent
# Update Time : Thu Mar 26 10:05:00 2026
# State : clean
# Active Devices : 2
# Working Devices : 2
# Failed Devices : 0
# Spare Devices : 0
# Layout : -
# Chunk Size : -
# Name : server:0
# UUID : a1b2c3d4:e5f6a7b8:c9d0e1f2:a3b4c5d6
# Events : 18
# Number Major Minor RaidDevice State
# 0 8 64 0 active sync /dev/sde
# 1 8 80 1 active sync /dev/sdf
RAID 어레이에 파일시스템 생성 및 마운트 — RAID 어레이에 파일시스템을 만들고 영구 마운트합니다:
# RAID 장치에 ext4 파일시스템 생성
mkfs.ext4 /dev/md0
# 마운트 포인트 생성 및 마운트
mkdir -p /data/raid1
mount /dev/md0 /data/raid1
# mdadm 설정 파일 저장 (재부팅 후 자동 인식)
mdadm --detail --scan >> /etc/mdadm.conf
# 또는 Rocky Linux/RHEL의 경우:
mdadm --detail --scan >> /etc/mdadm/mdadm.conf
# fstab에 등록 (UUID 사용 권장)
blkid /dev/md0
# /dev/md0: UUID="d4e5f6a7-..." TYPE="ext4"
echo 'UUID=d4e5f6a7-... /data/raid1 ext4 defaults 0 2' >> /etc/fstab
디스크 고장 시뮬레이션 및 교체 — 디스크 하나를 강제로 고장 내고 핫스왑 교체 과정을 실습합니다:
# 디스크 고장 시뮬레이션 (/dev/sdf를 고장 상태로 표시)
mdadm /dev/md0 --fail /dev/sdf
# 상태 확인
cat /proc/mdstat
# md0 : active raid1 sde[0] sdf[1](F)
# 20955136 blocks super 1.2 [2/1] [U_]
# ← [U_]: sde는 정상(U), sdf는 고장(_)
# 고장 디스크 제거
mdadm /dev/md0 --remove /dev/sdf
# 새 디스크 준비 (실습에서는 /dev/sdg 사용)
# 실제 환경에서는 물리적으로 디스크를 교체한 후 진행
# 새 디스크 추가 (자동 rebuild 시작)
mdadm /dev/md0 --add /dev/sdg
# rebuild 진행 확인
cat /proc/mdstat
# md0 : active raid1 sdg[2] sde[0]
# 20955136 blocks super 1.2 [2/1] [U_]
# [========>............] recovery = 42.3% (8857856/20955136) finish=1.0min speed=195M/s
# rebuild 완료 후
# md0 : active raid1 sdg[1] sde[0]
# 20955136 blocks super 1.2 [2/2] [UU]
7. LVM 상태 모니터링 명령어 총정리
# ========== PV 관련 ==========
# PV 목록 간략히
pvs
# PV 상세 정보
pvdisplay
# 특정 PV 상세
pvdisplay /dev/sdb1
# PV 삭제 (VG에서 제거 후 가능)
pvremove /dev/sdb1
# ========== VG 관련 ==========
# VG 목록 간략히
vgs
# VG 상세 정보
vgdisplay
# 특정 VG 상세
vgdisplay vg_data
# VG에 PV 추가
vgextend vg_data /dev/sdd1
# VG에서 PV 제거 (데이터 이동 후)
pvmove /dev/sdb1 # 기존 PV의 데이터를 다른 PV로 이동
vgreduce vg_data /dev/sdb1
# VG 이름 변경
vgrename vg_data vg_production
# VG 삭제
vgremove vg_data
# ========== LV 관련 ==========
# LV 목록 간략히
lvs
# LV 상세 정보
lvdisplay
# 특정 LV 상세
lvdisplay /dev/vg_data/lv_mysql
# LV 이름 변경
lvrename vg_data lv_mysql lv_database
# LV 삭제 (언마운트 후)
umount /var/lib/mysql
lvremove /dev/vg_data/lv_mysql
# ========== 전체 구조 한 눈에 ==========
# pvs + vgs + lvs 통합 출력
lvm fullreport
새벽 2시, 모니터링 알림이 울립니다. /var/lib/mysql 파티션 사용률이 95%를 넘었습니다.
LVM이 없었다면 (전통 파티션):
- 서비스 중단 공지 발송
- MySQL 서비스 중지
- 데이터 임시 이동
- 파티션 재편성 또는 새 디스크로 데이터 마이그레이션
- 서비스 재시작
- 총 소요 시간: 2~4시간, 서비스 다운타임 발생
LVM이 있다면 — LVM 위에서 파티션 크기를 온라인으로 변경하는 방법입니다:
# 1. 상황 파악 (1분)
df -h /var/lib/mysql
vgs vg_data # VFree 확인
# 2a. VG에 여유공간이 있는 경우 (3분)
lvextend -L +20G -r /dev/vg_data/lv_mysql
# 완료. 서비스 중단 없음.
# 2b. VG가 꽉 찬 경우 (10분)
# 새 디스크 연결 → pvcreate → vgextend → lvextend -r
# 완료. 서비스 중단 없음.
총 소요 시간: 3~15분, 서비스 다운타임 0
이것이 현대 리눅스 시스템 관리자가 LVM을 기본으로 사용하는 이유입니다. 클라우드 환경(AWS EBS, GCP Persistent Disk)에서도 온라인 볼륨 확장이 가능하지만, 인스턴스 내부의 파일시스템까지 확장하려면 결국 LVM 또는 resize2fs/xfs_growfs 명령이 필요합니다.
8. 트러블슈팅
상황 — LV 확장 명령은 성공했는데 실제 사용 가능한 공간이 늘지 않았습니다:
lvextend -L +10G /dev/vg_data/lv_mysql
# Logical volume vg_data/lv_mysql successfully resized.
df -h /var/lib/mysql
# Filesystem Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql 15G 14G 1.0G 94% /var/lib/mysql
# ← 여전히 15G로 표시됨!
원인 — lvextend는 LV(블록 장치) 크기만 늘립니다. 파일시스템은 그 위에 별도로 올라가 있어서, 파일시스템 드라이버에게 "공간이 늘었다"고 따로 알려야 합니다. -r 옵션을 생략하면 이 단계를 잊기 쉽습니다.
진단 — 파일시스템 타입을 먼저 확인합니다:
df -T /var/lib/mysql
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql ext4 15G 14G 1.0G 94% /var/lib/mysql
# ↑ 타입 확인 (ext4 또는 xfs)
해결 — 파일시스템 타입에 맞는 확장 명령을 실행합니다:
# ext4인 경우
resize2fs /dev/vg_data/lv_mysql
# resize2fs 1.46.5 (30-Dec-2021)
# Filesystem at /dev/mapper/vg_data-lv_mysql is mounted on /var/lib/mysql;
# on-line resizing required
# The filesystem on /dev/mapper/vg_data-lv_mysql is now 6553600 (4k) blocks long.
# xfs인 경우 (반드시 마운트된 상태에서, 마운트 포인트 경로 사용)
xfs_growfs /var/lib/mysql
# 결과 확인
df -h /var/lib/mysql
# Filesystem Size Used Avail Use% Mounted on
# /dev/mapper/vg_data-lv_mysql 25G 14G 11G 56% /var/lib/mysql
예방: lvextend에 항상 -r 옵션을 붙이면 파일시스템 확장까지 자동으로 처리됩니다.
lvextend -L +10G -r /dev/vg_data/lv_mysql
상황 — LV를 확장하려는데 VG 여유 공간이 없어 명령이 실패합니다:
lvextend -L +10G /dev/vg_data/lv_mysql
# Insufficient free space: 2560 extents needed, but only 0 available
원인 — VG 풀 자체가 꽉 찬 상태입니다. 물리 디스크를 새로 추가해 VG를 확장해야 합니다.
진단 — VFree가 0인지 확인합니다:
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 2 3 0 wz--n- <39.99g 0
# ↑ VFree 0: VG가 꽉 참
해결 — 새 디스크를 연결하고 PV → VG → LV 순으로 확장합니다:
# 1. 새 디스크 연결 확인
lsblk
# sdd 8:48 0 50G 0 disk ← 새로 추가된 디스크
# 2. 파티션 생성 (또는 디스크 전체 사용)
fdisk /dev/sdd
# n → p → 1 → Enter → Enter → t → 8e → w
partprobe /dev/sdd
# 디스크 전체를 바로 PV로 사용할 경우 파티션 불필요:
# pvcreate /dev/sdd
# 3. PV 생성
pvcreate /dev/sdd1
# Physical volume "/dev/sdd1" successfully created.
# 4. VG에 PV 추가
vgextend vg_data /dev/sdd1
# Volume group "vg_data" successfully extended
# 5. VG 여유공간 확인
vgs vg_data
# VG #PV #LV #SN Attr VSize VFree
# vg_data 3 3 0 wz--n- <89.98g <49.99g
# ↑ VFree 50GB 생김
# 6. LV 확장 진행
lvextend -L +10G -r /dev/vg_data/lv_mysql
# Logical volume vg_data/lv_mysql successfully resized.
반복 작업이라면 스크립트로 자동화할 수 있습니다:
#!/bin/bash
# 새 디스크를 VG에 추가하는 스크립트
NEW_DISK="/dev/sdd"
VG_NAME="vg_data"
# 파티션 생성
parted -s $NEW_DISK mklabel gpt mkpart primary 0% 100% set 1 lvm on
partprobe $NEW_DISK
# PV 생성 및 VG 확장
pvcreate ${NEW_DISK}1
vgextend $VG_NAME ${NEW_DISK}1
# 결과 확인
echo "=== 확장 완료 ==="
vgs $VG_NAME
상황 — 재부팅 후 시스템이 부팅되지 않고 Emergency Mode로 떨어집니다:
[FAILED] Failed to mount /var/lib/mysql.
See 'systemctl status var-lib-mysql.mount' for details.
[DEPEND] Dependency failed for Local File Systems.
You are in emergency mode. After logging in, type "journalctl -xb" to view
system logs, "systemctl reboot" to reboot, "systemctl default" or "exit"
to boot into default mode.
Give root password for maintenance (or press Control-D to continue):
원인 — /etc/fstab에 등록된 UUID가 실제 파일시스템 UUID와 다릅니다. LV를 삭제 후 재생성하거나 mkfs를 다시 실행하면 UUID가 바뀌는데, fstab이 이를 반영하지 못한 경우 발생합니다.
진단 — blkid로 실제 UUID를 확인하고 fstab과 비교합니다:
# Emergency Mode에서 root 암호 입력 후:
blkid
# /dev/mapper/vg_data-lv_mysql: UUID="NEW-UUID-HERE" TYPE="ext4"
cat /etc/fstab | grep mysql
# UUID=OLD-UUID-HERE /var/lib/mysql ext4 defaults 0 2
# ← UUID 불일치 확인
해결 — 루트를 읽기-쓰기로 리마운트 후 fstab을 수정합니다:
# 루트 파일시스템을 읽기-쓰기로 리마운트
mount -o remount,rw /
# fstab 수정 (실제 UUID로 교체)
sed -i 's/OLD-UUID-HERE/NEW-UUID-HERE/' /etc/fstab
# 마운트 테스트
mount -a
# 오류 없으면 성공
reboot
예방법:
# fstab 수정 후 항상 검증 실행
mount -a --fake # 실제 마운트 없이 문법 검사
mount -a # 실제 마운트로 오류 확인
# UUID를 fstab에 추가할 때는 blkid 출력을 직접 활용
blkid /dev/vg_data/lv_mysql | awk '{print $2}' | tr -d '"'
# UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890
긴급 복구 팁:
Emergency Mode에서 vi를 사용하기 어렵다면, 해당 라인을 주석처리하고 재부팅 후 정상 환경에서 수정하세요.
# fstab에서 문제 라인을 주석처리
sed -i '/lv_mysql/s/^/#/' /etc/fstab
reboot
# 정상 부팅 후 fstab 수정 및 수동 마운트
9. 실전 운영 패턴
대용량 데이터를 다루는 프로덕션 환경에서는 LVM과 RAID를 함께 사용합니다. RAID로 하드웨어 고장에 대비하고, LVM으로 유연한 용량 관리를 합니다.
일반적인 프로덕션 스토리지 구성 — 운영 서버에서 자주 쓰는 LVM+RAID 조합 예시입니다:
물리 계층:
/dev/sdb (1TB SSD) ─┐
/dev/sdc (1TB SSD) ─┘ → RAID 1 → /dev/md0 (1TB, 미러링)
/dev/sdd (4TB HDD) ─┐
/dev/sde (4TB HDD) ─┤
/dev/sdf (4TB HDD) ─┘ → RAID 5 → /dev/md1 (8TB, 패리티)
LVM 계층:
pvcreate /dev/md0 /dev/md1
vgcreate vg_prod /dev/md0 /dev/md1
lvcreate -L 100G -n lv_system vg_prod → ext4 → /
lvcreate -L 200G -n lv_mysql vg_prod → xfs → /var/lib/mysql
lvcreate -L 500G -n lv_data vg_prod → xfs → /data
lvcreate -L 2T -n lv_archive vg_prod → ext4 → /archive
모니터링 스크립트 예시 — VG 사용률을 주기적으로 점검하고 임계치 초과 시 알림을 보냅니다:
#!/bin/bash
# /usr/local/bin/check-lvm-health.sh
echo "=== LVM 상태 리포트 $(date) ==="
echo -e "\n[PV 상태]"
pvs
echo -e "\n[VG 상태]"
vgs
echo -e "\n[LV 상태]"
lvs
echo -e "\n[LV 사용률 (80% 이상 경고)]"
df -h | grep "/dev/mapper/" | while read line; do
usage=$(echo $line | awk '{print $5}' | tr -d '%')
mountpoint=$(echo $line | awk '{print $6}')
if [ "$usage" -ge 80 ]; then
echo "경고: $mountpoint → ${usage}% 사용 중"
fi
done
echo -e "\n[RAID 상태]"
cat /proc/mdstat
echo -e "\n[스냅샷 사용률]"
lvs -o lv_name,snap_percent --select 'lv_attr=~s'
Cron으로 정기 확인 등록 — 스토리지 모니터링 스크립트를 cron에 등록해 자동화합니다:
# 매 시간 상태 체크, 이상 시 이메일 발송
echo "0 * * * * root /usr/local/bin/check-lvm-health.sh | mail -s 'LVM Health Report' admin@company.com" \
>> /etc/cron.d/lvm-monitor
실무에서 자주 쓰는 명령어 조합 — LVM 운영에서 반복적으로 쓰는 명령어 패턴 모음입니다:
# LV 전체 사용률 한 눈에 보기
df -hT | grep mapper | sort -k5 -rh
# LV별 I/O 모니터링 (iostat 사용)
iostat -x /dev/mapper/vg_data-lv_mysql 1
# LVM 이벤트 로그 확인
lvmconfig --type current | head -50
journalctl -u lvm2-monitor.service
# PV 간 데이터 균등 배분
pvmove /dev/sdb1 # sdb1의 데이터를 VG 내 다른 PV로 분산
# VG 스캔 (새 디스크 연결 후 인식이 안 될 때)
vgscan
pvscan
10. 주요 명령어 빠른 참조
# ========== LVM 구성 전체 흐름 ==========
pvcreate /dev/sdb1 # 1. PV 생성
vgcreate vg_data /dev/sdb1 # 2. VG 생성
lvcreate -L 20G -n lv_data vg_data # 3. LV 생성
mkfs.ext4 /dev/vg_data/lv_data # 4. 파일시스템 생성
mount /dev/vg_data/lv_data /mnt/data # 5. 마운트
# ========== 용량 확장 ==========
lvextend -L +10G -r /dev/vg_data/lv_data # LV 확장 + 파일시스템 확장
pvcreate /dev/sdc1 && vgextend vg_data /dev/sdc1 # 새 디스크 추가
# ========== 상태 확인 ==========
pvs && vgs && lvs # 전체 요약
pvdisplay && vgdisplay && lvdisplay # 전체 상세
# ========== 스냅샷 ==========
lvcreate -L 5G -s -n snap /dev/vg_data/lv_data # 스냅샷 생성
lvremove /dev/vg_data/snap # 스냅샷 삭제
# ========== RAID ==========
mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sde /dev/sdf
mdadm --detail /dev/md0 # RAID 상태
cat /proc/mdstat # RAID 동기화 진행 상황
mdadm /dev/md0 --fail /dev/sdf # 디스크 고장 처리
mdadm /dev/md0 --remove /dev/sdf # 고장 디스크 제거
mdadm /dev/md0 --add /dev/sdg # 새 디스크 추가 (rebuild 시작)
LVM Thin Provisioning — 용량 초과 리스크와 모니터링

개발 서버에서 각 팀마다 20GB씩 할당해달라는 요청이 10개 들어왔는데 실제 물리 디스크는 100GB뿐일 때, 실제로 다들 5GB씩만 쓰고 있다면 Thin Provisioning으로 모두를 만족시킬 수 있습니다. 하지만 이 방식에는 함정이 있습니다. 실제 사용량이 물리 용량을 초과하는 순간 thin pool이 가득 차고, 이때 데이터 손실이 발생합니다. 경고 없이 가득 찬 게 아니라 — 모니터링이 없으면 경고 없이 조용히 서비스가 멈춥니다. 그래서 Thin Provisioning을 쓰는 환경에서는 Data%, Metadata% 모니터링이 선택이 아닌 필수입니다.
Thin Provisioning은 실제 사용량보다 큰 논리 볼륨을 선언할 수 있습니다. 스토리지 효율이 높아지지만 thin pool이 가득 차면 데이터 손실이 발생합니다.
Thin Pool 생성 — 오버커밋을 허용하는 Thin Provisioning LV 풀을 만듭니다:
# 10GB thin pool 생성 (VG에서 메타데이터 + 데이터 풀 분리)
sudo lvcreate -L 10G --thinpool thin_pool vg_data
# Thin Pool에서 20GB 논리 볼륨 생성 (실제 10GB 풀에서 오버프로비저닝)
sudo lvcreate -V 20G --thin -n lv_app vg_data/thin_pool
# Thin Pool 상태 확인
sudo lvs -o name,pool_lv,data_percent,metadata_percent vg_data
# LV Pool Data% Meta%
# lv_app thin_pool 45.23 12.11
# thin_pool 45.23 12.11
용량/메타데이터 고갈 모니터링 — Thin Pool이 가득 차기 전에 경보를 받아야 서비스 장애를 막을 수 있습니다:
# 위험 기준: Data% > 80%, Meta% > 50%
sudo lvs --noheadings -o data_percent,metadata_percent vg_data/thin_pool | \
awk '{
data=$1+0; meta=$2+0
if (data > 80) print "[경고] Thin Pool 데이터 " data "% 사용"
if (meta > 50) print "[경고] Thin Pool 메타데이터 " meta "% 사용"
}'
# Thin Pool 자동 확장 설정 (lvm.conf)
# thin_pool_autoextend_threshold = 80 # 80% 도달 시
# thin_pool_autoextend_percent = 20 # 현재 크기의 20% 추가
sudo lvmconfig --type diff | grep autoextend
메타데이터 고갈 복구 절차 — Thin Pool 메타데이터가 고갈됐을 때 복구하는 절차입니다:
# 메타데이터 영역 긴급 확장 (데이터는 보존)
sudo lvextend --poolmetadatasize +1G vg_data/thin_pool
# 데이터 영역 확장
sudo lvextend -L +5G vg_data/thin_pool
LVM 장애 대응 — pvmove·vgchange 실패·스냅샷 rollback

디스크 하나가 이상 징후를 보여서 pvmove로 데이터를 안전하게 옮기는 중에 서버가 재부팅됐습니다. 재부팅 후 VG가 활성화되지 않아 lvs를 쳐도 아무것도 안 보입니다. 이 상황에서 어떻게 해야 할지 모르면 패닉 상태에서 잘못된 명령을 치다가 데이터를 날릴 수 있습니다. 또는 신규 배포 이후 서비스에 문제가 생겨서 스냅샷으로 롤백해야 하는데, 롤백 절차를 한 번도 연습해보지 않았다면 장애 상황에서 제대로 작동하지 않습니다. 이 ConceptBlock은 LVM 운영 중 실제로 마주치는 세 가지 장애 상황과 복구 절차를 다룹니다.
pvmove 실패 시 복구 — pvmove 중단 후 미완료 상태로 남은 LV를 복구합니다:
# pvmove 중 시스템 재부팅된 경우 — 재개 명령
sudo pvmove --abort # 진행 중인 pvmove 중단
sudo vgchange --sysinit vg_data # VG 상태 초기화
sudo pvmove /dev/sdb1 # 재시작
# pvmove 진행 상황 확인
sudo lvs -a -o name,copy_percent | grep pvmove
vgchange 활성화 실패 복구 — 부팅 후 VG가 활성화되지 않을 때 복구하는 방법입니다:
# 디바이스를 찾지 못해 VG 활성화 실패
# "Couldn't find device with uuid XXX"
sudo vgscan --mknodes # 디바이스 노드 재생성
sudo vgchange -ay vg_data # 강제 활성화
# 일부 PV 실패 시 partial 모드로 활성화 (데이터 손실 위험 — 긴급용)
sudo vgchange -ay --partial vg_data
LVM 스냅샷 merge/rollback — 스냅샷을 원본에 병합하거나 원본을 스냅샷 시점으로 롤백합니다:
# 스냅샷 생성 (변경 전)
sudo lvcreate -L 2G -s -n lv_app_snap /dev/vg_data/lv_app
# 롤백: 스냅샷으로 되돌리기
sudo umount /mnt/app
sudo lvconvert --merge vg_data/lv_app_snap
sudo lvchange -an vg_data/lv_app
sudo lvchange -ay vg_data/lv_app
sudo mount /dev/vg_data/lv_app /mnt/app
ext4 vs XFS 확장/축소 제약 — 두 파일시스템의 온라인 확장·축소 지원 여부를 비교합니다:
| 파일시스템 | 확장 | 축소 | 주의사항 |
|---|---|---|---|
| ext4 | 온라인 가능 (resize2fs) | 오프라인만 가능 | 마운트 해제 후 e2fsck 필수 |
| XFS | 온라인 가능 (xfs_growfs) | 불가 | XFS는 축소 자체가 지원되지 않음 |
# ext4 확장 (온라인)
sudo lvextend -L +5G vg_data/lv_app
sudo resize2fs /dev/vg_data/lv_app
# XFS 확장 (온라인)
sudo lvextend -L +5G vg_data/lv_app
sudo xfs_growfs /mnt/app # 마운트 포인트를 인수로 전달
# lvextend -r 옵션으로 자동 파일시스템 확장 (ext4/XFS 모두 지원)
sudo lvextend -r -L +5G vg_data/lv_app
XFS 볼륨 축소가 필요한 경우: 데이터 백업 → 볼륨 삭제 → 더 작은 볼륨 생성 → 데이터 복원 순서로 진행해야 합니다.
다음 모듈에서는 디스크 장애 진단 — smartctl, badblocks, fsck로 디스크 하드웨어 오류와 파일시스템 손상을 탐지하고 복구하는 방법을 다룹니다.