[ Infra ]/Data Platform

[Data Platform] Rook-Ceph 구축 가이드(Block / FileSystem / Object Store)

환이s 2026. 6. 2. 14:30
728x90
반응형


Intro

 

안녕하세요. 환이s입니다 👋

최근 사내에서 데이터 레이크하우스 플랫폼을 직접 구축하는 프로젝트를 진행했습니다.

 

단순히 데이터를 쌓는 것에서 끝나는 게 아니라,

수집 → 저장 → 처리 → 분석 → 시각화까지 이어지는

 

전체 데이터 파이프라인을

오픈소스 기반으로 직접 설계하고 올려보는 작업이었는데요.

 

이번 시리즈에서는

그 과정에서 구축한 스택들을 하나씩 순서대로 정리해보려고 합니다.

 

전체 구성은 아래와 같습니다.

역할 오픈소스
분산 스토리지 Ceph
스트리밍 파이프라인 Kafka
대규모 데이터 처리 Spark
쿼리 엔진 Trino
워크플로우 오케스트레이션 Airflow
인프라 모니터링 Prometheus / Grafana
데이터 시각화 Superset

 

그 첫 번째 주제로,

모든 데이터가 실제로 저장되는 기반 스토리지인 Rook-Ceph 구축부터 시작하겠습니다.

 

Kubernetes 위에서 Ceph를 운영하다 보면

단순히 디스크를 마운트하는 것에서 끝나는 게 아니라,

 

Block / FileSystem / Object Store

세 가지 스토리지 타입을 상황에 맞게 골라 써야 하고,

 

이후 Spark, Trino, Delta Lake 같은

컴포넌트들이 이 스토리지 위에서 데이터를 읽고 쓰는 구조가 됩니다.

 

이번 글에서는

  • Rook-Ceph 전체 아키텍처가 어떻게 구성되는지
  • 실제로 어떤 순서로 설치하면 되는지
  • Block / FileSystem / Object Store 각각을 어떻게 구성하는지
  • S3 외부 접근까지 어떻게 연결하는지

까지 순서대로 확인해 보겠습니다.


1. Rook-Ceph 아키텍처 개요

 

본격적인 설치에 앞서,

Rook-Ceph가 어떤 구조로 동작하는지 먼저 이해하는 것이 중요합니다.

 

Ceph는 크게 아래 5가지 핵심 컴포넌트로 구성됩니다.

컴포넌트 역할
MON (Monitor) 클러스터 상태 및 맵 관리, 홀수 개 운영 권장
MGR (Manager) 메트릭 수집, Dashboard, 모듈 관리
OSD (Object Storage Daemon) 실제 데이터를 저장하는 디스크 단위 데몬
MDS (Metadata Server) CephFS 파일시스템의 메타데이터 관리
RGW (RADOS Gateway) S3 / Swift 호환 오브젝트 스토리지 API 제공

 

Rook은 이 Ceph 컴포넌트들을

KubernetesCustom Resource로 선언하면 자동으로 배포하고 관리해주는 오퍼레이터입니다.

 

즉, 여기서 확인할 수 있는 핵심은

복잡한 Ceph 운영을 Kubernetes 선언형 방식으로 관리할 수 있다는 점입니다.


2. 사전 준비: 노드 디스크와 레이블 확인

 

Rook-CephOSD가 올라갈 노드에 미포맷 상태의 빈 디스크가 있어야 합니다.

 

이번 구성에서는 worker3, worker4, worker5 노드의 vdb 디스크를 사용합니다.

 

디스크 상태 확인은 각 노드에서 아래 명령어로 할 수 있습니다.

lsblk

 

정상적인 상태라면 아래처럼 vdb가 마운트 없이 보여야 합니다.

NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
vda    252:0    0   50G  0 disk
└─vda1 252:1    0   50G  0 part /
vdb    252:16   0  100G  0 disk        ← 마운트 없음

 

그다음 OSD 노드에 레이블을 설정합니다.

kubectl label node k8s-worker3 role=ceph
kubectl label node k8s-worker4 role=ceph
kubectl label node k8s-worker5 role=ceph

 

레이블 확인은 아래 명령어로 할 수 있습니다.

kubectl get nodes --show-labels | grep ceph

 

즉, 여기서 확인할 수 있는 핵심은

