Using the Kubernetes Python Client with AWS
January 27, 2021 / 5 minute read
Share this:
As someone who normally just uses kubectl
and helm
to talk to my Kubernetes
clusters, the idea of scripting modifications to my Kubernetes cluster was
exciting!! I cracked open the
kubernetes-python
client and
started playing.
TL;DR;
We use the boto3
, eks-token
, and kubernetes
python packages to
talk to an EKS cluster without depending on kubeconfig
.
Why
For those of us interactively building and maintaining kubernetes resources,
helm
or kubectl
become our bread and butter. They provide very nice CLI
interfaces and have all the bells and whistles one could ask for!
Moreover, when it comes to AWS (Amazon Web Services) and their EKS (Elastic
Kubernetes Service) clusters, they handle authentication smoothly and easily via
a handy CLI command and the kubeconfig
file (usually stored at ~/.kube/config
:
aws eks update-kubeconfig --alias mycluster
(Tip: Don't forget the --alias
flag!)
My assume-role settings and environment variables integrate nicely from my shell: everything is dandy!
However, in this case, I want to build an app / software program that itself
accesses, modifies, and maintains resources on my Kubernetes cluster. Of course,
one can script a solution around these CLIs, but that is not very portable (requires
the CLIs installed) and is prone to failure because kubeconfig
is user-defined,
user-maintained, and hinges entirely on contexts
which are arbitrary text strings.
So I headed for the kubernetes-python
API client which simplifies making API requests
against a Kubernetes cluster. With any luck, I will finish the day with a Python program
that creates my Kubernetes resources.
NOTE: Before we get started, it is worth noting that I am presuming you have a way to authenticate to AWS. In my case, I have already provided environment variables, an instance profile, etc. that gives this program access to the AWS API as the necessary IAM role.
The Story is Auth
As with many of my stories, this story will become one largely consisting of
authentication. Unraveling the "magic" of the kubeconfig
file and AWS's IAM
authentication was not an easy task - as I soon learned, the kubernetes-python
API client also depends heavily on kubeconfig
by default.
Most of the
examples look
something like this, and if you want to use kubeconfig
, this works very well.
from kubernetes import config
config.load_kube_config()
core_v1 = core_v1_api.CoreV1Api()
(Although there is also a cool example of in-cluster config)
However, for portability, I want to bypass kubeconfig
but will be running outside of a cluster. So let's see how this
class works.
config.kube_config.Configuration
After a bit of flailing, fighting, and digging, I learned that you can initialize an API connection with the API endpoint and a bearer token. Basically, you initialize a configuration object directly and then use that to initialize an API client.
import kubernetes
def k8s_api_client(endpoint: str, token: str, cafile: str) -> kubernetes.client.CoreV1Api:
kconfig = kubernetes.config.kube_config.Configuration(
host=endpoint,
api_key={'authorization': 'Bearer ' + token}
)
kconfig.ssl_ca_cert = cafile
kclient = kubernetes.client.ApiClient(configuration=kconfig)
return kubernetes.client.CoreV1Api(api_client=kclient)
There are other useful options in kconfig
, like kconfig.proxy
and
kconfig.verify_ssl
. Have a look at the class for more details!
Authenticate to AWS / EKS
Now we need to figure out how to get a bearer token to talk to EKS. I cannot use
the magic in kubeconfig
, which led me to the AWS CLI's aws eks get-token
command. I
chose to opt for a pure python solution in the
eks-token
package. You can evaluate the
source for yourself here and more
specifically,
here.
With this module, we can acquire an EKS token easily:
import eks_token
cluster_name = 'my-eks-cluster'
my_token = eks_token.get_token(cluster_name)
NOTE: It is worth checking whether the boto3
or aws
libraries provide
access to this functionality directly from Python as things improve!
Now TLS
Unfortunately, the kubernetes-python
client does not allow for inlining
the TLS CA Certificate. As a result, we have to write it to a temp file
(which by inspection is exactly what the kubeconfig
approach is doing)
import boto3
import tempfile
import base64
def _write_cafile(data: str) -> tempfile.NamedTemporaryFile:
# protect yourself from automatic deletion
cafile = tempfile.NamedTemporaryFile(delete=False)
cadata_b64 = data
cadata = base64.b64decode(cadata_b64)
cafile.write(cadata)
cafile.flush()
return cafile
bclient = boto3.client('eks')
cluster_data = bclient.describe_cluster(name=cluster_name)['cluster']
my_cafile = _write_cafile(cluster_data['certificateAuthority']['data'])
Put it all together
Armed with a bearer token and transport layer security (TLS), we now have the tools we need to succeed!!
api_client = k8s_api_client(
endpoint=cluster_data['endpoint'],
token=my_token['status']['token'],
cafile=my_cafile.name
)
api_client.list_namespace()
Go wild
The first time list_namespace()
returned data, I was ecstatic.
Authentication successful! Now the world is your oyster.
All that's left is perusing the API client docs and refreshing AWS creds every so often!
For example, creating a configmap:
my_configmap = kubernetes.client.V1ConfigMap(
api_version='v1',
metadata={'name': 'my-configmap'},
kind='ConfigMap',
data={'my_file.txt': 'mycontent'}
)
api_client.create_namespaced_config_map(namespace='default', body=my_configmap)
# NOTE: to update a configmap, you need to
# use k8s_client.replace_namespaced_config_map
#
# If it already exists, create will give a 409 conflict
Have fun!!
The source for this article is available here (along with a few extra examples)
Outtakes
Yes, there were some outtakes:
- Standing up
mitmproxy
in a docker container and settingkconfig.proxy='http://localhost:8080
andkconfig.verify_ssl=False
before initializingapi_client
so I could see the requests being made to the Kubernetes cluster and verify whether authentication was being sent - Flailing on temp files (today, I learned that python disposes of temp files rather quickly by default)
- Trolling the internet and finding an unfortunate absence of docs on this topic. "Is this obvious to everyone else? Am I doing something wrong?" Classic questions of a lonely explorer.
- Accidentally installing the
aws
package into mypyenv
and breaking my terminal's ability to assume roles when in certain directories.
Programming can be hard! It is so nice to know we are not alone!