Sunday, December 26, 2021

Build Docker Image with Azure DevOps and Push to Docker Hub -JAVA

In this article we will :

  • Build a docker image from a NodeJS application
  • Build the Docker image with an Azure DevOps pipeline
  • Push the Docker image to Docker Hub with Azure

Create an Azure account.

This is a prerequisite to continue this tutorial. Please note that you can go for the free-tier for now. The link HERE.

Create a Git Repository

I went for the Gitlab free account, but any git repo will do the job.

Create a Docker Hub repository

Again, the free account is fully sufficient to start. Only limitation is number of repositories and users. Link HERE.

Your App

For the tutorial, I’ll build a simple NodeJS app, following their Hello World.

Dockerize the App

Let’s say, we have our Micro Service in whatever language (we don’t care because the goal is to put it in a docker container with the deployment stack).

It can be a NodeJS app, a Spring Boot App, a ReactJS front…

Dockerfile & .dockerignore

Everything is explained in the NodeJS documentation.

Resulting Dockerfile

Test on local with a docker build if you want to be sure it works

Azure Pipeline

Create a new Build Pipeline

  • Select Other git for any git repository to continue with the configuration screens.
  • Create a new service connection for your Git Repo
  • Set the default branch (if you don’t know or care, surely it’s master)
  • Select the Docker Container template

We can see now that our pipeline has 2 steps, we will configure them

Build an Image (docker build)

  • Create a new Docker Registry Service Connection
  • Select “Container Registry” as “Container Registry Type”. Instead of Azure Registry because we will build our image and push to Docker Hub instead of Azure Registry
  • Change the Image name to reflect you Docker Hub’s path and project name/version

This example assumes that my Docker Hub username is : hubuser

My repository is : hubrepo

My image name : 1 (Build Id, incremented at each pipeline execution)

Push an Image (docker push)

  • Again, the Container Registry and the Service Connection
  • The Image name, matching the name defined earlier

And that’s it! We can save the job and queue.

Monday, December 20, 2021

Azure DevOps CI/CD Pipeline — JAVA

 

Here, For the beginners I explained about “How to implement Azure DevOps Pipeline”

Image Copied from Azure

Introduction

Using tools like Jenkins does not mean you follow CI/CD.

Continuous Integration (CI) is a development practice. In which developers integrate code into a shared repository frequently, preferably several times a day. An automated build or automated tests should verify each integration build.

Mostly, we follow below flow for CI/CD.

Above image copied from “Release It! Design and Deploy by Michael Nygard”

In Continuous integration — For individual commits — the Azure pipeline will build a container image (prefer docker image), then test it and push it to container registry.

Create Azure Devops Project

Create new repository

*** Refer to the last section for cloning a repository from your Github account.

|
`-- microservice1
|-- Dockerfile
|-- pom.xml
`-- src
|-- main
| |-- java
| | `-- edu
| | `-- sampleapplication
| | `-- MS1
| | |-- ApiController.java
| | `-- Ms1Application.java
| `-- resources
| |-- application.properties
| `-- logback-spring.xml
`-- test
`-- java
`-- edu
`-- sampleapplication
`-- MS1
`-- Ms1ApplicationTests.java

Build pipeline

Do click “Set up Build button” for building a pipeline.

Here, I am selecting an option to build a docker image and push it to the Azure container registry.

Azure pipeline —

Default pipeline YAML file will contain stages like build with the steps or tasks to push the docker image to the container registry.

# Docker
# Build and push an image to Azure Container Registry
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
trigger:
- master
resources:
- repo: self
variables:
# Container registry service connection established during pipeline creation
dockerRegistryServiceConnection: '8eb2472c-86b9-4669-871b-9b9872670821'
imageRepository: 'springazuredemo'
containerRegistry: 'ritresh.azurecr.io'
dockerfilePath: '$(Build.SourcesDirectory)/microservice1/Dockerfile'
tag: '$(Build.BuildId)'

# Agent VM image name
vmImageName: 'ubuntu-latest'
stages:
- stage: Build
displayName: Build and push stage
jobs:
- job: Build
displayName: Build
pool:
vmImage: $(vmImageName)
steps:
- task: Docker@2
displayName: Build and push an image to container registry
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: $(dockerfilePath)
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
We need to add maven tasks to build the spring boot application as default conf won’t work for our set up.
# Docker
# Build and push an image to Azure Container Registry
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
trigger:
- master
resources:
- repo: self
variables:
# Container registry service connection established during pipeline creation
dockerRegistryServiceConnection: 'c1a4b0b1-bbda-4066-810a-a3b646af911f'
imageRepository: 'springazuredemo'
containerRegistry: 'ritresh.azurecr.io'
dockerfilePath: '$(Build.SourcesDirectory)/microservice1/Dockerfile'
tag: '$(Build.BuildId)'

# Agent VM image name
vmImageName: 'ubuntu-latest'
stages:
- stage: Build
displayName: Build and push stage
jobs:
- job: Build
displayName: Build
pool:
vmImage: $(vmImageName)
steps:
- task: Maven@3
inputs:
mavenPomFile: 'microservice1/pom.xml'
mavenOptions: '-Xmx3072m'
javaHomeOption: 'JDKVersion'
jdkVersionOption: '1.8'
jdkArchitectureOption: 'x64'
publishJUnitResults: false
goals: 'clean install'
- task: Docker@2
displayName: Build and push an image to container registry
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: $(dockerfilePath)
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)

Azure container registry —

Let’s check the Azure container registry, here we could see the newly build docker image under springazuredemo repository.

Lets Run it on local environment

