Deploying containers without a container registry on OpenShift/Kubernetes
2024-04-22 | kubernetes, infrastructure, devops
Use case #
Though you’d usually assume that a Kubernetes or OpenShift environment would always have a container registry available, this may not always be the case in more restricted or highly secured environments. While I wouldn’t recommend a long-lived environment setup without a registry, it is possible to work around this limitation by directly transferring containers to all of your nodes manually.
This post describes an approach to deploying container images in Kubernetes/OpenShift without the use of any registry. Please note that the transfer process would need to be executed once for every container, and for every node that would/could need to run that container. This gets very tedious very quickly, so I recommend scripting automations for this once you’ve got the initial flow down.
What are we trying to do? #
When you schedule a pod, the container runtime interface available on the relevant node is tasked with scheduling that pod. What the CRI does next is as follows:
- Depending on the
ImagePullPolicy, the container runtime will first check if that container is already present on the server - If the image is not present (or as required
ImagePullPolicy), resolve that container artifact from the relevant container registry - Pull the container image to local storage of the node
- Launch the container
Without a container registry, step #3 is impossible, so we need to bring over the container to the container runtime of the relevant node offline. The first graph below describes the standard flow described here, with the steps usually requiring a container registry represented as dotted lines. The second graph describes the workaround flow implemented in this post.
Standard flow #
Alternative flow: Load images directly to CRI #
Instructions #
Note that my examples use podman, but you should be able to replace it at any stage with an equivalent container runtime like docker or containerd. This depends on the setup of your nodes.
Step 1: Get your container image to a jump-host machine using a .tar file
#
In an environment without a container registry, you’ll likely need to transfer your images as .tar files at some stage This requires first saving the image as a .tar file, transferring it, and then loading it into the node’s container runtime.
To save an image as a .tar file:
# on a machine where you have built/pulled the image
podman save -o ./myimage.tar myimage:latest
Transfer it to your node, perhaps with scp, where MYUSER is your username on the node, and MYNODE is the IP address or hostname of your node;
# on a machine where you have built/pulled/saved the image
scp ./myimage.tar $MYUSER@$MYNODE:/home/$MYUSER/myimage.tar
On your node, load to the local container runtime:
# on your node/server
podman load --input myimage.tar
If you now execute podman images, you should be able to find myimage:latest if the loading was successful.
However, this image is still not visible to the node’s container runtime interface - you can check that this is the case by running sudo crictl images. Your image is on the node, but is not yet visible to the CRI and therefore would still not be schedulable.
Step 2: Figure out the container runtime endpoint of your node #
This is documented here in the Kubernetes docs, but boils down to:
# on your node/server
cat /proc/"$(pgrep kubelet)"/cmdline
The endpoint is the value of the --container-runtime-endpoint argument. In my case, using OpenShift CRC, this was /var/run/crio/crio.sock.
Step 3: Transfer the image to the Container Runtime Interface endpoint #
Use the value you obtained for the container runtime endpoint in step 3:
# on your node/server
podman image scp myimage:latest /var/run/crio/crio.sock
Now, this image should be visible to the CRI as well:
sudo crictl images # check output for your image
Testing your setup #
To ensure you do not accidentally pull an image, set your test workload’s imagePullPolicy to Never, like this:
apiVersion: v1
kind: Pod
metadata:
name: my-test-image
spec:
containers:
- name: my-test-image
image: myimage:latest
imagePullPolicy: Never # this is the important bit
If the referenced image is not available, the pod will end up in ErrImageNeverPull. After finishing the steps above, it should pick up the available image in a few seconds and move to a Running status afterwards.