Invoking the Kubernetes API in Node.js

- 11 mins

Life becomes just a little bit easier if you can invoke the Kubernetes API directly. That’s why GoDaddy decided to make kubernetes-client, an easy to use Node.js client for Kubernetes.

It is listed as the officially supported Kubernetes client library for JavaScript. This is significant as it is has the backing of Kubernetes SIG API Machinery, which means it is kept up to date with changes to the Kubernetes API specification. It also means that your support requests and issues are much more likely to get a timely response.

Installing

Install with npm:

npm install kubernetes-client --save

Configuring

kubernetes-client generates a Kubernetes API client at runtime based on a Swagger/OpenAPI definition.

kubernetes-client will configure itself automatically by first trying to load configuration from the KUBECONFIG environment variable, then in ~/.kube/config. If it hasn’t found anything yet it will then try to use an in-cluster service account, and eventually settle on a default proxy configuration as a last resort.

A simple configuration:

const { Client, KubeConfig } = require('kubernetes-client');
const Request = require('kubernetes-client/backends/request');

async function initKubeClient() {
  const kubeconfig = new KubeConfig();

  if (process.env.NODE_ENV === 'production') {
    kubeconfig.loadFromCluster();
  } else {
    kubeconfig.loadFromDefault();
  }

  const backend = new Request({ kubeconfig });
  const kubeclient = new Client({ backend });

  await kubeclient.loadSpec();

  return kubeclient;
}

Using

kubernetes-client maps path item objects to object chains ending in HTTP methods. So for example, api/v1/namespaces/myproject/configmaps maps to to .api.v1.namespaces('myproject').configmaps. This mapping logic can be used for all resources types.

You can refer to the Kubernetes API documentation to find the API endpoint for a particular resource.

Deployments

Let’s learn how to interact with Deployments. I picked Deployments as an example as they are commonly used, and the same logic can be applied to all other resources.

Creating a deployment

You can create a deployment by making a POST request to the Kubernetes API.

const deploymentManifest = require('./nginx-deployment.json');

const createdDeployment = await kubeclient.apis.apps.v1.namespaces(namespace).deployments.post({ body: deploymentManifest });
  
console.log('NGINX Deployment created:', createdDeployment);

You can also verify that the deployment was created using kubectl.

$ kubectl get deployments
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   1         1         1            0           1m

Modifying a deployment

To modify part of a resource you can send a PATCH request.

const labels = {
  metadata: {
    labels: {
      environment: 'DEVELOPMENT'
    }
  }
};

const modified = await kubeclient.apis.apps.v1.namespaces(namespace).deployments(deploymentManifest.metadata.name).patch({ body: labels });

console.log('Deployment modified:', modified.body.metadata);

Updating a deployment

By making a PUT request you can replace the entire resource.

const updated = await kubeclient.apis.apps.v1.namespaces(namespace).deployments(deploymentManifest.metadata.name).put({ body: deploymentManifest });

console.log('Deployment updated:', updated);

Fetching deployment(s)

Getting all deployments in a namespace.

const deployment = await kubeclient.apis.apps.v1.namespaces(namespace).deployments(deploymentManifest.metadata.name).get();

console.log('Deployment:', deployment);

Fetching a single deployment by in a namespace.

const deployments = await kubeclient.apis.apps.v1.namespaces(namespace).deployments.get();

console.log('Deployments:', deployments);

Getting all deployments in all namespaces.

const deployments = await kubeclient.apis.apps.v1.deployments.get();

console.log('Deployments (all namespaces):', deployments);

You can optionally specify a query string object qs to GET requests. For example, to filter on label selector.

const deployments = await kubeclient.apis.apps.v1.namespaces(namespace).deployments.get({ qs: { labelSelector: 'app=nginx'}});

console.log(deployments);

You can use the Kubernetes API documentation to see what other query parameters are available for a resource.

Deleting a deployment

Deployments can be deleted with DELETE.

const removed = await kubeclient.apis.apps.v1.namespaces(namespace).deployments(deploymentManifest.metadata.name).delete();

console.log('Deployment deleted:', removed);

Custom Resources

With kubernetes-client it is possible to extend the Kubernetes API with a CustomResourceDefinition.

In this example, I am creating a CustomResourceDefinition for GitHub accounts.

githubaccount-crd.json

{
  "kind": "CustomResourceDefinition",
  "spec": {
    "scope": "Namespaced",
    "version": "v1",
    "versions": [{
      "name": "v1",
      "served": true,
      "storage": true
    }],
    "group": "craicoverflow.github.io",
    "names": {
      "shortNames": [
        "ga"
      ],
      "kind": "GitHubAccount",
      "plural": "githubaccounts",
      "singular": "githubaccount"
    }
  },
  "apiVersion": "apiextensions.k8s.io/v1beta1",
  "metadata": {
    "name": "githubaccounts.craicoverflow.github.io"
  }
}

Creating a CustomResourceDefinition

const crd = require('./githubaccount-crd.json');

const createCRD = await kubeclient.apis['apiextensions.k8s.io'].v1beta1.customresourcedefinitions.post({ body: crd });

console.log('CustomResourceDefinition created:', createCRD);

You then need to add the endpoints for the new CustomResourceDefinition to kubeclient.

kubeclient.addCustomResourceDefinition(crd);

Creating a custom resource

Now that we have created the GitHubAccount CRD, we will be able to create a GitHubAccount custom resource.

githubaccount-cr.json

{
    "apiVersion": "craicoverflow.github.io/v1",
    "kind": "GitHubAccount",
    "metadata": {
        "name": "craicoverflow"
    },
    "spec": {
        "login": "craicoverflow",
        "blog": "https://endaphelan.me",
        "bio": "// TODO: Add a bio",
        "type": "User",
        "public_repos": "52"
    }
}
const customResource = require('./githubaccount-crd.json');

const createdAccount = await kubeclient.apis[crd.spec.group].v1.namespaces(namespace).githubaccounts.post({ body: customResource });

console.log('Created GitHubAccount:', createdAccount);

Fetching custom resource(s)

Fetching a GitHubAccount custom resource.

const githubAccount = await kubeclient.apis[crd.spec.group].v1.namespaces(namespace).githubaccounts(customResource.metadata.name).get();

console.log('GitHubAccount:', githubAccount);

Fetching all GitHubAccount custom resources in a namespace.

const allAccounts = await kubeclient.apis[crd.spec.group].v1.namespaces(namespace).githubaccounts.get();

console.log('GitHubAccountList:', allAccounts);

Deleting a custom resource

const deleteAccounts = await kubeclient.apis[crd.spec.group].v1.namespaces(namespace).githubaccounts(customResource.metadata.name).delete();

console.log('Deleted GitHubAccount:', deleteAccounts);

Deleting a CustomResourceDefinition

const deletedCRD = await kubeclient.apis['apiextensions.k8s.io'].v1beta1.customresourcedefinitions(crd.metadata.name).delete();

console.log('GitHubAccount CRD deleted:', deletedCRD);

Error handling

kubernetes-client outputs a HTTP error when a request fails. The following example emulates kubectl apply, by handling a 409 Conflict error when creating a deployment, and replacing the resource instead.

try {
  const createdDeployment = await kubeclient.apis.apps.v1.namespaces(namespace).deployments.post({ body: deploymentManifest });
  
  console.log('Deployment created:', createdDeployment);
} catch (err) {
  
  if (err.statusCode === 409) {
    const updatedDeployment = await kubeclient.apis.apps.v1.namespaces(namespace).deployments(deploymentManifest.metadata.name).put({ body: deploymentManifest });

    console.log('Updated updated:', updatedDeployment);
  }
}

Watching resources

You can use watch endpoints to stream events from resources. Common event types are ADDED, MODIFIED, DELETED, which signal a new or changed resource at that endpoint.

Watching deployments

const deploymentStream = await kubeclient.apis.apps.v1.watch.namespaces(namespace).deployments.getObjectStream();

deploymentStream.on('data', event => {
  if (event.type === 'ADDED') {
    console.log('Deployment created:', event.body);
  }
});

Watching custom resources

const githubAccountStream = await kubeclient.apis[crd.spec.group].v1.watch.namespaces(namespace).githubaccounts.getObjectStream();

githubAccountStream.on('data', event => {
  if (event.type === 'CLOSED') {
    console.log('GitHub account deleted:', event);
  }
});

To see the watch in action, create or update a GitHubAccount custom resource from your terminal and you will see the event stream output a new event in your Node.js application.

cat <<EOF | kubectl apply -n myproject -f -
apiVersion: craicoverflow.github.io/v1
kind: GitHubAccount
metadata:
  name: testuser
spec:
  bio: ''
  blog: https://example.com
  login: testuser
  public_repos: "100"
  type: User
EOF

Additional Resources

Enda Phelan

Enda Phelan

Software Engineer @ Red Hat.

rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium vimeo