Using Podman to deploy an image directly to OpenShift 4
A standard way to deploy an image to OpenShift (3 or 4) is to build the image
on a development workstation, then push it to a public image registry like
Quay (quay.io
). We can use an OpenShift deployment configuration
(DC), or just a command-line operation, to instantiate the image in an
OpenShift Pod, given its registry URI.
While the use of a public registry makes it easy to share images, and to deploy on multiple clusters, it can be inconvenient during the early stages of development and testing. It's often easier simply to push an image from the development workstation directly to the OpenShift cluster. This way it's generally easier to be certain that you're running the latest build of an image, and there are fewer steps in each development cycle.
OpenShift requires some repository to store its images but,
fortunately, there is a Docker registry built in. It's not always very
visible -- its pods and configuration are tucked away in the
openshift-image-registry
namespace.
If we can get access to the built-in repository from outside the OpenShift cluster, then we can deploy to OpenShift directly, without needing any other remote repository.
In this article I will demonstrate how to carry out an image deployment this way, and explain at each step what exactly is happening. However, I won't explain how to build an image, and I certainly won't be explaining how to write code -- I'm assuming the user is familiar with Podman or Docker, and knows how to code the application in a way that will be appropriate to run in a container. There are other articles on my website about these subjects.
Prerequisites
To follow the steps in this article yourself, you'll need the following:
an OpenShift 4 cluster. I've tested the procedure with OpenShift verisons 4.4 - 4.10, but it should work with any 4.x release;
sufficient access rights on the cluster to push images to the built-in image registry. A cluster admin user will be fine; lesser privileges might also suffice, but configuring the user privileges is beyond the scope of this article
a compatible
oc
utility;a working installation of Podman. In principle, Docker should work with the same commands, but I haven't tried it
the
git
utility (although it's perfectly possible to download the required files from GitHub using a browser -- it just takes a bit longer);
Expose the OpenShift image registry
Before proceeding further, we need to ensure that the built-in registry is accessible outside the cluster. If it is accessible, it will be assigned a secured route for encrypted access. You can find out as follows:
$ oc get route default-route -n openshift-image-registry
You don't need to be a privileged user to run this command. If the registry is not exposed, follow the instructions in the OpenShift documentation.
Create the image
For illustration, I will use a simple webservice called
solunar_ws
. This service reports sunrise and sunset times for
specific dates and locations. Neither the function of the application
nor its implementation are
important in the context of this article -- it's just an application
written in C that can be built into a container image
from a Dockerfile using Podman.
$ git clone https://github.com/kevinboone/solunar_ws.git
There's no particular reason to download the complete source -- all you actually need is the Dockerfile. However, there are example YAML files in the source that might be useful later (but aren't used in this article).
Build the image:
$ podman build .
Assuming the build is successful (please let me know if it isn't), you'll have a new image in your local repository. You'll need to tag this image with a useful name, or just make a note of the numeric image ID:
$ podman image list REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> 11797b801cb9 27 seconds ago 10.4 MB ...
10.4Mb is the correct size for this image. The build will also generate a first-stage image which is much larger, but does not need to be deployed.
Prepare the OpenShift imagestream
Ensure you have an OpenShift namespace (or project) to deploy to. I'm using the name "foo" in the following steps, because I have absolutely no imagination. If necessary, create the namespace:
$ oc new-project foo
Create an imagestream to hold the image that will deployed. You don't need to use a privileged user on OpenShift to do this, if you're creating the imagestream in a namespace your user created. If there is a simpler way to do this than creating a YAML file, I don't know what it is.
Create the file is.yaml
with these contents:
kind: ImageStream apiVersion: image.openshift.io/v1 metadata: name: solunar-ws spec: {}
As always with YAML, be careful about the indentation. The only entry in the YML file that will be important later is the name, "solunar-ws". This is the name of the new imagestream.
Deploy the YAML and check the full URI of the new imagestream:
$ oc create -f is.yaml $ oc get is solunar_ws default-route-openshift-image-registry.apps-crc.testing/foo/solunar-ws latest 2 minutes ago
The URI contains the hostname of the route that maps onto the image registry's
service, and the project name -- "foo" in this case. This is the URI that
we'll use as the target of the podman push
operation later.
Assign the registry hostname to an environment variable for convenience;
use oc get route
like this:
REGHOST=`oc get route default-route -n openshift-image-registry \ --template='{{ .spec.host }}'`
or just cut-and-paste the URI
it from the output of oc get is
, whatever's easier.
Push the image
Now we have an imagestream called solunar-ws
in the namespace foo
-- but it's empty.
We need to push the image to the imagestream.
The following steps need to be carried out by a user with permissions to push images to the OpenShift registry. This could be the cluster admin user, but need not be, if you set appropriate roles on another user.
Authenticate Podman against the OpenShift image registry:
$ oc login -u [my_admin_user] $ podman login -u bogus -p $(oc whoami -t) ${REGHOST} Login Succeeded!
The username bogus
here is not an accident. The registry
interprets the password argument as an authentication token (the output of
oc whoami -t
), not a real password. With a token, we don't need a
username. However, the protocol requires a username to be supplied. Although
it's neither documented nor intuitive, any username that contains a colon
(like 'kube:admin') will be rejected -- even though the username is not
actually used. If there's any logic to this behaviour, I don't know what it
is. In any event, you can use any username that does not contain a colon.
If podman login
shows a certificate error, like this:
Error: error authenticating creds for "default-route-openshift-image-registry.apps-crc.testing": pinging docker registry returned: Get https://default-route-openshift-image-registry.apps-crc.testing/v2/: x509: certificate signed by unknown authority
it may be beause the server certificate from the OpenShift router
is not recognized by Podman. The proper solution is probably to
extract the server certificate and pass its location to podman
.
In a development context, though, you can probably use --tls-verify=false
and disable certificate verification. Other Podman commands
in this article may also encounter the same problem.
Once you've successfully authenticated, you should be able
to push the image. There are (at least) two ways to specify
the remote image stream: you can specify it directly as
an argument to podman push
, or you can tag the
image in your local repository. Here's the first method,
which references the local image by its image ID (obtained
from podman image list
):
$ podman push 11797b801cb9 $REGHOST/foo/solunar-ws:latest --tls-verify=false Getting image source signatures Copying blob 50644c29ef5a [======================================] 5.6MiB / 5.6MiB ... Writing manifest to image destination Copying config fed7dc7576 [======================================] 1.5KiB / 1.5KiB Writing manifest to image destination Storing signatures
The remote URI in the podman push
command
is the OpenShift imagestream URI, which
you can get from oc get is
, as I showed earlier.
Note:
Unhelpfully, if you deploy to an incorrect or non-existent imagestream, you'll get an authentication error, which does not help to identify the source of the problem at all.
The second method is to tag the local image with the full URI of the remote repository. You can do this at build time, like this:
$ podman build -t ${REGHOST}/foo/solunar-ws:latest .
Alternatively, you can use podman tag
after the build has
finished. In either case, you can push the image to OpenShift using
the tag:
$ podman push $REGHOST/foo/solunar-ws:latest --tls-verify=false
Both methods have exactly the same effect. Tagging the local image avoids the need to use hexadecimal image IDs, but it can be awkward to deploy to multiple registries, should the need arise.
Create the pod
There's usually no need to work as a privileged user beyond this point.
Now that the image is in the OpenShift registry, you can instantiate it in a pod. There are various ways to do this. The simplest is probably to let OpenShift create all the configuration using defaults.
Within the cluster, the image repository can be addressed by its service
name, which by default is
image-registry.openshift-image-registry.svc
. The default port
is 5000, as is conventional for Docker.
With this information in hand, you can do a default deployment like this:
$ oc new-app --docker-image=\ image-registry.openshift-image-registry.svc:5000/foo/solunar-ws:latest \ --name=solunar-ws -l app=solunar-ws
The image URI here is the same as the one we used before for
podman push
, except that the hostname in the URI
is the service name,
not the external route name. The route name will usually also work --
so long as the registry continues to be exposed as a route.
This oc new-app
command creates a default OpenShift deployment configuration
called solunar-ws
, with an app
tag with the
same name. This tag will be useful if you later want to create a service
to expose the application to clients.
A more sophisticated method of deployment is to define the deployment configration as, say, a YAML file. You could if you wished create a deployment configuration, a service, and a route for the application all in the same YAML file. You can also define readiness and liveness probes to suit the application, if required. However, all these steps are beyond the scope of this article, which deals only with deployment.
The development cycle
The process I've described above might look complicated at first, but
it's all just set-up. Thereafter, deploying a new image consists
of podman build
, podman push
, and
oc rollout latest solunar-ws
.
Closing remarks
In short, the deployment process consists of authenticating Podmand (or Docker) against the OpenShift internal image registry, and pushing the image, either by its ID or its tag. Simple in principle, but there are a few oddities that can cause problems, some of which I've demonstrated in this article.