First look at OpenShift
My company has set its (internal) Cloud strategy as:
- Use Docker containers as the standard application packing mechanism
- Use Kubernetes as the standard server runtime environment
- Use OpenShift as the supported distribution of Docker and k8s
So, having got a simple proof of concept app set up using Docker and k8s on Azure, it’s time to look into how to get the same app running in-house on OpenShift.
.NET Core support in OpenShift
.NET Core 2.0 support was added to Red Hat (RHEL and OpenShift) in August 2017 and an OpenShift blog article describes how to use it. Currently .NET code will run on Linux only, Windows container support is due to be added some time in 2018.
(We were planning to target .NET Core 2.0 on Linux as much as possible, so this is fine for us.)
Source-to-image (S2I)
In order to run a .NET Core 2.0 container in OpenShift there are a number of “guidelines” that should be followed. The recommended way is to have OpenShift build your code into an image itself, using Source-to-image (S2I). You merely tell OpenShift to create a new application from the .NET Core 2.0 base image, giving the URL of your git repository. It will build, test and create a OpenShift-compatible Docker image for you.
For example: oc new-app dotnet/dotnet-20-rhel7~https://github.com/my/git/repo.git
.
I couldn’t get this to work in-house (translation: I ran out of patience setting up CA certificates etc) so I decided to build my own simple Docker image from the OpenShift .NET Core runtime base image.
Details of the .NET Core S2I images (for .NET Core 1.0, 1.1, 2.0) are on github. I used the .NET Core 2.0 “runtime” image, which allows you to build an OpenShift-compatible image from pre-built .NET Core binaries, OpenShift won’t build anything for you.
Building the Docker image
The details are all on the github link, but in a nutshell:
- Restore nuget packages, ensuring the RHEL runtime is targeted
$ dotnet restore -r rhel.7-x64
- Build and publish the app, targeting the same framework
$ dotnet publish -f netcoreapp2.0 -c Release -r rhel.7-x64 --self-contained false /p:PublishWithAspNetCoreTargetManifest=false
(more about the ASP.NET Core implicit store) - Build the Docker image as normal using a modified Dockerfile
FROM dotnet/dotnet-20-runtime-rhel7 ADD bin/Release/netcoreapp2.0/rhel.7-x64/publish/. . CMD [ "dotnet", "FirstDockerApp.dll" ]
A couple things to note here:
- I pulled the base image with
docker pull registry.access.redhat.com/dotnet/dotnet-20-runtime-rhel7
and re-tagged to remove theregistry.access.redhat.com
- The base image runs ASP.NET Core on port 8080, not port 80. This is because OpenShift doesn’t allow privileged access to containers and any ports < 1024 are unavailable
Running locally
The OpenShift-compatible image can be tested locally as normal, I just need to remember to map port 8080 instead of port 80: docker run -d --rm -p 3333:8080 first-docker-app
Pushing the image to an OpenShift repository
First I need to push my image to the local OpenShift registry. I do this by logging into the UI and creating a new “image stream”. The UI tells me what commands I need to use to push the image, but in brief:
- Tag the docker image with the appropriate registry and project name
$ docker tag first-docker-app your.registry.net:5000/project-name/first-docker-app:0.1
- I’m giving an explicit version for the tag rather than relying on
latest
as this seems to be best practice - The OpenShift UI gives instructions on how to log in to the docker registry
$ docker login -p some-open-shift-token -u unused your.registry.net:5000
Deploying on OpenShift
Using the OpenShift UI I created a new project for my image. To deploy my image I found that the oc
command-line tools were needed; the OpenShift UI’s “help” link explains how to download this and log in.
I used the same k8s manifest file which I used when playing with AKS, but I needed to make a few changes:
- The docker image needs to point to my in-house repository
- The port numbers in both the deployment and service need to be changed to 8080
- The service type I had to change from
LoadBalancer
toClusterIP
This last change means that the service is only reachable inernally, i.e. to the rest of the k8s cluster. I haven’t researched too much into it yet, but it seems that it is better to create an internal service, then a Route (Ingress in k8s) so that it can be reached from the outside world.
To create the OpenShift deployment, I ran $ ./oc create -f k8s-all-in-one.yml
and it span up my deployment in OpenShift! I then created a route using the OpenShift UI, and my image is reachable via a browser.
tl;dr
A number of tweaks are needed to run on OpenShift but the same basic principles apply.
cloud (15) docker (9) netcoreapp (10)