Openshift Posts

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. (^)

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? 😉

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 🙂

Backup All Openshift Template and Restore it to Another Openshift Instance

Previously i have to move a lot of Openshift template from one instance to another instance. So instead of copying a template one by one, why not just dump the whole template and import it to the new instance.

Below is the screenshot of source Openshift where we want to export the template from.

 
oc get -n openshift -o yaml --export templates > mytemplate.yaml

Below is the screenshot of the target Openshift,

oc apply -f mytemplate.yaml

This is the end result,

Creating A Simple Openshift Template

An Openshift Template is, as its name, a template on creating an application deployment. We can define url, imagestream location, service, and other things. We’ll start on a very simple json template which will took a specific image from docker hub, and deploy it to Openshift and generating URL on the fly automatically.

{
  "kind": "Template",
  "apiVersion": "v1",
  "metadata": {
    "name": "create-quarkus-template",
    "annotations": {
      "description": "This example shows how to create a simple template in openshift 3.11",
      "tags": "Quarkus"
    }
  },
  "objects": [
    {
      "kind": "Service",
      "apiVersion": "v1",
      "metadata": {
        "name": "my-quarkus"
      },
      "spec": {
        "ports": [
          {
            "name": "web",
            "protocol": "TCP",
            "port": 8080,
            "targetPort": 8080,
            "nodePort": 0
          }
        ],
        "selector": {
          "name": "my-quarkus"
        },
        "type": "ClusterIP",
        "sessionAffinity": "None"
      },
      "status": {
        "loadBalancer": {}
      }
    },
    {
        "kind": "Route",
        "apiVersion": "v1",
        "metadata": {
          "name": "my-quarkus"
        },
        "spec": {
          "to": {
            "kind": "Service",
            "name": "my-quarkus"
          },
          "tls": {
            "termination": "edge"
          }
        }
    },
    {
      "kind": "ImageStream",
      "apiVersion": "v1",
      "metadata": {
        "name": "my-quarkus"
      },
      "spec": {
        "dockerImageRepository": "edwinkun/my-quarkus"
      },
      "status": {
        "dockerImageRepository": ""
      }
    },
    {
        "kind": "DeploymentConfig",
        "apiVersion": "v1",
        "metadata": {
          "name": "my-quarkus",
          "annotations": {
            "template.alpha.openshift.io/wait-for-ready": "true"
          }
        },
        "spec": {
          "strategy": {
            "type": "Rolling",
            "rollingParams": {
              "updatePeriodSeconds": 1,
              "intervalSeconds": 1,
              "timeoutSeconds": 120,
              "pre": {
                "failurePolicy": "Abort",
                "execNewPod": {
                  "command": [
                    "/bin/true"
                  ],
                  "env": [
                  ],
                  "containerName": "my-quarkus"
                }
              },
              "post": {
                "failurePolicy": "Ignore",
                "execNewPod": {
                  "command": [
                    "/bin/true"
                  ],
                  "env": [
                  ],
                  "containerName": "my-quarkus"
                }
              }
            },
            "resources": {}
          },
          "triggers": [
            {
              "type": "ImageChange",
              "imageChangeParams": {
                "automatic": true,
                "containerNames": [
                  "my-quarkus"
                ],
                "from": {
                  "kind": "ImageStreamTag",
                  "name": "my-quarkus:latest"
                }
              }
            },
            {
              "type": "ConfigChange"
            }
          ],
          "replicas": 3,
          "selector": {
            "name": "my-quarkus"
          },
          "template": {
            "metadata": {
              "labels": {
                "name": "my-quarkus"
              }
            },
            "spec": {
              "containers": [
                {
                  "name": "my-quarkus",
                  "image": "my-quarkus",
                  "ports": [
                    {
                      "containerPort": 8080,
                      "protocol": "TCP"
                    }
                  ],
                  "env": [
                  ],
                  "resources": {},
                  "terminationMessagePath": "/dev/termination-log",
                  "imagePullPolicy": "IfNotPresent",
                  "securityContext": {
                    "capabilities": {},
                    "privileged": false
                  }
                }
              ],
              "restartPolicy": "Always",
              "dnsPolicy": "ClusterFirst"
            }
          }
        },
        "status": {}
      }
  ],
  "labels": {
    "template": "my-quarkus-dockerbuild"
  }
}