Repository navigation
feat(operator): add a Helm chart for unbounded-operator - #1056
Alex Huang (wenhug) wants to merge 4 commits into
Conversation
5acee90 to
c70727b
Compare
Philip Lombardi (plombardi89)
left a comment
There was a problem hiding this comment.
Thanks for this. Making the chart the single source works well: the static and Helm renders match, the config hash is computed the same way as install's, and --set-file keeps commas in the endpoint intact.
Requesting changes for three problems where kubectl unbounded install, helm uninstall or the AKS extension can overwrite or delete a working install. Each inline comment has steps to reproduce. I reproduced the first two on kind with Helm v3.21.3 and a kubectl-unbounded built from this branch (v0.22.0-5-gc70727b3c). The third is a question about Extension Manager.
Blocking
kubectl unbounded installsilently takes over a Helm-managed operator (inline ondocs/content/reference/cli.md).namespace.create=trueon a Helm install leads tohelm uninstalldeleting the namespace and every component in it (inline onvalues.schema.json).- Question: does deleting the AKS extension delete the namespace? (inline on
NOTES.txt).
Suggestion
- Fail instead of skip when helm is missing (inline on
chart_test.go).
| --set image.tag=<release> | ||
| ``` | ||
|
|
||
| Use one method per cluster. The chart refuses to install over an operator that |
There was a problem hiding this comment.
Blocking: kubectl unbounded install silently takes over a Helm-managed operator
The chart refuses to install over a CLI install, but not the other way around. install server-side applies every object with ForceOwnership and never checks for Helm ownership (cmd/kubectl-unbounded/app/install.go:161, :774). So this line overstates what's enforced.
I reproduced both cases below on kind with Helm v3.21.3 and kubectl-unbounded built from this branch (v0.22.0-5-gc70727b3c).
Same namespace: the operator version flips back and forth
helm install unbounded-operator ./deploy/unbounded-operator/chart -n unbounded-system --create-namespace --set image.tag=v0.21.0kubectl unbounded install --wait=falsesucceeds with no warning.- The Deployment image is now
unbounded-operator:v0.22.0-5-gc70727b3c, andkubectl-unboundedowns the fields. The operator deploys components at its own version (install.go:304-306), so on a real cluster the components move to that version too. helm upgradewith the same values sets the image back tov0.21.0.kubectl unbounded installagain sets it back tov0.22.0-5-gc70727b3c. This repeats every time either tool runs, and an AKS extension update counts as ahelm upgrade.
Different namespaces: the two operators take permissions from each other
helm install unbounded-operator ./deploy/unbounded-operator/chart -n flex-system --create-namespace --set image.tag=v0.21.0kubectl unbounded install --wait=false(defaults tounbounded-system) succeeds.- There are now two operator Deployments, and the only subject of the cluster-wide
unbounded-operatorClusterRoleBinding isunbounded-system/unbounded-operator. kubectl auth can-i list nodes --as=system:serviceaccount:flex-system:unbounded-operatorreturnsno. The Helm-managed operator has lost its permissions.helm upgradepoints the binding back atflex-system. Now the CLI-installed operator has no permissions.
Suggested fix: before applying, have install read the unbounded-operator Deployment in the target namespace and the unbounded-operator ClusterRoleBinding. If either has the meta.helm.sh/release-name annotation, fail with a message saying the operator is managed by Helm or the AKS extension. If you'd rather do this in a follow-up, please open an issue and reword this line so it doesn't read as if both directions are guarded.
There was a problem hiding this comment.
Good catch, the doc only held one way. install now checks the operator Deployment in the target namespace and the unbounded-operator ClusterRoleBinding before applying anything, and bails out if either has meta.helm.sh/release-name.
Ran both your cases on AKS with a CLI built from the branch:
- same namespace:
installerrors out, image stays at v0.21.0 - flex-system + default namespace: errors on the ClusterRoleBinding, binding still points at flex-system and that SA can still list nodes
Unit tests cover both, and a plain reinstall over a CLI install still works. Reworded the doc so it says what each side actually refuses.
| "type": "object", | ||
| "additionalProperties": false, | ||
| "required": ["create"], | ||
| "properties": {"create": {"type": "boolean"}} |
There was a problem hiding this comment.
Blocking: namespace.create=true on a Helm install leads to helm uninstall deleting the namespace
The schema accepts namespace.create: true for Helm installs, and values-static.yaml ships in the chart. Helm rejects that install, but it leaves behind a failed release whose manifest includes the Namespace. From there, either a retry or an uninstall deletes the namespace and everything in it. The operator deploys every component into this namespace, including unbounded-net (internal/operator/component/namespace.go:11-12).
I reproduced both paths below on kind with Helm v3.21.3.
Path 1: retry
helm install op ./deploy/unbounded-operator/chart -n ns-a --create-namespace --set namespace.create=true --set image.tag=v0.21.0- It fails with
namespaces "ns-a" already exists. However, the ServiceAccount, RBAC, ConfigMap and Deployment are all created, and the release is recorded asfailed. - To simulate a component deployed by the operator:
kubectl -n ns-a create configmap component-sentinel --from-literal=x=1 - Retry with
helm upgrade --installand the same flags. This succeeds, and the namespace now hasapp.kubernetes.io/managed-by: Helmand the release annotations. helm uninstall op -n ns-aputs the namespace intoTerminating, andcomponent-sentinelis deleted with it.
Path 2: give up and uninstall
- Run steps 1-3 from path 1.
helm uninstall op -n ns-aalso deletes the namespace, even though--create-namespacenamespaces are normally left behind byhelm uninstall.
Suggested fix: follow the gantry chart. Pin "create": {"const": false} in the schema (deploy/gantry/chart/values.schema.json:55-65) and render the static profile with --skip-schema-validation (Makefile:928). Alternatively, add helm.sh/resource-policy: keep to the Namespace template. A .helmignore that excludes values-static.yaml from the packaged chart would also help.
There was a problem hiding this comment.
Went with the gantry approach: create is const: false in the schema, the static render uses --skip-schema-validation, and .helmignore keeps values-static.yaml out of the packaged chart. --set namespace.create=true now fails validation before Helm records a release or creates anything, so there's nothing left behind to retry or uninstall. Also added it to TestChartRejectsInvalidValues.
| Then create Sites, for example: | ||
| kubectl unbounded site init --name <site> ... | ||
|
|
||
| Uninstall removes the operator, its RBAC and ConfigMap. CRDs, Sites and the |
There was a problem hiding this comment.
Blocking (question): does deleting the AKS extension delete the namespace?
This note and cli.md say that uninstalling leaves the components in place. The operator deploys all of them, including the unbounded-net CNI, into the release namespace (internal/operator/component/namespace.go:11-12).
- Create the AKS extension. Extension Manager installs the chart into its release namespace.
- Run
kubectl unbounded site init. The operator deploys unbounded-net, machina and the other components into that same namespace. - Delete the extension.
- Extension Manager runs
helm uninstall, which removes the operator, its RBAC and its ConfigMap. This part is expected. - Unknown: if Extension Manager then deletes the release namespace, Kubernetes deletes everything in it, including the CNI. I'd expect that to break networking for attached nodes.
In your create/update/delete test through Extension Manager, were the namespace and the components still there after the delete? If the namespace is deleted, please either make the extension keep it, or update this note and cli.md to say that removing the extension removes every component.
There was a problem hiding this comment.
No. In the Extension Manager test (create, config update, delete) the namespace was still Active after the delete. Only the operator Deployment, SA, ConfigMap and ClusterRole/Binding went away, and the 12 CRDs stayed. Same result with an earlier minimal chart in a different namespace.
Caveat: that run didn't have Sites, so nothing else was living in the namespace. With the namespace kept, I'd expect unbounded-net etc. to survive too, but I haven't re-run it with Sites. Happy to do that before merge if you want it confirmed. Updated NOTES and cli.md to say the namespace and the components in it are left in place.
|
|
||
| p, err := exec.LookPath("helm") | ||
| if err != nil { | ||
| t.Skip("helm not found; run `make install-helm` or set HELM") |
There was a problem hiding this comment.
Suggestion: fail instead of skip when helm is missing
Before this PR, the operator render tests had no outside dependencies and always ran. With t.Skip here, a missing helm turns all of them into skips:
- Use a fresh checkout with no helm on
PATHand nobin/helm(i.e.makehas never been run). - Change the chart in a way that breaks a template, for example the RBAC rules.
- Run
go test ./deploy/unbounded-operator/.... - Go prints
okfor the package. All 9 helm-backed tests (RBAC, lease scope, config hash, registry, ...) are skipped, and the skips only show with-v. - The developer sees a pass and pushes, and only CI catches the breakage.
Suggest t.Fatal("helm not found; run make install-helm or set HELM"), which matches deploy/gantry/render_test.go:760-762 and internal/gantry/origin/configmap_yaml_test.go:54-56.
There was a problem hiding this comment.
Done, t.Fatal now, same as gantry.
c70727b to
d7efa13
Compare
Review fixes for Azure#1056: - kubectl unbounded install refuses to apply over an operator Deployment or ClusterRoleBinding owned by a Helm release, so it can't flip the image or steal RBAC from the chart or the AKS extension. - The schema pins namespace.create to false. Only the static render sets it (with --skip-schema-validation, as gantry does), and values-static.yaml is left out of the packaged chart, so a Helm release can't own the namespace that holds every operator-managed component. - Chart tests fail instead of skipping when helm is missing. - Docs and NOTES say what each method refuses and what uninstall leaves. Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks for digging into this, and for the repro steps. All four are fixed in d7efa13 (kept as a separate commit so it's easy to diff). Details inline. Unrelated, but I hit it while testing: on a clean AKS cluster, |
|
Follow-up after running the whole thing through Extension Manager on a fresh AKS cluster:
|
…fests Define the unbounded-operator manifests as a Helm chart in deploy/unbounded-operator/chart and render the static kubectl-apply manifests from it, so platforms that install add-ons with Helm (such as AKS cluster extensions) and kubectl unbounded install share one source. - The chart replaces the .yaml.tmpl files. Templates keep their file names, so make unbounded-operator-manifests renders deploy/unbounded-operator/ rendered with helm template --output-dir, like gantry-manifests. The output is unchanged. - values-static.yaml turns on the Namespace for the static render; Helm installs use the release namespace. - The chart refuses to install over an operator it doesn't own, such as one created by kubectl unbounded install. - Existing render tests now render the chart; a new test pins the static layout and checks that a Helm install renders the same objects. - Add unbounded-operator-chart-lint and -package targets and document the Helm path. Co-Authored-By: Claude <noreply@anthropic.com>
Review fixes for Azure#1056: - kubectl unbounded install refuses to apply over an operator Deployment or ClusterRoleBinding owned by a Helm release, so it can't flip the image or steal RBAC from the chart or the AKS extension. - The schema pins namespace.create to false. Only the static render sets it (with --skip-schema-validation, as gantry does), and values-static.yaml is left out of the packaged chart, so a Helm release can't own the namespace that holds every operator-managed component. - Chart tests fail instead of skipping when helm is missing. - Docs and NOTES say what each method refuses and what uninstall leaves. Co-Authored-By: Claude <noreply@anthropic.com>
A real AKS extension install failed schema validation: besides Azure.Cluster.*, Extension Manager passes IdentityClientId, IdentityPrincipalId, IdentityTenantId, IdentityType, IsWorkloadIdentityEnabled, DataBoundary and scope at the root. Let the root accept extra keys; the chart's own objects (image, namespace) stay strict, and namespace.create is still pinned to false. Co-Authored-By: Claude <noreply@anthropic.com>
The operator manifests now come from chart/, not .yaml.tmpl files. Co-Authored-By: Claude <noreply@anthropic.com>
6fac94c to
71eb877
Compare
Summary
Makes a Helm chart the single source for the
unbounded-operatormanifests,per the discussion with Jordan Olshevski (@jveski) and Philip Lombardi (@plombardi89). The static
kubectl applymanifests (
deploy/unbounded-operator/rendered/, embedded inkubectl unbounded install, and the releaseunbounded-operator-<tag>.yaml)are now rendered from the chart at build time, so there is one definition to
maintain.
The immediate consumer of the chart is AKS, which wants to ship the operator as
a managed cluster extension for flex nodes (Extension Manager only installs
Helm charts).
Changes
deploy/unbounded-operator/chart: ServiceAccount, ConfigMap, ClusterRole/Binding,registry-auth-migration Role/Binding, Deployment, and an optional Namespace
(
namespace.create, on only for the static render viavalues-static.yaml).Values:
imageRegistry,image.name/tag/digest/reference,apiServerEndpoint,reapLegacyResources. Config hash is the same canonical JSON asinstall.deploy/unbounded-operator/*.yaml.tmplremoved.00-namespace.yaml…04-deployment.yaml), somake unbounded-operator-manifestsis justhelm template --output-dir+ copy, the same pattern asgantry-manifests.values.schema.jsonkeepsimageandnamespacestrict but lets the roottake extra keys, because AKS cluster extensions inject platform values
(
Azure.Cluster.*,IdentityClientId,DataBoundary,scope, ...).apiServerEndpointis passed with--set-filein the Makefile so commas andother
--setsyntax survive verbatim, as with the old renderer.unbounded-operatorexists and isn't owned by the release (e.g. fromkubectl unbounded install), andkubectl unbounded installfails if theoperator Deployment or ClusterRoleBinding is owned by a Helm release.
namespace.createis pinned tofalsein the schema; only the static rendersets it (with
--skip-schema-validation, like gantry), andvalues-static.yamlis excluded from the packaged chart, so a Helm releasenever owns the namespace the operator deploys components into.
TestStaticAndHelmRenderspins the five files/eight objects and checks a Helminstall renders the same objects minus the Namespace;
TestChartAcceptsExtensionManagerValuescovers the values Extension Manager injects; namespace drift checkfollows the gantry Makefile-default approach;
make testpasses the pinnedbin/helm, and the chart tests fail (not skip) without helm.make unbounded-operator-chart-lint/-package; CLI reference documents theHelm path.
Validation
rendered/is object-for-object identical to the previous.yaml.tmploutput for default and custom (namespace/registry/endpoint/reap) values. The 6
objects that existed in v0.21.0 match the published v0.21.0 release YAML; the
2 extra are the Role/RoleBinding added in feat(gantry): managed authentication using acr-credential-provider #1037.
--network-plugin none, real flex node attached:helm install --set image.tag=v0.21.0: operator and all components atv0.21.0, both nodes
Ready, 80/80 cross-node requests OK during the swap;helm upgradeofapiServerEndpointrolls the operator.kubectl unbounded installbuilt from this branch applied all 8objects, operator
Ready, 13 CRDs; the Helm guard then refused to install overit.
kubectl applyof the branch-built release YAML also works.extension type): create, config update (operator rolls) and delete all
succeed;
kubectl unbounded installrefuses to touch the extension'soperator; after delete the namespace and a workload placed in it are still
there, only the operator, its RBAC and ConfigMap are removed.
Notes for reviewers
$(HELM)isdownloaded on demand, as for
gantry-manifests).helm templateadds no labels today, which is why static and Helm outputmatch. Adding Helm-specific labels to the chart would change the static
manifests too.
release.yaml(likegantry-chart)and cleanup of CRDs/Sites on uninstall.
🤖 Generated with Claude Code