Deploying manifests with StageSet see history edit this page

Talks about: , , and

JaaS pairs with stageset-controller to deploy Kubernetes manifests as code: you author the manifests in Jsonnet, the JaaS operator renders and publishes them as a Flux ExternalArtifact, and stageset-controller rolls that artifact out across ordered, gated stages.

This tutorial covers the JaaS side — authoring the manifests with top-level arguments and external variables, and publishing the rendered JSON. For the rollout side (the StageSet resource, its stages, gates, and actions), see the links at the end.

Prerequisites

This tutorial uses the namespace default and the tenant ServiceAccount manifests-tenant.

Step 1 — Grant the tenant ServiceAccount its verbs

The snippet publishes an ExternalArtifact, so the tenant needs the externalartifacts write verbs:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: manifests-tenant
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: manifests-tenant
rules:
  - apiGroups: [source.toolkit.fluxcd.io]
    resources: [externalartifacts]
    verbs: [get, create, update, patch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: default
  name: manifests-tenant
subjects:
  - kind: ServiceAccount
    name: manifests-tenant
    namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: manifests-tenant
EOF

Verify the ServiceAccount and binding:

kubectl --namespace default get serviceaccount manifests-tenant
kubectl --namespace default get rolebinding manifests-tenant

Step 2 — Author and apply the manifest snippet

The snippet renders a List of a Deployment and a Service from top-level arguments (spec.tlas) and an external variable (spec.externalVariables). A single-value TLA arrives as a string; the snippet parses the replica count with std.parseInt. spec.output stays at its default rendered so the artifact carries the evaluated manifest JSON:

cat <<EOF | kubectl apply -f -
apiVersion: jaas.metio.wtf/v1
kind: JsonnetSnippet
metadata:
  name: web-app
  namespace: default
spec:
  serviceAccountName: manifests-tenant
  output: rendered
  files:
    main.jsonnet: |
      function(name='web', replicas=2)
        local image = std.extVar('image');
        local labels = { 'app.kubernetes.io/name': name };
        {
          apiVersion: 'v1',
          kind: 'List',
          items: [
            {
              apiVersion: 'apps/v1',
              kind: 'Deployment',
              metadata: { name: name, labels: labels },
              spec: {
                replicas: replicas,
                selector: { matchLabels: labels },
                template: {
                  metadata: { labels: labels },
                  spec: {
                    containers: [{
                      name: name,
                      image: image,
                      ports: [{ containerPort: 8080 }],
                    }],
                  },
                },
              },
            },
            {
              apiVersion: 'v1',
              kind: 'Service',
              metadata: { name: name, labels: labels },
              spec: {
                selector: labels,
                ports: [{ port: 80, targetPort: 8080 }],
              },
            },
          ],
        }
  tlas:
    - name: name
      value: web
    - name: replicas
      value: "3"
      code: true
  externalVariables:
    - name: image
      value: "ghcr.io/example/web:1.4.0"
EOF

spec.tlas binds the arguments of the snippet’s outermost function, and spec.externalVariables seeds its std.extVar lookups. Both are lists of named entries, and both bind the value as a string by default — which is why replicas sets code: true: the value is parsed as Jsonnet source, so the snippet receives the number 3 and spec.replicas is a valid integer. Without it the field would render as the string "3" and the apiserver would reject the Deployment.

Step 3 — Confirm the manifests rendered

kubectl --namespace default get jsonnetsnippet web-app
# NAME      READY   URL                                                                                     AGE
# web-app   True    http://jaas-storage.jaas-system.svc.cluster.local:8082/default/web-app/<sha256>.tar.gz  5s

If READY is False, describe the snippet — the Ready condition’s Reason and Message name the cause:

kubectl --namespace default describe jsonnetsnippet web-app

Step 4 — Inspect the published manifests

Fetch the artifact from a one-shot pod to see the rendered manifests:

URL=$(kubectl --namespace default get jsonnetsnippet web-app -o jsonpath='{.status.artifactURL}')
kubectl run --rm -i --restart=Never --image=docker.io/curlimages/curl:8.10.1 fetch -- \
    sh -c "curl -fsSL '$URL' | tar -xzO rendered.json"
# {
#    "apiVersion": "v1",
#    "kind": "List",
#    "items": [ { "kind": "Deployment", ... }, { "kind": "Service", ... } ]
# }

rendered.json is the manifest set a Flux consumer applies to the cluster.

Handoff: roll the manifests out with StageSet

The published ExternalArtifact is now ready for a Flux consumer. A consumer references it in one of two ways:

stageset-controller consumes the published artifact the producer-aware way and rolls it out across ordered, gated stages. The StageSet resource, its stages, gates, and actions live on the stageset-controller documentation:

Follow that guide for the rollout side; it picks up exactly where this tutorial leaves off — at the published ExternalArtifact.

Where to go next