ocp

How to Connect OpenShift BuildConfig to Docker Hub

On this article im trying to use Openshift as a Build Server to build a containerized image from a plain sourcecode and store the result in external Image Registry, in this case Docker Hub. The goal is to provide another options to build containerized image when we have limitation, especially we dont have any Docker command installed in our local laptop.

The concept is displayed in below image,

For this example, im using a simple PHP file.

<?php
    echo "hello world php";
?>

So lets start by creating a BuildConfig to containerized this PHP code in OpenShift, and trigger it by using our local PHP file,

 $ oc new-build . --name=hello-world-php --to-docker \ 
		--to=docker.io/dockerusername/hello-world-php

 $ oc start-build hello-world-php \ 
		--from-dir=. --follow --wait

But before we do that, we need to register our Docker Hub credentials into our OCP so that OCP can push the build image into Docker Hub.

 $ oc create secret docker-registry --docker-server=docker.io \ 
		--docker-username=dockerusername --docker-password=dockerpassword 
		--docker-email=your@email.com docker-login

 $ oc secret link builder docker-login --for=mount

In summary, there are multiple ways and strategies of deploying your app into OpenShift and this is one way of doing it.

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.

Injecting Openshift Secret and Reading it as an Environment Variables in Spring Boot

In this writing, im planning to create a simple Spring Boot application but with a dynamic configuration that is going to be fetched from environment variables. Usually we are using this for securing some sensitive values such as Database credentials or endpoints.

For this scenario, im trying to make password variables as parameterized inside Spring Boot’s application.properties. Binds it with environment variables with the name of OPENSHIFT_APP_PASSWORD.

server.port=8080
server.password=${OPENSHIFT_APP_PASSWORD}

And call it from our controller,

package com.edw.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class IndexController {

    @Value("${server.password}")
    private String serverPassword;

    @GetMapping("/")
    public Map helloWorld() {
        return new HashMap() {{
            put("hello", "world");
            put("password", serverPassword);
        }};
    }
}

Dont forget setting up maven’s configuration,

<?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>ocpsecret</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>redhat-early-access</id>
            <name>Red Hat Early Access Repository</name>
            <url>https://maven.repository.redhat.com/earlyaccess/all/</url>
        </repository>
        <repository>
            <id>redhat-ga</id>
            <name>Red Hat GA Repository</name>
            <url>https://maven.repository.redhat.com/ga/</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>redhat-early-access</id>
            <name>Red Hat Early Access Repository</name>
            <url>https://maven.repository.redhat.com/earlyaccess/all/</url>
        </pluginRepository>
        <pluginRepository>
            <id>redhat-ga</id>
            <name>Red Hat GA Repository</name>
            <url>https://maven.repository.redhat.com/ga/</url>
        </pluginRepository>
    </pluginRepositories>

    <properties>
        <snowdrop-bom.version>2.3.6.Final-redhat-00001</snowdrop-bom.version>
        <spring-boot.version>2.1.4.RELEASE-redhat-00001</spring-boot.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <start-class>com.edw.Main</start-class>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>dev.snowdrop</groupId>
                <artifactId>snowdrop-dependencies</artifactId>
                <version>${snowdrop-bom.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

After we commit all the code into github, we can pull them from Openshift with a simple oc command.

$ oc new-app registry.access.redhat.com/ubi8/openjdk-11~https://github.com/edwin/spring-boot-and-ocp-secret

We can create a variable as a Secret by using below oc command

$ oc create secret generic mypassword --from-literal=OPENSHIFT_APP_PASSWORD=whatever

And inject it into our application,

$ oc set env --from=secret/mypassword dc/spring-boot-and-ocp-secret

Expose our app’s endpoint,

$ oc expose service spring-boot-and-ocp-secret

And do a curl to see that variable “password” has been filled with “whatever” which comes from our OCP Secret.

$ curl -kv http://ocp-endpoint/

* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: application/json
<
{"password":"whatever","hello":"world"}

Code for this can be found on below link

https://github.com/edwin/spring-boot-and-ocp-secret

How to Solve Openshift “Failed to pull image, unauthorized: authentication required”

Just recently got an unique error, this happens when my application is pulling an image within a different Openshift namespace. In this example, im creating my application in “xyz-project” and try to pull image from “abc-project”. Here’s the complete error detail,

Failed to pull image "image-registry.openshift-image-registry.svc:5000/abc-project/image01@sha256:xxxxxxxxxxxx": 
rpc error: code = Unknown desc = Error reading manifest sha256:xxxxxxxxxxxx in 
image-registry.openshift-image-registry.svc:5000/abc-project/image01: unauthorized: authentication required

Solution for this is quite easy, actually we need to give a specific access right in order for “xyz-project” to be able to pull image from “abc-project”.

oc policy add-role-to-user system:image-puller system:serviceaccount:xyz-project:default -n abc-project

Hope it helps.

Get ImageStream Name and SHA from All DeploymentConfig within a Namespace on Openshift 4

There are times where we want to display list of DC within one Namespace, and want to see what are the images involved within it. We can do that easily by using a simple OC command like below,

oc get dc -n  <namespace> --no-headers -o template \
     --template='{{range.items}}{{.metadata.namespace}}{{"/"}}{{.metadata.name}}{{" - "}}
     {{(index .spec.template.spec.containers 0).image}}{{"\n"}}{{end}}'