ローカル LLM(Ollama + Open WebUI)を Argo CDでデプロイしてみた

ローカル LLM(Ollama + Open WebUI)を Argo CDでデプロイしてみた

2026.04.18

orbstack + Argo CD でローカル LLM 環境(Ollama + Open WebUI)を GitOps デプロイしてみた

はじめに

クラウド事業本部、あきやまです。

みなさんはローカル環境でLLM 動かしたい!!!という衝動に駆られたことはありますか?私はあります。
やるなら他の技術も色々試したいと思い手元の Mac 上に Kubernetes クラスタを立ち上げ Ollama と Open WebUI を Argo CD 経由でデプロイし、Private GitHub リポジトリと繋げるところまでを一通りやってみました。

環境

項目
OS macOS
チップ Apple M4
メモリ 16GB
Kubernetes ランタイム orbstack
kubectl v1.29 以上
Argo CD stable(公式 install.yaml)
Ollama ollama/ollama:latest
Open WebUI ghcr.io/open-webui/open-webui:main
GitHub リポジトリ Private(PAT 認証)

結論

  • orbstack の Kubernetes を使えば、Docker Desktop や minikube なしでもローカルクラスタを即座に立ち上げられます
  • Argo CD の App of Apps パターン により、apps/ 配下にマニフェストを追加するだけで新しいアプリが自動登録される構成が作れます
  • Private リポジトリ接続は argocd.argoproj.io/secret-type: repository ラベル付き Secret を apply するだけで完結します(PAT は Secret に格納し、git には載せない)
  • CPU のみでも gemma2:2b など小型モデルであれば十分動作し、Open WebUI からチャット可能でした

以降で具体的な手順を示します。

構成図

全体像は以下の通りです。

ディレクトリ構成

最終的な全ファイルは以下の通りです。このまま同じ構成を作れば再現できます。

localllm/
├── bootstrap/
│   └── root-app.yaml                  # App of Apps(apps/ を watch)
├── apps/
│   ├── ollama.yaml                    # ArgoCD Application: Ollama
│   └── openwebui.yaml                 # ArgoCD Application: Open WebUI
├── manifests/
│   ├── ollama/
│   │   ├── kustomization.yaml         # resources を束ねる
│   │   ├── namespace.yaml             # Namespace: ollama
│   │   ├── deployment.yaml            # Ollama 本体
│   │   ├── service.yaml               # ClusterIP :11434
│   │   └── pvc.yaml                   # モデル保存用 PVC (20Gi)
│   └── openwebui/
│       ├── kustomization.yaml
│       ├── namespace.yaml             # Namespace: openwebui
│       ├── deployment.yaml            # Open WebUI 本体
│       ├── service.yaml               # ClusterIP :8080
│       └── pvc.yaml                   # 設定・履歴用 PVC (2Gi)
└── scripts/
    ├── 01-start-minikube.sh           # orbstack の context 切り替え
    ├── 02-install-argocd.sh           # Argo CD 本体インストール
    ├── 03-register-repo.sh            # Private repo 認証情報を登録
    ├── 04-bootstrap.sh                # root Application を apply
    └── pull-model.sh                  # Ollama にモデルを pull

ファイル数は 17 個です。それぞれの中身を以降のステップで順番に示します。

やってみた

Step 1: orbstack の Kubernetes を有効化

orbstack の設定画面から Kubernetes を有効にし、kubectl のコンテキストを切り替えます。

scripts/01-start-minikube.sh
#!/usr/bin/env bash
set -euo pipefail

kubectl config use-context orbstack
kubectl cluster-info
$ ./scripts/01-start-minikube.sh
Switched to context "orbstack".
Kubernetes control plane is running at https://127.0.0.1:xxxxx

※ orbstack は Docker Desktop の代替として使えるランタイムで、軽量な Kubernetes が内蔵されています。minikube 等と違って別途 VM を起動する必要がありません。

Step 2: Argo CD をインストール

Argo CD 本体は imperative(命令的)にインストールします。Argo CD で Argo CD 自身を管理する鶏と卵の問題を避けるためです。

scripts/02-install-argocd.sh
#!/usr/bin/env bash
set -euo pipefail

kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -n argocd \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml \
  --server-side=true

kubectl -n argocd wait --for=condition=available deploy/argocd-server --timeout=300s

# 初期パスワードを表示
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d; echo

--server-side=true を付けているのは、install.yaml に含まれる大きな CRD のサイズがクライアントサイド apply の last-applied annotation 上限を超える可能性があるためです。

Step 3: Private リポジトリ認証情報を登録

Argo CD が Private GitHub リポジトリを fetch できるように、PAT を Secret に入れて apply します。

PAT は事前に GitHub の Settings から発行しておきます。

  • Classic PAT: repo スコープを有効化
  • Fine-grained PAT: 対象リポジトリに限定し、Contents: Read-only を付与

PAT は git にコミットしない運用にします。

scripts/03-register-repo.sh
#!/usr/bin/env bash
set -euo pipefail

: "${GITHUB_USERNAME:?Set GITHUB_USERNAME}"
: "${GITHUB_PAT:?Set GITHUB_PAT}"

