2.3.1 OpenShift image requirements
Task 2.3.1.1: Additional requirements
OpenShift has additional security features enabled in comparison to Docker or a vanilla Kubernetes plattform. The most relevant mechanism are prevention of root user, SELinux enabled and arbitrary user ids.
This adds additional requirements to the container images that will be deployed to OpenShift. This lab shows how to deal with them.
Task 2.3.1.2: Demo application
We use a Go application running a HTTP server listening on port 8080. Every request is logged with the actual time and the client IP address. The log file is placed under /home/golang/hello-go.log
.
This writing to a file helps us testing file access and write permissions inside a container.
Go source
package main
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"time"
)
var logFile = "/home/golang/hello-go.log"
func main() {
_, err := createFile(logFile)
if err != nil {
panic(err)
}
http.HandleFunc("/", HelloServer)
http.ListenAndServe(":8080", nil)
}
func HelloServer(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
appendToFile(r.RemoteAddr)
}
func createFile(p string) (*os.File, error) {
if err := os.MkdirAll(filepath.Dir(p), 0644); err != nil {
return nil, err
}
return os.Create(p)
}
func appendToFile(remoteAddr string) {
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
}
defer f.Close()
line := fmt.Sprintf("%s: Request from: %s\n", time.Now().String(), remoteAddr)
if _, err := f.WriteString(line); err != nil {
log.Println(err)
}
}
Dockerfile
The Dockerfile defines a multi-stage build. The build is done in several stages using different containers.
- use a Go container to build the Go application
- copy the go binary from the build to a minimal alpine image
FROM golang:1.14-alpine as builder
COPY main.go /opt/app-root/src
RUN env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o go-hello-world-app .
FROM alpine
COPY --from=builder /opt/app-root/src/go-hello-world-app /home/golang/
EXPOSE 8080
ENTRYPOINT /home/golang/go-hello-world-app
Task 2.3.1.3: Deploy application
The application is available on Docker Hub: chrira/container-openshift-ifie
This is a list with all needed OpenShift resources.
apiVersion: v1
kind: List
metadata: {}
items:
- apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
labels:
app: container-openshift-ifie
name: container-openshift-ifie
spec:
lookupPolicy:
local: false
tags:
- from:
kind: DockerImage
name: chrira/container-openshift-ifie
importPolicy: {}
name: latest
referencePolicy:
type: Source
- apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
creationTimestamp: null
labels:
app: container-openshift-ifie
app.kubernetes.io/component: container-openshift-ifie
app.kubernetes.io/instance: container-openshift-ifie
name: container-openshift-ifie
spec:
replicas: 1
selector:
deploymentconfig: container-openshift-ifie
strategy:
resources: {}
template:
metadata:
creationTimestamp: null
labels:
deploymentconfig: container-openshift-ifie
spec:
containers:
- image: 'container-openshift-ifie:latest'
name: container-openshift-ifie
ports:
- containerPort: 8080
protocol: TCP
resources: {}
test: false
triggers:
- type: ConfigChange
- type: ImageChange
imageChangeParams:
automatic: true
containerNames:
- container-openshift-ifie
from:
kind: ImageStreamTag
name: 'container-openshift-ifie:latest'
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: container-openshift-ifie
app.kubernetes.io/component: container-openshift-ifie
app.kubernetes.io/instance: container-openshift-ifie
name: container-openshift-ifie
spec:
ports:
- name: 8080-tcp
port: 8080
protocol: TCP
targetPort: 8080
selector:
deploymentconfig: container-openshift-ifie
Now it is time to deploy the image in our OpenShift cluster.
oc apply -f https://raw.githubusercontent.com/puzzle/amm-techlab/main/content/en/docs/02.0/additional/ocp-image-requirements/application-infrastructure.yaml
imagestream.image.openshift.io/container-openshift-ifie created
deploymentconfig.apps.openshift.io/container-openshift-ifie created
service/container-openshift-ifie created
Verify if all resources are created
oc get all
NAME READY STATUS RESTARTS AGE
pod/container-openshift-ifie-1-d8rln 0/1 CrashLoopBackOff 6 9m19s
pod/container-openshift-ifie-1-deploy 1/1 Running 0 9m22s
NAME DESIRED CURRENT READY AGE
replicationcontroller/container-openshift-ifie-1 1 1 0 9m22s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/container-openshift-ifie ClusterIP 172.30.40.136 <none> 8080/TCP 9m22s
NAME REVISION DESIRED CURRENT TRIGGERED BY
deploymentconfig.apps.openshift.io/container-openshift-ifie 1 1 1 config
You can see that your Deployment isn’t ready and the corresponding Pod has the status CrashLoopBackOff
Troubleshoot
Let’ figure out what happened. First let’s examine the Pods log
oc logs container-openshift-ifie-1-d8rln
panic: open /home/golang/hello-go.log: permission denied
goroutine 1 [running]:
main.main()
/opt/app-root/src/main.go:18 +0x126
As we can see in the log, there is a permission denied error when we try to create our log file. The reason is because OpenShift will run by containers as non root user and we don’t have permissions as non root user to write into the /home/golang
directory.
Task 2.3.1.4: Add a user to the container
So let us extend the image with a specified user.
For that we add a BuildConfiguration with a new Dockerfile extending the used image.
apiVersion: v1
kind: List
metadata: {}
items:
- apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
creationTimestamp: null
labels:
app: container-openshift-ifie
build: container-openshift-ifie
name: container-openshift-ifie-original
spec:
lookupPolicy:
local: false
tags:
- from:
kind: DockerImage
name: chrira/container-openshift-ifie
importPolicy: {}
name: latest
referencePolicy:
type: Source
status:
- apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
creationTimestamp: null
labels:
app: container-openshift-ifie
build: container-openshift-ifie
name: container-openshift-ifie
spec:
nodeSelector: null
output:
to:
kind: ImageStreamTag
name: container-openshift-ifie:latest
resources:
limits:
cpu: 100m
memory: 500Mi
requests:
cpu: 10m
memory: 10Mi
source:
dockerfile: |-
FROM chrira/container-openshift-ifie:latest
USER golang
type: Dockerfile
strategy:
dockerStrategy:
from:
kind: ImageStreamTag
name: container-openshift-ifie-original:latest
type: Docker
triggers: []
Create BuildConfiguration:
oc apply -f https://raw.githubusercontent.com/puzzle/amm-techlab/main/content/en/docs/02.0/additional/ocp-image-requirements/buildconfig.yaml
imagestream.image.openshift.io/container-openshift-ifie-original created
buildconfig.build.openshift.io/container-openshift-ifie created
We do not have trigger, start the build manually:
oc start-build container-openshift-ifie
build.build.openshift.io/container-openshift-ifie-1 started
We still get same error.
panic: open /home/golang/hello-go.log: permission denied
goroutine 1 [running]:
main.main()
/opt/app-root/src/main.go:18 +0x126
So what is the reason for this? We already specified the golang
user in the Dockerfile
. So technically the user should have access to its own home directory. Even if we specify a user with the USER directive in a Dockerfile, OpenShift is going to ignore it. We need to fix the permissions for this particular user.
Task 2.3.1.5: Fix permissions
Even if we specify a user with the USER directive in a Dockerfile, OpenShift is going to ignore it. It starts the container with an arbitrary userID and group 0 (root group).
We need to extend the Dockerfile
to give group 0 access.
FROM chrira/container-openshift-ifie:latest
RUN chgrp -R 0 /home/golang && \
chmod -R g+rwX /home/golang
USER golang
We add additional commands before the USER directive. First we add the home directory to the group 0 (root group) with the chgrp
command. Last step is adding read/write/execute permission to the group. We do this with the chmod
command. Now the application should be able to access the home directory and write the log file.
Let’s update the BuildConfiguration and build and deploy the app again.
oc apply -f https://raw.githubusercontent.com/puzzle/amm-techlab/main/content/en/docs/02.0/additional/ocp-image-requirements/buildconfig-permissions.yaml
oc start-build container-openshift-ifie
oc get pods
Task 2.3.1.6: Create route
oc create route edge --service=container-openshift-ifie
oc get route
Further reading
Red Hat blog post: