Use QLKube to query the Kubernetes API

QLKube is a project that exposes the Kubernetes API as GraphQL. GraphQL is a data query and manipulation language for APIs developed initially by Facebook and released as open-source. It strives to reduce the chattiness clients can experience when querying REST APIs. It is very useful for mobile application and web development: by reducing the number of roundtrips needed to fetch the relevant data and by fetching only the needed field, the network usage is greatly reduced.

To install QLKube in OpenShift, use the NodeJS Source-to-Image builder:

oc new-project qlkube --display-name=QLKube
oc new-app nodejs~https://github.com/qlkube/qlkube.git --name=qlkube

Disable TLS certificate validation to accommodate your self-signed certificates:

oc set env dc/qlkube NODE_TLS_REJECT_UNAUTHORIZED=0

And enable the NodeJS development mode to enable the GraphQL explorer (disabled in production mode):

oc set env dc/qlkube NODE_ENV=development

Give the GLKube’s Service Account the right to query the Kubernetes API for its own namespace:

oc adm policy add-role-to-user view -z default

Once deployed, open the QLKube URL in your web browser:

open $(oc get route qlkube -o go-template --template="http://{{.spec.host}}")

You can try the following queries in the GraphQL explorer.

Get all pods in the current namespace

Unless you gave the cluster-admin right to the QLKube Service Account, you will have to specify a target namespace in all your queries. The all type is a meta type defined by QLKube to ease the use of common types such as services, deployments, pods, daemonSets, replicaSets, statefulSets, jobs or cronJobs.

Query:

query getAllPodsInCurrentNamespace {
  all(namespace: "qlkube") {
    pods {
      items {
        metadata {
          name
          creationTimestamp
        }
        status {
          phase
        }
      }
    }
  }
}

Response:

{
  "data": {
    "all": {
      "pods": {
        "items": [
          {
            "metadata": {
              "name": "qlkube-1-build",
              "creationTimestamp": "2019-06-07T07:56:53Z"
            },
            "status": {
              "phase": "Succeeded"
            }
          },
          {
            "metadata": {
              "name": "qlkube-3-jplpc",
              "creationTimestamp": "2019-06-07T14:03:48Z"
            },
            "status": {
              "phase": "Running"
            }
          }
        ]
      }
    }
  }
}

Get a service by name

To get an object by name, you can use the fieldSelector parameter (in this example, we are filtering on the name field in the metadata section).

Query:

query getServiceByNameAndNamespace {
  all(namespace: "qlkube", fieldSelector: "metadata.name=qlkube") {
    services {
      items{
        metadata {
          name
          namespace
        }
        spec {
          clusterIP
        }
      }
    }
  }
}

Response:

{
  "data": {
    "all": {
      "services": {
        "items": [
          {
            "metadata": {
              "name": "qlkube",
              "namespace": "qlkube"
            },
            "spec": {
              "clusterIP": "172.30.213.61"
            }
          }
        ]
      }
    }
  }
}

Type introspection

Playing with the built-in types of GLKube is nice but you might soon be limited. To discover all the available types, run this query:

{
  __schema {
    types {
      name
    }
  }
}

This query returns a list of all the available types (truncated here for brevity):

{
  "data": {
    "__schema": {
      "types": [
        {
          "name": "Query"
        },
        {
          "name": "String"
        },
        {
          "name": "Boolean"
        },
        {
          "name": "Int"
        },
        {
          "name": "ComGithubOpenshiftApiAppsV1DeploymentConfig"
        },
        {
          "name": "ComGithubOpenshiftApiRouteV1Route"
        },
        {
          "name": "ComGithubOpenshiftApiRouteV1RouteList"
        }
      ]
    }
  }
}

Get a Deployment Config by name and namespace

Once the desired data type discovered, you can use it directly.

Query:

query getDeploymentConfigByNameAndNamespace {
  comGithubOpenshiftApiAppsV1DeploymentConfig(name: "qlkube", namespace: "qlkube") {
    metadata {
      name
    }
    status {
      replicas
      availableReplicas
    }
  }
}

Reponse:

{
  "data": {
    "comGithubOpenshiftApiAppsV1DeploymentConfig": {
      "metadata": {
        "name": "qlkube"
      },
      "status": {
        "replicas": 1,
        "availableReplicas": 1
      }
    }
  }
}

Get routes by hostname and namespace

This query use a fieldSelector on the host field in the spec section and uses aliasing to rename the status field.

Query:

query getRouteByHostnameAndNamespace {
  routes: comGithubOpenshiftApiRouteV1RouteList(namespace: "qlkube" fieldSelector: "spec.host=qlkube-qlkube.app.itix.fr") {
    items {
      metadata {
        name
      }
      status {
        ingress {
          routerName
          conditions {
            deployed: status
          }
        }
      }
    }
  }
}

Reponse:

{
  "data": {
    "routes": {
      "items": [
        {
          "metadata": {
            "name": "qlkube"
          },
          "status": {
            "ingress": [
              {
                "routerName": "router",
                "conditions": [
                  {
                    "deployed": "True"
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
}

Sending your GraphQL request from curl

Once your GraphQL queries refined in the GraphQL Explorer, you can send them directly using curl or any HTTP client.

export GLKUBE_HOSTNAME=$(oc get route qlkube -o go-template --template="{{.spec.host}}")

cat <<EOF | curl -XPOST "http://$GLKUBE_HOSTNAME/" -H "Content-Type: application/json" -d @- -s |jq .
{
  "query": "query getAllPodsInCurrentNamespace {
              all(namespace: \"qlkube\") {
                services {
                  items {
                    metadata {
                      name
                      namespace
                      creationTimestamp
                      labels
                    }
                  }
                }
              }
            }"
}
EOF

Advanced use-cases

One use case in which GraphQL is very interesting is the ability to request in the same query an object and it’s linked objects. For instance, it would be nice from a hostname to query the route that matches this hostname, along with the service backing this route and the pods behind the service.

Unfortunately, this is not yet possible with GLKube. Since it auto-generates its GraphQL schema from the OpenAPI Specifications of the Kubernetes APIs and those APIs are loosely coupled, some code would be required to link the relevant object between them.

Conclusion

GLKube is a nice initiative to ease the use of the Kubernetes API. Definitely worth checking from times to times the status of this project. Some more code would be needed to enable the really interesting use cases that GraphQL can bring.