Kubernetes

Kubernetes

Haystack 파이프라인을 Kubernetes로 배포하는 방법을 살펴볼게요. 컨테이너 오케스트레이터(Kubernetes 같은)에서 Haystack을 워크로드로 돌리는 가장 좋은 방법은, 한 개 이상의 Hayhooks 인스턴스를 노출하는 서비스를 만드는 거예요.

출처: Kubernetes

본문

Hayhooks로 Haystack Kubernetes 서비스 만들기

첫 단계로 로컬 KinD 또는 Minikube Kubernetes 클러스터를 만드는 걸 권장해요. 클러스터는 CLI로 관리할 수도 있지만, k9s나 Lens 같은 도구를 쓰면 더 편해요.

준비가 끝나면, Hayhooks Pod 한 개를 돌리는 아주 단순한 Kubernetes Service부터 시작해 볼게요.

kind: Pod
apiVersion: v1
metadata:
  name: hayhooks
  labels:
    app: haystack
spec:
  containers:
  - image: deepset/hayhooks:v1.23.0
    name: hayhooks
    imagePullPolicy: IfNotPresent
    resources:
      limits:
        memory: "512Mi"
        cpu: "500m"
      requests:
        memory: "256Mi"
        cpu: "250m"

---
kind: Service
apiVersion: v1
metadata:
  name: haystack-service
spec:
  selector:
    app: haystack
  type: ClusterIP
  ports:
  # Default port used by the Hayhooks Docker image
  - port: 1416

위 구성을 기존 Kubernetes 클러스터에 적용하면, hayhooks Pod가 haystack-service라는 이름의 Service로 나타나요.

여기서 정의한 Service는 ClusterIP 타입이에요. 즉 클러스터 내부에서만 노출돼요. Hayhooks API를 외부에까지 노출하려면 NodePort나 Ingress 리소스가 필요해요. 대안으로 Port Forwarding을 써서 Service에 로컬로 접근할 수도 있어요.

그러려면 KinD 클러스터의 Host-To-Node Mapping에 포트 30080을 추가해야 해요. 즉 클러스터를 다음 비슷한 노드 설정으로 만들어야 한다는 뜻이에요.

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  # ...
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30080
    protocol: TCP

그다음 Hayhooks Pod가 올바르게 동작하는지 확인하기 위해 간단한 NodePort를 만들어요.

apiVersion: v1
kind: Service
metadata:
  name: haystack-nodeport
spec:
  selector:
    app: haystack
  type: NodePort
  ports:
  - port: 1416
    targetPort: 1416
    nodePort: 30080
    name: http

이 설정을 적용하면 hayhooks Pod가 localhost:30080에서 접근 가능해져요.

여기부터는 파이프라인을 관리할 수 있어요. 단일 Hayhooks 인스턴스에 여러 개의 서로 다른 파이프라인을 배포하는 것도 가능하다는 걸 기억해 두세요. 자세한 내용은 Hayhooks 개요나 공식 Hayhooks 문서를 확인하세요.

Pod 시작 시 파이프라인 자동 실행

Hayhooks는 시작 시점에 Haystack 파이프라인을 로드해서, 서버가 시작되면 바로 사용할 수 있게 만들 수 있어요. 이 메커니즘을 활용하면 Pod가 시작하자마자 한 개 이상의 파이프라인을 즉시 서빙할 수 있어요.

시작할 때 HAYHOOKS_PIPELINES_DIR에 지정된 경로에서 배포된 파이프라인을 찾아 로드해요.

배포된 파이프라인은 기본적으로 pipeline_wrapper.py 파일과 (필요하면) 다른 파일들을 포함하는 디렉토리예요. 예시 파이프라인을 미리 로드하려면, 로컬 폴더를 클러스터 노드에 마운트한 뒤 Hayhooks Pod에서도 그 폴더를 사용할 수 있게 해야 해요.

먼저 로컬 폴더가 KinD 클러스터 노드의 /data에 올바르게 마운트됐는지 확인해요.

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  # ...
  extraMounts:
  - hostPath: /path/to/local/pipelines/folder
    containerPath: /data

