Helm 차트

Helm 차트 (Helm Charts)

이 디렉토리는 vLLM 애플리케이션을 쿠버네티스(Kubernetes)에 배포하기 위한 Helm 차트를 담고 있습니다. Deployment·자동 스케일링(HPA)·리소스 관리·프로브·S3 모델 다운로드·PV/PVC·커스텀 init 컨테이너 등 운영에 필요한 설정을 템플릿으로 제공합니다.

출처: 문서

본문

소스: https://github.com/vllm-project/vllm/tree/main/examples/deployment/chart-helm

이 디렉토리는 vLLM 애플리케이션을 배포하기 위한 Helm 차트를 포함합니다. 차트에는 deployment, autoscaling, resource management 등에 대한 설정이 들어 있습니다.

파일 구성 (Files)

  • Chart.yaml — 차트 이름·버전·maintainer 같은 차트 메타데이터 정의.
  • ct.yaml — chart testing 구성.
  • lintconf.yaml — YAML 파일 린팅 규칙.
  • values.schema.jsonvalues.yaml 검증용 JSON 스키마.
  • values.yaml — Helm 차트 기본값.
  • templates/_helpers.tpl — 공통 설정을 정의하는 헬퍼 템플릿.
  • templates/configmap.yaml — ConfigMap 생성 템플릿.
  • templates/custom-objects.yaml — 커스텀 쿠버네티스 오브젝트 템플릿.
  • templates/deployment.yaml — Deployment 생성 템플릿.
  • templates/hpa.yaml — Horizontal Pod Autoscaler 템플릿.
  • templates/job.yaml — Kubernetes Job 템플릿.
  • templates/poddisruptionbudget.yaml — Pod Disruption Budget 템플릿.
  • templates/pvc.yaml — Persistent Volume Claim 템플릿.
  • templates/secrets.yaml — Kubernetes Secret 템플릿.
  • templates/service.yaml — Service 생성 템플릿.

테스트 실행 (Running Tests)

이 차트는 helm-unittest를 사용한 단위 테스트를 포함합니다. 플러그인을 설치하고 테스트를 실행합니다.

# Install plugin
helm plugin install https://github.com/helm-unittest/helm-unittest

# Run tests
helm unittest .

예제 자료 (Example materials)

.helmignore

*.png
.git/
ct.yaml
lintconf.yaml
values.schema.json
/workflows

Chart.yaml

apiVersion: v2
name: chart-vllm
description: Chart vllm

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.0.2

maintainers:
  - name: mfournioux

ct.yaml

chart-dirs:
  - charts
validate-maintainers: false

lintconf.yaml

---
rules:
  braces:
    min-spaces-inside: 0
    max-spaces-inside: 0
    min-spaces-inside-empty: -1
    max-spaces-inside-empty: -1
  brackets:
    min-spaces-inside: 0
    max-spaces-inside: 0
    min-spaces-inside-empty: -1
    max-spaces-inside-empty: -1
  colons:
    max-spaces-before: 0
    max-spaces-after: 1
  commas:
    max-spaces-before: 0
    min-spaces-after: 1
    max-spaces-after: 1
  comments:
    require-starting-space: true
    min-spaces-from-content: 2
  document-end: disable
  document-start: disable           # No --- to start a file
  empty-lines:
    max: 2
    max-start: 0
    max-end: 0
  hyphens:
    max-spaces-after: 1
  indentation:
    spaces: consistent
    indent-sequences: whatever      # - list indentation will handle both indentation and without
    check-multi-line-strings: false
  key-duplicates: enable
  line-length: disable              # Lines can be any length
  new-line-at-end-of-file: disable
  new-lines:
    type: unix
  trailing-spaces: enable
  truthy:
    level: warning

templates/_helpers.tpl

{{/*
Define ports for the pods
*/}}
{{- define "chart.container-port" -}}
{{-  default "8000" .Values.containerPort }}
{{- end }}

