Skip to content

PR #3 review fixes (batch) - #4

Merged
radudobrinescu merged 10 commits into
mainfrom
feat/bedrock-self-service
Sep 18, 2026
Merged

radudobrinescu merged 10 commits into
mainfrom
feat/bedrock-self-service

Conversation

@radudobrinescu

@radudobrinescu radudobrinescu commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

PR #3 review — batch of follow-up fixes + self-service Bedrock

Integration branch for the fixes surfaced while reviewing #3, plus self-service
Bedrock enrollment. Validated end-to-end on a real EKS cluster in eu-central-1,
both with and without EKS Managed Capabilities
(see summary at the bottom).


Included fixes

  • Self-service Amazon Bedrock enrollment (BedrockModel CRD). new-model --source bedrock [--list-available-models] resolves the region/partition-correct
    id + price from the live Bedrock/Price List APIs and commits a BedrockModel CR;
    litellm-sync registers it; litellm.yaml ships model_list: []. Replaces the
    static, region-pinned default from AWS European Sov. Cloud Support + Minor Refactoring #3.
  • Langfuse PVCs: document the implicit default StorageClass (pinning gp3
    breaks Auto Mode; comments only).
  • --extra-arg YAML escaping via json.dumps (a trailing \ produced an
    unparseable manifest; a\tc became a TAB). Adds TestYamlScalarQuoting.
  • hf-token Secret built in-process, never on kubectl argv (was readable in
    /proc/<pid>/cmdline). Adds tests/test_gitops.py.
  • status --check exits non-zero on real failures (gateway down / model
    non-200 / Langfuse unhealthy), exit 0 for the expected "no models yet" case.
  • Comment the cluster_endpoint_public_access_cidrs placeholder in the tfvars
    so an unedited copy fails closed with the intended message, not a CIDR parse error.
  • Karpenter node-role parity guard — verified the hand-rolled node role is a
    strict superset of the module's base set; no instance-profile regression. Comment.
  • fix(argocd): permit BedrockModel in the ai-platform-workloads AppProject.
    (Found via e2e.) The workloads AppProject whitelist allowed only the four kro.run
    kinds, so ArgoCD refused every Bedrock enrollment (resource … BedrockModel is not permitted in project). Whitelisted bedrock.ai-platform/BedrockModel (still
    namespaced-only).
  • fix(argocd): allow the workloads destination by name for EKS-managed ArgoCD.
    (Found via e2e; a AWS European Sov. Cloud Support + Minor Refactoring #3 regression.) PR AWS European Sov. Cloud Support + Minor Refactoring #3's esc commit changed the workloads
    AppProject destinations from name: local-clusterserver: https://kubernetes.default.svc. That works on self-managed ArgoCD but breaks
    EKS-managed ArgoCD (managed capabilities — the original tested setup), where the
    in-cluster destination is matched by name: every workload app failed with
    InvalidSpecError. Re-added the by-name match alongside the URL entries so both
    modes work.

End-to-end validation (eu-central-1, real cluster)

Provisioned + torn down a full cluster twice — once without and once with
EKS Managed Capabilities — using the README flow. Validated on both:

  • platformctl up → healthy cluster; all ArgoCD apps Synced/Healthy; BedrockModel CRD present.
  • status --check: gateway ✓, Langfuse ✓, correct exit codes (0 with no models; live-model path 200).
  • --list-available-models: live Bedrock catalog + eu.-profile resolution + Price List pricing.
  • Bedrock enroll (nova-liteeu.amazon.nova-lite-v1:0) → CR → litellm-sync register → live chat 200.
  • HuggingFace new-model (Qwen2.5-0.5B) → Karpenter GPU node → vLLM → live chat 200.
  • tunnel (all 5 UIs), undeploy (deregistration for both types), onboard-team (AITeam → ns/quota).
  • down → clean teardown; residual checks (clusters/VPCs/instances/roles) empty.

The last two fixes above were discovered and fixed during this e2e (self-service
was broken on managed capabilities before them). Teardown needed the documented
manual cleanup for orphan Karpenter instances + security groups (noted in the run).

Merge once satisfied (merge commit preserves each fix's commit; squash collapses them).

Replace the single static, region/partition-specific Bedrock entry in
litellm.yaml with self-service enrollment that mirrors the self-hosted path:

  ./platformctl new-model --source bedrock --list-available-models
  ./platformctl new-model --source bedrock <model> --deploy

- New BedrockModel CRD (bedrock.ai-platform/v1alpha1) - a plain, infra-less
  marker applied automatically by the platform-config ArgoCD app.
- litellm-sync generalized to watch BedrockModels alongside the serving tiers
  and register them as native bedrock/ upstreams (IRSA auth, no api_base). Same
  finalizer/reconcile lifecycle, so a live CR protects the model from the orphan
  sweep. Health probe no longer crashloops when a watched CRD is absent
  (kro=false / Bedrock-only installs).
- platformctl --source bedrock resolves the region/partition-correct invocation
  id (a cross-region inference profile where one exists, else the base model id)
  from the live Bedrock APIs, looks up per-token price from the AWS Price List
  API (best-effort; --input-cost/--output-cost override for partitions it
  doesn't cover, e.g. ESC), and commits a BedrockModel CR (reusing deploy_model).
- litellm.yaml now ships model_list: [] - nothing model-specific pinned in git,
  so the committed config is correct in any region/partition (ESC included).
- Docs: README day-one story + quick start, litellm-sync and workloads READMEs.

No Terraform changes: the LiteLLM pod's existing IRSA/region/STS/endpoint wiring
already covers invocation.
@radudobrinescu radudobrinescu changed the title feat: self-service Amazon Bedrock model enrollment (BedrockModel CRD) PR #3 review fixes (batch) Sep 17, 2026
@radudobrinescu
radudobrinescu marked this pull request as draft September 17, 2026 13:59
Re-lock all six stages on the current provider set (aws ~> 6.28, helm ~> 3.0)
using 'terraform providers lock' for linux_amd64, linux_arm64, darwin_amd64,
and darwin_arm64. Restores the cryptographic hash verification that terraform
init enforces, which was lost when the lock files were deleted during the
5.x -> 6.x / helm 2.x -> 3.x provider bump.

Resolved: aws 6.28.0, helm 3.3.0, kubernetes 2.38.0, alekc/kubectl 2.4.1,
null 3.3.2, time 0.14.2 (0.10.0 in oss-observability, pinned). All stages
pass 'terraform validate'.
…Redis PVCs

PR #3 dropped 'storageClass: gp3' from the ClickHouse and Redis persistence
blocks. That is the correct, portable choice — but it was undocumented and the
review flagged the resulting dependency on an implicit default.

The PVCs bind the cluster DEFAULT StorageClass, which Terraform marks per mode:
gp3 (ebs.csi.aws.com) on classic/self-managed clusters incl. ESC
(capabilities.blockstorage), and auto-ebs-sc (ebs.csi.eks.amazonaws.com) on EKS
Auto Mode. Pinning gp3 would break Auto Mode (no such class). Add comments on
both blocks stating the intent and the per-mode default, and note the edge where
a non-Auto cluster with blockstorage=false has no default SC.

No behavior change — comments only.
…ping

_yaml_scalar hand-rolled its YAML quoting and left backslashes unescaped in the
single-quote and no-quote branches: a token ending in '\' escaped the closing
quote (unterminated scalar -> a manifest ArgoCD can't parse), and a token like
'a\tc' was silently reinterpreted ('\t' -> TAB). --extra-arg is user-supplied and
interpolated into a committed, cluster-applied manifest, so this is an
injection / structure-alteration hazard, not just cosmetic.

YAML is a strict superset of JSON, so json.dumps(v) is a valid YAML
double-quoted scalar that decodes back to exactly v. Delegate to the stdlib
encoder (already imported) — no new runtime dependency (avoids requiring PyYAML
at runtime, which the prereqs don't include). Newlines stay rejected as an
input-sanity guard.

Adds tests/test_render.py::TestYamlScalarQuoting: round-trips adversarial tokens
(trailing/embedded backslashes, both quote styles, \t/\0/\x/\u sequences, a real
TAB, non-ASCII) back to the exact input, plus newline rejection and a full
extraArgs-fragment parse.
_ensure_hf_token_secret rendered the Secret with `kubectl create secret
--from-literal=token=<token>`, placing the HuggingFace token on argv — readable
via /proc/<pid>/cmdline by any co-tenant on a shared operator/CI host, and prone
to landing in shell history / CI logs.

Build the Secret manifest in-process (token base64-encoded in memory) and pipe
it to `kubectl apply -f -`, so the token reaches kubectl only via stdin, never on
a command line. Idempotent (apply creates or updates); the manual fallbacks now
model the safe pattern (--from-file=token=/dev/stdin). base64/json are stdlib.

Adds tests/test_gitops.py asserting the token never appears on argv and is
applied only as base64 in the Secret's data.
_status_check() was changed to unconditionally `return 0`, so CI/automation —
which the README recommends running as the post-deploy smoke test — could no
longer detect a broken platform: LiteLLM not responding, a registered model
returning non-200, or Langfuse unhealthy all only printed warn lines.

Track failures and return non-zero when a real check fails, while keeping exit 0
for the legitimately-empty case (no models registered yet — expected now that
nothing ships by default). Chose a meaningful default exit over a --strict flag:
a health check whose exit code is always 0 isn't checkable.

Also drop the stale "configure a Bedrock model in litellm.yaml" hint (Bedrock is
now self-service via `new-model --source bedrock`) and include `bedrockmodels`
in the `status` serving-resource listing.
… closed

#3 turned cluster_endpoint_public_access_cidrs into an ACTIVE assignment with a
placeholder ["<REPLACE>/32"] in example.tfvars and example-esc.tfvars. That is
not a valid CIDR, so copying the template and running before editing errors on
the malformed CIDR — and it even slips past the fail-closed precondition (the
value is non-empty and != 0.0.0.0/0), so the failure surfaces later/downstream
as a confusing parse error instead of the intended allowlist guidance.

Comment the line (with a valid example) so an unedited copy uses the variable's
default [] and the plan-time precondition fails closed with its clear
"...is not scoped. Set cluster_endpoint_public_access_cidrs..." message.
Conscious opt-in; ships no 0.0.0.0/0. terraform fmt -check clean.
…dule

#3 moved the Karpenter node IAM role out of the module (create_node_iam_role =
false) so its trust principal is ec2.amazonaws.com (the module builds
ec2.<dns_suffix> = ec2.amazonaws.eu, which ESC IAM rejects). The hand-rolled role
must then carry every policy the module used to attach, on BOTH the commercial
and ESC paths.

Verified parity (module v21.1.5): the module's node-role base set is
AmazonEKSWorkerNodePolicy + AmazonEKS_CNI_Policy(ipv4) + AmazonEC2ContainerRegistryReadOnly
(no node_iam_role_additional_policies passed); the hand-rolled role attaches those
three PLUS AmazonSSMManagedInstanceCore (+ optional ECR pull-through) — a strict
superset. No instance-profile regression: create_instance_profile defaults false
and isn't enabled, so Karpenter 1.x creates the profile at runtime from the
EC2NodeClass spec.role, and the module scopes iam:PassRole + the EKS access entry
to node_iam_role_arn (this role).

Adds a parity-guard comment enumerating the module base set + the instance-profile
rationale, with a note to re-verify on any karpenter module bump so a future change
can't silently drop a node policy. Comment only; no behavior change.
The workloads AppProject namespaceResourceWhitelist allowed only the four kro.run
kinds, so ArgoCD refused to sync a BedrockModel CR committed by
'platformctl new-model --source bedrock' ('resource bedrock.ai-platform:BedrockModel
is not permitted in project ai-platform-workloads') — self-service Bedrock
enrollment never reached the cluster. Whitelist group bedrock.ai-platform / kind
BedrockModel (still namespaced-only; no cluster-scoped grant). Found via e2e test.
The ai-platform-workloads AppProject allowed the in-cluster destination only by
server URL (https://kubernetes.default.svc). Self-managed ArgoCD canonicalizes
the apps' 'name: local-cluster' destination to that URL, so it matched. But
EKS-managed ArgoCD (managed capabilities) registers local-cluster with a
different server URL, so every workload app fails with InvalidSpecError
('destination server local-cluster ... not match allowed destinations') — all
self-service models/teams (incl. BedrockModel) break on managed capabilities.
Also allow the destination by name (same namespaces/boundary). Found via e2e test.
@radudobrinescu
radudobrinescu marked this pull request as ready for review September 18, 2026 07:27
@radudobrinescu
radudobrinescu merged commit b71be0b into main Sep 18, 2026
9 checks passed
@radudobrinescu
radudobrinescu deleted the feat/bedrock-self-service branch September 18, 2026 07:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant