[Coder] 머신러닝 플랫폼 엔지니어링: Evidently와 Kubeflow Notebooks - 7.1.2. Coder로 시험한 세션 회수

· 7 분 소요

MLOps › 자원 관리·스케줄링·MLOps › GPU·스터디

시리즈 · 머신러닝 플랫폼 엔지니어링: Evidently와 Kubeflow Notebooks (4 / 6)·← 이전 편·다음 편 →

서종호(가시다)님의 Machine Learning Platform Engineering Study 3주차 학습 내용을 기반으로 합니다.


TL;DR

  • Coder는 개발 환경을 Terraform 템플릿으로 정의해 띄우는 오픈소스 서버다. 로컬 kind 클러스터에 v2.36.0을 올려 앞 글과 같은 시나리오를 돌렸다
  • 세션을 정지하면 템플릿대로 Deployment가 지워지고 홈 PVC는 남는다. GPU가 넘어가는 동작은 앞 글과 같았다
  • 남의 세션을 멈추는 정지 요청은 일반 사용자 토큰으로 404(찾을 수 없음), 관리자 토큰으로 201(빌드 생성)이었다
  • 공식 예제와 같은 wait_for_rollout = false 템플릿에서는 GPU가 없어 파드가 Pending이어도 시작 빌드가 성공하고 빌드 상태는 running이다. 빌드 상태만 보면 GPU를 받지 못한 세션도 켜진 세션으로 센다
  • last_used_at은 연결이 있는 동안 약 60초 주기로 올랐다. 연결을 끊으면 세션 안에 sleep이 돌아도 약 80초 뒤부터 멈췄다
  • 템플릿의 자동 정지 시간은 기존 세션에 재시작 뒤에도 적용되지 않았다. 새 세션은 사용자가 연장하거나 끌 수 있었다(오픈소스 기능 범위)


개발 환경 도구의 역할

앞 글에서는 Deployment를 직접 만들어 GPU를 받은 세션을 시험했다. 세션을 정지하면 GPU 요구가 없어져 대기 세션이 뜬다. 파드만 지우면 요구가 남는다. 이렇게 쓰지 않는 세션을 정지해 GPU를 돌려받는 일을 회수라고 불렀다. 가짜 GPU는 kind 노드에 광고한 숫자다. 스케줄러의 계산만 실제와 같다.

세션을 여럿에게 나눠 주려면 그 위에 사용자, 세션 생성 화면, 접속 경로, 기록이 필요하다. 이 부분은 개발 환경 도구가 맡을 수 있다. 앞 글 표의 JupyterHub, Eclipse Che, Kubeflow Notebooks가 그렇다. 오픈소스 개발 환경 도구로는 Coder도 있어서 Coder로 구현해 봤다. 세션 정의가 Terraform 파일이라 앞 글의 Deployment와 PVC를 옮기기 쉬웠다. 같은 구조는 앞 글 표의 다른 도구로도 구현해 볼 수 있다.

도구가 끼면 세션의 상태를 어디서 어떻게 읽어야 할까. 남의 세션을 멈출 권한, 세션 상태와 파드 상태, 접속 기록, 자동 정지를 봤다.

앞 글처럼 Mac의 kind 클러스터에 가짜 GPU 1장을 광고했고 서버 버전은 v2.36.0이다. 코드는 AGPL-3.0이고 enterprise/ 디렉터리만 별도 라이선스다. 문서 인용도 같은 태그에서 확인했다. 시험 스크립트가 정지 API를 직접 불렀고 판정 프로그램을 붙이지는 않았다. 시나리오마다 한 번 돌렸다. 실험 코드 작성과 실행에는 AI의 도움을 받았다. 코드는 coder 폴더, 로그는 stage3_part1.txt와 stage3_part2.txt에 있다.


Coder의 구성 요소

