Openshift Posts

Deploying A Simple Hello World App using OpenLiberty S2I to Openshift

For this example im using OpenLiberty version 19.0.0.6, and install corresponding image to my Openshift cluster by using skopeo command or just by a simple oc new-app,

oc new-app openliberty/open-liberty-s2i:19.0.0.6

and delete the dc afterwards,

oc delete dc open-liberty-s2i

Can check our list of images on our imagestream by using this command,

oc get is

Next is creating a simple hello-world webapps, with below pom

<?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>hello-world-servlet</groupId>
    <artifactId>com.edw</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src/main/java</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <webXml>src\main\webapp\WEB-INF\web.xml</webXml>
                </configuration>
            </plugin>
            <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>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

And web.xml,

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <display-name>My Web Application</display-name>

    <servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.edw.MyServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>helloServlet</servlet-name>
        <url-pattern>/hello.servlet</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>/hello.jsp</welcome-file>
    </welcome-file-list>
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
</web-app>

A simple JSP file,

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello World</title>
</head>
<body>
Hello World
</body>
</html>

And a simple java file,

package com.edw;

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

public class MyServlet  extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        response.getWriter().println("Hello");
    }
}

And a simple server.xml file,

<?xml version="1.0" encoding="UTF-8"?>
<server description="OpenLiberty Server">
    <httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="9080" httpsPort="9443"/>
    <webApplication location="com.edw-1.0-SNAPSHOT.war"/>
</server>

After project is properly setup, we can do a simple mvn build,

mvn clean package

And push our application to Openshift, run below command on the root of your project location

oc new-build --name=my-openliberty-full --image-stream=open-liberty-s2i:19.0.0.6 --binary=true

oc start-build my-openliberty-full --from-dir=.

oc new-app my-openliberty-full --name=my-openliberty-full

We can access our newly created app directly thru browser,

Google+

Creating a Simple Openshift Pipeline for NodeJS 10 Apps with Jenkins Slave

Jenkins pipeline build have a slave mechanism, where it will spawn a new pod based on a specific image and will build on top of it. Slave mechanism have several benefits compared to traditional build, and one of the benefit is it can build with a different environment compared to jenkins master’s environment.

So, lets start with a simple docker file. We’ll create an imagestream using it, and will be used as a slave image. Basically it will use jenkins-slave-base-rhel7 as base image, and will install nodejs 10 on top of it.

oc new-build -D $'
FROM registry.access.redhat.com/openshift3/jenkins-slave-base-rhel7:v3.11
\n
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
\n
ENV NVM_DIR=/home/jenkins/.nvm \ NODE_VERSION=10.16.0
\n
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION} && nvm use v${NODE_VERSION} && nvm alias default v${NODE_VERSION}
\n
ENV PATH="/home/jenkins/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
\n
RUN node --version && npm --version
\n
USER 1001' --name=new-jenkins-slave-node10-rhel7

And you can see the imagestream on Openshift,

Next step is, creating a Jenkins ephemeral on Openshift and creating new Jenkins slave with our newly created image.

oc new-app jenkins-persistent --param ENABLE_OAUTH=true --param MEMORY_LIMIT=2Gi --param VOLUME_CAPACITY=4Gi --param DISABLE_ADMINISTRATIVE_MONITORS=true -e OPENSHIFT_JENKINS_JVM_ARCH=i386  

For creating new slave, we can login to Jenkins page, open manage Jenkins menu, and go to Configure System menu, press Add Pod Template button.

Once successfully add new pod, we can start build our pipeline. Select New Item menu, add select Pipeline after that.Add put below code on Pipeline script,

def gitRepo="https://github.com/ariemay/node-test-app.git"
def branch="master"

