Skip to main content

k8s

Query, provision and operate Kubernetes cluster resources - pods, deployments, services, config maps, RBAC and every other built-in control plane API group - using SQL. The provider works against any conformant cluster (kind, EKS, GKE, AKS, OpenShift, k3s, bare metal) and covers all built-in API groups including subresources such as status, scale and log.

Provider Summary

total services: 20
total resources: 172

See also: [SHOW] [DESCRIBE] [REGISTRY]


Installation​

To pull the latest version of the k8s provider, run the following command:

REGISTRY PULL k8s;

To view previous provider versions or to pull a specific provider version, see here.

Connecting to a cluster​

The API server endpoint is supplied in the WHERE clause of each query using two server parameters:

  • cluster_addr - hostname and port of the Kubernetes API server endpoint (default: localhost)
  • protocol - http or https (default: https)
SELECT json_extract(metadata, '$.name') AS name,
json_extract(status, '$.phase') AS phase
FROM k8s.core.pods
WHERE protocol = 'http'
AND cluster_addr = 'localhost:8001'
AND namespace = 'default';

Both parameters can be defaulted from environment variables, removing them from the WHERE clause entirely:

  • KUBE_HOST - default for cluster_addr; the hostname and port only, with no scheme (this differs from the Terraform variable of the same name, which takes a full URI)
  • KUBE_PROTOCOL - default for protocol
export KUBE_HOST='localhost:8001'
export KUBE_PROTOCOL='http'
SELECT json_extract(metadata, '$.name') AS name
FROM k8s.core.pods
WHERE namespace = 'default';

An explicit WHERE value always takes precedence over the environment variable.

cluster_addr must currently be a dot-free hostname (localhost, or a hosts-file alias for a remote endpoint) - bare IP addresses and fully qualified domain names are not yet supported for direct connections. The kubectl proxy workflow below is unaffected.

Authentication​

The provider default is null_auth (no credentials), designed for the kubectl proxy workflow - the proxy authenticates to the cluster using your kubeconfig (including cloud credential plugins for EKS, GKE and AKS), and StackQL connects to the local proxy port with no additional configuration:

kubectl proxy --port=8001

then in a separate terminal:

stackql shell
SELECT json_extract(metadata, '$.name') AS name
FROM k8s.core.namespaces
WHERE protocol = 'http' AND cluster_addr = 'localhost:8001';

This works with any cluster your kubectl can reach and is the recommended vector.

Direct access using a bearer token

To connect directly to the API server (no proxy), supply a bearer token via the --auth flag using the KUBE_TOKEN environment variable (the same variable the Terraform kubernetes provider uses), along with the cluster CA bundle:


export KUBE_TOKEN='eyJhbGciOiJ...'
AUTH='{ "k8s": { "type": "bearer", "credentialsenvvar": "KUBE_TOKEN" }}'
stackql shell --auth="${AUTH}" --tls.CABundle cluster-ca.pem

or using PowerShell:


$env:KUBE_TOKEN = 'eyJhbGciOiJ...'
$Auth = "{ 'k8s': { 'type': 'bearer', 'credentialsenvvar': 'KUBE_TOKEN' }}"
stackql.exe shell --auth=$Auth --tls.CABundle cluster-ca.pem

A service account token can be created with kubectl create token <serviceaccount>. For managed clusters, obtain a token from the platform credential helper and pass it the same way, for example:

export KUBE_TOKEN=$(aws eks get-token --cluster-name my-cluster | jq -r .status.token)

Tokens issued by cloud credential helpers are short lived (typically 15 minutes) and are not refreshed automatically - for long running sessions prefer the kubectl proxy vector. Client certificate (mTLS) authentication and native kubeconfig resolution are not currently supported; use kubectl proxy for those clusters. Alternatively --tls.allowInsecure=true skips CA verification (not recommended).

Example Queries​

Try the following queries using stackql shell, or run them from a script or CI pipeline with stackql exec. Row columns are the top-level fields of each Kubernetes object (metadata, spec, status, ...); nested values are addressed with json_extract. The queries below assume KUBE_HOST and KUBE_PROTOCOL are set (or add protocol and cluster_addr to each WHERE clause).

Pod estate​

All pods in the cluster with their phase and node placement:

SELECT json_extract(metadata, '$.namespace') AS namespace,
json_extract(metadata, '$.name') AS name,
json_extract(status, '$.phase') AS phase,
json_extract(spec, '$.nodeName') AS node
FROM k8s.core.pods_all_namespaces;

Pods that are not running - a one-line cluster health check:

SELECT json_extract(metadata, '$.namespace') AS namespace,
json_extract(metadata, '$.name') AS name,
json_extract(status, '$.phase') AS phase
FROM k8s.core.pods_all_namespaces
WHERE json_extract(status, '$.phase') NOT IN ('Running', 'Succeeded');

Deployment rollout state​

Desired versus ready replicas for every deployment in a namespace:

SELECT json_extract(metadata, '$.name') AS name,
json_extract(spec, '$.replicas') AS want,
json_extract(status, '$.readyReplicas') AS ready
FROM k8s.apps.deployments
WHERE namespace = 'default';

Node inventory​

Kubelet version, OS image, and schedulability per node:

SELECT json_extract(metadata, '$.name') AS name,
json_extract(status, '$.nodeInfo.kubeletVersion') AS kubelet,
json_extract(status, '$.nodeInfo.osImage') AS os,
json_extract(spec, '$.unschedulable') AS cordoned
FROM k8s.core.nodes;

RBAC audit​

Who is bound to cluster-admin:

SELECT json_extract(metadata, '$.name') AS binding,
subjects
FROM k8s.rbac.cluster_role_bindings
WHERE json_extract(role_ref, '$.name') = 'cluster-admin';

Warning events​

Recent warning events across the cluster, most likely the first place to look when something is off:

SELECT json_extract(metadata, '$.namespace') AS namespace,
reason,
message
FROM k8s.core.events_all_namespaces
WHERE type = 'Warning';

Provision, mutate and tear down​

Mutations use the same SQL grammar - INSERT creates an object, UPDATE applies a merge patch, REPLACE performs a full update and DELETE removes it. Body columns are the native wire property names (metadata, spec, data). A configmap end to end:

-- create
INSERT INTO k8s.core.config_maps(namespace, metadata, data)
SELECT 'default', '{"name": "app-config"}', '{"greeting": "hello"}';

-- partial update (merge patch)
UPDATE k8s.core.config_maps
SET data = '{"mood": "optimistic"}'
WHERE namespace = 'default' AND name = 'app-config';

-- remove it
DELETE FROM k8s.core.config_maps
WHERE namespace = 'default' AND name = 'app-config';

Subresources are first-class resources - scale a deployment through its scale subresource:

UPDATE k8s.apps.deployments_scale
SET spec = '{"replicas": 3}'
WHERE namespace = 'default' AND name = 'web';

SELECT ... LIMIT n is pushed down to the API server as the Kubernetes limit parameter, and list pagination (continue tokens) is traversed transparently - a SELECT returns all rows even when the API server caps page sizes.

Services​