CephFS — 커널 클라이언트
CephFS를 사용하는 Linux 커널 클라이언트 경로를 중심으로 분산 파일시스템 동작을 설명합니다. MON 맵 갱신과 세션 유지, CRUSH에 따른 객체 배치, MDS 메타데이터 조회와 capability/lease 기반 일관성 제어, objecter를 통한 OSD I/O 제출·재시도·복구, 스냅샷 realm 처리, 네트워크 장애 시 재연결 전략, mount 옵션과 캐시 정책 튜닝까지 fs/ceph/·net/ceph/ 코드 흐름에 맞춰 상세히 다룹니다. RADOS 아키텍처 심화, CRUSH 알고리즘 내부 동작, MDS capability 모델, 인라인 데이터, 디렉터리 프래그먼테이션, 파일 레이아웃, libceph messenger v2 프로토콜, CephX 인증, RBD 커널 드라이버 내부까지 포괄적으로 분석합니다.
핵심 요약
- Ceph — 오픈소스 분산 스토리지 플랫폼입니다. 오브젝트(RADOS), 블록(RBD), 파일시스템(CephFS) 세 가지 인터페이스를 제공합니다.
- CRUSH — Controlled Replication Under Scalable Hashing. 중앙 메타데이터 서버 없이 오브젝트 위치를 계산하는 알고리즘입니다.
- MON (Monitor) — 클러스터 맵(OSD map, CRUSH map)을 유지하는 관리 서버입니다. 홀수 개(3, 5, 7)로 구성합니다.
- OSD (Object Storage Daemon) — 실제 데이터를 저장하는 서버입니다. 보통 디스크 1개당 OSD 1개를 실행합니다.
- MDS (Metadata Server) — CephFS의 디렉터리 구조와 파일 메타데이터를 관리합니다. 데이터는 OSD에 저장합니다.
- ceph.ko — Linux 커널 CephFS 클라이언트입니다. FUSE(ceph-fuse)보다 성능이 우수합니다.
- RBD (RADOS Block Device) — RADOS 위에서 블록 디바이스를 제공합니다. rbd.ko로 커널 블록 디바이스를 마운트합니다.
- Capability — MDS가 발급하는 파일 접근 토큰입니다. 읽기/쓰기 캐싱을 허용하고, 다른 클라이언트가 접근하면 revoke됩니다.
단계별 이해
- Ceph 클러스터 구성 파악 — MON이 클러스터 맵을 유지하고, OSD가 실제 데이터를 저장하며, MDS가 파일 메타데이터를 관리합니다.
클라이언트는 MON에서 최신 클러스터 맵을 받고, 이를 사용해 데이터가 저장된 OSD를 계산합니다.
- CRUSH 알고리즘 이해 — 오브젝트 이름 → 해시 → PG(Placement Group) → OSD 집합 순서로 결정적(Deterministic)으로 계산합니다.
클라이언트가 어떤 OSD에 쓸지 스스로 계산하므로, 중앙 메타데이터 서버의 병목이 없습니다.
- 커널 클라이언트 마운트 —
mount -t ceph mon:6789:/ /mnt/cephfs -o name=admin,secret=xxx로 마운트합니다.첫 번째 마운트는 MON에 연결하여 클러스터 맵을 받고, MDS에 접속하여 루트 디렉터리를 가져옵니다.
- MDS Capability 이해 — 파일을 열면 MDS가 클라이언트에게 Capability(읽기/쓰기/캐시)를 발급합니다.
클라이언트는 Capability 범위 내에서 로컬에서 데이터를 캐시하여 MDS 왕복 없이 처리합니다.
- I/O 경로 추적 — 파일 읽기 시 MDS에서 inode 정보를 받고, objecter가 해당 오브젝트를 저장한 OSD에 직접 읽기 요청을 보냅니다.
데이터 I/O는 OSD에 직접, 메타데이터 I/O는 MDS를 경유합니다.
- RBD 활용 — rbd.ko로 마운트한 블록 디바이스(
/dev/rbd0) 위에 ext4/XFS를 올립니다. 단일 마운트 용도에 적합합니다.Kubernetes에서 RBD CSI 드라이버로 영구 볼륨을 자동 프로비저닝합니다.
CephFS 개요
CephFS는 Ceph 분산 스토리지 시스템의 POSIX 호환 파일시스템 레이어입니다. RADOS(Reliable Autonomic Distributed Object Store)를 백엔드로 사용하며, Linux 커널 2.6.34(2010년)부터 메인라인에 포함되었습니다. CephFS는 데이터와 메타데이터를 분리하여 저장하는 독특한 구조를 가지며, 데이터는 OSD에 직접 저장하고 메타데이터는 MDS(Metadata Server)가 관리합니다. 이 분리 덕분에 대용량 파일의 I/O는 MDS를 거치지 않고 OSD에 직접 전달되어 높은 처리량을 달성합니다.
| 특성 | 내용 |
|---|---|
| 메인라인 포함 | Linux 2.6.34 (2010년, 실험적). 안정화: 4.x |
| 커널 소스 | fs/ceph/ (CephFS 클라이언트), net/ceph/ (RADOS 클라이언트) |
| 커널 모듈 | ceph.ko (CephFS + RBD 공통 RADOS 코드), rbd.ko |
| FUSE 대안 | ceph-fuse (사용자 공간, 기능 더 많음, 성능 낮음) |
| 지원 기능 | POSIX ACL, xattr, 심볼릭 링크, 하드링크, 스냅샷, 쿼터 |
| 다중 활성 MDS | 디렉터리 단위로 여러 MDS에 분산 (MDS 샤딩) |
| 데이터 풀 | cephfs_data — 파일 데이터 오브젝트 저장 |
| 메타데이터 풀 | cephfs_metadata — inode, dentry, 디렉터리 프래그먼트 저장 |
| 인라인 데이터 | 소규모 파일 내용을 inode에 직접 저장 (실험적) |
| 다중 파일시스템 | 하나의 Ceph 클러스터에 여러 CephFS 볼륨 지원 (Nautilus+) |
CONFIG_CEPH_FS=m (또는 =y)을 활성화해야 합니다. 이 옵션은 CONFIG_CEPH_LIB(net/ceph/ 라이브러리)에 의존합니다.
# 커널 설정 확인
$ grep -E "CEPH" /boot/config-$(uname -r)
CONFIG_CEPH_LIB=m
CONFIG_CEPH_LIB_PRETTYDEBUG=y
CONFIG_CEPH_LIB_USE_DNS_RESOLVER=y
CONFIG_CEPH_FS=m
CONFIG_CEPH_FSCACHE=y
CONFIG_CEPH_FS_POSIX_ACL=y
CONFIG_CEPH_FS_SECURITY_LABEL=y
CONFIG_BLK_DEV_RBD=m
# 모듈 로드 확인
$ lsmod | grep ceph
ceph 524288 1
libceph 393216 2 ceph,rbd
fscache 65536 1 ceph
# CephFS 파일시스템 타입 등록 확인
$ cat /proc/filesystems | grep ceph
nodev ceph
CephFS 풀 구조
CephFS는 최소 두 개의 RADOS 풀을 사용합니다. 메타데이터 풀은 inode 오브젝트, 디렉터리 프래그먼트, 파일 레이아웃 정보를 저장하며, 데이터 풀은 실제 파일 내용을 오브젝트 단위로 저장합니다.
# CephFS 볼륨 생성 (ceph-volume이 풀을 자동 생성)
$ ceph fs volume create myfs
# 또는 수동으로 풀과 파일시스템 생성
$ ceph osd pool create cephfs_data 128
$ ceph osd pool create cephfs_metadata 64
$ ceph fs new cephfs cephfs_metadata cephfs_data
# 풀 정보 확인
$ ceph fs ls
name: cephfs, metadata pool: cephfs_metadata, data pools: [cephfs_data ]
# 추가 데이터 풀 연결 (예: SSD 전용 풀)
$ ceph osd pool create cephfs_ssd 64
$ ceph fs add_data_pool cephfs cephfs_ssd
# 디렉터리별 데이터 풀 지정
$ setfattr -n ceph.dir.layout.pool -v cephfs_ssd /mnt/cephfs/fast_data
Ceph 클러스터 아키텍처
Ceph 클러스터는 MON(Monitor), OSD(Object Storage Daemon), MDS(Metadata Server), MGR(Manager)의 네 가지 데몬으로 구성됩니다. 각 데몬은 독립적인 역할을 수행하며, 클러스터의 확장성과 내결함성을 보장합니다.
클러스터 맵 종류
MON이 관리하는 클러스터 맵은 5종류입니다. 각 맵은 에포크(epoch) 번호가 있어 버전을 추적합니다.
| 맵 이름 | 내용 | 갱신 시점 |
|---|---|---|
| MON Map | 모니터 목록과 주소 | MON 추가/제거 |
| OSD Map | OSD 목록, 상태(up/down/in/out), 풀 정보 | OSD 상태 변경, 풀 생성/삭제 |
| CRUSH Map | 장애 도메인 계층, 배치 규칙 | 호스트/랙 추가, 규칙 변경 |
| PG Map | PG 상태(active+clean 등), 통계 | PG 상태 변경 |
| MDS Map | MDS 목록, 상태, 랭크, 서브트리 할당 | MDS failover, 리밸런싱 |
/* 커널 클라이언트 MON 연결 — net/ceph/mon_client.c */
struct ceph_mon_client {
struct ceph_client *client;
struct ceph_monmap *monmap; /* 현재 모니터 맵 */
struct ceph_connection con; /* 현재 연결된 MON */
int cur_mon; /* 현재 MON 인덱스 */
struct delayed_work delayed_work; /* 주기적 구독 갱신 */
bool hunting; /* MON 탐색 중 */
u32 have_osdmap; /* 보유 중인 OSD map 에포크 */
u32 have_mdsmap; /* 보유 중인 MDS map 에포크 */
};
/* MON 구독 요청 — 맵 갱신을 push로 받기 */
static void __send_subscribe(struct ceph_mon_client *monc)
{
/* osdmap, mdsmap, monmap 구독 요청 전송 */
/* 에포크 번호를 보내 차분 업데이트 요청 */
}
RADOS 아키텍처 심화
RADOS(Reliable Autonomic Distributed Object Store)는 Ceph의 기반 계층으로, 모든 데이터를 오브젝트로 저장합니다. CephFS, RBD, RGW(RADOS Gateway) 모두 RADOS 위에서 동작합니다. RADOS의 핵심은 자율적 복구와 결정론적 데이터 배치입니다.
RADOS 오브젝트 모델
RADOS의 오브젝트는 고유한 이름(oid), 데이터(최대 수 MB~수십 MB), 확장 속성(xattr), 오브젝트 맵(omap key-value)으로 구성됩니다. CephFS에서 파일 데이터는 고정 크기 오브젝트로 분할되어 저장됩니다.
| 오브젝트 구성 요소 | 설명 | CephFS 활용 |
|---|---|---|
| OID (오브젝트 이름) | 풀 내 고유 식별자 | {inode_number}.{stripe_offset} 형식 |
| 데이터 (data) | 바이트 스트림 (0~object_size) | 파일 내용의 청크 |
| xattr | 키-값 속성 (크기 제한: ~64KB) | 파일 레이아웃, 잠금 정보 |
| omap | 키-값 저장소 (크기 제한 없음) | 디렉터리 엔트리, MDS 메타데이터 |
/* RADOS 오브젝트 요청 구조체 — net/ceph/osd_client.c */
struct ceph_osd_request {
struct ceph_osd *r_osd; /* 대상 OSD */
struct ceph_object_id r_base_oid; /* 오브젝트 이름 */
struct ceph_object_locator r_base_oloc; /* 풀 ID */
struct ceph_pg r_pgid; /* 대상 PG */
int r_num_ops; /* 오퍼레이션 수 (복합 요청) */
struct ceph_osd_req_op r_ops[CEPH_OSD_MAX_OPS]; /* 오퍼레이션 배열 */
unsigned long r_stamp; /* 요청 타임스탬프 */
int r_attempts; /* 재시도 횟수 */
struct ceph_msg *r_request; /* 요청 메시지 */
struct ceph_msg *r_reply; /* 응답 메시지 */
ceph_osdc_callback_t r_callback; /* 완료 콜백 */
};
/* OSD 오퍼레이션 종류 */
enum {
CEPH_OSD_OP_READ = 0x0001, /* 오브젝트 읽기 */
CEPH_OSD_OP_WRITE = 0x0002, /* 오브젝트 쓰기 */
CEPH_OSD_OP_TRUNCATE = 0x0003, /* 오브젝트 잘라내기 */
CEPH_OSD_OP_ZERO = 0x0004, /* 영역 제로 채우기 */
CEPH_OSD_OP_STAT = 0x0007, /* 오브젝트 상태 조회 */
CEPH_OSD_OP_GETXATTR = 0x0101, /* xattr 읽기 */
CEPH_OSD_OP_SETXATTR = 0x0102, /* xattr 쓰기 */
CEPH_OSD_OP_OMAPGETVALS = 0x0311, /* omap 값 읽기 */
CEPH_OSD_OP_OMAPSETVALS = 0x0312, /* omap 값 쓰기 */
};
Placement Group (PG)
PG(Placement Group)는 오브젝트와 OSD 사이의 중간 매핑 계층입니다. 오브젝트는 PG에 매핑되고, PG는 OSD 집합에 매핑됩니다. PG가 없으면 오브젝트-OSD 매핑이 너무 세밀해져 OSD 추가/제거 시 대량의 데이터 이동이 발생합니다.
# PG 수 계산 공식
# 총 PG 수 = (OSD 수 * 100) / 복제 수, 가장 가까운 2의 거듭제곱으로 올림
# 예: 30 OSD, 복제 3 → (30 * 100) / 3 = 1000 → 1024 PG
# PG 상태 확인
$ ceph pg stat
256 pgs: 256 active+clean; 1.2 GiB data, 3.8 GiB used, 296 GiB / 300 GiB avail
# 특정 PG 상태 상세
$ ceph pg 2.1f query
{
"state": "active+clean",
"up": [3, 7, 12],
"acting": [3, 7, 12],
"info": {
"pgid": "2.1f",
"last_update": "100'1234",
"log_tail": "100'1000"
}
}
# PG가 어떤 OSD에 있는지 확인
$ ceph pg dump pgs_brief | head -5
PG_STAT STATE UP ACTING
2.0 active+clean [3,7,12]p3 [3,7,12]p3
2.1 active+clean [1,5,9]p1 [1,5,9]p1
CRUSH 알고리즘
CRUSH(Controlled Replication Under Scalable Hashing)는 Ceph의 핵심 혁신으로, 클라이언트가 중앙 서버 없이 오브젝트 위치를 직접 계산하는 알고리즘입니다. 2006년 Sage Weil의 논문에서 발표된 이 알고리즘은 의사 난수(pseudo-random) 분포를 통해 균일한 데이터 배치를 달성하면서도, 장애 도메인(호스트, 랙, 데이터센터)을 인식하여 내결함성을 보장합니다.
CRUSH 계산 흐름
# 오브젝트 배치 계산 과정
# 1. 파일 → 오브젝트
object_name = "ino.{inode}.{offset}" # 예: ino.10000.0
# 2. 오브젝트 → PG (Placement Group)
pgid = crc32(object_name) mod pg_num # 예: 0.1f
# 3. PG → OSD 집합 (CRUSH 맵으로 계산)
osds = CRUSH(pgid, crush_map) # 예: [osd.3, osd.7, osd.12]
# primary OSD: osd.3, replica: osd.7, osd.12
# 실제 확인
$ ceph osd map cephfs_data myfile
osdmap e100 pool 'cephfs_data' (2) object 'myfile' -> pg 2.3f7de88 (2.88) -> up ([3,7,12], p3) acting ([3,7,12], p3)
CRUSH 맵 규칙
# CRUSH 맵 구조 (계층: osd → host → rack → room → datacenter)
$ ceph osd getcrushmap -o crushmap.bin
$ crushtool -d crushmap.bin -o crushmap.txt
$ cat crushmap.txt
# 장애 도메인별 복제 규칙 예시
rule replicated_rule {
id 0
type replicated
step take default # 최상위 버킷에서 시작
step chooseleaf firstn 0 type host # 서로 다른 호스트의 OSD 선택
step emit
}
# 복제 수 및 규칙 확인
$ ceph osd pool ls detail
CRUSH 알고리즘 심화
CRUSH 알고리즘의 핵심은 버킷 선택(bucket selection)과 장애 도메인 인식(failure domain awareness)입니다. CRUSH 맵은 트리 구조의 장치 계층을 정의하며, 각 레벨(OSD, 호스트, 랙, 데이터센터 등)은 버킷으로 표현됩니다.
CRUSH 버킷 타입
CRUSH는 4가지 버킷 타입을 지원합니다. 각 타입은 OSD 추가/제거 시 데이터 이동량과 선택 속도 사이의 트레이드오프가 다릅니다.
| 버킷 타입 | 알고리즘 | 선택 시간 | 데이터 이동 | 용도 |
|---|---|---|---|---|
uniform | 해시 모듈로 | O(1) | 전체 재분배 (아이템 수 변경 시) | 동일 가중치 OSD |
list | 연결 리스트 순회 | O(n) | 최적 (마지막에 추가 시) | 점진적 확장 |
tree | 이진 트리 탐색 | O(log n) | 최적 | 대규모 계층 |
straw2 | 빨대 뽑기 (기본) | O(n) | 최적 | 범용 (권장) |
/* CRUSH 버킷 선택 — net/ceph/crush/mapper.c */
/* straw2 알고리즘: 각 아이템에 "빨대 길이"를 계산하여 가장 긴 것을 선택 */
static int bucket_straw2_choose(
const struct crush_bucket_straw2 *bucket,
int x, int r) /* x = PG 해시, r = replica 번호 */
{
int i;
int high = 0;
__u64 high_draw = 0;
__u64 draw;
for (i = 0; i < bucket->h.size; i++) {
/* 각 아이템의 빨대 길이 = hash(x, item_id, r) * ln(weight) */
draw = crush_hash32_3(bucket->h.hash,
x, bucket->h.items[i], r);
draw = crush_ln(draw) + bucket->item_weights[i];
if (i == 0 || draw > high_draw) {
high = i;
high_draw = draw;
}
}
return bucket->h.items[high];
}
/* CRUSH 규칙 실행기 — 주요 step 처리 */
static int crush_do_rule(
const struct crush_map *map,
int ruleno, int x, /* PG 해시값 */
int *result, int result_max,
const __u32 *weight, int weight_max)
{
/* step take default → 루트 버킷 선택 */
/* step chooseleaf firstn N type T →
N개의 서로 다른 type T 버킷에서 leaf OSD 선택 */
/* step emit → 결과 출력 */
}
ceph osd crush reweight osd.0 8.0으로 조정합니다.
커널 클라이언트 내부 구조
CephFS 커널 클라이언트(ceph.ko)는 fs/ceph/과 net/ceph/ 두 부분으로 구성됩니다. fs/ceph/는 VFS 인터페이스를 구현하고 MDS와 통신하며, net/ceph/(libceph)는 RADOS 프로토콜, OSD/MON 클라이언트, messenger 등 하위 네트워크 계층을 담당합니다.
핵심 구조체
/* CephFS 슈퍼블록 정보 */
struct ceph_fs_client {
struct super_block *sb;
struct ceph_client *client; /* RADOS 클라이언트 (net/ceph/) */
struct ceph_mds_client *mdsc; /* MDS 통신 관리 */
struct ceph_mount_options *mount_options;
unsigned long mount_state; /* 마운트 상태 머신 */
struct backing_dev_info backing_dev_info;
int wb_paused; /* writeback 일시 정지 */
};
/* RADOS 클라이언트 최상위 구조체 — net/ceph/ceph_common.c */
struct ceph_client {
struct ceph_options *options;
struct ceph_messenger msgr; /* 네트워크 메시지 처리 */
struct ceph_mon_client monc; /* MON 클라이언트 */
struct ceph_osd_client osdc; /* OSD 클라이언트 (objecter) */
struct ceph_auth_client *auth; /* CephX 인증 */
};
/* inode 확장 — Ceph 전용 메타데이터 */
struct ceph_inode_info {
struct ceph_vino i_vino; /* 가상 inode 번호 (ino + snap) */
struct ceph_file_layout i_layout; /* 파일 레이아웃 (오브젝트 크기 등) */
struct ceph_cap *i_auth_cap; /* 인증된 MDS의 캐퍼빌리티 */
struct list_head i_caps; /* 모든 캐퍼빌리티 목록 */
u64 i_inline_version;
struct ceph_snap_realm *i_snap_realm; /* 소속 스냅샷 realm */
struct ceph_dir_layout i_dir_layout; /* 디렉터리 레이아웃 */
u64 i_max_size; /* MDS 허가 최대 파일 크기 */
u64 i_truncate_size; /* truncate 크기 */
u32 i_truncate_seq; /* truncate 시퀀스 */
struct timespec64 i_rctime; /* 재귀 ctime (디렉터리) */
u64 i_rbytes; /* 재귀 바이트 수 (디렉터리) */
u64 i_rfiles; /* 재귀 파일 수 (디렉터리) */
u64 i_rsubdirs; /* 재귀 서브디렉터리 수 */
struct inode vfs_inode; /* VFS inode (항상 마지막 멤버) */
};
/* Capability — MDS가 발급하는 접근 토큰 */
struct ceph_cap {
struct ceph_inode_info *ci;
struct ceph_mds_session *session;
u32 issued; /* 현재 발급된 캡: CEPH_CAP_FILE_RD/WR/CACHE/LAZYIO 등 */
u32 implemented; /* 실제 구현된 캡 (issued의 슈퍼셋) */
u32 wanted; /* 클라이언트가 원하는 캡 */
u64 seq; /* 시퀀스 번호 */
u64 issue_seq; /* 발급 시퀀스 */
u64 mseq; /* 마이그레이션 시퀀스 */
u32 gen; /* 세션 세대 번호 */
int mds; /* 발급한 MDS 랭크 */
};
VFS 오퍼레이션 매핑
| VFS 인터페이스 | CephFS 구현체 | 소스 파일 |
|---|---|---|
super_operations | ceph_super_ops | fs/ceph/super.c |
inode_operations (파일) | ceph_file_iops | fs/ceph/inode.c |
inode_operations (디렉터리) | ceph_dir_iops | fs/ceph/dir.c |
file_operations | ceph_file_fops | fs/ceph/file.c |
file_operations (디렉터리) | ceph_dir_fops | fs/ceph/dir.c |
address_space_operations | ceph_aops | fs/ceph/addr.c |
xattr_handler | ceph_xattr_handlers | fs/ceph/xattr.c |
export_operations | ceph_export_ops | fs/ceph/export.c |
dentry_operations | ceph_dentry_ops | fs/ceph/dir.c |
MDS 프로토콜 (Capability/Lease)
MDS는 파일 메타데이터를 관리하고, 클라이언트에게 Capability를 발급하여 클라이언트 측 캐싱을 제어합니다. Capability는 데이터 일관성과 성능의 균형을 맞추는 핵심 메커니즘입니다. MDS 프로토콜은 MDS 요청(MClientRequest), MDS 응답(MClientReply), Capability 메시지(MClientCaps), Lease 메시지(MClientLease) 네 가지 주요 메시지 타입으로 구성됩니다.
Capability 타입
| Capability | 비트값 | 의미 |
|---|---|---|
| FILE_RD | 0x00010 | 파일 내용 읽기 가능 |
| FILE_WR | 0x00020 | 파일 내용 쓰기 가능 |
| FILE_CACHE | 0x00040 | 페이지 캐시에 읽기 데이터 캐싱 가능 |
| FILE_BUFFER | 0x00080 | 페이지 캐시에 쓰기 데이터 버퍼링 가능 |
| FILE_LAZYIO | 0x00100 | Lazy I/O (캐시 무효화 지연) |
| AUTH_SHARED | 0x00001 | 메타데이터 읽기 가능 |
| AUTH_EXCL | 0x00002 | 메타데이터 쓰기 가능 (단독) |
| LINK_SHARED | 0x00004 | 링크 수 읽기 가능 |
| LINK_EXCL | 0x00008 | 링크 수 변경 가능 (단독) |
| XATTR_SHARED | 0x00400 | xattr 읽기 가능 |
| XATTR_EXCL | 0x00800 | xattr 변경 가능 (단독) |
Capability 발급 및 회수
파일 열기 → MDS에 open 요청
MDS → 클라이언트에게 Capability(FILE_RD|FILE_CACHE) 발급
클라이언트 B가 같은 파일 쓰기 요청
→ MDS가 클라이언트 A에게 Capability Revoke (FILE_CACHE 회수)
→ 클라이언트 A: 캐시 플러시 + Cap 반납
→ MDS: 클라이언트 B에게 FILE_WR 발급
파일 닫기 → 클라이언트가 Capability 반납 + dirty 데이터 플러시
# 현재 Capability 상태 확인
$ cat /sys/kernel/debug/ceph/*/caps
MDS Capability 모델 심화
Capability는 MDS의 분산 일관성 모델의 핵심입니다. 단일 클라이언트가 파일에 접근할 때는 강력한 Capability(EXCL, BUFFER, CACHE)를 모두 발급받아 모든 작업을 로컬에서 수행합니다. 여러 클라이언트가 동시에 접근하면 MDS가 적절히 Capability를 회수(revoke)하여 일관성을 유지합니다.
커널에서의 Capability 확인 코드
/* fs/ceph/caps.c — Capability 확인 및 대기 */
int ceph_get_caps(struct file *filp, int need, int want,
loff_t endoff, int *got)
{
struct ceph_file_info *fi = filp->private_data;
struct inode *inode = filp->f_inode;
struct ceph_inode_info *ci = ceph_inode(inode);
int ret;
/* need: 반드시 필요한 cap (없으면 대기) */
/* want: 있으면 좋은 cap (없어도 진행) */
while (1) {
spin_lock(&ci->i_ceph_lock);
int have = __ceph_caps_issued(ci, NULL);
if ((have & need) == need) {
/* 필요한 cap 보유 → 바로 반환 */
*got = have & (need | want);
ceph_take_cap_refs(ci, *got, false);
spin_unlock(&ci->i_ceph_lock);
return 0;
}
/* cap 부족 → MDS에 요청 후 대기 */
ceph_check_caps(ci, CHECK_CAPS_AUTHONLY, NULL);
spin_unlock(&ci->i_ceph_lock);
ret = wait_event_interruptible(ci->i_cap_wq,
__ceph_caps_issued(ci, NULL) & need);
if (ret)
return ret;
}
}
Dentry Lease
Capability가 inode 단위라면, Dentry Lease는 디렉터리 엔트리(dentry) 단위의 캐싱 허가입니다. MDS는 클라이언트에게 dentry lease를 발급하여, 같은 이름의 lookup 결과를 로컬에서 재사용할 수 있게 합니다.
/* fs/ceph/dir.c — dentry lease 검증 */
static int ceph_d_revalidate(struct dentry *dentry, unsigned int flags)
{
struct ceph_dentry_info *di = ceph_dentry(dentry);
struct ceph_mds_client *mdsc;
/* lease가 유효한지 확인 */
if (di->lease_session &&
time_before(jiffies, di->time)) {
/* lease 유효 → MDS 왕복 없이 dentry 재사용 */
return 1; /* valid */
}
/* lease 만료 → MDS에 재확인 필요 */
return 0; /* invalid, VFS가 d_lookup을 다시 호출 */
}
/* Dentry lease 정보 */
struct ceph_dentry_info {
struct ceph_mds_session *lease_session;
u32 lease_gen; /* 세션 세대 번호 */
u32 lease_seq; /* lease 시퀀스 */
unsigned long time; /* lease 만료 시간 (jiffies) */
u64 offset; /* readdir 오프셋 */
};
CephFS 커널 클라이언트 I/O 경로
읽기 I/O 경로
/* CephFS 읽기 경로 (간략화) */
/* 1. VFS → ceph_read_iter */
static ssize_t
ceph_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct file *filp = iocb->ki_filp;
struct ceph_inode_info *ci = ceph_inode(filp->f_inode);
/* 2. Capability 확인 (FILE_RD 필요) */
ceph_check_caps(ci, CHECK_CAPS_NODELAY, NULL);
/* 3. 페이지 캐시에서 읽기 시도 */
if (ceph_has_inline_data(ci))
return ceph_read_inline_data(inode, to, pos, len);
/* 4. 페이지 캐시 미스 → objecter로 OSD 읽기 */
return generic_file_read_iter(iocb, to);
/* → address_space_operations.readpage → ceph_readpage */
/* → objecter_read_async → OSD에 RADOS READ 전송 */
}
쓰기 I/O 경로
CephFS의 쓰기 경로는 읽기보다 복잡합니다. 쓰기에는 FILE_WR Capability가 필요하며, 버퍼링 쓰기(buffered write)와 직접 쓰기(direct write) 두 가지 경로가 있습니다. 버퍼링 쓰기는 FILE_BUFFER Capability가 있을 때 페이지 캐시에 데이터를 쓰고, 나중에 OSD로 플러시합니다.
/* CephFS 쓰기 경로 — fs/ceph/file.c */
static ssize_t ceph_write_iter(
struct kiocb *iocb, struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct ceph_inode_info *ci = ceph_inode(inode);
int got = 0;
/* 1. Capability 획득 (FILE_WR 필수, FILE_BUFFER 선호) */
ret = ceph_get_caps(file, CEPH_CAP_FILE_WR,
CEPH_CAP_FILE_BUFFER,
endoff, &got);
if (got & CEPH_CAP_FILE_BUFFER) {
/* 2a. 버퍼링 쓰기: 페이지 캐시에 기록 */
written = generic_perform_write(iocb, from);
/* dirty 페이지는 나중에 ceph_writepages_start()로 플러시 */
} else {
/* 2b. 동기 쓰기: OSD에 직접 전송 */
written = ceph_sync_write(iocb, from, pos, got);
/* 파일을 오브젝트 단위로 분할하여 각 OSD에 전송 */
}
/* 3. MDS에 파일 크기 갱신 알림 (i_max_size 초과 시) */
if (pos + written > ci->i_max_size)
ceph_check_caps(ci, CHECK_CAPS_AUTHONLY, NULL);
ceph_put_cap_refs(ci, got);
return written;
}
Writeback 메커니즘
/* fs/ceph/addr.c — 페이지 캐시에서 OSD로 쓰기 플러시 */
static int ceph_writepages_start(
struct address_space *mapping,
struct writeback_control *wbc)
{
struct inode *inode = mapping->host;
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
/* 1. dirty 페이지를 수집 */
/* 2. 오브젝트 경계에 맞춰 분할 */
/* 3. ceph_osdc_new_request()로 OSD 요청 생성 */
/* 4. 요청 제출 (비동기) */
/* 5. 완료 시 페이지 clean 표시 */
/* 핵심: 오브젝트 경계를 넘는 쓰기는 두 개의 OSD 요청으로 분할 */
/* 예: object_size=4MB, offset=3MB, len=2MB →
오브젝트 N에 1MB + 오브젝트 N+1에 1MB */
}
O_DIRECT 플래그로 파일을 열면 페이지 캐시를 거치지 않고 OSD에 직접 쓰기를 수행합니다. 이 경우 FILE_BUFFER Capability는 사용되지 않으며, 각 쓰기마다 OSD 응답을 대기합니다. 대역폭은 높지만 레이턴시는 버퍼링 쓰기보다 길어질 수 있습니다.
인라인 데이터 (Inline Data)
CephFS의 인라인 데이터 기능은 소규모 파일의 내용을 MDS의 inode 오브젝트에 직접 저장합니다. 이렇게 하면 작은 파일을 읽을 때 OSD 왕복이 필요 없어, 소규모 파일 접근 성능이 크게 향상됩니다. 인라인 데이터는 실험적 기능으로, 기본적으로 비활성화되어 있습니다.
| 항목 | 설명 |
|---|---|
| 최대 크기 | 기본 4KB (MDS inline_data_max 설정) |
| 활성화 | ceph fs set cephfs inline_data true (실험적) |
| 작동 조건 | 파일 크기가 임계값 이하일 때 자동 적용 |
| 전환 | 파일이 커지면 자동으로 일반 오브젝트 스토리지로 전환 (uninline) |
| 저장 위치 | MDS의 inode 오브젝트 xattr 영역 |
/* fs/ceph/inline.c — 인라인 데이터 읽기 */
static int ceph_read_inline_data(
struct inode *inode,
struct page *locked_page,
u64 off, u64 len)
{
struct ceph_inode_info *ci = ceph_inode(inode);
/* 인라인 데이터는 MDS 응답(MClientReply)에 포함되어 옴 */
/* ci->i_inline_version으로 버전 관리 */
if (ci->i_inline_version == CEPH_INLINE_NONE)
return -EINVAL;
/* 페이지에 인라인 데이터 복사 */
memcpy(page_address(locked_page) + off,
ci->i_inline_data, len);
return 0;
}
/* 인라인 → 일반 오브젝트 전환 (uninline) */
static int ceph_uninline_data(struct file *file)
{
/* 1. 인라인 데이터를 OSD 오브젝트로 쓰기 */
/* 2. MDS에 inline_version = CEPH_INLINE_NONE 통보 */
/* 3. 이후 일반 I/O 경로 사용 */
}
# 인라인 데이터 활성화
$ ceph fs set cephfs inline_data true
# 인라인 상태 확인 (ceph-fuse에서만 가능)
$ getfattr -n ceph.file.inline_data /mnt/cephfs/small_file
# 주의: 인라인 데이터 활성화 후 비활성화는 불가
# 기존 인라인 파일은 쓰기 시 자동으로 uninline됨
디렉터리 프래그먼테이션
CephFS는 대규모 디렉터리의 메타데이터를 여러 MDS에 분산하기 위해 디렉터리 프래그먼테이션(directory fragmentation)을 사용합니다. 하나의 디렉터리가 너무 많은 엔트리를 가지면 MDS가 디렉터리를 여러 프래그먼트(frag)로 분할하고, 각 프래그먼트를 다른 MDS에 할당할 수 있습니다.
/* 디렉터리 프래그먼트 식별자 — include/linux/ceph/types.h */
struct ceph_frag {
__le32 frag; /* 상위 비트: 분할 수준, 하위 비트: 인덱스 */
};
/* 프래그먼트 계산 */
static inline int ceph_frag_bits(u32 f)
{
return f >> 24; /* 분할 비트 수 (0=전체, 1=2분할, 2=4분할...) */
}
static inline u32 ceph_frag_value(u32 f)
{
return f & 0xFFFFFF; /* 프래그먼트 인덱스 */
}
/* 이름 → 프래그먼트 매핑 */
static u32 ceph_frag_containing_value(u32 name_hash, int bits)
{
return (bits << 24) | (name_hash >> (32 - bits));
}
# MDS 디렉터리 프래그먼트 확인
$ ceph daemon mds.0 dirfrag ls /data
[
{"path":"/data","frag":"0x0","dirfrag":"1.0"},
{"path":"/data","frag":"0x100.0","dirfrag":"1.100"},
{"path":"/data","frag":"0x100.1","dirfrag":"1.101"}
]
# MDS 분할 설정
$ ceph config set mds mds_bal_split_size 10000 # 10000 엔트리 초과 시 분할
$ ceph config set mds mds_bal_merge_size 50 # 50 미만이면 병합
$ ceph config set mds mds_bal_split_bits 3 # 분할 시 2^3=8개로 나눔
ceph fs set cephfs max_mds 2로 다중 MDS를 활성화하면, MDS는 디렉터리 서브트리 단위로 부하를 분산합니다. 핫스팟 디렉터리의 프래그먼트를 다른 MDS로 마이그레이션하여 메타데이터 처리량을 향상시킵니다.
파일 레이아웃 심화
CephFS의 파일 레이아웃(file layout)은 파일 데이터가 RADOS 오브젝트로 어떻게 분할되어 저장되는지를 정의합니다. 레이아웃은 디렉터리 단위로 설정할 수 있으며, 하위 파일은 부모 디렉터리의 레이아웃을 상속합니다.
| 레이아웃 속성 | 기본값 | 설명 |
|---|---|---|
stripe_unit | 4 MB | 스트라이프 단위 크기 (한 OSD에 기록하는 단위) |
stripe_count | 1 | 스트라이프 폭 (동시에 기록하는 OSD 수) |
object_size | 4 MB | RADOS 오브젝트 최대 크기 |
pool | cephfs_data | 데이터를 저장할 RADOS 풀 |
/* CephFS 파일 레이아웃 구조체 — include/linux/ceph/ceph_fs.h */
struct ceph_file_layout {
u32 stripe_unit; /* 스트라이프 단위 (바이트) */
u32 stripe_count; /* 스트라이프 수 */
u32 object_size; /* 오브젝트 크기 (바이트) */
s64 pool_id; /* 데이터 풀 ID */
char pool_ns[0]; /* 풀 네임스페이스 (가변 길이) */
};
/* 파일 오프셋 → 오브젝트 매핑 계산 */
static inline void ceph_calc_file_object_mapping(
struct ceph_file_layout *l,
u64 off, u64 len,
u64 *ono, u64 *objoff, u64 *objlen)
{
u32 su = l->stripe_unit;
u32 sc = l->stripe_count;
u32 os = l->object_size;
u32 stripes_per_obj = os / su;
/* 스트라이프 번호 계산 */
u64 stripe_no = off / su;
/* 오브젝트 집합 번호 (stripe_count 단위로 그룹) */
u64 obj_set = stripe_no / (sc * stripes_per_obj);
/* 그룹 내 인덱스 */
u64 stripe_in_set = stripe_no % (sc * stripes_per_obj);
/* 오브젝트 번호 */
*ono = obj_set * sc + stripe_in_set % sc;
/* 오브젝트 내 오프셋 */
*objoff = (stripe_in_set / sc) * su + off % su;
}
# 파일 레이아웃 설정
$ setfattr -n ceph.file.layout.stripe_unit -v 4194304 /mnt/cephfs/bigfile # 4MB stripe
$ setfattr -n ceph.file.layout.stripe_count -v 4 /mnt/cephfs/bigfile # 4 OSD 스트라이프
$ setfattr -n ceph.file.layout.object_size -v 33554432 /mnt/cephfs/bigfile # 32MB 오브젝트
# 디렉터리 레이아웃 설정 (하위 파일에 상속)
$ setfattr -n ceph.dir.layout.stripe_count -v 4 /mnt/cephfs/fast_dir/
$ setfattr -n ceph.dir.layout.pool -v cephfs_ssd /mnt/cephfs/fast_dir/
# 레이아웃 확인
$ getfattr -n ceph.file.layout /mnt/cephfs/bigfile
ceph.file.layout="stripe_unit=4194304 stripe_count=4 object_size=4194304 pool=cephfs_data"
# 파일의 오브젝트 매핑 확인 (rados 명령)
$ rados -p cephfs_data ls | grep "10000000"
10000000.00000000 # 첫 번째 오브젝트
10000000.00000001 # 두 번째 오브젝트
마운트 및 설정
CephFS 커널 클라이언트는 mount -t ceph 명령으로 마운트합니다. Linux 5.4부터 mount helper가 개선되어 mount.ceph 유틸리티가 MON 주소를 /etc/ceph/ceph.conf에서 자동으로 읽습니다.
# CephFS 커널 클라이언트 마운트
$ mount -t ceph 192.168.1.10:6789,192.168.1.11:6789:/ /mnt/cephfs \
-o name=admin,secretfile=/etc/ceph/admin.secret
# 또는 Ceph keyring 직접 사용
$ mount -t ceph :/ /mnt/cephfs \
-o name=admin,secret=AQCx...==
# Linux 5.7+ 새 구문 (device = mon_addr=IP:PORT)
$ mount -t ceph admin@fsid.cephfs=/ /mnt/cephfs \
-o mon_addr=192.168.1.10:3300,secretfile=/etc/ceph/admin.secret
# /etc/fstab 항목
192.168.1.10:6789:/ /mnt/cephfs ceph name=admin,secretfile=/etc/ceph/admin.secret,_netdev,noatime 0 0
# 서브디렉터리만 마운트
$ mount -t ceph 192.168.1.10:6789:/data/project /mnt/project \
-o name=admin,secretfile=/etc/ceph/admin.secret
마운트 옵션 상세
| 옵션 | 기본값 | 설명 |
|---|---|---|
name= | guest | CephX 인증 사용자 이름 |
secretfile= | - | 키링 파일 경로 |
secret= | - | 인증 키 (평문, 보안 취약) |
rsize= | 64MB | 최대 읽기 요청 크기 |
wsize= | 64MB | 최대 쓰기 요청 크기 |
rasize= | 8MB | readahead 크기 |
caps_max= | 0 (무제한) | 최대 보유 Capability 수 |
caps_wanted_delay_min= | 5 (초) | 불필요한 cap 반납 최소 대기 |
caps_wanted_delay_max= | 60 (초) | 불필요한 cap 반납 최대 대기 |
readdir_max_bytes= | 512KB | readdir 최대 바이트 |
readdir_max_entries= | 1024 | readdir 최대 엔트리 수 |
congestion_kb= | 0 (자동) | writeback 혼잡 임계값 |
mds_namespace= | - | CephFS 볼륨 이름 (다중 FS) |
recover_session= | no | 세션 복구 모드 (clean/no) |
noatime | - | 접근 시간 갱신 안 함 |
nodiratime | - | 디렉터리 접근 시간 갱신 안 함 |
/* fs/ceph/super.c — 마운트 옵션 파싱 */
static const struct fs_parameter_spec ceph_mount_parameters[] = {
fsparam_flag_no("acl", Opt_acl),
fsparam_u32("caps_max", Opt_caps_max),
fsparam_u32("caps_wanted_delay_min", Opt_caps_wanted_delay_min),
fsparam_u32("caps_wanted_delay_max", Opt_caps_wanted_delay_max),
fsparam_u32("readdir_max_bytes", Opt_readdir_max_bytes),
fsparam_u32("readdir_max_entries", Opt_readdir_max_entries),
fsparam_u32("congestion_kb", Opt_congestion_kb),
fsparam_u32("rsize", Opt_rsize),
fsparam_u32("wsize", Opt_wsize),
fsparam_u32("rasize", Opt_rasize),
fsparam_string("mds_namespace", Opt_mds_namespace),
fsparam_enum("recover_session", Opt_recover_session,
ceph_param_recover_session),
{}
};
CephX 인증
CephX는 Ceph의 인증 프로토콜로, Kerberos와 유사한 대칭 키 기반 인증을 사용합니다. 클라이언트, MON, OSD, MDS 모두 CephX를 통해 상호 인증합니다.
/* net/ceph/auth_x.c — CephX 인증 클라이언트 */
struct ceph_auth_client {
const struct ceph_auth_client_ops *ops;
bool negotiating;
struct ceph_crypto_key key; /* 클라이언트 비밀 키 */
u64 global_id; /* MON이 할당한 전역 ID */
};
/* CephX 인증 핸들러 */
static const struct ceph_auth_client_ops ceph_auth_x_ops = {
.name = "x",
.is_authenticated = ceph_x_is_authenticated,
.build_request = ceph_x_build_request,
.handle_reply = ceph_x_handle_reply,
.create_authorizer = ceph_x_create_authorizer,
.verify_authorizer_reply = ceph_x_verify_authorizer_reply,
};
# CephX 키 생성 및 관리
$ ceph auth get-or-create client.myuser \
mon 'allow r' \
osd 'allow rw pool=cephfs_data' \
mds 'allow rw' \
-o /etc/ceph/ceph.client.myuser.keyring
# 권한 확인
$ ceph auth get client.myuser
[client.myuser]
key = AQC7r2Ff...==
caps mds = "allow rw"
caps mon = "allow r"
caps osd = "allow rw pool=cephfs_data"
# CephFS 경로 제한 (클라이언트별 접근 범위)
$ ceph fs authorize cephfs client.restricted / r /data rw
# → 루트는 읽기만, /data는 읽기+쓰기
# 인증 비활성화 (테스트 환경 전용)
$ ceph config set global auth_cluster_required none
$ ceph config set global auth_service_required none
$ ceph config set global auth_client_required none
libceph Messenger v2
Messenger v2는 Ceph의 차세대 네트워크 프로토콜입니다(Linux 5.11+). v1 프로토콜 대비 TLS 유사 암호화, 향상된 프레이밍, 인증 개선을 제공합니다. 커널 클라이언트는 net/ceph/messenger_v2.c에 구현되어 있습니다.
| 특성 | Messenger v1 | Messenger v2 |
|---|---|---|
| 포트 | 6789 | 3300 |
| 프레이밍 | simple header + data | 구조화된 프레임 (control + data) |
| 암호화 | 없음 | CephX 세션 키 기반 AES-GCM |
| 압축 | 없음 | 선택적 (snappy, zstd) |
| 커널 지원 | 2.6.34+ | 5.11+ |
| 배너 | ceph v027\n | ceph v2\n\x10\x00 |
/* net/ceph/messenger_v2.c — Messenger v2 프레임 구조 */
/* v2 프레임 헤더 */
struct ceph_frame_desc {
int fd_tag; /* 프레임 타입 태그 */
int fd_seg_cnt; /* 세그먼트 수 (최대 4) */
int fd_lens[4]; /* 각 세그먼트 길이 */
int fd_aligns[4]; /* 각 세그먼트 정렬 */
};
/* v2 프레임 태그 */
#define FRAME_TAG_HELLO 1
#define FRAME_TAG_AUTH_REQUEST 2
#define FRAME_TAG_AUTH_BAD_METHOD 3
#define FRAME_TAG_AUTH_REPLY_MORE 4
#define FRAME_TAG_AUTH_REQUEST_MORE 5
#define FRAME_TAG_AUTH_DONE 6
#define FRAME_TAG_AUTH_SIGNATURE 7
#define FRAME_TAG_CLIENT_IDENT 8
#define FRAME_TAG_SERVER_IDENT 9
#define FRAME_TAG_IDENT_MISSING_FEATURES 10
#define FRAME_TAG_SESSION_RECONNECT 11
#define FRAME_TAG_MESSAGE 21
#define FRAME_TAG_KEEPALIVE2 22
#define FRAME_TAG_KEEPALIVE2_ACK 23
#define FRAME_TAG_ACK 24
/* messenger v2 연결 초기화 */
static int ceph_con_v2_try_write(struct ceph_connection *con)
{
/* 1. Banner 교환 ("ceph v2\n") */
/* 2. HELLO 프레임 (entity_type, addr) */
/* 3. AUTH 핸드셰이크 (CephX) */
/* 4. CLIENT_IDENT / SERVER_IDENT 교환 */
/* 5. 암호화 설정 (선택적) */
/* 6. 정상 메시지 교환 시작 */
}
ms_mode= 마운트 옵션으로 선호하는 프로토콜을 지정할 수 있습니다. ms_mode=prefer-crc(v2 CRC 모드), ms_mode=secure(v2 암호화 모드), ms_mode=legacy(v1만) 옵션이 있습니다.
쿼터 및 파일 레이아웃
CephFS 쿼터는 디렉터리 단위로 바이트 수와 파일 수를 제한합니다. 쿼터는 비동기적으로 적용되므로, 여러 클라이언트가 동시에 쓰면 약간의 초과가 발생할 수 있습니다.
# 디렉터리 쿼터 설정
$ setfattr -n ceph.quota.max_bytes -v 10737418240 /mnt/cephfs/project # 10GB
$ setfattr -n ceph.quota.max_files -v 100000 /mnt/cephfs/project # 최대 파일 수
# 쿼터 확인
$ getfattr -n ceph.quota.max_bytes /mnt/cephfs/project
$ getfattr -n ceph.quota.max_files /mnt/cephfs/project
# 쿼터 사용량 확인 (재귀 통계)
$ getfattr -n ceph.dir.rbytes /mnt/cephfs/project # 재귀 바이트 수
$ getfattr -n ceph.dir.rfiles /mnt/cephfs/project # 재귀 파일 수
$ getfattr -n ceph.dir.rsubdirs /mnt/cephfs/project # 재귀 디렉터리 수
# 쿼터 제거
$ setfattr -n ceph.quota.max_bytes -v 0 /mnt/cephfs/project
$ setfattr -n ceph.quota.max_files -v 0 /mnt/cephfs/project
/* fs/ceph/quota.c — 쿼터 확인 */
bool ceph_quota_is_max_bytes_exceeded(
struct inode *inode, u64 newsize)
{
/* inode부터 루트까지 조상을 순회하며 쿼터 확인 */
while (inode != root) {
struct ceph_inode_info *ci = ceph_inode(inode);
if (ci->i_max_bytes &&
(ci->i_rbytes + newsize) > ci->i_max_bytes)
return true; /* 쿼터 초과 */
inode = inode->i_sb->s_root->d_inode; /* 부모로 이동 */
}
return false;
}
RBD (RADOS Block Device)
RBD는 RADOS 위에서 동작하는 블록 디바이스입니다. 리눅스 커널 rbd.ko로 /dev/rbd<n> 블록 디바이스를 제공합니다. RBD는 CephFS와 동일한 libceph 라이브러리를 사용하여 RADOS에 접근하며, 이미지(image) 단위로 블록 디바이스를 관리합니다.
rbd.ko 사용법
# RBD 이미지 생성
$ rbd create --size 100G mypool/myimage
# 커널 클라이언트로 매핑
$ rbd map mypool/myimage --name client.admin
# → /dev/rbd0 생성
# 파일시스템 생성 및 마운트
$ mkfs.ext4 /dev/rbd0
$ mount /dev/rbd0 /mnt/rbd
# 매핑된 RBD 디바이스 확인
$ rbd showmapped
id pool image snap device
0 mypool myimage - /dev/rbd0
# 크기 조정 (온라인)
$ rbd resize --size 200G mypool/myimage
$ resize2fs /dev/rbd0 # ext4 파일시스템 확장
# /etc/ceph/rbdmap으로 부팅 시 자동 매핑
$ echo "mypool/myimage id=admin,keyring=/etc/ceph/ceph.client.admin.keyring" >> /etc/ceph/rbdmap
$ systemctl enable rbdmap
RBD 기능 플래그
| 기능 | 설명 | 커널 요구 버전 |
|---|---|---|
| layering | COW 클론(스냅샷 기반) | 3.10+ |
| exclusive-lock | 단독 잠금 (단일 마운트 보장) | 4.9+ |
| object-map | 기록된 오브젝트 비트맵 (빠른 export) | 4.9+ |
| fast-diff | 빠른 diff (object-map 기반) | 4.9+ |
| deep-flatten | 재귀 flatten (클론 체인 해제) | 4.9+ |
| journaling | 저널링 (RBD Mirror 용) | 4.17+ |
RBD 커널 드라이버 내부
/* drivers/block/rbd.c — RBD 디바이스 구조체 */
struct rbd_device {
dev_t dev_id; /* 디바이스 번호 */
struct gendisk *disk; /* 블록 디바이스 */
struct rbd_spec *spec; /* 풀/이미지/스냅 식별 */
struct ceph_osd_client *rbd_client->client->osdc;
u64 mapping_size; /* 이미지 크기 */
u32 layout_stripe_unit;
u32 layout_object_size;
struct rbd_layout layout; /* 오브젝트 레이아웃 */
struct blk_mq_tag_set tag_set; /* blk-mq 태그셋 */
};
/* RBD I/O 요청 처리 — blk-mq 기반 */
static blk_status_t rbd_queue_rq(
struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct request *rq = bd->rq;
struct rbd_device *rbd_dev = rq->q->queuedata;
/* 1. 블록 요청을 오브젝트 단위로 분할 */
/* 2. 각 오브젝트에 대해 ceph_osdc_new_request() */
/* 3. OSD에 비동기 요청 제출 */
/* 4. 모든 오브젝트 완료 시 blk_mq_end_request() */
}
mypool/myimage의 데이터는 rbd_data.{image_id}.{object_number} 형식의 RADOS 오브젝트로 저장됩니다. 기본 오브젝트 크기는 4MB이므로, 100GB 이미지는 약 25,600개의 오브젝트로 구성됩니다.
스냅샷
CephFS 스냅샷은 디렉터리 단위로 생성되며, 가상 .snap 디렉터리를 통해 접근합니다. 커널에서는 스냅샷 realm을 통해 스냅샷 계층을 관리합니다. CephFS 스냅샷은 COW(Copy-on-Write) 방식으로 동작하여, 스냅샷 생성 시점의 데이터는 변경되기 전까지 원본과 동일한 오브젝트를 공유합니다.
# CephFS 스냅샷 활성화
$ ceph fs set cephfs allow_new_snaps true
# 스냅샷 생성
$ mkdir /mnt/cephfs/project/.snap/snapshot_20240101
# 스냅샷 목록
$ ls /mnt/cephfs/project/.snap/
# 스냅샷에서 파일 복구
$ cp /mnt/cephfs/project/.snap/snapshot_20240101/important.txt \
/mnt/cephfs/project/important.txt.restored
# 스냅샷 삭제
$ rmdir /mnt/cephfs/project/.snap/snapshot_20240101
# RBD 스냅샷
$ rbd snap create mypool/myimage@snap1
$ rbd snap ls mypool/myimage
$ rbd snap rollback mypool/myimage@snap1 # 롤백
스냅샷 Realm 내부
/* 스냅샷 realm — 디렉터리 트리의 스냅샷 컨텍스트 */
struct ceph_snap_realm {
u64 ino; /* 이 realm의 inode 번호 */
struct ceph_snap_realm *parent; /* 부모 realm */
struct list_head children; /* 자식 realm 목록 */
struct list_head inodes; /* 이 realm에 속한 inode 목록 */
u64 *snaps; /* 스냅샷 ID 배열 */
u32 num_snaps; /* 스냅샷 수 */
u64 seq; /* 스냅샷 시퀀스 */
u64 created; /* realm 생성 시점 */
u64 parent_since; /* 부모와 분리된 시점 */
struct list_head dirty_item; /* 플러시 대기 목록 */
struct list_head inodes_with_caps; /* cap이 있는 inode */
struct ceph_snap_context *cached_context; /* 캐시된 스냅 컨텍스트 */
};
/* 스냅샷 컨텍스트 — OSD 쓰기 시 함께 전송 */
struct ceph_snap_context {
struct kref kref;
u64 seq; /* 최신 스냅샷 시퀀스 */
u32 num_snaps; /* 스냅 ID 수 */
u64 snaps[]; /* 스냅 ID 배열 (내림차순) */
};
장애 복구 및 재연결
분산 시스템에서 네트워크 장애와 서버 장애는 불가피합니다. CephFS 커널 클라이언트는 다양한 장애 상황에 대해 자동 복구 메커니즘을 갖추고 있습니다.
OSD 장애 복구
/* net/ceph/osd_client.c — OSD 장애 시 재매핑 */
static void handle_osds_changed(
struct ceph_osd_client *osdc)
{
/* 1. 새 OSD map 수신 (MON에서 push) */
/* 2. 모든 진행 중인 요청의 PG → OSD 매핑 재계산 */
/* 3. 대상 OSD가 변경된 요청은 새 OSD로 재전송 */
/* 4. PG가 peering 중이면 요청 대기 */
}
/* OSD 요청 재시도 */
static void __submit_request(
struct ceph_osd_request *req, bool wrlocked)
{
/* 요청 타임아웃 처리 */
/* osd_request_timeout (기본 0=무한) */
/* 타임아웃 시 -EIO 또는 재시도 */
/* PG가 active가 아니면 → 대기 큐에 추가 */
/* OSD가 down이면 → MON에서 새 map 수신 후 재매핑 */
}
MDS 장애 복구
| 장애 유형 | 클라이언트 동작 | 데이터 영향 |
|---|---|---|
| MDS 재시작 (정상) | 세션 재연결, cap 재발급 | 없음 (MDS 저널에서 복구) |
| MDS failover | standby MDS로 재연결 | 없음 (새 MDS가 저널 재생) |
| MDS blacklist | 클라이언트 강제 제거 | 비플러시 데이터 손실 가능 |
| 세션 타임아웃 | cap 강제 회수 | dirty 캐시 무효화 |
/* fs/ceph/mds_client.c — MDS 세션 재연결 */
static void send_mds_reconnect(
struct ceph_mds_client *mdsc,
struct ceph_mds_session *session)
{
/* MDS가 재시작하면 클라이언트에 reconnect 요청 */
/* 클라이언트는 보유 중인 모든 cap 목록을 전송 */
/* 1. 세션의 모든 cap을 수집 */
/* 2. MDS에 reconnect 메시지 전송 */
/* - cap 목록 (inode, issued, wanted) */
/* - 열린 파일 목록 */
/* - 스냅 realm 목록 */
/* 3. MDS가 cap을 재확인하여 grant/revoke */
/* 타임아웃: mds_reconnect_timeout (기본 45초) */
/* 초과 시 세션 drop → I/O 에러 */
}
# MDS failover 시뮬레이션
$ ceph mds fail mds.0
# → standby MDS가 자동으로 active가 됨
# 클라이언트 세션 상태 확인
$ ceph daemon mds.0 session ls
[
{
"id": 1234,
"entity": "client.5678",
"state": "open",
"num_caps": 156,
"request_load_avg": 0.5
}
]
# 블랙리스트 확인 (evicted 클라이언트)
$ ceph osd blocklist ls
# 클라이언트 강제 퇴출
$ ceph tell mds.0 client evict id=1234
# 마운트 옵션으로 자동 복구 설정
$ mount -t ceph :/ /mnt/cephfs -o \
name=admin,recover_session=clean
# recover_session=clean: MDS가 클라이언트를 blocklist하면
# 자동으로 새 세션 생성 (5.4+ 커널)
recover_session=clean 옵션 없이는 클라이언트가 unmount 후 다시 mount해야 합니다. 이 과정에서 플러시되지 않은 dirty 데이터는 손실될 수 있습니다.
성능 튜닝
CephFS 성능은 클라이언트 마운트 옵션, OSD 구성, MDS 캐시 크기, 네트워크 대역폭 등 다양한 요소에 의해 결정됩니다.
# 커널 클라이언트 파라미터 (마운트 옵션)
$ mount -t ceph :/ /mnt/cephfs -o \
name=admin,secretfile=/etc/ceph/admin.secret,\
rsize=67108864,\ # 읽기 최대 크기 64MB
wsize=67108864,\ # 쓰기 최대 크기 64MB
caps_max=65536,\ # 최대 캐퍼빌리티 수
readdir_max_bytes=524288,\ # 디렉터리 읽기 최대 바이트
readdir_max_entries=1024 # 디렉터리 읽기 최대 엔트리
# OSD 성능 튜닝
$ ceph osd pool set cephfs_data size 2 # 복제 수 (기본 3)
$ ceph osd pool set cephfs_data pg_num 256 # PG 수 조정
$ ceph osd pool set cephfs_data pgp_num 256
# MDS 성능: 캐시 크기
$ ceph config set mds mds_cache_memory_limit 4G
# 다중 활성 MDS 설정
$ ceph fs set cephfs max_mds 2
# MDS 서브트리 핀 (핫 디렉터리 고정)
$ setfattr -n ceph.dir.pin -v 0 /mnt/cephfs/hot_data # MDS.0에 고정
$ setfattr -n ceph.dir.pin -v 1 /mnt/cephfs/hot_meta # MDS.1에 고정
# 성능 모니터링
$ ceph -s # 클러스터 상태
$ ceph osd perf # OSD 별 지연시간
$ ceph mds perf stats # MDS 성능 통계
ceph-fuse vs 커널 클라이언트 비교
| 항목 | 커널 클라이언트 (ceph.ko) | FUSE 클라이언트 (ceph-fuse) |
|---|---|---|
| 구현 위치 | 커널 공간 | 사용자 공간 |
| 성능 (순차 읽기) | 높음 | 중간 (FUSE 오버헤드) |
| 메타데이터 성능 | 높음 | 낮음 |
| 기능 지원 | 커널 버전 의존 | 최신 기능 빠른 지원 |
| 루트 권한 | 필요 (mount) | 불필요 (일반 사용자 가능) |
| 안정성 | 커널 패닉 위험 낮음 | 프로세스 크래시만 |
| Subtree 마운트 | 지원 | 지원 |
| 스냅샷 | 4.17+ 안정 | 완전 지원 |
| 쿼터 | 4.17+ 안정 | 완전 지원 |
| 다중 FS | 5.7+ (mds_namespace) | 완전 지원 |
fscache 통합
CephFS 커널 클라이언트는 CONFIG_CEPH_FSCACHE 옵션으로 fscache를 지원합니다. fscache는 로컬 디스크에 원격 데이터를 캐시하여, 반복 읽기 성능을 크게 향상시킵니다.
# fscache 활성화 마운트
$ mount -t ceph :/ /mnt/cephfs -o name=admin,fsc
# cachefiles 데몬 설정 (/etc/cachefilesd.conf)
dir /var/cache/fscache
tag ceph
# cachefiles 데몬 시작
$ systemctl start cachefilesd
# fscache 통계 확인
$ cat /proc/fs/fscache/stats
Cookies: idx=3 dat=1567 spc=0
Objects: alc=1570 inv=0 avl=1570 ded=0
Pages : mrk=0 unc=0
Acquire: n=1570 nul=0 noc=0 ok=1570 nbk=0 int=0 oom=0
Lookups: n=1570 neg=0 pos=1570 crt=156 tmo=0
커널 소스 구조
| 경로 | 내용 | 주요 함수/구조체 |
|---|---|---|
fs/ceph/ | CephFS 파일시스템 클라이언트 | - |
fs/ceph/super.c | 슈퍼블록 등록, 마운트 처리 | ceph_mount(), ceph_fs_client |
fs/ceph/inode.c | inode 작업, MDS 응답 처리 | ceph_inode_info, ceph_fill_trace() |
fs/ceph/file.c | 파일 읽기/쓰기, mmap | ceph_read_iter(), ceph_write_iter() |
fs/ceph/dir.c | 디렉터리 작업 | ceph_readdir(), ceph_d_revalidate() |
fs/ceph/caps.c | Capability 관리 | ceph_check_caps(), ceph_get_caps() |
fs/ceph/mds_client.c | MDS 통신 클라이언트 | ceph_mdsc_do_request() |
fs/ceph/snap.c | 스냅샷 realm 관리 | ceph_snap_realm |
fs/ceph/addr.c | address_space 작업, page cache | ceph_readpage(), ceph_writepages_start() |
fs/ceph/xattr.c | 확장 속성 (xattr) | ceph_xattr_handlers |
fs/ceph/quota.c | 쿼터 확인 | ceph_quota_is_max_bytes_exceeded() |
fs/ceph/export.c | NFS export 지원 | ceph_export_ops |
fs/ceph/locks.c | 파일 잠금 (POSIX/flock) | ceph_lock(), ceph_flock() |
net/ceph/ | RADOS 클라이언트 (libceph) | - |
net/ceph/osd_client.c | OSD 클라이언트 (objecter) | ceph_osdc_new_request() |
net/ceph/mon_client.c | MON 클라이언트 | ceph_mon_client |
net/ceph/crush/ | CRUSH 알고리즘 구현 | crush_do_rule() |
net/ceph/messenger_v1.c | Messenger v1 프로토콜 | ceph_con_v1_try_write() |
net/ceph/messenger_v2.c | Messenger v2 프로토콜 | ceph_con_v2_try_write() |
net/ceph/auth_x.c | CephX 인증 | ceph_auth_x_ops |
drivers/block/rbd.c | RBD 블록 디바이스 드라이버 | rbd_device, rbd_queue_rq() |
주요 커널 설정 옵션
| CONFIG 옵션 | 설명 | 의존성 |
|---|---|---|
CONFIG_CEPH_LIB | libceph (RADOS 라이브러리) | NET, CRYPTO |
CONFIG_CEPH_LIB_PRETTYDEBUG | 메시지 hex dump 디버그 | CEPH_LIB |
CONFIG_CEPH_LIB_USE_DNS_RESOLVER | MON 호스트명 DNS 해석 | CEPH_LIB, DNS_RESOLVER |
CONFIG_CEPH_FS | CephFS 파일시스템 | CEPH_LIB |
CONFIG_CEPH_FSCACHE | fscache 로컬 캐시 통합 | CEPH_FS, FSCACHE |
CONFIG_CEPH_FS_POSIX_ACL | POSIX ACL 지원 | CEPH_FS |
CONFIG_CEPH_FS_SECURITY_LABEL | SELinux 레이블 지원 | CEPH_FS, SECURITY |
CONFIG_BLK_DEV_RBD | RBD 블록 디바이스 | CEPH_LIB, BLK_DEV |
디버깅 도구
CephFS 디버깅은 커널 클라이언트 측(debugfs, dmesg)과 클러스터 측(ceph 명령줄, 관리 소켓)을 모두 활용해야 합니다.
# 커널 클라이언트 디버그 정보
$ ls /sys/kernel/debug/ceph/
$ cat /sys/kernel/debug/ceph/*/monc_status # MON 연결 상태
$ cat /sys/kernel/debug/ceph/*/osdc_status # OSD 연결 상태
$ cat /sys/kernel/debug/ceph/*/mdsc_status # MDS 연결 상태
$ cat /sys/kernel/debug/ceph/*/caps # 캐퍼빌리티 상태
$ cat /sys/kernel/debug/ceph/*/dentry_lru # dentry LRU 캐시
$ cat /sys/kernel/debug/ceph/*/mds_sessions # MDS 세션 정보
# 로그 레벨 조정 (런타임)
$ echo "module ceph +p" > /sys/kernel/debug/dynamic_debug/control
# 또는 커널 파라미터로
$ modprobe ceph debug_ceph=3
# Ceph 클러스터 상태
$ ceph health detail
$ ceph df # 용량 사용량
$ ceph osd df # OSD별 용량
$ ceph mds stat # MDS 상태
$ ceph fs status # CephFS 상태
# MDS 관리 소켓 (상세 상태)
$ ceph daemon mds.0 session ls # 클라이언트 세션 목록
$ ceph daemon mds.0 dump_ops_in_flight # 처리 중인 요청
$ ceph daemon mds.0 cache status # MDS 캐시 상태
$ ceph daemon mds.0 perf dump # 성능 카운터
$ ceph daemon mds.0 dump_blocked_ops # 차단된 요청
# 느린 요청 확인
$ ceph osd pool stats
$ ceph daemon osd.0 dump_ops_in_flight # 처리 중인 I/O
$ ceph daemon mds.0 dump_ops_in_flight
# 클라이언트 eviction 이력
$ ceph osd blocklist ls
# bpftrace로 CephFS 읽기 레이턴시
$ bpftrace -e '
kprobe:ceph_read_iter { @start[tid] = nsecs; }
kretprobe:ceph_read_iter /@start[tid]/ {
@us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
# bpftrace로 CephFS 쓰기 레이턴시
$ bpftrace -e '
kprobe:ceph_write_iter { @start[tid] = nsecs; }
kretprobe:ceph_write_iter /@start[tid]/ {
@us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
# OSD 느린 요청 추적
$ ceph daemon osd.0 dump_historic_slow_ops
# 커널 CephFS tracepoints (ftrace)
$ echo 1 > /sys/kernel/debug/tracing/events/ceph/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
- "slow requests" — OSD 디스크 성능 확인 (
ceph osd perf), PG 불균형 확인 - "client blocked" — MDS가 cap revoke 대기 중,
dump_ops_in_flight로 확인 - "mds laggy" — MDS 메모리 부족,
mds_cache_memory_limit증가 고려 - EIO 에러 — blocklist 확인 (
ceph osd blocklist ls), 세션 상태 확인
RADOS 내부 동작 심화
RADOS는 Ceph의 모든 스토리지를 관장하는 기반 계층입니다. OSD, Monitor, Manager 각 데몬의 내부 동작을 커널 클라이언트 관점에서 상세히 살펴봅니다. 커널 클라이언트(net/ceph/)는 이들 데몬과 직접 TCP 연결을 맺고, 맵 갱신과 I/O 요청을 주고받습니다.
OSD 쓰기 내부 흐름
커널 클라이언트가 OSD에 WRITE 요청을 보내면, Primary OSD는 다음 단계를 거쳐 처리합니다. 모든 복제본이 커밋되어야 클라이언트에 ACK를 반환합니다.
| 단계 | 위치 | 동작 | 지연 시간 영향 |
|---|---|---|---|
| 1. 수신 | Messenger | TCP에서 메시지 수신, 디코딩 | 네트워크 RTT |
| 2. PG 디스패치 | OSD::dispatch | PG ID로 라우팅, PG 잠금 획득 | 큐 대기 시간 |
| 3. 복제 전파 | ReplicatedBackend | Replica OSD에 서브 쓰기 전송 | 네트워크 RTT |
| 4. 로컬 커밋 | BlueStore | WAL에 기록, 비동기 apply | 디스크 레이턴시 |
| 5. Replica ACK | Messenger | 모든 복제본 ACK 수집 | 가장 느린 복제본 |
| 6. 클라이언트 ACK | Primary OSD | 클라이언트에 WRITE_REPLY 전송 | 네트워크 RTT |
/* OSD 쓰기 흐름에서의 커널 클라이언트 콜백 */
/* net/ceph/osd_client.c — 쓰기 완료 처리 */
static void handle_reply(
struct ceph_osd *osd,
struct ceph_msg *msg)
{
struct ceph_osd_request *req;
/* 1. 요청 ID로 pending request 조회 */
req = lookup_request(&osd->o_requests, tid);
/* 2. 결과 코드 확인 */
if (result == -ENOENT || result == -EBLACKLISTED) {
/* blocklist → 세션 종료 */
}
/* 3. 에포크 확인 — 맵이 변경되었으면 재매핑 */
if (req->r_map_epoch < msg_epoch)
ceph_osdc_handle_map(osdc, msg);
/* 4. 사용자 콜백 호출 (writeback 완료 등) */
req->r_callback(req);
}
Monitor Paxos 동작
Monitor 클러스터는 Paxos 합의 프로토콜로 클러스터 맵의 일관성을 유지합니다. 커널 클라이언트는 MON에 구독(subscribe)하여 맵 변경 알림을 받습니다.
/* net/ceph/mon_client.c — MON 구독 메커니즘 */
struct ceph_mon_subscribe_item {
__le64 start; /* 구독 시작 에포크 */
__u8 flags; /* CEPH_SUBSCRIBE_ONETIME 등 */
} __attribute__((packed));
/* MON 맵 갱신 처리 */
static void ceph_monc_handle_map(
struct ceph_mon_client *monc,
struct ceph_msg *msg)
{
/* 증분(incremental) 맵 또는 전체(full) 맵 적용 */
/* 에포크 번호 비교 → 연속적 업데이트 보장 */
/* epoch 100 → 101 → 102 순서로 적용 */
/* 간격이 있으면(100 → 103) full 맵 요청 */
if (incremental) {
apply_incremental(monc, epoch, payload);
} else {
apply_full_map(monc, epoch, payload);
}
/* OSD 클라이언트에 맵 변경 알림 */
ceph_osdc_handle_map(&monc->client->osdc, msg);
}
Manager 모듈 시스템
| MGR 모듈 | 기능 | 커널 클라이언트 영향 |
|---|---|---|
| Dashboard | 웹 UI, 모니터링 대시보드 | 간접 (운영 도구) |
| Prometheus | 메트릭 내보내기 | 간접 (성능 분석) |
| Balancer | PG 자동 균형 조정 | PG 이동 시 I/O 일시 증가 |
| Orchestrator | 데몬 자동 배포/관리 | 간접 |
| pg_autoscaler | PG 수 자동 조정 | PG split/merge 시 I/O 일시 중단 |
| telemetry | 익명 사용 통계 수집 | 없음 |
| rbd_support | RBD 미러링, 자동 삭제 | RBD 디바이스에만 해당 |
CRUSH 알고리즘 상세
CRUSH 알고리즘의 핵심 연산인 straw2 선택과 collision 처리, tunables(조정 변수)를 상세히 분석합니다. CRUSH는 단순한 해시가 아니라, 가중치 기반 의사 난수 선택과 장애 도메인 인식을 결합한 정교한 배치 알고리즘입니다.
CRUSH Tunables (조정 변수)
CRUSH tunables는 알고리즘의 세부 동작을 제어하는 매개변수입니다. 클러스터 버전에 따라 프로파일이 다르며, 오래된 프로파일에서 최신 프로파일로 전환하면 데이터 이동이 발생합니다.
| 프로파일 | Ceph 버전 | 주요 변경 | 커널 최소 요구 |
|---|---|---|---|
argonaut | 0.48 (초기) | 기본 straw 버킷 | 3.2+ |
bobtail | 0.56 | 충돌(collision) 처리 개선 | 3.9+ |
firefly | 0.80 | straw2 버킷 도입 | 3.15+ |
hammer | 0.94 | straw2 기본, 가중치 안정성 | 4.1+ |
jewel | 10.2 | chooseleaf_vary_r 활성화 | 4.5+ |
optimal | (최신) | 모든 최적화 활성화 | 5.0+ |
# 현재 CRUSH tunables 확인
$ ceph osd crush show-tunables
{
"choose_local_tries": 0,
"choose_local_fallback_tries": 0,
"choose_total_tries": 50,
"chooseleaf_descend_once": 1,
"chooseleaf_vary_r": 1,
"chooseleaf_stable": 1,
"straw_calc_version": 1,
"allowed_bucket_algs": 54,
"profile": "jewel",
"optimal_tunables": 0,
"legacy_tunables": 0,
"require_feature_tunables": 1,
"require_feature_tunables2": 1,
"require_feature_tunables3": 1,
"require_feature_tunables5": 1
}
# tunables 프로파일 변경 (주의: 데이터 이동 발생)
$ ceph osd crush tunables optimal
# CRUSH 가중치 변경 시뮬레이션 (실제 변경 없이 영향 확인)
$ crushtool -i crushmap.bin --test --show-statistics \
--min-x 0 --max-x 255 --num-rep 3
# 커널 클라이언트가 지원하는 tunables 확인
$ cat /sys/module/libceph/parameters/supported_features
CRUSH 충돌(Collision) 처리
/* net/ceph/crush/mapper.c — 충돌 해결 */
/* chooseleaf에서 같은 버킷 내 중복 선택 시 재시도 */
static int crush_choose_firstn(
const struct crush_map *map,
struct crush_bucket *bucket,
const __u32 *weight, int weight_max,
int x, int numrep, /* 필요한 복제본 수 */
int type, /* 장애 도메인 타입 */
int *out, int outpos,
int tries, /* choose_total_tries (기본 50) */
int recurse_tries,
int vary_r, /* chooseleaf_vary_r */
int stable) /* chooseleaf_stable */
{
for (rep = outpos; rep < numrep; rep++) {
/* 각 복제본에 대해 최대 tries회 시도 */
for (ftotal = 0; ftotal < tries; ftotal++) {
/* straw2로 아이템 선택 */
item = bucket_choose(in, x, r + ftotal);
/* 이미 선택된 아이템과 중복 → r 증가 후 재시도 */
if (is_collision(out, outpos, item))
continue;
/* 아이템이 down(가중치 0) → 재시도 */
if (weight[item] == 0)
continue;
/* 유효한 아이템 → 결과에 추가 */
out[rep] = item;
break;
}
}
}
ceph osd crush tunables로 choose_total_tries를 늘리거나, 장애 도메인 레벨을 낮추세요(예: type host → type osd).
Erasure Coding과 CRUSH 규칙
CRUSH는 복제(replicated) 풀뿐 아니라 삭제 코딩(Erasure Coding, EC) 풀도 지원합니다. EC 풀은 데이터를 k개의 데이터 청크와 m개의 코딩 청크로 분할하여, 더 적은 저장 공간으로 동일한 내결함성을 달성합니다. CephFS에서 EC 풀은 데이터 풀로만 사용할 수 있습니다(메타데이터 풀은 반드시 replicated).
| 풀 타입 | 저장 오버헤드 | 내결함성 | 쓰기 성능 | 읽기 성능 |
|---|---|---|---|---|
| replicated (size=3) | 3x (300%) | 2 OSD 장애 허용 | 높음 (1 write) | 높음 (1 read) |
| EC k=4, m=2 | 1.5x (150%) | 2 OSD 장애 허용 | 중간 (인코딩 필요) | 중간 (디코딩 필요) |
| EC k=8, m=3 | 1.375x (137.5%) | 3 OSD 장애 허용 | 낮음 (많은 청크) | 중간 |
# EC 프로파일 생성
$ ceph osd erasure-code-profile set my_ec_profile \
k=4 m=2 crush-failure-domain=host
# EC 풀 생성
$ ceph osd pool create cephfs_data_ec 128 erasure my_ec_profile
# EC 풀을 CephFS 데이터 풀로 추가
$ ceph osd pool set cephfs_data_ec allow_ec_overwrites true # 필수!
$ ceph fs add_data_pool cephfs cephfs_data_ec
# EC 풀에 저장할 디렉터리 지정
$ setfattr -n ceph.dir.layout.pool -v cephfs_data_ec /mnt/cephfs/archive
# CRUSH 규칙 확인
$ ceph osd crush rule ls
replicated_rule
erasure-code
# EC 프로파일 확인
$ ceph osd erasure-code-profile get my_ec_profile
k=4
m=2
plugin=jerasure
technique=reed_sol_van
crush-failure-domain=host
allow_ec_overwrites=true 설정이 필수입니다(BlueStore 전용). EC 풀은 랜덤 쓰기 성능이 replicated보다 낮으므로, 대용량 순차 쓰기 워크로드(아카이브, 백업)에 적합합니다. 또한 EC 풀은 omap을 지원하지 않으므로 메타데이터 풀로 사용할 수 없습니다.
CRUSH 가중치와 데이터 분포 분석
CRUSH 가중치(weight)는 OSD가 받는 데이터 양을 직접 결정합니다. straw2 알고리즘에서 draw = ln(hash) + ln(weight) 공식을 사용하므로, 가중치가 2배인 OSD는 통계적으로 2배의 PG를 담당합니다. 실제 분포를 확인하고 조정하는 방법을 살펴봅니다.
# OSD별 PG 분포 확인
$ ceph osd df tree
ID CLASS WEIGHT REWEIGHT SIZE RAW USE DATA OMAP META AVAIL %USE VAR PGS STATUS
-1 48.00000 - 48 TiB 12 TiB 11 TiB 0 B 1.0 GiB 36 TiB 25.0 1.00 -
-3 8.00000 - 8 TiB 2 TiB 1.9 TiB 0 B 170 MiB 6 TiB 25.1 1.00 - host-a
0 ssd 4.00000 1.00000 4 TiB 1 TiB 0.9 TiB 0 B 85 MiB 3 TiB 24.8 0.99 128 up
1 ssd 4.00000 1.00000 4 TiB 1 TiB 1.0 TiB 0 B 85 MiB 3 TiB 25.3 1.01 128 up
# REWEIGHT으로 미세 조정 (0.0~1.0, 기본 1.0)
$ ceph osd reweight 2 0.8 # OSD.2의 PG 20% 감소
# CRUSH 가중치 변경 (디스크 용량 변경 시)
$ ceph osd crush reweight osd.5 8.0 # 8TB 디스크
# 자동 밸런서로 편차 최소화
$ ceph balancer on
$ ceph balancer mode upmap # upmap 모드 (최적)
$ ceph balancer eval # 현재 균형 점수 확인
current cluster score 0.014523 # 낮을수록 균형적
# 밸런서 계획 미리보기
$ ceph balancer optimize my_plan
$ ceph balancer eval my_plan
$ ceph balancer execute my_plan # 실제 적용
ceph osd reweight은 OSD의 CRUSH 가중치를 변경하지 않고, PG 배치에만 영향을 주는 일시적 조정입니다(0.0~1.0). ceph osd crush reweight은 실제 CRUSH 가중치를 변경합니다. 디스크 교체 등 영구적 변경은 crush reweight를, 핫스팟 임시 완화는 reweight를 사용하세요.
MDS Capability 상태 머신
MDS Capability(Cap)는 단순한 비트 플래그가 아니라, 발급(grant), 회수(revoke), 갱신(update), 플러시(flush) 등의 상태 전이를 거치는 유한 상태 머신입니다. 커널 클라이언트의 fs/ceph/caps.c는 이 상태 머신을 구현합니다.
Capability 회수(Revoke) 상세 과정
/* fs/ceph/caps.c — Cap 회수 메시지 처리 */
static void handle_cap_grant(
struct inode *inode,
struct ceph_mds_caps *grant,
struct ceph_mds_session *session)
{
struct ceph_inode_info *ci = ceph_inode(inode);
int issued = grant->caps; /* MDS가 허용하는 Cap */
int revoking;
spin_lock(&ci->i_ceph_lock);
revoking = ci->i_auth_cap->issued & ~issued;
if (revoking & CEPH_CAP_FILE_CACHE) {
/* 1. 페이지 캐시 무효화 */
invalidate_inode_pages2(inode->i_mapping);
}
if (revoking & CEPH_CAP_FILE_BUFFER) {
/* 2. dirty 페이지 전부 플러시 */
filemap_write_and_wait(inode->i_mapping);
}
if (revoking & CEPH_CAP_AUTH_EXCL) {
/* 3. 메타데이터 변경사항 MDS에 전송 */
__ceph_flush_snaps(ci, session);
}
/* 4. Cap 갱신 후 MDS에 ACK 전송 */
ci->i_auth_cap->issued = issued;
__send_cap(ci, session, CEPH_CAP_OP_UPDATE);
spin_unlock(&ci->i_ceph_lock);
}
Cap 타임아웃과 세션 관리
| 타임아웃 파라미터 | 기본값 | 설명 |
|---|---|---|
session_timeout | 60초 | MDS 세션 타임아웃, 초과 시 Cap 전체 회수 |
caps_wanted_delay_min | 5초 | 불필요한 Cap 자발적 반납 최소 대기 |
caps_wanted_delay_max | 60초 | 불필요한 Cap 자발적 반납 최대 대기 |
cap_revoke_eviction_timeout | 0 (비활성) | Cap 회수 요청 후 응답 없으면 클라이언트 퇴출 |
mds_session_blocklist_on_timeout | true | 세션 타임아웃 시 blocklist 등록 |
caps_max 마운트 옵션으로 클라이언트당 최대 Cap 수를 제한하세요. Cap이 한도에 도달하면, 커널 클라이언트는 가장 오래 사용하지 않은 Cap부터 자발적으로 반납합니다. mds_max_caps_per_client MDS 설정으로 서버 측에서도 강제할 수 있습니다(기본 1M).
디렉터리 프래그먼트와 서브트리 파티셔닝
CephFS의 다중 활성 MDS(multi-active MDS) 환경에서는 서브트리 파티셔닝(subtree partitioning)이 메타데이터 부하 분산의 핵심입니다. MDS는 디렉터리 트리를 서브트리 단위로 나누어 각 MDS 랭크에 할당합니다. 핫스팟 디렉터리는 자동으로 다른 MDS로 마이그레이션됩니다.
# MDS 서브트리 할당 확인
$ ceph fs status cephfs
cephfs - 2 clients
=================
RANK STATE MDS ACTIVITY DNS INOS DIRS CAPS
0 active mds.a Reqs: 0 /s 6723 5247 853 3412
1 active mds.b Reqs: 0 /s 4521 3012 412 2156
# 서브트리 분포 확인
$ ceph daemon mds.a get subtrees
[
{"dir":{"path":"/","snapid":"head"},"auth_first":0},
{"dir":{"path":"/home","snapid":"head"},"auth_first":0},
{"dir":{"path":"/logs","snapid":"head"},"auth_first":1}
]
# 디렉터리를 특정 MDS에 고정 (pin)
$ setfattr -n ceph.dir.pin -v 1 /mnt/cephfs/logs # MDS.1에 고정
$ setfattr -n ceph.dir.pin -v -1 /mnt/cephfs/logs # pin 해제
# 분산 pin: 서브디렉터리를 MDS 간 자동 분산
$ setfattr -n ceph.dir.pin.distributed -v 1 /mnt/cephfs/home
# → /home/user1, /home/user2 등이 자동으로 다른 MDS에 분배
# MDS 밸런서 동작 확인
$ ceph config get mds mds_bal_split_size # 분할 임계값 (기본 10000)
$ ceph config get mds mds_bal_interval # 밸런싱 주기 (기본 10초)
$ ceph config get mds mds_bal_mode # 밸런싱 모드 (0=off, 1=on)
ceph.dir.pin.distributed=1을 상위 디렉터리에 설정하면, 서브디렉터리가 자동으로 MDS 간에 분배됩니다. 파일 수가 매우 많은 단일 디렉터리는 프래그먼트가 분할되지만, 같은 MDS 내에서만 병렬 처리됩니다.
스냅샷 메커니즘 내부
CephFS 스냅샷은 RADOS 오브젝트의 SNAP 컨텍스트와 MDS의 스냅샷 Realm을 결합하여 구현됩니다. 스냅샷 생성 시 데이터 복사는 일어나지 않으며, 이후 해당 오브젝트에 쓰기가 발생하면 OSD가 자동으로 COW(Copy-on-Write)를 수행합니다.
스냅샷과 dirty 페이지 플러시
/* fs/ceph/snap.c — 스냅샷 생성 시 커널 클라이언트 동작 */
void ceph_handle_snap(
struct ceph_mds_client *mdsc,
struct ceph_mds_session *session,
struct ceph_msg *msg)
{
int op = le32_to_cpu(h->op);
switch (op) {
case CEPH_SNAP_OP_SPLIT:
/* 새로운 snap realm 생성 */
break;
case CEPH_SNAP_OP_UPDATE:
/* 스냅샷 추가/삭제 알림 */
/* 1. 새 snap context 생성 */
/* 2. realm에 속한 모든 inode의 dirty 페이지를
이전 snap context로 플러시 시작 */
queue_realm_cap_snaps(realm);
break;
case CEPH_SNAP_OP_DESTROY:
/* snap realm 삭제 */
break;
}
}
/* dirty 페이지를 이전 snap context로 플러시 */
static void queue_realm_cap_snaps(
struct ceph_snap_realm *realm)
{
struct ceph_inode_info *ci;
/* realm에 속한 모든 dirty inode를 순회 */
list_for_each_entry(ci, &realm->inodes_with_caps, i_snap_realm_item) {
if (__ceph_have_pending_cap_snap(ci)) {
/* cap_snap 생성: 이 시점의 dirty 페이지를
OSD에 flush할 때 이전 snap_context 사용 */
ceph_queue_cap_snap(ci);
}
}
}
osd snap trim sleep 설정으로 정리 속도를 조절할 수 있습니다. 또한 스냅샷 realm 트리가 깊으면 snap context 크기가 커져 모든 WRITE OSD 요청의 오버헤드가 증가합니다.
스냅샷 스케줄링
Ceph MGR의 snap_schedule 모듈을 사용하면 CephFS 스냅샷을 자동으로 생성하고 보존 정책을 적용할 수 있습니다.
# snap_schedule 모듈 활성화
$ ceph mgr module enable snap_schedule
# 스냅샷 스케줄 설정: 매 시간 자동 스냅샷
$ ceph fs snap-schedule add /data 1h
# 보존 정책: 최근 24개 (24시간) + 7개 일간 + 4개 주간
$ ceph fs snap-schedule retention add /data h 24
$ ceph fs snap-schedule retention add /data d 7
$ ceph fs snap-schedule retention add /data w 4
# 스케줄 확인
$ ceph fs snap-schedule list /data
/data: every 1h, retention: 24h 7d 4w
# 스냅샷 목록 확인
$ ceph fs snap-schedule status /data | head -5
{ "path": "/data",
"schedule": "1h",
"start": "2024-01-01T00:00:00",
"created_count": 156,
"pruned_count": 132 }
# 특정 경로의 스케줄 비활성화
$ ceph fs snap-schedule deactivate /data
# 스케줄 삭제
$ ceph fs snap-schedule remove /data 1h
/* 커널 클라이언트에서의 스냅샷 디렉터리(.snap) 처리 */
/* fs/ceph/dir.c — .snap 가상 디렉터리 */
static struct dentry *ceph_lookup(
struct inode *dir,
struct dentry *dentry,
unsigned int flags)
{
/* ".snap" 디렉터리 접근 감지 */
if (ceph_snap(dir) == CEPH_NOSNAP &&
strcmp(dentry->d_name.name, ".snap") == 0) {
/* snap realm 정보를 사용하여 가상 디렉터리 생성 */
return ceph_snapdir_lookup(dir, dentry, flags);
}
/* 스냅샷 내부에서의 lookup */
if (ceph_snap(dir) != CEPH_NOSNAP) {
/* snap_id를 포함한 MDS 요청 전송 */
/* MDS는 해당 시점의 inode 상태를 반환 */
}
}
rbd snap rollback이나 클론으로 접근합니다. 두 메커니즘 모두 RADOS 레벨의 OSD snap clone을 사용하므로 COW 방식으로 효율적입니다.
쿼터 시스템 내부 동작
CephFS 쿼터는 MDS가 관리하는 재귀 통계(recursive statistics)를 기반으로 동작합니다. 각 디렉터리 inode에는 하위 모든 파일의 총 바이트 수(rbytes), 파일 수(rfiles), 디렉터리 수(rsubdirs)가 기록됩니다. 이 통계는 MDS가 비동기적으로 갱신하므로, 쿼터 확인은 근사적(approximate)입니다.
/* fs/ceph/quota.c — 쿼터 확인 전체 흐름 */
/* 쓰기 시 쿼터 확인 */
bool ceph_quota_is_max_bytes_approaching(
struct inode *inode, loff_t newsize)
{
/* inode에서 루트까지 조상을 순회 */
while (!IS_ROOT(dentry)) {
struct ceph_inode_info *ci = ceph_inode(d_inode(dentry));
/* 이 디렉터리에 max_bytes 쿼터가 설정되어 있는지 */
if (ci->i_max_bytes) {
/* 현재 사용량(rbytes)과 새 파일 크기를 합산 */
u64 total = ci->i_rbytes + (newsize - inode->i_size);
/* 80% 이상이면 "근접" → MDS에 갱신 요청 */
if (total > ci->i_max_bytes * 8 / 10)
return true;
/* 100% 초과면 -EDQUOT */
if (total > ci->i_max_bytes)
return true; /* → -EDQUOT */
}
dentry = dentry->d_parent;
}
return false;
}
/* 파일 수 쿼터 확인 */
bool ceph_quota_is_max_files_exceeded(
struct inode *inode)
{
while (!IS_ROOT(dentry)) {
struct ceph_inode_info *ci = ceph_inode(d_inode(dentry));
if (ci->i_max_files &&
ci->i_rfiles + ci->i_rsubdirs >= ci->i_max_files)
return true;
dentry = dentry->d_parent;
}
return false;
}
| 쿼터 관련 xattr | 용도 | 커널 코드 |
|---|---|---|
ceph.quota.max_bytes | 디렉터리 바이트 제한 | ci->i_max_bytes |
ceph.quota.max_files | 디렉터리 파일/디렉터리 수 제한 | ci->i_max_files |
ceph.dir.rbytes | 재귀 바이트 수 (읽기 전용) | ci->i_rbytes |
ceph.dir.rfiles | 재귀 파일 수 (읽기 전용) | ci->i_rfiles |
ceph.dir.rsubdirs | 재귀 디렉터리 수 (읽기 전용) | ci->i_rsubdirs |
ceph.dir.rctime | 재귀 ctime (읽기 전용) | ci->i_rctime |
rbytes 캐시를 기반으로 쿼터를 확인합니다. MDS의 재귀 통계 갱신이 지연되므로, 실제 사용량이 쿼터를 소량(수 MB~수십 MB) 초과할 수 있습니다. 엄격한 용량 제한이 필요한 경우에는 CephFS 쿼터 대신 외부 모니터링 도구를 사용하거나, 단일 클라이언트만 쓰도록 운영하세요.
파일 스트라이핑 심화
CephFS의 스트라이핑은 단순한 오브젝트 분할을 넘어, 오브젝트 집합(object set) 단위의 순환 배치와 다중 풀 지원을 제공합니다. 스트라이핑 파라미터를 올바르게 설정하면 대용량 파일의 순차 I/O 성능을 크게 향상시킬 수 있습니다.
스트라이프 오프셋 계산 예시
/* 파일 오프셋 → (오브젝트 번호, 오브젝트 내 오프셋) 변환 */
/* 예: stripe_unit=4MB, stripe_count=2, object_size=8MB */
/* stripes_per_obj = object_size / stripe_unit = 8M / 4M = 2 */
/* 파일 오프셋 10MB에서의 오브젝트 계산: */
stripe_no = 10MB / 4MB = 2 /* 3번째 스트라이프 (0-indexed) */
obj_set = 2 / (2 * 2) = 0 /* 첫 번째 오브젝트 집합 */
stripe_in_set = 2 % (2 * 2) = 2 /* 집합 내 인덱스 2 */
obj_no = 0 * 2 + 2 % 2 = 0 /* → obj.0 */
obj_offset = (2 / 2) * 4MB + (10MB % 4MB) = 4MB + 2MB = 6MB
/* 결과: 파일의 10MB 위치 → obj.0의 6MB 위치 */
/* stripe_count=1 (기본)이면 계산 단순화: */
obj_no = file_offset / object_size
obj_offset = file_offset % object_size
# 스트라이핑 레이아웃 시나리오별 권장 설정
# 1. 대용량 순차 I/O (영상, 백업)
$ setfattr -n ceph.file.layout.stripe_unit -v 4194304 /mnt/cephfs/video
$ setfattr -n ceph.file.layout.stripe_count -v 8 /mnt/cephfs/video
$ setfattr -n ceph.file.layout.object_size -v 33554432 /mnt/cephfs/video
# → 8개 OSD에 병렬 I/O, 32MB 오브젝트
# 2. 소규모 랜덤 I/O (데이터베이스)
$ setfattr -n ceph.file.layout.stripe_unit -v 65536 /mnt/cephfs/db
$ setfattr -n ceph.file.layout.stripe_count -v 1 /mnt/cephfs/db
$ setfattr -n ceph.file.layout.object_size -v 4194304 /mnt/cephfs/db
# → 단일 OSD, 작은 stripe_unit으로 정렬
# 3. 다중 풀 레이아웃 (SSD + HDD 혼합)
$ setfattr -n ceph.dir.layout.pool -v cephfs_ssd /mnt/cephfs/hot_data
$ setfattr -n ceph.dir.layout.pool -v cephfs_hdd /mnt/cephfs/archive
stripe_unit은 반드시 object_size의 약수여야 하며, 4096바이트 이상이어야 합니다. object_size는 stripe_unit의 정수배여야 합니다. 레이아웃 속성은 빈 파일에만 설정할 수 있으며, 데이터가 기록된 파일의 레이아웃은 변경할 수 없습니다. 디렉터리 레이아웃은 이후 생성되는 파일에만 적용됩니다.
libceph Messenger v2 심화
Messenger v2 프로토콜의 프레임 인코딩, 암호화 모드, 연결 재설정 메커니즘을 상세히 분석합니다. 커널 클라이언트의 net/ceph/messenger_v2.c는 약 3,000줄 이상의 복잡한 상태 머신으로, 비동기 TCP 소켓 위에서 구조화된 메시지 교환을 수행합니다.
v2 프레임 상세 구조
/* net/ceph/messenger_v2.c — Preamble 구조 (32바이트) */
struct ceph_frame_preamble {
__u8 tag; /* 프레임 타입 (FRAME_TAG_*) */
__u8 seg_cnt; /* 세그먼트 수 (1~4) */
__le32 seg_lengths[4]; /* 각 세그먼트 길이 */
__le32 crc; /* preamble CRC32 */
__u8 padding[2]; /* 정렬 패딩 */
} __attribute__((packed));
/* Epilogue: CRC 모드 */
struct ceph_frame_epilogue_crc {
__u8 late_status; /* 0=정상, 1=중단 */
__le32 crc_values[4]; /* 각 세그먼트 CRC32 */
} __attribute__((packed));
/* Epilogue: Secure 모드 */
struct ceph_frame_epilogue_secure {
__u8 late_status;
__u8 padding[15]; /* GCM 태그로 채워짐 */
__u8 gcm_tag[16]; /* AES-GCM 인증 태그 */
} __attribute__((packed));
/* 커널 v2 연결 옵션 */
struct ceph_connection_v2_info {
bool is_v2;
int con_mode; /* CRC(0) 또는 Secure(1) */
struct ceph_gcm_context gcm; /* AES-GCM 컨텍스트 */
u64 peer_global_seq; /* 피어의 전역 시퀀스 */
u64 connect_seq; /* 연결 시퀀스 */
};
| ms_mode 옵션 | 동작 | 보안 수준 | 오버헤드 |
|---|---|---|---|
legacy | v1 프로토콜만 사용 | 없음 | 최소 |
crc | v2 CRC 모드 | 무결성 검증 | 낮음 |
secure | v2 AES-GCM 암호화 | 기밀성 + 무결성 | 중간 (~5%) |
prefer-crc | v2 CRC 우선, fallback v1 | 가변 | 낮음 |
prefer-secure | v2 Secure 우선, fallback CRC | 가변 | 가변 |
KEEPALIVE2 프레임을 주기적으로 교환하여 TCP 연결 건전성을 확인합니다. 커널 클라이언트는 osd_keepalive_timeout(기본 5초) 간격으로 keepalive를 전송하고, 응답이 없으면 연결을 끊고 재연결합니다. 이 메커니즘은 TCP keepalive보다 빠르게 장애를 감지합니다.
RBD 커널 드라이버 심화
RBD(rbd.ko)는 RADOS 오브젝트를 블록 디바이스로 매핑하는 커널 드라이버입니다. drivers/block/rbd.c에 구현되며, blk-mq를 사용하여 고성능 I/O를 제공합니다. RBD의 핵심 기능인 이미지 레이어링(COW 클론), 라이브 마이그레이션, 오브젝트 맵을 상세히 살펴봅니다.
/* drivers/block/rbd.c — COW(copyup) 처리 */
static int rbd_img_obj_request_submit(
struct rbd_obj_request *obj_req)
{
struct rbd_device *rbd_dev = obj_req->img_request->rbd_dev;
switch (obj_req->img_request->op_type) {
case OBJ_OP_READ:
/* 읽기: 클론에서 시도 → ENOENT → 부모에서 읽기 */
return rbd_obj_read_submit(obj_req);
case OBJ_OP_WRITE:
if (rbd_obj_is_entire(obj_req)) {
/* 전체 오브젝트 쓰기 → copyup 불필요 */
return rbd_obj_write_submit(obj_req);
}
/* 부분 쓰기 → copyup 필요 */
return rbd_obj_copyup_submit(obj_req);
case OBJ_OP_DISCARD:
return rbd_obj_discard_submit(obj_req);
}
}
/* copyup: 부모 → 클론 복사 후 쓰기 */
static int rbd_obj_copyup_submit(
struct rbd_obj_request *obj_req)
{
/* 1. 부모 이미지에서 전체 오브젝트 읽기 */
rbd_obj_read_from_parent(obj_req);
/* 2. 읽은 데이터 + 새 쓰기를 합쳐서 OSD에 전송 */
/* OSD가 CEPH_OSD_OP_COPY_FROM + WRITE를 원자적 실행 */
/* 3. object-map 갱신 (있으면) */
}
RBD Object Map
Object Map은 RBD 이미지의 각 오브젝트 상태를 비트맵으로 추적하는 기능입니다. 어떤 오브젝트가 실제로 존재하는지(allocated), 존재하지 않는지(nonexistent), 보류 중인지(pending)를 빠르게 조회할 수 있어, export/import, diff, flatten 등의 작업이 크게 빨라집니다.
| 오브젝트 상태 | 비트 값 | 의미 |
|---|---|---|
OBJECT_NONEXISTENT | 0 | 오브젝트 미존재 (읽기 시 부모 또는 제로) |
OBJECT_EXISTS | 1 | 오브젝트 존재 (데이터 있음) |
OBJECT_PENDING | 2 | 쓰기 진행 중 (exclusive-lock 필요) |
OBJECT_EXISTS_CLEAN | 3 | 존재하며 부모와 동일 (diff에서 제외) |
# RBD 기능 활성화
$ rbd create --size 100G --image-feature layering,exclusive-lock,object-map \
mypool/myimage
# 기존 이미지에 object-map 활성화
$ rbd feature enable mypool/myimage object-map
$ rbd object-map rebuild mypool/myimage
# 클론 생성 (레이어링)
$ rbd snap create mypool/base@snap1
$ rbd snap protect mypool/base@snap1
$ rbd clone mypool/base@snap1 mypool/vm-disk-1
# 클론 체인 확인
$ rbd info mypool/vm-disk-1
rbd image 'vm-disk-1':
parent: mypool/base@snap1
overlap: 107374182400
# flatten (부모 의존성 제거, 모든 데이터 독립 복사)
$ rbd flatten mypool/vm-disk-1
# diff: object-map이 있으면 매우 빠름
$ rbd diff mypool/myimage --from-snap snap1 | head
offset,length
0,4194304
16777216,4194304
ceph-csi)를 통해 PersistentVolume으로 자동 프로비저닝됩니다. 템플릿 이미지의 스냅샷에서 클론을 생성하면, 수백 개의 Pod에 고유한 볼륨을 즉시 제공할 수 있습니다. 이때 exclusive-lock 기능이 활성화되어야 단일 마운트가 보장되며, object-map은 스냅샷/클론 성능을 크게 향상시킵니다.
RBD 미러링
RBD 미러링은 두 Ceph 클러스터 간에 RBD 이미지를 비동기적으로 복제하는 재해 복구(DR) 기능입니다. journaling 기능이 활성화된 이미지에서 저널 이벤트를 원격 클러스터에 재생하여 동기화합니다.
# RBD 미러링 설정 (사이트 A → 사이트 B)
# 1. 양쪽 클러스터에서 미러링 모드 설정
$ rbd mirror pool enable mypool pool # 풀 전체 미러링
# 또는
$ rbd mirror pool enable mypool image # 이미지 단위 미러링
# 2. 이미지에 journaling 기능 활성화
$ rbd feature enable mypool/myimage journaling
# 3. 피어 클러스터 등록
$ rbd mirror pool peer add mypool client.admin@cluster-b
# 4. rbd-mirror 데몬 시작 (사이트 B)
$ systemctl start ceph-rbd-mirror@admin
# 미러링 상태 확인
$ rbd mirror pool status mypool
health: OK
daemon health: OK
image health: OK
images: 3 total
3 replaying
# 이미지별 미러링 상태
$ rbd mirror image status mypool/myimage
myimage:
global_id: ...
state: up+replaying
description: replaying, {"bytes_per_second": 1048576, ...}
last_update: 2024-01-15 12:00:00
# 페일오버 (사이트 B에서 primary로 승격)
$ rbd mirror image promote mypool/myimage --force
RBD-NBD (Network Block Device)
rbd.ko 대신 rbd-nbd 유틸리티를 사용하면 사용자 공간에서 NBD(Network Block Device) 프로토콜을 통해 RBD 이미지를 매핑할 수 있습니다. 커널 rbd.ko가 지원하지 않는 최신 기능(예: 암호화, 복잡한 미러링)을 활용할 수 있습니다.
# rbd-nbd로 이미지 매핑
$ rbd-nbd map mypool/myimage
/dev/nbd0
# 매핑 확인
$ rbd-nbd list-mapped
id pool image snap device
0 mypool myimage - /dev/nbd0
# rbd-nbd vs rbd.ko 비교
# rbd.ko: 커널 공간, 높은 성능, 기능 제한적
# rbd-nbd: 사용자 공간(librbd), 유연한 기능, NBD 오버헤드
# 매핑 해제
$ rbd-nbd unmap /dev/nbd0
rbd encryption format 명령으로 설정합니다. 커널 rbd.ko는 암호화를 직접 지원하지 않으므로, dm-crypt/LUKS를 rbd 디바이스 위에 올리거나 rbd-nbd + librbd 암호화를 사용해야 합니다.
성능 튜닝 심화
CephFS 성능을 최대화하려면 클라이언트 옵션, MDS 구성, OSD 튜닝, 네트워크 설정을 종합적으로 고려해야 합니다. 다음은 워크로드별 최적화 전략과 구체적인 튜닝 파라미터입니다.
워크로드별 튜닝 가이드
| 워크로드 | 클라이언트 옵션 | MDS 튜닝 | OSD 튜닝 |
|---|---|---|---|
| 대용량 순차 I/O (영상, 백업) | rsize=128M, wsize=128M, rasize=32M, stripe_count=8 | 기본 (메타데이터 적음) | object_size 16~32MB, 복제 2 |
| 소규모 랜덤 I/O (데이터베이스, VM) | rsize=4M, wsize=4M, stripe_count=1 | mds_cache_memory_limit=8G | NVMe WAL, 복제 3 |
| 메타데이터 집중 (코드 저장소, 빌드) | caps_max=131072, readdir_max_entries=4096 | max_mds=2+, mds_cache_memory_limit=16G | 메타데이터 풀 SSD |
| 다수 클라이언트 (HPC, 공유 홈) | caps_wanted_delay_min=1, caps_wanted_delay_max=10 | max_mds=4+, bal_mode=2 | PG autoscaler 활성화 |
# 종합 성능 벤치마크
# 1. 순차 쓰기 성능 측정
$ dd if=/dev/zero of=/mnt/cephfs/testfile bs=1M count=4096 conv=fdatasync
# 4.3 GB/s (10x25GbE bonding, NVMe OSD 12개)
# 2. 순차 읽기 성능 측정
$ dd if=/mnt/cephfs/testfile of=/dev/null bs=1M count=4096
# 5.1 GB/s (클라이언트 메모리 캐시 미사용 시)
# 3. 랜덤 I/O (fio)
$ fio --name=randwrite --ioengine=libaio --direct=1 \
--bs=4k --iodepth=64 --numjobs=4 \
--size=1G --runtime=60 \
--filename=/mnt/cephfs/fio_test --rw=randwrite
# IOPS: 50k~150k (OSD 수와 NVMe 여부에 따라)
# 4. 메타데이터 성능 (mdtest)
$ mdtest -C -T -F -d /mnt/cephfs/mdtest -n 100000 -i 3
# File creation: 10k~50k ops/s per MDS
# 5. OSD 레이턴시 모니터링
$ ceph osd perf
osd commit_latency(ms) apply_latency(ms)
0 0.500 0.600
1 0.450 0.550
2 1.200 1.500 # ← 이상 감지
# 6. MDS 성능 카운터
$ ceph daemon mds.a perf dump | jq '.mds'
{
"request": 123456,
"reply": 123450,
"reply_latency": { "avgcount": 123450, "sum": 15.234 },
"caps": 50000,
"subtrees": 5,
"inodes": 250000,
"inodes_with_caps": 48000,
"inodes_pinned": 12000
}
# 7. TCP 튜닝 (대역폭 최적화)
$ sysctl -w net.core.rmem_max=67108864
$ sysctl -w net.core.wmem_max=67108864
$ sysctl -w net.ipv4.tcp_rmem="4096 87380 33554432"
$ sysctl -w net.ipv4.tcp_wmem="4096 65536 33554432"
$ sysctl -w net.ipv4.tcp_mtu_probing=1
$ sysctl -w net.core.netdev_max_backlog=5000
성능 튜닝 체크리스트
| 항목 | 확인 방법 | 권장 조치 |
|---|---|---|
| OSD 레이턴시 | ceph osd perf | commit_latency > 10ms이면 디스크 교체 또는 WAL 분리 |
| PG 균형 | ceph osd df | 용량 편차 > 20%이면 CRUSH 가중치 조정 |
| MDS 캐시 | ceph daemon mds perf dump | 캐시 히트율 < 90%이면 mds_cache_memory_limit 증가 |
| Cap 수 | debugfs caps | caps_max 근접 시 한도 증가 또는 불필요 파일 닫기 |
| 네트워크 | ethtool -S, sar -n DEV | 패킷 드롭 발생 시 링 버퍼 증가, MTU 9000 |
| slow requests | ceph health detail | 원인 파악: OSD 느림, PG peering, 네트워크 |
| MDS blocked ops | ceph daemon mds dump_blocked_ops | Cap revoke 대기: 해당 클라이언트 확인 |
| fscache | /proc/fs/fscache/stats | 반복 읽기 많으면 fsc 옵션 활성화 |
- MON 3/5/7대 — 짝수 MON은 split-brain 위험
- 전용 네트워크 — 클러스터(OSD 간 복제)와 퍼블릭(클라이언트) 네트워크 분리
- 메타데이터 풀 SSD — MDS 성능에 직접 영향, HDD 사용 금지
- journal/WAL NVMe — OSD 쓰기 레이턴시에 직접 영향
- 커널 버전 — CephFS 커널 클라이언트는 가능한 최신 LTS 커널 사용 권장 (5.15+ 또는 6.x)
- noatime — 마운트 시 반드시 noatime 옵션 사용 (불필요한 메타데이터 갱신 방지)
커널 버전별 CephFS 기능 지원
| 커널 버전 | 추가된 CephFS 기능 | Ceph 서버 호환 |
|---|---|---|
| 2.6.34 | CephFS 최초 도입 (실험적) | 0.24+ |
| 3.10 | RBD layering (COW 클론) | 0.58+ |
| 4.5 | CRUSH tunables v5 (jewel) | 10.2+ |
| 4.9 | RBD exclusive-lock, object-map, fast-diff | 10.2+ |
| 4.17 | CephFS 스냅샷 안정화, 쿼터 지원 | 12.2+ |
| 5.0 | CRUSH optimal tunables | 14.2+ |
| 5.4 | recover_session=clean, 다중 FS 지원 | 14.2+ |
| 5.7 | 새 마운트 구문 (mon_addr=), mds_namespace | 15.2+ |
| 5.11 | Messenger v2 (암호화, 구조화된 프레이밍) | 15.2+ |
| 5.15 | POSIX ACL 개선, 성능 최적화 | 16.2+ |
| 5.19 | fscrypt 지원 (파일 암호화) | 17.2+ |
| 6.1 | netfs 라이브러리 통합, readahead 개선 | 17.2+ |
| 6.5 | idmapped mounts 지원 | 18.2+ |
Prometheus 메트릭을 활용한 모니터링
# Prometheus MGR 모듈 활성화
$ ceph mgr module enable prometheus
$ ceph config set mgr mgr/prometheus/port 9283
# 주요 CephFS 관련 Prometheus 메트릭
# OSD 레이턴시
# ceph_osd_op_r_latency_sum / ceph_osd_op_r_latency_count (읽기)
# ceph_osd_op_w_latency_sum / ceph_osd_op_w_latency_count (쓰기)
# MDS 메트릭
# ceph_mds_request_count (요청 수)
# ceph_mds_reply_latency_sum (응답 레이턴시)
# ceph_mds_caps (캡 수)
# ceph_mds_inodes (inode 캐시 크기)
# ceph_mds_mem_rss (MDS 메모리 사용량)
# 클러스터 용량
# ceph_pool_stored_raw (풀별 저장 바이트)
# ceph_pool_max_avail (풀별 가용 공간)
# PG 상태
# ceph_pg_active (active PG 수)
# ceph_pg_degraded (degraded PG 수)
# ceph_pg_undersized (undersized PG 수)
# Grafana 대시보드 예시 PromQL
# 평균 OSD 쓰기 레이턴시 (밀리초):
# rate(ceph_osd_op_w_latency_sum[5m]) / rate(ceph_osd_op_w_latency_count[5m]) * 1000
# MDS 초당 요청 수:
# rate(ceph_mds_request_count[5m])
# 클라이언트별 Cap 사용량 확인 (MDS 관리 소켓)
$ ceph daemon mds.a session ls | jq '.[].num_caps' | sort -n | tail -10
- OSD commit latency > 50ms — 디스크 문제 의심, 해당 OSD 점검
- MDS reply latency > 100ms — MDS 캐시 부족 또는 OSD 메타 풀 느림
- PG degraded > 0 (10분 이상) — 복제 실패, OSD 상태 확인
- 클러스터 용량 > 80% — 용량 추가 또는 데이터 정리 필요
- MDS caps > 500k per client — 클라이언트 cap 누수 의심
관련 문서
CephFS와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.