{{/*
Define service name
*/}}
{{- define "chart.service-name" -}}
{{-  if .Values.serviceName }}
{{-    .Values.serviceName | lower | trim }}
{{-  else }}
{{-    printf "%s-service" .Release.Name }}
{{-  end }}
{{- end }}

{{/*
Define deployment name
*/}}
{{- define "chart.deployment-name" -}}
{{- printf "%s-deployment-vllm" .Release.Name }}
{{- end }}

{{/*
Define service port
*/}}
{{- define "chart.service-port" -}}
{{-  if .Values.servicePort }}
{{-    .Values.servicePort }}
{{-  else }}
{{-    include "chart.container-port" . }}
{{-  end }}
{{- end }}

{{/*
Define service port name
*/}}
{{- define "chart.service-port-name" -}}
"service-port"
{{- end }}

{{/*
Define container port name
*/}}
{{- define "chart.container-port-name" -}}
"container-port"
{{- end }}

{{/*
Define deployment strategy
*/}}
{{- define "chart.strategy" -}}
strategy:
{{-   if not .Values.deploymentStrategy }}
  rollingUpdate:
    maxSurge: 100%
    maxUnavailable: 0
{{-   else }}
{{      toYaml .Values.deploymentStrategy | indent 2 }}
{{-   end }}
{{- end }}

{{/*
Define additional ports
*/}}
{{- define "chart.extraPorts" }}
{{-   with .Values.extraPorts }}
{{      toYaml . }}
{{-   end }}
{{- end }}

{{/*
Define chart external ConfigMaps and Secrets
*/}}
{{- define "chart.externalConfigs" -}}
{{-   with .Values.externalConfigs -}}
{{      toYaml . }}
{{-   end }}
{{- end }}

{{/*
Define liveness et readiness probes
*/}}
{{- define "chart.probes" -}}
{{-   if .Values.readinessProbe  }}
readinessProbe:
{{-     with .Values.readinessProbe }}
{{-       toYaml . | nindent 2 }}
{{-     end }}
{{-   end }}
{{-   if .Values.livenessProbe  }}
livenessProbe:
{{-     with .Values.livenessProbe }}
{{-       toYaml . | nindent 2 }}
{{-     end }}
{{-   end }}
{{- end }}

{{/*
Define resources
*/}}
{{- define "chart.resources" -}}
requests:
  memory: {{ required "Value 'resources.requests.memory' must be defined !" .Values.resources.requests.memory | quote }}
  cpu: {{ required "Value 'resources.requests.cpu' must be defined !" .Values.resources.requests.cpu | quote }}
  {{- if and (gt (int (index .Values.resources.requests "nvidia.com/gpu")) 0) (gt (int (index .Values.resources.limits "nvidia.com/gpu")) 0) }}
  nvidia.com/gpu: {{ required "Value 'resources.requests.nvidia.com/gpu' must be defined !" (index .Values.resources.requests "nvidia.com/gpu") | quote }}
  {{- end }}
limits:
  memory: {{ required "Value 'resources.limits.memory' must be defined !" .Values.resources.limits.memory | quote }}
  cpu: {{ required "Value 'resources.limits.cpu' must be defined !" .Values.resources.limits.cpu | quote }}
  {{- if and (gt (int (index .Values.resources.requests "nvidia.com/gpu")) 0) (gt (int (index .Values.resources.limits "nvidia.com/gpu")) 0) }}
  nvidia.com/gpu: {{ required "Value 'resources.limits.nvidia.com/gpu' must be defined !" (index .Values.resources.limits "nvidia.com/gpu") | quote }}
  {{- end }}
{{- end }}

{{/*
Define User used for the main container
*/}}
{{- define "chart.user" }}
{{-   if .Values.image.runAsUser  }}
runAsUser: 
{{-     with .Values.runAsUser }}
{{-       toYaml . | nindent 2 }}
{{-     end }}
{{-   end }}
{{- end }}

{{- define "chart.extraInitEnv" -}}
- name: S3_ENDPOINT_URL
  valueFrom:
    secretKeyRef:
      name: {{ .Release.Name }}-secrets
      key: s3endpoint