Coder에서는 세션을 워크스페이스(workspace)라고 부른다. 여기서는 앞 글과 맞춰 세션이라고 쓴다. 구성 요소는 셋이다.

  • 템플릿(template): 세션에 무엇을 만들지 적은 Terraform 파일이다. Terraform은 인프라를 파일로 적어 두면 실제 상태를 그대로 맞춰 주는 도구다.
  • 빌드(build): 세션을 시작하거나 정지할 때 서버가 Terraform을 한 번 실행하는 일이다. 시작과 정지가 각각 빌드 하나다. 이미지 빌드와는 다른 말이다.
  • 에이전트(agent): 세션 파드 안에서 도는 Coder 프로세스다. 접속을 중계하고 상태를 서버에 보고한다.
flowchart TB
  %% 사용자가 시작을 요청하면 서버가 Terraform을 돌려 쿠버네티스 객체를 만든다
  U[사용자] -->|시작, 정지| C[Coder 서버]
  C -->|빌드: Terraform 실행| K[쿠버네티스 API]
  K --> D[세션 Deployment]
  K --> V[홈 PVC]
  D --> A[세션 파드 안의 에이전트]
  A -->|상태 보고| C
  U -->|브라우저, SSH| A
AI를 이용해 직접 그린 도식

정지하면 Deployment만 지워진다. 템플릿에 그렇게 적혀 있다.

# main.tf (발췌, 앞 두 주석은 글에서 달았다)
resource "kubernetes_persistent_volume_claim" "home" {   # count가 없어 정지해도 남는다
  ...
}

resource "kubernetes_deployment" "main" {
  count            = data.coder_workspace.me.start_count   # 시작이면 1, 정지면 0
  wait_for_rollout = false # GPU가 없으면 파드가 대기하므로 빌드는 기다리지 않는다
  ...
}

count는 Terraform에서 그 리소스를 몇 개 만들지 정하는 값이다. start_count는 시작 빌드에서 1, 정지 빌드에서 0이므로 정지 빌드는 Deployment를 지운다. 앞 글의 표로 보면 replicas 0이 아니라 Deployment 삭제에 해당한다. 다시 시작할 때는 템플릿으로 다시 만든다.


정지 권한

세션 정지는 빌드 생성 API로 한다. 정지도 빌드 하나를 새로 만드는 일이라 성공 응답이 201 Created다.

# 세션을 정지하는 요청. 토큰은 API를 부를 때 쓰는 로그인 키다
curl -X POST "$CODER_URL/api/v2/workspaces/$WORKSPACE_ID/builds" \
  -H "Coder-Session-Token: $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"transition":"stop"}'

회수에서는 다른 사람의 세션을 멈춰야 한다. 사용자 A의 세션이 GPU를 배정받았고 사용자 B의 세션이 기다리는 상태였다. A의 세션을 멈추는 같은 요청을 사용자 B의 토큰과 관리자의 토큰으로 보냈다.

  사용자 B의 stop: 404 Resource not found or you do not have access to this resource
  owner의 stop: 201 initiator: owner reason: initiator
  stop 요청부터 B 파드 Ready까지 136초
  pod coder-userb-ws-b-698fcf4fb7-llhtr Running true
  PVC: coder-usera-ws-a-home   Bound | coder-userb-ws-b-home   Bound

owner는 Coder의 최상위 관리자 역할이다. 일반 사용자에게는 남의 세션이 없는 것처럼 404가 나왔다. 관리자 요청은 빌드를 만들었다. 역할 문서의 표에서 모든 세션을 변경할 수 있는 사이트 역할은 Owner뿐이고 Template Admin은 조회까지다.

요청은 한 번이지만 완료는 빌드 결과와 파드 소멸로 확인해야 한다. 201은 빌드가 만들어졌다는 응답이다. GPU는 빌드가 끝나 Deployment가 사라진 뒤에 돌아온다. 위 136초는 앞 글의 5초와 비교할 값이 아니고 회수 지연의 기준값으로도 쓸 수 없다. 정지가 Terraform 실행을 거치고 이 로컬 환경의 다른 빌드도 약 135~143초 걸렸다. 이 템플릿에는 readiness 검사가 없어 Ready는 컨테이너가 시작됐다는 뜻이다.

