ThinkPad gpu-01 이전 작업 기록

기록 시점: 2026-10-16에 설치와 데이터 복사를 적었고, 2026-10-20에 트래픽과 운영 포인터를 갱신했다. 2026-10-21에 DGX Spark 초기화와 context 이름을 적었다. 설치 절차의 기준은 RKE2 GPU cluster다.

ThinkPad 172.31.0.21이 서비스한다. context는 tirosh-home/rke2/gpu다. DGX Spark는 2026-10-21에 초기화됐고, etcd와 롤백 대상이 아니다. rke2-uninstall.sh와 make rke2/server/uninstall은 그 초기화가 아니었다. 지금 실행하면 inventory의 ThinkPad를 지운다. 옛 etcd를 복원하거나 Spark 클러스터에 join하지 않았다.

1. 에이전트가 유지할 것

2026-10-20 이후 사용자 승인 없이 아래를 하지 않는다.

  • make rke2/server/uninstall, make rke2. inventory의 gpu-01은 172.31.0.21이라 uninstall은 ThinkPad를 지운다.
  • make rke2/install. config 변경은 rke2-server를 restart한다. tls-san에서 이전 주소를 빼는 작업은 2026-10-20에 끝났으므로 install을 다시 실행하지 않는다.
  • make argocd/cluster/add. 기본 인자는 --yes --upsert다. argocd.cluster_name은 tirosh-home-rke2-latop이다. 철자를 고치지 않는다.
  • bloodbag-dev의 skip-reconcile: "true" 해제와 Redis 재복사.
  • secrets.toml에 없는 Secret 값을 Git에 쓰기.
  • 새 클러스터에 kubectl apply -k를 다시 실행하거나, 지운 migrate Job을 다시 만들기.
  • ThinkPad에서 rke2 server status. 이 명령은 systemd 서버와 별개인 rke2 server를 띄워 2026-10-16 19:49에 API를 죽였다. 상태 확인은 systemctl is-active rke2-server다.
  • SSH 비밀번호를 명령줄, 스크립트, 이 문서에 남기기. ThinkPad는 Mac id_ed25519와 tirosh NOPASSWD sudo를 받는다.

make rke2와 make host/network/apply는 ThinkPad를 대상으로 한다. inventory의 gpu-01 ansible_host가 172.31.0.21이기 때문이다. kubectl context tirosh-home/rke2/gpu도 ThinkPad API다.

2. 두 클러스터

Spark, 2026-10-21 초기화 전 기록 ThinkPad
주소 172.31.0.20 172.31.0.21
노드 이름 gpu-01 gpu-01
머신 DGX Spark, arm64, GB10 ThinkPad P1 Gen 6, amd64, RTX 5000 Ada Laptop
OS Ubuntu 24.04.3, kernel 6.11.0-1016-nvidia Ubuntu 26.04.1, kernel 7.0.0-38-generic
RKE2 v1.35.5+rke2r2, containerd 2.2.3-k3s1 v1.35.9+rke2r1, containerd 2.2.7-k3s1
드라이버 580.95.05, label model=dgx-spark 595.99.02 open, label model=thinkpad-p1-gen6
GPU nvidia.com/gpu=1 nvidia.com/gpu=1, Operator HelmChart v26.3.2, deploy.driver=pre-installed
context tirosh-home/rke2/gpu tirosh-home/rke2/gpu
API https://172.31.0.20:6443 https://172.31.0.21:6443

2026-10-16에 양쪽 노드 모두 Ready였다. Spark Service bloodbag-dev/bloodbag-signal-source-redis-external은 172.31.0.50이다. ThinkPad의 같은 Service는 LoadBalancer이고 주소가 없다.

호스트 네트워크는 플레이북으로 적용했다. NIC enx00e04c4e5868, MAC 00:e0:4c:4e:58:68, 주소 172.31.0.21/16, gateway와 DNS 172.31.0.1, renderer NetworkManager. 파일은 /etc/netplan/99-tirosh-static.yaml이다. ASK_PASS=false가 필요하다. Ubuntu 26.04 sudo 프롬프트는 Ansible과 맞지 않아 ThinkPad tirosh에 NOPASSWD sudo를 두었다.

