LVM (Logical Volume Manager) 은 LVM은 기존의 물리적인 디스크 파티션 구조의 방식에서 더 유연한 스토리지 관리 기능을 제공합니다. 파일시스템이 블록장치에 직접 접근해서 읽기,쓰기를 했다면 LVM은 파일시스템이 LVM이 만든 가상의 블록장치에 읽기, 쓰기를 수행합니다. 이것을 논리 볼륨이라고 하는데, 필요에 따라 크기를 조정하거나 관리할 수 있습니다.
LVM은 다음과 같은 주요 구성 요소로 구성됩니다:
- Physical Volumes (PV): 하나 이상의 물리적인 디스크 또는 파티션으로 구성된 스토리지 공간입니다. 이러한 물리적인 디스크 또는 파티션은 LVM의 기본 구성 요소입니다.
- Volume Groups (VG): 하나 이상의 Physical Volumes(PV)를 물리적 또는 가상적으로 그룹화한 것입니다. Volume Group은 논리 볼륨을 생성하고 할당하는 데 사용됩니다.
- Logical Volumes (LV): Volume Group 내에서 생성되며, 논리적인 블록 장치로 사용되는 가상의 스토리지 공간입니다. 논리 볼륨은 필요에 따라 크기를 조정하거나 다른 논리 볼륨과 병합할 수 있습니다.
이러한 LVM 이 디스크에서는 어떻게 표현되고 관리되는 지 살펴봅시다!
LVM 디스크 덤프
+--------------------------+
| thick_vol | thick_snap | | LV
+--------------------------+
| thick | VG
+--------------------------+
| /dev/sdd | /dev/sde | PV
+--------------------------+
위 그림과 같이 구성된 LVM 에서 /dev/sdd 와 /dev/sde 의 덤프를 해보면 어떻게 나올까요?
# pvs
PV VG Fmt Attr PSize PFree
/dev/sdd thick lvm2 a-- <50.00g 48.98g
/dev/sde thick lvm2 a-- <50.00g <50.00g
# vgs
VG #PV #LV #SN Attr VSize VFree
thick 2 2 1 wz--n- 99.99g 98.98g
# lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
thick_snap thick swi-a-s--- 12.00m thick_vol 0.00
thick_vol thick owi-a-s--- 1.00g
덤프를 하고, 알아보기 쉽도록 hex 데이터로 바꿔봅시다.
# dd if=/dev/sdd of=/root/sdd_dump bs=1020k count=1
# dd if=/dev/sde of=/root/sde_dump bs=1020k count=1
# xxd sdd_dump > sdd_hex
# xxd sde_dump > sde_hex
# cat sdd_hex | head -n 656
...
0000200: 4c41 4245 4c4f 4e45 0100 0000 0000 0000 LABELONE........
0000210: a982 7799 2000 0000 4c56 4d32 2030 3031 ..w. ...LVM2 001
0000220: 4a6f 6c4f 5177 7833 4466 5033 756a 6e75 JolOQwx3DfP3ujnu
0000230: 776e 6435 4342 4735 6230 6747 5975 3874 wnd5CBG5b0gGYu8t
LABELONE 등등 여러가지 문자 들이 뚜렷하게 보이네요! ![]()
/dev/sde 은 어떻게 되어 있을까요?
# cat sde_hex | head -n 656
...
0000200: 4c41 4245 4c4f 4e45 0100 0000 0000 0000 LABELONE........
0000210: 1d8e 939d 2000 0000 4c56 4d32 2030 3031 .... ...LVM2 001
0000220: 4647 5a33 4d45 6150 4c68 394e 784f 724f FGZ3MEaPLh9NxOrO
0000230: 6e39 7a38 4172 6459 7071 4f33 6a35 6c52 n9z8ArdYpqO3j5lR
뭔가 알 것 같으면서도 아직은 어떤 데이터가 있는지 모르겠네요. 어떻게 구성되어 있는 걸까요?
LVM 디스크에 쓰여진 데이터 해석
LVM 소스 코드를 통해서 디스크에 어떻게 작성하고 있는지 엿볼 수 있습니다. 다음 구조체를 보면 라벨이 찍혀있고 그 뒤의 여러 정보들이 있습니다.
$ cat lib/label/label.h
35 /* On disk - 32 bytes */
36 struct label_header {
37 >---int8_t id[8];>-->---/* LABELONE */
38 >---uint64_t sector_xl;>/* Sector number of this label */
39 >---uint32_t crc_xl;>---/* From next field to end of sector */
40 >---uint32_t offset_xl;>/* Offset from start of struct to contents */
41 >---int8_t type[8];>>---/* LVM2 001 */
42 } __attribute__ ((packed));
주석을 살펴보니 라벨, 섹터정보, crc 정보, offset 정보, LVM 타입 등이 보이네요.
LVM 이 사용하고 있는 디스크이고, 어디서부터 정보를 저장하고 있는지 간략하게 파악할 수 있었습니다.
그 다음 내용은 뭘까요?
# cat sde_hex | head -n 656
...
0000220: 4647 5a33 4d45 6150 4c68 394e 784f 724f FGZ3MEaPLh9NxOrO
0000230: 6e39 7a38 4172 6459 7071 4f33 6a35 6c52 n9z8ArdYpqO3j5lR
0000240: 0000 0080 0c00 0000 0000 1000 0000 0000 ................
0000250: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000260: 0000 0000 0000 0000 0010 0000 0000 0000 ................
0000270: 00f0 0f00 0000 0000 0000 0000 0000 0000 ................
0000280: 0000 0000 0000 0000 0200 0000 0100 0000 ................
0000290: 0000 0000 0000 0000 0000 0000 0000 0000 ................
$ cat lib/format_text/layout.h
40 /* Fields with the suffix _xl should be xlate'd wherever they appear */
41 /* On disk */
42 struct pv_header {
43 >---int8_t pv_uuid[ID_LEN];
44
45 >---/* This size can be overridden if PV belongs to a VG */
46 >---uint64_t device_size_xl;>---/* Bytes */
47
48 >---/* NULL-terminated list of data areas followed by */
49 >---/* NULL-terminated list of metadata area headers */
50 >---struct disk_locn disk_areas_xl[0];>-/* Two lists */
51 } __attribute__ ((packed));
52
53 /*
54 * Ignore this raw location. This allows us to
pvcreate 시에 pvuuid 의 정보에 대해서 저장을 해뒀나 보네요. pv 의 uuid 와 디바이스의 정보를 포함하고 있습니다.
disk_areas_xl 에는 각각 데이터 영역 정보와 메타데이터 영역 정보를 포함하고 있습니다. 영역 정보는 리스트로 들어갈 수 있는데, 리스트가 끝났다면 zero 값으로 offset 과 size 를 채워넣습니다.
$ cat lib/format_text/format-text.h
67 /* On disk */
68 struct disk_locn {
69 >---uint64_t offset;>---/* Offset in bytes to start sector */
70 >---uint64_t size;>->---/* Bytes */
71 } __attribute__ ((packed));
아래에서 보면 0000 1000 0000 0000 값이 데이터 영역의 offset, 0000 0000 0000 0000 이 데이터 영역의 size 임을 확인할 수 있습니다.
offset,size등은bswap을 사용하여 저장하고 읽습니다. 위의0000 1000 0000 0000는 그러므로x100000값으로 읽을 수 있습니다.
0000240: 0000 0080 0c00 0000 0000 1000 0000 0000 ................
0000250: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000260: 0000 0000 0000 0000 0010 0000 0000 0000 ................
0000270: 00f0 0f00 0000 0000 0000 0000 0000 0000 ................
0000280: 0000 0000 0000 0000 0200 0000 0100 0000 ................
데이터 영역의 위치로 가면 아직 아무것도 작성을 하지 않아서 그런지 아무것도 없습니다.
0100000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0100010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0100020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0100030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0100040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0100050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0100060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0100070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0100080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
하지만 메타데이터 영역의 위치(x10 00) 으로 가면 메타데이터의 헤더가 있는 것을 확인할 수 있죠.
0001000: 2af4 634e 204c 564d 3220 785b 3541 2572 *.cN LVM2 x[5A%r
0001010: 304e 2a3e 0100 0000 0010 0000 0000 0000 0N*>............
0001020: 00f0 0f00 0000 0000 00a0 0100 0000 0000 ................
0001030: 0007 0000 0000 0000 a181 917c 0000 0000 ...........|....
0001040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0001050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0001060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0001070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0001080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
메타데이터 헤더는 어떻게 구성되어 있을까요?
$ cat lib/format_text/layout.h
71 /* On disk */
72 /* Structure size limited to one sector */
73 struct mda_header {
74 >---uint32_t checksum_xl;>--/* Checksum of rest of mda_header */
75 >---int8_t magic[16];>--/* To aid scans for metadata */
76 >---uint32_t version;
77 >---uint64_t start;>>---/* Absolute start byte of mda_header */
78 >---uint64_t size;>->---/* Size of metadata area */
79
80 >---struct raw_locn raw_locns[0];>--/* NULL-terminated list */
81 } __attribute__ ((packed));
60 /* On disk */
61 struct raw_locn {
62 >---uint64_t offset;>---/* Offset in bytes to start sector */
63 >---uint64_t size;>->---/* Bytes */
64 >---uint32_t checksum;
65 >---uint32_t flags;
66 } __attribute__ ((packed));
메타데이터 헤더는 헤더 정보의 유효성을 검증하고 메타데이터의 위치를 가리키고 있습니다. 실제로 메타데이터는 mda_header->start + raw_locns->offset 에서 확인이 가능합니다.
이제 주소를 계산하는 것도 어렵지 않습니다!
x1000 + x1a000 = x1b000
001b000: 7468 6963 6b20 7b0a 6964 203d 2022 3266 thick {.id = "2f
001b010: 7559 4431 2d48 5856 792d 315a 3275 2d76 uYD1-HXVy-1Z2u-v
001b020: 797a 332d 7765 4530 2d6c 6553 4a2d 716e yz3-weE0-leSJ-qn
001b030: 6d79 784f 220a 7365 716e 6f20 3d20 3333 myxO".seqno = 33
001b040: 0a66 6f72 6d61 7420 3d20 226c 766d 3222 .format = "lvm2"
001b050: 0a73 7461 7475 7320 3d20 5b22 5245 5349 .status = ["RESI
001b060: 5a45 4142 4c45 222c 2022 5245 4144 222c ZEABLE", "READ",
001b070: 2022 5752 4954 4522 5d0a 666c 6167 7320 "WRITE"].flags.
001b080: 3d20 5b5d 0a65 7874 656e 745f 7369 7a65 = [].extent_size
이렇게 LVM 이 디스크에 어떻게 정보를 저장하고 활용하는지 살펴보았습니다. 만약 LVM 으로 구성한 서비스를 사용하다가 메타데이터가 깨진 오류가 나온다면 이곳에 문제가 있는지 없는지 살펴볼 수 있겠네요 ![]()
참고
- lvm: https://github.com/lvmteam/lvm2
게이트웨이 On-promise 제품 팀에서 시스템 모니터링 및 관리를 쉽게 다가갈 수 있도록 하기 위한 업무를 하고 있습니다.
Contact: lhjnano@gmail.com