DeepSeek を EKS Auto Mode でセルフホストしてみた
こんにちは!
クラウド事業本部コンサルティング部のたかくに(@takakuni_)です。
皆さん DeepSeek AI 使ってますでしょうか。私は今日から触り始めました。
最近かなりホットなトピックとなっており、AWS 上で Amazon Bedrock のカスタムモデルインポートや on EC2 で動かしてみた記事をよく見かけます。
私も流行に合わせて、今回は EKS の Auto Mode を利用しながら、 DeepSeek の推論サーバーを立ち上げてみたいと思います。
Auto Mode
EKS の Auto Mode は re:Invent 2024 で発表された新しい機能です。
Auto Mode は、パッチ適用、スケーリングといったノードの管理を AWS 側にオフロードできるようにする機能です。
on EC2 を利用するため、コンテナのイメージキャッシュが働きます。コンテナイメージが大きくなりがちな推論ワークロードの場合は、このあたりがとても嬉しさなのではないでしょうか。(もちろん GPU/Inf のインスタンスも利用可能です)
やってみる
今回は以下のような構成で EKS on EC2 (Auto Mode) を利用しながら、DeepSeek の推論サーバーを API 公開してみたいと思います。
モデルは Cyber Agents さんが公開している日本語の追加学習が行われたモデルをお借りしました。
作成したコードは、以下に格納されています。参考になれば幸いです。
上限緩和
AWS 上で GPU インスタンスを動かすため Service Quotas から上限緩和申請を行います。
g6.12xlarge
を利用するため、最低限 48 vCPU 以上は引き上げておきましょう。
VPC
AWS インフラは主に Terraform で構築を行いました。
今回、 g6.12xlarge
を利用したため、サポートしている AZ を選ぶ必要があります。
私の場合は us-east-1a, us-east-1c, us-east-1d, us-east-1b
がサポートしていたため、そちらを利用しました。
ALB の作成は EKS Auto Mode の仕組みで行うため、public_subnet_tags
で "kubernetes.io/role/elb" = 1
を付与しておきます。
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1c", "us-east-1d", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24", "10.0.104.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = true
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
}
tags = {
Environment = "deepseek-inference"
}
}
EKS
続いて EKS です。執筆時点での最近ですが、v1.32 がサポートしたので記念に使ってみました。
node_pools で "general-purpose" を選択していますが、 GPU を利用する場合は、別途自身でノードプールを利用する必要があります。
今回は "general-purpose" を作ることで払い出されたノードクラス(default
)を利用したかったため、こういった書き方にしています。
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.31.6"
cluster_name = "deepseek-inference"
cluster_version = "1.32"
cluster_endpoint_public_access = true
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
enable_cluster_creator_admin_permissions = true
cluster_compute_config = {
enabled = true
node_pools = ["general-purpose"]
}
}
output "command" {
value = <<EOF
aws eks update-kubeconfig --name ${module.eks.cluster_name} --region us-east-1
EOF
}
ノードクラスをカスタムする場合は、必須でノード用の IAM ロールや Subnet ID をマニフェストファイルに記載し、Apply する必要があり、少し手間だったため省略したかった意図が含まれます。
apiVersion: eks.amazonaws.com/v1
kind: NodeClass
metadata:
name: default
spec:
# Required: Name of IAM Role for Nodes
role: 'MyNodeRole'
# Required: Subnet selection for node placement
subnetSelectorTerms:
- tags:
Name: '<tag-name>'
kubernetes.io/role/internal-elb: '1'
# Alternative using direct subnet ID
# - id: "subnet-0123456789abcdef0"
# Required: Security group selection for nodes
securityGroupSelectorTerms:
- tags:
Name: 'eks-cluster-node-sg'
# Alternative approaches:
# - id: "sg-0123456789abcdef0"
# - name: "eks-cluster-node-security-group"
# Optional: Configure SNAT policy (defaults to Random)
snatPolicy: Random # or Disabled
# Optional: Network policy configuration (defaults to DefaultAllow)
networkPolicy: DefaultAllow # or DefaultDeny
# Optional: Network policy event logging (defaults to Disabled)
networkPolicyEventLogs: Disabled # or Enabled
# Optional: Configure ephemeral storage (shown with default values)
ephemeralStorage:
size: '80Gi' # Range: 1-59000Gi or 1-64000G or 1-58Ti or 1-64T
iops: 3000 # Range: 3000-16000
throughput: 125 # Range: 125-1000
# Optional: Additional EC2 tags
tags:
Environment: 'production'
Team: 'platform'
Terraform 側でのインフラのセットアップは以上です。とてもシンプルですね。
マニフェストファイル
続いてマニフェストファイルです。
名前空間
apiVersion: v1
kind: Namespace
metadata:
name: inference
ノードプール
Auto Mode のカスタムノードプールを作成します。
g6
を利用するため、eks.amazonaws.com/instance-family
で指定します。
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: inference
namespace: inference
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ['on-demand']
- key: eks.amazonaws.com/instance-family
operator: In
values: ['g6']
nodeClassRef:
group: eks.amazonaws.com
kind: NodeClass
name: default
デプロイメント
続いてデプロイメントです。
今回は vLLM コンテナを利用して Hugging Face 経由でモデルをダウンロードし推論する処理にしました。
apiVersion: apps/v1
kind: Deployment
metadata:
name: deepseek-r1-32b-japanese
namespace: inference
labels:
app: deepseek-r1-32b-japanese
spec:
replicas: 1
selector:
matchLabels:
app: deepseek-r1-32b-japanese
template:
metadata:
labels:
app: deepseek-r1-32b-japanese
spec:
nodeSelector:
'node.kubernetes.io/instance-type': 'g6.12xlarge'
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
volumes:
- emptyDir:
medium: Memory
sizeLimit: 10Gi
name: cache-volume
containers:
- name: vllm
image: vllm/vllm-openai:v0.7.0
resources:
limits:
memory: '80Gi'
cpu: '24'
nvidia.com/gpu: 4
requests:
memory: '40Gi'
cpu: '6'
nvidia.com/gpu: 4
readinessProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 10
startupProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 30
successThreshold: 1
failureThreshold: 30
args:
- --model=cyberagent/DeepSeek-R1-Distill-Qwen-32B-Japanese
- --tensor-parallel-size=4
- --max-model-len=16000
- --enforce-eager
volumeMounts:
- mountPath: /dev/shm
name: cache-volume
ports:
- containerPort: 8000
name: http
---
apiVersion: v1
kind: Service
metadata:
name: deepseek-r1-32b-japanese
namespace: inference
labels:
app: deepseek-r1-32b-japanese
spec:
selector:
app: deepseek-r1-32b-japanese
ports:
- protocol: TCP
port: 8000
name: http
type: ClusterIP
イングレス
最後にイングレスです。こちらも Auto Mode の機能を利用します。
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
namespace: inference
labels:
app.kubernetes.io/name: LoadBalancerController
name: alb
spec:
controller: eks.amazonaws.com/alb
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: inference
name: deepseek-r1-32b-japanese
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: deepseek-r1-32b-japanese
port:
number: 8000
推論
払い出された外部 ALB 宛に HTTP リクエストを送信してみました。うまく返ってきていますね。
takakuni@ deepseek-eks-automode % curl -X POST http://k8s-inferenc-deepseek-hogehoge-hogehoge.us-east-1.elb.amazonaws.com/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "cyberagent/DeepSeek-R1-Distill-Qwen-32B-Japanese",
"messages": [{"role": "user", "content": "日本の首都はどこですか?"}]
}'
{"id":"chatcmpl-0da0a1b5b104463d942ce5b24238be8c","object":"chat.completion","created":1738164363,"model":"cyberagent/DeepSeek-R1-Distill-Qwen-32B-Japanese","choices":[{"index":0,"message":{"role":"assistant","content":"<think>\nまず、日本の首都を特定するために、日本の地理や政治的な構造を理解する必要があります。日本の首都は伝統的に地域の発展や政情によって変わってきました。例えば、平安京(現在の京都市)が約一千年前に首都となりました。その後、江戸時代には江戸(現在の東京都)が重要な行政中心地となり、明治政府が設立された際には正式に東京が首都とされました。\n\nしかし、東京には言論と学問の中心地である京都や大阪など他の大都市が存在します。これらの都市も経済や文化で国内を牽引しています。また、日本の政治的ドライブは東京一極集中が進み、都心部に政府機関や主要企業が集積しているため、事実上は東京が首都として機能しています。\n\nただし、この一極集中を是正するための政策が各地で行われており、haarの仁時点では、東京の人口や経済活動の集中による課題が顕在化しています。京都や大阪にも歴史的・文化的意義はありますが、法的に首都として定められているのは東京です。したがって、正式な首都は東京であり、その歴史的背景と現代的な状況を踏まえて判断します。\n</think>\n\n日本の首都は**東京**です。 \nもともと、日本の首都は平安京(京都)や大阪など、時代とともに移転してきました。明治政府が江戸を「東京都」と改称し、1868年に首都を東京に移したことが正式な始まりです。 \n現代では、政府機関や主要企業が集積し、事実上「首都」として機能しています。ただし、歴史的・文化的意義があり、冠城市・多摩美術館などの文化拠点がある京都や大阪など他の都市も重要な役割を担っています。","tool_calls":[]},"logprobs":null,"finish_reason":"stop","stop_reason":null}],"usage":{"prompt_tokens":9,"total_tokens":436,"completion_tokens":427,"prompt_tokens_details":null},"prompt_logprobs":null}%
まとめ
以上、「DeepSeek を EKS Auto Mode でセルフホストしてみた」でした。
EKS Auto Mode とても便利ですね。時間があるときに Inf2 にもチャレンジしてみたいです。
このブログがどなたかの参考になれば幸いです。
クラウド事業本部コンサルティング部のたかくに(@takakuni_)でした!