Kubernetes 정복기: Ceph 분산 스토리지로 진정한 동적 프로비저닝 구현 (Day 7)

문한성·6일 전
0

K8S 정복하기

목록 보기
7/9

2025년 11월 7일
단일 노드 한계를 넘어! RBD/CephFS/RGW 세 가지 스토리지를 모두 구축하다

들어가며

Day 6에서 Prometheus + Grafana로 모니터링, Vector + OpenSearch로 로그 중앙화를 마스터했습니다. 이제 Day 7에서는 Kubernetes의 꽃이라 할 수 있는 분산 스토리지 Ceph를 직접 구축하고 세 가지 스토리지 타입을 모두 테스트했습니다.

오늘 배운 것:
1. Ceph 아키텍처 완벽 이해 (RADOS, CRUSH, PG 개념)
2. Rook Operator로 단일 노드 Ceph 클러스터 구축
3. RBD 블록 스토리지 (ReadWriteOnce) - 동적 프로비저닝
4. CephFS 파일 시스템 (ReadWriteMany) - 다중 Pod 동시 접근
5. RGW 오브젝트 스토리지 (S3 API) - 사용자 및 버킷 생성
6. Ceph Dashboard 웹 UI로 클러스터 모니터링
7. 단일 노드 환경의 한계와 해결 방법


1. Ceph가 뭐길래?

왜 Ceph인가?

🤔 내가 이해한 것:

기존에는 hostPathemptyDir로 Pod에 볼륨을 붙였습니다. 하지만 이건 문제가 있었어요:

문제점:
❌ hostPath: 특정 노드에 종속 (Pod가 다른 노드로 옮겨가면 데이터 유실)
❌ emptyDir: Pod 삭제 시 데이터 사라짐
❌ NFS: 단일 장애점 (NFS 서버 다운 = 전체 다운)

Ceph의 해결책:
✅ 분산 스토리지: 데이터를 여러 노드에 자동 복제
✅ 자가 복구: 노드 장애 시 자동으로 다른 노드에 데이터 재배치
✅ 무제한 확장: 노드 추가 = 스토리지 용량 증가
✅ 3가지 인터페이스: Block (RBD), File (CephFS), Object (S3)

HostPath vs Ceph 비교:

HostPath 방식:
Pod A (cpu1 노드)
  ↓
/data/app (cpu1의 로컬 디스크)

→ Pod가 cpu2로 옮겨가면? 데이터 접근 불가!

Ceph 방식:
Pod A (어느 노드든)
  ↓
Ceph 클러스터 (cpu1 + cpu2 + gpu1)
  ├─ 복제본 1: cpu1
  ├─ 복제본 2: cpu2
  └─ 복제본 3: gpu1

→ Pod가 어디로 옮겨가든 데이터 접근 가능!
→ 노드 1개 다운되어도 괜찮음!

Ceph 핵심 개념 정리

RADOS (Reliable Autonomic Distributed Object Store):

  • Ceph의 심장부
  • 모든 데이터를 Object로 저장
  • 자가 관리, 자가 복구

CRUSH (Controlled Replication Under Scalable Hashing):

마법의 알고리즘!
- 중앙 조회 테이블 없음
- 클라이언트가 직접 계산해서 데이터 위치 찾음
- 노드 추가/제거 시 최소한의 데이터 이동

작동 원리:
Object Name + Cluster Map → Hash 계산
→ Placement Group (PG) 결정
→ CRUSH Rules 적용
→ 최종 OSD 위치 계산

Placement Groups (PG):

왜 필요한가?

100만 객체를 12 OSD에 직접 매핑하면?
→ 관리 복잡도: O(n×m) = 1,200만 관계

100만 객체 → 512 PG → 12 OSD로 매핑하면?
→ 관리 복잡도: O(n+m) = 100만 + 512 + 12

PG는 객체와 OSD 사이의 중간 레이어!

2. Ceph 클러스터 구축 실습

실습 환경 결정

원래 계획: 3노드 HA 구성

cpu1 + cpu2 + gpu1 = 3노드 Ceph 클러스터
각 노드에 sdb 디스크 할당
복제본 3개 (size: 3)

변경된 계획: 단일 노드 학습용

이유:

  • cpu2는 이미 다른 워크로드 사용 중
  • gpu1에 여유 디스크 sdb (465GB) 발견
  • 학습 목적이므로 단일 노드로 충분
# gpu1 노드 디스크 확인
$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0 465.8G  0 disk
├─sda1   8:1    0 465.7G  0 part /
└─sda2   8:2    0     1M  0 part
sdb      8:16   0 465.8G  0 disk  ← 이걸 Ceph에 사용!

🎯 단일 노드 Ceph 설정의 핵심:

failureDomain: osd  # host가 아닌 osd 단위
replicated:
  size: 1            # 복제본 1개
  requireSafeReplicaSize: false  # 안전성 검사 비활성화

실습 1: Rook Operator 설치

Rook이 뭐지?

  • Kubernetes Operator 패턴으로 Ceph 자동 관리
  • CRD (Custom Resource Definition) 제공
  • 선언적 설정 (YAML로 Ceph 클러스터 생성)
# 1. CRD 설치
$ kubectl apply -f https://raw.githubusercontent.com/rook/rook/release-1.14/deploy/examples/crds.yaml
customresourcedefinition.apiextensions.k8s.io/cephblockpools.ceph.rook.io created
customresourcedefinition.apiextensions.k8s.io/cephfilesystems.ceph.rook.io created
customresourcedefinition.apiextensions.k8s.io/cephobjectstores.ceph.rook.io created
...

# 2. Common 리소스 (RBAC, ServiceAccount 등)
$ kubectl apply -f https://raw.githubusercontent.com/rook/rook/release-1.14/deploy/examples/common.yaml
namespace/rook-ceph created
serviceaccount/rook-ceph-system created
...