:study ritgirdh$ docker login ritresh.azurecr.io
Username: ritresh
Password:
Login Succeeded
:study ritgirdh$ docker pull ritresh.azurecr.io/springazuredemo:17
17: Pulling from springazuredemo
e7c96db7181b: Already exists
f910a506b6cb: Already exists
c2274a1a0e27: Already exists
8b7b20a1fb06: Pull complete
Digest: sha256:13f46fce6f2910c6fe84e9ff8459ab1a2973740df442717c99c79a9016fe1caa
Status: Downloaded newer image for ritresh.azurecr.io/springazuredemo:17
ritresh.azurecr.io/springazuredemo:17
WKMIN1307242:study ritgirdh$ docker run -p5555:8080 ritresh.azurecr.io/springazuredemo:17
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.0.RELEASE)
2020-05-06 11:55:45.969 INFO 1 --- [ main] e.sampleapplication.MS1.Ms1Application : Starting Ms1Application v1.0.0-SNAPSHOT on 8c5ac374947d with PID 1 (/ms1.jar started by root in /)
2020-05-06 11:55:45.983 INFO 1 --- [ main] e.sampleapplication.MS1.Ms1Application : No active profile set, falling back to default profiles: default
2020-05-06 11:55:46.086 INFO 1 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@38cccef: startup date [Wed May 06 11:55:46 GMT 2020]; root of context hierarchy
2020-05-06 11:55:48.285 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-05-06 11:55:48.353 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-05-06 11:55:48.353 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.28
2020-05-06 11:55:48.381 INFO 1 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/usr/lib/jvm/java-1.8-openjdk/jre/lib/amd64/server:/usr/lib/jvm/java-1.8-openjdk/jre/lib/amd64:/usr/lib/jvm/java-1.8-openjdk/jre/../lib/amd64:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib]
2020-05-06 11:55:48.579 INFO 1 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-05-06 11:55:48.579 INFO 1 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2499 ms
2020-05-06 11:55:49.257 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2020-05-06 11:55:49.265 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2020-05-06 11:55:49.265 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2020-05-06 11:55:49.265 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2020-05-06 11:55:49.266 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2020-05-06 11:55:49.266 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpTraceFilter' to: [/*]
2020-05-06 11:55:49.266 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'webMvcMetricsFilter' to: [/*]
2020-05-06 11:55:49.792 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@38cccef: startup date [Wed May 06 11:55:46 GMT 2020]; root of context hierarchy
2020-05-06 11:55:49.923 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/message]}" onto java.lang.String edu.sampleapplication.MS1.ApiController.getMessage()
2020-05-06 11:55:49.925 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/v1],methods=[GET]}" onto public java.lang.String edu.sampleapplication.MS1.ApiController.test()
2020-05-06 11:55:49.929 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2020-05-06 11:55:49.930 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2020-05-06 11:55:50.015 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-05-06 11:55:50.015 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-05-06 11:55:50.095 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-05-06 11:55:50.938 INFO 1 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2020-05-06 11:55:50.946 INFO 1 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6c779568: startup date [Wed May 06 11:55:50 GMT 2020]; parent: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@38cccef
2020-05-06 11:55:51.012 INFO 1 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'handlerExceptionResolver' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration; factoryMethodName=handlerExceptionResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.actuate.autoconfigure.web.servlet.WebMvcEndpointChildContextConfiguration; factoryMethodName=compositeHandlerExceptionResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.class]]
2020-05-06 11:55:51.097 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8001 (http)
2020-05-06 11:55:51.099 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-05-06 11:55:51.100 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.28
2020-05-06 11:55:51.122 INFO 1 --- [ost-startStop-1] o.a.c.c.C.[Tomcat-1].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-05-06 11:55:51.123 INFO 1 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 177 ms
2020-05-06 11:55:51.180 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2020-05-06 11:55:51.256 INFO 1 --- [ main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/health],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
2020-05-06 11:55:51.258 INFO 1 --- [ main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/info],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
2020-05-06 11:55:51.260 INFO 1 --- [ main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto protected java.util.Map<java.lang.String, java.util.Map<java.lang.String, org.springframework.boot.actuate.endpoint.web.Link>> org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.links(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2020-05-06 11:55:51.334 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public java.util.Map<java.lang.String, java.lang.Object> org.springframework.boot.actuate.autoconfigure.web.servlet.ManagementErrorEndpoint.invoke(org.springframework.web.context.request.ServletWebRequest)
2020-05-06 11:55:51.342 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-05-06 11:55:51.343 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-05-06 11:55:51.377 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6c779568: startup date [Wed May 06 11:55:50 GMT 2020]; parent: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@38cccef
2020-05-06 11:55:51.585 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8001 (http) with context path ''
2020-05-06 11:55:51.615 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-06 11:55:51.619 INFO 1 --- [ main] e.sampleapplication.MS1.Ms1Application : Started Ms1Application in 6.497 seconds (JVM running for 7.807)

Lets test it on local —

curl -ivk localhost:5555/v1   
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5555 (#0)
> GET /v1 HTTP/1.1
> Host: localhost:5555
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200
HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
Content-Type: text/plain;charset=UTF-8
< Content-Length: 13
Content-Length: 13
< Date: Wed, 06 May 2020 11:58:35 GMT
Date: Wed, 06 May 2020 11:58:35 GMT
<
* Connection #0 to host localhost left intact
OKHello world

Clone existing repo from Github

Here, I am cloning existing github repo https://github.com/RitreshGirdhar/SpringBoot-Docker-Ansible. And for this demo I have deleted some of the files related to ansible which don’t belong to this Azure-devops demo.

The only purpose of this article is to demonstrate the purpose of Continuous Integration.

We could Configure the Azure pipeline for every Merge request or pull request.

It will build the application and run the default test cases. Once the application gets build successfully, it will containerize the application and push it to the specified container registry.

Later, the pipeline would deploy the application on some azure server or some other service via ansible or native commands.