- name: S3_BUCKET_NAME
  valueFrom:
    secretKeyRef:
      name: {{ .Release.Name }}-secrets
      key: s3bucketname
- name: AWS_ACCESS_KEY_ID
  valueFrom:
    secretKeyRef:
      name: {{ .Release.Name }}-secrets
      key: s3accesskeyid
- name: AWS_SECRET_ACCESS_KEY
  valueFrom:
    secretKeyRef:
      name: {{ .Release.Name }}-secrets
      key: s3accesskey
{{- if .Values.extraInit.s3modelpath }}
- name: S3_PATH
  value: "{{ .Values.extraInit.s3modelpath }}"
{{- end }}
{{- if hasKey .Values.extraInit "awsEc2MetadataDisabled" }}
- name: AWS_EC2_METADATA_DISABLED
  value: "{{ .Values.extraInit.awsEc2MetadataDisabled }}"
{{- end }}
{{- end }}

{{/*
  Define chart labels
*/}}
{{- define "chart.labels" -}}
{{-   with .Values.labels -}}
{{      toYaml . }}
{{-   end }}
{{- end }}

templates/configmap.yaml

{{- if .Values.configs -}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: "{{ .Release.Name }}-configs"
  namespace: {{ .Release.Namespace }}
data:
  {{- with .Values.configs }}
  {{- toYaml . | nindent 2 }}
  {{- end }}
{{- end -}}

templates/custom-objects.yaml

{{- if .Values.customObjects }}
{{- range .Values.customObjects }}
{{- tpl (. | toYaml) $ }}
---
{{- end }}
{{- end }}

templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "chart.deployment-name" . | quote }}
  namespace: {{ .Release.Namespace }}
  labels:
  {{- include "chart.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  {{- include "chart.strategy" . | nindent 2 }}
  selector:
    matchLabels:
      {{- include "chart.labels" . | nindent 6 }}
  progressDeadlineSeconds: 1200
  template:
    metadata:
      labels:
        {{- include "chart.labels" . | nindent 8 }}
    spec:
      containers:
        - name: "vllm"
          image: "{{ required "Required value 'image.repository' must be defined !" .Values.image.repository }}:{{ required "Required value 'image.tag' must be defined !" .Values.image.tag }}"
          {{- if .Values.image.command }}
          command :
            {{- with .Values.image.command }}
            {{- toYaml . | nindent 10 }}
            {{- end }}
          {{- end }}
          securityContext:
            {{- if .Values.image.securityContext }}
              {{- with .Values.image.securityContext }}
              {{- toYaml . | nindent 12 }}
              {{- end }}
            {{- else }}
            runAsNonRoot: false
              {{- include "chart.user" . | indent 12 }}
            {{- end }}
          imagePullPolicy: IfNotPresent
          {{- if .Values.image.env }}
          env :
            {{- with .Values.image.env }}
            {{- toYaml . | nindent 10 }}
            {{- end }}
          {{- else }}
          env: []
          {{- end }}
          {{- if or .Values.externalConfigs .Values.configs .Values.secrets }}
          envFrom:
            {{- if .Values.configs }}
            - configMapRef:
                name: "{{ .Release.Name }}-configs"
            {{- end }}
            {{- if .Values.secrets}}
            - secretRef:
                name: "{{ .Release.Name }}-secrets"
            {{- end }}
            {{- include "chart.externalConfigs" . | nindent 12 }}
          {{- end }}          
          ports:
            - name: {{ include "chart.container-port-name" . }}
              containerPort: {{ include "chart.container-port" . }}
            {{- include "chart.extraPorts" . | nindent 12 }}
          {{- include "chart.probes" . | indent 10 }}
          resources: {{- include "chart.resources" . | nindent 12 }}
          volumeMounts:
          - name: {{ .Release.Name }}-storage
            mountPath: /data

        {{- with .Values.extraContainers }}
        {{ toYaml . | nindent 8 }}
        {{- end }}

      {{- if and .Values.extraInit (or .Values.extraInit.modelDownload.enabled .Values.extraInit.initContainers) }}
      initContainers:
      {{- if .Values.extraInit.modelDownload.enabled }}
      - name: wait-download-model
        image: {{ .Values.extraInit.modelDownload.image.repository }}:{{ .Values.extraInit.modelDownload.image.tag }}
        imagePullPolicy: {{ .Values.extraInit.modelDownload.image.pullPolicy }}
        command: {{ .Values.extraInit.modelDownload.waitContainer.command | toJson }}
        args:
        {{- toYaml .Values.extraInit.modelDownload.waitContainer.args | nindent 10 }}
        env:
        {{- if .Values.extraInit.modelDownload.waitContainer.env }}
        {{- toYaml .Values.extraInit.modelDownload.waitContainer.env | nindent 10 }}
        {{- else }}
        {{- include "chart.extraInitEnv" . | nindent 10 }}
        {{- end }}
        resources:
          requests:
            cpu: 200m
            memory: 1Gi
          limits:
            cpu: 500m
            memory: 2Gi
        volumeMounts:
        - name: {{ .Release.Name }}-storage
          mountPath: /data
      {{- end }}
      {{- with .Values.extraInit.initContainers }}
      {{- toYaml . | nindent 6 }}
      {{- end }}
      {{- end }}
      volumes:
        - name: {{ .Release.Name }}-storage
          persistentVolumeClaim:
            claimName: {{ .Release.Name }}-storage-claim     

      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- if and (gt (int (index .Values.resources.requests "nvidia.com/gpu")) 0) (gt (int (index .Values.resources.limits "nvidia.com/gpu")) 0) }}
      runtimeClassName: nvidia
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                - key: nvidia.com/gpu.product
                  operator: In
                  {{- with .Values.gpuModels }}
                  values:
                    {{- toYaml . | nindent 20 }}
                  {{- end }}
      {{- end }}

