openshift

Deploying a Java Apps and JBoss EAP into Openshift 4

Sometimes we still have to maintain an application which is still deployed on top of JBoss EAP and in a Virtual Machine, and planning in onboarding them into Openshift.

Deploying this kind of applications is almost the same as deploying a Spring Boot applications on Openshift. Only need one command for doing it.

So lets start with a basic maven file,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>HelloWorldWar</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <optimize>true</optimize>
                    <debug>true</debug>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warName>${project.name}</warName>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Lets set the context root of this app,

<jboss-web>
    <context-root>/</context-root>
</jboss-web>

And a servlet for serving some content,

package com.edw;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "HelloServlet", urlPatterns = "/")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html");

        PrintWriter out = response.getWriter();
        out.println("<h1> Hello World </h1>");

    }
}

And make sure the structure of our project is like below,

+--- .gitignore
+--- pom.xml
+--- README.md
+--- src
|   +--- main
|   |   +--- java
|   |   |   +--- com
|   |   |   |   +--- edw
|   |   |   |   |   +--- HelloServlet.java
|   |   +--- webapp
|   |   |   +--- WEB-INF
|   |   |   |   +--- jboss-web.xml

We can deploy our code to test-project namespace in our Openshift by using below command,

$ oc new-app openshift/jboss-eap73-openjdk11-openshift:latest~. \ 
	--name=hello-world -n test-project

Code for this tutorial can be seen in below Github url

https://github.com/edwin/hello-world-jboss-eap

A Jenkins Agent with Helm 3 for Build and Deploying Java Application in Openshift 4

Basically im trying to create a Jenkins Agent base image, with the capability of building and deploying Java application. For building, i need to have a Maven installation within Jenkins Agent. But for deploying, im planning on exploring Helm 3.

Below is a Dockerfile to build a slave image than can be use for build and deploying Java applications.

FROM registry.redhat.io/openshift4/ose-jenkins-agent-maven:latest
USER root

