Concept Reference

The ksonnet framework is centered around a couple of key concepts and terms. This reference gives a brief explanation of each. To get a more tangible sense of how these concepts tie together, see the Tutorial.

The following diagram shows how the ksonnet framework works holistically:

ksonnet overview diagram

ksonnet’s package management schema can be summed up as follows:

registry > package > prototype

Ex: incubator repo > Redis package > redis-stateless prototype


Application

A ksonnet application represents a well-structured directory of Kubernetes manifests. This directory is autogenerated by ks init. These manifests typically tie together in some way—for example, they might collectively define a web service like the following:


ksonnet application diagram


Environment

An environment consists of four elements, some of which can be pulled from your current kubeconfig context:

What Example Description How is this tracked?
(1) Name dev A string used to identify a particular environment. Must be unique within a ksonnet app. A directory under environments/ (e.g. environments/dev/).

A name with slashes results in a hierarchical file structure. For example, us-west/staging creates environments/us-west/staging/.
(2) Server https://cluster-name-with-hash.us-west-2.elb.amazonaws.com The address and port of a Kubernetes API server. In other words, identifies a unique cluster. Inside app.yaml
(3) Namespace dev Specifically, a Kubernetes namespace. Inside app.yaml
(4) Kubernetes API version version:v1.7.1 The version of your cluster’s Kubernetes API server, defaults to v1.8.0. Used to generate appropriate files in lib/ksonnet-lib/ based on the specified version of the Kubernetes OpenAPI spec

ksonnet allows you to deploy any particular application to multiple environments. Below is a visualization of two environments that represent different namespaces on the same cluster:

ksonnet environment diagram

If you’re wondering why you might deploy a common set of manifests to different environments, here are some potential use cases:

  • Release Management (dev vs test vs prod)
  • Multi-AZ (us-west-2 vs us-east-1)
  • Multi-cloud (AWS vs GCP vs Azure)

Component

A ksonnet application can be broken down into a series of discrete components:


ksonnet component diagram

Components can be as simple as a single Kubernetes resource (e.g. a Deployment) or as complex as a complete logging stack (e.g. EFK). More concretely, a component corresponds to a Kubernetes manifest in components/. There are two ways to add this manifest:

  • Typically, you autogenerate it with the ks generate command. In this case, the manifest is expressed in a language called Jsonnet.

  • Alternatively, you can manually drop in a file into components/. In this case, you can use either JSON or Jsonnet, as long the file is saved with the *.jsonnet extension. (JSON is a subset of Jsonnet so no other changes are needed. YAML is not currently supported, but you can use an open-source CLI tool such as yaml2json to convert from YAML to JSON).

    This approach allows you to introduce ksonnet to existing codebases.

How does the autogeneration process work? When you use ks generate, the component is generated from a prototype. The distinction between a component and a prototype is a bit subtle. If you are familiar with object oriented programming, you can roughly think of a prototype as a “class”, and a component as its instantiation:


component class analogy

All of the component files in an app can be deployed to a specified environment using ks apply.


Prototype

Prototypes are examples that you can use as the basis for your components. By ks generate-ing components from prototypes, you can quickly move from a blank app to multiple complete manifests.

Although you can use these components right away, they are really intended as a starting point. If a prototype-generated component does not fully address your needs, you can directly modify its Jsonnet afterwards (e.g. adding parameters).

By itself, a prototype is a pre-written but incomplete manifest:


prototype diagram

Why is this useful? Prototypes allow you to avoid copying and pasting boilerplate code, and to focus on the parts of your configuration that are specific to your app. Specifically, during ks generate, you can specify certain command-line parameters to “fill-in-the-blanks” of a prototype and output a complete manifest into the components/ directory:

prototype parameter component diagram

Out of the box, ksonnet comes with some system prototypes (like io.ksonnet.pkg.deployed-service) that you can explore with the various ks prototype commands. See package and registry for information on downloading or sharing additional prototypes.


Parameter

Parameters allow you to customize your prototypes — both at the time of their creation, and after the fact. You can use the various ks param commands to view or modify current parameters. Params can be set globally or per-environment.

Under the hood, the ks param commands update a couple of local Jsonnet files, so that you always have a version-controllable representation of what you ks apply onto your Kubernetes cluster. These are structured as follows:

  • App params (components/params.libsonnet) — treated like defaults
    • (Optional) Global params
      • Manually specified by editing the Jsonnet
      • Used to make a variable accessible to multiple components (e.g. if the number of Redis and nginx replicas are related)
    • Component-specific params
      • e.g. 80 for deployment-example.port
      • Populated from ks generate
  • Per-environment params (environments/<env-name>/params.libsonnet) — override app params, similar to inheritance
    • Component-specific params only

For example, you can use params to ensure that you have 3 Redis replicas in your prod environment and 1 in dev, because prod needs to handle higher traffic.


Module

Modules provide a way for you to share components across environments. More concisely, a module refers to a subdirectory in components/ containing its own params.libsonnet.

For example, ks module create <module-name> creates components/<module-name>/params.libsonnet.

ksonnet will use the components defined in components/ by default. Modules provide a clean way to make your configurations more modular. If you want to use another set of components or use specific components, modules provides a way to do this without initializing another ksonnet app.

A module can:

  • be used across multiple environments similar to components.
  • have a nested structure to group components in a more selective way.
  • be used in conjunction with additional modules for a given environment.

Part

Prototypes often come in sets, where each prototype offers a slightly different “flavor” of a common base (e.g. redis-persistent and redis-stateless are two possible ways of deploying a Redis datastore).

Given that ksonnet is designed around modularity, a common pattern is to refactor most of the shared prototype code into a single parts library. This might look something like the following:

  1. {
  2. parts:: {
  3. deployment:: {
  4. persistent(...)::
  5. ...,
  6. nonPersistent(...)::
  7. ...,
  8. base(...)::
  9. ...,
  10. },
  11. networkPolicy:: {
  12. allowExternal(...)::
  13. ...,
  14. denyExternal(...)::
  15. ...,
  16. },
  17. ...
  18. }
  19. }

Once a library like this is established, different parts like redis.parts.deployment.persistent can be mixed and matched to produce new prototypes. Intuitively, this looks a bit like the following:

parts diagram

NOTE: Parts do not have to be 1:1 with Kubernetes API resources like Deployments. This is just a relatively intuitive way to organize them.

Prototypes that are distributed via ksonnet packages typically adhere to the pattern described above.


Package

A package contains:

  • A set of related prototypes (e.g. redis-persistent, redis-stateless)
  • Associated helper libraries that define the prototype parts (e.g. redis.libsonnet)

Packages allow you to easily distribute and reuse code in any ksonnet application, using the various ks pkg commands. The CLI writes package code into the vendor/ directory.

To be recognized and imported by ksonnet, packages need to follow a specific schema. See the annotated file tree below, as an example:

  1. .
  2. ├── README.md // Human-readable description of the package
  3. ├── parts.yaml // Provides metadata about the package
  4. ├── prototypes // Can be imported and used to generate components
  5. ├── redis-all-features.jsonnet
  6. ├── redis-persistent.jsonnet
  7. └── redis-stateless.jsonnet
  8. └── redis.libsonnet // Helper library, includes prototype parts

parts.yaml metadata is used to populate the output of the ks prototype describe command. The official packages in ksonnet/parts/incubator also use parts.yaml to autogenerate README.md documentation.

You can take a look at the nginx and Redis packages as additional examples.


Registry

Registries are essentially repositories for packages. (We mean registry here in the same sense as registries for container images). Registries are identified by a registry.yaml in their root that declares a list of packages.

You can use default or custom registries:

  • By default, ksonnet allows you do download packages from the ksonnet/parts/incubator registry.

  • You can set up a registry with three different protocols:

    • Github - a Github URI
    • Filesystem - a valid path to a local registry
    • Helm - a URI to a Helm repository

    A registry contains a registry.yaml file with directories containing packages similar to the following structure:

    1. .
    2. ├── nginx // nginx package
    3. ├── README.md
    4. ├── nginx.libsonnet
    5. ├── parts.yaml
    6. └── prototypes
    7. ├── nginx-server-block.jsonnet
    8. ...
    9. ├── redis // redis package
    10. ├── README.md
    11. ├── parts.yaml
    12. ├── prototypes
    13. ├── redis-all-features.jsonnet
    14. ...
    15. └── redis.libsonnet
    16. └── registry.yaml // Lists all packages in the registry

Use the various ks registry commands to list available registries, add new ones, and see what packages they contain.


Manifest

When you’re trying to run code on a Kubernetes cluster, there’s a relatively clean separation between:

  • “What you run” (your code, containerized in an image)
  • “How you run it” (YAML or JSON manifests)

The “how” — your YAML and JSON manifests — declare the API resources that your code uses to actually run ON Kubernetes. Example resources include Pods and Deployments.

One of ksonnet’s key features is that it allows you to write more concise manifests, using a language called Jsonnet.


Jsonnet

ksonnet uses Jsonnet to express all of your components/ manifests (and associated configuration files, like components/params.libsonnet).

Jsonnet is a data templating language — for simplicity, think of it as a fancier superset of JSON, one that supports features like variables and object concatenation. If you’ve ever had to copy and paste large chunks of YAML or JSON manifests, Jsonnet avoids this problem!

You can see how much more concise it is by comparing the same manifest, written in two different languages:

jsonnet vs yaml

Note that we’ve omitted Jsonnet’s import lines in this example.

Jsonnet is also JSON-compatible, meaning that you can drop parts of your legacy manifests into your ksonnet manifests, without having to rewrite them all at once. (It is more common to have Kubernetes manifests in YAML than JSON, but there are several open-source CLI tools such as yaml2json that can do this conversion for you).