배포 가이드¶
이 가이드는 저장소에 실제로 존재하는 배포 표면인 릴리스 바이너리, 공식 컨테이너 이미지, Docker Compose, 유지 관리되는 Kubernetes/Kustomize 자산, Helm 차트, 사용자 정의 systemd 유닛을 설명합니다. Debian 패키지는 systemd 유닛을 설치하지 않습니다.
배포 전 준비¶
설정을 만들고 검증합니다.
continuum-router config generate --template production-ha --output config.yaml
# 템플릿이 참조하는 필수 환경 변수를 설정합니다.
continuum-router config validate config.yaml
더 작은 출발점에는 --template minimal을 사용하십시오. 사용 가능한 템플릿은 minimal, development, multi-provider, production-ha, api-gateway, kv-cache-optimized, smart-routing, disaggregated, cost-optimized, sglang, ab-testing입니다.
외부 리스너가 필요하지 않으면 루프백에 바인딩하십시오. 신뢰하지 않는 네트워크에 노출하기 전 API 및 Admin 인증을 활성화하십시오. 라우터는 평문 HTTP를 제공하므로 신뢰할 수 있는 리버스 프록시, 인그레스 컨트롤러 또는 로드 밸런서에서 TLS를 종료하십시오.
릴리스 기능 세트¶
기본 소스 빌드는 Cargo의 full 기능 세트를 사용합니다. 공식 릴리스 바이너리와 공식 컨테이너 이미지는 여기에 control-plane, appproxy-router, redis-cache를 추가로 컴파일하며 셋 다 런타임 opt-in입니다. appproxy-legacy, bedrock-sigv4, s3-cache는 릴리스 워크플로가 명시적으로 추가하지 않는 한 소스 빌드 opt-in입니다.
redis-cache가 공식 세트에 들어온 이유는 여러 replica로 구성한 배포가 사용자 정의 빌드 없이 속도 제한 counter와 응답 캐시를 공유할 수 있게 하기 위해서입니다. 컴파일 자체로는 아무것도 활성화되지 않습니다. rate_limiting.storage: redis 또는 response_cache.backend: redis가 엔드포인트를 지정할 때만 라우터가 Redis에 접속합니다. 공유 Redis 상태를 참고하십시오.
런타임 설정으로 컴파일되지 않은 코드를 활성화할 수 없습니다. 기능 제한 배포를 진단할 때 Admin 자격 증명으로 /admin/capabilities를 확인하십시오.
Docker¶
릴리스 워크플로가 멀티 아키텍처 이미지 하나를 GitHub Container Registry에 게시합니다.
ghcr.io/lablup/continuum-router:<version>: 고정 버전ghcr.io/lablup/continuum-router:<major>.<minor>: 해당 마이너의 최신 패치latest: 최신 비프리릴리스
프로덕션에서는 latest 대신 버전이나 digest를 고정하십시오.
이미지는 gcr.io/distroless/static-debian12 위에 정적 링크된 musl 바이너리와 passwd 파일, 타임존 데이터만 담습니다. 셸과 패키지 관리자는 들어 있지 않습니다. 라우터가 rustls를 번들 신뢰 루트와 함께 링크하므로 시스템 인증서 저장소가 없어도 제공자로 HTTPS 요청을 보낼 수 있고, 그래서 CA 인증서 패키지도 설치하지 않습니다. 셸이 없는 컨테이너를 디버깅하는 방법은 셸 붙이기를 참고하십시오.
docker run --rm \
-p 8080:8080 \
-v "$PWD/config.yaml:/etc/continuum-router/config.yaml:ro" \
-e OPENAI_API_KEY \
ghcr.io/lablup/continuum-router:1.28.0
이미지는 uid 65532(distroless가 정의하는 nonroot 사용자)로 실행되고 --config /etc/continuum-router/config.yaml로 시작하며 내장 --health-check 명령을 컨테이너 상태 검사에 사용합니다.
Docker Compose¶
저장소에는 docker-compose.yml이 있습니다. ./config.yaml과 VERSION 환경 변수를 사용합니다.
cp config.yaml.example config.yaml
# 주석 샘플을 실제 백엔드/인증 값으로 바꾼 뒤 검증합니다.
continuum-router config validate config.yaml
VERSION=1.28.0 docker compose up -d
docker compose logs -f continuum-router
Compose 파일은 ghcr.io/lablup/continuum-router:${VERSION:-latest}를 사용합니다. 마운트한 설정의 ${ENV_VAR} 참조에 제공자 자격 증명을 환경 변수로 전달하고 이미지나 Compose 파일에 비밀을 하드코딩하지 마십시오.
저장소 Dockerfile 빌드¶
Dockerfile은 일치하는 GitHub 릴리스 아카이브를 내려받습니다. 릴리스 버전을 명시하십시오.
이는 소스 빌드 Dockerfile이 아닙니다. 사용자 정의 기능 세트가 필요하면 바이너리를 별도로 컴파일한 뒤 해당 산출물을 복사하는 이미지를 만들거나 Dockerfile.ci를 의도적인 빌드 파이프라인에 맞게 수정하십시오.
Kubernetes¶
유지 관리되는 Kustomize 번들은 deploy/kubernetes/base에 있습니다. 3개 replica, 리소스 요청/제한, startup/readiness/liveness probe, ClusterIP Service, TLS Ingress, HPA, PodDisruptionBudget, 제한된 pod 보안 설정, 읽기 전용 root 파일 시스템, ingress/egress NetworkPolicy를 포함합니다. deploy/kubernetes/overlays/staging의 스테이징 오버레이는 자동 스테이징 워크플로가 사용합니다.
적용 전에 제공자/모델 설정, 이미지 태그 또는 digest, Ingress 호스트와 TLS Secret, NetworkPolicy namespace selector, egress 규칙, 리소스 크기, 신뢰할 프록시 등 운영자별 값을 모두 검토하십시오. continuum-router-secrets는 별도로 생성해야 하며 체크인된 번들은 자격 증명을 생성하거나 저장하지 않습니다.
kubectl apply -f deploy/kubernetes/base/namespace.yaml
kubectl -n continuum-router create secret generic continuum-router-secrets \
--from-literal=OPENAI_API_KEY="$OPENAI_API_KEY" \
--from-literal=ROUTER_API_KEY="$ROUTER_API_KEY" \
--from-literal=ADMIN_TOKEN="$ADMIN_TOKEN"
kubectl apply -k deploy/kubernetes/base
kubectl -n continuum-router rollout status deployment/continuum-router --timeout=5m
매니페스트를 바꾼 뒤 scripts/validate-deployment-assets.sh를 실행하십시오. 이 스크립트는 base, 스테이징 오버레이, 모니터링 번들, 모든 Helm 프로필을 렌더링하며 CI도 같은 검증을 수행합니다.
다음 최소 예시는 핵심 객체를 설명하는 용도로 유용합니다. 실제 배포의 유지 관리되는 단일 소스는 deploy/kubernetes/ 아래 파일입니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: continuum-router-config
data:
config.yaml: |
server:
bind_address: 0.0.0.0:8080
workers: 4
selection_strategy: RoundRobin
backends:
- name: ollama
type: ollama
url: http://ollama:11434
models: [llama3.2]
health_checks:
interval: 30s
timeout: 5s
unhealthy_threshold: 3
healthy_threshold: 2
endpoint: /health
logging:
level: info
format: json
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: continuum-router
spec:
replicas: 2
selector:
matchLabels:
app: continuum-router
template:
metadata:
labels:
app: continuum-router
spec:
terminationGracePeriodSeconds: 30
containers:
- name: router
image: ghcr.io/lablup/continuum-router:1.28.0
args: ["--config", "/etc/continuum-router/config.yaml"]
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: http
livenessProbe:
httpGet:
path: /health
port: http
volumeMounts:
- name: config
mountPath: /etc/continuum-router/config.yaml
subPath: config.yaml
readOnly: true
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
# server.max_concurrent_requests와 따로가 아니라 함께
# 정하십시오. 아래 "용량 산정: 메모리"를 참고하십시오. 최악의
# 상주 메모리는 동시성 곱하기 요청당 예산입니다. 파일 업로드는
# 디스크로 스트리밍되어 더 이상 이 예산을 지배하지 않지만, 파일
# 해석 상한에 닿은 요청 하나도 base64 확장 후 약 43 MiB를
# 더합니다.
memory: 1Gi
volumes:
- name: config
configMap:
name: continuum-router-config
---
apiVersion: v1
kind: Service
metadata:
name: continuum-router
spec:
selector:
app: continuum-router
ports:
- name: http
port: 8080
targetPort: http
제공자 자격 증명에는 비밀 관리자나 Kubernetes Secret-to-environment 통합을 사용하십시오. ${ENV_VAR} 확장은 라우터 프로세스 안에서 일어납니다.
/health가 실제 구현된 비인증 상태 엔드포인트입니다. /healthz는 존재하지 않습니다. /version도 공개됩니다. 메트릭은 설정한 경로와 별도 인증 설정을 사용합니다.
위 readinessProbe 예시에는 periodSeconds/failureThreshold를 명시하지 않았지만 일부 오케스트레이터와 임베딩 호스트는 /health를 고정된 준비성 데드라인으로 폴링합니다. 기본값(health_checks.block_startup: true)에서는 모든 백엔드의 연결 프리워밍과 첫 헬스 체크 라운드가 끝날 때까지 리스너 바인딩을 기다리므로 응답 없는 백엔드 하나가 그 프리바인드 구간을 좁은 데드라인 너머로 늘릴 수 있습니다. 리스너를 먼저 바인딩하고 백엔드 상태를 백그라운드에서 수렴시키려면 health_checks.block_startup: false로 설정하세요. 자세한 내용은 시작 시 동작을 참고하십시오.
종료와 롤링 업데이트¶
HTTP 서버는 SIGINT/SIGTERM을 처리해 graceful shutdown을 시작합니다. 예상 스트리밍 요청에 충분한 오케스트레이션 유예 시간을 설정하되 라우터도 백그라운드 작업과 연결에 설정된 종료 시간 제한을 적용함을 고려하십시오.
롤아웃 가용성에는 두 개 이상의 replica를 사용하십시오. 인메모리 속도 제한, 응답 캐시, 저장된 Responses 세션, 일부 통계 같은 라우터 로컬 상태는 replica 사이에 자동 공유되지 않습니다. 지원되는 경우 Redis/영속 기능을 선택하거나 인스턴스별 동작을 수용하십시오.
Helm¶
deploy/helm/continuum-router는 development, staging, canary, production values 프로필을 제공합니다. 차트는 유지 관리되는 Kustomize 번들과 같은 보안·가용성 제어를 렌더링하며 Prometheus Operator ServiceMonitor를 선택적으로 생성할 수 있습니다.
helm lint deploy/helm/continuum-router
helm template router deploy/helm/continuum-router \
--namespace continuum-router \
-f deploy/helm/continuum-router/values-production.yaml
kubectl create namespace continuum-router --dry-run=client -o yaml | kubectl apply -f -
kubectl label namespace continuum-router app.kubernetes.io/name=continuum-router --overwrite
helm upgrade --install router deploy/helm/continuum-router \
--namespace continuum-router \
-f deploy/helm/continuum-router/values-production.yaml \
--set-string image.digest="$IMAGE_DIGEST"
프로덕션 설치에서는 IMAGE_DIGEST를 승인된 sha256:... manifest digest로 설정하고 예제 호스트를 교체하며 existingSecret을 제공하고 기본 NetworkPolicy를 검토해야 합니다. 차트는 기본적으로 replica별 인메모리 속도 제한을 사용합니다. 공유 상태로 바꾸려면 환경 프로필 위에 values-shared-redis.yaml을 겹쳐 적용하십시오. 공유 Redis 상태를 참고하십시오.
자동 스테이징 배포¶
.github/workflows/deploy-staging.yml은 성공한 공개 릴리스 워크플로 실행 또는 수동으로 선택한 이미지 태그를 continuum-router-staging namespace에 배포합니다. 보호된 GitHub staging environment에 KUBECONFIG_B64, ROUTER_HOST, 필수 reviewer, 배포 branch 제한을 설정하십시오. 첫 실행 전에 continuum-router-staging namespace를 먼저 만들고 app.kubernetes.io/name=continuum-router 레이블을 지정한 뒤 그 안에 continuum-router-secrets와 continuum-router-tls를 생성해야 합니다.
워크플로는 게시된 semver 릴리스만 허용하고 manifest attestation을 검증하며 이미지를 변경 불가능한 digest로 해석합니다. 취소하지 않는 단일 concurrency group에서 server-side apply와 Deployment rollout 대기를 수행합니다. 모든 high/critical 이미지 취약점은 게시를 차단하며 빌드는 최종 멀티 아키텍처 manifest에 대한 SBOM과 provenance attestation을 게시합니다.
배포 전략¶
유지 관리되는 Deployment는 maxUnavailable: 0인 롤링 업데이트를 사용합니다. 롤백할 때는 helm rollback을 실행하거나 이전의 변경 불가능한 이미지를 다시 적용하십시오. 블루-그린 배포는 후보를 별도 release로 설치하고 상태와 메트릭을 확인한 뒤 ingress나 상위 Service를 원자적으로 전환하며 관찰 기간 동안 이전 release를 유지합니다.
카나리는 values-canary.yaml과 변경 불가능한 후보 이미지로 두 번째 release를 설치합니다. 이 프로필은 별도 Ingress, HPA, disruption budget 없이 레이블이 지정된 replica 하나를 만듭니다. Ingress controller나 service mesh에서 해당 release의 Service에 작은 명시적 가중치를 할당하고 오류율과 지연 시간을 관찰한 뒤 기본 release를 업그레이드해 승격하십시오. 차트는 특정 제품의 가중 라우팅 annotation을 가정하지 않습니다. 설정 및 기능 롤아웃도 같은 두 release 패턴을 사용할 수 있으며 release가 겹치는 동안 라우터 로컬 session, cache, 통계, 속도 제한 counter를 고려해야 합니다.
Argo Rollouts 기반 자동 점진적 배포¶
위의 전략들은 컨트롤러에 의존하지 않으며 앞으로도 그렇습니다. 기본 차트, Kustomize 번들, values-canary.yaml, 블루-그린 두 release 전환은 모두 Kubernetes 외에 아무것도 설치하지 않은 상태에서 동작합니다. 대신 자동화가 없습니다. 트래픽 전환, 메트릭 분석, promote, 롤백이 전부 수동이라 계획된 릴리스에는 충분하지만 새벽 3시에는 곤란합니다.
차트는 그 일을 Argo Rollouts에 넘길 수 있습니다. 기본값은 비활성이며 값 하나로 켭니다.
활성화하기¶
컨트롤러와 CRD가 클러스터에 먼저 있어야 합니다. 없으면 API 서버가 Rollout 객체를 거부합니다.
그다음 환경 프로필 위에 오버레이를 겹칩니다.
helm upgrade --install router deploy/helm/continuum-router \
--namespace continuum-router \
-f deploy/helm/continuum-router/values-production.yaml \
-f deploy/helm/continuum-router/values-progressive-delivery.yaml \
--set-string image.digest="$IMAGE_DIGEST"
렌더링된 manifest에서 달라지는 부분입니다.
| 객체 | 컨트롤러 비활성(기본) | 컨트롤러 활성 |
|---|---|---|
| Workload | Deployment | Rollout (Deployment는 아무것도 렌더링하지 않음) |
| Analysis | 없음 | <release>-health 이름의 AnalysisTemplate |
HPA scaleTargetRef | apps/v1 Deployment | argoproj.io/v1alpha1 Rollout |
| Service, Ingress, PDB, NetworkPolicy, ServiceMonitor | 변화 없음 | 변화 없음 |
두 workload 객체는 pod template 하나(_helpers.tpl의 continuum-router.podTemplate)를 공유하므로 한쪽에 추가한 security context, probe, volume은 다른 쪽에도 그대로 추가됩니다. 둘이 어긋날 수 없습니다.
HPA 대상 전환은 중요합니다. 더 이상 존재하지 않는 Deployment를 계속 가리키는 HPA는 아무 일도 하지 않고 라우터는 결코 스케일되지 않습니다. Rollout을 가리키면 Argo Rollouts가 원하는 replica 수를 받아 stable과 canary ReplicaSet에 직접 나눠 배분합니다.
가중치 canary step¶
progressiveDelivery.argoRollouts.steps는 strategy.canary.steps로 그대로 전달되므로 Argo Rollouts가 이해하는 step은 무엇이든 쓸 수 있습니다. production 오버레이는 10%, 30%, 60%에 단계 사이 10분 관찰 시간을 두고 제공합니다.
traffic-routing provider가 없으면 가중치는 단일 Service 뒤의 ReplicaSet pod 수로 근사됩니다. canary를 관찰하기에는 충분히 정확하며, 특정 mesh나 ingress를 가정하지 않는 범용 차트가 보장할 수 있는 최대치입니다. 가중치를 강제하려면 progressiveDelivery.argoRollouts.trafficRouting.enabled: true로 설정하고 provider 블록을 trafficRouting.spec에 넣으십시오. 그러면 차트가 <release>-canary와 <release>-stable Service를 렌더링하고 그 블록을 그대로 전달합니다.
progressiveDelivery:
argoRollouts:
trafficRouting:
enabled: true
spec:
nginx:
stableIngress: continuum-router
분석¶
AnalysisTemplate <release>-health는 canary ReplicaSet으로 범위를 좁혀 라우터 자체 메트릭에 두 가지 측정을 수행합니다.
- error-rate:
sum(rate(errors_total[2m])) / sum(rate(http_requests_total[2m])),analysis.maxErrorRate(0.05) 이하를 유지해야 합니다. - latency-p95:
histogram_quantile(0.95, ...http_request_duration_seconds_bucket...),analysis.maxLatencyP95Seconds(2.0) 이하를 유지해야 합니다.
측정을 의미 있게 만드는 것은 canary로 범위를 좁히는 일이고, 이는 레이블 하나에 달려 있습니다. 차트의 ServiceMonitor는 pod의 rollouts-pod-template-hash를 모든 시계열에 rollouts_pod_template_hash로 relabel하며 monitoring/prometheus/의 독립 번들도 같은 규칙을 담고 있습니다. 다른 곳에서 설정한 Prometheus에도 이 규칙이 필요합니다. 없으면 분석이 전체 평균을 측정하게 되고 문제 있는 canary가 정상적인 stable 트래픽 속에 묻힙니다.
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_rollouts_pod_template_hash]
target_label: rollouts_pod_template_hash
두 쿼리 모두 and sum(rate(http_requests_total{...})) > analysis.minRequestRate로 끝납니다. 그 요청률 아래에서는 쿼리가 빈 벡터를 반환해 성공 조건도 실패 조건도 맞지 않고 Argo Rollouts는 측정을 Inconclusive로 기록합니다. Inconclusive인 실행은 스스로 판단하는 대신 운영자를 기다리며 멈춥니다. 이것이 저트래픽 보호 장치이며, 근거를 보고 판단하는 canary와 밤사이 요청 세 건으로 판단하는 canary를 가릅니다. 세 건 중 한 건 실패는 오류율 33%이고, 멀쩡한 릴리스를 롤백시킵니다. analysis.inconclusiveLimit(3)은 멈추기 전까지 기다리는 한도를 정합니다.
분석은 step 사이에만 도는 것이 아니라 analysis.startingStep 이후 백그라운드에서 계속 실행되므로, 관찰 도중 나빠진 canary는 다음 step 경계까지 기다리지 않고 즉시 abort합니다.
조정은 다음 순서로 시도할 만합니다.
| 증상 | 조치 |
|---|---|
| 릴리스마다 rollout이 Inconclusive로 멈춤 | minRequestRate를 낮추거나 window를 늘리거나 첫 setWeight를 올려 canary가 더 많은 트래픽을 받게 합니다 |
| 문제가 있는 릴리스가 그대로 promote됨 | maxErrorRate/maxLatencyP95Seconds를 낮추거나 count를 올리거나 pause를 길게 잡습니다 |
| 정상 릴리스가 롤백됨 | failureLimit을 1보다 크게 올리거나 window를 늘려 스파이크를 완만하게 만들거나, p95 임계값이 이 배포로 라우팅되는 가장 느린 모델을 감안하는지 확인합니다 |
promote¶
manualPromotionPause: true(production 오버레이)이면 rollout은 마지막 가중치 step 뒤에 멈춰 무기한 대기합니다. promote는 의도적인 행위입니다.
kubectl argo rollouts get rollout router-continuum-router --watch
kubectl argo rollouts promote router-continuum-router
이 pause는 promote만 막습니다. 그동안에도 분석은 계속 돌기 때문에 승인을 기다리는 사이 나빠진 canary는 스스로 abort합니다. 무인 promote가 허용되는 곳, 예를 들어 staging에서는 manualPromotionPause: false로 설정하십시오.
롤백¶
분석이 실패하면 rollout이 자동으로 abort합니다. canary ReplicaSet은 0으로 축소되고 stable ReplicaSet이 계속 서비스하므로 기존에 돌던 버전은 한 번도 서비스에서 빠지지 않습니다. 수동으로 하려면 다음과 같습니다.
kubectl argo rollouts abort router-continuum-router # 중단하고 트래픽을 stable로 되돌립니다
kubectl argo rollouts undo router-continuum-router # 이전 revision으로 롤백합니다
의존하기 전에 실패 경로를 미리 연습하십시오. staging 설치에서 readiness에 실패하거나 오류를 반환하는 이미지를 배포하고 canary 가중치가 더 올라가지 않는 것을 지켜본 뒤, kubectl argo rollouts get rollout <name>으로 분석 실행이 Failed이고 rollout이 Degraded이며 stable ReplicaSet이 여전히 전체 replica 수를 유지하는지 확인하십시오. 마지막 확인이 핵심입니다. stable까지 함께 축소하는 abort는 롤백이 아니라 장애입니다.
설정 롤아웃¶
설정 변경도 같은 방식으로 전달되며 추가 설정이 필요 없습니다. pod template에는 렌더링된 ConfigMap에 대한 checksum/config annotation이 있으므로 routerConfig(또는 existingConfigMap이 지정한 ConfigMap)를 수정하면 pod template hash가 바뀌고, 같은 step과 같은 분석을 거치는 새 canary 진행이 시작됩니다. 잘못된 제한값, 잘못된 시간 제한, 잘못된 백엔드 목록은 잘못된 이미지와 똑같이 트래픽 10%에서 잡혀 자동으로 롤백됩니다.
여기에 해당하지 않는 것이 두 가지 있습니다. 라우터가 제자리에서 hot-reload하는 설정은 새 pod 없이 실행 중인 프로세스가 적용하므로 rollout을 통째로 우회합니다. 이런 설정을 통제하려면 설정 revision(ConfigMap 변경)으로 전달하십시오. 그리고 Cargo 기능은 컴파일 타임이므로 '기능 롤아웃'은 언제나 설정 revision이나 이미지를 롤아웃한다는 뜻이지 런타임 플래그를 켜고 끄는 것이 아닙니다. 릴리스 기능 세트를 참고하십시오.
일반 Deployment로 복구¶
점진적 배포는 되돌릴 수 있습니다. 오버레이 없이 다시 설치하십시오.
helm upgrade --install router deploy/helm/continuum-router \
--namespace continuum-router \
-f deploy/helm/continuum-router/values-production.yaml \
--set-string image.digest="$IMAGE_DIGEST"
kubectl -n continuum-router delete rollout router-continuum-router
kubectl -n continuum-router rollout status deployment/router-continuum-router --timeout=5m
Rollout 삭제는 Deployment가 올라온 뒤에 하고 그 전에 하지 마십시오. 이 순서라야 그동안 pod가 계속 서비스합니다. 컨트롤러 자체가 망가졌거나 제거 중일 때도 이것이 탈출구이며, 동작에 컨트롤러가 전혀 필요하지 않습니다.
Flagger¶
차트는 Flagger 리소스를 제공하지 않지만 Flagger가 여러분의 생태계라면 대응 관계는 단순합니다. analysis.steps를 가진 Canary가 Rollout을 대신하고, MetricTemplate과 함께 쓰는 analysis.metrics가 AnalysisTemplate을 대신하며, 같은 PromQL 쿼리 두 개와 같은 저트래픽 보호 장치가 그대로 적용됩니다. Flagger는 Deployment를 유지하고 자체 ReplicaSet을 생성하므로 여기의 deployment 가드는 필요 없습니다. progressiveDelivery.argoRollouts.enabled: false로 두고 차트와 나란히 Canary를 설치하십시오.
systemd¶
Debian 패키지는 바이너리, 예시, 문서, manpage를 설치하지만 유닛 파일은 설치하지 않습니다. systemd 관리가 필요하면 로컬 유닛을 만드십시오.
[Unit]
Description=Continuum Router
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=continuum-router
Group=continuum-router
EnvironmentFile=-/etc/continuum-router/environment
ExecStart=/usr/bin/continuum-router --config /etc/continuum-router/config.yaml
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/continuum-router /var/log/continuum-router
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
활성화한 기능에 맞게 사용자와 쓰기 가능 경로를 만든 뒤 시작 전 검증합니다.
sudo /usr/bin/continuum-router config validate /etc/continuum-router/config.yaml
sudo systemctl daemon-reload
sudo systemctl enable --now continuum-router
journalctl -u continuum-router -f
tarball 설치는 ExecStart를 /usr/local/bin/continuum-router로 바꾸십시오. Files API, 통계 영속성, OAuth 토큰 저장소, control-plane 상태, Unix 소켓, 캐시 저장소에 맞게 sandbox 경로를 조정하십시오.
용량 산정: 메모리¶
최악의 상주 메모리는 합이 아니라 곱입니다.
라우터가 집행하는 메모리 상한은 모두 오른쪽 항에 걸려 있습니다. 왼쪽 항은 server.max_concurrent_requests가 생기기 전까지 아무것도 묶지 않았으므로, 이 값을 설정하기 전에는 상한이 곧 클라이언트가 열 수 있는 연결 수입니다.
요청당 항목¶
각 항목은 요청 하나에 걸리는 독립적인 상한입니다. 요청 하나가 모든 항목에 동시에 도달하지는 않지만, 각각은 단독으로 도달할 수 있습니다.
| 항목 | 기본 상한 | 설정 키 | 비고 |
|---|---|---|---|
| 요청 본문 | 10 MiB | 설정 불가 | Files 업로드를 제외한 모든 라우트에 적용됩니다. 파싱된 serde_json::Value가 원본 바이트 위에 추가로 상주하며, 보통 원본보다 큽니다. |
| 파일 업로드 본문 | 512 MiB | files.max_file_size | 청크 단위로 디스크에 스트리밍되므로, 상한과 무관하게 전송 1건의 메모리 비용은 64 KiB 버퍼입니다. 이 상한은 디스크와 정책의 문제입니다. 업로드는 전송이 끝날 때까지 files.storage_path에서 제 크기만큼을 차지하며, 검증에서 거부될 업로드도 마찬가지입니다. files 섹션을 생략하면 활성화가 기본값입니다. |
| 요청당 해석된 파일 내용 | 원본 32 MiB, base64 확장 후 약 43 MiB | 설정 불가 | chat-completions, Anthropic Messages, Responses 경로에서 한 요청의 모든 file_id 참조를 합산한 값이며, 같은 파일을 여러 번 참조해도 참조마다 계산합니다. chat-completions는 요청당 참조 20개, Anthropic Messages와 Responses는 요청당 참조 100개 상한도 함께 적용됩니다. 인라인 파일 하나는 내용을 읽기 전에 10 MiB로 제한됩니다. |
| 비스트리밍 업스트림 응답 | 없음 | 설정 불가 | 통째로 버퍼링됩니다. 실질적으로는 라우터가 아니라 프로바이더가 돌려주는 크기가 상한입니다. |
곱셈 해 보기¶
속도 제한은 동시성 항을 대신하지 못합니다. 속도 제한이 묶는 것은 단위 시간당 도착 수이지, 동시에 상주하는 수가 아닙니다. Little의 법칙에 따르면 다음과 같습니다.
LLM 완성은 수십 초가 예사이므로, per_client: 10(초당 10건)에 평균 30초면 한 클라이언트에서만 약 300건이 동시에 떠 있고, 이는 전부 정책 안입니다. 파일 해석 상한까지 채우면 약 12.6 GiB가 상주하고, 본문만 따져도 파싱된 JSON을 세기 전에 약 2.9 GiB입니다. 업로드는 이제 이 메모리 수치에 기여하지 않지만, 기본 files.max_file_size로 업로드 10건이 동시에 진행되면 끝날 때까지 디스크 5 GiB를 차지하므로, files.storage_path는 허용하는 동시성에 맞춰 잡으십시오.
상한 설정¶
기본값은 설정하지 않음이며, 이는 상한 없음, 즉 이전 모든 릴리스와 같은 동작을 뜻합니다. 상한을 넘으면 라우터는 대기열에 넣지 않고 503과 Retry-After, 그리고 상한을 명시한 JSON 본문으로 응답합니다. 대기열에 넣으면 메모리 부족 종료를 무한정 늘어나는 지연으로 바꿀 뿐, 메모리는 그대로 붙잡고 있기 때문입니다.
CPU가 아니라 감수할 메모리를 기준으로 정하십시오.
값을 고르기 전에 알아 둘 것이 둘 있습니다.
- 퍼밋은 요청 전체 동안 유지되며, 스트리밍 완성이라면 스트림 전체 동안입니다. 짧은 비스트리밍 트래픽에 맞춰 잡은 상한은 긴 스트리밍 트래픽을 굶깁니다. 가장 긴 요청을 기준으로 잡으십시오.
/health,/healthz, 설정된metrics.path는 면제됩니다. 포화된 라우터도 자기 활성 상태 검사에 답하고 스크레이프도 계속 받습니다. 프로브를 차단하는 것은 정상적으로 성능을 낮추고 있는 레플리카를 과부하 한복판에서 재시작시키는 길입니다.
상한과 rate_limiting은 택일이 아니라 함께 씁니다. 속도 제한은 도착을, 상한은 상주를 묶으며 서로를 대체하지 않습니다. 앞단 리버스 프록시는 동시 연결 수와 본문 크기를 제한하도록 명시적으로 설정해야만 도움이 되고, 파일 참조 100개를 실은 요청의 비용은 알지 못합니다.
요청 타임아웃도 함께 두십시오. 라우터가 적용하는 타임아웃은 업스트림 호출에 대한 것이지, 인바운드 클라이언트가 슬롯을 얼마나 오래 쥐고 있어도 되는지에 대한 것이 아닙니다. 스트리밍 완성을 열어 두고 읽기를 멈춘 클라이언트는 연결이 살아 있는 동안 퍼밋을 계속 붙잡습니다. 그래서 상한만 두면 메모리 고갈 공격(공격자에게 비싼 공격)이 슬롯 고갈 공격(유휴 소켓 N개면 되는 값싼 공격)으로 바뀝니다. 프록시나 인그레스에서 인바운드 요청 또는 유휴 타임아웃을 걸어 두십시오.
컨트롤 플레인 supply 피드를 사용한다면 보고상의 주의점이 하나 있습니다. max_concurrency는 모든 라우트를 아우르는 상한인 반면 active_requests는 API 라우트만 셉니다. 따라서 파일 업로드로 포화된 라우터는 상한이 꽉 찬 상태에서도 낮은 active_requests를 보고하므로, 이 비율을 이용률로 읽지 마십시오.
컨테이너 크기 잡기¶
위의 Kubernetes 예시는 256Mi를 요청하고 1Gi로 제한합니다. 이 숫자들은 유휴 라우터의 하한으로만 보고, 설정하는 상한과 함께 올리십시오. 파일 해석 상한에 닿은 요청 하나도 base64 확장 후 약 43 MiB를 더하므로 동시성 상한과 컨테이너 메모리 상한은 사실상 한 결정입니다. 예를 들어 요청당 10 MiB를 예상하며 max_concurrent_requests: 64를 쓴다면 기본 사용량 위로 대략 1Gi의 여유가 필요합니다.
files.max_file_size는 메모리 예산이 아니라 디스크 예산입니다. 업로드를 받는 배포라면 files.storage_path를 그 값 곱하기 허용 동시성으로 잡으십시오. 또한 files.max_file_size가 컨테이너 메모리 한도에 비해 크면 라우터가 시작 시 경고합니다. 파일 전체를 한 번에 다루는 소비자가 아직 몇 군데 남아 있기 때문입니다(채팅 요청으로의 base64 파일 주입, 프로바이더 패스스루 재업로드). chat-completions의 base64 주입 경로는 내용을 읽기 전에 저장된 메타데이터를 확인해 10 MiB를 넘는 파일이나 원본 합계 32 MiB를 넘는 요청을 거부하므로, 더 큰 업로드가 디스크에 존재할 수는 있어도 하나의 채팅 요청 안으로 확장되지는 않습니다.
메모리 상한이 설정된 오케스트레이터 아래에서 상한을 넘기면 OOM 종료 후 재시작이며, 영향 범위는 해당 레플리카의 가용성과 그 위에서 처리 중이던 모든 요청입니다. 베어메탈이거나 메모리 상한이 없는 컨테이너라면 호스트 수준의 압박이 됩니다.
용량 산정: 처리량¶
앞 절은 레플리카 하나가 무엇을 들고 있을 수 있는지의 상한을 다뤘습니다. 이 절은 무엇을 처리할 수 있는지, 그리고 거기에 의존하는 배포 기본값을 다룹니다. deploy/kubernetes/base/deployment.yaml의 CPU 요청 200m과 상한 1코어, HorizontalPodAutoscaler의 CPU 목표 70%가 그것입니다.
문서에서 읽지 말고 직접 측정하십시오¶
라우터 처리량은 상수가 아닙니다. 업스트림 프로바이더 지연, 스트리밍 비중, 요청 형태, 레플리카에 준 CPU의 함수이고, 이 중 프로젝트가 통제하는 것은 마지막 하나뿐입니다. 여기에 대표 초당 요청 수를 적어 두면 거의 모든 배포에서 틀린 값이 됩니다. 그래서 저장소는 숫자 대신 측정 도구를 제공합니다.
외부 의존이 전혀 없습니다. 하네스는 첫 토큰 지연과 토큰 간 간격이 설정된 자체 모의 백엔드를 띄우고, 그 백엔드를 가리키는 라우터 설정을 생성하고, 라우터를 별도 프로세스로 시작한 뒤 실제 소켓 위로 지속 부하를 겁니다. 결과 JSON에는 처리량, 지연 백분위, 첫 토큰까지의 시간, 오류율, 라우터 프로세스의 RSS와 CPU, 백엔드 풀 점유율이 담깁니다.
기본 제공 프로필은 두 개입니다. non-streaming(연결 32개, 업스트림 지연 60ms 고정)과 streaming(연결 32개, 토큰 64개를 5ms 간격으로)입니다. 프로필과 결과 스키마는 perf/README.md에 정리되어 있습니다. .github/workflows/perf.yml은 고정된 러너 등급에서 매주 같은 프로필을 실행해 perf/thresholds.json의 검토된 임계값과 비교하며, 커밋 단위 빌드에서는 절대 돌지 않습니다.
측정은 목표 레플리카와 같은 사양의 하드웨어에서, 실제로 처리하는 요청 형태로 하십시오. 워크스테이션에서 잰 값은 CPU 요청 200m짜리 레플리카에 대해 아무것도 말해 주지 않습니다.
측정값을 레플리카 수로 옮기기¶
산정의 대부분은 두 가지 사실에서 나옵니다.
레플리카 수를 정하는 것은 요청 속도가 아니라 동시성입니다. 리틀의 법칙으로 동시 처리 수 ~= 도착률 x 평균 처리 시간입니다. 초당 5건이 오고 각 완성이 20초 걸리면 동시 요청은 100건이고, 레플리카가 들고 있어야 하는 것은 초당 5가 아니라 그 100개의 슬롯입니다. non-streaming 프로필의 throughput_rps는 해당 동시성에서 라우터 자체 요청 경로가 감당하는 상한으로 읽고, 그보다 큰 부하는 레플리카를 키울 게 아니라 늘려야 하는 부하로 취급하십시오.
라우터는 스트림을 버퍼링하지 않고 중계합니다. streaming 프로필이 이를 직접 측정하며, 스트리밍 레플리카의 산정 방식이 다른 이유가 여기에 있습니다. 첫 토큰을 50ms에 내보내고 약 315ms 뒤에 스트림을 끝내는 백엔드에 대해, 측정된 첫 토큰까지의 시간은 스트림 전체 길이가 아니라 업스트림 첫 토큰 지연 근처에 머뭅니다. 따라서 스트리밍 레플리카의 비용은 동시에 열려 있는 스트림 수가 지배하며, 열린 스트림 하나하나가 스트림이 끝날 때까지 max_concurrent_requests 슬롯을 붙잡습니다. 완료된 초당 요청 수가 아니라 동시 스트림 수와 그 지속 시간을 기준으로 잡으십시오.
두 계산은 별개이고 둘 다 필요합니다.
- 처리량은 레플리카 몇 개인지를 알려 줍니다. 예상 피크 동시성을 측정 당시 동시성으로 나누십시오.
- 메모리는 레플리카 하나가 얼마나 커야 하는지를 알려 줍니다.
server.max_concurrent_requests와 컨테이너 메모리 상한은 이 절이 아니라 앞 절을 기준으로 정하십시오.
클러스터에서 오토스케일링 기본값 검증하기¶
배포 번들의 기본값은 minReplicas: 3, maxReplicas: 10(Helm 프로덕션 값에서는 20)이고 CPU 사용률 목표는 70%입니다. 이 기본값은 레플리카가 메모리보다 CPU에 먼저 묶인다고 가정합니다. 그 가정이 성립하는지는 워크로드의 성질이므로, 의존하기 전에 확인해 둘 값어치가 있습니다.
- 클러스터 안에서 레플리카당 기준선을 잡습니다. 라우터와 같은 노드 등급에서 Job으로 단일 레플리카 프로필을 실행해, 측정이 노트북이 아니라 클러스터의 CPU 상한을 물려받게 하십시오.
- 부하를 키웁니다.
continuum-perf run --target-url <service-host>:<port>로 드라이버를 Service에 붙이고, 제공 동시성을 레플리카 하나의 측정 상한 위로 올리십시오.--target-url모드에서는 하네스가 클러스터 안 프로세스의/proc을 읽을 수 없으므로, 메모리와 CPU는kubectl top pod에서 가져오십시오. - 오토스케일러와 지연을 함께 봅니다.
kubectl get hpa continuum-router --watch를 드라이버의 p95와 나란히 두십시오. p95가 단일 레플리카 구간을 벗어나기 전에 레플리카가 늘고, 스케일업 안정화 창 30초 안에 지연이 회복되며, 스케일다운 창 300초가 톱니 모양 부하에서 진동하지 않으면 기본값이 유효합니다. - 정작 중요한 실패 양상을 확인합니다. 지연이 오르는 동안 CPU 사용률이 70%에 한참 못 미친다면, 그 워크로드에서 레플리카는 CPU에 묶인 것이 아니며 CPU 목표 HPA는 아예 스케일하지 않습니다. 레플리카가 연산이 아니라 열린 스트림에 묶이는 스트리밍 위주 배포에서 흔한 경우입니다.
maxReplicas를 올리고 잘 되기를 바라는 대신,averageUtilization을 낮추거나 처리 중 요청 수 같은 커스텀 메트릭으로 스케일하십시오.
측정한 것은 기록해 두십시오. 요청 형태, 업스트림 지연, 그 값을 만들어 낸 레플리카 크기가 빠진 용량 수치는 재현할 수 없습니다. 하네스가 결과 파일마다 그 셋을 모두 적어 두는 이유가 이것입니다.
고가용성¶
Continuum Router에는 regions:, geographic_routing:, failover:, postgresql: 최상위 설정이 없습니다. 리전 간 트래픽 관리와 TLS는 외부 인프라에서 구현하십시오.
단일 라우터 안에서는 상태 필터, 서킷 브레이커, 재시도, 모델 폴백, 여섯 선택 전략이 백엔드 수준 복원력을 제공합니다. 일반 프록시 트래픽은 결과를 기록하고 열린 백엔드 서킷을 우회합니다. 여러 라우터 replica에서는 다음을 고려하십시오.
/health검사를 사용하는 외부 로드 밸런서를 둡니다.- 설정과 비밀 버전을 일치시킵니다.
- 어떤 속도 제한/캐시/통계 저장소가 프로세스별인지 확인합니다.
- 현재 세션 저장소는 프로세스 로컬이므로 sticky ingress 없이 저장된
/v1/responses/{id}세션을 다른 replica에서 조회할 수 있다고 가정하지 않습니다. - Files API를 수평 확장하기 전 공유 스토리지와 소유권 메타데이터를 계획합니다.
공유 Redis 상태¶
속도 제한 counter와 응답 캐시는 기본적으로 프로세스별입니다. 그래서 replica가 셋인 배포는 설정한 속도의 세 배를 허용하고 같은 응답을 세 번 캐시합니다. 모든 replica가 Redis 또는 Valkey 엔드포인트 하나를 바라보게 하면 두 문제가 함께 사라집니다. 공식 바이너리와 이미지는 redis-cache를 컴파일하므로 설정만 하면 됩니다.
지원 토폴로지¶
| 토폴로지 | 상태 | 비고 |
|---|---|---|
| 단독 서버 | 지원 | 호스트와 포트 하나, redis:// 또는 rediss://. |
| 제공자 관리형 HA 엔드포인트 | 지원 | 자동 장애 조치를 지원하는 ElastiCache, Azure Cache for Redis, Memorystore, 관리형 Valkey. 장애 조치가 고정된 DNS 이름이나 가상 IP 뒤에서 일어나므로 라우터는 주소 하나를 계속 사용합니다. |
| 프록시 뒤의 클러스터 | 지원 | 클러스터를 엔드포인트 하나로 노출하는 모든 프록시. |
| Unix 소켓 | 지원 | 같은 파드의 사이드카를 위한 redis+unix:///path/to/redis.sock. |
| Redis Sentinel 직접 지정 | 지원 범위 밖 | 설정 로드 시 거부. |
| Redis Cluster 직접 지정 | 지원 범위 밖 | 설정 로드 시 거부. |
다중 호스트 URL (host-a:6379,host-b:6379) | 지원 범위 밖 | 설정 로드 시 거부. |
이 경계는 클라이언트에서 나옵니다. 라우터는 redis::aio::ConnectionManager 위에 deadpool-redis 풀 하나를 만드는데, 이 클라이언트는 Sentinel master를 탐색하지 않고 클러스터 slot도 라우팅하지 않습니다. Sentinel이나 Cluster URL을 주면 연결에 실패하거나, 더 나쁘게는 응답한 노드 하나에 모든 replica가 고정됩니다. 지원하는 것처럼 보이게 두는 대신 설정 로더가 그런 URL을 제한 사항을 밝히는 메시지와 함께 거부합니다. 시작 시, 핫 리로드 시, admin 설정 API를 통할 때, continuum-router config validate에서 모두 마찬가지입니다.
설정¶
rate_limiting:
enabled: true
storage: redis
redis:
url: "${REDIS_URL}"
key_prefix: "continuum:ratelimit:"
ttl: 3600
response_cache:
enabled: true
backend: redis
ttl: "300s"
redis:
url: "${REDIS_URL}"
pool_size: 8
key_prefix: "cr:resp:"
fallback_to_memory: true
TLS는 rediss:// 스킴에서 옵니다(응답 캐시는 스킴을 다시 쓰는 tls: true도 받습니다). 자격 증명은 URL userinfo에 넣고 환경 변수로 전달해야 하며 저장소에 커밋되는 파일에는 절대 넣지 마십시오. config validate는 설정 파일에 적힌 비밀번호를 경고하고, 자격 증명이 평문 redis:// 엔드포인트를 지나갈 때 다시 경고합니다. 라우터 로그는 항상 비밀번호를 마스킹합니다.
배포 연결¶
Helm 차트도 Kustomize 번들도 Redis 서버, StatefulSet, 생성된 자격 증명을 제공하지 않습니다. 둘 다 사용자가 만든 Secret을 참조하고 엔드포인트에 도달하는 데 필요한 NetworkPolicy egress만 엽니다.
kubectl -n continuum-router create secret generic continuum-router-redis \
--from-literal=REDIS_URL="rediss://:$REDIS_PASSWORD@cache.example.com:6380"
# Helm: 환경 프로필 위에 오버레이를 겹칩니다.
helm upgrade --install router deploy/helm/continuum-router \
--namespace continuum-router \
-f deploy/helm/continuum-router/values-production.yaml \
-f deploy/helm/continuum-router/values-shared-redis.yaml \
--set-string image.digest="$IMAGE_DIGEST"
# Kustomize: base 대신 오버레이를 적용합니다.
kubectl apply -k deploy/kubernetes/overlays/shared-redis
두 오버레이 모두 6380 포트를 포트 번호만으로 egress 허용합니다. 관리형 엔드포인트는 클러스터 밖에 있을 수 있어 namespace나 pod 레이블로 선택할 수 없기 때문입니다. 주소를 알고 나면 ipBlock으로 좁히십시오. Secret 이름 없이 redis.enabled: true만 주면 자격 증명을 임의로 만들어 내는 대신 Helm 렌더링이 실패합니다.
장애 시 동작¶
| 소비자 | Redis 사용 불가 시 | 설정 가능 여부 |
|---|---|---|
| 속도 제한 | fail-open. 각 replica가 자체 인메모리 bucket으로 폴백하므로 실효 플릿 속도는 제한값 x replica 수가 됩니다. | 불가 |
| 응답 캐시, 기본값 | fail-open. replica별 인메모리 계층에서 응답하며, 백그라운드 모니터가 PING을 보내 복구되면 다시 전환합니다. | fallback_to_memory: true |
| 응답 캐시, fail-closed | replica 로컬 데이터로 응답하는 대신 장애를 보고합니다. 캐시 계층이 이를 로깅하고 미스로 처리하므로 요청은 백엔드까지 도달하되 캐시되지는 않습니다. | fallback_to_memory: false |
Redis가 사라져도 두 소비자 모두 트래픽을 거부하지는 않습니다. 속도 제한은 저하된 상한을 기준으로 잡으십시오. replica가 여섯이고 플릿 전체가 600 rps를 넘으면 안 되는데 공유 제한을 600 rps로 두면, 장애 시 3600 rps까지 허용됩니다. 두 소비자 모두 재연결은 자동이며 엔드포인트가 돌아온 뒤 재시작할 필요가 없습니다.
공유 상태 배포에서 replica별 상태로 되돌리는 것은 설정 변경입니다. storage: memory와 backend: memory로 바꾸고 다시 적용한 뒤 롤링하십시오. counter는 빈 상태에서 다시 시작합니다.
성능 프로필¶
안전한 튜닝 절차:
- 생성된 템플릿에서 시작합니다.
- 제공자 직접 트래픽과 라우터 경유 트래픽을 측정합니다. 라우터 경유 쪽은
perf/run.sh로 프로바이더 계정 없이 재현 가능하게 측정할 수 있습니다(위의 "용량 산정: 처리량" 참고). server.connection_pool_size, 시간 초과, 재시도, 상태 확인,selection_strategy를 튜닝합니다.- 관측 트래픽을 기준으로 응답 캐시와 속도 제한 크기를 정합니다.
- 모든 변경을 검증합니다. 선택 전략, 재시도 정책, 요청별 타임아웃 예산은 실시간 리로드됩니다. 리스너/HTTP 클라이언트 구성처럼 시작 시 만들어지는 필드(
timeouts.connection, 공유 클라이언트 상한인timeouts.request.streaming.total)는 재시작합니다.
보안¶
- 공개 API 트래픽에는 API 키 blocking 모드를 사용합니다.
- Admin, Files, WebUI, Metrics 인증을 각각 설정합니다.
- 라우터 앞에서 TLS를 종료합니다.
- 제공자 키는 환경/비밀 저장소에 두고
${ENV_VAR}로 참조합니다. - Admin 접근은 사설 네트워크나 엄격한 프록시 정책으로 제한합니다.
config show --resolved출력은 평문 자격 증명을 포함할 수 있으므로 공개하지 않습니다.- Admin 설정 저장/import 작업을 사용하지 않는다면 설정을 읽기 전용으로 마운트합니다.
공개 /health와 /version 엔드포인트는 운영 정보를 포함하며 API 키 인증을 사용하지 않습니다. 이 정보도 비공개여야 하면 네트워크 계층에서 필터링하십시오.
관측성¶
메트릭을 명시적으로 활성화하고 보호합니다.
metrics:
enabled: true
endpoint: /metrics
auth:
enabled: true
username: metrics
password: "${METRICS_PASSWORD}"
구조화된 프로덕션 로그에는 logging.format: json을 사용하십시오. CONTINUUM_LOG_LEVEL과 RUST_LOG로 상세도를 조절할 수 있으며 민감한 워크로드에서 디버그 로그를 장기간 사용하지 마십시오.
Admin 진단 예시:
curl -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:8080/admin/health
curl -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:8080/admin/backends
curl -H "Authorization: Bearer $ADMIN_TOKEN" http://localhost:8080/admin/circuit/all
백업과 복구¶
설정에서 실제 활성화한 운영자 소유 상태를 백업하십시오.
- 비밀을 제외하고 버전 관리하는
config.yaml또는 TOML 원본 - 설정한 경우 외부 API 키 파일
- Files API 스토리지와 메타데이터 데이터베이스
- 통계/메트릭 영속 파일
- OAuth 토큰 저장소
- 기능 활성 시 control-plane 상태와 probe-budget sidecar
- 영속성이 필요한 응답 캐시 백엔드
동일한 바이너리 버전과 기능 세트로 복원 절차를 시험하십시오. 시작 전 복원한 설정을 검증합니다.
문제 해결¶
셸 붙이기¶
이미지에 셸이 없으므로 docker exec ... sh와 kubectl exec ... -- sh는 동작하지 않습니다. 대신 라우터의 네임스페이스를 공유하는 별도 컨테이너에 셸을 띄우십시오. 라우터의 파일시스템은 /proc/1/root로 접근합니다.
# Kubernetes: 실행 중인 파드에 임시(ephemeral) 디버그 컨테이너를 붙입니다
kubectl debug -it <pod> --image=busybox --target=continuum-router
# Docker: 라우터의 PID·네트워크 네임스페이스를 공유하는 일회용 컨테이너
docker run --rm -it \
--pid=container:continuum-router \
--network=container:continuum-router \
busybox sh
대부분의 점검에는 셸이 아예 필요 없습니다. 라우터의 서브커맨드가 이미지 엔트리포인트로 그대로 실행되기 때문입니다.
docker run --rm -v "$PWD/config.yaml:/c.yaml:ro" \
ghcr.io/lablup/continuum-router:1.28.0 config validate /c.yaml
docker run --rm -v "$PWD/config.yaml:/c.yaml:ro" \
ghcr.io/lablup/continuum-router:1.28.0 config show --resolved /c.yaml
컨테이너 비정상¶
docker exec continuum-router /usr/local/bin/continuum-router --health-check
docker logs continuum-router
컨테이너 안에서 라우터가 루프백이 아니라 0.0.0.0:8080에 수신 중인지 확인하십시오.
설정 실패¶
continuum-router config validate /etc/continuum-router/config.yaml
continuum-router config show /etc/continuum-router/config.yaml
config show --resolved는 해석된 비밀을 노출할 수 있으므로 보호된 터미널에서만 사용하십시오.
백엔드가 선택되지 않음¶
요청 모델, 백엔드 모델 목록, 상태, 서킷 상태, 호출자 허용 목록을 확인하십시오. 선택 전략은 적격 후보 안에서만 선택합니다.