templates/hpa.yaml

{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: "{{ .Release.Name }}-hpa"
  namespace: {{ .Release.Namespace }}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: {{ include "chart.deployment-name" . | quote }}
  minReplicas: {{ .Values.autoscaling.minReplicas }}
  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
  metrics:
    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
    {{- end }}
    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
    {{- end }}
{{- end }}

templates/job.yaml

{{- if and .Values.extraInit .Values.extraInit.modelDownload.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ .Release.Name }}-init-vllm"
  namespace: {{ .Release.Namespace }}
spec:
  ttlSecondsAfterFinished: 100
  template:
   metadata:
     name: init-vllm
   spec:
    containers:
    - name: job-download-model
      image: {{ .Values.extraInit.modelDownload.image.repository }}:{{ .Values.extraInit.modelDownload.image.tag }}
      imagePullPolicy: {{ .Values.extraInit.modelDownload.image.pullPolicy }}
      command: {{ .Values.extraInit.modelDownload.downloadJob.command | toJson }}
      args:
      {{- toYaml .Values.extraInit.modelDownload.downloadJob.args | nindent 8 }}
      env:
      {{- if .Values.extraInit.modelDownload.downloadJob.env }}
      {{- toYaml .Values.extraInit.modelDownload.downloadJob.env | nindent 8 }}
      {{- else }}
      {{- include "chart.extraInitEnv" . | nindent 8 }}
      {{- end }}
      volumeMounts:
        - name: {{ .Release.Name }}-storage
          mountPath: /data
      resources:
        requests:
          cpu: 200m
          memory: 1Gi
        limits:
          cpu: 500m
          memory: 2Gi
    restartPolicy: OnFailure
    volumes:
    - name: {{ .Release.Name }}-storage
      persistentVolumeClaim:
        claimName: "{{ .Release.Name }}-storage-claim"
{{- end }}

templates/poddisruptionbudget.yaml

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: "{{ .Release.Name }}-pdb"
  namespace: {{ .Release.Namespace }}
spec:
  maxUnavailable: {{ default 1 .Values.maxUnavailablePodDisruptionBudget }}

templates/pvc.yaml

{{-   if .Values.extraInit  }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: "{{ .Release.Name }}-storage-claim"
  namespace: {{ .Release.Namespace }}
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: {{ .Values.extraInit.pvcStorage }}
{{- end }}

templates/secrets.yaml

apiVersion: v1
kind: Secret
metadata:
  name: "{{ .Release.Name }}-secrets"
  namespace: {{ .Release.Namespace }}
type: Opaque
data:
  {{- range $key, $val := .Values.secrets }}
  {{ $key }}: {{ $val | b64enc | quote }}
  {{- end }}

templates/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: {{ include "chart.service-name" . | quote }}
  namespace: {{ .Release.Namespace }}
spec:
  type: ClusterIP
  ports:
    - name: {{ include "chart.service-port-name" . }}
      port: {{ include "chart.service-port" . }}
      targetPort: {{ include "chart.container-port-name" . }}
      protocol: TCP
  selector:
  {{- include "chart.labels" . | nindent 4 }}

values.yaml (기본값)

차트의 기본값은 다음과 같습니다. 이미지·컨테이너 포트·서비스·레플리카·리소스(GPU 포함)·자동 스케일링·S3 모델 다운로드(init)·프로브·레이블을 설정합니다.

# -- Default values for chart vllm
# -- Declare variables to be passed into your templates.

# -- Image configuration
image:
  # -- Image repository
  repository: "vllm/vllm-openai"
  # -- Image tag
  tag: "latest"
  # -- Container launch command
  command: ["vllm", "serve", "/data/", "--served-model-name", "opt-125m", "--enforce-eager", "--dtype", "bfloat16", "--block-size", "16", "--host", "0.0.0.0", "--port", "8000"]

# -- Container port
containerPort: 8000
# -- Service name
serviceName:
# -- Service port
servicePort: 80
# -- Additional ports configuration
extraPorts: []

# -- Number of replicas
replicaCount: 1

# -- Deployment strategy configuration
deploymentStrategy: {}

# -- Resource configuration
resources:
  requests:
    # -- Number of CPUs
    cpu: 4
    # -- CPU memory configuration
    memory: 16Gi
    # -- Number of gpus used
    nvidia.com/gpu: 1
  limits:
    # -- Number of CPUs
    cpu: 4
    # -- CPU memory configuration
    memory: 16Gi
    # -- Number of gpus used
    nvidia.com/gpu: 1

# -- Type of gpu used
gpuModels:
  - "TYPE_GPU_USED"

# -- Autoscaling configuration
autoscaling:
  # -- Enable autoscaling
  enabled: false
  # -- Minimum replicas
  minReplicas: 1
  # -- Maximum replicas
  maxReplicas: 100
  # -- Target CPU utilization for autoscaling
  targetCPUUtilizationPercentage: 80
  # targetMemoryUtilizationPercentage: 80

# -- Configmap
configs: {}

# -- Secrets configuration
secrets: {}

# -- External configuration
externalConfigs: []

# -- Custom Objects configuration
customObjects: []

# -- Disruption Budget Configuration
maxUnavailablePodDisruptionBudget: ""

# -- Additional configuration for the init container
extraInit:
  # -- Model download functionality (optional)
  modelDownload:
    # -- Enable model download job and wait container
    enabled: true
    # -- Image configuration for model download operations
    image:
      # -- Image repository
      repository: "amazon/aws-cli"
      # -- Image tag
      tag: "2.6.4"
      # -- Image pull policy
      pullPolicy: "IfNotPresent"
    # -- Wait container configuration (init container that waits for model to be ready)
    waitContainer:
      # -- Command to execute
      command: ["/bin/bash"]
      # -- Arguments for the wait container
      args:
        - "-eucx"
        - "while aws --endpoint-url $S3_ENDPOINT_URL s3 sync --dryrun s3://$S3_BUCKET_NAME/$S3_PATH /data | grep -q download; do sleep 10; done"
      # -- Environment variables (optional, overrides S3 defaults entirely if specified)
      # env:
      #   - name: HUGGING_FACE_HUB_TOKEN
      #     value: "your-token"
      #   - name: MODEL_ID
      #     value: "meta-llama/Llama-2-7b"
    # -- Download job configuration (job that actually downloads the model)
    downloadJob:
      # -- Command to execute
      command: ["/bin/bash"]
      # -- Arguments for the download job
      args:
        - "-eucx"
        - "aws --endpoint-url $S3_ENDPOINT_URL s3 sync s3://$S3_BUCKET_NAME/$S3_PATH /data"
      # -- Environment variables (optional, overrides S3 defaults entirely if specified)
      # env:
      #   - name: HUGGING_FACE_HUB_TOKEN
      #     value: "your-token"
      #   - name: MODEL_ID
      #     value: "meta-llama/Llama-2-7b"

  # -- Custom init containers (appended after wait-download-model if modelDownload is enabled)
  initContainers: []
  # Example for llm-d sidecar:
  # initContainers:
  #   - name: llm-d-routing-proxy
  #     image: ghcr.io/llm-d/llm-d-routing-sidecar:v0.2.0
  #     imagePullPolicy: IfNotPresent
  #     ports:
  #       - containerPort: 8080
  #         name: proxy
  #     securityContext:
  #       runAsUser: 1000

  # -- Path of the model on the s3 which hosts model weights and config files
  s3modelpath: "relative_s3_model_path/opt-125m"
  # -- Storage size for the PVC
  pvcStorage: "1Gi"
  # -- Disable AWS EC2 metadata service
  awsEc2MetadataDisabled: true

# -- Additional containers configuration
extraContainers: []

# -- Readiness probe configuration
readinessProbe:
  # -- Number of seconds after the container has started before readiness probe is initiated
  initialDelaySeconds: 5
  # -- How often (in seconds) to perform the readiness probe
  periodSeconds: 5
  # -- Number of times after which if a probe fails in a row, Kubernetes considers that the overall check has failed: the container is not ready
  failureThreshold: 3
   # -- Configuration of the Kubelet http request on the server
  httpGet:
    # -- Path to access on the HTTP server
    path: /health
    # -- Name or number of the port to access on the container, on which the server is listening
    port: 8000

# -- Liveness probe configuration
livenessProbe:
 # -- Number of seconds after the container has started before liveness probe is initiated
  initialDelaySeconds: 15
  # -- Number of times after which if a probe fails in a row, Kubernetes considers that the overall check has failed: the container is not alive
  failureThreshold: 3
  # -- How often (in seconds) to perform the liveness probe
  periodSeconds: 10
  # -- Configuration of the Kubelet http request on the server
  httpGet:
    # -- Path to access on the HTTP server
    path: /health
    # -- Name or number of the port to access on the container, on which the server is listening
    port: 8000

labels:
  environment: "test"
  release: "test"

동작 요약

  • vllm serve/data/에서 실행하며 --served-model-name opt-125m, --enforce-eager, --dtype bfloat16, --block-size 16, 포트 8000으로 기동합니다.
  • nvidia.com/gpu 요청/한도가 0보다 크면 runtimeClassName: nvidia + nvidia.com/gpu.product node affinity를 적용합니다.
  • extraInit.modelDownload.enabled=true면 Deployment에 wait-download-model init 컨테이너 + Job(job-download-modelamazon/aws-cli로 S3에서 모델을 /data로 sync)을 생성합니다. initContainers를 추가로 지정하면 함께 주입됩니다.
  • values.schema.json으로 values.yaml을 JSON 스키마 검증합니다(이미지·리소스·autoscaling·extraInit 등 required).
  • tests/에 helm-unittest 단위 테스트(deployment/hpa/job/pvc/service)가 포함되어 있으며, helm unittest .로 검증합니다.

더 알아보기 (Learn more)