OSD 노드는 빈 디스크와 레이블이 사전에 준비되어 있어야 Rook이 정상적으로 OSD를 배포할 수 있다는 점입니다.


3. 설치 순서 개요

 

이번 구축에서 사용하는 매니페스트 디렉토리 구조는 아래와 같습니다.

manifests/
├── 10_crd           # CRD 등록
├── 20_common        # Namespace, 공통 RBAC
├── 30_operator      # Rook Operator 배포
├── 40_cluster       # CephCluster 생성
├── 50_toolbox       # 디버깅용 Toolbox + Dashboard 외부 접근
├── 60_object-store  # CephFS + Object Store(RGW)
├── 70_object-user   # S3 유저 + NodePort Service
├── 80_storageclass  # StorageClass 등록
└── 90_rbac          # ServiceAccount / Role

 

설치는 반드시 이 번호 순서대로 진행해야 하며,

중간에 반드시 대기해야 하는 구간이 3곳 있습니다.

10 → 20 → 30 → ⏳ → 40 → ⏳ HEALTH_OK → 50 → 60 → ⏳ → 70 → 80 → 90

 

각 단계를 순서대로 살펴보겠습니다.


4. crd / common / operator : 기반 리소스 설치

 

기반 리소스는 대부분 Rook 공식 GitHub에서 제공하는 파일을 그대로 사용하지만,

이번 구성에서는 두 가지를 추가로 커스텀했습니다.

 

첫 번째는 common 디렉토리에 Namespace 정의를 별도로 추가했습니다.

공식 common.yaml에는 Namespace가 포함되어 있지 않아서 아래 파일을 직접 추가했습니다.

# common에 추가
apiVersion: v1
kind: Namespace
metadata:
  name: rook-ceph

 

두 번째는 operator 디렉토리를 아래처럼 3개 파일로 분리해서 구성했습니다.

operator/
├── csi-operator.yaml      # 공식 파일 그대로 사용
├── operator.yaml          # 공식 파일 그대로 사용
└── dashboard-secret.yaml  # Ceph Dashboard 접근용 Secret (커스텀 추가)

 

dashboard-secret.yaml은

Ceph Dashboard 로그인에 사용할 패스워드를 Secret으로 등록하는 파일입니다.

apiVersion: v1
kind: Secret
metadata:
  name: rook-ceph-dashboard-password
  namespace: rook-ceph
type: kubernetes.io/rook
stringData:
  password: "admin123"

 

⚠️ 운영 환경에서는 password를 반드시 변경해서 사용하세요.

 

순서대로 적용합니다.

kubectl apply -f crd/
kubectl apply -f common/
kubectl apply -f operator/

 

Operator Pod가 정상적으로 올라왔는지 확인합니다.

kubectl get pods -n rook-ceph -w

 

아래처럼 Running 상태가 되면 다음 단계로 진행합니다.

NAME                                  READY   STATUS    RESTARTS   AGE
rook-ceph-operator-xxxxxxxxxx-xxxxx   1/1     Running   0          ...

 

즉, 여기서 확인할 수 있는 핵심은 Namespace가 먼저 생성된 이후에 Operator가 배포되어야 하고,

Operator가 Running 상태가 된 이후에만 CephCluster 리소스를 인식하고 처리할 수 있다는 점입니다.


5. cluster: CephCluster 생성

 

CephCluster Rook-Ceph의 핵심 리소스입니다.

 

이 yaml 하나로 MON, MGR, OSD, Dashboard 등 전체 클러스터 구성이 결정됩니다.

 

전체 설정 파일은 아래와 같습니다.

apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  cephVersion:
    image: harbor.local/library/rook-ceph/ceph:v19.2.3
    allowUnsupported: false

  cephConfig:
    global:
      mon_max_pg_per_osd: "300"

  dataDirHostPath: /var/lib/rook

  skipUpgradeChecks: false
  continueUpgradeAfterChecksEvenIfNotHealthy: false
  waitTimeoutForHealthyOSDInMinutes: 10
  upgradeOSDRequiresHealthyPGs: false

  mgr:
    count: 2
    allowMultiplePerNode: false
    modules:
      - name: rook
        enabled: true

  dashboard:
    enabled: true
    ssl: true

  monitoring:
    enabled: false
    metricsDisabled: false
    exporter:
      perfCountersPrioLimit: 5
      statsPeriodSeconds: 5

  network:
    connections:
      encryption:
        enabled: false
      compression:
        enabled: false
      requireMsgr2: false

  crashCollector:
    disable: false

  logCollector:
    enabled: true
    periodicity: daily
    maxLogSize: 500M

  cleanupPolicy:
    confirmation: ""
    sanitizeDisks:
      method: quick
      dataSource: zero
      iteration: 1
    allowUninstallWithVolumes: false

  mon:
    count: 3
    allowMultiplePerNode: true

  placement:
    all:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
            - matchExpressions:
                - key: role
                  operator: In
                  values:
                    - "ceph"
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
        - key: node-role.kubernetes.io/master
          operator: Exists
          effect: NoSchedule
    mon:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
            - matchExpressions:
                - key: role
                  operator: In
                  values:
                    - "ceph"
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
        - key: node-role.kubernetes.io/master
          operator: Exists
          effect: NoSchedule

  removeOSDsIfOutAndSafeToRemove: false

  priorityClassNames:
    mon: system-node-critical
    osd: system-node-critical
    mgr: system-cluster-critical

  storage:
    useAllNodes: false
    nodes:
      - name: "k8s-worker3"
        deviceFilter: "^vdb"
      - name: "k8s-worker4"
        deviceFilter: "^vdb"
      - name: "k8s-worker5"
        deviceFilter: "^vdb"
    useAllDevices: false
    allowDeviceClassUpdate: false
    allowOsdCrushWeightUpdate: false
    scheduleAlways: false
    onlyApplyOSDPlacement: false
    # fullRatio: 0.95
    # backfillFullRatio: 0.90
    # nearFullRatio: 0.85

  disruptionManagement:
    managePodBudgets: true
    osdMaintenanceTimeout: 30

  csi:
    readAffinity:
      enabled: false

  healthCheck:
    daemonHealth:
      mon:
        disabled: false
        interval: 45s
      osd:
        disabled: false
        interval: 60s
      status:
        disabled: false
        interval: 60s
    livenessProbe:
      mon:
        disabled: false
      mgr:
        disabled: false
      osd:
        disabled: false
    startupProbe:
      mon:
        disabled: false
      mgr:
        disabled: false
      osd:
        disabled: false

 

주요 설정 포인트를 섹션별로 살펴보겠습니다.

 

Ceph 버전

cephVersion:
  image: harbor.local/library/rook-ceph/ceph:v19.2.3

 

공식 quay.io 대신 내부 Harbor 레지스트리에서 이미지를 가져오도록 설정했습니다.

에어갭(Air-gap) 환경이나 사내 네트워크 환경에서는 이처럼 내부 레지스트리를 사용하는 것이 일반적입니다.

 

MON / MGR

mon:
  count: 3
  allowMultiplePerNode: true   # 테스트 환경에서 유연성 확보

mgr:
  count: 2                     # MGR 이중화로 가용성 향상
  allowMultiplePerNode: false

 

MON은 항상 홀수 개로 운영해야 quorum이 유지됩니다.

MGR은 2개로 설정해서 Active/Standby 구성을 갖췄습니다.

 

Dashboard

dashboard:
  enabled: true
  ssl: true

 

Dashboard를 활성화하고 SSL을 적용했습니다.

앞서 operator에서 등록한 rook-ceph-dashboard-password Secret이 이 Dashboard 로그인에 사용됩니다.

 

 

Monitoring

monitoring:
  enabled: false   # 현재는 비활성화

 

현재는 비활성화 상태입니다.

이후 Prometheus 구축 편에서 enabled: true로 변경해서 메트릭 연동을 활성화할 예정입니다.

 

Placement

placement:
  all:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: role
                operator: In
                values:
                  - "ceph"

role=ceph 레이블이 있는 노드에만 Ceph 컴포넌트가 배포되도록 강제했습니다.

사전 준비 단계에서 worker3, 4, 5에 레이블을 붙인 이유가 바로 이 설정 때문입니다.

 

Storage

storage:
  useAllNodes: false
  nodes:
    - name: "k8s-worker3"
      deviceFilter: "^vdb"
    - name: "k8s-worker4"
      deviceFilter: "^vdb"
    - name: "k8s-worker5"
      deviceFilter: "^vdb"

useAllNodes: false로 설정해서 의도하지 않은 노드에 OSD가 올라가는 것을 방지하고,

각 노드의 vdb 디바이스만 OSD로 사용하도록 명시했습니다.

 