# 3. Operator 배포
$ kubectl apply -f https://raw.githubusercontent.com/rook/rook/release-1.14/deploy/examples/operator.yaml
deployment.apps/rook-ceph-operator created

# 4. Operator Pod 확인
$ kubectl get pods -n rook-ceph
NAME                                 READY   STATUS    RESTARTS   AGE
rook-ceph-operator-5b7c8d8d4-xxxxx   1/1     Running   0          2m

🎉 성공! Rook Operator가 실행되고 있습니다!


2.5 Rook CRD와 Ceph 컴포넌트 완전 분석

Rook Operator를 설치했으니, 이제 어떤 CRD가 제공되고 각 Pod가 무슨 역할을 하는지 정확히 이해해야 합니다!

Rook이 제공하는 주요 CRD (Custom Resource Definition)

$ kubectl get crd | grep ceph.rook.io

핵심 CRD 목록:

CRD 이름용도예시
CephClusterCeph 클러스터 전체 정의MON, MGR, OSD 개수, 버전 설정
CephBlockPoolRBD용 Pool 생성replicapool (size=1, failureDomain=osd)
CephFilesystemCephFS 파일시스템myfs (MDS 서버 포함)
CephObjectStoreRGW Object Storagemy-store (S3 API 엔드포인트)
CephObjectStoreUserS3 사용자 생성my-s3-user (AccessKey/SecretKey)
CephClient외부 클라이언트 인증다른 클러스터에서 접근
CephRBDMirrorRBD 미러링 (재해복구)다른 클러스터로 복제
CephNFSNFS GatewayNFS 프로토콜로 접근

🤔 왜 CRD가 필요한가?

전통적인 Ceph 구축:

# 수동 명령어 실행 (어렵고 복잡)
ceph-deploy mon create node1 node2 node3
ceph-deploy osd prepare node1:/dev/sdb
ceph osd pool create mypool 128
ceph fs new myfs myfs-metadata myfs-data

Rook + CRD 방식:

# 선언적 YAML (쉽고 재현 가능!)
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
spec:
  mon:
    count: 3
  storage:
    nodes:
    - name: "gpu1"
      devices:
      - name: "sdb"

kubectl apply 한 번으로 끝!
→ Operator가 알아서 MON, MGR, OSD 배포
→ Git으로 버전 관리 가능 (GitOps)

Ceph 클러스터 배포 후 실제 Pod 분석

$ kubectl get pods -n rook-ceph
NAME                                             READY   STATUS
rook-ceph-operator-xxxxx                          1/1     Running   # Operator
rook-ceph-mon-a-xxxxx                             1/1     Running   # Monitor
rook-ceph-mgr-a-xxxxx                             1/1     Running   # Manager
rook-ceph-osd-0-xxxxx                             1/1     Running   # OSD
rook-ceph-mds-myfs-a-xxxxx                        1/1     Running   # MDS (CephFS용)
rook-ceph-mds-myfs-b-xxxxx                        1/1     Running   # MDS Standby
rook-ceph-rgw-my-store-a-xxxxx                    1/1     Running   # RGW (S3용)
rook-ceph-crashcollector-gpu1-xxxxx               1/1     Running   # Crash 수집
rook-ceph-exporter-gpu1-xxxxx                     1/1     Running   # Prometheus 메트릭
csi-rbdplugin-xxxxx                               2/2     Running   # RBD CSI Driver
csi-rbdplugin-provisioner-xxxxx                   5/5     Running   # RBD Provisioner
csi-cephfsplugin-xxxxx                            2/2     Running   # CephFS CSI Driver
csi-cephfsplugin-provisioner-xxxxx                5/5     Running   # CephFS Provisioner
rook-ceph-tools-xxxxx                             1/1     Running   # CLI 도구

각 Pod의 역할 상세 설명

1️⃣ rook-ceph-operator (Operator 패턴의 두뇌)

역할:
- CephCluster CRD를 감시
- YAML 변경 시 실제 Ceph 리소스 생성/업데이트
- Pod 장애 시 자동 복구
- Kubernetes API와 Ceph 명령어를 연결하는 브릿지

예시:
CephBlockPool YAML 적용 → Operator가 감지
→ ceph osd pool create 명령 실행
→ Pool 생성 완료

2️⃣ rook-ceph-mon-a (Monitor - 클러스터 상태 추적)

역할:
- Cluster Map 유지 (MON, OSD, PG 상태)
- Quorum 형성 (과반수 합의)
- 클라이언트에게 Cluster Map 제공

왜 홀수 개?
- 2개: 1개 다운 시 Quorum 불가 (50% < 과반수)
- 3개: 1개 다운 OK (2/3 = 66% > 과반수)
- 1개: 단일 노드 학습용 (HA 불가)

통신 포트:
- 6789: Ceph v1 protocol
- 3300: Ceph v2 protocol

3️⃣ rook-ceph-mgr-a (Manager - 관리와 모니터링)

역할:
- Ceph Dashboard 웹 UI 제공
- Prometheus 메트릭 노출
- PG Autoscaler 실행
- REST API 제공

모듈:
- dashboard: 웹 UI (port 7000)
- prometheus: 메트릭 수집 (port 9283)
- pg_autoscaler: PG 수 자동 조정
- balancer: OSD 간 데이터 균형

Active-Standby:
- mgr-a: Active (요청 처리)
- mgr-b: Standby (장애 대기)

4️⃣ rook-ceph-osd-0 (Object Storage Daemon - 실제 데이터 저장)

역할:
- 실제 데이터 저장 (sdb 디스크 사용)
- 데이터 복제 (다른 OSD로)
- Heartbeat (다른 OSD와 상태 확인)
- Backfilling (데이터 재배치)

OSD 번호:
- OSD.0: gpu1의 sdb 디스크
- (추가 노드 시 OSD.1, OSD.2, ...)

데이터 경로:
/var/lib/rook/osd0/
  ├─ block → /dev/sdb (심볼릭 링크)
  ├─ block.db (메타데이터, SSD 권장)
  └─ block.wal (WAL, SSD 권장)

