LocalAI Self-hosted GitOps Workflow คืออะไร
LocalAI เป็น open-source AI inference server ที่รัน LLMs, Image Generation และ Audio models บนเครื่องตัวเอง เข้ากันได้กับ OpenAI API GitOps คือแนวทาง DevOps ที่ใช้ Git repository เป็น single source of truth สำหรับ infrastructure และ application configuration ทุกการเปลี่ยนแปลงต้องผ่าน Git (pull request → review → merge → auto-deploy) การรวม LocalAI กับ GitOps Workflow ช่วยให้จัดการ AI model deployments อย่างเป็นระบบ track changes, rollback ได้ง่าย และ automate ทุกขั้นตอน
GitOps Principles
# gitops_principles.py — GitOps fundamentals
import json
class GitOpsPrinciples:
PRINCIPLES = {
"declarative": {
"name": "Declarative Configuration",
"description": "ประกาศ desired state ใน YAML/JSON — ไม่ใช่ imperative commands",
"example": "บอกว่าต้องการ LocalAI 3 replicas + model llama-3 → ไม่ใช่ run kubectl scale",
},
"versioned": {
"name": "Versioned & Immutable",
"description": "ทุก configuration อยู่ใน Git — มี version history, audit trail",
"benefit": "rollback ได้ทุกเมื่อ ด้วย git revert",
},
"pulled_automatically": {
"name": "Pulled Automatically",
"description": "GitOps agent (ArgoCD/Flux) ดึง config จาก Git → apply อัตโนมัติ",
"benefit": "ไม่ต้อง kubectl apply มือ — auto-sync",
},
"continuously_reconciled": {
"name": "Continuously Reconciled",
"description": "ตรวจสอบอยู่ตลอดว่า actual state = desired state — fix drift อัตโนมัติ",
"benefit": "ถ้าใครแก้ manual → GitOps agent จะ revert กลับ",
},
}
TOOLS = {
"argocd": {
"name": "Argo CD",
"description": "GitOps controller สำหรับ Kubernetes — UI ดี, application CRD",
"use_case": "Multi-cluster, multi-app deployments",
},
"flux": {
"name": "Flux CD",
"description": "GitOps toolkit จาก Weaveworks — lightweight, composable",
"use_case": "Simple setups, Helm/Kustomize native",
},
}
def show_principles(self):
print("=== GitOps Principles ===\n")
for key, p in self.PRINCIPLES.items():
print(f"[{p['name']}]")
print(f" {p['description']}")
print()
def show_tools(self):
print("=== GitOps Tools ===")
for key, tool in self.TOOLS.items():
print(f" [{tool['name']}] {tool['description']}")
gp = GitOpsPrinciples()
gp.show_principles()
gp.show_tools()
Repository Structure
# repo_structure.py — GitOps repository structure for LocalAI
import json
class RepoStructure:
STRUCTURE = """
localai-gitops/
├── apps/
│ └── localai/
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── configmap.yaml
│ │ ├── pvc.yaml
│ │ └── kustomization.yaml
│ └── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ └── production/
│ ├── kustomization.yaml
│ ├── patch-replicas.yaml
│ └── patch-resources.yaml
├── models/
│ ├── dev/
│ │ └── models.yaml
│ ├── staging/
│ │ └── models.yaml
│ └── production/
│ └── models.yaml
├── argocd/
│ ├── application-dev.yaml
│ ├── application-staging.yaml
│ └── application-production.yaml
└── scripts/
├── promote-model.sh
└── rollback.sh
"""
BASE_DEPLOYMENT = """
# apps/localai/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: localai
labels:
app: localai
spec:
replicas: 1
selector:
matchLabels:
app: localai
template:
metadata:
labels:
app: localai
spec:
containers:
- name: localai
image: localai/localai:latest-aio-gpu-nvidia-cuda-12
ports:
- containerPort: 8080
env:
- name: THREADS
value: "4"
- name: CONTEXT_SIZE
value: "4096"
volumeMounts:
- name: models
mountPath: /build/models
- name: config
mountPath: /build/models/config
resources:
requests:
memory: 8Gi
limits:
memory: 16Gi
nvidia.com/gpu: 1
volumes:
- name: models
persistentVolumeClaim:
claimName: localai-models
- name: config
configMap:
name: localai-models-config
"""
def show_structure(self):
print("=== Repository Structure ===")
print(self.STRUCTURE[:500])
def show_deployment(self):
print("\n=== Base Deployment ===")
print(self.BASE_DEPLOYMENT[:500])
repo = RepoStructure()
repo.show_structure()
repo.show_deployment()
Model Management via Git
# model_mgmt.py — Model management through GitOps
import json
class ModelManagement:
MODEL_CONFIG = """
# models/production/models.yaml
models:
- name: llama-3-8b
url: "huggingface://TheBloke/Llama-3-8B-GGUF/llama-3-8b.Q4_K_M.gguf"
backend: llama-cpp
parameters:
model: llama-3-8b.Q4_K_M.gguf
context_size: 4096
threads: 4
- name: mistral-7b
url: "huggingface://TheBloke/Mistral-7B-v0.3-GGUF/mistral-7b-v0.3.Q4_K_M.gguf"
backend: llama-cpp
parameters:
model: mistral-7b-v0.3.Q4_K_M.gguf
context_size: 8192
threads: 4
- name: whisper-large
url: "huggingface://ggerganov/whisper.cpp/ggml-large-v3.bin"
backend: whisper
"""
PROMOTION_FLOW = {
"step1": "Developer pushes model config change → PR to dev branch",
"step2": "CI tests: lint YAML, validate model URLs, check resource limits",
"step3": "Review + merge → ArgoCD auto-deploys to dev cluster",
"step4": "Smoke tests pass → PR from dev to staging branch",
"step5": "Staging tests (load test, quality eval) → PR from staging to production",
"step6": "Production deploy → ArgoCD syncs → canary analysis",
}
CODE = """
# model_promoter.py — Promote model between environments
import subprocess
import json
import yaml
from pathlib import Path
class ModelPromoter:
def __init__(self, repo_path):
self.repo_path = Path(repo_path)
def get_models(self, env):
'''Get models config for environment'''
config_path = self.repo_path / "models" / env / "models.yaml"
with open(config_path) as f:
return yaml.safe_load(f)
def promote(self, model_name, source_env, target_env):
'''Promote a model from one environment to another'''
source = self.get_models(source_env)
target = self.get_models(target_env)
# Find model in source
model = None
for m in source.get('models', []):
if m['name'] == model_name:
model = m
break
if not model:
return {'error': f'Model {model_name} not found in {source_env}'}
# Add/update in target
target_models = target.get('models', [])
existing = [i for i, m in enumerate(target_models) if m['name'] == model_name]
if existing:
target_models[existing[0]] = model
else:
target_models.append(model)
target['models'] = target_models
# Write target config
target_path = self.repo_path / "models" / target_env / "models.yaml"
with open(target_path, 'w') as f:
yaml.dump(target, f, default_flow_style=False)
# Git commit
subprocess.run(['git', 'add', str(target_path)], cwd=self.repo_path)
subprocess.run([
'git', 'commit', '-m',
f'Promote {model_name} from {source_env} to {target_env}'
], cwd=self.repo_path)
return {
'model': model_name,
'from': source_env,
'to': target_env,
'status': 'committed',
}
def rollback(self, env, commit_hash=None):
'''Rollback environment to previous state'''
if commit_hash:
cmd = ['git', 'revert', '--no-commit', commit_hash]
else:
cmd = ['git', 'revert', '--no-commit', 'HEAD']
subprocess.run(cmd, cwd=self.repo_path)
subprocess.run(['git', 'commit', '-m', f'Rollback {env}'], cwd=self.repo_path)
return {'env': env, 'status': 'rolled back'}
# promoter = ModelPromoter("/path/to/localai-gitops")
# promoter.promote("llama-3-8b", "staging", "production")
"""
def show_config(self):
print("=== Model Config ===")
print(self.MODEL_CONFIG[:400])
def show_flow(self):
print("\n=== Promotion Flow ===")
for key, step in self.PROMOTION_FLOW.items():
print(f" {step}")
def show_code(self):
print("\n=== Promoter Code ===")
print(self.CODE[:500])
mgmt = ModelManagement()
mgmt.show_config()
mgmt.show_flow()
ArgoCD Application
# argocd.py — ArgoCD application setup
import json
class ArgoCDSetup:
APPLICATION = """
# argocd/application-production.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: localai-production
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/localai-gitops.git
targetRevision: main
path: apps/localai/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: ai-production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 3m
"""
CI_PIPELINE = """
# .github/workflows/localai-gitops.yml
name: LocalAI GitOps CI
on:
pull_request:
paths:
- 'apps/localai/**'
- 'models/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate YAML
run: |
pip install yamllint
yamllint apps/ models/
- name: Validate Kustomize
run: |
for overlay in apps/localai/overlays/*/; do
kustomize build $overlay > /dev/null
echo "Valid: $overlay"
done
- name: Check Model URLs
run: |
python scripts/validate_models.py models/
- name: Dry-run ArgoCD Diff
run: |
argocd app diff localai-production --local apps/localai/overlays/production
"""
def show_application(self):
print("=== ArgoCD Application ===")
print(self.APPLICATION[:500])
def show_ci(self):
print("\n=== CI Pipeline ===")
print(self.CI_PIPELINE[:400])
argocd = ArgoCDSetup()
argocd.show_application()
argocd.show_ci()
Monitoring & Rollback
# rollback.py — GitOps rollback strategies
import json
class GitOpsRollback:
STRATEGIES = {
"git_revert": {
"name": "Git Revert",
"description": "git revert commit → ArgoCD auto-sync → rollback อัตโนมัติ",
"speed": "1-3 นาที (ขึ้นกับ sync interval)",
"command": "git revert HEAD && git push",
},
"argocd_rollback": {
"name": "ArgoCD Rollback",
"description": "ArgoCD UI → Application → History → Rollback to previous",
"speed": "< 1 นาที",
"command": "argocd app rollback localai-production",
},
"model_swap": {
"name": "Model Config Swap",
"description": "เปลี่ยน model URL กลับ version เก่าใน models.yaml",
"speed": "2-5 นาที (commit + sync + model download)",
},
}
MONITORING = {
"sync_status": "ArgoCD sync status: Synced, OutOfSync, Unknown",
"health_status": "Application health: Healthy, Degraded, Missing",
"drift_detection": "ตรวจจับว่า actual state ≠ desired state → alert",
"deployment_history": "Git log = deployment history — ดูว่าใครเปลี่ยนอะไรเมื่อไหร่",
}
def show_strategies(self):
print("=== Rollback Strategies ===\n")
for key, s in self.STRATEGIES.items():
print(f"[{s['name']}]")
print(f" {s['description']}")
print(f" Speed: {s['speed']}")
print()
def show_monitoring(self):
print("=== Monitoring ===")
for key, desc in self.MONITORING.items():
print(f" [{key}] {desc}")
rollback = GitOpsRollback()
rollback.show_strategies()
rollback.show_monitoring()
FAQ - คำถามที่พบบ่อย
Q: GitOps กับ CI/CD ธรรมดาต่างกันอย่างไร?
A: CI/CD ธรรมดา: CI build → CD push ไป cluster (push-based) GitOps: CI build → commit config ไป Git → GitOps agent pull จาก Git → apply (pull-based) ข้อดี GitOps: Git = audit trail, rollback ง่าย (git revert), drift detection, declarative ข้อดี push-based: ง่ายกว่า setup, ไม่ต้อง agent ใน cluster
Q: ArgoCD กับ Flux อันไหนดีกว่า?
A: ArgoCD: UI ดีมาก, Application CRD, multi-cluster, RBAC, SSO — เหมาะ team ใหญ่ Flux: lightweight, GitOps Toolkit (composable), Helm/Kustomize native — เหมาะ simple setups เลือก ArgoCD: ถ้าต้องการ UI + multi-cluster + team collaboration เลือก Flux: ถ้าต้องการ lightweight + Helm-native + ไม่ต้อง UI
Q: Model files ใหญ่มาก เก็บใน Git ได้ไหม?
A: ไม่ควรเก็บ model files ใน Git (หลาย GB) — เก็บเฉพาะ config (URL, parameters) Config ใน Git: model name, download URL, parameters, resource limits Model files: เก็บใน HuggingFace, S3, GCS หรือ internal registry LocalAI download models ตอน startup จาก URL ที่ระบุใน config
Q: ต้องมี Kubernetes ไหม?
A: GitOps เหมาะกับ Kubernetes มากที่สุด — ArgoCD/Flux ออกแบบมาสำหรับ K8s ถ้าไม่มี K8s: ใช้ Docker Compose + Git + simple deploy script ก็ได้ ทางเลือก: Ansible + Git (GitOps-style แต่ไม่ใช่ K8s), Terraform + Git สำหรับ LocalAI: Docker Compose พอสำหรับ single server, K8s + GitOps สำหรับ production scale
