Kubernetes Openshift Compatibility
Openshift comes with enforced security context design which aims to solve security issues that normal Kubernetes cluster ignores. In a non-prod environment, the default Kubernetes approach is capible to deploy simple application and providing access to the service, but such design often introduce challenges to enterprise companies like banks or teleco which cause them hasitate to migrate data to the cloud.
Build A Openshift Compatible Image
Normal docker image which uses root level action like following would cause trouble in Openshift:
docker FROM ubuntu:18.04 RUN echo "echo 'Starting Shell Script...'" > shell.sh RUN echo "rm /etc/passwd && echo '-> execution of rm command successful.' || '-> execution of rm command failed.'" >> shell.sh RUN echo "echo 'Ending Shell Script...'" >> shell.sh RUN chmod +x shell.sh CMD [ "sh", "shell.sh" ]
The problem here is that
rm /etc/passwd tries to delete root file
passwd, in a local docker or Kubernetes default enviroment, this action is fine to use, because Docker doesn’t care and Kubernetes default image runuser is
root. But in Openshift, the default runuser is
And it’s why when you try to run it in Openshift, it’ll give error:
oc get pods NAME READY STATUS RESTARTS AGE root-app-1-lljqm 0/1 CrashLoopBackOff 1 5s Starting Shell Script... rm: cannot remove '/etc/passwd': Permission denied shell.sh: 2: shell.sh: -> execution of rm command failed.: not found Ending Shell Script...
This enforces user to build up a possitive habit to properly secure their images from very low lever. It means that you need to firstly assess whether your container image requires root access and modify the image not to run as root. If the image does not require any root user access, the best practice is to specify a USER who is non-root in your Dockerfile as shown.
FROM <base-image> .. .. USER <user-id> .. .. CMD [.., ...]
Pods run in an OpenShift cluster as arbitrary user IDs. All of these user IDs are members of the root group. Any files user creates will belong to root Group, the solution is to change ownership of folder users interact with.
## 4. Two-stage image builds (stage 1: builder image) FROM maven:3.6.3-jdk-11 as builder WORKDIR /app COPY pom.xml . RUN mvn -e -B dependency:resolve COPY src ./src RUN mvn clean -e -B package ## 4. Two-stage image builds (stage 2: deployment image) ## 1. Universal Base Image (UBI) FROM registry.access.redhat.com/ubi8/openjdk-11:1.3-15 ## 2. Non-root, arbitrary user IDs USER 1001 # Or USER default; or nothing, the UBI already set the user ## 6. Image identification LABEL name="my-namespace/my-image-name" \ vendor="My Company, Inc." \ version="1.2.3" \ release="45" \ summary="Web search application" \ description="This application searches the web for interesting stuff." USER root ## 7. Image license COPY ./licenses /licenses ## 5. Latest security updates RUN dnf -y update-minimal --security --sec-severity=Important --sec-severity=Critical && \ dnf clean all USER default # Or USER 1001 ## 3. Group ownership and file permission, make is usable for both openshift and kubernetes RUN chown -R 1001:0 /some/directory && \ chmod -R g=u /some/directory COPY --from=builder /app/target/*.jar app.jar EXPOSE 8080 CMD ["java", "-jar", "app.jar"]
Two Stage Image Builds
From the above case example we can see here the problem/security concern is all caused by user interaction with file system. If we can seperate them from basic image actions, then we don’t need to worry about compatibility and security anymore. Multi-stage image builds is one of the solutions for this purpose. We can make all user interaction in the base image, and then import it in next image which only includes app run related cmds and build/run on cloud.
FROM golang:1.7.3 AS builder WORKDIR /go/src/github.com/alexellis/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /go/src/github.com/alexellis/href-counter/app . CMD ["./app"]