REPO_URL="https://github.com/<your-org>/<your-repo>.git"

kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: repo-localllm
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  type: git
  url: ${REPO_URL}
  username: ${GITHUB_USERNAME}
  password: ${GITHUB_PAT}
EOF

実行時は環境変数で PAT を渡します。

export GITHUB_USERNAME=<your-github-username>
export GITHUB_PAT=ghp_xxxxxxxxxxxx
./scripts/03-register-repo.sh

Argo CD は argocd.argoproj.io/secret-type: repository ラベル付き Secret を自動検知し、その URL に一致する Application から利用します。これで CLI(argocd repo add)を使わずに宣言的に登録できます。

Step 4: App of Apps(root Application)を apply

bootstrap/root-app.yamlapps/ ディレクトリを watch し、配下の Application CR を自動登録する役割を持ちます。

bootstrap/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/<your-org>/<your-repo>.git
    targetRevision: HEAD
    path: apps
    directory:
      recurse: true
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - ServerSideApply=true

この root Application を apply するためのラッパースクリプトを用意しておきます。

scripts/04-bootstrap.sh
#!/usr/bin/env bash
set -euo pipefail

# App of Apps(root Application)を apply
# 以降のアプリ追加・変更は git push のみで反映される
kubectl apply -f bootstrap/root-app.yaml

echo ""
echo "Bootstrap completed. Monitoring sync status..."
kubectl -n argocd get applications

実行します。

./scripts/04-bootstrap.sh

