Helm 으로 Rook-Ceph 세우기 — 블록·파일·오브젝트와 장애
Rook 과 Ceph — 빈 디스크가 PVC 가 되기까지
한 줄 요약
Rook 은 쿠버네티스 안에 Ceph 를 세우고 돌보는 오퍼레이터입니다. 우리는 "어느 디스크를, 몇 벌로, 메모리를 얼마나" 만 values 에 적고, 데몬을 띄우고 고치는 일은 오퍼레이터가 합니다.
왜 이게 필요했나
쿠버네티스는 PVC 를 내 주는 방법(CSI)은 정해 두었지만 저장소 자체는 갖고 있지 않습니다. 클라우드에서는 EBS 같은 디스크 서비스가 그 자리를 채웁니다. 온프렘이나 집 서버에는 그런 서비스가 없어서, 노드의 로컬 디스크(local-path)를 쓰거나 NAS 의 NFS 를 붙입니다. 로컬 디스크는 노드가 죽으면 데이터도 함께 갇히고, NFS 는 NAS 한 대가 모든 볼륨의 단일 장애점이 됩니다.
Ceph 는 이 문제를 반대쪽에서 풉니다. 여러 노드의 디스크를 하나의 저장소로 묶고, 데이터를 여러 노드에 나눠 복제합니다. 노드 하나가 꺼져도 다른 노드의 사본으로 계속 읽고 씁니다. 대신 데몬이 여럿이고(mon·mgr·osd·mds·rgw) 설정이 많아서 손으로 운영하기 어렵습니다. Rook 은 그 운영을 쿠버네티스 방식으로 바꿉니다 — CephCluster 라는 객체 하나를 선언하면 오퍼레이터가 데몬을 파드로 띄우고, 죽으면 다시 띄우고, 판을 올릴 때 순서를 지킵니다.
어떻게 동작하나
차트 셋. Rook 은 Helm 차트를 나눠 냅니다. rook-ceph 는 오퍼레이터와 CRD 를 설치합니다. rook-ceph-cluster 는 CephCluster 와 풀·StorageClass 같은 "무엇을 만들지" 를 담습니다. 오퍼레이터는 클러스터에 하나, 클러스터 차트는 Ceph 클러스터마다 하나라서 나뉘어 있습니다. 그리고 볼륨을 노드에 붙이는 CSI 드라이버는 셋째 차트 ceph-csi-drivers 가 맡습니다(아래에서 다시 봅니다).
데몬의 역할. mon 은 클러스터 지도(누가 살아 있고 데이터가 어디 있는지)를 다수결로 지킵니다. 셋을 두면 하나가 죽어도 둘이 과반이라 계속 결정합니다. mgr 은 상태 집계와 대시보드를 맡습니다. OSD 는 디스크 하나에 하나씩 떠서 실제 데이터를 저장합니다. 데이터는 풀(pool)에 들어가고, 풀은 배치 그룹(PG)으로 잘게 나뉘며, CRUSH 라는 규칙이 PG 를 어느 OSD 에 둘지 계산합니다. failureDomain: host 는 같은 PG 의 사본을 서로 다른 노드에 두라는 뜻입니다.
디스크를 고르는 법. OSD 는 파일시스템도 파티션도 없는 디스크만 가져갑니다. 무언가 적힌 디스크는 남의 데이터일 수 있어서 건너뜁니다. 그래서 useAllDevices: true 로도 빈 디스크만 잡히지만, 운영에서는 이름으로 못박는 편이 안전합니다. vdd 같은 이름은 디스크가 붙는 순서로 정해지므로 바뀔 수 있고, /dev/disk/by-id/… 처럼 일련번호에서 나온 이름은 바뀌지 않습니다.
용량 계산. 이 실습의 원시 용량은 17GiB × 3 = 51GiB 입니다. 풀이 3벌 복제라 같은 데이터가 세 번 저장되므로 쓸 수 있는 것은 3분의 1인 17GiB 가 출발점입니다. 여기서 두 가지가 더 빠집니다. Ceph 는 OSD 가 95%(full_ratio)에 닿으면 쓰기를 막으므로 그만큼을 남겨 두고 계산합니다. 그리고 BlueStore 가 OSD 마다 쓰는 메타데이터 공간이 있습니다. ceph df 의 MAX AVAIL 은 이것을 다 뺀 "지금 이 풀에 더 쓸 수 있는 양" 이고, 실습에서 직접 잽니다.
CSI 는 세 번째 차트입니다. Rook v1.20 부터 볼륨을 노드에 붙이는 CSI 드라이버는 Rook 오퍼레이터가 직접 만들지 않고, ceph-csi-operator 가 Driver 라는 객체를 보고 만듭니다. 오퍼레이터 차트는 그 ceph-csi-operator 만 함께 깔고, 실제 드라이버(컨트롤러 Deployment 와 노드마다 뜨는 DaemonSet)는 ceph-csi-drivers 차트가 선언합니다. 이 차트를 빠뜨리면 Ceph 는 HEALTH_OK 인데 PVC 는 Pending 에서 움직이지 않습니다 — 저장소는 멀쩡하고 그것을 쿠버네티스에 잇는 다리만 없는 상태라, 오류 메시지가 저장소 쪽에서는 하나도 나오지 않습니다. 이 실습은 그 차트를 Rook 이 권하는 values 로 설치합니다. 드라이버 이름 앞에 rook-ceph. 을 붙여 StorageClass 의 provisioner 와 맞추는 값입니다.
현장에서 만나는 모습
작은 노드에 Rook 을 기본값으로 올리면 가장 먼저 만나는 것이 메모리입니다. 차트 기본값은 OSD 하나가 메모리 4GiB 를 요청하고, Ceph 의 osd_memory_target 도 4GiB 입니다. 요청이 노드보다 크면 파드가 Pending 에 머뭅니다. 요청만 낮추고 목표를 그대로 두면 더 나쁩니다 — OSD 는 목표를 향해 캐시를 키우다 컨테이너 한도에 걸려 OOMKilled 되고, 다시 떠서 또 키우기를 반복합니다. 그래서 한도와 목표를 함께 낮춥니다. 이 실습은 OSD 한도 1536Mi, 목표 1GiB, mon 한도 768Mi, mgr 은 하나로 줄였습니다. 이것은 Rook 이 권하는 최소 한도(mon 1024MB·mgr 512MB·OSD 2048MB, CephCluster CRD 문서)보다 낮은 값이라 오퍼레이터 로그에 경고가 남고, 캐시가 작아 읽기 성능도 떨어집니다. 4GiB 노드에 데몬 셋을 함께 올리려고 감수한 값이며, 실제 운영 노드에서는 되돌려야 합니다.
values 의 목록이 합쳐지지 않는다는 것도 자주 밟습니다. Helm 은 맵은 기본값과 합치지만 목록은 통째로 바꿉니다. 그래서 cephFileSystems 를 values 에서 빼면 "없음" 이 아니라 "기본값 그대로" 가 되어, 차트가 파일시스템과 오브젝트 저장소까지 만들어 MDS 와 RGW 가 메모리를 먹습니다. 쓰지 않을 것은 [] 로 비워야 합니다.
다음 실습에서 할 것
세 스토리지 노드에서 빈 디스크를 찾고, 오퍼레이터 차트와 CSI 드라이버 차트를 판을 못박아 설치합니다. 실습 VM 에 맞게 values 를 쓴 뒤 클러스터 차트를 설치하고, 커널 때문에 생기는 경고의 이유를 읽고 HEALTH_OK 로 만듭니다. OSD 셋이 각 노드의 빈 디스크를 잡았는지와 원시·가용 용량을 숫자로 확인하고, 마지막으로 RBD 볼륨에 한 노드에서 쓰고 다른 노드에서 읽습니다.