Instantiating a Pulumi Kubernetes provider inside a Pod
Usually, we call the Kubernetes API from within a Pod using kubectl
or by exposing the API via kubectl proxy and making HTTP requests to it.
The Pulumi Kubernetes provider requires a kubeconfig file, which is either loaded from disk (details) or passed as a keyword argument. Thus, we need to generate a kubeconfig that is valid from within a Pod.
Note that the kubeconfig becomes part of the stack state. Since the service account token is tied to a particular service account and rotated regularly, we cannot simply embed the token into the kubeconfig as in this gist by Rich Adams. Instead, we have to load the token from the Pod environment when the Kubernetes provider (which, just like kubectl
, uses the client-go library) requests credentials.
Here's a utility function that generates a kubeconfig using the information available in the Pod:
import os
import pathlib
def get_kubeconfig():
service_host = os.getenv("KUBERNETES_SERVICE HOST")
service_port = os.getenv("KUBERNETES_SERVICE_PORT")
service_scheme = (
"http" if service_port in ("80", "8080", "8081") else "https"
)
server_url = f"{service_scheme}://{service_host}:{service_port}"
service_account_dir = pathlib.Path(
"/var/run/secrets/kubernetes.io/serviceaccount"
)
ca_crt_path = service_account_dir / "ca.crt"
token_path = service_account_dir / "token"
with open(service_account_dir / "namespace") as f:
namespace = f.read()
context = "my-context"
return f"""apiVersion: v1
kind: Config
preferences: {{}}
current-context: {context}
clusters:
- name: myCluster
cluster:
server: {server_url}
certificate-authority: {ca_crt_path}
users:
- name: podServiceAccount
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: bash
args:
- -c
- |
cat <<EOF
{{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"spec": {{}},
"status": {{
"token": "$(cat {token_path})"
}}
}}
EOF
contexts:
- name: {context}
context:
cluster: myCluster
namespace: {namespace}
user: podServiceAccount
"""
Note that the generated kubeconfig requires bash
to be available in the Pod the Pulumi program is executed in.
Using this get_kubeconfig()
utility function, we can generate a kubeconfig on-the-fly when instantiating the Pulumi Kubernetes provider and/or pass it on as a stack output:
import pulumi
import pulumi_kubernetes as k8s
k8s_provider = k8s.Provider("k8s-provider",
kubeconfig=get_kubeconfig())
pulumi.export("kubeconfig", get_kubeconfig())
Subscribe to my newsletter
Read articles from Kilian Kluge directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Kilian Kluge
Kilian Kluge
My journey into software and infrastructure engineering started in a physics research lab, where I discovered the merits of loose coupling and adherence to standards the hard way. I like automated testing, concise documentation, and hunting complex bugs.