#!/bin/bash # 把已经跑着的 Deployment 收进 Gitea,交给 Argo CD。 # Secret 不进 Git。vLLM 的 Pod 模板有差异时,不会创建自动同步。 set -eu export KUBECONFIG=/root/.kube/config if kubectl -n argocd get application platform >/dev/null 2>&1; then echo "platform Application 已经在。这份脚本停在这里,避免再次滚动导航页。" exit 1 fi ROOT=/usr/cluster/gitops PASS='Gitops-argo-2026' REPO_URL='http://gitea.gitea.svc.cluster.local:3000/gitops/platform.git' mkdir -p /usr/cluster/gitea "$ROOT/manifests" chown 1000:1000 /usr/cluster/gitea chmod 750 /usr/cluster/gitea if ! docker image inspect docker.m.daocloud.io/gitea/gitea:1.24.6 >/dev/null 2>&1; then if docker image inspect docker.m.daocloud.io/gitea/gitea:1.22.6 >/dev/null 2>&1; then sed -i 's#gitea/gitea:1.24.6#gitea/gitea:1.22.6#' /tmp/gitea.yaml else echo "Gitea 镜像不在本机" exit 1 fi fi kubectl apply -f /tmp/gitea.yaml kubectl apply -f /tmp/argocd-ui-proxy.yaml kubectl -n gitea rollout status deploy/gitea --timeout=180s kubectl -n argocd rollout status deploy/argocd-ui-proxy --timeout=180s echo "等待 Gitea" ok=0 for i in $(seq 1 40); do if curl -fsS --max-time 3 http://127.0.0.1:3002/api/healthz >/dev/null; then ok=1 break fi sleep 3 done if [ "$ok" != "1" ]; then echo "Gitea 没有就绪" kubectl -n gitea logs deploy/gitea --tail=40 || true exit 1 fi kubectl -n gitea exec deploy/gitea -- su git -c "gitea admin user create --admin --username gitops --password $PASS --email gitops@example.com --must-change-password=false" || true curl -fsS --max-time 15 -u "gitops:${PASS}" \ -H 'Content-Type: application/json' \ -d '{"name":"platform","private":true,"default_branch":"main","auto_init":false}' \ http://127.0.0.1:3002/api/v1/user/repos || true python3 /tmp/export_gitops.py "$ROOT/manifests" cat > "$ROOT/README.md" << 'EOF' # platform Argo CD 盯着这个仓库的 manifests 目录。 这里有 llm、dify、observability 的 Deployment、Service、ExternalSecret 和 ScaledObject。 Secret 的内容在集群和 Vault 里,不在这个仓库。 dify/worker 的副本数由 KEDA 改写,Application 忽略 spec.replicas。 自动同步不会删除 Git 里没有的对象。 EOF python3 - << 'PY' import json, subprocess, sys from pathlib import Path import yaml root = Path("/usr/cluster/gitops/manifests") bad = [] for path in sorted(root.rglob("deploy-*.yaml")): desired = yaml.safe_load(path.read_text()) ns = desired["metadata"]["namespace"] name = desired["metadata"]["name"] dry = json.loads(subprocess.check_output([ "kubectl", "apply", "--dry-run=server", "-f", str(path), "-o", "json" ])) live = json.loads(subprocess.check_output([ "kubectl", "-n", ns, "get", "deploy", name, "-o", "json" ])) def template(obj): tmpl = obj["spec"]["template"] meta = dict(tmpl.get("metadata") or {}) meta.pop("creationTimestamp", None) tmpl = dict(tmpl) tmpl["metadata"] = meta return tmpl if template(dry) != template(live): bad.append(f"{ns}/{name}") Path(f"/tmp/gitops-dry-{ns}-{name}.json").write_text(json.dumps(template(dry), indent=2, ensure_ascii=False)) Path(f"/tmp/gitops-live-{ns}-{name}.json").write_text(json.dumps(template(live), indent=2, ensure_ascii=False)) if bad: print("TEMPLATE_MISMATCH " + ",".join(bad)) sys.exit(2) print("templates match") PY python3 - << 'PY' import json, subprocess from pathlib import Path raw = subprocess.check_output(["kubectl", "get", "pods", "-A", "-o", "json"]) data = json.loads(raw) keep = [] for pod in data["items"]: ns = pod["metadata"]["namespace"] if ns in ("llm", "dify", "observability"): keep.append({ "ns": ns, "name": pod["metadata"]["name"], "uid": pod["metadata"]["uid"], }) Path("/tmp/gitops-pods-before.json").write_text(json.dumps(keep)) print("snapshotted", len(keep), "pods") PY cd "$ROOT" rm -rf .git git init -b main git add manifests README.md git -c user.email=gitops@local -c user.name=gitops commit -m "纳入 llm、dify、observability 的工作负载" git push "http://gitops:${PASS}@127.0.0.1:3002/gitops/platform.git" HEAD:main git remote add origin "$REPO_URL" kubectl -n argocd create secret generic gitops-repo \ --from-literal=type=git \ --from-literal=url="$REPO_URL" \ --from-literal=username=gitops \ --from-literal=password="$PASS" \ --dry-run=client -o yaml | kubectl apply -f - kubectl -n argocd label secret gitops-repo argocd.argoproj.io/secret-type=repository --overwrite kubectl apply -f /tmp/argocd-app-platform.yaml echo "等待 Application" synced=0 for i in $(seq 1 24); do sync=$(kubectl -n argocd get application platform -o jsonpath='{.status.sync.status}' 2>/dev/null || true) health=$(kubectl -n argocd get application platform -o jsonpath='{.status.health.status}' 2>/dev/null || true) echo "t=${i} sync=${sync:-empty} health=${health:-empty}" if [ "$sync" = "Synced" ] && [ "$health" = "Healthy" ]; then synced=1 break fi sleep 5 done python3 - << 'PY' import json from pathlib import Path before = { (p["ns"], p["uid"]): p["name"] for p in json.loads(Path("/tmp/gitops-pods-before.json").read_text()) } import subprocess raw = subprocess.check_output(["kubectl", "get", "pods", "-A", "-o", "json"]) changed = [] for pod in json.loads(raw)["items"]: ns = pod["metadata"]["namespace"] if ns not in ("llm", "dify", "observability"): continue uid = pod["metadata"]["uid"] if (ns, uid) not in before: changed.append(ns + "/" + pod["metadata"]["name"]) print("new_pods", ",".join(changed) if changed else "none") if any(item.startswith("llm/vllm") for item in changed): raise SystemExit(3) PY if [ "$synced" != "1" ]; then echo "Application 还没有同时处于 Synced 和 Healthy" kubectl -n argocd get application platform -o yaml | sed -n '/status:/,$p' | head -80 exit 1 fi echo "纳管完成,现有业务 Pod 没有被替换" VLLM_UID=$(kubectl -n llm get pod -l app=vllm -o jsonpath='{.items[0].metadata.uid}') python3 - << 'PY' import yaml from pathlib import Path path = Path("/usr/cluster/gitops/manifests/llm/deploy-nav.yaml") doc = yaml.safe_load(path.read_text()) meta = doc["spec"]["template"].setdefault("metadata", {}) anns = meta.setdefault("annotations", {}) anns["gitops.local/demo"] = "nav-1" meta["annotations"] = anns path.write_text(yaml.safe_dump(doc, sort_keys=False, allow_unicode=True)) print("nav annotation added") PY cd "$ROOT" git add manifests/llm/deploy-nav.yaml git -c user.email=gitops@local -c user.name=gitops commit -m "给导航页加一个 GitOps 演示注解" git push "http://gitops:${PASS}@127.0.0.1:3002/gitops/platform.git" HEAD:main kubectl -n argocd annotate application platform argocd.argoproj.io/refresh=hard --overwrite echo "等待导航页按 Git 里的注解滚动" demo_ok=0 for i in $(seq 1 24); do now=$(kubectl -n llm get pod -l app=nav -o jsonpath='{.items[0].metadata.uid}' 2>/dev/null || true) ready=$(kubectl -n llm get deploy nav -o jsonpath='{.status.readyReplicas}' 2>/dev/null || true) vnow=$(kubectl -n llm get pod -l app=vllm -o jsonpath='{.items[0].metadata.uid}' 2>/dev/null || true) echo "demo t=${i} nav_ready=${ready:-empty} vllm_same=$([ "$vnow" = "$VLLM_UID" ] && echo yes || echo NO)" if [ "$vnow" != "$VLLM_UID" ]; then echo "vLLM Pod 被替换了,停止" exit 4 fi if [ -n "$now" ] && [ "$ready" = "1" ]; then ann=$(kubectl -n llm get pod -l app=nav -o jsonpath='{.items[0].metadata.annotations.gitops\.local/demo}' 2>/dev/null || true) if [ "$ann" = "nav-1" ]; then demo_ok=1 break fi fi sleep 5 done if [ "$demo_ok" != "1" ]; then echo "导航页没有在预期时间内带上新注解" exit 1 fi echo "演示完成:Git 提交后 Argo CD 只滚动了导航页"