Skip to main content

No project description provided

Project description

Square is a tool to reconcile local manifests to/from a Kubernetes cluster.

It facilitates a lightweight and CLI based GitOps workflow, but developers can also use it as library to drive the Kubernetes state at scale.

It is important to understand that Square is neither a version manager, nor a package manager nor a render engine nor an authoring tool for manifests. Its sole purpose is to show the difference between the manifests on the local file system vs those on a cluster, and reconcile the difference in either direction. The plan/apply workflow will be familiar to Terraform users.

Square is safe to use alongside other tools like Helm, Kustomize or kubectl since it never modifies any manifests it touches. This includes the app.kubernetes.io/managed-by annotation. Similarly, it does not create any Secrets or other resource to track its own state because it has no state.

Installation

Grab a binary release or use pip to install it into a Python 3.10+ environment:

foo@bar:~$ pip install kubernetes-square --upgrade
foo@bar:~$ square version
2.1.0

Quickstart

Run square config to create a .square.yaml and open it in an editor. It comes with sensible defaults but you probably want to double check the values for kubeconfig and kubecontext near the top of the file. You can also tweak the selectors.kinds, selectors.labels and selectors.namespaces to target specific resources, labels and namespaces.

A typical Square workflow is this:

# Create a `.square.yaml` configuration file and adjust at least the `kubeconfig` value.
square config

# Download all resources that match the selectors in the `.square.yaml` file.
square get

# Show the differences and exit.
square plan

# Show the differences and give user the option to reconcile it.
square apply

Command line arguments take precedence over the values defined in .square.yaml:

# Plan the resources based on `.square.yaml`.
square plan

# Limit the plan to the `default` and `foo` namespaces.
square plan -n default foo

# Limit the plan to ConfigMaps and Deployments only.
square plan configmap deployment

# Limit the plan to resources that have the `app=foo` label.
square plan -l app=foo

# Limit plan to all deployments called `foo`.
square plan deploy/foo

# Mix and match several options.
square plan configmap deploy/foo -n default -l app=foo

These commands will also work with the square apply command.

Example Workflow

This section shows how to use Square to import resources from a cluster as well as plan and apply changes.

Setup

Assuming you have a valid Kubeconfig you can setup the demo as follows:

# Deploy the demo resources with `kubectl`. We are not using Square here
# to demonstrate how to import existing resources with it.
foo@bar:~$ kubectl apply -f integration-test-cluster/test-resources.yaml
...

# Create a new folder for the experiment.
foo@bar:~$ mkdir try_square
foo@bar:~$ cd try_square

# Creata a vanilla Square configuration file `.square.yaml`.
foo@bar:~$ square config
Created configuration file <.square.yaml>.
Please open the file in an editor and adjust the values, most notably `kubeconfig` and `selectors.[kinds | namespaces | labels]`.

# Open `.square.yaml` in your editor and change the values for `kubeconfig` and
# (optionally) `kubecontext` to point to your Kubeconfig for your cluster.
foo@bar:~$ emacs .square.yaml
...

Get Current Cluster State

Download all Namespace- and Deployment manifests from the cluster and save them to ./manifests:

# Import all the resources specified in `.square.yaml`.
foo@bar:~$ square get --groupby ns kind
foo@bar:~$ tree
.
└── manifests
    ├── default
    │   ├── configmap.yaml
    │   ├── namespace.yaml
    │   └── service.yaml
    ├── _global_
    │   ├── clusterrolebinding.yaml
    │   ├── clusterrole.yaml
    │   └── customresourcedefinition.yaml
    ├── kube-node-lease
    │   ├── configmap.yaml
    │   └── namespace.yaml
    ├── kube-public
    │   ├── configmap.yaml
    │   ├── namespace.yaml
    │   ├── rolebinding.yaml
    │   └── role.yaml
    ├── kube-system
    │   ├── configmap.yaml
    │   ├── daemonset.yaml
    │   ├── deployment.yaml
    │   ├── namespace.yaml
    │   ├── rolebinding.yaml
    │   ├── role.yaml
    │   ├── secret.yaml
    │   ├── serviceaccount.yaml
    │   └── service.yaml
    ├── local-path-storage
    │   ├── configmap.yaml
    │   ├── deployment.yaml
    │   ├── namespace.yaml
    │   └── serviceaccount.yaml
    ├── square-tests-1
    │   ├── configmap.yaml
    │   ├── cronjob.yaml
    │   ├── daemonset.yaml
    │   ├── deployment.yaml
    │   ├── horizontalpodautoscaler.yaml
    │   ├── ingress.yaml
    │   ├── namespace.yaml
    │   ├── persistentvolumeclaim.yaml
    │   ├── poddisruptionbudget.yaml
    │   ├── rolebinding.yaml
    │   ├── role.yaml
    │   ├── secret.yaml
    │   ├── serviceaccount.yaml
    │   ├── service.yaml
    │   └── statefulset.yaml
    └── square-tests-2
        ├── configmap.yaml
        ├── cronjob.yaml
        ├── daemonset.yaml
        ├── deployment.yaml
        ├── horizontalpodautoscaler.yaml
        ├── ingress.yaml
        ├── namespace.yaml
        ├── persistentvolumeclaim.yaml
        ├── rolebinding.yaml
        ├── role.yaml
        ├── secret.yaml
        ├── serviceaccount.yaml
        ├── service.yaml
        └── statefulset.yaml

