General Purpose Containers in Kubernetes¶
Sometimes you just need to have a quick solution for a simple task.
Up until recently I had the habit of creating dedicated containers for each specific Pod or Deployment I needed in Kubernetes.
But I found this approach to be a complete overkill for smaller tasks that usually just consist of a simple Python or shell script. The base image, tooling etc. are always the same.
Through some experimentation, and because I love using Python, I created a general purpose Python container that I can use for many different smaller tasks without needing to create new container images for each "little project".
Towards a solution...¶
Typically, containers can be deployed in Kubernetes (in a Pod) and without any special or fancy configuration, it will just run the default CMD
as defined in the Dockerfile.
With this in mind, it is possible to quickly serve static content through nginx
, as can be seen from the following example (credit to zjor on GitHub):
# Adapted from https://gist.github.com/zjor/25c79bc5792ca32805cd6f50d180952e
---
apiVersion: v1
kind: ConfigMap
metadata:
name: config-map
namespace: testing
data:
index.html: |
<!DOCTYPE html>
<html>
<body>
<h1>Hello from the Cloud</h1>
<script>
const queryDict = {};
location.search.substr(1).split("&").forEach(function(item) {queryDict[item.split("=")[0]] = item.split("=")[1]})
document.querySelector("h1").innerText=`Hello ${queryDict["name"]} from the Cloud`;
</script>
</body>
</html>
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
namespace: testing
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
restartPolicy: Always
containers:
- image: nginx:stable-alpine
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
volumeMounts:
- name: index-volume
mountPath: /usr/share/nginx/html
volumes:
- name: index-volume
configMap:
name: config-map
---
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: nginx-svc
namespace: testing
spec:
ports:
- name: "80"
port: 80
targetPort: 80
selector:
app: nginx
For a quick deployment, follow these steps (you will need two terminals):
Terminal 1:
# If not already created earlier, create a testing namespace
kubectl create namespace testing
# Deploy
kubectl apply -f - <<EOF
# Content from above manifest...
EOF
# Port Forward
kubectl port-forward $(kubectl get pods -lapp=nginx --output jsonpath='{.items[0].metadata.name}' -n testing) -n testing 9090:80
In terminal two the static content can be retrieved:
curl -s http://localhost:9090/
Or. open this link in a web browser.
To cleanup, stop the port forwarding and run the following:
kubectl delete namespace testing
Therefore, the nginx
container is configured to automatically start the HTTP server and serve what ever is in the directory /usr/share/nginx/html
. If you use ConfigMaps and Volumes, you can easily serve static pages from Kubernetes.
What is even better, is that you can have multiple of these static web page serving Pods, all serving different content while they are all using the same base container image.
Pretty cool!
A more advanced use case¶
Serving static content is one thing, but what if I need a REST API with some logic, or even some interaction with the Kubernetes API?
And that is why I created my general purpose Python container.
In this repository there is an example using a Python script with integration to the Kubernetes API, which will return a list of namespaces (just as a test).
It uses the same basic principle of the nginx
example, but there is no default CMD
option, and therefore in the deployment, the container has a specific command
and arguments to start the REST API server. The source code is defined in a ConfigMap.
It should be really easy to see how more complex solutions can be devised through the use of volumes. If you have a packaged Python module in a ZIP or other compressed format, the started ConfigMap can easily just contain a script to unpack and install the package and then run the main application.
Advantages and Disadvantages¶
There are a lot of advantages, and I think the key ones for me include the following:
- Convenience and fast time to solutions
- Simpler repositories without complex build pipelines to create dedicated images for each project.
- You can still separate your Kubernetes manifests and content (or source files) if you need to and through ConfigMaps and Volumes you can tie these two worlds together. Of course you can also keep everything together.
- The less tools (or tooling) and pipelines you have the less complexity there is and less opportunity for mistakes or errors.
- Long term maintainability is easier if you have overall less images. As an example here, since we use ArgoCD a lot, I tend to use the same image of Redis when I have applications that also require Redis. I let ArgoCD determine the Redis version and everything else just re-use that same image.
The main disadvantages are:
- Larger images, if you plan to use images with various tools and libraries installed to re-use in a larger variety of use cases. This is certainly the case with my Python container. But in all honesty, it does not really impact performance at runtime and the storage is inexpensive. Also, images like
nginx
are still really small (< 100 MB). - Larger images contains potentially more vulnerabilities which is an important consideration for environments that require a higher level of security and robustness against cyber attacks.
- The shared image is in effect a single point of failure. If the image itself contains some error that prevents it from starting up at all, all deployments that depend on that image will effectively be broken.
Conclusion¶
Kubernetes and containers gives us a lot of flexibility and using a shared container image to run various workloads is definitely a very handy tool to have. However, always be aware of the risks and make sure you make informed decisions before blindly just using a shared container for everything.
Tags¶
kubernetes, python