💡 운영 환경 팁: 현재 주석 처리된 아래 값들은 디스크 용량 관리에 중요한 설정입니다. 운영 환경에서는 명시적으로 설정하는 것을 권장합니다.

# fullRatio: 0.95       # IO 차단 임계값
# backfillFullRatio: 0.90   # 백필 중단 임계값
# nearFullRatio: 0.85   # 경고 발생 임계값

 

이제 적용합니다.

kubectl apply -f cluster/

 

클러스터 생성은 5~10분 정도 소요됩니다.

아래 명령어로 Pod 상태를 확인합니다.

kubectl get pods -n rook-ceph -w

 

정상적으로 올라오면 아래처럼 보입니다.

rook-ceph-mon-a-xxxxxxxxxx        1/1   Running   0   ...
rook-ceph-mon-b-xxxxxxxxxx        1/1   Running   0   ...
rook-ceph-mon-c-xxxxxxxxxx        1/1   Running   0   ...
rook-ceph-mgr-a-xxxxxxxxxx        1/1   Running   0   ...
rook-ceph-mgr-b-xxxxxxxxxx        1/1   Running   0   ...
rook-ceph-osd-0-xxxxxxxxxx        1/1   Running   0   ...
rook-ceph-osd-1-xxxxxxxxxx        1/1   Running   0   ...
rook-ceph-osd-2-xxxxxxxxxx        1/1   Running   0   ...

 

즉, 여기서 확인할 수 있는 핵심은

MON 3개 + MGR 2개 + OSD 3개가 모두 Running 상태여야 다음 단계로 진행할 수 있다는 점입니다.


6. toolbox: Ceph 상태 확인 및 Dashboard 외부 접근 설정

 

toolbox 디렉토리에는

단순 디버깅 도구뿐 아니라 Dashboard를 외부에서 접근하기 위한 설정도 함께 담겨 있습니다.

50_toolbox/
├── toolbox.yaml                   # Toolbox Deployment (디버깅용)
├── dashboard-external-https.yaml  # Dashboard NodePort Service
└── ceph-dashboard-ingress.yaml    # Dashboard Ingress

 

toolbox: 디버깅용 Pod

 

ToolboxCeph 클러스터 내부를 직접 조회할 수 있는 디버깅용 Deployment입니다.

 

내부적으로 MON 엔드포인트와

admin keyring을 자동으로 읽어와 별도 설정 없이 ceph 명령어를 바로 사용할 수 있습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: rook-ceph-tools
  namespace: rook-ceph
  labels:
    app: rook-ceph-tools
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rook-ceph-tools
  template:
    spec:
      dnsPolicy: ClusterFirstWithHostNet
      serviceAccountName: rook-ceph-default
      containers:
        - name: rook-ceph-tools
          image: harbor.local/library/rook-ceph/ceph:v19
          securityContext:
            runAsNonRoot: true
            runAsUser: 2016
            runAsGroup: 2016
            capabilities:
              drop: ["ALL"]
          env:
            - name: ROOK_CEPH_USERNAME
              valueFrom:
                secretKeyRef:
                  name: rook-ceph-mon
                  key: ceph-username
          volumeMounts:
            - mountPath: /etc/ceph
              name: ceph-config
            - name: mon-endpoint-volume
              mountPath: /etc/rook
            - name: ceph-admin-secret
              mountPath: /var/lib/rook-ceph-mon
              readOnly: true
            - name: rook-config-override
              mountPath: /etc/rook-config-override
              readOnly: true
      volumes:
        - name: ceph-admin-secret
          secret:
            secretName: rook-ceph-mon
            items:
              - key: ceph-secret
                path: secret.keyring
        - name: mon-endpoint-volume
          configMap:
            name: rook-ceph-mon-endpoints
            items:
              - key: data
                path: mon-endpoints
        - name: rook-config-override
          configMap:
            name: rook-config-override
            optional: true
        - name: ceph-config
          emptyDir: {}

 

Toolbox Pod에 접속해서 클러스터 상태를 확인합니다.

kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph status

 

정상적인 경우 아래처럼 HEALTH_OK가 출력됩니다.

cluster:
  id:     xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  health: HEALTH_OK

services:
  mon: 3 daemons, quorum a,b,c
  mgr: a(active), b(standby)
  osd: 3 osds: 3 up, 3 in

 