9 directories, 54 files

The --groupby determines the layout of the manifests/ folder. In this case, Square created one folder for each namespace and grouped the resources type within those folders. Non-namespaced resources, like ClusterRole will be in the _global_ folder.

It is important to note that Square does not attach any meaning to the directory layout. Internally, it will compile all manifests into a flat list. You can therefore rename and move files as you see fit, and even move individual manifests between files. Furthermore, square get is smart enough to update the right manifests in the right files.

Group By Label

Square can also group manifests based on a _single label. For instance, here are the integration test cluster manifests grouped by Namespace and the app label:

foo@bar:~$ rm -rf manifests
foo@bar:~$ square get --groupby ns label=app kind
foo@bar:~$ tree
.
└── manifests
    ├── default
    │   └── _other
    │       ├── configmap.yaml
    │       ├── namespace.yaml
    │       └── service.yaml
    ├── _global_
    │   ├── demoapp-1
    │   │   ├── clusterrolebinding.yaml
    │   │   ├── clusterrole.yaml
    │   │   └── customresourcedefinition.yaml
    │   └── _other
    │       ├── clusterrolebinding.yaml
    │       └── clusterrole.yaml
    ├── kube-node-lease
    │   └── _other
    │       ├── configmap.yaml
    │       └── namespace.yaml
    ├── kube-public
    │   └── _other
    │       ├── configmap.yaml
    │       ├── namespace.yaml
    │       ├── rolebinding.yaml
    │       └── role.yaml
    ├── kube-system
    │   ├── kindnet
    │   │   └── daemonset.yaml
    │   ├── kube-proxy
    │   │   └── configmap.yaml
    │   └── _other
    │       ├── configmap.yaml
    │       ├── daemonset.yaml
    │       ├── deployment.yaml
    │       ├── namespace.yaml
    │       ├── rolebinding.yaml
    │       ├── role.yaml
    │       ├── secret.yaml
    │       ├── serviceaccount.yaml
    │       └── service.yaml
    ├── local-path-storage
    │   └── _other
    │       ├── configmap.yaml
    │       ├── deployment.yaml
    │       ├── namespace.yaml
    │       └── serviceaccount.yaml
    ├── square-tests-1
    │   ├── demoapp-1
    │   │   ├── configmap.yaml
    │   │   ├── cronjob.yaml
    │   │   ├── daemonset.yaml
    │   │   ├── deployment.yaml
    │   │   ├── horizontalpodautoscaler.yaml
    │   │   ├── ingress.yaml
    │   │   ├── namespace.yaml
    │   │   ├── persistentvolumeclaim.yaml
    │   │   ├── rolebinding.yaml
    │   │   ├── role.yaml
    │   │   ├── secret.yaml
    │   │   ├── serviceaccount.yaml
    │   │   ├── service.yaml
    │   │   └── statefulset.yaml
    │   └── _other
    │       ├── configmap.yaml
    │       ├── poddisruptionbudget.yaml
    │       └── secret.yaml
    └── square-tests-2
        ├── demoapp-1
        │   ├── configmap.yaml
        │   ├── cronjob.yaml
        │   ├── daemonset.yaml
        │   ├── deployment.yaml
        │   ├── horizontalpodautoscaler.yaml
        │   ├── ingress.yaml
        │   ├── namespace.yaml
        │   ├── persistentvolumeclaim.yaml
        │   ├── rolebinding.yaml
        │   ├── role.yaml
        │   ├── secret.yaml
        │   ├── serviceaccount.yaml
        │   ├── service.yaml
        │   └── statefulset.yaml
        └── _other
            ├── configmap.yaml
            └── secret.yaml