정지 뒤에 A의 PVC는 남았고 B 파드가 GPU를 받았다. A를 다시 시작하자 앞 글처럼 홈 파일은 남고 /tmp 파일은 사라졌다.

정지가 끝 상태가 아닐 수 있다. 문서는 사용자 접속으로 세션이 시작될 수 있다고 적는다.


세션 상태와 파드 상태

GPU가 없는 상태에서 사용자 B가 세션을 시작한 결과다. 로그의 워크스페이스 상태는 API의 latest_build.status이다. 파드 줄의 값은 이름, 상태, 준비 여부다.

  B 빌드: start succeeded / 워크스페이스 상태: running
  pod coder-usera-ws-a-689b78747d-dw548 Running true
  pod coder-userb-ws-b-698fcf4fb7-llhtr Pending <none>

파드는 Pending인데 빌드 상태는 running이다. 이 값은 마지막 시작 빌드가 성공했다는 뜻이다. 템플릿의 wait_for_rollout = false 때문에 Terraform은 Deployment를 만든 시점에 끝나고 파드가 뜨기를 기다리지 않는다. 이 설정은 공식 쿠버네티스 예제 템플릿과 같다. 켜면 Terraform이 파드를 기다리고 프로바이더 기본 제한 10분 안에 GPU가 비지 않으면 빌드가 실패할 것으로 본다.

Coder에도 이 상황을 나타내는 값이 있다. 문서는 리소스는 만들어졌지만 에이전트가 연결하지 못한 세션을 Unhealthy라고 부른다. API 응답에는 health와 에이전트 status가 따로 있다. 소스대로라면 에이전트가 연결되지 않은 세션은 unhealthy로 나온다.

빌드 상태로 세면 GPU를 받지 못한 세션도 켜진 세션으로 센다. health는 접속할 수 있는지를 알려 준다. GPU를 배정받았는지는 파드 쪽을 봐야 한다.

회수를 Coder 대신 kubectl의 파드 삭제로 하면 이 어긋남이 더 커진다. 앞 글의 대기 세션 시나리오를 Coder에서 돌린 결과다. 위 두 줄이 삭제 전, 아래 두 줄이 삭제 30초 뒤다.

  pod coder-usera-ws-a-6d44b84b97-tkbb9 Running true
  pod coder-userb-ws-b-866746c7cd-xmlzg Pending <none>
  pod coder-usera-ws-a-6d44b84b97-rc4k4 Pending <none>
  pod coder-userb-ws-b-866746c7cd-xmlzg Running true
  A 상태: running / B 상태: running

이번 실행에서 대기하던 B가 GPU를 받았고 A의 새 파드는 Pending이다. 빌드 상태는 둘 다 running이다. kubectl로 파드를 지운 일은 Coder의 빌드 기록에 남지 않는다. Deployment가 남아 있으니 앞 글처럼 GPU 요구도 남는다. 회수는 정지 API로 해야 Coder의 기록과 쿠버네티스의 상태가 맞는다.


접속 기록

세션의 활동을 볼 값으로 Coder는 last_used_at을 준다. 연결 활동을 서버가 약 60초 단위로 모아 기록한 시각이다. 필드의 정의는 문서에 없고 소스에서 확인했다. 스크립트로 돌리기 쉬운 SSH 경로로 시험했다. 30초마다 현재 시각과의 차이를 읽었다.

먼저 SSH 연결이 있는 구간을 봤다.

  접속 전 last_used_at 경과 406 초
  접속 중 last_used_at 경과 16 초
  접속 중 last_used_at 경과 46 초
  접속 중 last_used_at 경과 16 초
  접속 중 last_used_at 경과 46 초
  접속 중 last_used_at 경과 16 초

