published on 16 December 2015

With the new Puppet module for Kubernetes, you can now manage Pods, Replication Controllers, Services and more, all with the easy-to-use Puppet language. With Puppet’s defined types, you can build your own domain-specific interfaces to your Kubernetes configuration. In this third post in today's series, I'm going to show you how to build your own higher-level abstractions for Kubernetes, using Puppet.

Higher-Level Abstractions

As well as providing low-level types, the Puppet language allows you to create your own abstractions using defined types. The example in the previous post demonstrated the low-level Kubernetes types, which follow the same format as the standard YAML files. But what would this look like if we built our own higher-level interface?

Rather than expose all the possible configuration options available, we’re looking for some simplifying assumptions. For instance:

  • The guestbook example defines the same number of services and controllers.
  • The controller and service always point at the same port, and have the same labels.
  • All of our resources take an app, a role and a tier.
  • All of our containers require the same resource levels.

From these, we can create a higher-level interface that makes clearer the important information we care about.

controller_service_pair { 'redis-master':
  app  => 'redis',
  role => 'master',
  tier => 'backend',
  port => 6379,
}
controller_service_pair { 'redis-slave':
  app      => 'redis',
  role     => 'slave',
  tier     => 'backend',
  port     => 6379,
  image    => 'gcr.io/google_samples/gb-redisslave:v1',
  replicas => 2,
}
controller_service_pair { 'frontend':
  app          => 'guestbook',
  role         => 'php-redis',
  tier         => 'frontend',
  port         => 80,
  image        => 'gcr.io/google_samples/gb-frontend:v3',
  replicas     => 3,
  service_type => 'LoadBalancer',
}

This interface is much clearer than the verbose Puppet example, but it’s also much clearer than the YAML files you may be using with kubectl.

The Defined Type

If the above represents our user interface, the defined type controller_service_pair represents our implementation. We are able to set some sane defaults, too — in this case defaulting to a single replica, or defaulting the service type to ClusterIP. To make using the new type easier and more robust, we add simple data types to the properties. For example, if you don’t pass an integer number of replicas, we can provide a useful error message, and without making any API requests for Kubernetes.

define controller_service_pair(
  String $app,
  String $role,
  String $tier,
  Integer $port,
  String $image = $app,
  Integer $replicas = 1,
  String $service_type = 'ClusterIP',
) {

  kubernetes_replication_controller { $title:
    ensure   => 'present',
    metadata => {
      'labels' => {'app' => $app, 'role' => $role, 'tier' => $tier},
      'namespace' => 'default',
    },
    spec     => {
      'replicas' => $replicas,
      'template' => {
        'metadata' => {
          'labels' => {'app' => $app, 'role' => $role, 'tier' => $tier},
        },
        'spec' => {
          'containers' => [
            {
              'env' => [{'name' => 'GET_HOSTS_FROM', 'value' => 'dns'}],
              'image' => $image,
              'name' => $role,
              'ports' => [
                {'containerPort' => $port, 'protocol' => 'TCP'}
              ],
              'resources' => {
                'requests' => {
                  'cpu' => '100m',
                  'memory' => '100Mi',
                }
              }
            }
          ]
        }
      }
    }
  }
  kubernetes_service { $title:
    ensure   => 'present',
    metadata => {
      'labels' => {'app' => $app, 'role' => $role, 'tier' => $tier},
      'namespace' => 'default',
    },
    spec      => {
      'type'  => $service_type,
      'ports' => [
        {'port' => $port, 'protocol' => 'TCP', 'targetPort' => $port}
      ],
      'selector' => {'app' => $app, 'role' => $role, 'tier' => $tier},
    }
  }
}

Remember this is just an example. Using the Puppet language, you can create your own abstractions. For example, the above is still quite close to the Kubernetes primitives — the type is called controller_service_pair, after all. If we just wanted to interact with a type for a guestbook, we could do just that, with something like:

guestbook { 'myguestbook':
    redis_slave_replicas => 2,
    frontend_replicas    => 3,
    redis_master_image   => 'redis',
    redis_slave_image    => 'gcr.io/google_samples/gb-redisslave:v1',
    frontend_image       => 'gcr.io/google_samples/gb-frontend:v3',     
 }

Benefits

This isn’t just about writing less code, it’s about creating a higher-level user interface and facilitating reuse. Reuse comes from well-designed defined types. In the case of Kubernetes, these building blocks can be used to reduce the amount of code you need to manage, and drastically cut down on the duplication of that config. You should need to define a value, say for a port or label, just once, rather than once per Kubernetes resource.

The high-level interface means that not everyone needs to be an expert in Kubernetes to use it. For early adopters and small teams, this might sound like a negative — surely everyone will want to learn everything about Kubernetes? But in larger teams or organizations, everyone being an expert in everything just isn’t possible. So providing user-friendly and domain-specific user interfaces becomes a critical path to rolling out change. Take the guestbook type example above, and imagine your own applications modelled that way.

A high-level interface also means you can quickly see what is important, and what is available for you to change. Reading through several YAML files or a set of Puppet code to find where to increase the number of frontend replicas, for instance, is time consuming at best. The guestbook example exposes that as one of five key pieces of information you want to manage.

Conclusion

The module is available now as a proof of concept. We’d love any feedback you might have, and any ideas for what you would like to see in the future. Feel free to leave comments on this post, file issues against the module or email Gareth with any questions.

Gareth Rushgrove is a senior software engineer at Puppet Labs.

Learn More

Share via:

Add new comment

The content of this field is kept private and will not be shown publicly.

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.