Kubernetes GraphQL Query in Go. Build GraphQL server for searching… | by Stefanie Lai | Jun, 2022

Build GraphQL server for searching Kubernetes resources in the cluster

In shared-cluster governance, part of our responsibility is to allow users to access their resources, which we fulfill by RBAC to assign reasonable permissions and enable users’ queries via kubectl. “None barrier” to back-end developers, while not the case for users like mobile, web, and data developers, who are not familiar with command-line operations or kubectl.

Then how shall we cut through the barrier for our users?

We made it with a UI queryable interface, providing a query service and APIs like REST or GraphQL to secure easier access, higher platform visibility, and better user experience. And in regard to implementation, we carried out a GraphQL service with Go.

REST and GraphQL are two popular choices when implementing frontend-backend interaction. The former has been an Internet standard for years, and the latter is a Facebook open-source API query language, whose basics you can find on graphql.org. And many comparisons are made between these two, but here I will only post the one from https://graphcms.com/blog/graphql-vs-rest-apis, which best visualizes the differences.

So, it’s not hard to conclude why we choose GraphQL over REST.

  • GraphQL is more flexible.

Its opponent REST is rigid. TakingPod querying(name, namespace, labels, container_name, maintainer) as an example, we can find out why: REST usually designs multi-query APIs like GET /pod/:name, GET /pod/:namespace. And there are only two approaches in scenarios where the returned data needs customizing and not all the information but only certain fields are needed.

  • Return all fields and filter them with the client, increasing the network overhead greatly when the response contains tens of fields.
  • The server customizes the return data based on needs, which weighs much on the backend workload.

How overwhelming it is when in Kubernetes there are dozens of resource types, each of which supports at least three queries, thus hundreds of REST APIs are to be designed, never to mention the future maintenance.

GraphQL saves us. It doesn’t have dozens of APIs and enables the client to select the data independently and the server to return the targeted data accurately. Moreover, it provides the client with an unified format to obtain data, no matter what the data type is, in a more rigorous, scalable, and maintainable way.

query {
Pod(namespace: $namespace, name: $name) {
metadata {
name
namespace
//labels
//annotations
}
status {
conditions {
lastTransitionTime
message
reason
status
type
}
}
spec {
//spec fields
}
}
}
  • GraphQL is more familiar to our users.

Backstage, a platform widely used in many big companies, gives GraphQL good support, and bases many internal implementations on GraphQL, making our query APIs more acceptable to users since they are well familiar with it.

Schema(GraphQL schema language) is the core of GraphQL and describes the data model we want to query, among which the most basic and critical is abstracting and defining the object type.

When developing GraphQL in Go, we use the framework graphql-go, based on which we get the definition of GraphQL schema done. The 4 elements are

  • Type schemawhich defines the query name, the arguments that the query uses, and the fields and types returned by the query.
  • Resolverthe callback method for filling the return message.
  • Subscriberthe callback method for incremental update of the returns.
  • Mutationa method for modifying data (not into details in this case).

Type Schema

Define the field.

type Resource {
name: String!
labels: [Label!]
status: Status!
}

Type is just as Class in Java and struct in Go, consisting of a set of Fields, each of which has a corresponding type. As shown in the example, there are different categories of type, among which Scalar Type is the most common one, like String in the example and many of the following.

GraphQL also supports customized Scalar types. The Labeland Status in the example are of Object Typewhich allows us to define GraphQL types like defining class diagrams and links them together.

[], !, []! are Type Modifierwhich are used to mark Field as an array or as not empty, such as [Label!] indicates that labels is an array composed of Label types and the array can be empty, but the Label mustn’t.

For other interace types, union types, and input types that GraphQL supports, you can refer to the examples in the documentation if interested.

And on the graph-go side, there are also one-to-one corresponding types, such as graphql.Stringthat each field is nullable by default, and you can use graphql.NewNonNull to wrap the type if non-null is needed.

Resolver

Assigning or parsing the field is the next step.

The Resolver fills data for the return in any way you define. And to query the resources in the Kubernetes cluster, client-go is used here.

Subscriber

By registering Subscriberwhich is very similar to List/Watch, we can update the GraphQL data later. It is not new to you if you are familiar with the Kubernetes Informer Pattern.

Since we use Kubernetes queries, client-go informer is a perfect match. But when querying a database or a message store like Kafka, there must be other ways to support this.

After Type Schema, Resolver and Subscribera simple GraphQL schema for Pod queries can be defined.

Now is about applying client-go, which is considered the best choice for Go’s interacting with Kubernetes clusters, to implement the resolver and subscriber methods.

As to Pod queries, we directly use the clientset API, combining them with the various parameters we defined above.

For the subscriber, we customize the update of the Add event with Informer, return a channel and process the channel update message from the subscriber with the graphql-go framework.

Let’s start the GraphQL server with Go httpserverstep by step.

graphqlHandler := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: false,
Playground: true,
})
http.Handle("/graphql", graphqlHandler)
err = http.ListenAndServe(":8080", nil)

Now test it on http://localhost:8080/graphql.

Input

query {
Pod(namespace: "prometheus") {
name
status
}
}

And we get

{
"data":{
"Pod":{
"name":"prometheus-khdf12",
"status":"Running"
}
}
}

Upon encapsulating the program into a docker image and deploying it to the cluster, we are now ready to open the service to users.

The Dockerfile 👇

To be noted, WorkloadIdentity should be configured to enable client-go in a GKE cluster. See details in Cluster Governance — clean up resources periodically.

The GraphQL query of Kubernetes Pod we completed is far from perfect, and we want the return of the full Pod information rather than a simplePodShort.

However, there are a few issues lying on the path to manually returning the Pod fields.

  • Cumbersome. There may be dozens of fields, which are nested lay by layer.
  • Not maintenance-friendly. Code updating is inevitable if Kubernetes discards or adds some fields in future versions.
  • None-expansion-conducive. One Pod type requires cumbersome definitions, what if expanding it to dozens or even hundreds of types and CRDs in the cluster? It seems to be a mission impossible.

Is there a flexible and extensible way? The short answer is to apply client-go to obtain the CRD definition in the cluster and parse it into a graph.Fields collection. For a detailed answer, please follow my next article.

Thanks for reading!

https://graphcms.com/blog/graphql-vs-rest-apis

https://graphql.org/learn/schema/

Leave a Comment