차이가 16초와 46초를 오갔다. 30초마다 읽었으니 약 60초마다 갱신된 것이다. 다음은 세션 안에 sleep 600을 백그라운드로 남기고 연결을 끊은 뒤다.

  세션 안 sleep 프로세스 수: 1
  접속 끊긴 뒤 last_used_at 경과 11 초
  접속 끊긴 뒤 last_used_at 경과 42 초
  접속 끊긴 뒤 last_used_at 경과 12 초
  접속 끊긴 뒤 last_used_at 경과 42 초
  접속 끊긴 뒤 last_used_at 경과 72 초
  접속 끊긴 뒤 last_used_at 경과 102 초
  ...
  접속 끊긴 뒤 last_used_at 경과 222 초

끊은 뒤에도 두 번 더 갱신됐고 마지막 갱신은 약 80초 뒤였다. 그 뒤로는 끝까지 차이가 늘기만 했다. 끊은 직후 세션 안의 sleep 프로세스 1개를 확인했다. 소스상 갱신 조건이 세션과 연결뿐이라 학습도 같을 것으로 본다. 앞 글의 code-server heartbeat와 같은 종류의 신호다.


자동 정지

Coder에는 템플릿 단위의 자동 정지 기능이 있다. Default autostop은 활동 없이 세션이 도는 시간의 상한이다. 활동이 감지되면 마감을 미룬다. 이 설정이 어떤 세션에 적용되고 사용자가 바꿀 수 있는지 시험했다. 세션이 떠 있는 상태에서 템플릿 값을 1시간으로 바꿨다. 로그의 ttl_ms는 세션에 저장된 자동 정지 시간(밀리초)이고 deadline은 이번 실행의 마감이다.

  템플릿 변경 직후 기존 세션: ttl_ms=None deadline=없음
  재시작 뒤 기존 세션: ttl_ms=None deadline=없음
  템플릿 변경 뒤 만든 세션: ttl_ms=3600000 deadline=60분 뒤
  사용자의 연장(3시간 뒤로): 200 Deadline updated to 2026-11-04T09:10:31Z.
  연장 뒤: ttl_ms=3600000 deadline=180분 뒤
  사용자의 자동 정지 끄기: 204 None
  끈 뒤: ttl_ms=None deadline=없음

기존 세션은 재시작한 뒤에도 값이 비어 있었다. 문서는 기존의 실행 중 세션에 적용되지 않는다고 적고 재시작한 경우는 적지 않는다. 새 세션에는 적용됐지만 사용자가 마감을 연장할 수도, 끌 수도 있었다.

사용자가 TTL을 바꾸지 못하게 --allow-user-autostop을 끄거나 Autostop requirement를 쓰려면 Premium 라이선스가 필요하다고 문서는 적는다. Autostop requirement는 활동과 무관하게 세션을 멈춘다. 두 기능도 유휴를 판정하지는 않는다.

문서 기준으로 자동 정지는 연결을 활동으로 본다.


정리

세션을 멈추는 요청은 한 번이고 GPU가 넘어가는 동작은 앞 글과 같았다. 상태를 읽는 곳이 달라졌다.

시험 결과 볼 점
남의 세션 정지 일반 사용자 404, owner 201 관리자 토큰이 필요하다
GPU 대기 중인 세션 빌드 상태 running, 파드 Pending 파드와 에이전트 상태를 함께 본다
kubectl로 파드 삭제 GPU 요구가 남고 빌드 상태는 running 정지 API를 쓴다
연결과 last_used_at 연결이 있으면 갱신되고 끊으면 멈춘다 세션 안의 일은 이 값에 나타나지 않는다
자동 정지 설정 기존 세션 미적용, 사용자가 끌 수 있음 설정의 적용 범위를 확인한다

running은 빌드의 결과이고 last_used_at은 연결의 시각이다. 둘 다 GPU를 쓰는지는 알려 주지 않는다. 다음 글에서는 GPU 쪽 신호를 재 본다.


참고 링크




hit count

댓글남기기