포트:
- 6800-7300: OSD 간 통신

5️⃣ rook-ceph-mds-myfs-a/b (Metadata Server - CephFS 전용)

역할:
- CephFS 파일 메타데이터 관리
- 디렉토리 구조, 권한, 소유자 정보
- inode 캐싱 (성능 향상)

왜 2개?
- mds-a: Active (실제 요청 처리)
- mds-b: Standby (장애 대기)
- Active 장애 시 Standby가 즉시 승격 (30초 이내)

메타데이터 vs 데이터:
메타데이터 (MDS가 관리):
  - 파일명: /data/myfile.txt
  - 크기: 1GB
  - 권한: 0644

실제 데이터 (OSD에 저장):
  - Object 1: myfile.txt.00000000 (4MB)
  - Object 2: myfile.txt.00000001 (4MB)
  - ...

6️⃣ rook-ceph-rgw-my-store-a (RADOS Gateway - S3 API)

역할:
- S3 호환 REST API 제공
- 버킷 및 오브젝트 관리
- 사용자 인증 (AccessKey/SecretKey)
- Multi-part Upload 지원

엔드포인트:
- http://rook-ceph-rgw-my-store.rook-ceph:80
- S3 API: PUT /bucket/object

데이터 흐름:
1. AWS CLI: aws s3 cp file.txt s3://mybucket/
2. RGW: S3 요청 → Ceph Object 변환
3. RADOS: Object를 PG로 매핑 → OSD 저장

7️⃣ csi-rbdplugin / csi-cephfsplugin (CSI Driver)

역할:
- Kubernetes CSI 인터페이스 구현
- PVC 생성 시 실제 볼륨 Attach/Mount
- 각 노드마다 DaemonSet으로 배포

RBD CSI Driver:
- RBD 이미지 생성: rbd create
- 노드에 Map: rbd map /dev/rbd0
- 파일시스템 생성: mkfs.ext4
- Pod에 Mount: mount /dev/rbd0 /var/lib/kubelet/pods/...

CephFS CSI Driver:
- CephFS 서브볼륨 생성
- Ceph Client 마운트
- FUSE 또는 Kernel Driver 사용

8️⃣ csi-rbdplugin-provisioner / csi-cephfsplugin-provisioner

역할:
- PVC 생성 요청 감시
- 동적 프로비저닝 실행
- PV 생성 및 PVC 바인딩

워크플로우:
1. User: kubectl apply -f pvc.yaml
2. Provisioner: PVC 감지 (StorageClass 확인)
3. Provisioner: Ceph에 볼륨 생성 (RBD 또는 CephFS)
4. Provisioner: PV 오브젝트 생성
5. Kubernetes: PVC ↔ PV 바인딩 (Bound 상태)

왜 5개 컨테이너?
- csi-provisioner: 메인 프로비저너
- csi-resizer: 볼륨 크기 조정
- csi-snapshotter: 스냅샷 생성
- csi-attacher: 볼륨 Attach
- liveness: Health Check

9️⃣ rook-ceph-crashcollector (Crash 정보 수집)

역할:
- Ceph 데몬 Crash 시 덤프 수집
- /var/lib/rook/crash/ 디렉토리 모니터링
- Crash 정보를 MON에 전송
- 문제 디버깅에 사용

확인:
$ ceph crash ls
$ ceph crash info <crash-id>

🔟 rook-ceph-exporter (Prometheus 메트릭)

역할:
- Ceph 메트릭을 Prometheus 포맷으로 노출
- OSD 상태, Pool 사용량, PG 상태 등
- Day 6 Prometheus와 연동

메트릭 예시:
- ceph_health_status (0=OK, 1=WARN, 2=ERR)
- ceph_osd_up (OSD UP 개수)
- ceph_pool_stored_bytes (Pool 사용량)

ServiceMonitor 연동:
monitoring:
  enabled: true
→ MGR이 ServiceMonitor 생성
→ Prometheus가 자동 수집

1️⃣1️⃣ rook-ceph-tools (CLI 도구 Pod)

역할:
- ceph 명령어 실행 환경
- 디버깅 및 관리 작업

사용법:
$ kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph status
$ kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph osd tree
$ kubectl exec -n rook-ceph deploy/rook-ceph-tools -- rados df

포함된 도구:
- ceph: 메인 CLI
- rbd: RBD 관리
- rados: Object 관리
- ceph-volume: OSD 관리

Ceph 데이터 흐름 완전 정리

예시: MySQL Pod가 10GB RBD PVC 요청

1. kubectl apply -f mysql-pvc.yaml
   ↓
2. csi-rbdplugin-provisioner가 감지
   ↓
3. Provisioner → Ceph: rbd create replicapool/pvc-xxxxx --size 10240
   ↓
4. CRUSH 알고리즘: PG.1 계산
   ↓
5. PG.1 → OSD.0 매핑 (단일 노드이므로 OSD.0만)
   ↓
6. OSD.0: /dev/sdb에 데이터 저장
   ↓
7. Provisioner: PV 오브젝트 생성
   ↓
8. Kubernetes: PVC ↔ PV 바인딩 (Bound)
   ↓
9. MySQL Pod 스케줄링 → cpu1 노드
   ↓
10. csi-rbdplugin (cpu1): rbd map → /dev/rbd0
   ↓
11. csi-rbdplugin: mount /dev/rbd0 → /var/lib/kubelet/pods/.../volumes/
   ↓
12. MySQL 컨테이너: /var/lib/mysql 마운트 완료!

Multi-node 환경이라면?

PG.1 → [OSD.0, OSD.1, OSD.2] (3중 복제)
- Primary OSD.0 (cpu1): 쓰기 처리
- Replica OSD.1 (cpu2): 복제본 저장
- Replica OSD.2 (gpu1): 복제본 저장

→ 노드 1개 다운되어도 데이터 안전!

실습 2: 디스크 초기화

