Helm 으로 Rook-Ceph 세우기 — 블록·파일·오브젝트와 장애
CephFS 공유 볼륨과 RGW 버킷 — 이레이저 코딩의 용량까지
목표
같은 Ceph 클러스터에서 블록 말고 두 가지를 더 냅니다. 여러 노드가 함께 마운트하는 공유 파일시스템(CephFS)과, S3 API 로 쓰는 오브젝트 저장소(RGW)입니다. 둘 다 1실습에서 비워 둔 values 목록을 helm upgrade 로 채워서 켜고, 오브젝트 저장소의 데이터 풀은 이레이저 코딩 2+1 로 만들어 복제 3벌과 가용 용량을 견줍니다.
왜 중요한가
쿠버네티스에서 "여러 파드가 같은 파일을 본다" 는 요구(업로드 디렉터리, 모델 가중치 공유)는 블록 볼륨으로는 풀 수 없습니다. 블록 장치는 한 노드에만 붙기 때문입니다. CephFS 는 파일 단위의 잠금을 MDS 가 맡아 여러 노드의 동시 마운트를 허락합니다. 오브젝트 저장소는 요즘 애플리케이션의 기본 저장소입니다. ObjectBucketClaim 은 PVC 가 볼륨을 받는 방식 그대로 버킷과 키를 받아, 앱이 버킷을 "요청" 하게 해 줍니다. 그리고 용량은 복제 방식이 정합니다. 같은 51GiB 에서 3벌 복제와 2+1 이레이저 코딩이 내는 가용 용량은 두 배 차이가 납니다 — 대신 무엇을 내주는지를 함께 알아야 고를 수 있습니다.
단계
/root/rook/cluster-values.yaml(1실습에서 만든 values)로 Rook 을 다시 세우세요. 1실습과 같은 차트 셋을 같은 판으로 네임스페이스rook-ceph에 설치합니다 —rook-ceph(rook-release/rook-cephv1.21.1),ceph-csi-drivers(ceph-csi-operator/ceph-csi-drivers1.1.0, Rook 이 권하는 values 를/root/rook/csi-drivers-values.yaml로 받아서),rook-ceph-cluster(rook-release/rook-ceph-clusterv1.21.1,-f /root/rook/cluster-values.yaml). 세 릴리스가 deployed 이고 CephCluster 가 만들어져야 합니다.- CephCluster 가 Ready 가 될 때까지 기다린 뒤, 1실습처럼 커널(6.8) 때문에 생기는 AUTH_INSECURE_ 경고 셋만
ceph health mute <코드> --sticky로 묵음 처리하세요. Ceph 가 HEALTH_OK 이고, mon 셋이 정족수를 이루고, OSD 셋이 up·in 이어야 합니다. /root/rook/fs-values.yaml에cephFileSystems를 채우세요 — 이름ceph-filesystem, 메타데이터·데이터 풀(data0) 3벌, MDS 활성 1개 + 대기(activeStandby), MDS 메모리 한도 1Gi 이하, MDS 를role=storage노드에만 두는 placement, StorageClassceph-filesystem. 그리고-f /root/rook/cluster-values.yaml -f /root/rook/fs-values.yaml로 릴리스를 helm upgrade 합니다. CephFilesystem 이 Ready 이고 MDS 둘이 떠야 합니다.- 네임스페이스
fs-demo에 StorageClassceph-filesystem의 ReadWriteMany 1Gi PVCshared를 만들고, 파드a(controlplane)와b(node01)가 둘 다/shared에 붙인 채 떠 있게 하세요. 각 파드는 자기 이름의 파일(/shared/a.txt·/shared/b.txt)에 자기 노드 이름을 씁니다. - 파드
a에서/shared/b.txt를, 파드b에서/shared/a.txt를 읽어/root/rook/rwx.txt에a-sees-b=<읽은 값>·b-sees-a=<읽은 값>두 줄로 적으세요. 서로 다른 노드에서 쓴 것을 서로 읽었다면 값은 상대의 노드 이름입니다. /root/rook/object-values.yaml에cephObjectStores를 채우세요 — 이름ceph-objectstore, 메타데이터 풀 3벌, 데이터 풀은 이레이저 코딩 2+1(dataChunks 2·codingChunks 1), RGW 1개·메모리 한도 1Gi 이하·role=storage노드에만, 버킷용 StorageClassceph-bucket. 그리고 세 values(cluster·fs·object)로 다시 helm upgrade 합니다. CephObjectStore 가 Ready 여야 합니다.- 네임스페이스
s3-demo에 StorageClassceph-bucket의 ObjectBucketClaimphotos(generateBucketName: photos)를 만드세요. OBC 가 Bound 이고, 같은 이름의 ConfigMap(버킷 이름·주소)과 Secret(키)이 생겨야 합니다. - controlplane 에 파드
s3cli(이미지public.ecr.aws/aws-cli/aws-cli:2.31.3)를 띄워 ConfigMap·Secretphotos를 환경 변수로 받게 하고, 그 안에서 무작위 8MiB 파일을 버킷에 올렸다가 다른 이름으로 내려받으세요. 올린 파일과 받은 파일의 sha256 을/root/rook/s3.txt에put=<sha>·get=<sha>두 줄로 적습니다. - toolbox 의
ceph df에서 풀ceph-blockpool(복제 3벌)과 RGW 데이터 풀ceph-objectstore.rgw.buckets.data(이레이저 코딩 2+1)의 MAX AVAIL 을 GiB 로 읽어/root/rook/ec.txt에rep3_gib=·ec21_gib=·ratio=(ec21 ÷ rep3, 소수 둘째 자리) 세 줄로 적으세요.
참고
- 이 터미널은 controlplane 입니다. 1실습에서 만든 values 가
/root/rook/cluster-values.yaml에 들어 있습니다. - MDS 둘과 RGW 하나가 늘어나면 스토리지 노드(4GiB)의 메모리가 빠듯해집니다.
kubectl top은 없으니ssh node01 free -m으로 보세요. - 처음 설치에 시간이 들어 60분을 넘길 수 있습니다. 화면의 +시간으로 연장하세요. 세션이 끝나면 VM 과 디스크가 함께 사라집니다.
- 흔한 실수: 오브젝트 저장소를 켜는 upgrade 에서 fs-values 를 빼는 것. Helm 은 그것을 '파일시스템을 지워라' 로 읽습니다.
1실습의 values 로 Ceph 를 다시 설치한다
/root/rook/cluster-values.yaml(1실습에서 만든 values)로 Rook 을 다시 세우세요. 1실습과 같은 차트 셋을 같은 판으로 네임스페이스 rook-ceph 에 설치합니다 — rook-ceph(rook-release/rook-ceph v1.21.1), ceph-csi-drivers(ceph-csi-operator/ceph-csi-drivers 1.1.0, Rook 이 권하는 values 를 /root/rook/csi-drivers-values.yaml 로 받아서), rook-ceph-cluster(rook-release/rook-ceph-cluster v1.21.1, -f /root/rook/cluster-values.yaml). 세 릴리스가 deployed 이고 CephCluster 가 만들어져야 합니다.
1실습의 2·3·5단계와 같습니다. 오퍼레이터 차트가 CRD 를 함께 깔기 때문에 그것을 먼저 설치하고, CRD 가 등록될 때까지 기다린 뒤(kubectl wait --for=condition=Established crd/drivers.csi.ceph.io) 드라이버 차트를 설치합니다. Rook 의 CSI values 주소는 {CSI_VALUES_URL} 입니다. 설치 명령은 금방 끝나고, 클러스터가 서는 것은 다음 단계에서 기다립니다.
HEALTH_OK 까지 기다린다
CephCluster 가 Ready 가 될 때까지 기다린 뒤, 1실습처럼 커널(6.8) 때문에 생기는 AUTH_INSECURE_ 경고 셋만 ceph health mute <코드> --sticky 로 묵음 처리하세요. Ceph 가 HEALTH_OK 이고, mon 셋이 정족수를 이루고, OSD 셋이 up·in 이어야 합니다.
처음 설치는 이미지와 OSD 준비 때문에 수 분이 걸립니다 — kubectl -n rook-ceph get pods -w. toolbox 의 ceph health detail 이 남은 경고를 말합니다. AUTH_INSECURE_ 가 아닌 경고는 묵음 처리하지 말고 기다리거나 원인을 고치세요.
helm upgrade 로 CephFS 를 켠다
/root/rook/fs-values.yaml 에 cephFileSystems 를 채우세요 — 이름 ceph-filesystem, 메타데이터·데이터 풀(data0) 3벌, MDS 활성 1개 + 대기(activeStandby), MDS 메모리 한도 1Gi 이하, MDS 를 role=storage 노드에만 두는 placement, StorageClass ceph-filesystem. 그리고 -f /root/rook/cluster-values.yaml -f /root/rook/fs-values.yaml 로 릴리스를 helm upgrade 합니다. CephFilesystem 이 Ready 이고 MDS 둘이 떠야 합니다.
values 파일을 여러 개 주면 뒤의 것이 앞의 것을 덮습니다. 1실습의 파일은 cephFileSystems: [] 였으니 뒤에 둔 fs-values 가 그 목록을 바꿉니다. MDS 는 CephCluster 의 placement.all 을 따르지 않습니다 — CephFilesystem 이 따로 만들기 때문입니다. StorageClass 의 parameters 에는 CSI 가 쓸 Secret 이름이 들어가야 합니다(차트 기본값을 helm show values 로 보세요). ceph fs status 로 활성·대기 MDS 를 확인합니다.
두 노드의 파드가 한 볼륨에 함께 쓴다
네임스페이스 fs-demo 에 StorageClass ceph-filesystem 의 ReadWriteMany 1Gi PVC shared 를 만들고, 파드 a(controlplane)와 b(node01)가 둘 다 /shared 에 붙인 채 떠 있게 하세요. 각 파드는 자기 이름의 파일(/shared/a.txt·/shared/b.txt)에 자기 노드 이름을 씁니다.
RBD 와 달리 CephFS 는 여러 노드가 동시에 마운트할 수 있습니다 — accessModes 에 ReadWriteMany 를 적습니다. 노드 이름은 Downward API(spec.nodeName → 환경 변수)로 파드에 넘길 수 있습니다. 이미지는 registry.k8s.io/e2e-test-images/busybox:1.36.1-1 입니다.
서로의 파일을 읽는다
파드 a 에서 /shared/b.txt 를, 파드 b 에서 /shared/a.txt 를 읽어 /root/rook/rwx.txt 에 a-sees-b=<읽은 값>·b-sees-a=<읽은 값> 두 줄로 적으세요. 서로 다른 노드에서 쓴 것을 서로 읽었다면 값은 상대의 노드 이름입니다.
kubectl -n fs-demo exec a -- cat /shared/b.txt 처럼 읽습니다. 같은 파일을 두 노드가 동시에 고치는 일은 이 실습에서 하지 않습니다 — CephFS 는 MDS 가 잠금(capability)을 나눠 주어 일관성을 지키지만, 응용이 같은 파일을 동시에 쓰면 그 순서는 응용이 책임집니다.
helm upgrade 로 오브젝트 저장소(RGW)를 켠다
/root/rook/object-values.yaml 에 cephObjectStores 를 채우세요 — 이름 ceph-objectstore, 메타데이터 풀 3벌, 데이터 풀은 이레이저 코딩 2+1(dataChunks 2·codingChunks 1), RGW 1개·메모리 한도 1Gi 이하·role=storage 노드에만, 버킷용 StorageClass ceph-bucket. 그리고 세 values(cluster·fs·object)로 다시 helm upgrade 합니다. CephObjectStore 가 Ready 여야 합니다.
upgrade 에 fs-values 를 빼면 Helm 은 그것을 '파일시스템을 없애라' 로 읽습니다 — 이번 values 가 전부이기 때문입니다. 지금까지 준 파일을 모두 다시 줍니다. 이레이저 코딩 2+1 은 데이터를 두 조각으로 나누고 패리티 한 조각을 더해 세 노드에 둡니다. 노드가 셋이라 그보다 큰 k+m 은 놓을 자리가 없습니다.
ObjectBucketClaim 으로 버킷을 받는다
네임스페이스 s3-demo 에 StorageClass ceph-bucket 의 ObjectBucketClaim photos(generateBucketName: photos)를 만드세요. OBC 가 Bound 이고, 같은 이름의 ConfigMap(버킷 이름·주소)과 Secret(키)이 생겨야 합니다.
OBC 는 PVC 의 버킷판입니다. apiVersion: objectbucket.io/v1alpha1, kind: ObjectBucketClaim. Bound 가 되면 Rook 이 RGW 에 사용자와 버킷을 만들고, 앱이 쓸 값을 ConfigMap photos(BUCKET_NAME·BUCKET_HOST·BUCKET_PORT)와 Secret photos(AWS_ACCESS_KEY_ID·AWS_SECRET_ACCESS_KEY)로 내 줍니다. Secret 의 값은 화면에 찍지 마세요 — 다음 단계에서 파드가 환경 변수로 받습니다.
S3 클라이언트로 올리고 내려받는다
controlplane 에 파드 s3cli(이미지 public.ecr.aws/aws-cli/aws-cli:2.31.3)를 띄워 ConfigMap·Secret photos 를 환경 변수로 받게 하고, 그 안에서 무작위 8MiB 파일을 버킷에 올렸다가 다른 이름으로 내려받으세요. 올린 파일과 받은 파일의 sha256 을 /root/rook/s3.txt 에 put=<sha>·get=<sha> 두 줄로 적습니다.
aws CLI 는 AWS_ACCESS_KEY_ID·AWS_SECRET_ACCESS_KEY 를 환경 변수에서 그대로 읽습니다 — Secret 을 envFrom 으로 붙이면 됩니다. 엔드포인트는 --endpoint-url http://$BUCKET_HOST:$BUCKET_PORT 입니다. 이 이미지는 진입점이 aws 라서 계속 떠 있게 하려면 command: [sleep, …] 로 바꿉니다. 클러스터 안 주소(*.svc)라 바깥 인터넷이 필요 없습니다.
복제 3벌과 이레이저 코딩 2+1 의 가용 용량을 견준다
toolbox 의 ceph df 에서 풀 ceph-blockpool(복제 3벌)과 RGW 데이터 풀 ceph-objectstore.rgw.buckets.data(이레이저 코딩 2+1)의 MAX AVAIL 을 GiB 로 읽어 /root/rook/ec.txt 에 rep3_gib=·ec21_gib=·ratio=(ec21 ÷ rep3, 소수 둘째 자리) 세 줄로 적으세요.
같은 디스크 셋에서 복제 3벌은 원시의 1/3, 2+1 은 2/3 를 쓸 수 있으므로 비율은 2 근처입니다. 대신 이레이저 코딩은 쓰기마다 조각을 계산하고, 작은 객체·덮어쓰기에 약하며, 노드 하나를 잃으면 읽을 때 조각을 다시 맞춥니다. 그래서 RGW 데이터처럼 크고 한 번 쓰는 것에 맞고, RBD 처럼 작은 덮어쓰기가 많은 볼륨에는 잘 쓰지 않습니다.