CSI 스토리지
CSI 스토리지 (Container Storage Interface)
이 페이지는 Nomad의 스토리지 플러그인 지원에 대한 개념 정보를 제공해요. 이 지원은 Container Storage Interface (CSI)를 따르는 외부 스토리지 볼륨(AWS Elastic Block Storage (EBS) 볼륨, Google Cloud Platform (GCP) 영구 디스크, Ceph 포함)을 사용하는 태스크 스케줄링을 가능하게 해줘요. 컨트롤러(controller), 노드(node), 모놀리스(monolith) 스토리지 플러그인뿐 아니라 스토리지 볼륨 수명주기와 상태에 대해 알아봐요.
출처: 문서
본문
소개 (Introduction)
모든 스토리지 벤더는 고유한 API와 워크플로를 가지고 있어요. 업계 표준인 Container Storage Interface 스펙은 이러한 API를 스토리지 벤더와 컨테이너 오케스트레이터 모두에 대해 중립적인 방식으로 통합해요. 각 스토리지 제공자는 자체 CSI 플러그인을 구축할 수 있어요. 작업은 AWS Elastic Block Storage (EBS) 볼륨, GCP 영구 디스크, Ceph에서 스토리지 볼륨을 요청할 수 있어요. Nomad 스케줄러는 CSI 플러그인이 만든 볼륨을 인지하고, 주어진 Nomad 클라이언트 노드에서 볼륨의 가용성에 따라 워크로드를 스케줄링해요.
사용 가능한 CSI 플러그인 목록은 Kubernetes CSI 문서에서 찾을 수 있어요. 스펙을 준수하는 플러그인은 Nomad에서 작동해야 해요. 하지만 플러그인 벤더가 Kubernetes API 호출을 하도록 구현했거나, 다른 방식으로 CSI 스펙을 준수하지 않을 수도 있어요. 그러한 상황에서는 플러그인이 Nomad 환경에서 올바르게 작동하지 않을 수 있어요. 프로덕션에 배포하기 전에 플러그인의 Nomad 호환성을 검증해야 해요.
CSI 플러그인 태스크는 csi_plugin 블록이 필요해요.
csi_plugin {
id = "csi-hostpath"
type = "monolith"
mount_dir = "/csi"
stage_publish_base_dir = "/local/csi"
}
CSI 플러그인에는 세 가지 유형이 있어요. 컨트롤러 플러그인(Controller Plugins)은 스토리지 제공자의 API와 통신해요. 예를 들어 AWS EBS 볼륨이 필요한 작업의 경우 Nomad는 컨트롤러 플러그인에 볼륨을 클라이언트 노드에 "게시(publish)"해야 한다고 알리고, 컨트롤러는 AWS에 API 호출을 해 EBS 볼륨을 올바른 EC2 인스턴스에 연결해요. 노드 플러그인(Node Plugins)은 마운트 포인트 생성 같은 작업을 각 클라이언트 노드에서 수행해요. 모놀리스 플러그인(Monolith Plugins)은 컨트롤러와 노드 역할을 모두 같은 인스턴스에서 수행하는 플러그인이에요. 모든 플러그인 제공자가 컨트롤러를 가지거나 필요로 하는 것은 아니며, 이는 제공자 구현에 따라 달라요.
플러그인은 볼륨을 마운트·언마운트 하지만, 볼륨이 태스크용으로 마운트된 후에는 데이터 경로에 있지 않아요. 플러그인 태스크는 해당 볼륨을 사용하는 태스크가 정지할 때 필요하므로, 볼륨을 사용하는 모든 태스크가 정지될 때까지 플러그인을 Nomad 클라이언트에서 계속 실행해야 해요. nomad node drain 명령어는 플러그인 태스크를 마지막에 정지시켜 이 작업을 자동으로 처리해요.
일반적으로 노드 플러그인은 Nomad system 작업으로 실행해 플러그인이 실행되는 모든 클라이언트에서 볼륨을 마운트할 수 있게 해야 해요. 컨트롤러 플러그인은 스토리지 제공자 API와 통신할 수 있는 곳이면 어디서든 볼륨을 생성·연결할 수 있으므로 보통 service 작업으로 실행할 수 있어요. 고가용성을 위해 항상 컨트롤러 플러그인 할당을 두 개 이상 실행해야 해요.
Nomad는 각 CSI 플러그인 태스크 안에 csi.sock이라는 Unix 도메인 소켓을 노출하고, CSI 스펙이 기대하는 gRPC 프로토콜로 통신해요. mount_dir 필드는 Nomad에 플러그인이 소켓 파일을 기대하는 위치를 알려줘요. 이 소켓의 경로는 컨테이너 안에서 CSI_ENDPOINT 환경 변수로 노출돼요.
일부 플러그인은 stage_publish_base_dir 필드도 요구하는데, 이 필드는 Nomad에 스테이징 및/또는 게시를 위해 볼륨을 마운트하도록 플러그인에 지시할 위치를 알려줘요.
플러그인 수명주기와 상태 (Plugin Lifecycle and State)
CSI 플러그인은 다른 Nomad 작업처럼 건강 상태를 보고해요. 플러그인이 충돌하거나 종료되면 Nomad는 다른 작업에 사용되는 것과 같은 재시작·재스케줄링 로직으로 다시 실행해요. 플러그인이 비정상이면 Nomad는 플러그인이 관리하는 볼륨을 "unscheduable"로 표시해요.
스토리지 플러그인은 볼륨을 요청하는 태스크의 상태를 모니터링할 책임(또는 능력)이 없어요. Nomad는 태스크가 볼륨을 요청할 때 스토리지 플러그인에 마운트·게시 요청을 보내고, 태스크가 정지할 때 언마운트·언게시 요청을 보내요.
동적 플러그인 레지스트리는 상태를 Nomad 클라이언트에 영속화해, 클라이언트 재시작 후 스토리지를 방해하지 않고 플러그인 작업에 대한 볼륨 관리자를 복원할 수 있게 해줘요.
볼륨 수명주기 (Volume Lifecycle)
Nomad 스케줄러는 주어진 클라이언트가 그 볼륨에 대한 노드 플러그인을 보유하고 있는지에 따라 해당 클라이언트가 할당을 실행할 수 있는지 결정해요. 하지만 태스크가 볼륨을 사용하기 전에 클라이언트가 할당을 위해 볼륨을 "요청(claim)"해야 해요. 클라이언트는 서버에 RPC 호출을 하고 응답을 기다려요. 볼륨이 요청되고 준비될 때까지 할당의 태스크는 시작되지 않아요.
볼륨의 플러그인이 컨트롤러를 요구하면 서버는 해당 컨트롤러가 실행 중인 모든 Nomad 클라이언트에 RPC를 보내요. Nomad 클라이언트는 이 요청을 컨트롤러 플러그인의 gRPC 소켓을 통해 전달해요. 컨트롤러 플러그인은 요청된 볼륨을 필요로 하는 노드에서 사용 가능하게 만들어요.
컨트롤러가 완료되면(또는 컨트롤러가 필요 없는 경우) 서버는 볼륨의 요청 수를 증가시키고 클라이언트에 반환해요. 이 수는 Nomad의 상태 저장소를 통과하므로 Nomad는 어떤 볼륨이 스케줄링에 사용 가능한지 일관된 시각을 가져요.
그런 다음 클라이언트는 해당 클라이언트에서 실행 중인 노드 플러그인에 RPC 호출을 하고, 노드 플러그인은 볼륨을 Nomad 데이터 디렉터리의 스테이징 영역에 마운트해요. Nomad는 이 스테이징된 디렉터리를 볼륨을 마운트하는 각 태스크에 바인드 마운트해요.
볼륨을 요청한 태스크가 terminal이 되면 이 주기는 역순으로 진행돼요. 클라이언트는 노드 플러그인에 "unpublish" RPC를 보내 볼륨을 로컬로 해제해요. 노드 플러그인은 할당에서 바인드 마운트를 언마운트하고, 다른 태스크가 사용하고 있지 않다면 플러그인에서 볼륨을 언마운트해요. 그런 다음 클라이언트는 서버에 "unpublish" RPC를 보내고, 서버는 이를 컨트롤러 플러그인으로 전달하며(있는 경우) 볼륨의 요청 수를 감소시켜요. 이 시점에서 볼륨의 요청 용량은 스케줄링을 위해 해제돼요.