Free — no signup required

Constructs: The Building Blocks of CDK8s

3 min read

Everything Is a Construct

In CDK8s, every resource you define is a Construct—a reusable, composable class that represents one or more Kubernetes resources. Constructs can contain other Constructs, forming a tree. When you call app.synth(), CDK8s walks this tree and renders every Construct into its YAML representation.

This model is borrowed directly from AWS CDK, where the same pattern is used for cloud resources. If you have used AWS CDK, CDK8s will feel immediately familiar.

The Three Levels of Abstraction

CDK8s organizes Constructs into three levels, each offering a different trade-off between control and convenience.

Level 1 (L1): Raw Kubernetes API Objects

L1 Constructs are auto-generated, 1:1 mappings to the Kubernetes API specification. Every field in the Kubernetes API has a corresponding typed property in the L1 class.

  • Class naming convention: prefixed with Kube. For example, KubeDeployment, KubeService, KubeConfigMap.
  • You must specify every detail manually—selectors, pod templates, container specs—just as you would in raw YAML, but with type safety and autocomplete.
  • Use L1 when you need full control or when no higher-level abstraction exists for your resource type.
# L1 example: explicit, verbose, but fully type-safe
k8s.KubeDeployment(self, "my-deployment",
    spec=k8s.DeploymentSpec(
        replicas=3,
        selector=k8s.LabelSelector(match_labels={"app": "api"}),
        template=k8s.PodTemplateSpec(...)
    )
)

Level 2 (L2): Simplified Standard Library

L2 Constructs are provided by the cdk8s-plus library (a separate package maintained by the CDK8s team). They wrap L1 Constructs with sensible defaults and higher-level methods that reduce boilerplate.

  • You do not manually wire labels to selectors—the library handles that.
  • Common operations become single method calls: deployment.expose_via_service(port=80) creates both a Service and configures the selector automatically.
  • L2 Constructs enforce Kubernetes best practices by default (e.g., they set selector and matchLabels consistently).
# L2 example: concise, opinionated, handles wiring automatically
import cdk8s_plus_27 as kplus

deployment = kplus.Deployment(self, "my-deployment",
    containers=[kplus.ContainerProps(image="nginx:1.25")]
)
# Creates a Service and links it to the Deployment automatically
deployment.expose_via_service(port=80)

Level 3 (L3): Company-Specific Patterns

L3 Constructs are custom classes that your team builds on top of L1 or L2. They encode your organization's standards and policies into reusable, versioned components.

  • Example: A CompanyWebService Construct that always adds a liveness probe, sets resource limits, configures a standard logging sidecar, and creates a PodDisruptionBudget.
  • Developers consuming L3 Constructs only specify what varies (image name, port, replica count). Everything else is enforced by the Construct.
  • L3 Constructs are typically published as internal Python packages or npm modules and versioned like application code.
# L3 example: a custom construct your platform team publishes
class CompanyWebService(Construct):
    def __init__(self, scope, id, *, image: str, port: int, replicas: int = 2):
        super().__init__(scope, id)
        deployment = kplus.Deployment(self, "deployment",
            replicas=replicas,
            containers=[kplus.ContainerProps(
                image=image,
                liveness=kplus.Probe.from_http_get("/healthz", port=port),
                resources=kplus.ContainerResources(
                    memory=kplus.ResourceQuantity.of("256Mi")
                )
            )]
        )
        deployment.expose_via_service(port=port)

A developer using this L3 Construct writes three lines instead of fifty, and the liveness probe and memory limit are guaranteed to be present.

Key Point: The real leverage of CDK8s in a platform engineering context is L3 Constructs. A platform team publishes a versioned library of L3 Constructs. Application teams consume them. When the platform team updates the library (e.g., to add a new required security annotation), all consuming teams get the update by bumping a version number—no YAML search-and-replace across 50 repositories required.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free