OpenShift-ify the conference management system “frab”
frab (https://github.com/frab/frab) is an opensource conference management system. A docker-ized version is already provided upstream. However I wanted to have it run on OpenShift without the need of any additional security context constraints (SCC).
Without any change to the container image, you would need the anyuid
SCC added to the serviceaccount which runs the pod because the container is using a specific user. I wanted to deploy frab on a managed OpenShift Container Platform where I don’t have the permissions to do that. Furthermore it shouldn’t be necessary.
So, basically what I am doing is to build the container image from source code, staying as close as possible to the origial.
This is the Dockerfile I came up with (which probably can be enhanced, but for now it’s good enough for me):
FROM ruby:latest
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs file imagemagick git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
ENV FRAB_HOME=/opt/frab/app
ENV HOME=$FRAB_HOME
VOLUME $FRAB_HOME/public
COPY . $FRAB_HOME
RUN mkdir -p $FRAB_HOME
RUN chgrp -R 0 $FRAB_HOME && \
chmod -R g=u $FRAB_HOME
RUN chmod g=u $FRAB_HOME /etc/passwd
USER 10001
WORKDIR $FRAB_HOME
RUN bundle install
RUN cp config/database.yml.template config/database.yml
EXPOSE 3000
ENV RACK_ENV=production \
SECRET_KEY_BASE=asdkjf3245jsjfakjq435jadsgjlkq4j5jwj45jasdjvlj \
FRAB_HOST=localhost \
FRAB_PROTOCOL=http \
RAILS_SERVE_STATIC_FILES=true \
RAILS_LOG_TO_STDOUT=true \
CAP_USER=frab \
FROM_EMAIL=frab@localhost \
SMTP_ADDRESS=172.17.0.1 \
SMTP_PORT=25 \
HOME=$FRAB_HOME \
DATABASE_URL=sqlite3://localhost/opt/frab/app/data/database.db
ENV GEM_HOME="/usr/local/bundle"
ENV PATH $GEM_HOME/bin:$GEM_HOME/gems/bin:$PATH
CMD ["./docker-cmd.sh"]
Alongside with that, these are the OpenShift resources I am using:
- in case you want to build the image in OpenShift - BuildConfig:
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
labels:
build: frab
name: frab
namespace: frab
spec:
failedBuildsHistoryLimit: 5
nodeSelector: null
output:
to:
kind: ImageStreamTag
name: 'frab:latest'
postCommit: {}
resources: {}
runPolicy: Serial
source:
binary: {}
type: Binary
strategy:
dockerStrategy:
env:
- name: BUNDLE_WITHOUT
value: 'development:test:mysql:sqlite3'
type: Docker
successfulBuildsHistoryLimit: 5
triggers: []
- A slighlty modified postgres template since frab needs the admin password:
apiVersion: template.openshift.io/v1
kind: Template
labels:
template: postgresql-persistent-template
message: |-
The following service(s) have been created in your project: ${DATABASE_SERVICE_NAME}.
Username: ${POSTGRESQL_USER}
Password: ${POSTGRESQL_PASSWORD}
Database Name: ${POSTGRESQL_DATABASE}
Admin Password: ${POSTGRESQL_ADMIN_PASSWORD}
Connection URL: postgresql://${DATABASE_SERVICE_NAME}:5432/
For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/.
metadata:
annotations:
description: |-
PostgreSQL database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/.
NOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template.
iconClass: icon-postgresql
openshift.io/display-name: PostgreSQL
openshift.io/documentation-url: https://docs.openshift.org/latest/using_images/db_images/postgresql.html
openshift.io/long-description: This template provides a standalone PostgreSQL
server with a database created. The database is stored on persistent storage. The
database name, username, and password are chosen via parameters when provisioning
this service.
openshift.io/provider-display-name: Red Hat, Inc.
openshift.io/support-url: https://access.redhat.com
tags: database,postgresql
name: postgresql-persistent
namespace: openshift
objects:
- apiVersion: v1
kind: Secret
metadata:
annotations:
template.openshift.io/expose-database_name: '{.data[''database-name'']}'
template.openshift.io/expose-password: '{.data[''database-password'']}'
template.openshift.io/expose-username: '{.data[''database-user'']}'
template.openshift.io/expose-admin-password: '{.data[''database-admin-password'']}'
name: ${DATABASE_SERVICE_NAME}
stringData:
database-name: ${POSTGRESQL_DATABASE}
database-password: ${POSTGRESQL_PASSWORD}
database-user: ${POSTGRESQL_USER}
database-admin-password: ${POSTGRESQL_ADMIN_PASSWORD}
- apiVersion: v1
kind: Service
metadata:
annotations:
template.openshift.io/expose-uri: postgres://{.spec.clusterIP}:{.spec.ports[?(.name=="postgresql")].port}
name: ${DATABASE_SERVICE_NAME}
spec:
ports:
- name: postgresql
nodePort: 0
port: 5432
protocol: TCP
targetPort: 5432
selector:
name: ${DATABASE_SERVICE_NAME}
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
- apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ${DATABASE_SERVICE_NAME}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: ${VOLUME_CAPACITY}
- apiVersion: v1
kind: DeploymentConfig
metadata:
annotations:
template.alpha.openshift.io/wait-for-ready: "true"
name: ${DATABASE_SERVICE_NAME}
spec:
replicas: 1
selector:
name: ${DATABASE_SERVICE_NAME}
strategy:
type: Recreate
template:
metadata:
labels:
name: ${DATABASE_SERVICE_NAME}
spec:
containers:
- capabilities: {}
env:
- name: POSTGRESQL_USER
valueFrom:
secretKeyRef:
key: database-user
name: ${DATABASE_SERVICE_NAME}
- name: POSTGRESQL_PASSWORD
valueFrom:
secretKeyRef:
key: database-password
name: ${DATABASE_SERVICE_NAME}
- name: POSTGRESQL_DATABASE
valueFrom:
secretKeyRef:
key: database-name
name: ${DATABASE_SERVICE_NAME}
- name: POSTGRESQL_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
key: database-admin-password
name: ${DATABASE_SERVICE_NAME}
image: ' '
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command:
- /usr/libexec/check-container
- --live
initialDelaySeconds: 120
timeoutSeconds: 10
name: postgresql
ports:
- containerPort: 5432
protocol: TCP
readinessProbe:
exec:
command:
- /usr/libexec/check-container
initialDelaySeconds: 5
timeoutSeconds: 1
resources:
limits:
memory: ${MEMORY_LIMIT}
securityContext:
capabilities: {}
privileged: false
terminationMessagePath: /dev/termination-log
volumeMounts:
- mountPath: /var/lib/pgsql/data
name: ${DATABASE_SERVICE_NAME}-data
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- name: ${DATABASE_SERVICE_NAME}-data
persistentVolumeClaim:
claimName: ${DATABASE_SERVICE_NAME}
triggers:
- imageChangeParams:
automatic: true
containerNames:
- postgresql
from:
kind: ImageStreamTag
name: postgresql:${POSTGRESQL_VERSION}
namespace: ${NAMESPACE}
lastTriggeredImage: ""
type: ImageChange
- type: ConfigChange
status: {}
parameters:
- description: Maximum amount of memory the container can use.
displayName: Memory Limit
name: MEMORY_LIMIT
required: true
value: 512Mi
- description: The OpenShift Namespace where the ImageStream resides.
displayName: Namespace
name: NAMESPACE
value: openshift
- description: The name of the OpenShift Service exposed for the database.
displayName: Database Service Name
name: DATABASE_SERVICE_NAME
required: true
value: postgresql
- description: Username for PostgreSQL user that will be used for accessing the database.
displayName: PostgreSQL Connection Username
from: user[A-Z0-9]{3}
generate: expression
name: POSTGRESQL_USER
required: true
- description: Password for the PostgreSQL connection user.
displayName: PostgreSQL Connection Password
from: '[a-zA-Z0-9]{16}'
generate: expression
name: POSTGRESQL_PASSWORD
required: true
- description: Password for the PostgreSQL admin user.
displayName: PostgreSQL Admin Password
from: '[a-zA-Z0-9]{16}'
generate: expression
name: POSTGRESQL_ADMIN_PASSWORD
required: true
- description: Name of the PostgreSQL database accessed.
displayName: PostgreSQL Database Name
name: POSTGRESQL_DATABASE
required: true
value: sampledb
- description: Volume space available for data, e.g. 512Mi, 2Gi.
displayName: Volume Capacity
name: VOLUME_CAPACITY
required: true
value: 1Gi
- description: Version of PostgreSQL image to be used (9.4, 9.5, 9.6 or latest).
displayName: Version of PostgreSQL Image
name: POSTGRESQL_VERSION
required: true
value: "9.6"
- a PVC for the app data of frab, so that the assets like attachments and alike aren’t lost after a container restart:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: frab-appdata
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1G
- the DeploymentConfig:
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
labels:
app: frab
name: frab
spec:
replicas: 1
selector:
app: frab
deploymentconfig: frab
strategy:
activeDeadlineSeconds: 21600
recreateParams:
timeoutSeconds: 600
resources: {}
type: Recreate
template:
metadata:
labels:
app: frab
deploymentconfig: frab
spec:
containers:
- env:
- name: FRAB_DBPASSWORD
valueFrom:
secretKeyRef:
key: database-admin-password
name: postgresql
- name: FRAB_DBNAME
valueFrom:
secretKeyRef:
key: database-name
name: postgresql
- name: FRAB_DBHOST
value: postgresql
- name: TZ
value: Europe/Berlin
- name: SECRET_KEY_BASE
valueFrom:
secretKeyRef:
key: SECRET_KEY_BASE
name: frab-secret-key-base
- name: FRAB_HOST
value: localhost
- name: FROM_EMAIL
value: frab@localhost
- name: SMTP_ADDRESS
value: 172.17.0.1
- name: SMTP_PORT
value: '25'
- name: BUNDLE_WITHOUT
value: 'development:test:mysql:sqlite3'
image: quay.io/jaeichle/frab:0.9
imagePullPolicy: Always
name: frab
ports:
- containerPort: 3000
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /
port: 3000
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources:
limits:
cpu: '1'
memory: 512Mi
requests:
cpu: '1'
memory: 512Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /home/frab/app/public
name: appdata
imagePullSecrets:
- name: jaeichle-openpaas-pull-secret
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- name: appdata
persistentVolumeClaim:
claimName: frab-appdata
test: false
triggers:
- A service:
apiVersion: v1
kind: Service
metadata:
labels:
app: frab
name: frab
spec:
ports:
- port: 3000
protocol: TCP
name: 3000-tcp
targetPort: 3000
selector:
app: frab
sessionAffinity: None
type: ClusterIP
- A template to create frab secrets:
apiVersion: template.openshift.io/v1
kind: Template
labels:
template: frab-secret-keybase-secret
message: |-
The following secret has been created in your project: ${FRAB_SECRET_KEYBASE_SECRET}.
metadata:
annotations:
tags: frab
name: frab-secret-key-base-secret-template
objects:
- apiVersion: v1
kind: Secret
metadata:
annotations:
name: ${FRAB_SECRET_KEYBASE_SECRET}
stringData:
SECRET_KEY_BASE: ${FRAB_SECRET_KEYBASE}
parameters:
- description: The name of the OpenShift secret for the frab secret key base.
displayName: Frab Secret Key Base
name: FRAB_SECRET_KEYBASE_SECRET
required: true
value: frab-secret-key-base
- description: Password for the PostgreSQL connection user.
displayName: PostgreSQL Connection Password
from: '[a-zA-Z0-9]{32}'
generate: expression
name: FRAB_SECRET_KEYBASE
required: true
- And the route to be able to access it:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
labels:
app: frab
name: frab
namespace: frab
spec:
host: frab.openshift.example.com
port:
targetPort: 3000-tcp
tls:
insecureEdgeTerminationPolicy: Redirect
termination: edge
to:
kind: Service
name: frab
weight: 100
wildcardPolicy: None
After that’s all there I just did:
- Make sure quay secret is there
- oc process -f postgresql-template.yaml | oc apply -f -
- oc create -f frab-appdata-pvc.yaml
- oc process -f secret-template.yaml | oc create -f -
- oc create -f deploymentconfig.yaml
- oc create -f route.yaml
An easier way would be to create a template for the whole application, but that’s for another time when there’s more time.