Skip to content

AcmeService — Platform Abstraction with KRO

The Vision

A developer should deploy a service by declaring what it needs, not how to wire it. The platform handles security, scaling, and networking automatically.

apiVersion: platform.acme.com/v1alpha1
kind: AcmeService
metadata:
  name: my-service
spec:
  template: ...      # What to run (containers, env, resources)
  service: ...       # How to expose it (port, type)
  scaling: ...       # How it scales (knative or static)
  security: ...      # Who can talk to it

Why KRO?

KRO (Kube Resource Orchestrator) lets us define a ResourceGraphDefinition — a template that takes a simple custom resource as input and generates multiple Kubernetes resources as output.

Without KRO, deploying a secured service requires the developer to create and maintain:

Resource Count Total
ServiceAccount 1 1
Deployment or KNative Service 1 2
Service (if static) 1 3
NetworkPolicy (deny) 1 4
NetworkPolicy (per allowed source) N 4+N
AuthorizationPolicy (per allowed source) N 4+2N
NetworkPolicy (eventing) 0-1 5+2N
AuthorizationPolicy (eventing) 0-1 6+2N

For a typical service with 2 callers and eventing: 10 YAML resources. With KRO: 1 YAML resource.

Spec Reference

spec.template

Follows the KNative Serving pattern (spec.template.spec.containers[]). This makes it familiar and extensible — you can add volumes, env vars, resource limits, probes, init containers, etc.

spec:
  template:
    metadata:
      labels:
        app: my-service         # Required: used for pod selection
      annotations: {}
    spec:
      containers:
        - name: app
          image: myregistry.azurecr.io/my-service:v1
          ports:
            - containerPort: 8080
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi

spec.service

spec:
  service:
    port: 8080                  # Service port (maps to container port)
    type: ClusterIP             # ClusterIP (default) or LoadBalancer

Note

When scaling.type is knative, the Service is managed by KNative Serving — the spec.service is only used for NetworkPolicy port rules.

spec.scaling

spec:
  scaling:
    type: knative               # "knative" or "static"
    minScale: 0                 # Minimum replicas (knative only)
    maxScale: 10                # Maximum replicas (knative only)
    concurrency: 100            # Target concurrent requests per pod (knative only)
Mode What's Generated Use Case
static Deployment + Service Always-on services: APIs, UIs, databases
knative KNative Service (ksvc) Event-driven, bursty workloads, scale-to-zero

When to Use KNative

Use scaling.type: knative when:

  • The service processes events and is idle most of the time
  • You want scale-to-zero to save costs
  • You need revision management (canary, blue-green)
  • Concurrency-based autoscaling fits better than CPU-based HPA

Use scaling.type: static when:

  • The service must be always available (frontends, APIs with strict latency)
  • It manages persistent connections (WebSockets, gRPC streams)
  • It has local state that shouldn't be disrupted by scale events

spec.security

spec:
  security:
    ingress: false              # Exposed via external LoadBalancer?
    eventingSink: true          # Receives CloudEvents from KNative dispatcher?
    eventingSource: true        # Publishes CloudEvents to the broker?
    allowFrom:                  # Which other AcmeServices can call this one?
      - frontend
      - admin-api

Each field generates specific security resources:

Field NetworkPolicy AuthorizationPolicy
ingress: true Allow all ingress on service port Allow all (external traffic has no mTLS identity)
eventingSink: true Allow from knative-eventing, knative-serving, kourier-system Allow from those namespaces
allowFrom: [svc] Allow from pods labeled app: <svc> Allow principal cluster.local/ns/<ns>/sa/<svc>

Service Identity

Each AcmeService gets its own ServiceAccount (name = service name). This ServiceAccount becomes the pod's identity in the Istio mesh, used by AuthorizationPolicies to verify who is calling.

Generated Resources

For this input:

apiVersion: platform.acme.com/v1alpha1
kind: AcmeService
metadata:
  name: demo-backend
spec:
  scaling:
    type: static
  security:
    eventingSink: true
    allowFrom:
      - demo-frontend

KRO generates:

ServiceAccount/demo-backend
Deployment/demo-backend
Service/demo-backend
NetworkPolicy/demo-backend-deny-ingress              # always
NetworkPolicy/demo-backend-allow-eventing             # eventingSink=true
NetworkPolicy/demo-backend-allow-from-demo-frontend   # allowFrom collection
AuthorizationPolicy/demo-backend-allow-eventing       # eventingSink=true
AuthorizationPolicy/demo-backend-allow-from-demo-frontend  # allowFrom collection

8 resources from 1 YAML. Add another caller to allowFrom → 2 more resources generated automatically.

Collections (allowFrom)

KRO's forEach directive creates one resource per element in an array. For allowFrom, we generate both a NetworkPolicy and an AuthorizationPolicy per allowed source service:

# In the ResourceGraphDefinition:
- id: netpol-allow-from
  forEach:
    - svc: ${schema.spec.security.allowFrom}
  template:
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: ${schema.metadata.name}-allow-from-${svc}
    # ...

When the user updates allowFrom (adds or removes a service), KRO automatically creates or deletes the corresponding policies.

Examples

See k8s/kro/examples/ for complete AcmeService definitions:

  • demo-backend — Static deployment, event sink, called by frontend
  • demo-frontend — Static deployment, external ingress
  • event-display — KNative service, event sink, scale-to-zero