중요! OSD로 사용할 디스크는 완전히 깨끗해야 합니다!

# sdb 디스크 완전 초기화
$ wipefs -a /dev/sdb
$ sgdisk --zap-all /dev/sdb
$ dd if=/dev/zero of=/dev/sdb bs=1M count=100

# 확인 (FSTYPE이 비어있어야 함)
$ lsblk -f | grep sdb
sdb

실습 3: Ceph 클러스터 생성

단일 노드용 cluster YAML 작성:

# ~/ceph/ceph-cluster.yaml
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  cephVersion:
    image: quay.io/ceph/ceph:v18.2.0
  dataDirHostPath: /var/lib/rook

  mon:
    count: 1  # 단일 노드이므로 MON 1개

  mgr:
    count: 1
    modules:
      - name: pg_autoscaler
        enabled: true

  dashboard:
    enabled: true
    ssl: false

  monitoring:
    enabled: true  # Prometheus 연동!

  storage:
    useAllNodes: false
    useAllDevices: false
    nodes:
    - name: "gpu1"
      devices:
      - name: "sdb"  # 465.8GB 디스크

  disruptionManagement:
    managePodBudgets: false  # 단일 노드에서는 비활성화

적용:

$ kubectl apply -f ~/ceph/ceph-cluster.yaml
cephcluster.ceph.rook.io/rook-ceph created

# Pod 생성 확인 (30초~1분 소요)
$ kubectl get pods -n rook-ceph
NAME                                      READY   STATUS    RESTARTS   AGE
rook-ceph-mon-a-xxxxx                     1/1     Running   0          45s
rook-ceph-mgr-a-xxxxx                     1/1     Running   0          30s
rook-ceph-osd-prepare-gpu1-xxxxx          0/1     Completed 0          15s
rook-ceph-osd-0-xxxxx                     1/1     Running   0          10s

🎉 MON, MGR, OSD 모두 Running!

트러블슈팅 1: ServiceMonitor 권한 에러

Ceph 클러스터가 올라오긴 했지만, MGR Pod 로그를 보니 에러가 보였습니다!

문제 발견:

$ kubectl logs -n rook-ceph rook-ceph-mgr-a-xxxxx
Error: servicemonitors.monitoring.coreos.com "rook-ceph-mgr" is forbidden:
User "system:serviceaccount:rook-ceph:rook-ceph-system" cannot get resource "servicemonitors"
in API group "monitoring.coreos.com" at the cluster scope

🤔 왜 RBAC 권한이 필요한가?

상황 이해:

  1. Ceph MGR의 역할:

    • Prometheus 메트릭 노출 (Day 6에서 설치한 Prometheus와 통합)
    • monitoring.enabled: true 설정 시 ServiceMonitor CRD 자동 생성
  2. ServiceMonitor란?:

    # Prometheus Operator의 CRD
    apiVersion: monitoring.coreos.com/v1
    kind: ServiceMonitor
    metadata:
      name: rook-ceph-mgr
    spec:
      endpoints:
      - port: http-metrics  # Ceph MGR 메트릭 수집
    • Prometheus에게 "이 Service를 스크랩해라" 알림
    • Prometheus Operator가 감시하다가 자동으로 설정 업데이트
  3. 권한 문제:

    Ceph MGR Pod (rook-ceph-system ServiceAccount)
      ↓
    "ServiceMonitor 생성하려고 함"
      ↓
    Kubernetes API Server: "권한 없음! Forbidden!"

RBAC의 3가지 구성요소

1. ServiceAccount (신원 증명):

$ kubectl get sa -n rook-ceph rook-ceph-system
NAME               SECRETS   AGE
rook-ceph-system   0         10m
  • MGR Pod가 사용하는 계정
  • Pod spec에 serviceAccountName: rook-ceph-system 설정됨
  • 이 계정으로 Kubernetes API 호출

2. ClusterRole (권한 정의):

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: rook-ceph-servicemonitor-access
rules:
- apiGroups: ["monitoring.coreos.com"]   # Prometheus Operator API
  resources: ["servicemonitors"]         # ServiceMonitor CRD
  verbs: ["get", "list", "watch", "create", "update", "delete"]  # CRUD 권한
  • apiGroups: CRD의 API 그룹 (Prometheus Operator 설치 시 생성)
  • resources: 어떤 리소스에 접근?
  • verbs: 무슨 작업? (GET, POST, PUT, DELETE)

3. ClusterRoleBinding (계정과 권한 연결):

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: rook-ceph-servicemonitor-access
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: rook-ceph-servicemonitor-access  # 위에서 정의한 Role
subjects:
- kind: ServiceAccount
  name: rook-ceph-system                # 이 계정에게
  namespace: rook-ceph                   # rook-ceph 네임스페이스의

왜 기본 RBAC에 없었나?

이유:

  • Rook Operator는 Prometheus Operator 설치 여부를 모름
  • Prometheus Operator의 CRD (ServiceMonitor)는 선택적 의존성
  • 모든 사용자가 Prometheus를 쓰는 건 아니므로, 기본 RBAC에 포함 안 함

Rook이 제공하는 기본 RBAC:

# common.yaml에 포함된 기본 권한
- apiGroups: [""]
  resources: ["pods", "services", "configmaps", "secrets"]
  verbs: ["get", "list", "watch", "create", "update", "delete"]
- apiGroups: ["apps"]
  resources: ["deployments", "daemonsets", "statefulsets"]
  verbs: ["get", "list", "watch", "create", "update", "delete"]
# ... (ServiceMonitor는 없음!)

해결 방법

RBAC 생성:

# ~/ceph/rook-servicemonitor-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: rook-ceph-servicemonitor-access
rules:
- apiGroups: ["monitoring.coreos.com"]
  resources: ["servicemonitors"]
  verbs: ["get", "list", "watch", "create", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: rook-ceph-servicemonitor-access
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: rook-ceph-servicemonitor-access
subjects:
- kind: ServiceAccount
  name: rook-ceph-system
  namespace: rook-ceph

적용:

$ kubectl apply -f ~/ceph/rook-servicemonitor-rbac.yaml
clusterrole.rbac.authorization.k8s.io/rook-ceph-servicemonitor-access created
clusterrolebinding.rbac.authorization.k8s.io/rook-ceph-servicemonitor-access created

결과 확인:

# MGR이 ServiceMonitor 생성 성공!
$ kubectl get servicemonitor -n rook-ceph
NAME              AGE
rook-ceph-mgr     2m

# Prometheus가 자동 수집 시작
$ kubectl get servicemonitor -n rook-ceph rook-ceph-mgr -o yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: rook-ceph-mgr
  namespace: rook-ceph
spec:
  endpoints:
  - port: http-metrics  # MGR의 9283 포트
    interval: 30s
  namespaceSelector:
    matchNames:
    - rook-ceph
  selector:
    matchLabels:
      app: rook-ceph-mgr

Prometheus 확인:

# Prometheus UI (http://<NODE-IP>:30090) → Targets
# rook-ceph/rook-ceph-mgr/0 (http://10.x.x.x:9283/metrics) UP

✅ 해결! MGR이 정상적으로 ServiceMonitor를 생성하고, Prometheus가 Ceph 메트릭을 수집하기 시작했습니다!

배운 점

RBAC의 중요성:

  • Kubernetes는 기본적으로 최소 권한 원칙 (Least Privilege)
  • Pod가 API 호출 시 ServiceAccount의 권한 확인
  • CRD는 동적으로 추가된 리소스이므로 명시적 RBAC 필요

실무 팁:

# 권한 에러 디버깅
$ kubectl logs -n <namespace> <pod-name> | grep -i "forbidden\|unauthorized"

# ServiceAccount 권한 확인
$ kubectl auth can-i create servicemonitors \
  --as=system:serviceaccount:rook-ceph:rook-ceph-system \
  -n rook-ceph
no

# RBAC 적용 후
$ kubectl auth can-i create servicemonitors \
  --as=system:serviceaccount:rook-ceph:rook-ceph-system \
  -n rook-ceph
yes  # ✅

실습 4: Ceph Toolbox로 상태 확인

# Toolbox 배포
$ kubectl apply -f https://raw.githubusercontent.com/rook/rook/release-1.14/deploy/examples/toolbox.yaml
deployment.apps/rook-ceph-tools created

# Toolbox에 들어가서 Ceph 상태 확인
$ kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph status
  cluster:
    id:     6fa3b356-4964-460e-9a29-e8e350febeff
    health: HEALTH_WARN
            OSD count 1 < osd_pool_default_size 3

  services:
    mon: 1 daemons, quorum a (age 5m)
    mgr: a(active, since 4m)
    osd: 1 osds: 1 up (since 3m), 1 in (since 3m)

  data:
    pools:   0 pools, 0 pgs
    objects: 0 objects, 0 B
    usage:   27 MiB used, 466 GiB / 466 GiB avail

HEALTH_WARN는 정상입니다!

  • 단일 노드 환경이라 복제본을 못 만들어서 경고
  • 프로덕션에서는 HEALTH_OK가 나와야 함

3. RBD 블록 스토리지 (ReadWriteOnce)

왜 RBD가 필요한가?

RBD (RADOS Block Device):

  • 가상 블록 디바이스 제공
  • 데이터베이스, VM 디스크 같은 단일 Pod 전용 스토리지
  • ReadWriteOnce (RWO): 한 번에 하나의 Pod만 접근

실습 5: CephBlockPool 생성

트러블슈팅 2: PG undersized+peered 문제

첫 시도 (실패):

# GitHub의 기본 storageclass 적용
$ kubectl apply -f https://raw.githubusercontent.com/rook/rook/release-1.14/deploy/examples/csi/rbd/storageclass.yaml

# PVC 생성
$ kubectl apply -f test-pvc.yaml

# PVC 상태 확인
$ kubectl get pvc
NAME        STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS      AGE
test-pvc    Pending                                      rook-ceph-block   2m

문제 확인:

$ kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph status
  data:
    pools:   1 pools, 32 pgs
    pgs:     32 undersized+peered  ← 문제!
             100.000% pgs not active

원인: 기본 BlockPool 설정이 다중 노드용

failureDomain: host  # ❌ 여러 호스트에 분산
replicated:
  size: 3  # ❌ 복제본 3개 필요

해결: 단일 노드용 BlockPool 생성

# ~/ceph/single-node-blockpool.yaml
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  failureDomain: osd  # ✅ OSD 단위로 변경
  replicated:
    size: 1  # ✅ 복제본 1개
    requireSafeReplicaSize: false  # ✅ 안전성 검사 비활성화
# 기존 pool 삭제 후 재생성
$ kubectl delete cephblockpool -n rook-ceph replicapool
$ kubectl apply -f ~/ceph/single-node-blockpool.yaml

# PG 상태 확인
$ kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph status
  data:
    pools:   1 pools, 32 pgs
    pgs:     32 active+clean  ← ✅ 성공!

실습 6: PVC 동적 프로비저닝 테스트

# ~/ceph/test-ceph-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-ceph-pvc
spec:
  accessModes:
  - ReadWriteOnce  # RWO
  storageClassName: rook-ceph-block
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: test-ceph-pod
spec:
  containers:
  - name: test
    image: busybox:1.28
    command: ["sh", "-c", "echo 'Hello Ceph!' > /data/test.txt && cat /data/test.txt && sleep 3600"]
    volumeMounts:
    - name: ceph-volume
      mountPath: /data
  volumes:
  - name: ceph-volume
    persistentVolumeClaim:
      claimName: test-ceph-pvc
$ kubectl apply -f ~/ceph/test-ceph-pvc.yaml
persistentvolumeclaim/test-ceph-pvc created
pod/test-ceph-pod created

# PVC 상태 확인
$ kubectl get pvc
NAME            STATUS   VOLUME                                     CAPACITY   ACCESS MODES
test-ceph-pvc   Bound    pvc-4d7204b9-98c0-416b-9574-25af51682854   1Gi        RWO

# Pod 상태 확인
$ kubectl get pod test-ceph-pod
NAME            READY   STATUS    RESTARTS   AGE
test-ceph-pod   1/1     Running   0          20s

# 로그 확인
$ kubectl logs test-ceph-pod
Hello Ceph!

🎉 RBD 블록 스토리지 성공!


4. CephFS 파일 시스템 (ReadWriteMany)

왜 CephFS가 특별한가?

ReadWriteMany (RWX)의 의미:

  • 여러 Pod가 동시에 같은 볼륨을 읽고 쓸 수 있음!
  • 공유 파일 시스템 (POSIX 호환)
  • NFS 대체용

사용 사례:

  • 여러 웹 서버가 같은 static files 공유
  • 분산 로그 수집
  • 공유 설정 파일

실습 7: CephFilesystem 생성

CephFS는 MDS (Metadata Server)가 필요합니다!

# ~/ceph/cephfs-filesystem.yaml
apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
  name: myfs
  namespace: rook-ceph
spec:
  metadataPool:
    failureDomain: osd
    replicated:
      size: 1
      requireSafeReplicaSize: false

  dataPools:
    - name: data0
      failureDomain: osd
      replicated:
        size: 1
        requireSafeReplicaSize: false

  metadataServer:
    activeCount: 1
    activeStandby: false
$ kubectl apply -f ~/ceph/cephfs-filesystem.yaml
cephfilesystem.ceph.rook.io/myfs created

# MDS Pod 확인
$ kubectl get pods -n rook-ceph | grep mds
rook-ceph-mds-myfs-a-xxxxx   1/1     Running   0          30s
rook-ceph-mds-myfs-b-xxxxx   1/1     Running   0          26s

# CephFS 상태 확인
$ kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph fs status myfs
myfs - 0 clients
====
RANK  STATE    MDS       ACTIVITY     DNS    INOS   DIRS   CAPS
 0    active  myfs-a  Reqs:    0 /s    12     15     14      0
     POOL        TYPE     USED  AVAIL
myfs-metadata  metadata  40.0k   442G
  myfs-data0     data       0    442G
STANDBY MDS
   myfs-b

✅ Active MDS 1개, Standby MDS 1개 정상 작동!

실습 8: RWX 동시 접근 테스트

2개 Pod가 동시에 같은 볼륨 사용:

# ~/ceph/cephfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-cephfs
provisioner: rook-ceph.cephfs.csi.ceph.com
parameters:
  clusterID: rook-ceph
  fsName: myfs
  pool: myfs-data0
  # ... CSI 시크릿 설정 ...
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-cephfs-pvc
spec:
  accessModes:
  - ReadWriteMany  # ✅ RWX!
  storageClassName: rook-cephfs
  resources:
    requests:
      storage: 1Gi
---
# Pod 1
apiVersion: v1
kind: Pod
metadata:
  name: test-cephfs-pod1
spec:
  containers:
  - name: test
    image: busybox:1.28
    command: ["sh", "-c", "while true; do echo '[Pod 1] Writing...' >> /data/pod1.txt; ls -lh /data; sleep 10; done"]
    volumeMounts:
    - name: cephfs-volume
      mountPath: /data
  volumes:
  - name: cephfs-volume
    persistentVolumeClaim:
      claimName: test-cephfs-pvc
---
# Pod 2 (같은 PVC 사용!)
apiVersion: v1
kind: Pod
metadata:
  name: test-cephfs-pod2
spec:
  containers:
  - name: test
    image: busybox:1.28
    command: ["sh", "-c", "while true; do echo '[Pod 2] Writing...' >> /data/pod2.txt; ls -lh /data; sleep 10; done"]
    volumeMounts:
    - name: cephfs-volume
      mountPath: /data
  volumes:
  - name: cephfs-volume
    persistentVolumeClaim:
      claimName: test-cephfs-pvc  # ✅ 같은 PVC!
$ kubectl apply -f ~/ceph/cephfs-storageclass.yaml
storageclass.storage.k8s.io/rook-cephfs created
persistentvolumeclaim/test-cephfs-pvc created
pod/test-cephfs-pod1 created
pod/test-cephfs-pod2 created

# PVC 상태 (RWX 확인!)
$ kubectl get pvc test-cephfs-pvc
NAME              STATUS   VOLUME                                     CAPACITY   ACCESS MODES
test-cephfs-pvc   Bound    pvc-b267c8ad-ebae-4253-8685-e5d6487cef5e   1Gi        RWX

# 두 Pod 모두 Running
$ kubectl get pods test-cephfs-pod1 test-cephfs-pod2
NAME               READY   STATUS    RESTARTS   AGE
test-cephfs-pod1   1/1     Running   0          1m
test-cephfs-pod2   1/1     Running   0          1m

# Pod 1에서 파일 목록 확인
$ kubectl exec test-cephfs-pod1 -- ls -lh /data
total 3
-rw-r--r--    1 root     root        1.2K Nov  6 15:11 pod1.txt
-rw-r--r--    1 root     root        1.2K Nov  6 15:11 pod2.txt

# Pod 2에서도 동일하게 보임!
$ kubectl exec test-cephfs-pod2 -- ls -lh /data
total 3
-rw-r--r--    1 root     root        1.2K Nov  6 15:11 pod1.txt
-rw-r--r--    1 root     root        1.2K Nov  6 15:11 pod2.txt

# Pod 2에서 Pod 1의 파일 읽기
$ kubectl exec test-cephfs-pod2 -- head -5 /data/pod1.txt
[Pod 1] Writing...
[Pod 1] Writing...
[Pod 1] Writing...

🎉 진정한 공유 파일 시스템! 두 Pod가 같은 데이터를 실시간으로 공유합니다!


5. RGW 오브젝트 스토리지 (S3 API)

왜 오브젝트 스토리지?

S3 호환 API:

  • AWS S3와 동일한 API
  • 버킷 기반 스토리지
  • 웹 애플리케이션, 백업, 아카이브

실습 9: CephObjectStore 생성

# ~/ceph/ceph-objectstore.yaml
apiVersion: ceph.rook.io/v1
kind: CephObjectStore
metadata:
  name: my-store
  namespace: rook-ceph
spec:
  metadataPool:
    failureDomain: osd
    replicated:
      size: 1
      requireSafeReplicaSize: false

  dataPool:
    failureDomain: osd
    replicated:
      size: 1
      requireSafeReplicaSize: false

  gateway:
    port: 80
    instances: 1
$ kubectl apply -f ~/ceph/ceph-objectstore.yaml
cephobjectstore.ceph.rook.io/my-store created

# RGW Pod 확인
$ kubectl get pods -n rook-ceph -l app=rook-ceph-rgw
NAME                                      READY   STATUS    RESTARTS   AGE
rook-ceph-rgw-my-store-a-xxxxx            1/1     Running   0          1m

# RGW 서비스 확인
$ kubectl get svc -n rook-ceph | grep rgw
rook-ceph-rgw-my-store   ClusterIP   10.107.184.210   <none>   80/TCP   1m

실습 10: S3 사용자 생성

# ~/ceph/s3-user.yaml
apiVersion: ceph.rook.io/v1
kind: CephObjectStoreUser
metadata:
  name: my-s3-user
  namespace: rook-ceph
spec:
  store: my-store
  displayName: "My S3 User"
$ kubectl apply -f ~/ceph/s3-user.yaml
cephobjectstoreuser.ceph.rook.io/my-s3-user created

# Access Key와 Secret Key 확인
$ kubectl get secret -n rook-ceph rook-ceph-object-user-my-store-my-s3-user -o jsonpath='{.data.AccessKey}' | base64 -d
VZ4O1JVZQZX92XDKCNPM

$ kubectl get secret -n rook-ceph rook-ceph-object-user-my-store-my-s3-user -o jsonpath='{.data.SecretKey}' | base64 -d
VbtubT0XNLDjrzHcKoVKeNYvXjdEhfFOT2la2GVE

✅ S3 API 사용 준비 완료! 이제 AWS CLI나 boto3로 접근 가능합니다.


6. Ceph Dashboard로 클러스터 모니터링

실습 11: Dashboard 접근 설정

트러블슈팅 3: NodePort가 계속 ClusterIP로 되돌아가는 문제

# Dashboard 서비스 확인
$ kubectl get svc -n rook-ceph rook-ceph-mgr-dashboard
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)
rook-ceph-mgr-dashboard   ClusterIP   10.104.241.243   <none>        7000/TCP

