Extending ModusKube

ModusKube is designed to be used as a framework for building custom infrastructure. The extension methods discussed below can be used to customize ModusKube to fit your needs in a clean and modular way.

Cluster state extensions

Cluster state extensions let you run arbitrary code during cluster bootstrapping and run on the master node directly. You can use this extension method to install custom Kubernetes resources, Helm charts, tools or anything else that needs to be run during cluster bootstrapping.

Once defined in your cluster configuration, these extensions will be stored as part of the cluster’s state definition during the creation process. If the cluster is replaced, it will automatically run all of its defined state extensions when re-provisioned.

Creating a state extension

State extensions can be added to a cluster configuration with the extra_cluster_addons variable. Like other cluster configuration settings, this can be overridden in a config file and passed to cluster commands like create or replace with the -f option:

# /shared/my-addons.yaml

extra_cluster_addons:
  - name: my-addon
    enabled: yes
    install_script: |
      #!/usr/bin/env bash

      echo "Hello from my addon"
$ moduskube cluster create -f /shared/my-addons.yaml

State extensions can also include text files that can be referenced within the install_script section. The extension below uses kubectl to install a Kubernetes StorageClass resource by pointing to its YAML manifest included in the files section of the extension:

# /shared/my-addons.yaml

extra_cluster_addons:
  - name: storageclass
    enabled: yes
    install_script: |
      #!/usr/bin/env bash

      set -eo pipefail

      kubectl apply -f files/standard-storageclass.yaml
    files:
      - name: standard-storageclass.yaml
        contents: |
          kind: StorageClass
          apiVersion: storage.k8s.io/v1
          metadata:
            name: standard
            annotations:
              storageclass.kubernetes.io/is-default-class: 'true'
          provisioner: kubernetes.io/aws-ebs
          parameters:
            type: gp2

You can define multiple extensions within the extra_cluster_addons variable and they will be loaded in the order defined. And like all configuration variables, Jinja templating can be used to reference any other cluster configuration variable within an extension.

Built-in state extensions

ModusKube uses a number of built-in state extensions to install things like Tiller into the clusters it provisions. These base extensions are defined with the base_cluster_addons variable in /moduskube/ansible/inventory/group_vars/all/addons.yml. You shouldn’t normally need to override these extensions, but the option is available if required.

Base extensions can be enabled or disabled with a configuration variable. For example, you could disable Tiller by setting helm_tiller_enabled to no on the command line:

$ moduskube cluster create -e helm_tiller_enabled no

CLI extensions

CLI extensions let you access the ModusKube API and create powerful custom commands. A CLI extension can be as simple as a custom command that adds a useful shortcut or a complete command group hierarchy for managing complex infrastructure projects.

A common pattern is to create a CLI extension for managing custom infrastructure on top of the ModusKube API. This extension would expose a top level command group that holds multiple commands and groups for managing all aspects of the infrastructure. For an example of this, see the PortX CLI Extension.

Loading extensions

A CLI extension is any Python module or package containing top-level subclasses of click.Command or click.Group. If a module or package contains multiple commands, ModusKube will load all of them.

By default ModusKube looks for CLI extensions in /moduskube/plugins and /shared/plugins. To install an extension, you can simply copy it to /shared/plugins, or you can use ModusKube as a base image in your own Dockerfile and build the extension directly into the container by copying it to the /moduskube/plugins directory.

Important

CLI extension code must be compatible with Python 3.10.

Creating a CLI extension

To get familiar with how CLI extensions work, we’ll create a very basic “Hello World” extension that adds a single hello command to ModusKube.

First, create a Python file called hello_world.py in /shared/plugins containing the following code:

import click


@click.command()
def hello():
    """Hello World command"""

    click.echo('Hello World')

Now, run moduskube to see your new hello command:

$ moduskube
Usage: moduskube [OPTIONS] COMMAND [ARGS]...

ModusKube v0.15
(C) Copyright 2018 - 2019 ModusBox, Inc.
https://github.com/modusintegration/moduskube

Options:
-f, --conf-file FILENAME  Load command configuration from a YAML file
-v, --verbose
-q, --quiet
--version                 Show the version and exit.
-h, --help                Show this message and exit.

Commands:
cloud    Cloud utilities
cluster  Manage clusters
hello    Hello World command
ip       Get IP address of cluster nodes or bastion server
lb       Manage load balancers
run      Run COMMAND inside ModusKube container
shell    Start ModusKube shell
ssh      SSH to cluster instances
tests    Run tests

...

And finally, run the hello command:

$ moduskube hello
Hello World

Using the ModusKube API

CLI extensions give you access to the ModusKube API. This contrived example creates a simple create-multiple command that uses the create() method of the Cluster class to create multiple clusters:

import click

from moduskube.resources.cluster import Cluster


@click.command()
@click.option('--region', '-r', required=True)
@click.argument('clusters', nargs=-1)
def create_multiple(region, clusters):
    """Create multiple clusters"""

    for name in clusters:
        cluster = Cluster(name, region=region)
        cluster.create()

You can then use the new create-multiple command like this:

$ moduskube create-multiple -r us-west-2 cluster-1 cluster-2 cluster-3

Importing objects from your extension

To import objects from your CLI extension in Python code you need to use the moduskube.plugins module namespace along with the name of your extension’s package. For example, a CLI extension package called hello_world would be importable in Python code as moduskube.plugins.hello_world.

This is only needed if you are building your extension as a package and want to import other parts of the package internally. For example:

from moduskube.plugins.hello_world.utils import my_function

CLI extension examples

A collection of CLI extension examples is available in the examples directory at the root of the project. For a look at a complex CLI extension used for managing production infrastructure, see the PortX CLI Extension.