node('new-jenkins-slave-node10-rhel7') {
    stage('test npm') {
        sh("node --version")
        sh("npm --version")
        sh("oc whoami")
    }
    stage ('pull code') {
        git branch: branch, url: gitRepo
    }
    stage ('build') {
        sh("npm install")
        sh("npm run build")
    }
    stage('check and prepare') {
        sh("cd /tmp")
        sh("pwd")
        sh("ls -alrth")
    }
    stage ('deploy') {
        try {
            sh("oc delete bc hello-react")
        } catch (Exception e) {
            sh("echo \"fail deleting bc \"")
        }
        try {
            sh("oc delete is hello-react")
        } catch (Exception e) {
            sh("echo \"fail deleting is \"")
        }
        try {
            sh("oc delete svc hello-react")
        } catch (Exception e) {
            sh("echo \"fail deleting svc \"")
        }
        try {
            sh("oc delete route hello-react")
        } catch (Exception e) {
            sh("echo \"fail deleting route \"")
        }
        sh("oc new-build --binary=true --name=hello-react --image-stream=nginx-112-rhel7")
        sh("oc start-build hello-react --from-dir=build --follow --wait" )

        try {
            sh("oc new-app  hello-react --name=hello-react" )
        } catch (Exception e) {
            sh("echo \"fail creating new-app, dc exists \"")
        }

        sh("oc expose svc/hello-react --name=hello-react")
    }
}

Press Build Now in order to see the build result,

We can see the url for result pod, and click it to see the built webpage.

So simple right?

Google+

Create A Simple Canary Deployment on Openshift

Openshift support multiple ways of deployements, such as traditional, canary and blue/green deployment. On this blog, im trying to create a simple canary deployment in order to see how can we leverage Openshift routing in deploy partially within a timeframe to reduce unwanted risks.

First we create two simple hello world app, one on top of PHP, and another one is on top of Java. We call my-blue and my-green. The goal of this scenario is to partially moving traffic from my-blue to my-green seamlessly.

oc new-app registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift~https://github.com/edwin/hello-world --name=my-blue
oc new-app php:7.0~https://github.com/edwin/php-helloworld --name=my-green

First is giving a 100percent traffic to my-blue microservice.

oc expose svc/my-blue --name=my-bluegreen

Then gradually reduce it to 75 percent,

oc set route-backends my-bluegreen my-blue=75 my-green=25 

And 15 percent,

oc set route-backends my-bluegreen my-blue=15 my-green=85 

Until the end is 100 percent of traffic goes to my-green.

oc set route-backends my-bluegreen my-green=100 

We can test the url output with below curl command

curl http://your-openshift-url
Google+

Distributed Tracing on Openshift using Jaeger and Spring Sleuth

There is one big issue when we are using microservices environment, that is sometimes we are unable to see messages goes from each microservice goes to which microservice and also unable to see latency for each microservices.

Luckily we have Jaeger to do that. According to its website, Jaeger is an open source, end-to-end distributed tracing for monitor and troubleshoot transactions in complex distributed systems. And it can also be installed easily on Openshift with a very simple oc command,

oc process -f https://raw.githubusercontent.com/jaegertracing/jaeger-openshift/master/all-in-one/jaeger-all-in-one-template.yml | oc create -f -

After installed, you will see Jaeger pod on Openshift project dashboard with several opened ports and a url for accessing query dashboard. See the red box on the image, it is the url for accessing zipkin api from other pods internally.

Next is creating two simple java app, one as backend, and another one as api gateway.
As usual, we’ll start with a simple maven file for our backend service,

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.edw.test</groupId>
    <artifactId>HelloWorld</artifactId>
    <version>1.0.1</version>
    <name>Hello World</name>
    <description>A Simple Hello World</description>

    <properties>
        <java.version>1.8</java.version>

        <version.fabric8.plugin>3.5.38</version.fabric8.plugin>
        <fabric8.generator.fromMode>istag</fabric8.generator.fromMode>
        <fabric8.generator.from>redhat-openjdk18-openshift:1.0</fabric8.generator.from>

    </properties>

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

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>


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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>io.fabric8</groupId>
                <artifactId>fabric8-maven-plugin</artifactId>
                <version>${version.fabric8.plugin}</version>

                <executions>
                    <execution>
                        <id>fmp</id>
                        <goals>
                            <goal>resource</goal>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>
</project>

Create a simple java app,

package com.edw.test.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
package com.edw.test.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class IndexController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @GetMapping("/")
    public HashMap sayHelloApi(@RequestParam String id) {
        logger.debug("say something, anything - {}", id);
        return new HashMap(){{
            put("Message", "Hello My World "+id);
        }};
    }
}

A logback.xml file for logging format,

<configuration>
    <statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
    <springProperty scope="context" name="springAppName" source="spring.application.name"/>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level [${springAppName},%X{X-B3-SpanId:-}] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <logger name="com.edw" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <root level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