3. kubeconfig

Mac 파일:

파일 내용
~/.kube/config context tirosh-home/k3s, tirosh-home/rke2/gpu. RKE2 server는 https://172.31.0.21:6443
~/.kube/tirosh-home-rke2.yml profile 경로. server https://172.31.0.21:6443
~/.kube/tirosh-home.yml k3s tirosh-home/k3s

profile.toml의 context 이름은 tirosh-home/rke2/gpu다. [argocd] cluster_name만 tirosh-home-rke2-latop이다.

  • [kube] kubeconfig = "~/.kube/config"
  • [kube.contexts] rke2_gpu = "tirosh-home/rke2/gpu"
  • [rke2.gpu] context_name = "tirosh-home/rke2/gpu", kubeconfig_path = "~/.kube/tirosh-home-rke2.yml", cluster_name = "rke2-gpu"
  • [argocd] cluster_context = "tirosh-home/rke2/gpu", cluster_name = "tirosh-home-rke2-latop"

ThinkPad 사용자 tirosh의 /home/tirosh/.kube/config도 context, cluster, user가 tirosh-home/rke2/gpu이고 server는 https://172.31.0.21:6443다. Mac 기본 context와 같다.

kubectl --context tirosh-home/rke2/gpu get nodes

make kubernetes/secret/apply는 KUBECONFIG가 가리키는 파일 안의 context만 본다. ThinkPad에 적용할 때는 context tirosh-home/rke2/gpu를 쓴다.

4. 단계

4-1. 완료

  1. 호스트 네트워크를 플레이북으로 맞췄다. SSH 172.31.0.21:22가 유지된다.
  2. Argo CD k3s에 클러스터를 추가만 했다. 등록 이름은 tirosh-home-rke2-latop이다. 철자는 latop이다. 서버 https://172.31.0.21:6443, Successful, v1.35.9. 당시 Spark 등록 이름 tirosh-home-rke2-gpu는 https://172.31.0.20:6443에 남아 있었다. 7단계에서 그 secret을 지웠다. 등록은 argocd --core와 context tirosh-home/k3s로 했다. 새 클러스터 ServiceAccount는 kube-system/argocd-manager다.
  3. 데이터 차이를 받았다. 마지막 Redis는 쓰기 중지 없이 Spark writer를 둔 채 online BGSAVE다. 다시 쓰기 중지를 시작하지 않는다.
  4. ThinkPad에만 local-path, StorageClass, 워크로드, 데이터를 올렸다. GPU Operator도 새 context에만 적용했다.
  5. 새 클러스터만 검증했다. Caddy와 Application destination은 Spark다.

4-2. 2026-10-20

  1. 트래픽 전환은 끝났다. VIP 172.31.0.50은 ThinkPad NIC enx00e04c4e5868에 있다. RKE2 Application destination은 https://172.31.0.21:6443이다. bloodbag-dev의 skip-reconcile: "true"는 유지한다.

6단계에서 destination을 바꾼 파일:

  • tirosh-gitops/clusters/tirosh-home/k3s/applications/local-path-provisioner.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/bloodbag-dev.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/bloodbag-stg.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/bloodbag-smc.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/fetal-decel-dev.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/cloudbeaver.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/redisinsight.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/observability-grafana.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/observability-loki.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/observability-alloy.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/rke2-gpu-metallb.yaml
  • tirosh-gitops/clusters/tirosh-home/k3s/applications/rke2-gpu-metallb-config.yaml
  • tirosh-dev-guide/cd-repo/clusters/dev/k3s/applications/tirosh-guide-dev.yaml
  • tirosh-dev-guide/cd-repo/clusters/stg/k3s/applications/tirosh-guide-stg.yaml

자동 sync는 local-path-provisioner, bloodbag-smc, fetal-decel-dev, cloudbeaver, redisinsight, tirosh-guide-dev, tirosh-guide-stg다. 부모 Application tirosh-home-k3s-applications도 자동이며 자식 Application spec을 되돌린다. destination을 바꾸는 순간 관리 대상이 새 API로 넘어가고, 자동 prune은 옛 클러스터에 남은 오브젝트를 지우지 않는다.

  1. inventory, network.vars.yml, argocd.cluster_name=tirosh-home-rke2-latop을 이 브랜치에 커밋한다. context 이름은 tirosh-home/rke2/gpu로 두고, 커밋 직후 Mac kubeconfig의 그 context만 ThinkPad API로 바꾼다. Spark kubeconfig는 ~/.kube/tirosh-home-rke2-spark.yml에 남긴다. Caddy의 cysec, demo.cysec도 172.31.0.21:80으로 바꾸고, Argo cluster secret tirosh-home-rke2-gpu는 지운다. 당시 tls-san의 172.31.0.20은 API restart를 피하려고 남겼다. DGX Spark 초기화는 별도 작업이고 uninstall 절차로 하지 않는다.
  2. Spark를 현재 대상으로 적는 기록을 걷어낸다. tls-san에서 172.31.0.20을 빼고, 절차 문서와 cysec EXPECTED_SERVER를 https://172.31.0.21:6443로 맞춘다. 브랜치를 main에 머지한 뒤, 라이브 config 차이가 그 SAN뿐이면 make rke2/install로 인증서를 갱신한다.

2026-10-20에 적어 둔 롤백은 Caddy를 172.31.0.20:80으로 되돌리고 Application destination을 .20으로 되돌리는 것이었다. 2026-10-21에 DGX Spark를 초기화했으므로 그 롤백은 무효다.

4-3. 2026-10-21

  1. DGX Spark 172.31.0.20을 초기화했다. 호스트는 ping과 TCP 22, 6443, 9345에 응답하지 않는다.
  2. ThinkPad와 Mac의 RKE2 context 이름은 tirosh-home/rke2/gpu다. Spark API를 가리키던 kubeconfig 사본은 지웠다.
  3. 인증서 SAN에서 172.31.0.20을 뺀 것은 8단계의 make rke2/install만으로는 끝나지 않았다. 기존 serving 인증서가 남아서 rke2 certificate rotate -s api-server -s etcd를 실행했다. CA와 admin 클라이언트 인증서는 돌리지 않았다. 다시 install하지 않는다.

5. 새 클러스터에 올라간 것

네임스페이스: bloodbag-dev, bloodbag-stg, bloodbag-smc, fetal-decel-dev, cloudbeaver, redisinsight, observability, tirosh-guide-dev, tirosh-guide-stg. bloodbag-cysec 네임스페이스는 없다. StorageClass bloodbag-cysec-local-retain만 새 context에 적용했다. Retain, WaitForFirstConsumer, provisioner rancher.io/local-path, default 아님. 매니페스트는 sites/tirosh-home/rke2/bloodbag-cysec-storageclass.yaml이다. 기본 RKE2_CONTEXT_NAME은 Spark이므로 override 없이 apply하면 Spark에 들어간다.

local-path는 네임스페이스 local-path-storage, StorageClass local-path, provisioner rancher.io/local-path, WaitForFirstConsumer, reclaim Delete, nodePathMap DEFAULT_PATH /opt/local-path-provisioner다. 벤더 매니페스트는 tirosh-gitops의 clusters/tirosh-home/rke2-gpu/platform/local-path-provisioner, rancher v0.0.36이다. PV host path는 spec.local.path 아래 /opt/local-path-provisioner/pvc-<uid>_<ns>_<claim>이다.

observability에는 Helm으로 Loki chart 18.11.7과 Grafana chart 13.0.1을 새 context에만 설치했다. Grafana는 기존 Secret grafana-admin을 쓴다. Mac에 Helm 바이너리는 없고 당시 /tmp/helm은 v3.18.4였다.

Git에 없는 채 ThinkPad로 값만 복사한 Secret 이름: bloodbag-dev/bloodbag-dev-runtime, observability/grafana-admin, bloodbag-smc/sct034-evidence-key.

CloudBeaver와 RedisInsight는 클러스터에서만 고쳤다. Git은 그대로다. RedisInsight 볼륨 소유자는 1000:1000이다.

guide 앱은 적용하지 않았다. 5단계의 선택 항목이다.

6. 데이터

덤프는 ThinkPad /var/tmp/rke2-migration mode 700에 있다. Postgres는 pg_dump -Fc --no-owner다. 동작 중인 Postgres 데이터 디렉터리를 파일 복사하지 않았다. 복원 순서는 DROP DATABASE WITH (FORCE), CREATE DATABASE, CREATE EXTENSION timescaledb, timescaledb_pre_restore(), pg_restore --no-owner --no-acl, timescaledb_post_restore()다.

덤프 바이트 복원 후 논리 크기
postgres/bloodbag-dev-bloodbag.dump 152673556 bloodbag 546542383. 중지 시점 기준 589181743
postgres/bloodbag-stg-bio_guardian_v2.dump 163433097 bio_guardian_v2 1556258151. 기준 2326273383. session_overlay_cadence_applications 784622
postgres/bloodbag-smc-bio_guardian_v2.dump 1309608 bio_guardian_v2 20479335. 기준 1148132711
postgres/fetal-decel-dev-perivio_fd.dump 9430 perivio_fd 9346071. 기준 9296919

smc TOC에는 public.session_overlay_cadence_applications와 public.prediction_results의 TABLE DATA가 있으나 COPY 본문이 비어 있다. cadence, prediction_results, waveform_segments 건수 0은 덤프와 같다. smc를 다시 복원하지 않는다. 건수를 지금 Spark의 live count와 비교하지 않는다. stg Timescale continuous aggregate의 circular FK 경고는 pg_dump에서 예상된 것이다.

pg_restore --no-owner --no-acl은 role과 GRANT를 가져오지 않는다. dev 로그인 role과 GRANT는 Spark pg_dumpall --roles-only와 schema dump의 GRANT로 복원했다. 비밀번호 해시는 문서에 남기지 않는다. dev DB 사용자는 bloodbag_product_api, 데이터베이스 bloodbag, 호스트 bloodbag-postgres다. inference worker 사용자는 bloodbag_inference_context다. product-api /health/ready는 ready이고 revision product_result_ack_targets_0021이 expected와 같다. alembic 테이블은 bloodbag_product.bloodbag_product_alembic_version이다. public.alembic_version이 없는 것은 dev에서 정상이다. stg와 smc는 public.alembic_version이 있다. fetal에는 alembic 테이블이 없고 backend는 Running이다.

smc에는 Spark와 같이 audit_maintenance_user(login)와 audit_purge_owner(nologin)가 있다. 둘 다 pg_authid.rolpassword가 NULL이다. stg role은 bio_user뿐이다. fetal role은 postgres뿐이다.

fetal Postgres는 emptyDir이다. RKE2 재시작 한 번을 넘겼고 그 뒤에도 perivio_fd가 남아 있었다. Pod를 다시 만들면 사라진다.

Redis 8은 --appendonly yes일 때 AOF가 없으면 기존 dump.rdb를 무시하고 빈 AOF base를 만든다. 그 프로세스가 종료되면 PVC의 dump.rdb를 89바이트로 덮어쓴다. ingress, internal, stg는 StatefulSet을 0으로 둔 채 완전한 dump.rdb를 dump.rdb와 appendonlydir/appendonly.aof.1.base.rdb 양쪽에 넣고, manifest와 0바이트 incr은 유지한 뒤 기존 기동 인자로 올렸다. 로그는 DB loaded from base file이어야 한다. Creating AOF base file이면 실패한 기동이다. redis-check-rdb를 먼저 통과시켰다. 큰 RDB는 kubectl exec cat으로 받으면 잘린다. Spark 호스트 경로를 sudo cat으로 읽고, 크기와 앞 5바이트 REDIS가 맞은 뒤에만 .new를 mv했다.

Redis 로드한 파일 DBSIZE
dev signal-ingress 55765817 8039
dev internal-processing 247004091 16980
stg 228734417 1843538. check는 1843546이고 8키는 로드 시 만료
smc 이전 중지 파일 259455096. 교체하지 않음 2090868
dev signal-source AOF 디렉터리를 유지 DB loaded from base file와 incr. TLS *:6380