RUN curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 \ 
    && chmod 700 /tmp/get_helm.sh  \
	&& cd /tmp/ && ./get_helm.sh  \ 
	&& rm -Rf /tmp/*
RUN helm version

USER 1001

We can build it by using below command,

docker build -t ose-jenkins-agent-maven-helm .

And as always, my code can be downloaded on below url

https://github.com/edwin/ose-jenkins-agent-maven-helm

How to Solve “Error from server (Forbidden): buildconfigs.build.openshift.io build-name is forbidden”

I had this error while building an image on Jenkins from one Openshift namespace to another namespace. This happens mainly due to limited access that the corresponding Jenkins user have on the target namespace.

Error from server (Forbidden): buildconfigs.build.openshift.io "hello-world" is forbidden: 
User "system:serviceaccount:cicd:jenkins" 
cannot create resource "buildconfigs/instantiatebinary" in API group 
"build.openshift.io" in the namespace "apps"

How to solve it is actually quite easy, we are giving admin privilege to jenkins service account user.

oc policy add-role-to-user admin system:serviceaccount:cicd:jenkins -n apps

A Beego Golang Framework on top of Openshift 4

Golang has a lot of web frameworks and Beego is one it, it provides a rapid development of enterprise application in Go, including RESTful APIs, web apps and backend services.

The concept for this is pretty much simple, a json rest api showing helo world as response. Deployed as containerized image, on top of Kubernetes. So lets start with some Go files,

package main

import (
	_ "HelloBeego/routers"
	"github.com/beego/beego/v2/server/web"
)

func main() {
	web.Run()
}

A router file,

package routers

import (
	"HelloBeego/controllers"
	"github.com/beego/beego/v2/server/web"
)

func init() {
	web.Router("/", &amp;amp;controllers.HelloController{})
}

and a controller,

package controllers

import "github.com/beego/beego/v2/server/web"

type HelloController struct {
	web.Controller
}

type helloResponse struct {
	Greeting string `json:"greeting"`
}

func (u *HelloController) Get() {

	user := helloResponse{}
	user.Greeting = "Hello World Edwin"

	u.Data["json"] = user
	u.ServeJSON()
}

The tricky part is creating a multi-stage build by using Docker to build this and deploy by using a different images.

FROM registry.access.redhat.com/ubi8/go-toolset
USER root
RUN mkdir /build/
ADD . /build/
RUN cd /build/  go build

FROM registry.access.redhat.com/ubi8/ubi-minimal
COPY --from=0 /build/HelloBeego /usr/local/bin/app
CMD app
EXPOSE 8080

Run docker build command, deploy the containerized image into Openshift 4 and create Routes in order to make the application accessible. The sample commands are in below,

$ oc new-app . --strategy=docker

$ oc create route edge --service=beego-rest-hello-world

Code can be accessed in below Github repo,

https://github.com/edwin/beego-rest-hello-world

Deploying Sonarqube to Openshift 4 by Using Openshift Template

Sonarqube is an automatic code review tool to detect bugs, vulnerabilities, and code smells in your code. It can fits with existing CI/CD tools as a static code analysis tools, and providing a quality gate to make sure that your code and delivery can fits in with a specific quality standard.

Deploying Sonarqube to Openshift should be a pretty much straightforward activity which have multiple approaches. But now, im trying to deploy Sonarqube by using a simple Openshift Template. In short, Openshift Template is a set of objects that can be parameterized and processed to produce a list of objects for creation by OpenShift Container Platform. Which means, we are deploying Sonarqube with its configuration, and surrounding infrastructures.

On this article, im trying to create two different Sonarqube deployments. One is by using an in-memory database, which is recommended for a non-production environments, and another way is by having a Postgresql database which is suitable for production usage.

So, on below YML i have the first Sonarqube template deployment. It would deploy Sonarqube, setting up its Router, provisioning PersistentVolume, and setting up its configurations. Lets name it sonarqube-h2-db-template.yml

apiVersion: v1
kind: Template
labels:
  template: sonarqube-h2-db-template
message: A Sonarqube service has been created in your project. You can access using admin/admin.
metadata:
  annotations:
    description: |-
      Sonarqube service, with H2 DB.
      NOTE: Data will not be gone despite restarts, but dont use this for production usage.
    openshift.io/display-name: SonarQube (H2 DB)
    openshift.io/documentation-url: https://docs.sonarqube.org/
    openshift.io/long-description: This template deploys a SonarQube server with an embeddable H2 DB.
    tags: instant-app,sonarqube
  creationTimestamp: null
  name: sonarqube-h2-db
objects:
- apiVersion: v1
  kind: Route
  metadata:
    annotations:
      template.openshift.io/expose-uri: http://{.spec.host}{.spec.path}
    name: ${SONARQUBE_SERVICE_NAME}
  spec:
    to:
      kind: Service
      name: ${SONARQUBE_SERVICE_NAME}
    tls:
      termination: edge
- apiVersion: v1
  kind: DeploymentConfig
  metadata:
    annotations:
      template.alpha.openshift.io/wait-for-ready: "true"
    name: ${SONARQUBE_SERVICE_NAME}
  spec:
    replicas: 1
    selector:
      name: ${SONARQUBE_SERVICE_NAME}
    strategy:
      type: Recreate
    template:
      metadata:
        labels:
          name: ${SONARQUBE_SERVICE_NAME}
      spec:
        containers:
        - capabilities: {}
          image: 'sonarqube:latest'
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 9000
              protocol: TCP
          livenessProbe:
            failureThreshold: 30
            httpGet:
              path: /
              port: 9000
            initialDelaySeconds: 420
            timeoutSeconds: 3
          name: sonarqube
          readinessProbe:
            httpGet:
              path: /
              port: 9000
            initialDelaySeconds: 3
            timeoutSeconds: 3
          volumeMounts:
            - mountPath: /opt/sonarqube/data
              name: ${SONARQUBE_SERVICE_NAME}-data
            - mountPath: /opt/sonarqube/logs
              name: ${SONARQUBE_SERVICE_NAME}-logs
            - mountPath: /opt/sonarqube/extensions
              name: ${SONARQUBE_SERVICE_NAME}-extensions
          resources:
            requests:
              memory: ${SONARQUBE_MEMORY_LIMITS}
            limits:
              memory: ${SONARQUBE_MEMORY_LIMITS}
          securityContext:
            capabilities: {}
            privileged: false
          terminationMessagePath: /dev/termination-log
        dnsPolicy: ClusterFirst
        restartPolicy: Always
        volumes:
        - name: ${SONARQUBE_SERVICE_NAME}-data
          persistentVolumeClaim:
            claimName: ${SONARQUBE_SERVICE_NAME}-data-pv
        - name: ${SONARQUBE_SERVICE_NAME}-logs
          persistentVolumeClaim:
            claimName: ${SONARQUBE_SERVICE_NAME}-logs-pv
        - name: ${SONARQUBE_SERVICE_NAME}-extensions
          persistentVolumeClaim:
            claimName: ${SONARQUBE_SERVICE_NAME}-extensions-pv
    triggers:
    - type: ConfigChange
- apiVersion: v1
  kind: Service
  metadata:
    name: ${SONARQUBE_SERVICE_NAME}
  spec:
    ports:
    - port: 9000
      protocol: TCP
      targetPort: 9000
    selector:
      name: ${SONARQUBE_SERVICE_NAME}
    sessionAffinity: None
    type: ClusterIP
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: ${SONARQUBE_SERVICE_NAME}-data-pv
  spec:
    resources:
      requests:
        storage: 1Gi
    accessModes:
    - ReadWriteOnce
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: ${SONARQUBE_SERVICE_NAME}-logs-pv
  spec:
    resources:
      requests:
        storage: 1Gi
    accessModes:
    - ReadWriteOnce
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: ${SONARQUBE_SERVICE_NAME}-extensions-pv
  spec:
    resources:
      requests:
        storage: 1Gi
    accessModes:
    - ReadWriteOnce
parameters:
- description: The name of the OpenShift Service exposed for the SonarQube container.
  displayName: SonarQube Service Name
  name: SONARQUBE_SERVICE_NAME
  value: sonar
- description: SonarQube container memory limits.
  displayName: Memory Limits
  name: SONARQUBE_MEMORY_LIMITS
  required: true
  value: 2Gi

Deploy the template to OCP by using below command,

$ oc create -f sonarqube-h2-db-template.yml

And we can initiate the template from Openshift web console,

Or, we can initiate it by using an OC command instead,

$ oc new-app --template sonarqube-h2-db

The second template deployment is deploying Sonarqube with a separate Postgresql database installed. So we need to prepare the Sonarqube and Postgresql database configurations, Services, and PersistentVolume.

apiVersion: v1
kind: Template
labels:
  template: sonarqube-pgsql-db-template
message: A Sonarqube service has been created in your project. You can access using admin/admin.
metadata:
  annotations:
    description: |-
      Sonarqube service, with Postgresql DB.
      NOTE: Data will not be gone despite restarts, preferable for production usages.
    openshift.io/display-name: SonarQube (Postgresql DB)
    openshift.io/documentation-url: https://docs.sonarqube.org/
    openshift.io/long-description: This template deploys a SonarQube server with a standalone Postgresql DB.
    tags: instant-app,sonarqube
  creationTimestamp: null
  name: sonarqube-pgsql-db
objects:
- apiVersion: v1
  kind: Route
  metadata:
    annotations:
      template.openshift.io/expose-uri: http://{.spec.host}{.spec.path}
    name: ${SONARQUBE_SERVICE_NAME}
  spec:
    to:
      kind: Service
      name: ${SONARQUBE_SERVICE_NAME}
    tls:
      termination: edge
- apiVersion: v1
  kind: DeploymentConfig
  metadata:
    annotations:
      template.alpha.openshift.io/wait-for-ready: "true"
    name: ${SONARQUBE_SERVICE_NAME}
  spec:
    replicas: 1
    selector:
      name: ${SONARQUBE_SERVICE_NAME}
    strategy:
      type: Recreate
    template:
      metadata:
        labels:
          name: ${SONARQUBE_SERVICE_NAME}
      spec:
        containers:
        - capabilities: {}
          image: 'sonarqube:latest'
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 9000
              protocol: TCP
          livenessProbe:
            failureThreshold: 30
            httpGet:
              path: /
              port: 9000
            initialDelaySeconds: 420
            timeoutSeconds: 3
          env:
            - name: SONAR_JDBC_URL
              value: jdbc:postgresql://${SONARQUBE_SERVICE_NAME}-pgsql:5432/sonar
            - name: SONAR_JDBC_USERNAME
              value: sonar
            - name: SONAR_JDBC_PASSWORD
              value: sonar
          name: sonarqube
          readinessProbe:
            httpGet:
              path: /
              port: 9000
            initialDelaySeconds: 3
            timeoutSeconds: 3
          volumeMounts:
            - mountPath: /opt/sonarqube/data
              name: ${SONARQUBE_SERVICE_NAME}-data
            - mountPath: /opt/sonarqube/logs
              name: ${SONARQUBE_SERVICE_NAME}-logs
            - mountPath: /opt/sonarqube/extensions
              name: ${SONARQUBE_SERVICE_NAME}-extensions
          resources:
            requests:
              memory: ${SONARQUBE_MEMORY_LIMITS}
            limits:
              memory: ${SONARQUBE_MEMORY_LIMITS}
          securityContext:
            capabilities: {}
            privileged: false
          terminationMessagePath: /dev/termination-log
        dnsPolicy: ClusterFirst
        restartPolicy: Always
        volumes:
        - name: ${SONARQUBE_SERVICE_NAME}-data
          persistentVolumeClaim:
            claimName: ${SONARQUBE_SERVICE_NAME}-data-pv
        - name: ${SONARQUBE_SERVICE_NAME}-logs
          persistentVolumeClaim:
            claimName: ${SONARQUBE_SERVICE_NAME}-logs-pv
        - name: ${SONARQUBE_SERVICE_NAME}-extensions
          persistentVolumeClaim:
            claimName: ${SONARQUBE_SERVICE_NAME}-extensions-pv
    triggers:
    - type: ConfigChange
- apiVersion: v1
  kind: Service
  metadata:
    name: ${SONARQUBE_SERVICE_NAME}
  spec:
    ports:
    - port: 9000
      protocol: TCP
      targetPort: 9000
    selector:
      name: ${SONARQUBE_SERVICE_NAME}
    sessionAffinity: None
    type: ClusterIP
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: ${SONARQUBE_SERVICE_NAME}-data-pv
  spec:
    resources:
      requests:
        storage: 1Gi
    accessModes:
    - ReadWriteOnce
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: ${SONARQUBE_SERVICE_NAME}-logs-pv
  spec:
    resources:
      requests:
        storage: 1Gi
    accessModes:
    - ReadWriteOnce
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: ${SONARQUBE_SERVICE_NAME}-extensions-pv
  spec:
    resources:
      requests:
        storage: 1Gi
    accessModes:
    - ReadWriteOnce

# postgresql starts here
- apiVersion: v1
  kind: DeploymentConfig
  metadata:
    annotations:
      template.alpha.openshift.io/wait-for-ready: "true"
    name: ${SONARQUBE_SERVICE_NAME}-pgsql
  spec:
    replicas: 1
    selector:
      name: ${SONARQUBE_SERVICE_NAME}-pgsql
    strategy:
      type: Recreate
    template:
      metadata:
        labels:
          name: ${SONARQUBE_SERVICE_NAME}-pgsql
      spec:
        containers:
        - capabilities: {}
          env:
          - name: POSTGRESQL_USER
            value: sonar
          - name: POSTGRESQL_PASSWORD
            value: sonar
          - name: POSTGRESQL_DATABASE
            value: sonar
          image: 'image-registry.openshift-image-registry.svc:5000/openshift/postgresql:12'
          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: 512Mi
          securityContext:
            capabilities: {}
            privileged: false
          terminationMessagePath: /dev/termination-log
          volumeMounts:
          - mountPath: /var/lib/pgsql/data
            name: ${SONARQUBE_SERVICE_NAME}-pgsql-data
        dnsPolicy: ClusterFirst
        restartPolicy: Always
        volumes:
        - name: ${SONARQUBE_SERVICE_NAME}-pgsql-data
          persistentVolumeClaim:
            claimName: ${SONARQUBE_SERVICE_NAME}-pgsql-data
    triggers:
    - type: ConfigChange
- apiVersion: v1
  kind: PersistentVolumeClaim
  metadata:
    name: ${SONARQUBE_SERVICE_NAME}-pgsql-data
  spec:
    resources:
      requests:
        storage: 1Gi
    accessModes:
    - ReadWriteOnce
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      template.openshift.io/expose-uri: postgres://{.spec.clusterIP}:{.spec.ports[?(.name=="postgresql")].port}
    name: ${SONARQUBE_SERVICE_NAME}-pgsql
  spec:
    ports:
    - name: postgresql
      nodePort: 0
      port: 5432
      protocol: TCP
      targetPort: 5432
    selector:
      name: ${SONARQUBE_SERVICE_NAME}-pgsql
    sessionAffinity: None
    type: ClusterIP
  status:
    loadBalancer: {}

parameters:
- description: The name of the OpenShift Service exposed for the SonarQube container.
  displayName: SonarQube Service Name
  name: SONARQUBE_SERVICE_NAME
  value: sonar
- description: SonarQube container memory limits.
  displayName: Memory Limits
  name: SONARQUBE_MEMORY_LIMITS
  required: true
  value: 2Gi

Deploy above template to OCP by using below command,

$ oc create -f sonarqube-pgsql-db-template.yml

The code for above template can be found on below Github url,

https://github.com/edwin/sonarqube-on-openshift-4

Hope it helps, and have fun using Sonarqube.