# NodePort로 변경 시도
$ kubectl patch svc rook-ceph-mgr-dashboard -n rook-ceph -p '{"spec":{"type":"NodePort"}}'
service/rook-ceph-mgr-dashboard patched

# 잠시 후 다시 확인하면...
$ kubectl get svc -n rook-ceph rook-ceph-mgr-dashboard
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)
rook-ceph-mgr-dashboard   ClusterIP   10.104.241.243   <none>        7000/TCP  ← 다시 ClusterIP!

원인: Rook Operator가 서비스를 관리하면서 원래 상태로 복원

해결: 별도의 NodePort 서비스 생성

# ~/ceph/dashboard-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: ceph-dashboard-external
  namespace: rook-ceph
spec:
  type: NodePort
  selector:
    app: rook-ceph-mgr  # MGR Pod를 타겟으로
  ports:
  - name: dashboard
    port: 7000
    targetPort: 7000
    protocol: TCP
$ kubectl apply -f ~/ceph/dashboard-nodeport.yaml
service/ceph-dashboard-external created

$ kubectl get svc -n rook-ceph ceph-dashboard-external
NAME                      TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)
ceph-dashboard-external   NodePort   10.110.34.58   <none>        7000:31383/TCP

# 접근 테스트
$ curl -I http://172.30.1.38:31383
HTTP/1.1 200 OK
Server: Ceph-Dashboard

