- Hands On Lab instructions for the Cloud Native Development
- Table of Contents
- Requirements
- Lab Scenario
- Objectives
- Discover the OpenShift platform
- Generate Spring Boot Cloud Native Frontend project using the launcher
- Create a MySQL service instance using the Service Catalog
- Use the launcher to generate a Cloud Native Demo - Backend zip
- Debug your application
- Develop an Arquillian Cube Test
- Use Distributed Tracing to collect app traces
- Show case horizontal scaling
- S2I Build using pipeline
- How to share your feedback
- Lab material for instructors
-
Java - 1.8.x as default JVM
-
Maven - 3.5.x
-
OpenShift oc client - 3.9.0
The purpose of this lab is to develop and deploy 2 microservices on the OpenShift platform; a front and backend and to use a MySQL Database which has been provisioned as a service.
The objectives
section defined hereafter summarizes the features that you will play with this Hands On Lab material.
- Discover OpenShift Cloud Platform
- Play with the different strategies to build a project on the platform
- Develop a real application (frontend, backend, database)
- Use Service Catalog to instantiate a service
- Debug a microservice
- Design a JUnit Integration test using Arquillian Cube
- Enable Distributed Tracing
- Use Jenkins CI/CD and Pipeline
Time: 15min
-
To access to the OpenShift Demo machine, please open your browser at this address
https://195.201.87.126:8443/console/
and log on according to the convention defined hereafter- user : user01, user02, ....., user30
- password : pwd1, pwd2, ......, pwd30
NOTE : The lab will use by convention the following parameter
HETZNER_IP_ADDRESS
to refer to the IP address of the cloud machine running on Hetzner :195.201.87.126
. -
Get the token from the
command-line
screen using this URLhttps://HETZNER_IP_ADDRESS:8443/console/command-line
-
Next, execute this command within your terminal to access to the cluster using your
oc
client tool (making sure you substitute the token with one from the previous step)
oc login https://HETZNER_IP_ADDRESS:8443 --token=3WiSqc3JyW5dkJ5izQvOBVFK-njXTTnpse8ruLiYaoQ
Logged into "https://HETZNER_IP_ADDRESS:8443" as "user01" using the token provided.
You have one project on this server: "user01"
Using project "user01".
- Check the status of the project
oc status
In project `user01` on server https://HETZNER_IP_ADDRESS:8443
You have no services, deployment configs, or build configs.
Run 'oc new-app' to create an application.
- Familiarize your self with the
oc
client and look to the different commands
oc -h
Time: 30 min
The launcher upstream project is at https://github.com/fabric8-launcher
- Access to the launcher using the following URL
http://launcher-cloud-demo.HETZNER_IP_ADDRESS.nip.io
- From the
launcher application
screen, click onlaunch
button
- Within the deployment type screen, click on the button
I will build and run locally
- Next, select your mission :
Cloud Native Demo - Frontend
- Choose
Spring Boot Runtime
- Accept the
Project Info
- Finally click on the button Select
Download as zip file
- Create a folder where you will develop your code
mkdir -p cloud-native-demo
cd cloud-native-demo
- Unzip the generated project within the
cloud-native-demo
folder
cd cloud-native-demo
mv ~/Downloads/booster-demo-front-spring-boot.zip .
unzip booster-demo-front-spring-boot.zip
cd booster-demo-front-spring-boot
- Rename the
artifactId
of the pom.xml from<artifactId>booster-demo-front-spring-boot</artifactId>
to
<artifactId>cloud-native-frontend</artifactId>
- The following steps are optional as the code is part of the project downloaded
- Create a POJO class named
Note
undersrc/main/java/me/snowdrop/cloudnative/front
- Define the following fields and their respective setters/getters:
private Long id;
private String title;
private String content;
private Date createdAt;
private Date updatedAt;
- Add the interface
NoteGateway
within the same package
public interface NoteGateway {
...
}
- Create the CRUD methods for the
NoteGateway
interface. These methods will be implemented by Spring:
List<Note> all();
Note add(Note note);
Note update(Note note);
void delete(long id);
- Add a
NoteController
class within the same packageme.snowdrop.cloudnative.front
- Add the following
Spring
annotations to specify the mapping to be used to expose thenote
service as REST endpoint
@RestController
@RequestMapping("note")
- Add a
NoteGateway noteGateway
field and define it asprivate final
- Add constructor which accepts as parameter
NoteGateway noteGateway
and in the body of the constructor, assign this parameter to the noteGateway fieldthis.noteGateway = noteGateway;
- Create the CRUD / all, add, delete, update methods using as annotation respectively these values and return a
Note
orList<Note>
@GetMapping
public List<Note> all()
@PutMapping
public Note add(@RequestBody Note note)
@DeleteMapping("/{id}")
public DefaultResult delete(@PathVariable("id") long id)
@PostMapping("/{id}")
public Note update(@PathVariable("id") long id, @RequestBody Note note)
- Implement the body of each CRUD method as a passthrough to the NodeGateway instance:
noteGateway.all();
content forall
methodnoteGateway.add(note);
foradd(Note note)
noteGateway.update(note);
forupdate(long id, Note note)
. Note that before callingnoteGateway.update(note);
note.setId(id);
needs to be executedDefaultResult.INSTANCE
as response message fordelete(long id)
Inside NoteController
, define a private static
inner class named DefaultResult
.
private static class DefaultResult {
private final String result = "OK";
public String getResult() {
return result;
}
private static DefaultResult INSTANCE = new DefaultResult();
private DefaultResult() {}
}
- Compile the project
mvn clean compile
- Build and launch spring-boot application locally to ensure the application is working
mvn clean spring-boot:run
-
Open the following URL
http://localhost:8090
within a screen of your web browser -
Deploy the application on the cloud platform using the
s2i
build process
mvn package fabric8:deploy -Popenshift
- Open using the OpenShift UI console the
Routes
tab
- And click on the url of your frontend app
- Check that your
Cloud Native Frontend
is alive !
Time: 15min
Use the OpenShift Console to create a MySQL Service within your project and bind it.
- Within your project - overview, select from the menu
Add to project
, theBrowse catalog
option
- Next select
MySQL (APB)
from the catalog
- Review the information of the database
- Select
development
as plan per default
-
Configure the service by selecting your project and database name
devel
-
Define the user
devel
and passworddevel
- Don't bind the service instance to generate the secret for the moment
- Review the results of the creation of the service
- After a few seconds, a MySQL database should be created within your project and provisioned
-
Generate the secret from the service by clicking on the button
create binding
. The secret created will contain the database info to access it. -
The secret has been created and will be used later to mount it to the
cloud-native-backend
pod
Remark : Alternatively, execute the following command within the cloud-native-backend
project using the definition file provided in order to create a serviceInstance for MySQL
oc create -f openshift/mysql_serviceinstance.yml
Time: 15min
- Access to the launcher using the following URL
http://launcher-cloud-demo.HETZNER_IP_ADDRESS.nip.io
- From the
launcher application
screen, click onlaunch
button - Within the deployment type screen, click on the button
I will build and run locally
- Next, select your mission :
Cloud Native Demo - Backend
- Choose the
Spring Boot Runtime
- Accept the
Project Info
- Finally click on the button Select
Download as zip file
- Unzip the project generated
cd cloud-native-demo
mv ~/Downloads/booster-demo-backend-spring-boot.zip .
unzip booster-demo-backend-spring-boot.zip
cd booster-demo-backend-spring-boot
- Build, launch spring-boot locally to test the in-memory H2 database
mvn clean spring-boot:run -Ph2 -Drun.arguments="--spring.profiles.active=local,--jaeger.sender=http://jaeger-collector-infra.HETZNER_IP_ADDRESS.nip.io/api/traces,--jaeger.protocol=HTTP,--jaeger.port=0"
curl -k http://localhost:8080/api/notes
curl -k -H "Content-Type: application/json" -X POST -d '{"title":"My first note","content":"Spring Boot is awesome!"}' http://localhost:8080/api/notes
curl -k http://localhost:8080/api/notes/1
- Deploy the application on the cloud platform using the
s2i
build process
oc new-app -f openshift/cloud-native-demo_backend_template.yml
- Start the build using project's source
oc start-build cloud-native-backend-s2i --from-dir=. --follow
- Wait until both the build and deployment are done !
- As the pod of the backend doesn't yet has the info to access the database (and therefore can't correctly configure its datasource), the application will report errors within the log of the console.
- Then, from the left menu bar of the console, select
Resources/Secrets
to list the secrets
- Select the secret created previously and containing
credentials
word within its name. Click on the name of the secret. - Click on the button
Add to application
. Select from the drop down list your cloud-backend application and click on the save button
- Next, open the
Applications/Deployments
view and verify that a second deployment of the has been triggered
Remark: If you prefer to do the job without using the console, then execute the following oc commands
- Bind the credentials of the ServiceInstances to a Secret
oc create -f openshift/mysql-secret_servicebinding.yml
- Next, mount the secret of the MySQL service to the
Deploymentconfig
of the backend
oc env --from=secret/spring-boot-notes-mysql-binding dc/cloud-native-backend
- Wait in both cases until the pod is recreated and then test the service
export BACKEND=$(oc get route/cloud-native-backend -o jsonpath='{.spec.host}' -n $(oc project -q))
curl -k $BACKEND/api/notes
curl -k -H "Content-Type: application/json" -X POST -d '{"title":"My first note","content":"Spring Boot is awesome!"}' $BACKEND/api/notes
curl -k $BACKEND/api/notes/1
Remark : if the Lab is deployed on Minishift, then you can get the route using this command #export BACKEND=$(minishift openshift service cloud-native-backend -n cnd-demo --url)
Time: 10min
- Edit the
deploymentConfig
of thecloud-native-frontend
to add this env parameter and redeploy the pod
- name: JAVA_ENABLE_DEBUG
value: 'true'
- Get the
NAME_OF_THE_POD
oc get pods -lapp=cloud-native-frontend
NAME READY STATUS RESTARTS AGE
cloud-native-frontend-2-ck9lz 1/1 Running 0 1m
- Next run this
oc
command to forward the pod traffic of the port5005
to your local remote debugger running at the addresslocalhost:5005
oc port-forward NAME_OF_POD 5005:5005
- Add a breakpoint within the
NoteController
class at the methodgetAll
- Configure a remote debugger into your IDE
- Start your remote debugger locally at the address
5005
- Open the Frontend application within your web browser and click on the button to get all the notes
- Then your remote debugger should stop at the line where the breakpoint has been added
Time: 20min
- Add Arquillian dependencies to the pom.xml file of the
cloud-native-backend
project
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.arquillian.cube</groupId>
<artifactId>arquillian-cube-openshift</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
</exclusion>
</exclusions>
</dependency>
- Create an
LocalTest
class and specify the fieldNoteRepository
. Annotate it with the annotation@Autowired
public class LocalTest {
@Autowired
protected NoteRepository noteRepository;
}
- Next, create 2 methods
testGetEmptyArray
andtestGetOneNote
to test the Note backend
@Test
public void testGetEmptyArray() {
when().get()
.then()
.statusCode(200)
.body(is("[]"));
}
@Test
public void testGetOneNote() {
Note cherry = new Note();
cherry.setContent("cherry");
cherry.setTitle("excellent");
noteRepository.save(cherry);
when().get(String.valueOf(cherry.getId()))
.then()
.statusCode(200)
.body("id", is(cherry.getId().intValue()))
.body("content", is(cherry.getContent()))
.body("title", is(cherry.getTitle()));
}
- Configure Junit using the annotation
@Runwith
to useSpringRunner
. The annotation should be added at the class level.
@RunWith(SpringRunner.class)
- Tell to Spring Boot Test to use a random port. The annotation should be added at the class level.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
- Set the field to use the local random port
@Value("${local.server.port}")
private int port;
- Define the URL of the endpoint to test using
RestAssured.baseURI
@Before
public void beforeTest() {
noteRepository.deleteAll();
RestAssured.baseURI = String.format("http://localhost:%d/api/notes", port);
}
- Test the
LocalTest
Junit class
mvn clean test
- Create the
OpenShiftIT
which will contain the code to test the deployed backend service over it's REST endpoints
public class OpenShiftIT {
}
- Configure Junit using the annotation
@Runwith
to useArquillian
. The annotation should be added at the class level.
@RunWith(Arquillian.class)
- Add the
baseURL
field and specify the following annotations
@AwaitRoute(path = "/health")
@RouteURL("cloud-native-backend")
public URL baseURL;
Remark: The RouteURL is used by arquillian to access the service deployed on the cloud platform
The @AwaitRule
annotation is used in order for Arquillian to wait until the application has stood up properly
- Configure
RestAssured.baseURI
to use the baseURL followed by the address of the endpoint
@Before
public void setup() throws Exception {
RestAssured.baseURI = baseURL + "api/notes";
}
- Finally create the actual test code
@Test
public void testPostGetAndDelete() {
//create a new note
Integer id = given()
.contentType(ContentType.JSON)
.body(new HashMap<String, String>() {{
put("title", "excellent");
put("content", "cherry");
}})
.when()
.post()
.then()
.statusCode(200)
.body("id", not(isEmptyString()))
.body("title", CoreMatchers.is("excellent"))
.extract()
.response()
.path("id");
//fetch the note by id
when().get(id.toString())
.then()
.statusCode(200)
.body("id", CoreMatchers.is(id))
.body("title", CoreMatchers.is("excellent"));
//delete the note
when().delete(id.toString())
.then()
.statusCode(200);
}
- Create an
arquillian.xml
file undersrc/main/resources
folder containing such parameters
<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://jboss.org/schema/arquillian"
xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<extension qualifier="openshift">
<property name="namespace.use.current">true</property>
<property name="env.init.enabled">false</property>
<property name="enableImageStreamDetection">false</property>
</extension>
</arquillian>
- Test the Integration Test using this command
mvn clean verify -Popenshift-it
Time : 15min
As we have decomposed the application into 2 microservices, then we will deploy the technology which is required to collect the logs/traces from the different spring boot applications and to aggregate them using a Distributed Tracer backend. We will use the OpenTracing specification implemented by the Jaeger project and available using the Spring Boot Jaeger starter For that purpose we will modify the existing applications to instrument them with the Spring Boot Jaeger.
- Verify that the pom file contains the
Spring Boot Jaeger starter
dependency
<!-- OpenTracing -->
<dependency>
<groupId>me.snowdrop</groupId>
<artifactId>opentracing-tracer-jaeger-spring-web-starter</artifactId>
<version>0.1</version>
</dependency>
- Update the
Deploymentconfig
of yourcloud-native-backend
project in order to pass these env variables
- name: OPENTRACING_JAEGER_HTTP_SENDER_URL
value: http://jaeger-collector-infra.svc:14268/api/traces
- name: OPENTRACING_JAEGER_SERVICE_NAME
value: cloud-native-backend-YOUR_PROJECT
Remark : you can get your project name using this command $(oc project -q)
- Safe the
Deploymentconfig
and check if the pod has been recreated using this oc command
oc get pods -w
- Open within your browser the url/address of the
jaeger
query -https://jaeger-query-infra.HETZNER_IP.nip.io/
- Select your project from the list and click on the button
Find traces
- Using the
cloud-native-frontend
application, send requests against the backend to fetch data from the database by clicking on the refreshbutton
- Check within the collector screen that traces have been generated
Time : 5min
The OpenShift platform offers a horizontal scaling feature that we will use within this module of the lab in order
to expose behind the cloud-native-frontend
router address 2 pods. By opening the address of the route of the Frontend application,
you will be able to see the pod-name
returned which corresponds to one of the pod load balanced by the Kubernetes API.
- In order to showcase/demo horizontal scaling, then you will execute the following
oc
command to scale the DeploymentConfig of thecloud-native-frontend application
oc scale --replicas=2 dc cloud-native-frontend
- Then, verify that 2 pods are well running
oc get pods -l app=cloud-native-frontend
NAME READY STATUS RESTARTS AGE
cloud-native-frontend-1-2pnbb 1/1 Running 0 3h
cloud-native-frontend-1-cc44g 1/1 Running 0 4h
- Next, open 2 Web browsers or curl to check that you get a response from on of the round robin called pod
curl -v http://cloud-native-frontend-PROJECT_NAME.HETZNER_IP.nip.io/ | grep 'id="_http_booster"'
<h2 id="_http_booster">Frontend at cloud-native-frontend-1-2pnbb</h2>
curl -v http://cloud-native-frontend-PROJECT_NAME.HETZNER_IP.nip.io/ | grep 'id="_http_booster"'
<h2 id="_http_booster">Frontend at cloud-native-frontend-1-cc44g</h2>
Tome : 15min
The Source to Image strategy - aka s2i
proposes different strategies to build a project on OpenShift. During the previous hands on lab modules, we have
used either the mode Binary
or Git
as the source of the project to be build by the s2i
bash script of the image redhat-openjdk-1.8
.
During this module, you will use the Pipeline
strategy where a Jenkinsfile
containing the build scenario will be created using Groovy syntax
for Jenkins. This file will be deployed on the platform as a new BuildConfig
in order to ask that Jenkins creates a Job running within a jnlp java client container
the scenario defined as groovy
script.
- Create a
Jenkinsfile
under thecloud-native-backend
project
cat > Jenkinsfile <<'EOL'
podTemplate(name: 'maven33', label: 'maven33', cloud: 'openshift', serviceAccount: 'jenkins', containers: [
containerTemplate(name: 'jnlp',
image: 'openshift/jenkins-slave-maven-centos7',
workingDir: '/tmp',
envVars: [
containerEnvVar(key: 'MAVEN_MIRROR_URL',value: 'http://nexus.infra.svc:8081/nexus/content/groups/public/'),
],
cmd: '',
args: '${computer.jnlpmac} ${computer.name}')
]){
node("maven33") {
checkout scm
// git url: 'https://github.com/snowdrop/cloud-native-backend.git'
stage("Test") {
sh "mvn test"
}
stage("Use appropriate namespace") {
sh "oc project ${OPENSHIFT_NAMESPACE}"
}
stage("Deploy") {
sh "sed -e 's/changeme/${env.OPENSHIFT_NAMESPACE}/g' src/main/fabric8/dc.yaml -i"
sh "mvn -Popenshift -DskipTests clean fabric8:deploy"
}
}
}
EOL
- Then delete the previously created buildConfig resource
oc delete bc/cloud-native-backend-s2i
- Create a script that can be leveraged to automatically create the pipeline build config
cat > create-pipeline.sh <<'EOL'
#!/usr/bin/env bash
# Creates jenkins pipeline for a namespace
# The only argument passed to the script is the namespace / project into which the pipeline BC will be created
# If no argument is passed, it's assumed that the namespace to be used is the current namespace
# The script assumes that oc login has been performed
namespace=${1:-$(oc project -q)}
substituted_var_filename=jenkins-bc-temp.yml
# change the namespace placeholder
sed -e "s/changeme/${namespace}/g" openshift/jenkins-bc.yaml > ${substituted_var_filename}
# actually create the JenkinsPipeline BuildConfig
oc apply -f ${substituted_var_filename}
# remove temporary file
rm ${substituted_var_filename}
EOL
chmod +x create-pipeline.sh
- Create the new build
./create-pipeline.sh
- Start the new build
oc start-build cloud-native-backend-$(oc project -q)
- Open your project within the OpenShift console and select
Pipelines
under theBuild
screen - Look to your pipeline created and check if the build has been started
- Click on the link
view log
to access to thejenkins job console
- When you will access to the
jenkins
server, then use youruser/password
to log on - Click on the button
Allow selected permissions
- Log on again with your user/password
- Consult the output of the build within the job screen
- To see the progression of the pipeline created and started, then open the OpenShift UI console and move to the tab
Builds/Pipelines
- When the build is finished, select within your OpenShift Console the
overview
screen and access to the newly pod created
As we need your help to improve the Cloud Native Development and Experience
that we are currently developing for you, please report a ticket using github issue
and tag your ticket using the label feedback
under https://github.com/snowdrop/cloud-native-lab
-
List of projects used and containing the code
-
Informations for the instructor concerning how to setup the lab and play with it