OSD 상세 상태도 같이 확인해 두면 좋습니다.

kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph osd status
+----+-------------+-------+-------+--------+---------+
| id |     host    |  used | avail | wr ops | wr data |
+----+-------------+-------+-------+--------+---------+
|  0 | k8s-worker3 |  ...  |  ...  |    0   |      0  |
|  1 | k8s-worker4 |  ...  |  ...  |    0   |      0  |
|  2 | k8s-worker5 |  ...  |  ...  |    0   |      0  |
+----+-------------+-------+-------+--------+---------+

 

HEALTH_OK를 반드시 확인한 이후에 다음 단계로 진행해야 합니다.


dashboard-external-https: Dashboard NodePort 노출

 

Ceph Dashboard를

클러스터 외부에서 접근할 수 있도록 NodePort Service를 생성합니다.

apiVersion: v1
kind: Service
metadata:
  name: rook-ceph-mgr-dashboard-external-https
  namespace: rook-ceph
  labels:
    app: rook-ceph-mgr
    rook_cluster: rook-ceph
spec:
  ports:
    - name: dashboard
      port: 8443
      protocol: TCP
      targetPort: 8443
      nodePort: 30510
  selector:
    app: rook-ceph-mgr
    mgr_role: active       # Active MGR Pod에만 트래픽 전달
    rook_cluster: rook-ceph
  sessionAffinity: None
  type: NodePort

 

mgr_role: active 셀렉터가 핵심입니다.

MGR이 2개 떠있을 때 Active 상태인 MGR Pod에만 트래픽이 전달됩니다.


ceph-dashboard-ingress: Ingress로 도메인 접근

 

NodePort 대신 도메인으로 접근할 수 있도록 Ingress도 함께 설정했습니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ceph-dashboard
  namespace: rook-ceph
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-headers: "Authorization, Content-Type"
spec:
  ingressClassName: nginx
  rules:
    - host: ceph.192.168.2.240.sslip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: rook-ceph-mgr-dashboard-external-https
                port:
                  number: 8443

 

backend-protocol: "HTTPS" 어노테이션이 중요한 포인트입니다.

 

Dashboard가 SSL로 서빙되기 때문에 Ingress가 백엔드로 HTTPS 통신을 해야 합니다.

이 어노테이션이 없으면 Ingress가 HTTP로 백엔드에 접근해서 연결이 실패합니다.

 

Dashboard 접근은 아래처럼 확인할 수 있습니다.

# NodePort로 직접 접근
https://<노드IP>:30510

# Ingress 도메인으로 접근
https://ceph.192.168.2.240.sslip.io

 

로그인 시 operator에서 등록한 Secret의 패스워드를 사용합니다.

 

ID: admin
PW: admin123  (dashboard-secret.yaml에서 설정한 값)

 

즉, 여기서 확인할 수 있는 핵심은 Toolbox로 HEALTH_OK를 확인하고,

Dashboard로 클러스터 상태를 시각적으로 모니터링하는 환경까지 이 단계에서 함께 구성된다는 점입니다.


7. object-store: CephFS와 Object Store(RGW) 구성

 

object-store 디렉토리에는 두 가지가 포함되어 있습니다.

  • CephFilesystem (MDS): ReadWriteMany PVC용 파일시스템
  • CephObjectStore (RGW): S3 호환 오브젝트 스토리지

 

CephFilesystem 설정

 

CephFilesystem은

여러 Pod가 동시에 마운트 할 수 있는 ReadWriteMany 방식의 공유 파일시스템을 제공합니다.

apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
  name: myfs
  namespace: rook-ceph
spec:
  metadataPool:
    replicated:
      size: 3
      requireSafeReplicaSize: true
    parameters:
      compression_mode: none

  dataPools:
    - name: replicated
      failureDomain: host
      replicated:
        size: 3
        requireSafeReplicaSize: true
      parameters:
        compression_mode: none

  preserveFilesystemOnDelete: true

  metadataServer:
    activeCount: 1
    activeStandby: true
    placement:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: role
              operator: In
              values:
              - "ceph"
      podAntiAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - rook-ceph-mds
            topologyKey: kubernetes.io/hostname
        preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - rook-ceph-mds
              topologyKey: topology.kubernetes.io/zone
    priorityClassName: system-cluster-critical
    livenessProbe:
      disabled: false
    startupProbe:
      disabled: false
