Container guideline

Building and Publishing AppAgile-Images

General Guidelines and Sources

Exemplary process

  • Check if the desired image is available by Red Hat (https://access.redhat.com/containers)
  • Study the setup guide of the respective application so that no important steps are missed
  • Search for a Docker image of the desired software on Docker Hub. Check if there is an official docker image(with the tag ‘official’). If not, the one with the most stars and/or pulls might be the most suitable. The descriptions for images on Docker Hub usually provide a link to the Github repository with the Dockerfile and required sources.
  • Usually, images on Docker Hub are not based on RHEL or even CentOS. Therefore a rebuild based on RHEL (or CentOS for testing purposes) is likely required. Here, the steps can often be adopted in a very similar way from the example.
  • Build and test the images locally.
  • If the test build was done with CentOS, don’t forget to base the image back on RHEL before pushing the result!
  • Create OpenShift template(s).
  • Push the results to GitLab.
  • Create a Jenkins pipeline job and build the image.
  • Perform the tests in OpenShift.
  • If successful, deliver the working image and templates to the requester

Components of an AppAgile Dockerfile

  • FROM registry.access.redhat.com/rhel7/rhel:...
    • base your image on a suitable version of RHEL
  • ENV ...
    • put important constants in environment variable declarations at the beginning of the Dockerfile as to highlight them
    • e.g. application version to install, install directory, config directory, etc.
  • LABEL ...
    • label the image to provide metadata
    • e.g. labels maintainer, io.k8s.description
  • RUN rpm --import file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release && yum-config-manager --disable "*" && yum-config-manager --enable rhel-7-server-rpms
    • import the RHEL license
    • enable the RHEL server rpms repository
  • RUN yum update -y --setopt=tsflags=nodocs && yum install -y --setopt=tsflags=nodocs something && yum clean all
    • update the installed libries
    • set the “nodocs” flag on update and install operations to keep the image smaller
    • run yum clean all after install and update operations to remove unwanted temporary files and keep the image smaller
    • Try to reduce the occurence of the command RUN, as each command adds a layer to the docker image. Concatenating installation commands with ‘&&’ is recommended
  • ENTRYPOINT ["/entrypoint.sh"]
    • provide an entrypoint script which performs necessary operations at container startup
    • the entrypoint should end with an exec "$@" to allow the container to start with an arbitrary command
  • EXPOSE 8080
    • expose necessary ports
  • VOLUME /var/application-data
    • expose directory paths for potential volume mounting
  • CMD ["/opt/application/bin/run", "--config=/var/application-data/config/config.json"]
    • provide a standard command for the container to start with in array form

Components of an AppAgile OpenShift template

Use templates to define objects needed for the application. These objects can be ImageStreams, Routes, Services, DeploymentConfigs, BuildsConfigs, PersistentVolumeClaims…
Here is a list of common objects used in application.
Service
– list the service name, ports for this service and the selector that connects them with the pods they are representing
Route
– Add a route object for applications that need to be accessed from external network
– Use the url naming convention allowed on the cluster. Eg. terminate the route with *.appad3.tsi-af.de" on Masterd3 DEV environment
– Use a parameter to request the ‘url’ value.
PersistentVolumeClaim
– Try to avoid using ephemeral containers in production environment
– Define the storage size using the proper units. Eg. 5Gi for 5 gigabytes
– Identify how the persistent volumes are provisioned, and choose the access mode accordingly. Eg. ‘ReadWriteOnce’ only allows pods from the same node to access the volume
– Use a parameter to request the PersistentVolume size.
ImageStream
– Use ‘dockerImageRepository’ spec to pull all tags under the repository
– Use a parameter to let the user provide docker image repository path
DeploymentConfig
– Choose deployment strategy. (If the application has error running two instances of the software during Rolling strategy, it is possible to use Recreate strategy)
– Use ImageChangetrigger to automatically start deployment when an image is pulled in ImageStream. Set the option automatic to true
– Make sure to define environment variables under containers in deployment config. Otherwise, the parameters used in the template will not be accessible from Dockerile or Entrypoint.
– Define volumeMounts to map container volumes for persistence. Identify which directory is to persist (mountPath) from the Dockerfile.
– Also add a resources section to allow the user to choose memory and cpu size.
Parameters
– For portions of the template that require user input, use parameters.
– To force the user to enter parameter values, remove the value parameter and set requiredto true.
– In the description use example values to show how the parameters should be entered. Eg. For parameters that require unit values

Git repository

Creation and naming

  • Create a repository for the new image in the GitLab.
  • Select the project path of the “OSE3” group.
  • Name the repository according to the following schema: appagile-[manufacturer-]?-product[-edition]?, eg. appagile-atlassian-jira-core
  • Select the visibility level “Internal”.

Structure and components

For consistency the following directory structure in the Git repositories is advised:
.
├── image
│   ├── conf
│   │   └── [configuration assets]
│   │ - required for the configuration of the application or the operating system
│   │ - get included in the image
│   ├── Dockerfile
│   └── scripts
│   └── [scripts]
│ - get included in the image
│ - e.g. entrypoint script
├── Jenkinsfile
│ - the configuration for the Jenkins pipeline plugin
├── ose-artifacts
│ └── [OpenShift templates]
│ - templates for the deployment in OpenShift
└── Readme.md
- Markdown-formatted readme

Committing

  • Only commit (basically) working states.
  • Group related changes in a commit. Try to avoid committing unrelated changes at once.
  • Choose meaningful commit messages.

Documentation

  • Provide a readme file at least at the top level of the git repository
  • The file should contain information about
    • the application version
    • deployment procedure
    • dependencies
    • known issues
    • configuration that can be provided via external measures like environment variables, config maps, secrets
  • Keep the documentation up-to-date with changes that might be applied to the described artifacts

Jenkins

Pipeline

Create a new Jenkins Pipeline
– choose a pipeline name. Eg for appagile project appagile-pojectname
– Under Build Triggers choose This project is parameterized, and add the following parameter provide an option to choose image tag.
– name = IMAGE_TAG
– Default Value = latest
– Description = Specify tag for docker image
– Under Pipeline choose Pipeline Script from SCM, and add the following parameter provide an option to choose image tag.
– Choose git as SCM and provide the git repository URL and credentials that will allow jenkins to pull from gitlab
– Select the git branch, provide the Jenkinsfile script path in gitlab and click on save
– A new build can be started by clicking on Build with parameters. This will build the docker image and push it to the Docker Repository Provided in Jenkinsfile.

Versioning/Tagging

  • To avoid confusion about the contents and compatibility of different versions of an image, it is important to adhere to a consistent versioning system when tagging images
  • The image tag should not necessarily be the version of the software that is included in the image, as this is likely stable during updates of the image
  • Semantic Versioning is a well-tried approach at software versioning. Its version numbers consist of three levels:
    • Major version, which should be incremented when incompatible changes were made
    • Minor version, which should be incremented when fuctionality was added in a backwards-compatible manner
    • Patch version, which shoule be incremented when bugs were fixed in a backwards-compatible manner
  • Backwards-compatibility might be measured against the ability to still deploy the image using the same templates as before

Pushing the image to a public registry

Central Registery (masteroc)

  • Create a new project in central Registry on OTC: https://masteroc.tsi-af.de:8443/console/. (Only a cluster admin can create new projects)
  • Add a Service Account:
    • “$ oc create -f – << EOF
      apiVersion: v1
      kind: ServiceAccount
      metadata:
      name: test-pusher
      EOF”
  • Add image-builder role to the service account
    • $ oc policy add-role-to-user system:image-builder system:serviceaccount:test-session:test-pusher
  • Get the token name from service account
    • oc describe sa test-pusher
  • Get the token that will be used to push images to this registry.
    • oc describe secret test-pusher-token-sr1d4 (Use this token value to login and push images from other repositories)
  • Create an Image Stream
    $ oc create -f - << EOF
    apiVersion: v1
    kind: ImageStream
    metadata:
    name: test-stream
    annotations:
    description: my test image stream
    EOF

Build Server

  • From the build server, login to the Central Registry
    • $ docker login --username=anyname --email=some@mail.de registry-appaoc.tsi-af.de (without ‘https://’ and Port)
    • The password is the token secret value
    • If login succeeds without asking password, it’s better to logout and login again because the last login might be using a different secret.
      • $ docker logout registry-appaoc.tsi-af.de
  • Tag the required image with the destination repository name
    • $ docker tag local-name:localtag registry-appaoc.tsi-af.de/test-session/test-stream:anytag
  • Push the image
    • $ docker push registry-appaoc.tsi-af.de/test-session/test-stream:anytag

Delivery of information and templates

  • Create a service account for image pulling (refer to the procedure used above for creating a service account)
    • Add image-puller role to the service account
    • Get the token name from service account
    • Get the token value from service account secret
  • Send an E-Mail to the customer with the following information:
    • Fully qualified addresses of the images (registry URL + namespace + image name + tag, e.g. registry-appaoc.tsi-af.de:443/namespace/imagename:1.0.0)
    • Token to be used for pulling images
    • Templates as file attachments

Security Context in Openshift

If the image contains directories and files that may be written to or if it contains files that needs to be executed, it is important to note the following points:

  • Contrary to a container started in a clean docker engine, containers in Openshift does not start with root user ID.
  • By default Openshift runs containers using arbitrarily assigned user ID. The user ID is granted dynamically
  • In Openshift the container user is always a member of the root group. So if read/write/execute permission of certain files is given to the root group, the user will be able to perform these tasks
  • Unlike root user, root group does not have any special permissions so there it does not pose a security concern.

Directory and File Permission

To give users in root group permission to a certain directory, add these lines in the Dockerfile
RUN chgrp -R 0 /some/directory \
&& chmod -R g+rwX /some/directory

This will give the container user (which is a member of the root group), read-write-execute permissions on this directory