At work last week, I finally solved an issue by writing some code, and I wanted to explain why I wrote it.
At it’s core, Kubernetes is an orchestrator which runs “Container Images”, which are structured filesystem snapshots, taken after running individual commands against a base system. These container images are stored in a container registry, and the most well known of these is the Docker registry, known as Docker Hub.
A registry can be public, meaning you don’t need credentials to get any images from it, or private. Some also offer a mixed-mode where you can make a certain number of requests without requiring authentication, but if you need more than that amount of requests, you need to provide credentials.
During the build-out of a new cluster, I discovered that the ECR (Elastic Container Registry) from AWS requires a new type of authentication – the Kubelet Credential Provider, which required the following changes:
- In
/etc/sysconfig/kubelet
you provide these two switches;--image-credential-provider-bin-dir /usr/local/bin/image-credential-provider
and--image-credential-provider-config /etc/kubernetes/image-credential-provider-config.json
. - In
/etc/kubernetes/image-credential-provider-config.json
you provide a list of registries and the credential provider to use, which looks like this:
{
"apiVersion": "kubelet.config.k8s.io/v1",
"kind": "CredentialProviderConfig",
"providers": [
{
"name": "binary-credential-provider-name",
"matchImages": [
"example.org",
"registry.*.example.org",
"*.registry.*.example.org"
],
"defaultCacheDuration": "12h",
"apiVersion": "credentialprovider.kubelet.k8s.io/v1"
}
]
}
- Downloading and placing the credential provider binary into the
/usr/local/bin/image-credential-provider
path.
The ECR Credential Provider has it’s own Github repostitory, and it made me think – we’ve been using the “old” method of storing credentials using the containerd configuration file, which is now marked as deprecated – but this means that any changes to these credentials would require a restart of the containerd service (which apparently used to have a big impact on the platform), but this new ECR provider doesn’t.
I decided to write my own Credential Provider, following the documentation for the Kubelet Credential Provider API and I wrote it in Python – a language I’m trying to get better in! (Pull requests, feature requests, etc. are all welcome!)
I will confess I made heavy use of ChatGPT to get a steer on certain aspects of how to write the code, but all the code is generic and there’s nothing proprietary in this code.
Using the Generic Credential Provider
- Follow the steps above – change your Kubernetes environment to ensure you have the kubelet configuration changes and the JSON credential provider configuration put in the relevant parts of your tree. Set the “matchImages” values to include the registry in question – for dockerhub, I’d probably use
["docker.io", "*.docker.io"]
- Download the generic-credential-provider script from Github, put it in the right path in your worker node’s filesystem (if you followed my notes above it’ll be in
/usr/local/bin/image-credential-provider/generic-credential-provider
but this is *your* system we’re talking about, not mine! You know your build better than I do!) - Create the /etc/kubernetes/registries directory – this can be changed by editing the script to use a new path, and for testing purposes there is a flag
--credroot /some/new/path
but that doesn’t work for the kubelet configuration file. - Create a credential file, for example,
/etc/kubernetes/registries/example.org.json
which contains this string:{"username":"token_username","password":"token_password"}
. [Yes, it’s a plaintext credential. Make sure it’s scoped for only image downloads. No, this still isn’t very good. But how else would you do this?! (Pull requests are welcomed!)] You can add aduration
value into that JSON dictionary, to change the default timeout from 5 minutes. Technically, the default is actually set in/etc/kubernetes/image-credential-provider-config.json
but I wanted to have my own per-credential, and as these values are coming from the filesystem, and therefore has very little performance liability, I didn’t want to have a large delay in the cache. - Test your credential! This code is what I used:
echo '{
"apiVersion": "credentialprovider.kubelet.k8s.io/v1",
"kind": "CredentialProviderRequest",
"image": "your.registry.example.org/org/image:version"
}' | /usr/local/bin/image-credential-provider/generic-credential-provider
which should return:
'{"kind": "CredentialProviderResponse", "apiVersion": "credentialprovider.kubelet.k8s.io/v1", "cacheKeyType": "Registry", "cacheDuration": "0h5m0s", "auth": {"your.registry.example.com": {"username": "token_username", "password": "token_password"}}}'
You should also see an entry in your syslog service showing a line that says “Credential request fulfilled for your.registry.example.com
” and if you pass it a check that it fails, it should say “Failed to fulfill credential request for failure.example.org
“.
If this helped you, please consider buying me a drink to say thanks!
Featured image is “Padlock on computer parts” by “Marco Verch Professional Photographer” on Flickr and is released under a CC-BY license.