以降は apps/*.yaml に Application を追加するだけで自動デプロイされます。

Step 5: 各アプリの Application 定義

apps/ollama.yamlapps/openwebui.yaml の 2 ファイルを作成します。それぞれ manifests/<app> 配下の Kustomize を指します。

apps/ollama.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ollama
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/<your-org>/<your-repo>.git
    targetRevision: HEAD
    path: manifests/ollama
  destination:
    server: https://kubernetes.default.svc
    namespace: ollama
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true
apps/openwebui.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: openwebui
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/<your-org>/<your-repo>.git
    targetRevision: HEAD
    path: manifests/openwebui
  destination:
    server: https://kubernetes.default.svc
    namespace: openwebui
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true

違いは path(参照する manifests ディレクトリ)と destination.namespace の 2 箇所だけです。

Step 6: Ollama のマニフェスト

manifests/ollama/ 配下に 5 ファイルを作ります。

kustomization.yaml

Kustomize がこのディレクトリの入り口になります。resources: で束ねる全ファイルを列挙します。

manifests/ollama/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: ollama

resources:
  - namespace.yaml
  - pvc.yaml
  - deployment.yaml
  - service.yaml

labels:
  - includeSelectors: false
    pairs:
      app.kubernetes.io/part-of: localllm

namespace.yaml

manifests/ollama/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: ollama

pvc.yaml

Ollama はモデルを /root/.ollama に保存します。Pod 再起動で消えないよう PVC を用意します。

manifests/ollama/pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ollama-models
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi

deployment.yaml

CPU 推論用にリソース要求を多めに設定しています。PVC が ReadWriteOnce なので、strategy.type: Recreate にして新旧 Pod が同時にマウントしないようにしています。

manifests/ollama/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ollama
  labels:
    app.kubernetes.io/name: ollama
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app.kubernetes.io/name: ollama
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ollama
    spec:
      containers:
        - name: ollama
          image: ollama/ollama:latest
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 11434
          env:
            - name: OLLAMA_HOST
              value: "0.0.0.0:11434"
          resources:
            requests:
              cpu: "2"
              memory: 4Gi
            limits:
              cpu: "4"
              memory: 8Gi
          volumeMounts:
            - name: models
              mountPath: /root/.ollama
          readinessProbe:
            tcpSocket:
              port: 11434
            initialDelaySeconds: 10
            periodSeconds: 10
          livenessProbe:
            tcpSocket:
              port: 11434
            initialDelaySeconds: 30
            periodSeconds: 30
      volumes:
        - name: models
          persistentVolumeClaim:
            claimName: ollama-models

service.yaml

manifests/ollama/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: ollama
  labels:
    app.kubernetes.io/name: ollama
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: ollama
  ports:
    - name: http
      port: 11434
      targetPort: http
      protocol: TCP

Step 7: Open WebUI のマニフェスト

manifests/openwebui/ 配下に同じく 5 ファイルを作ります。Ollama と同じ構造ですが、env で Ollama の Service DNS を指定するのがポイントです。

kustomization.yaml

manifests/openwebui/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: openwebui

resources:
  - namespace.yaml
  - pvc.yaml
  - deployment.yaml
  - service.yaml

labels:
  - includeSelectors: false
    pairs:
      app.kubernetes.io/part-of: localllm

namespace.yaml

manifests/openwebui/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: openwebui

pvc.yaml

Open WebUI は設定・履歴を /app/backend/data に保存します。

manifests/openwebui/pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: openwebui-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi

deployment.yaml

OLLAMA_BASE_URL に Ollama Service の FQDN を指定します。<service>.<namespace>.svc.cluster.local の形式です。

manifests/openwebui/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openwebui
  labels:
    app.kubernetes.io/name: openwebui
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app.kubernetes.io/name: openwebui
  template:
    metadata:
      labels:
        app.kubernetes.io/name: openwebui
    spec:
      containers:
        - name: openwebui
          image: ghcr.io/open-webui/open-webui:main
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: OLLAMA_BASE_URL
              value: "http://ollama.ollama.svc.cluster.local:11434"
            - name: WEBUI_AUTH
              value: "False"
          resources:
            requests:
              cpu: 200m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 2Gi
          volumeMounts:
            - name: data
              mountPath: /app/backend/data
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 30
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: openwebui-data

WEBUI_AUTH=False は検証用途のみ。本番では必ず認証を有効にしてください。

service.yaml

manifests/openwebui/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: openwebui
  labels:
    app.kubernetes.io/name: openwebui
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: openwebui
  ports:
    - name: http
      port: 8080
      targetPort: http
      protocol: TCP

Step 8: モデルを pull

デプロイが Synced / Healthy になったら、Ollama Pod に exec してモデルを pull します。CPU 推論でも現実的な速度で動く軽量モデル(gemma2:2b など)がおすすめです。

scripts/pull-model.sh
#!/usr/bin/env bash
set -euo pipefail

MODEL="${1:-llama3.2}"
kubectl exec -n ollama deploy/ollama -- ollama pull "${MODEL}"
kubectl exec -n ollama deploy/ollama -- ollama list
$ ./scripts/pull-model.sh llama3.2
Pulling model: llama3.2
pulling manifest 
pulling dde5aa3fc5ff: 100% ▕██████████████████▏ 2.0 GB                         
pulling 966de95ca8a6: 100% ▕██████████████████▏ 1.4 KB                         
pulling fcc5a6bec9da: 100% ▕██████████████████▏ 7.7 KB                         
pulling a70ff7e570d9: 100% ▕██████████████████▏ 6.0 KB                         
pulling 56bb8bd477a5: 100% ▕██████████████████▏   96 B                         
pulling 34bb5ab01051: 100% ▕██████████████████▏  561 B                         
verifying sha256 digest 
writing manifest 
success 

Installed models:
NAME               ID              SIZE      MODIFIED               
llama3.2:latest    a80c4f17acd5    2.0 GB    Less than a second ago    

Step 9: Open WebUI から動作確認

port-forward でブラウザから Open WebUI にアクセスします。

kubectl port-forward -n openwebui svc/openwebui 3000:8080

http://localhost:3000 を開くと、先ほど pull したモデルが選択肢に表示され、チャットができました。
ローカルに閉じられた環境で無事デプロイできました!今後ファインチューニングしていきたいですね。

screencapture-localhost-3000-c-036fcf0c-7a03-4722-bd3b-15921f6be698-2026-04-18-21_01_35

注意点・制約

PAT の扱い

  • PAT は絶対に git にコミットしない
  • 発行時は Fine-grained PAT で対象リポジトリと権限を最小化する
  • Secret を kubectl apply する際のシェル履歴にも注意(HISTCONTROL=ignorespace を使う、.envrc で管理する 等)

ストレージ容量

Ollama のモデルは 1 モデルで数 GB あります。PVC を 20 Gi で確保していますが、複数モデルを入れる場合は拡張が必要です。

CPU 推論の速度

CPU のみで動かす場合、7B 以上のモデルは体感的にかなり遅くなります。検証目的では以下のような小型モデルが実用的でした。

モデル サイズ 体感(初回トークンまでの応答時間)
gemma2:2b 1.6 GB 応答がスムーズ
qwen2.5:3b 2.0 GB ほぼ問題なし
llama3.2:latest(3B) 2.0 GB 5〜10 秒程度。実用可能
llama3.1:8b 4.7 GB 明らかに遅い

※ 応答時間は手元の orbstack(CPU のみ)で短めのプロンプトを投げた際の体感値です。プロンプト長や生成トークン数で大きく変わるのであくまで目安としてください。

PVC の ReadWriteOnce

Deployment の strategy を Recreate にしています。RollingUpdate だと新旧 Pod が同時に PVC をマウントしようとして失敗するためです。

Open WebUI の認証

WEBUI_AUTH=False は検証限定。ローカル以外に公開する場合は必ず True にして認証経路を設定します。

まとめ

今回の検証で実現できたことは以下の通りです。

  • orbstack の Kubernetes 上で Ollama と Open WebUI を動作させた
  • Argo CD の App of Apps パターンで複数アプリを宣言的に管理した
  • Private GitHub リポジトリへの接続を PAT + Declarative Secret で実現した
  • 以降の変更は git push のみで反映される GitOps フローを確立した

orbstack が想像以上に軽快で、minikube や kind を使わずに Kubernetes 検証ができるのは大きな発見でした。また Argo CD の App of Apps は、apps/ 配下にファイルを置くだけで新規アプリが追加されるので、学習用・検証用環境の管理として非常に使いやすいと感じました。

ローカル LLM 環境を GitOps で管理する構成を検討している方の参考になれば幸いです。

参考

この記事をシェアする

関連記事