---
apiVersion: ceph.rook.io/v1
kind: CephFilesystemSubVolumeGroup
metadata:
  name: myfs-csi
  namespace: rook-ceph
spec:
  name: csi
  filesystemName: myfs
  pinning:
    distributed: 1

 

주요 설정 포인트는 아래와 같습니다.

 

metadataPool / dataPools

replicated:
  size: 3
  requireSafeReplicaSize: true  # replica 1 설정 방지 (데이터 손실 예방)
failureDomain: host             # 호스트 단위로 replica 분산

 

requireSafeReplicaSize: true

실수로 replica를 1로 설정하는 것을 방지하는 안전장치입니다.

 

metadataServer

activeCount: 1       # Active MDS 1개
activeStandby: true  # Standby MDS도 1개 → 총 2개 MDS Pod 생성

 

activeStandby: true로 설정하면

Standby MDS가 메타데이터 캐시를 미리 워밍업 해두어 Active MDS 장애 시 빠르게 전환됩니다.

 

preserveFilesystemOnDelete

preserveFilesystemOnDelete: true  # CephFilesystem 삭제 시 실제 데이터 보존

 

CephObjectStore의

preservePoolsOnDelete: true와 같은 개념으로 리소스를 실수로 삭제해도 데이터가 보존됩니다.

 

SubVolumeGroup

kind: CephFilesystemSubVolumeGroup
spec:
  name: csi
  filesystemName: myfs
  pinning:
    distributed: 1  # MDS 부하를 여러 데몬에 분산

 

CSI 드라이버가 CephFS를 사용할 때 SubVolumeGroup 단위로 볼륨을 관리합니다.

distributed: 1로 설정하면 MDS 부하가 여러 데몬에 자동으로 분산됩니다.

 

MDS Pod 상태를 확인합니다.

kubectl get pods -n rook-ceph | grep mds

 

activeStandby: true로 설정했기 때문에 MDS Pod가 2개 뜹니다.

rook-ceph-mds-myfs-a-xxxxxxxxxx   1/1   Running   0   ...
rook-ceph-mds-myfs-b-xxxxxxxxxx   1/1   Running   0   ...

CephObjectStore 설정

apiVersion: ceph.rook.io/v1
kind: CephObjectStore
metadata:
  name: ceph-store
  namespace: rook-ceph
spec:
  metadataPool:
    failureDomain: host
    replicated:
      size: 3
  dataPool:
    failureDomain: host
    replicated:
      size: 3
  preservePoolsOnDelete: true
  gateway:
    port: 80
    instances: 2
    resources:
      requests:
        cpu: 500m
        memory: 1Gi
      limits:
        cpu: 2000m
        memory: 2Gi

 

주요 설정 포인트는 아래와 같습니다.

  • failureDomain: host → 호스트 단위로 replica를 분산해서 노드 장애 시에도 데이터 보존
  • preservePoolsOnDelete: true → CephObjectStore 삭제 시 Pool은 보존 (데이터 보호)
  • instances: 2 → RGW Pod를 2개 띄워 이중화

 

RGW Pod가 정상적으로 올라왔는지 확인합니다.

kubectl get pods -n rook-ceph | grep rgw

 

instances: 2로 설정했기 때문에 RGW Pod가 2개 뜹니다.

 

rook-ceph-rgw-ceph-store-a-xxxxxxxxxx   1/1   Running   0   ...
rook-ceph-rgw-ceph-store-b-xxxxxxxxxx   1/1   Running   0   ...

 

즉, 여기서 확인할 수 있는 핵심은

CephFilesystemMDS Pod 2개(Active + Standby)가, CephObjectStore는 RGW Pod 2개가 뜨면서 각각 이중화 구성이 완료된다는 점입니다.

 

그리고 RGW Pod가 완전히 Running 상태가 된 이후에만 다음 단계인 S3 유저 생성으로 진행해야 합니다.


8. object-user: S3 유저 생성 및 외부 노출

 

RGWRunning 상태가 된 이후에 S3 유저를 생성합니다.

 

이번 구성에서는 Delta Lake용 유저를 생성합니다.

Iceberg 유저는 이후 Spark + Iceberg 연동 편에서 활성화할 예정입니다.

apiVersion: ceph.rook.io/v1
kind: CephObjectStoreUser
metadata:
  name: delta-user
  namespace: rook-ceph