22 directories, 62 files

Note that resources without an app label are in the catch-all folder _other.

Create A Plan

The plan is currently clean because we just imported the manifests from the cluster. To very:

foo@bar:~$ square plan
--------------------------------------------------------------------------------
Plan: 0 to add, 0 to change, 0 to destroy.

To make this more interesting we will add a foo label to the Namespace manifest in manifests/square-tests-1/demoapp-1/namespace.yaml. It should look something like this:

apiVersion: v1
kind: Namespace
metadata:
  labels:
    app: demoapp-1
    foo: bar
    kubernetes.io/metadata.name: square-tests-1
  name: square-tests-1
spec:
  finalizers:
  - kubernetes

Now the plane will show a difference between the local and server manifests:

foo@bar:~$ square plan ns
Patch NAMESPACE None/square-tests-1 (v1)
    ---
    +++
    @@ -3,6 +3,7 @@
     metadata:
       labels:
         app: demoapp-1
    +    foo: bar
         kubernetes.io/metadata.name: square-tests-1
       name: square-tests-1
     spec:

--------------------------------------------------------------------------------
Plan: 0 to add, 1 to change, 0 to destroy.

We could now use square get ns to make the local manifest match the cluster state or square apply ns to make the cluster state match the local manifests. We will do the latter:

foo@bar:~$ square apply ns
Patch NAMESPACE None/square-tests-1 (v1)
    ---
    +++
    @@ -3,6 +3,7 @@
     metadata:
       labels:
         app: demoapp-1
    +    foo: bar
         kubernetes.io/metadata.name: square-tests-1
       name: square-tests-1
     spec:

--------------------------------------------------------------------------------
Plan: 0 to add, 1 to change, 0 to destroy.

Type yes to apply the plan.
  Your answer: yes

Patching NAMESPACE None/square-tests-1

We can use kubectl to verify that the square-tests-1 Namespace now has the foo=bar label:

foo@bar:~$ kubectl describe ns square-tests-1
Name:         default
Labels:       foo=bar
Annotations:  <none>
Status:       Active

No resource quota.

No resource limits.

The plan is now clean again because the local files and the cluster are in sync again:

foo@bar:~$ square plan ns
--------------------------------------------------------------------------------
Plan: 0 to add, 0 to change, 0 to destroy.

Create and Destroy Resources

So far we have only imported and modified existing resource but square apply will also create and remove resources as necessary. For instance, to add a new resource we add its manifest to the manifests/ folder, either in a new file or added to an existing one.

foo@bar:~$ cat > manifests/additional_manifest.yaml <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: dummy
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: random-user
  namespace: dummy
EOF

foo@bar:~$ square apply
Create NAMESPACE None/dummy (v1)
    apiVersion: v1
    kind: Namespace
    metadata:
      name: dummy

Create SERVICEACCOUNT dummy/random-user (v1)
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: random-user
      namespace: dummy

--------------------------------------------------------------------------------
Plan: 2 to add, 0 to change, 0 to destroy.

Type yes to apply the plan.
  Your answer: yes

Creating NAMESPACE None/dummy
Creating SERVICEACCOUNT dummy/random-user

Similarly, to remove those resources again we merely delete the manifest file and run square apply again:

foo@bar:~$ rm manifests/additional_manifest.yaml
foo@bar:~$ square apply
Delete CONFIGMAP dummy/kube-root-ca.crt (v1)
Delete NAMESPACE None/dummy (v1)
Delete SERVICEACCOUNT dummy/random-user (v1)
--------------------------------------------------------------------------------
Plan: 0 to add, 0 to change, 3 to destroy.

Type yes to apply the plan.
  Your answer: yes

Deleting SERVICEACCOUNT dummy/random-user
Deleting CONFIGMAP dummy/kube-root-ca.crt
Deleting NAMESPACE None/dummy

Use It As A Library

You can also use Square as a library. In fact, the CLI commands explained here are just thin wrappers around that library. See here for how to build a basic version of the CLI with only a few lines of code.

Automated Tests

Square ships with a comprehensive set of unit tests:

pipenv run pytest

To run the integration tests as well you need to have KinD and start it with:

cd integration-test-cluster
./start_cluster.sh

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kubernetes_square-2.1.0.tar.gz (53.0 kB view hashes)

Uploaded Source

Built Distribution

kubernetes_square-2.1.0-py3-none-any.whl (55.4 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page