Repository navigation
feat(deploy): Argo CD observe-only setup and Qwen judge release - #266
google-oss-prow[bot] merged 10 commits into
Conversation
Add Argo CD install values and Applications for docs-agent, gateway-guardrails and llm-runtime, tracking main with no syncPolicy so Argo reports drift without changing the cluster. Deployment-specific values (domain, ACME email, CORS origins, image overrides) come from a private config repo as a second source, so no personal or environment data lives in this repo. Secrets stay as Kubernetes Secrets referenced by name. Add llm-judge: a second llm-runtime release using the existing qwen profile on its own GPU, served as flo-judge, for LLM-as-a-judge evals. Gemma keeps answering; a different model family judges. An Istio AuthorizationPolicy limits callers to eval, triage and Langfuse. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
Per-environment values move to deploy/envs/prod/ in this repo and are read by Argo as a second source; no private config repo is needed. gateway-guardrails gains gateway.tls.acme.createIssuer (default true). With false, the Certificate uses an existing ClusterIssuer, so the ACME contact email lives only in the cluster and not in public values. deploy/secrets/ lists the Secrets the stack needs and adds a script that backs them up to a private local directory outside the repo. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
…ecrets folder Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
Capture the live Langfuse release values in deploy/envs/prod/langfuse, including the settings that were applied by hand (public NextAuth URL, sign-up disabled, image pull policy). Pin salt, encryption key and NextAuth secret to the existing langfuse-app Secret so an upgrade cannot rotate them. Allow LLM connections to llm-judge and llm-stable. Add an observe-only Argo Application for Langfuse, and a viewer account bound to role:readonly. Password hashes are passed at install time. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
…without sidecars Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
The live MCP deployment carried LANGFUSE_PUBLIC_KEY/SECRET_KEY added with kubectl set env. Neither kubectl diff nor Argo flags fields added outside Git, so a clean redeploy would have silently dropped tracing. Add a generic mcp.extraEnv list (default empty) and set the two Secret references in deploy/envs/prod/docs-agent.yaml. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
The live MCP server runs a fork image (santhoshtoorpu/...:otel-flo) whose tracing code was never upstreamed, so any image built from main loses the TOOL / RETRIEVER / EMBEDDING spans. Port it onto current main: - flo_tracing.py (from the live image): no-op unless LANGFUSE_PUBLIC_KEY is set; tracing errors never break a search. Adds a traced() decorator so each function gets one line instead of a re-indented body; functools.wraps keeps the signatures FastMCP builds tool schemas from. - Traced: the three MCP tools, search_docs_auto, search_collection, embed_query. Outputs record hit count, retrieval mode, intent and top scores. - Fix: top_scores read the score from entity, but Milvus puts distance on the hit, so it was always empty in production. - langfuse==4.15.2 (the version in the live image). Only the tracing is taken from the image; it was built from an older snapshot, and main's later fixes (argument validation, release dates, repo filter on code search) are kept. Verified in-cluster with the real SDK and the evaluation project: one search_kubeflow_docs call produced TOOL, RETRIEVER and EMBEDDING observations with environment=staging. tests/test_flo_tracing.py covers the decorator, the output summary and the tool schemas. Co-authored-by: SanthoshToorpu <90833739+SanthoshToorpu@users.noreply.github.com> Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
Merging kubeflow#269 made the tests pass again, and the oke-cicd deploy job ran for the first time since September: it helm-upgraded the docs-agent release on the demo cluster with chart defaults (only the MCP image changed; production stayed healthy). With Argo CD managing the cluster, two writers would keep overwriting each other. The workflow now tests every PR and, on main, only builds and pushes the MCP image. The kubectl/helm/OCI steps and the cluster credentials are gone; the job is renamed "Build and push MCP image" and still gated on ENABLE_OKE_DEPLOY. Rolling out an image is a PR to deploy/envs/<env>/docs-agent.yaml plus a sync in Argo CD. deploy/envs/prod pins ghcr.io/kubeflow/mcp-kubeflow-docs:0e49ebd, the image that CI deployed, so Git matches the cluster instead of pointing back at the old fork image. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
dc4ec70 to
e361a7a
Compare
| - key: kubernetes.io/hostname | ||
| operator: NotIn | ||
| values: | ||
| - 10.0.10.102 |
There was a problem hiding this comment.
Can you please look into not using IPs?
There was a problem hiding this comment.
Removed in 6757c39. The GPU nodes are tainted nvidia.com/gpu:NoSchedule, so Langfuse pods never schedule there; the hostname anti-affinity was redundant.
| - key: kubernetes.io/hostname | ||
| operator: NotIn | ||
| values: | ||
| - 10.0.10.102 |
There was a problem hiding this comment.
Removed in 6757c39. The GPU nodes are tainted nvidia.com/gpu:NoSchedule, so Langfuse pods never schedule there; the hostname anti-affinity was redundant.
| - key: kubernetes.io/hostname | ||
| operator: NotIn | ||
| values: | ||
| - 10.0.10.102 |
There was a problem hiding this comment.
Removed in 6757c39. The GPU nodes are tainted nvidia.com/gpu:NoSchedule, so Langfuse pods never schedule there; the hostname anti-affinity was redundant.
| - key: kubernetes.io/hostname | ||
| operator: NotIn | ||
| values: | ||
| - 10.0.10.102 |
There was a problem hiding this comment.
Removed in 6757c39. The GPU nodes are tainted nvidia.com/gpu:NoSchedule, so Langfuse pods never schedule there; the hostname anti-affinity was redundant.
| name: langfuse-app | ||
| key: encryption-key | ||
| nextauth: | ||
| url: https://langfuse.kmrrohit.space |
There was a problem hiding this comment.
Can you look into the url?
There was a problem hiding this comment.
These are the demo cluster's hostnames. Kubeflow doesn't have a subdomain for this yet, so they live only in deploy/envs/prod/ (env config); the charts take the domain as a value. Once a project-owned domain exists, it's a change to deploy/envs/prod/ only. I'd rather keep the real hosts in Git so Argo CD reflects what's live, but happy to switch them to placeholders if you prefer.
| - key: kubernetes.io/hostname | ||
| operator: NotIn | ||
| values: | ||
| - 10.0.10.102 |
There was a problem hiding this comment.
Removed in 6757c39. The GPU nodes are tainted nvidia.com/gpu:NoSchedule, so Langfuse pods never schedule there; the hostname anti-affinity was redundant.
GPU nodes carry the nvidia.com/gpu NoSchedule taint, so Langfuse pods never schedule there; the per-hostname rule was redundant. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
The gateway-guardrails chart defaults to ghcr.io/kubeflow/kubeflow-session-issuer, but nothing built it. Push it on main tagged by short SHA; prod moves to it in a follow-up once the first image exists. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
|
Thanks @kmr-rohit |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: tarekabouzeid The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
server.py imports flo_tracing since kubeflow#266, but the Dockerfile copies an explicit file list that was not updated, so every image built from main since then exits on start with ModuleNotFoundError. Production and staging still run 0e49ebd, built before kubeflow#266, so nothing live broke; promoting any newer commit would have. Found by the first /eval run, whose preview used such an image. A test now checks that every local module the copied files import is copied too. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
server.py imports flo_tracing since kubeflow#266, but the Dockerfile copies an explicit file list that was not updated, so every image built from main since then exits on start with ModuleNotFoundError. Production and staging still run 0e49ebd, built before kubeflow#266, so nothing live broke; promoting any newer commit would have. Found by the first /eval run, whose preview used such an image. A test now checks that every local module the copied files import is copied too. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
server.py imports flo_tracing since kubeflow#266, but the Dockerfile copies an explicit file list that was not updated, so every image built from main since then exits on start with ModuleNotFoundError. Production and staging still run 0e49ebd, built before kubeflow#266, so nothing live broke; promoting any newer commit would have. Found by the first /eval run, whose preview used such an image. A test now checks that every local module the copied files import is copied too. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
server.py imports flo_tracing since kubeflow#266, but the Dockerfile copies an explicit file list that was not updated, so every image built from main since then exits on start with ModuleNotFoundError. Production and staging still run 0e49ebd, built before kubeflow#266, so nothing live broke; promoting any newer commit would have. Found by the first /eval run, whose preview used such an image. A test now checks that every local module the copied files import is copied too. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
server.py imports flo_tracing since kubeflow#266, but the Dockerfile copies an explicit file list that was not updated, so every image built from main since then exits on start with ModuleNotFoundError. Production and staging still run 0e49ebd, built before kubeflow#266, so nothing live broke; promoting any newer commit would have. Found by the first /eval run, whose preview used such an image. A test now checks that every local module the copied files import is copied too. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
server.py imports flo_tracing since kubeflow#266, but the Dockerfile copies an explicit file list that was not updated, so every image built from main since then exits on start with ModuleNotFoundError. Production and staging still run 0e49ebd, built before kubeflow#266, so nothing live broke; promoting any newer commit would have. Found by the first /eval run, whose preview used such an image. A test now checks that every local module the copied files import is copied too. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
server.py imports flo_tracing since kubeflow#266, but the Dockerfile copies an explicit file list that was not updated, so every image built from main since then exits on start with ModuleNotFoundError. Production and staging still run 0e49ebd, built before kubeflow#266, so nothing live broke; promoting any newer commit would have. Found by the first /eval run, whose preview used such an image. A test now checks that every local module the copied files import is copied too. Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
… and image/credential fixes (#274) * feat(deploy): staging deploys and evaluates every commit on main The staging image tag is the synced commit's short SHA (the tag CI pushes), so each merge changes staging, Argo CD syncs it and the eval runs. Sync retries cover the minutes before CI has pushed the image. Signed-off-by: Rohit Kumar <rr7433446@gmail.com> * feat(deploy): promote production by commit, synced automatically Production Applications pin a commit (# prod-revision) and take both images from it, so the session issuer now comes from ghcr.io/kubeflow and needs no pull secret. deploy/promote.sh opens the promotion PR with the changelog and an eval table; merging it deploys, reverting it rolls back. The root Application flo-apps applies deploy/argocd/apps/ from main; otel-collector also syncs automatically. Signed-off-by: Rohit Kumar <rr7433446@gmail.com> * feat(deploy): weekly Langfuse data retention Self-hosted Langfuse retention is an Enterprise feature, so a CronJob deletes traces, observations, scores and raw S3 events older than 4 weeks for Flo-live and 2 weeks for flo-eval. Datasets are kept. Signed-off-by: Rohit Kumar <rr7433446@gmail.com> * docs(deploy): document sync policy, promotion and retention Signed-off-by: Rohit Kumar <rr7433446@gmail.com> * fix(deploy): keep Langfuse app credentials out of the chart's own Secret The chart renders a Secret named langfuse-app and fills it only with values it finds through Helm lookup or generates. Our values referenced the salt, encryption key and NextAuth secret from that same Secret, so under Argo CD, where lookup returns nothing, the chart treated them as provided elsewhere and rendered langfuse-app empty. The sync on 2026-10-16 applied it and wiped the values; the running pods kept them in memory, and new pods could not start. Read the credentials from langfuse-app-credentials, which an admin creates and Argo CD does not manage, and ignore /data on langfuse-app so a sync cannot blank it again. Signed-off-by: Rohit Kumar <rr7433446@gmail.com> * feat(eval): /eval runs the end-to-end eval on a pull request before merge The staging eval only runs after a merge, so a change that hurts answers is found on main. A maintainer can now comment /eval on a PR: - .github/workflows/pr-eval.yaml checks the commenter is an OWNERS approver, builds the PR's MCP image (pr-<n>-<sha8>) and labels the PR eval and eval-sha:<sha>, which pins the reviewed commit. One PR at a time; an eval that holds the preview for 3 hours is cleared. - The docs-agent-preview ApplicationSet deploys that commit into docs-agent-preview under a restricted AppProject (that namespace, the chart's kinds, nothing cluster-scoped); the namespace enforces Pod Security baseline. - The docs-agent-pr-eval ApplicationSet runs the pr-eval chart from main. Its Job (tests/eval/pr_eval.py) waits for the rollout, evaluates main (staging) and then the PR with the same model, index and judge, comments the comparison with public Langfuse traces per case, and removes the labels, which tears the preview down. Grading always comes from main. staging_eval.py gains evaluate(), a run-name prefix, an environment setting and public traces; the staging PostSync run is unchanged. Preview agent traces go to the eval project as environment preview. Signed-off-by: Rohit Kumar <rr7433446@gmail.com> * fix(mcp): copy flo_tracing.py into the MCP image server.py imports flo_tracing since #266, but the Dockerfile copies an explicit file list that was not updated, so every image built from main since then exits on start with ModuleNotFoundError. Production and staging still run 0e49ebd, built before #266, so nothing live broke; promoting any newer commit would have. Found by the first /eval run, whose preview used such an image. A test now checks that every local module the copied files import is copied too. Signed-off-by: Rohit Kumar <rr7433446@gmail.com> --------- Signed-off-by: Rohit Kumar <rr7433446@gmail.com>
What
deploy/argocd/: Argo CD install values (no Dex, no notifications, no ApplicationSet, anonymous and exec off) and Applications fordocs-agent,gateway-guardrails,llm-runtimeandllm-judge, trackingmain.deploy/envs/prod/: production values for the demo cluster (domain, CORS origins, image overrides) and the public HTTPS ingress for Langfuse and Argo CD. Argo reads them as a second source.deploy/argocd/README.md: the Kubernetes Secrets the stack needs, by name only. No secret values in Git.deploy/envs/prod/langfuse/values.yaml+deploy/argocd/apps/langfuse.yaml: Langfuse release values captured from the cluster, including settings that had been applied by hand (public NextAuth URL, sign-up disabled, pull policy). Generated secrets are pinned to the existinglangfuse-appSecret, so an upgrade cannot rotate them. LLM connections may callllm-judge.Argo CD
vieweraccount bound torole:readonly; admin and viewer password hashes are passed at install time (README).gateway-guardrails0.2.1: newgateway.tls.acme.createIssuer(defaulttrue, no behaviour change). Withfalsethe Certificate uses an existing ClusterIssuer, so the ACME contact email stays in the cluster instead of in public values.llm-judge: a secondllm-runtimerelease with the existingqwenprofile, served asflo-judge, on the second GPU node, with an AuthorizationPolicy declaring its intended callers (not enforced yet, see Known gaps).MCP tracing upstreamed (
docs-agent-mcp/mcp-server/flo_tracing.py, co-authored with @SanthoshToorpu): the live MCP server runs a fork image (santhoshtoorpu/...:otel-flo) whose Langfuse tracing was never onmain. Ported onto currentmain(not copied: the image predates main's argument validation, release-date formatting and code-search repo filter). Atraced()decorator adds TOOL / RETRIEVER / EMBEDDING observations to the three tools, both retrievers andembed_query; no-op without Langfuse keys. Also fixestop_scores, which was always empty in production.langfuse==4.15.2, as in the live image. Verified in-cluster with the real SDK.CI no longer deploys (
.github/workflows/oke-cicd.yaml): after fix(ci): make the PR checks pass again on main #269 made the tests pass, the deploy job ran again andhelm upgraded the demo cluster'sdocs-agentrelease with chart defaults (only the MCP image changed; production stayed healthy). The workflow now tests every PR and, onmain, only builds and pushes the MCP image; allkubectl/helm/OCI steps and cluster credentials are removed. Since a push runs the workflow file from the merged commit, merging this PR cannot trigger another cluster deploy.deploy/envs/prodpinsghcr.io/kubeflow/mcp-kubeflow-docs:0e49ebd, the image now running, so Git matches the cluster.Why
syncPolicy. Argo reports drift betweenmainand the cluster; a maintainer clicks Sync to deploy. Hand edits now show as OutOfSync.Tested
deploy/envs/prod/values and rankubectl diffagainst the live cluster:llm-runtimeandgateway-guardrailsshow no difference (the ClusterIssuer is no longer rendered; it must be annotatedhelm.sh/resource-policy=keepbefore anyhelm upgrade, see the chart README);docs-agentdiffers only inchecksum/config.kubectl apply --dry-run=serverpasses for all Applications and the renderedllm-judgeresources.Known gaps
ml-infra(LLM, judge, Milvus, embeddings) are not enforced:docs-agentandml-infrahave no sidecar injection, so a pod in any namespace can reach these services (verified fromdefault). The public gateway policies are enforced and unaffected. Fix planned as a follow-up: sidecar injection per namespace,ml-infrafirst. Recorded indeploy/argocd/README.md.Not in this PR
Staging namespace, eval-on-sync hook, chat-widget feedback, and the CI change from
helm upgradeto build-only. These follow as separate PRs.