spec:
  store: ceph-store
  displayName: "Delta Lake Spark User"

 

S3 엔드포인트는 NodePort로 외부에 노출합니다.

apiVersion: v1
kind: Service
metadata:
  name: rook-ceph-rgw-ceph-store-external
  namespace: rook-ceph
  labels:
    app: rook-ceph-rgw
    rook_cluster: rook-ceph
    rook_object_store: ceph-store
spec:
  ports:
    - name: rgw
      port: 80
      protocol: TCP
      targetPort: 8080
      nodePort: 30511
  selector:
    app: rook-ceph-rgw
    ceph_daemon_id: ceph-store
    rgw: ceph-store
    rook_cluster: rook-ceph
    rook_object_store: ceph-store
  sessionAffinity: None
  type: NodePort
kubectl apply -f object-user/

 

유저가 생성되면 Rook이 자동으로 Secret을 만들어 줍니다.

아래 명령어로 AccessKey와 SecretKey를 확인할 수 있습니다.

# AccessKey 확인
kubectl get secret rook-ceph-object-user-ceph-store-delta-user \
  -n rook-ceph -o jsonpath='{.data.AccessKey}' | base64 --decode

# SecretKey 확인
kubectl get secret rook-ceph-object-user-ceph-store-delta-user \
  -n rook-ceph -o jsonpath='{.data.SecretKey}' | base64 --decode

 

S3 접근 테스트는 아래처럼 할 수 있습니다.

aws s3 ls \
  --endpoint-url http://<노드IP>:30511 \
  --access-key <AccessKey> \
  --secret-key <SecretKey> \
  --no-verify-ssl

 

즉, 여기서 확인할 수 있는 핵심은

RGW Pod가 완전히 Running 상태가 된 이후에

CephObjectStoreUser를 생성해야 Secret이 정상적으로 만들어지고,

 

Secret 안의 AccessKey / SecretKey가 이후 Spark, Delta Lake 연동에서 S3 인증 정보로 사용된다는 점입니다.


9. storageclass : 스토리지 타입별 StorageClass 등록

 

이번 구성에서는 Block(RBD)CephFS 두 가지 StorageClass를 등록합니다.

 

Block StorageClass (RWO)

apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
    requireSafeReplicaSize: true
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
  clusterID: rook-ceph
  pool: replicapool
  imageFormat: "2"
  imageFeatures: layering
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
  csi.storage.k8s.io/controller-publish-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/controller-publish-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
  csi.storage.k8s.io/fstype: ext4
allowVolumeExpansion: true
reclaimPolicy: Delete

 

여기서 imageFeatures: layering만 사용한 이유는

worker 노드 커널 버전이 5.4 미만인 경우 fast-diff, object-map 등 고급 기능을 지원하지 않기 때문입니다.

 

커널 버전은 아래 명령어로 확인할 수 있습니다.

uname -r

 

커널이 5.4 이상이라면 아래처럼 변경하면 성능이 더 좋습니다.

# 커널 5.4 이상인 경우
imageFeatures: layering,fast-diff,object-map,deep-flatten,exclusive-lock

CephFS StorageClass (RWX)

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-replicated
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  csi.storage.k8s.io/controller-expand-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
  csi.storage.k8s.io/controller-publish-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/controller-publish-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
allowVolumeExpansion: true
reclaimPolicy: Delete
mountOptions: []
kubectl apply -f storageclass/
kubectl get storageclass

 

아래처럼 두 가지 StorageClass가 등록된 것을 확인할 수 있습니다.

NAME              PROVISIONER                     RECLAIMPOLICY   AGE
rook-ceph-block   rook-ceph.rbd.csi.ceph.com      Delete          ...
rook-cephfs       rook-ceph.cephfs.csi.ceph.com   Delete          ...

 

즉, 여기서 확인할 수 있는 핵심은

두 StorageClass의 차이는 접근 모드에 있다는 점입니다.

StorageClass 접근 모드 주 용도
rook-ceph-block RWO (단일 노드) DB, 단일 Pod 스토리지
rook-cephfs RWX (다중 노드) 공유 파일시스템, 여러 Pod 동시 마운트

 

그리고 현재 두 StorageClass 모두

reclaimPolicy: Delete로 설정되어 있어서 PVC를 삭제하면 데이터도 함께 삭제됩니다.

 

