RKE2 GPU Cluster¶
이 문서는 tirosh-home site에서 GPU node를 RKE2 기반 workload cluster로 설치하고, NVIDIA GPU Operator로 GPU resource를 Kubernetes에 노출하는 흐름을 설명합니다.
RKE2를 선택한 배경은 ADR 0001을 참고합니다. GPU Operator를 별도 계층으로 두는 배경은 ADR 0002를 참고합니다.
1. 전체 흐름¶
1-1. 설치와 GPU enablement 분리¶
GPU cluster 구성은 RKE2 설치와 GPU enablement를 분리해서 봅니다.
1-2. 구성 흐름¶
host static IP 적용
|
v
inventory 렌더링
|
v
RKE2 server 설치
|
v
RKE2 agent join
|
v
kubeconfig merge
|
v
NVIDIA GPU Operator 적용
|
v
GPU runtime 검증
1-3. 기본 실행 순서¶
기본 실행 순서는 아래와 같습니다.
make site/inventory/render SITE=tirosh-home
make validate SITE=tirosh-home
make rke2/server/install SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
make rke2/agent/install SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
make rke2/status SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
make rke2/kubeconfig/merge SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
make rke2/gpu/operator/apply SITE=tirosh-home
make rke2/gpu/benchmark SITE=tirosh-home
1-4. Ready와 GPU resource의 차이¶
RKE2 node가 Ready가 되는 것과 GPU가 nvidia.com/gpu resource로 보이는 것은 다른 단계입니다. node가 Ready인데 GPU가 n/a라면 RKE2 join은 성공했고 GPU driver/runtime/device plugin 계층을 확인해야 합니다.
2. site 설정과 inventory¶
RKE2 관련 설정도 실행 기본값, host catalog, cluster 변수로 나눕니다.
sites/tirosh-home/
profile.toml Make target이 사용할 RKE2 cluster profile
inventory.toml 사람이 관리하는 host catalog source
inventory.yml inventory.toml에서 렌더링한 Ansible inventory
rke2/
vars.yml RKE2 cluster 공통 설정
gpu-operator.helmchart.yaml
nbody-gpu-benchmark.yaml
2-1. profile 기본값¶
profile.toml의 rke2.default_cluster는 기본 cluster profile 이름입니다. 현재 기본값은 gpu입니다.
[rke2]
default_cluster = "gpu"
[rke2.gpu]
vars = "sites/tirosh-home/rke2/vars.yml"
gpu_operator_manifest = "sites/tirosh-home/rke2/gpu-operator.helmchart.yaml"
gpu_benchmark_manifest = "sites/tirosh-home/rke2/nbody-gpu-benchmark.yaml"
targets = ["rke2"]
server_targets = ["rke2_servers"]
agent_targets = ["rke2_agents"]
context_name = "tirosh-home/rke2/gpu"
kubeconfig_path = "~/.kube/tirosh-home-rke2.yml"
Make target은 이 값을 읽어서 Ansible --limit, kubeconfig path, Kubernetes context, GPU Operator manifest 경로로 변환합니다.
다른 cluster profile을 쓰려면 실행 시 지정합니다.
make rke2/status SITE=tirosh-home RKE2_CLUSTER=gpu
2-2. inventory 역할¶
host 자체의 SSH 정보와 RKE2 node 역할은 inventory.toml에 둡니다.
[baremetals.gpu-01]
ansible_host = "172.31.0.20"
ansible_user = "tirosh"
ansible_become_user = "root"
rke2_node_labels = [
"node.tirosh.dev/role=gpu",
"node.tirosh.dev/site=tirosh-home",
"node.tirosh.dev/model=dgx-spark",
]
workloads = ["gpu"]
rke2 = { cluster = "gpu", role = "server" }
[baremetals.gpu-02]
ansible_host = "172.31.0.21"
ansible_user = "tirosh"
ansible_become_user = "root"
rke2_node_labels = [
"node.tirosh.dev/role=gpu",
"node.tirosh.dev/site=tirosh-home",
"node.tirosh.dev/model=dgx-spark",
]
workloads = ["gpu"]
rke2 = { cluster = "gpu", role = "agent" }
workloads = ["gpu"]는 host를 gpu_nodes group에 넣습니다. rke2.role = "server"는 rke2_servers, rke2.role = "agent"는 rke2_agents group에 넣습니다.
수정 후 Ansible inventory를 다시 렌더링합니다.
make site/inventory/render SITE=tirosh-home
렌더링 후 inventory.yml에는 아래 group이 보여야 합니다.
rke2_servers:
hosts:
gpu-01: {}
rke2_agents:
hosts:
gpu-02: {}
gpu_nodes:
hosts:
gpu-01: {}
gpu-02: {}
rke2:
children:
rke2_servers: {}
rke2_agents: {}
2-3. static IP 전제¶
RKE2 server/agent join은 node IP가 바뀌면 운영이 불안정해집니다. 각 host의 static IP를 먼저 적용하고, inventory.toml의 ansible_host가 그 IP를 가리키게 맞춥니다.
static IP 적용 방법은 Host Network를 참고합니다.
3. RKE2 cluster 설정¶
RKE2 cluster 공통 설정은 sites/<site_id>/rke2/vars.yml에서 관리합니다.
rke2_cluster_config:
write-kubeconfig-mode: "0600"
cni: cilium
tls-san:
- gpu-01
- 172.31.0.20
3-1. CNI¶
현재 초기 설정은 Cilium을 bundled CNI로 선택합니다. Hubble, kube-proxy replacement, WireGuard 같은 고급 기능은 cluster bootstrap 이후 별도 단계에서 켭니다.
RKE2 설치 전에 CNI 방향을 정해야 합니다. 운영 중 CNI 변경은 cluster networking 전체에 영향을 주므로 별도 migration 작업으로 다룹니다.
3-2. TLS SAN¶
tls-san에는 Kubernetes API에 접근할 주소를 넣습니다. 내부망과 WireGuard로 172.31.0.0/24 대역에 접근할 수 있다면 내부 IP 중심으로 두는 편이 단순합니다.
외부 DNS나 VIP로 API에 접근할 계획이 있으면 해당 이름을 tls-san에 추가합니다. kubeconfig의 server 주소가 인증서 SAN에 없으면 TLS 검증 오류가 발생합니다.
3-3. node label¶
node별 label은 inventory host 변수로 둡니다.
rke2_node_labels = [
"node.tirosh.dev/role=gpu",
"node.tirosh.dev/site=tirosh-home",
"node.tirosh.dev/model=dgx-spark",
]
장비 모델명은 Kubernetes node label로 관리하고, 운영 alias는 gpu-01처럼 제조사에 덜 묶인 이름을 사용합니다.
4. RKE2 설치¶
RKE2 server와 agent는 같은 install playbook을 사용하지만, Make target에서 Ansible limit을 다르게 넘깁니다.
4-1. server 설치¶
첫 server node 또는 server group을 설치합니다.
make rke2/server/install SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
특정 server만 설치할 수도 있습니다.
make rke2/server/install SITE=tirosh-home RKE2_SERVER_TARGETS=gpu-01 ASK_PASS=true ASK_BECOME_PASS=true
server 설치 후 상태를 확인합니다.
make rke2/status SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
4-2. agent 설치¶
agent node가 준비되면 agent group을 join시킵니다.
make rke2/agent/install SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
특정 agent 몇 대만 추가할 때는 target을 좁힙니다.
make rke2/agent/install SITE=tirosh-home RKE2_AGENT_TARGETS=gpu-02,gpu-03 ASK_PASS=true ASK_BECOME_PASS=true
agent 설치는 기존 cluster에 join해야 하므로 server 쪽의 node token을 먼저 가져와 join config를 만듭니다.
make rke2/agent/install
|
v
inventory.yml에서 rke2_servers / rke2_agents group 확인
|
v
rke2_servers의 첫 번째 host를 bootstrap server로 선택
|
v
bootstrap server에서 node token 읽기
/var/lib/rancher/rke2/server/node-token
|
v
agent host에 /etc/rancher/rke2/config.yaml 작성
server: https://<bootstrap-server-ip>:9345
token: <node-token>
node-name: <inventory host name>
node-ip: <ansible_host>
|
v
rke2-agent 설치 및 service start
즉 gpu-02, gpu-03을 agent로 설치하려면 gpu-01 같은 RKE2 server가 먼저 정상 설치되어 있어야 합니다.
4-3. 설치 후 상태 확인¶
Ansible 기준 상태를 확인합니다.
make rke2/status SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
로컬 kubeconfig merge 후에는 Kubernetes API 기준으로 확인합니다.
kubectl --context tirosh-home/rke2/gpu get nodes -o wide
kubectl --context tirosh-home/rke2/gpu get pods -A -o wide
5. kubeconfig와 context¶
RKE2 설치 직후 kubeconfig는 remote host에만 있습니다. 로컬 kubectl, k9s, Argo CD CLI가 이 cluster를 보려면 kubeconfig를 가져와 ~/.kube/config에 merge해야 합니다.
RKE2 server 설치
|
v
remote kubeconfig 생성
/etc/rancher/rke2/rke2.yaml
|
v
local kubeconfig로 fetch
~/.kube/tirosh-home-rke2.yml
|
v
main kubeconfig에 merge
~/.kube/config
|
v
context 이름으로 접근
tirosh-home/rke2/gpu
5-1. kubeconfig merge¶
설치 후 아래 명령을 실행합니다.
make rke2/kubeconfig/merge SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
merge가 끝나면 context가 보이는지 확인합니다.
kubectl config get-contexts
kubectl --context tirosh-home/rke2/gpu get nodes
5-2. context를 사용하는 명령¶
아래 target과 도구는 모두 profile.toml의 rke2.gpu.context_name을 사용합니다.
make rke2/gpu/operator/applymake rke2/gpu/benchmarkmake argocd/cluster/addk9s --context tirosh-home/rke2/gpu
kubeconfig와 context의 자세한 배경은 k9s를 참고합니다.
6. agent 추가 시나리오¶
이미 gpu-01 server가 동작 중인 RKE2 cluster에 gpu-02, gpu-03을 agent로 추가하는 상황을 예로 듭니다.
6-1. host catalog 수정¶
inventory.toml에 새 host가 agent 역할로 선언되어 있어야 합니다.
[baremetals.gpu-02]
ansible_host = "172.31.0.21"
ansible_user = "tirosh"
ansible_become_user = "root"
workloads = ["gpu"]
rke2 = { cluster = "gpu", role = "agent" }
[baremetals.gpu-03]
ansible_host = "172.31.0.22"
ansible_user = "tirosh"
ansible_become_user = "root"
workloads = ["gpu"]
rke2 = { cluster = "gpu", role = "agent" }
6-2. inventory 렌더링과 검증¶
inventory를 렌더링하고 site 설정을 검증합니다.
make site/inventory/render SITE=tirosh-home
make validate SITE=tirosh-home
server가 먼저 정상인지 확인합니다. agent join은 bootstrap server에서 token을 읽어오므로, server가 준비되지 않았으면 여기서 먼저 해결해야 합니다.
make rke2/status SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
6-3. agent만 설치¶
gpu-02, gpu-03만 agent로 설치합니다.
make rke2/agent/install SITE=tirosh-home RKE2_AGENT_TARGETS=gpu-02,gpu-03 ASK_PASS=true ASK_BECOME_PASS=true
설치가 끝나면 Kubernetes node 목록에서 agent가 Ready 상태인지 확인합니다.
kubectl --context tirosh-home/rke2/gpu get nodes -o wide
일부 agent만 실패하면 성공한 host는 그대로 두고 실패한 host만 다시 실행합니다.
make rke2/agent/install SITE=tirosh-home RKE2_AGENT_TARGETS=gpu-03 ASK_PASS=true ASK_BECOME_PASS=true
7. RKE2 제거와 역할 전환¶
server와 agent 제거는 설치와 마찬가지로 target을 좁혀서 실행합니다.
7-1. agent 제거¶
agent node를 제거할 때는 대상만 좁혀서 실행합니다. 기본 동작은 Kubernetes node drain, RKE2 service 중지, RKE2 uninstall script 실행, Kubernetes node object 삭제입니다.
make rke2/agent/uninstall SITE=tirosh-home RKE2_AGENT_TARGETS=gpu-02 ASK_PASS=true ASK_BECOME_PASS=true
7-2. server 제거¶
server node 제거도 같은 방식으로 실행합니다. 다만 bootstrap server와 마지막 server 제거는 기본적으로 차단됩니다.
make rke2/server/uninstall SITE=tirosh-home RKE2_SERVER_TARGETS=gpu-03 ASK_PASS=true ASK_BECOME_PASS=true
운영 중인 server 제거는 quorum과 API endpoint를 먼저 확인해야 합니다. 의도적으로 bootstrap server나 마지막 server를 제거해야 하는 경우에만 guard를 해제합니다.
make rke2/server/uninstall SITE=tirosh-home RKE2_SERVER_TARGETS=gpu-01 \
ASK_PASS=true ASK_BECOME_PASS=true \
ANSIBLE_ARGS='--extra-vars rke2_uninstall_force_bootstrap_server=true'
drain이나 Kubernetes node object 삭제를 건너뛰어야 하는 복구 상황에서는 아래처럼 override할 수 있습니다.
make rke2/agent/uninstall SITE=tirosh-home RKE2_AGENT_TARGETS=gpu-02 \
ASK_PASS=true ASK_BECOME_PASS=true \
ANSIBLE_ARGS='--extra-vars rke2_uninstall_drain_node=false --extra-vars rke2_uninstall_delete_node=false'
7-3. agent를 server로 전환¶
agent를 server로 전환할 때는 agent를 제거한 뒤 inventory.toml에서 rke2.role을 server로 바꾸고 inventory를 다시 렌더링한 다음 server install을 실행합니다.
make rke2/agent/uninstall SITE=tirosh-home RKE2_AGENT_TARGETS=gpu-02 ASK_PASS=true ASK_BECOME_PASS=true
make site/inventory/render SITE=tirosh-home
make rke2/server/install SITE=tirosh-home RKE2_SERVER_TARGETS=gpu-02 ASK_PASS=true ASK_BECOME_PASS=true
8. GPU 구성¶
Kubernetes node가 Ready여도 GPU가 바로 보이는 것은 아닙니다. GPU/A, GPU/C가 n/a로 보이면 Kubernetes가 아직 nvidia.com/gpu resource를 알지 못한다는 뜻입니다.
RKE2 bootstrap과 GPU stack은 서로 다른 계층입니다. RKE2는 Kubernetes control plane, kubelet, containerd, CNI를 준비하고, GPU stack은 host driver와 Kubernetes device plugin을 통해 GPU를 allocatable resource로 노출합니다.
host GPU 장착
|
v
NVIDIA kernel driver / libnvidia-ml
|
v
NVIDIA Container Toolkit / CDI / NRI
|
v
NVIDIA GPU Operator / device plugin
|
v
node.status.capacity["nvidia.com/gpu"]
|
v
workload resources.limits["nvidia.com/gpu"]
공식 RKE2 문서도 GPU Operator를 별도 add-on으로 다룹니다. NVIDIA 문서 기준으로 GPU Operator는 driver, container toolkit, device plugin, GPU Feature Discovery, DCGM exporter 같은 구성요소를 함께 관리합니다.
8-1. 현재 증상 해석¶
아래처럼 node는 Ready인데 GPU column이 n/a로 보이면 RKE2 node join은 성공했지만 GPU 계층은 아직 구성되지 않은 상태입니다.
NAME STATUS ROLE GPU/A GPU/C
gpu-01 Ready control-plane,etcd n/a n/a
gpu-02 Ready <none> n/a n/a
gpu-03 Ready <none> n/a n/a
이 상태에서 먼저 host 레벨을 확인합니다.
ssh tirosh@172.31.0.20 'lspci | grep -i nvidia || true'
ssh tirosh@172.31.0.20 'lsmod | grep nvidia || true'
ssh tirosh@172.31.0.20 'cat /proc/driver/nvidia/version || true'
ssh tirosh@172.31.0.20 'find /usr -iname libnvidia-ml.so 2>/dev/null | head'
ssh tirosh@172.31.0.20 'nvidia-smi || true'
lspci에는 GPU가 보이는데 lsmod, /proc/driver/nvidia/version, nvidia-smi가 실패하면 host driver가 아직 준비되지 않은 것입니다. 이 경우 Kubernetes 안에서 아무리 확인해도 nvidia.com/gpu가 나오지 않습니다.
8-2. GPU Operator 적용¶
이 repo에서는 RKE2 설치 뒤 NVIDIA GPU Operator를 cluster add-on으로 적용합니다.
make rke2/gpu/operator/apply SITE=tirosh-home
이 target은 profile.toml의 rke2.<cluster>.gpu_operator_manifest, rke2.<cluster>.context_name, kube.kubeconfig를 사용합니다. 기본값 기준으로는 ~/.kube/config의 tirosh-home/rke2/gpu context에 GPU Operator manifest를 적용합니다.
따라서 먼저 kubeconfig merge가 완료되어 있어야 합니다.
make rke2/kubeconfig/merge SITE=tirosh-home
현재 manifest는 RKE2의 HelmChart resource를 사용합니다.
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: gpu-operator
namespace: kube-system
spec:
repo: https://helm.ngc.nvidia.com/nvidia
chart: gpu-operator
version: v26.3.2
targetNamespace: gpu-operator
createNamespace: true
초기값은 driver.enabled=true로 둡니다. 즉 host에 호환 가능한 NVIDIA driver가 없으면 GPU Operator가 driver 설치를 시도하는 모델입니다. 이미 검증된 host driver를 image나 수동 설치로 관리하는 운영 모델이라면 driver.enabled=false로 바꾸고 적용합니다.
RKE2 v1.35 계열에서는 최신 containerd를 사용하므로, manifest는 CDI/NRI 경로를 우선 사용합니다.
cdi:
nriPluginEnabled: true
GPU Operator는 container runtime이나 driver 계층을 건드릴 수 있습니다. RKE2 문서도 NVIDIA Operator가 containerd에 hangup을 보내 RKE2가 재시작될 수 있다고 안내합니다. 병원 현장에서는 업무 시간, workload drain, rollback 계획을 잡고 적용합니다.
8-3. GPU resource 검증¶
Operator pod 상태를 확인합니다.
kubectl --context tirosh-home/rke2/gpu get pods -n gpu-operator -o wide
node에 NVIDIA label이 붙었는지 확인합니다.
kubectl --context tirosh-home/rke2/gpu get node gpu-01 -o jsonpath='{.metadata.labels}' | grep nvidia.com
GPU가 allocatable resource로 올라왔는지 확인합니다.
kubectl --context tirosh-home/rke2/gpu get node gpu-01 -o jsonpath='{.status.allocatable}' | grep nvidia.com/gpu
kubectl --context tirosh-home/rke2/gpu describe node gpu-01 | grep -A8 -E 'Capacity|Allocatable|nvidia.com/gpu'
정상이라면 nvidia.com/gpu가 capacity와 allocatable에 나타납니다. 이 값이 보여야 GPU workload가 아래처럼 resource limit을 요청할 수 있습니다.
resources:
limits:
nvidia.com/gpu: 1
8-4. runtime validation¶
마지막으로 CUDA container를 실행해 실제 container에서 GPU가 잡히는지 확인합니다.
make rke2/gpu/benchmark SITE=tirosh-home
현재 검증 pod는 ARM64 node에서 CUDA base image의 nvidia-smi를 실행하고, 실행 후 자동으로 삭제됩니다. DGX Spark는 ARM64 기반이므로 x86_64로 빌드된 CUDA sample image를 사용하면 exec format error가 발생할 수 있습니다.
정상 출력 예시는 아래와 같습니다.
NVIDIA-SMI 580.159.03
Driver Version: 580.159.03
CUDA Version: 13.3
GPU Name
0 NVIDIA GB10
9. 문제 확인¶
문제가 생기면 RKE2 계층과 GPU 계층을 분리해서 봅니다.
9-1. agent join 실패¶
아래 단계에서 실패하면 playbook은 그 지점에서 중단되고 이후 작업으로 넘어가지 않습니다.
| 실패 지점 | 대표 원인 | 결과 |
|---|---|---|
| inventory 검증 | rke2_servers group이 없거나 비어 있음 |
bootstrap server를 고르지 못해 중단 |
| bootstrap server SSH 접속 | server host IP, SSH 계정, password, key 문제 | token을 읽기 전에 중단 |
| node token 읽기 | server가 아직 설치되지 않았거나 /var/lib/rancher/rke2/server/node-token이 없음 |
agent config를 만들기 전에 중단 |
| agent config 작성 | sudo 권한, filesystem 문제 | service 시작 전에 중단 |
rke2-agent service 시작 |
container runtime, CNI, kernel module, network 문제 | 설치 후 readiness 확인 전후로 실패 |
| readiness 확인 | agent가 cluster에 join하지 못함 | kubectl wait 단계에서 실패 |
token을 못 받아오는 경우에는 agent host에 join config를 쓰기 전에 멈추는 것이 정상입니다. 이 상태에서는 먼저 server 상태를 확인합니다.
make rke2/status SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
실패 지점에 따라 agent host에 일부 파일이나 service가 남을 수 있습니다. 상태가 애매하면 agent를 제거한 뒤 다시 설치합니다.
make rke2/agent/uninstall SITE=tirosh-home RKE2_AGENT_TARGETS=gpu-02 ASK_PASS=true ASK_BECOME_PASS=true
make rke2/agent/install SITE=tirosh-home RKE2_AGENT_TARGETS=gpu-02 ASK_PASS=true ASK_BECOME_PASS=true
9-2. kubeconfig 문제¶
kubectl --context tirosh-home/rke2/gpu get nodes가 실패하면 먼저 context가 ~/.kube/config에 있는지 확인합니다.
kubectl config get-contexts
make rke2/kubeconfig/merge SITE=tirosh-home ASK_PASS=true ASK_BECOME_PASS=true
kubeconfig의 server 주소가 접근 가능한 IP/DNS인지, 그리고 그 주소가 tls-san에 포함되어 있는지도 확인합니다.
9-3. GPU가 n/a로 보이는 경우¶
node가 Ready인데 nvidia.com/gpu가 없으면 아래 순서로 확인합니다.
kubectl --context tirosh-home/rke2/gpu get pods -n gpu-operator -o wide
kubectl --context tirosh-home/rke2/gpu describe node gpu-01 | grep -A8 -E 'Capacity|Allocatable|nvidia.com/gpu'
make rke2/gpu/benchmark SITE=tirosh-home
host driver가 없거나 GPU Operator pod가 실패하면 RKE2 재설치보다 GPU Operator manifest와 host driver 상태를 먼저 확인합니다.
10. 다음 단계¶
10-1. Argo CD 등록¶
GPU resource가 Kubernetes에 정상 노출되면 Argo CD에 workload cluster를 등록하고 application 배포를 시작합니다.
make argocd/cluster/add SITE=tirosh-home
10-2. 운영 확인¶
운영 중 cluster를 볼 때는 k9s를 사용합니다.
k9s --context tirosh-home/rke2/gpu