signal-source는 RKE2 재시작 뒤에도 AOF에서 다시 로드됐다. fetal Redis Deployment는 PVC가 없고 dbsize 0이라 복사하지 않았다. 앱 Pod는 replica 1로 되돌렸다. stg PVC bloodbag-vital-files와 bloodbag-vital-streamer-data는 streamer Deployment가 prune되어 Pending이다. 이번 복사 대상이 아니다.

dev result-writer는 redis://bloodbag-internal-processing-redis:6379/0이다. 그룹 bloodbag-result-writer-v1의 consumer가 17이어서 용량 16을 넘고 crash했다. ThinkPad internal Redis에서 pending 0인 stale consumer 16개를 XGROUP DELCONSUMER로 지우고 generation consumer는 남겼다. Spark는 건드리지 않았고 FLUSH하지 않았다. 이후 Pod는 1/1이다. 이 삭제를 반복하지 않는다.

API Pod는 기동 시 Redis ping이 실패하면 그 프로세스 동안 client가 nil이다. 복구는 rollout restart가 아니라 Pod 삭제다. Argo annotation을 붙이지 않기 위해서다.

지우고 다시 만들지 않은 Job: bloodbag-product-database-migrate, bloodbag-result-writer-group-initialize, bloodbag-evaluation-dataset, bloodbag-migrate, fetal-decel-migrate.

7. 일부러 0인 워크로드

ThinkPad는 amd64다. 아래 이미지는 arm64만 있어 replica 0이다. Git 태그를 바꾸지 않았다.

  • stg bloodbag-backend sha256:801c522ce58e911032820f4a5401cb26685a2c3138fb00e566a9b753e714698c
  • stg bloodbag-center sha256:c9c79105dbf3fbffdd607d3ba3cf43a8389f74f7998f9c1e6b300fdfd6470e2a
  • stg bloodbag-monitor sha256:a6d9ea3c6b1c80a3a7940de20554b2b1bfa48a1730f181867dd180faa4924cee 태그 smc-557-gaa5847500d1b
  • smc bloodbag-backend sha256:c9b23c3fe5e1efb9237daf1076627cbee4521ccf2a648516b3c33014f0d7aa6b
  • smc bloodbag-center sha256:3d42145f3b947c23af95436714fed21a9269da59196d0847f08af0b5f8ab597c
  • smc bloodbag-monitor sha256:88140fde1bd7c0968c19105b6e9e76800a32ad7f371581226972b1aba3a7f301 태그 smc-566-g4f1d6ffcc59b
  • demo-console 태그 smc-557-gaa5847500d1b, smc-566-g4f1d6ffcc59b

bloodbag-dev MinIO StatefulSet은 replica 0이다. docker.io/minio/minio:latest 익명 pull이 실패하고, gitops가 바꾸는 quay.io/minio/minio:latest digest sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e도 익명 401이다. 데이터는 PVC에 있다. dev MinIO는 IfNotPresent다.

Running으로 확인한 것: dev writer와 API, product-api, result-writer, inference-context-worker, Redis ingress/internal/stg/smc/signal-source, vital-file-streamer, Postgres, CloudBeaver, RedisInsight, Grafana, Loki, fetal Deployment 7개. 단일 노드에서 Pending인 것은 kube-system cilium-operator 두 번째 replica뿐이다. replica 수를 바꾸지 않는다.

현재 Pod가 nvidia.com/gpu를 요청하지 않으므로 Ada용 이미지 재빌드는 1일차 조건이 아니다.

8. inventory

gpu-01은 172.31.0.21, label node.tirosh.dev/model=thinkpad-p1-gen6이다. tls-san은 gpu-01, 172.31.0.21이다. 라이브 인증서 SAN 정리는 2026-10-20에 끝났다. make rke2/install을 다시 실행하지 않는다. inventory.yml은 손으로 고치지 않고 레포 루트에서 렌더한다.

uv run --package infra-tools infra-tools site-inventory render --site tirosh-home --output sites/tirosh-home/inventory.yml

profile.toml은 cwd 기준으로 찾는다. 홈 디렉터리에서 make repo/doctor를 실행하면 규칙이 없다.