Ollama K8S에 배포하기

Kubernetes에 Ollama(LLM REST API)를 배포

배경

비즈니스와 실제 시나리오에서 서비스의 확장성과 고가용성은 매우 중요하다. Kubernetes는 이러한 작업을 조율하는 도구로 각광받고 있다. 여러 모델을 선택할 수 있는 LLM을 REST API로 배포하고 이를 확장할 수 있다면 어떨까? 하는 생각에 시도해보았다.

환경

  • Firebat AK2 Plus
    • CPU : Intel N100
    • RAM : 16GB
    • storage : 512GB
    • OS : Rocky Linux 9

배포

namespace

ollama_ns.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: ollama
$ kubectl apply -f ollama_ns.yaml

deployment

ollama_deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ollama
  namespace: ollama
spec:
  selector:
    matchLabels:
      name: ollama
  template:
    metadata:
      labels:
        name: ollama
    spec:
      containers:
      - name: ollama
        image: ollama/ollama:latest
        ports:
        - name: ollamaport
          containerPort: 11434
          protocol: TCP
$ kubectl apply -f ollama_deploy.yaml

service

ollama_service.yaml

apiVersion: v1
kind: Service
metadata:
  name: ollama
  namespace: ollama
spec:
  type: ClusterIP
  selector:
    name: ollama
  ports:
  - port: 11434
    name: ollamaport
    targetPort: ollamaport
    protocol: TCP
$ kubectl apply -f ollama_service.yaml

기본적으로 네임스페이스, 디플로이먼트, 서비스를 올렸다.
배포가 정상적으로 되면 테스트해보자.

포트포워딩

$ kubectl -n ollama port-forward service/ollama 11434:11434 &
[1] 1980276
Forwarding from 127.0.0.1:11434 -> 11434
Forwarding from [::1]:11434 -> 11434

포트포워딩은 끄면 더 이상 동작하지 않기 때문에, 백그라운드로 실행해둔다.

테스트

테스트용으로 업로드한 EEVE-Korean-Instruct-10.8B-v1.0-Q4_0 모델을 실행한다.
커스텀 모델 업로드하는 방법은 다른 글에서 다룰 예정

$ curl http://localhost:11434/api/generate -d '{
    "model": "EEVE-Korean-Instruct-10.8B-v1.0-Q4_0",
    "prompt": "오늘 하루의 응원 문구를 하나 추천해줘"
    }'

응답은 아래와 같다.

{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:40:55.627097996Z","response":"\"","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:40:56.159037675Z","response":"당","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:40:56.694890053Z","response":"신의","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:40:57.235140309Z","response":" 꿈을","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:40:57.786867691Z","response":" 향해","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:40:58.326435132Z","response":" 나아가","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:40:58.864553471Z","response":"세요","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:40:59.409821047Z","response":"!","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:40:59.965482751Z","response":" 하늘","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:41:00.502485428Z","response":"은","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:41:01.058521242Z","response":" 한계가","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:41:01.622901471Z","response":" 아","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:41:02.18777015Z","response":"닙","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:41:02.751312498Z","response":"니다","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:41:03.319721477Z","response":"!\"","done":false}
{"model":"EEVE-Korean-Instruct-10.8B-v1.0-Q4_0","created_at":"2024-07-04T12:41:03.880403883Z","response":"","done":true,
"done_reason":"stop",
"context":[28705,13,1,330,10706,1444,264,13903,2188,304,396,18278,10895,13892,28723,415,13892,5212,10865,28725,10537,28725,304,27057,11194,298,272,2188,28742,28713,4224,28723,32321,35118,29538,36190,28732,28796,431,276,28731,32004,35118,29426,38146,28723,2,28705,13,1,10649,28747,13,35553,33186,29187,35999,32094,33557,32234,33260,29426,38146,2,28705,13,1,21631,28747,13,28739,30287,34677,35697,34680,34997,32649,28808,34199,29538,37579,32014,40063,32012,2781],
"total_duration":33565547597,
"load_duration":1534955218,
"prompt_eval_count":71,
"prompt_eval_duration":23734876000,
"eval_count":16,
"eval_duration":8253303000}

응답이 한 번에 생성되어 돌아오는 것이 아닌,
ChatGPT처럼 생성되는 대로 온다.
사용성 측면에서 더 나을 것으로 보인다.

시간 측정

응답에 duration들이 포함되어있지만, time 명령어를 사용하여 시간을 측정해보자.

$ time curl -o /dev/null -s -w "%{time_total}\n" http://localhost:11434/api/generate -d '{
  "model": "EEVE-Korean-Instruct-10.8B-v1.0-Q4_0",
  "prompt": "오늘 하루의 응원 문구를 하나 추천해줘"
}'
33.662070

real    0m33.668s
user    0m0.000s
sys     0m0.008s

해당 환경에선 약 30초 정도 소요된다.