운영 환경에서 중요한 데이터를 다루는 PVC라면 Retain으로 변경하는 것을 권장합니다.


10. rbac : ServiceAccount 및 권한 설정

 

마지막으로 Ceph 리소스에 접근할 수 있는 ServiceAccountRBAC을 적용합니다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: rook-ceph-sa
  namespace: rook-ceph
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: rook-ceph-role
  namespace: rook-ceph
rules:
  - apiGroups: [""]
    resources:
      - pods
      - pods/log
      - services
      - configmaps
      - secrets
      - events
      - persistentvolumeclaims
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"]
  - apiGroups: ["ceph.rook.io"]
    resources:
      - cephclusters
      - cephblockpools
      - cephobjectstores
      - cephfilesystems
      - cephobjectstoreusers
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: rook-ceph-rb
  namespace: rook-ceph
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: rook-ceph-role
subjects:
  - kind: ServiceAccount
    name: rook-ceph-sa
    namespace: rook-ceph
---
apiVersion: v1
kind: Secret
metadata:
  name: rook-ceph-sa-token
  namespace: rook-ceph
  annotations:
    kubernetes.io/service-account.name: rook-ceph-sa
type: kubernetes.io/service-account-token

 

⚠️ 참고: PersistentVolume은 클러스터 전체 범위(Namespace 미적용) 리소스입니다. PV에 직접 접근이 필요한 경우 Role이 아닌 ClusterRole을 별도로 추가해야 합니다.

 

# PV 접근이 필요한 경우 별도 추가
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: rook-ceph-clusterrole
rules:
  - apiGroups: [""]
    resources:
      - persistentvolumes
      - nodes
      - nodes/proxy
    verbs: ["get", "list", "watch"]

 

즉, 여기서 확인할 수 있는 핵심은

Role은 특정 Namespace 안의 리소스만 제어할 수 있고,

PV처럼 클러스터 전체에 속하는 리소스는 ClusterRole을 별도로 만들어야 접근이 가능하다는 점입니다.


11. 전체 구성 검증

 

모든 설치가 완료된 후 최종 상태를 확인합니다.

# 전체 Pod 상태
kubectl get pods -n rook-ceph

# Ceph 클러스터 전체 상태
kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph status

# OSD 상태
kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph osd status

# 스토리지 용량
kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph df

 

정상적인 경우 ceph df 결과는 아래처럼 나옵니다.

--- RAW STORAGE ---
CLASS  SIZE     AVAIL    USED     RAW USED  %RAW USED
hdd    300 GiB  299 GiB  xxx MiB   xxx MiB       0.xx
TOTAL  300 GiB  299 GiB  xxx MiB   xxx MiB       0.xx

--- POOLS ---
POOL              ID  PGS  STORED  OBJECTS  USED  %USED
replicapool        1   32    ...      ...    ...    ...
myfs-replicated    2   32    ...      ...    ...    ...
.rgw.root          3    8    ...      ...    ...    ...

 

즉, 여기서 확인할 수 있는 핵심은

ceph statusHEALTH_OK각 Pool이 정상적으로 등록된 것이 확인되면 스토리지 구축이 완료된 것이라는 점입니다.


📝 마무리

 

이번 글에서는 Kubernetes 위에서

Rook-Ceph를 활용해 분산 스토리지를 직접 구축하는 과정을 순서대로 정리해 봤습니다.

 

정리하면,

  • Rook Operator가 Ceph 컴포넌트를 Kubernetes 리소스로 관리
  • Block(RBD) → 단일 Pod 전용 RWO 스토리지
  • CephFS → 여러 Pod가 동시에 마운트 가능한 RWX 스토리지
  • Object Store(RGW) → S3 호환 API로 접근하는 오브젝트 스토리지

라고 볼 수 있습니다.

 

특히 이번 구축에서 주의해야 할 포인트는

  • Operator Running 확인 후 → Cluster 적용
  • HEALTH_OK 확인 후 → Object Store 적용
  • RGW Running 확인 후 → S3 유저 생성

이 3가지 대기 구간을 반드시 지켜야 한다는 점이었습니다.

 

다음 편에서는

이번에 구축한 Ceph Object Store를 Spark + Delta Lake 연동에서 실제로 사용하는 방법을 다뤄보겠습니다. 🙌

 

궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면 언제든지 편하게 말씀해 주세요! 😊

728x90
반응형