✅ 성공! Dashboard에 접근 가능합니다!

Dashboard 로그인

접속 정보:

URL: http://172.30.1.38:31383
Username: admin
Password: (Secret에서 확인)
# Password 확인
$ kubectl get secret -n rook-ceph rook-ceph-dashboard-password -o jsonpath='{.data.password}' | base64 -d
\nLp%F2g(aDx<PzY.$60

Dashboard 메뉴:

  • Block → RBD 블록 스토리지 상태
  • Filesystems → CephFS (myfs) 상태
  • Object Gateway → RGW (my-store) 상태
  • Cluster → 전체 클러스터 health

7. 최종 클러스터 상태

$ kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph status
  cluster:
    id:     6fa3b356-4964-460e-9a29-e8e350febeff
    health: HEALTH_WARN
            11 pool(s) have no replicas configured
            OSD count 1 < osd_pool_default_size 3

  services:
    mon: 1 daemons, quorum a (age 1h)
    mgr: a(active, since 1h)
    mds: 1/1 daemons up, 1 standby  ← CephFS
    osd: 1 osds: 1 up, 1 in
    rgw: 1 daemon active  ← RGW

  data:
    volumes: 1/1 healthy  ← CephFS
    pools:   11 pools, 168 pgs
    objects: 252 objects, 5.7 MiB
    usage:   33 MiB used, 466 GiB / 466 GiB avail
    pgs:     168 active+clean  ← ✅ 모든 PG 정상!

스토리지 사용량:

$ kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph df
--- POOLS ---
POOL              ID  PGS   STORED   OBJECTS     USED   %USED  MAX AVAIL
replicapool        2   32   672 KiB        13  680 KiB      0    442 GiB  ← RBD
myfs-metadata      3   16   455 KiB        21  484 KiB      0    442 GiB  ← CephFS
myfs-data0         4   32    22 KiB         3   28 KiB      0    442 GiB  ← CephFS
my-store.rgw.*     ...  ...  (8개 Pool)                                  ← RGW

Kubernetes 리소스:

$ kubectl get pvc
NAME              STATUS   VOLUME                                     CAPACITY   ACCESS MODES
test-ceph-pvc     Bound    pvc-4d7204b9-98c0-416b-9574-25af51682854   1Gi        RWO
test-cephfs-pvc   Bound    pvc-b267c8ad-ebae-4253-8685-e5d6487cef5e   1Gi        RWX

$ kubectl get pods
NAME               READY   STATUS    RESTARTS   AGE
test-ceph-pod      1/1     Running   0          30m
test-cephfs-pod1   1/1     Running   0          23m
test-cephfs-pod2   1/1     Running   0          23m

배운 점

1. Ceph는 마법이 아니라 수학이다

CRUSH 알고리즘의 아름다움:

  • 중앙 서버 없이도 모든 클라이언트가 데이터 위치를 계산
  • 노드 추가/제거 시 최소한의 데이터만 이동
  • Deterministic (같은 입력 = 항상 같은 결과)

Placement Group의 필요성:

  • 객체와 OSD 사이의 간접 레이어
  • 확장성과 관리 용이성의 균형

2. 단일 노드 vs 멀티 노드의 차이

단일 노드 Ceph의 한계:

문제점:
❌ SPOF (Single Point of Failure)
❌ 네트워크 분산 이점 없음
❌ 복제 효과 없음 (같은 서버)
❌ 장애 복구 불가

결론: 단일 노드는 학습용!
프로덕션은 최소 3노드 이상!

프로덕션 권장 구성:

최소:
  노드: 3개
  OSD/노드: 4개 (총 12 OSD)
  복제본: 3
  네트워크: 10GbE

이상적:
  노드: 5개+
  OSD/노드: 10개+
  복제본: 3
  네트워크: 25GbE+
  전용 Storage 노드 분리

3. 스토리지 타입별 명확한 차이

타입Access Mode용도기술
RBDRWO단일 Pod 전용 블록RADOS Block Device
CephFSRWX다중 Pod 공유 파일시스템MDS + POSIX
RGW-S3 API 오브젝트 스토리지RADOS Gateway

사용 예시:

  • 데이터베이스 → RBD (빠른 블록 접근)
  • 웹 서버 static files → CephFS (여러 Pod 공유)
  • 백업, 미디어 파일 → RGW (S3 API)

4. Rook Operator의 편리함

선언적 관리:

CephCluster CRD 작성
  ↓
kubectl apply
  ↓
Operator가 자동으로:
  - MON/MGR/OSD Pod 생성
  - Service, ConfigMap 생성
  - PG 계산 및 최적화
  - 장애 복구

vs 수동 Ceph 배포:

  • ceph-deploy 명령어 수십 개
  • 설정 파일 직접 편집
  • 수동 OSD 등록
  • 장애 시 수동 복구

삽질 포인트

1. PG undersized+peered 지옥

증상: PVC가 Pending 상태로 멈춤

원인: 기본 BlockPool 설정이 3복제본 요구

교훈:

  • 단일 노드 = size: 1, min_size: 1, failureDomain: osd
  • 항상 ceph status로 PG 상태 먼저 확인!

2. ServiceMonitor RBAC 권한

증상: MGR Pod 로그에 권한 에러

원인: Prometheus Operator CRD 접근 권한 없음

교훈:

  • Prometheus 있으면 monitoring.enabled: true
  • RBAC 권한 추가 필요
  • 로그를 꼼꼼히 읽자!

3. Dashboard NodePort 복원 문제

증상: NodePort로 변경해도 계속 ClusterIP로 되돌아감

원인: Rook Operator가 서비스 관리

교훈:

  • Operator 관리 리소스는 직접 수정 금지
  • 별도 서비스 생성이 답!

4. Disk 초기화 불충분

증상: OSD 생성 실패

원인: 파티션 테이블 잔여

교훈:

# 완전 초기화 3종 세트
wipefs -a /dev/sdb
sgdisk --zap-all /dev/sdb
dd if=/dev/zero of=/dev/sdb bs=1M count=100

다음 계획 (Day 8)

Day 7에서 Ceph 분산 스토리지의 기초를 다졌습니다. 이제 Day 8에서는 프로덕션 환경을 위한 고급 주제를 다룰 예정입니다:

Day 8 주제

  1. Helm 패키지 매니저 (차트 생성, 버전 관리, 롤백)
  2. CI/CD 파이프라인 (GitOps, ArgoCD)
  3. 고급 로깅 스택 (Fluent Bit, Loki, Grafana)
  4. 네트워크 정책 (NetworkPolicy로 Pod 간 통신 제어)
  5. 백업 및 재해 복구 (Velero로 클러스터 백업)

도전 과제

  • Ceph 멀티 노드 확장: cpu2 노드 추가하여 3노드 클러스터 구성
  • Erasure Coding: 복제 대신 EC로 용량 효율 개선
  • CephFS Subvolume: 테넌트별 격리된 파일시스템
  • RGW 버킷 정책: S3 버킷 접근 제어

마무리

Ceph는 복잡하지만 강력합니다. 단일 노드로 학습했지만, 프로덕션에서는 반드시 멀티 노드로 구성해야 합니다.

핵심 요약:

  • ✅ Ceph = RADOS + CRUSH + PG
  • ✅ RBD (RWO), CephFS (RWX), RGW (S3)
  • ✅ 단일 노드 = 학습용, 멀티 노드 = 프로덕션
  • ✅ Rook Operator로 선언적 관리
  • ✅ Dashboard로 GUI 모니터링

Day 8에서 만나요! 🚀


참고 자료

profile
기록하고 공유하려고 노력하는 DevOps 엔지니어

0개의 댓글