edwin Posts

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+

Dockerfile for Creating A UBI-based Docker Image and OpenJDK

I use UBI 8 as base images for basically almost every java deployment. It’s lightweight, freely redistributable, and easy to configure.

This is my simplified Dockerfile script, which consist of UBI8 and OpenJDK installation.

FROM registry.access.redhat.com/ubi8/ubi-minimal

MAINTAINER Muhammad Edwin < emuhamma at redhat dot com >

# Some version information
LABEL io.k8s.description="Platform for running plain Java applications (fat-jar and flat classpath)" \
      io.k8s.display-name="My Simple Java Applications" \
      io.openshift.tags="builder,java" \
      org.jboss.deployments-dir="/deployments" \
      com.redhat.deployments-dir="/deployments" \
      com.redhat.dev-mode="JAVA_DEBUG:false" \
      com.redhat.dev-mode.port="JAVA_DEBUG_PORT:5005"

# Install Java runtime
RUN microdnf install java-11-openjdk-headless --nodocs \
 && microdnf install shadow-utils && microdnf clean all \
 && mkdir /deployments

# Add user & group
RUN groupadd jboss && useradd jboss -g jboss

# Use /dev/urandom to speed up startups & Add jboss user to the root group
RUN echo securerandom.source=file:/dev/urandom >> /usr/lib/jvm/jre/lib/security/java.security \
 && usermod -g root -G jboss jboss

# set working directory at /deployments
WORKDIR /deployments

# copy corresponding jar file
COPY demo.jar demo.jar

# Necessary to permit running with a randomised UID
RUN mkdir -p /deployments/data \
 && chmod -R "g+rwX" /deployments \
 && chown -R jboss:jboss /deployments \
 && chmod 664 /etc/passwd

# gives uid
USER 185

# run it
CMD ["java","-jar","demo.jar"]
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+

How to Deploy Source Code from Local Folder to Openshift Using S2I Build

First lets create a simple PHP code, and name it index.php

<?php
echo "hello world";
?>

We want to deploy it on top of PHP 7 images, and make it online on Openshift Platform.

So how to do it basically consist of 3 steps.
1. First we need to create a new binary build, using preferred image stream as its base image

oc new-build --name=my-php --image-stream=php:7.0 --binary=true

2. Next is start building image using sourcecode’s directory

oc start-build my-php --from-dir=.

3. Last is create application using previously built image

oc new-app my-php --name=my-php

So simple right :)

Google+