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.
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-httporhttps(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 forcluster_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 forprotocol
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_addrmust 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. Thekubectl proxyworkflow 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 proxyvector. Client certificate (mTLS) authentication and native kubeconfig resolution are not currently supported; usekubectl proxyfor those clusters. Alternatively--tls.allowInsecure=trueskips 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.