And finally, a properties file for storing our configuration.

spring.application.name=Hello World
spring.zipkin.baseUrl: http://zipkin:9411/
spring.sleuth.sampler.probability=1.0

Next is creating our Api Gateway class, we’ll start with a simple POM 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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.edw</groupId>
    <artifactId>ApiGateway</artifactId>
    <version>1.0</version>

    <name>ApiGateway</name>
    <description>Demo project for Api Gateway</description>

    <properties>
        <java.version>1.8</java.version>

        <version.fabric8.plugin>3.5.38</version.fabric8.plugin>
        <fabric8.generator.fromMode>istag</fabric8.generator.fromMode>
        <fabric8.generator.from>redhat-openjdk18-openshift:1.0</fabric8.generator.from>

    </properties>


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

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


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>io.fabric8</groupId>
                <artifactId>fabric8-maven-plugin</artifactId>
                <version>${version.fabric8.plugin}</version>

                <executions>
                    <execution>
                        <id>fmp</id>
                        <goals>
                            <goal>resource</goal>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>
</project>

And several java classes,

package com.edw;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
package com.edw.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Date;
import java.util.UUID;

@RestController
public class IndexController {

    @Autowired
    private RestTemplate restTemplate;

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @GetMapping(value="/", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public String indexApi() throws Exception {
        String result = "";
        for (int i = 0; i< 3; i++) {
            logger.debug("firing");
            result = restTemplate.getForObject("http://helloworld:8080/?id="+ UUID.randomUUID().toString()+"&timestamp="+new Date().getTime(), String.class);
            logger.debug("response is {}, MDC is {}", result, MDC.get("X-B3-SpanId"));
        }
        return result;
    }
}
package com.edw.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * <pre>
 *     com.edw.config.RestTemplateConfig
 * </pre>
 *
 * @author Muhammad Edwin < emuhamma at redhat dot com >
 * 23 Sep 2019 10:47
 */
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate getRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    }
}

And a logback.xml, and application.properties.

<configuration>
    <statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
    <springProperty scope="context" name="springAppName" source="spring.application.name"/>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level [${springAppName},%X{X-B3-SpanId:-}] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <logger name="com.edw" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <root level="ERROR" additivity="false">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>
spring.application.name=API Gateway
spring.zipkin.baseUrl: http://zipkin:9411/
spring.sleuth.sampler.probability=1.0

We can deploy both project to Openshift using fabric8 command. And this is the result after deployed successfully and hitting Api Gateway url from browser,

And we can see the detail for each request by click on it,

To see a more detailed information, we can click more and see class and method name, and also information span.

And there is one good feature when using Jaeger, is that we can visualize how a message is delivered among different microservices,

And we can search the request SpanId on Kibana,

Well, hopefully it helps. (^)

Google+

Running A Simple Java Application CI/CD with Jenkins and Openshift

So basically im trying to create a simple CI/CD using Jenkins which runs on top of Openshift. It will do a very simple thing, fetching code from Github, and deploy it automatically to Openshift platform.

For this example, im using my previous Github repository which located at https://github.com/edwin/hello-world. It’s a very simple spring boot app, open an API and shows “hello world”.

But first, lets prepare our Jenkins instance on Openshift.

Once done, we can see Jenkins Dashboard.

And add Maven to Jenkins, on Manage jenkins > Global Tool Configuration

For this example, i want to deploy the app on a different Openshift project (eee project) compare to Jenkins which located on Fuse project. Therefore i need to create a service account specifially for Jenkins to deploy.

Create a simple pipeline item on Jenkins,

Which are triggered by a Poll SCM,

And after that, we can create a simple pipeline script for building the code. Changing project location to “eee”, and deploy it accordingly.

def gitRepo="https://github.com/edwin/hello-world.git"
def branch="master"

pipeline {
  agent any
  tools {
    maven 'M3'
  }
  stages {
    stage('Preparing'){
        steps{
            git branch: branch, url: gitRepo
        }
      }
    stage('Build and Deploy') {    
        steps {
            sh 'oc project eee'
            sh 'mvn -B clean fabric8:deploy'
        }
    }
  }
}

Simple isnt it? ;)

Google+