DevOpsDevOps 학습 로드맵 · 14/17

번외: 더미 워커를 실제 ONNX 추론으로, GPU 스케줄링까지 교체하는 절차

sleep으로 흉내낸 inference-worker를 실제 ONNX Runtime 추론으로 바꾸고, GPU 노드 스케줄링과 모델 교체를 CI/CD에 편입하는 절차. 선택 시리즈.

2026-07-225 min read
#ONNX#GPU#Kubernetes#MLOps
DevOps 학습 로드맵시리즈 목차
1채용공고 요구사항 6개 중 자신 있게 답할 수 있는 게 절반도 안 됐다2kubeadm으로 온프레미스 Kubernetes 클러스터를 처음부터 세우는 절차3FastAPI 서비스 골격, RabbitMQ DLQ, Harbor+Trivy, Jenkins→ArgoCD 파이프라인 구축 절차4Argo Rollouts로 카나리 배포와 Prometheus 메트릭 기반 자동 롤백 붙이기5yaml 복붙 관리의 드리프트 문제를 Kustomize overlay로 구조화하기6Terraform과 Ansible로 클러스터를 코드에서 다시 세울 수 있게 만드는 절차7Prometheus·Loki 관측 스택 구축과 장애 주입 실험 설계 절차8SLI/SLO/에러 버짓을 실제로 계산해서 배포 판단 기준으로 써보기9임계값 없이 - 통계 기반 이상탐지로 Datadog Watchdog 흉내내기10Grafana OnCall로 PagerDuty 없이 온콜·에스컬레이션 구현하기11자체 JWT 구현부터 취약점 실험, Keycloak 마이그레이션까지의 실제 절차12ISMS-P 인프라 영역 심사를 가정하고 내 클러스터의 증적을 만드는 절차13SealedSecret이 못 남기는 것 - OpenBao + External Secrets Operator로 감사 로그 확보하기14번외: 더미 워커를 실제 ONNX 추론으로, GPU 스케줄링까지 교체하는 절차15번외: 인터넷을 완전히 끊고도 클러스터가 돌아가는지 - 폐쇄망(에어갭) 시뮬레이션 절차16번외: 부하테스트로 관측 스택을 실전 검증하기 - k6 + HPA + MSA 파이프라인17번외: DORA 4대 지표를 Four Keys로 자동 집계해보기

목표

시리즈 2에서 time.sleep()으로 흉내낸 inference-worker를 실제 ONNX Runtime 추론으로 교체하고, GPU가 있다면 GPU 노드 스케줄링까지 붙인다. 이 시리즈는 선택이다 - 인프라·파이프라인이 이미 갖춰진 상태이므로, 워크로드 내용물만 바꾸는 작업에 가깝다.

개념: ONNX란, GPU 스케줄링이란

**ONNX(Open Neural Network Exchange)**란: AI 모델은 보통 PyTorch나 TensorFlow 같은 특정 프레임워크로 학습된다. 문제는 그 프레임워크를 학습 때와 똑같이 서빙(추론) 환경에도 그대로 설치해야 한다는 점이다 - 무겁고, 프레임워크 버전에 서비스가 종속된다. ONNX는 프레임워크에 상관없이 모델을 표현하는 공통 포맷이다. 학습은 PyTorch로 하되, 추론 시점엔 ONNX로 변환한 모델을 가벼운 ONNX Runtime으로 돌리면, 추론 서버가 특정 학습 프레임워크에 종속되지 않는다.

GPU 스케줄링이 CPU와 다른 이유: CPU는 여러 프로세스가 시분할로 나눠 쓰는 게 기본이라 Kubernetes가 "0.5개 코어"처럼 잘게 쪼개 요청할 수 있다. GPU는 기본적으로 나눠 쓰는 개념이 없다 - 한 컨테이너가 GPU 1개를 통째로 점유한다. 그래서 Kubernetes가 GPU를 인식하고 파드에 할당하려면 nvidia.com/gpu처럼 별도의 리소스 타입으로 노출해주는 디바이스 플러그인이 필요하다(아래 2번).

실제 구축 절차

1. 더미 모델 → ONNX Runtime 추론으로 교체

# 교체 전 - inference-worker/app/worker.py
def run_inference(job: dict):
    time.sleep(180)   # 오래 걸리는 작업 흉내
    return {"result": "dummy"}
# 교체 후
import onnxruntime as ort
import numpy as np

session = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])

def run_inference(job: dict):
    input_tensor = preprocess(job["image_path"])
    outputs = session.run(None, {"input": input_tensor})
    return postprocess(outputs)

기존 파이프라인(RabbitMQ ack/nack, DLQ, 상태 갱신)은 그대로 둔다 - 바뀌는 건 run_inference 내부뿐이다. 이렇게 인터페이스를 유지해두면 나중에 모델을 다시 바꿔도 파이프라인 코드는 안 건드려도 된다.

2. GPU 노드 스케줄링 (GPU가 있는 경우)

# NVIDIA device plugin 설치 - 노드의 GPU를 스케줄링 가능한 리소스로 노출
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/main/deployments/static/nvidia-device-plugin.yml
# inference-worker deployment
spec:
  containers:
    - name: inference-worker
      resources:
        limits:
          nvidia.com/gpu: 1
      env:
        - name: ONNX_PROVIDER
          value: "CUDAExecutionProvider"
providers = ["CUDAExecutionProvider", "CPUExecutionProvider"]  # GPU 우선, 실패 시 CPU로 폴백
session = ort.InferenceSession("model.onnx", providers=providers)

GPU가 없는 환경이라면 CPUExecutionProvider만 쓰고 이 단계는 건너뛴다 - 파이프라인 검증이 목적이라 추론 속도 자체는 부차적이다.

3. 모델 교체를 CI/CD에 편입

모델 파일을 이미지에 굽지 않고, 별도 아티팩트로 관리해 이미지 재빌드 없이 교체할 수 있게 한다.

// Jenkinsfile 추가 스테이지
stage('Upload Model') {
  steps {
    sh '''
      curl -T model-v2.onnx https://artifact-registry.local/models/model-v2.onnx
    '''
  }
}
# inference-worker가 시작 시 모델을 내려받는 initContainer
initContainers:
  - name: fetch-model
    image: curlimages/curl
    command: ["curl", "-o", "/models/model.onnx", "https://artifact-registry.local/models/model-v2.onnx"]
    volumeMounts:
      - {name: model-volume, mountPath: /models}

모델 버전을 매니페스트(Kustomize overlay)에서 관리하면, 모델 교체도 ArgoCD의 GitOps 흐름을 그대로 탄다 - 이미지 태그를 바꾸던 것과 같은 방식으로 모델 URL만 바꿔 커밋하면 된다.

검증 방법

# CPU/GPU 추론 결과가 동일한지 (provider만 다르고 출력은 같아야 함)
# 모델 교체 후 초기 요청 몇 건의 결과가 이전 모델과 다르게 나오는지 (버전이 실제로 바뀌었는지 확인)
kubectl logs <inference-worker-pod> | grep "provider"   # 어떤 provider로 실행됐는지 로그로 확인

체크리스트

  • 더미 워커를 실제 ONNX 추론으로 교체
  • (GPU 있으면) GPU 노드 스케줄링 적용
  • 모델 교체를 CI/CD 파이프라인에 편입 (이미지 재빌드 없이 교체 가능하게)