다음으로 /data를 볼륨으로 사용할 수 있게 만들고 Hayhooks Pod에 마운트해요. 그러려면 이전 Pod 구성을 아래처럼 업데이트해요.

kind: Pod
apiVersion: v1
metadata:
  name: hayhooks
  labels:
    app: haystack
spec:
  containers:
  - image: deepset/hayhooks:v1.23.0
    name: hayhooks
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh", "-c"]
    args:
    - |
      pip install trafilatura && \
      hayhooks run --host 0.0.0.0
    volumeMounts:
    - name: local-data
      mountPath: /mnt/data
    env:
    - name: HAYHOOKS_PIPELINES_DIR
      value: /mnt/data
    - name: OPENAI_API_KEY
      valueFrom:
        secretKeyRef:
          name: openai-secret
          key: api-key
    resources:
      limits:
        memory: "512Mi"
        cpu: "500m"
      requests:
        memory: "256Mi"
        cpu: "250m"
    volumes:
    - name: local-data
      hostPath:
        path: /data
        type: Directory

여기서 주의할 점이 몇 가지 있어요.

  • Hayhooks 컨테이너의 command를 바꿔서, 시작 전에 trafilatura 의존성을 설치하게 했어요. 우리의 chat_with_website 예시 파이프라인에 필요하기 때문이에요. 실제 프로덕션 환경에서는 여기에 설명된 대로 커스텀 Hayhooks 이미지를 만드는 걸 권장해요.
  • Hayhooks 컨테이너가 OPENAI_API_KEY를 Kubernetes Secret에서 읽도록 했어요.

새 구성을 적용하기 전에 openai-secret을 만들어요.

apiVersion: v1
kind: Secret
metadata:
  name: openai-secret
type: Opaque
data:
  # Replace the placeholder below with the base64 encoded value of your API key
  # Generate it using: echo -n $OPENAI_API_KEY | base64
  api-key: YOUR_B...HERE

이 설정을 적용한 뒤 Hayhooks Pod 로그를 확인하면 chat_with_website 파이프라인이 이미 배포된 것을 볼 수 있어요.

여러 Pod로 확장

Haystack 파이프라인은 보통 무상태(stateless)라서, 같은 파이프라인 세트를 실행하는 여러 Pod에 요청을 분산하기에 완벽한 경우예요. 단일-Pod 구성을 실제 Kubernetes Deployment로 바꿔 볼게요.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: haystack-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: haystack
  template:
    metadata:
      labels:
        app: haystack
    spec:
      initContainers:
      - name: install-dependencies
        image: python:3.12-slim
        workingDir: /mnt/data
        command: ["/bin/bash", "-c"]
        args:
        - |
          echo "Installing dependencies..."
          pip install trafilatura
          echo "Dependencies installed successfully!"
          touch /mnt/data/init-complete
        volumeMounts:
        - name: local-data
          mountPath: /mnt/data
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "250m"
      containers:
      - image: deepset/hayhooks:v1.23.0
        name: hayhooks
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh", "-c"]
        args:
        - |
          pip install trafilatura && \
          hayhooks run --host 0.0.0.0
        ports:
        - containerPort: 1416
          name: http
        volumeMounts:
        - name: local-data
          mountPath: /mnt/data
        env:
        - name: HAYHOOKS_PIPELINES_DIR
          value: /mnt/data
        - name: OPENAI_API_KEY
          valueFrom:
            secretKeyRef:
              name: openai-secret
              key: api-key
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        volumes:
        - name: local-data
          hostPath:
            path: /data
            type: Directory

이 구성을 적용하면 Pod 세 개가 만들어져요. 각 Pod는 서로 다른 Hayhooks 인스턴스를 실행하지만, 모두 이전 예시에서 마운트한 볼륨이 제공하는 같은 예시 파이프라인을 서빙해요.

앞서 만든 NodePort는 이제 로드 밸런서 역할을 해서, 들어오는 요청을 세 개의 Hayhooks Pod에 분산할 거예요.

더 알아보기 (Learn more)