DeepSeek を EKS Auto Mode でセルフホストしてみた

DeepSeek を EKS Auto Mode でセルフホストしてみた

Clock Icon2025.01.30

こんにちは!

クラウド事業本部コンサルティング部のたかくに(@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 のインスタンスも利用可能です)

https://dev.classmethod.jp/articles/eks-auto-mode/

やってみる

今回は以下のような構成で EKS on EC2 (Auto Mode) を利用しながら、DeepSeek の推論サーバーを API 公開してみたいと思います。

Untitled(122).png

モデルは Cyber Agents さんが公開している日本語の追加学習が行われたモデルをお借りしました。

https://huggingface.co/cyberagent/DeepSeek-R1-Distill-Qwen-32B-Japanese

作成したコードは、以下に格納されています。参考になれば幸いです。

https://github.com/takakuni-classmethod/genai-blog/tree/main/deepseek-eks-automode

上限緩和

AWS 上で GPU インスタンスを動かすため Service Quotas から上限緩和申請を行います。

g6.12xlarge を利用するため、最低限 48 vCPU 以上は引き上げておきましょう。

VPC

AWS インフラは主に Terraform で構築を行いました。

今回、 g6.12xlarge を利用したため、サポートしている AZ を選ぶ必要があります。

2025-01-30 at 00.33.26-InstanceTypes  EC2  us-east-1@2x.png

私の場合は us-east-1a, us-east-1c, us-east-1d, us-east-1b がサポートしていたため、そちらを利用しました。

ALB の作成は EKS Auto Mode の仕組みで行うため、public_subnet_tags"kubernetes.io/role/elb" = 1 を付与しておきます。

vpc.tf
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 がサポートしたので記念に使ってみました。

https://aws.amazon.com/jp/about-aws/whats-new/2025/01/amazon-eks-eks-distro-kubernetes-version-1-32/

node_pools で "general-purpose" を選択していますが、 GPU を利用する場合は、別途自身でノードプールを利用する必要があります。

今回は "general-purpose" を作ることで払い出されたノードクラス(default)を利用したかったため、こういった書き方にしています。

eks.tf
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'

https://docs.aws.amazon.com/eks/latest/userguide/create-node-class.html

Terraform 側でのインフラのセットアップは以上です。とてもシンプルですね。

マニフェストファイル

続いてマニフェストファイルです。

名前空間

ns.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: inference

ノードプール

Auto Mode のカスタムノードプールを作成します。

g6 を利用するため、eks.amazonaws.com/instance-family で指定します。

nodepool.yaml
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 経由でモデルをダウンロードし推論する処理にしました。

deploy.yaml
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 の機能を利用します。

ingress.yaml
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_)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.