Spring Microservices

Objective:

To develop micro services using Spring. We will also look at how to containarize it with Docker and orchestrate via kubernates.

What is a Web Service?

In layman terms, Service delivered over the web.
1. designed to communicate from one application to application
2. not platform dependent (interoperable)
3. should allow communication over a network.

Service Definition defines structure of request and response and other details
Data exchange takes place using xml or json.

Key terms:
1. Request => input to a web service
2. Response => output from a web service
3. Message Exchange Format => XML or JSON
4. Service Provider - which hosts the web service
5. Service Consumer - which is consuming web service
6. Service Definition - contract b/w service provider and service customer
7. Transport - How service is called like HTTP or MQ - web sphere MQ ( more like a queue ) - I mean communication happens over a queue.

Web service Groups
1. SOAP - xml request/response
    SOAP-ENV: Envelope contains header and body
    SOAP-ENV: Header => contains meta information like authentication/authorisation etc.
    SOAP-ENV: Body => xml request
    Service Definition - WSDL ( web service definition language )
2. REST - Representational state transfer
    makes best use of HTTP
    HTTP methods (GET, PUT, POST, DELETE)
    HTTP response codes like 200, 404
    URI ( Uniform Resource Identifier) - that is exposed to outside world.
    Ex: /user/good/1
    doesn't matter how request is configured like XML, JSON, HTTP (but JSON is popular)
    Transport happens only via HTTP
    Service Definition: No standard, WADL(web application definition language), Swagger

Restful Services with Spring Boot

1. Create your project with spring initializr - https://start.spring.io/
2. Select dependencies and click on Generate button (which gives you a maven project to get started with work)
    a. Spring Web
    b. Spring Boot Dev Tools
    c. Spring Data JPA
    d. H2 Database
3. import project into your favorite IDE ( I use IntelliJ ). From src/main/java, you can run spring boot application.
4. Let's try developing Restful webservices
    Social Media Application    
    User -> Posts

    Retrieve all Users - GET /users
    Create a User - POST /users
    Retrieve One User - GET /users/{id} -> /users/1
    Delete a user - DELETE /users/{id} -> /users/1

    Retrieve all posts for a User - GET /users/{id}/posts
    Create a posts for a User - POST /users/{id}/posts
    Retrieve details of a post - GET /users/{id}/posts/{post_id}
Note: ideally we won't expose id's, but for understanding we start with this.
5. Creating hello world web service :-)
    Define a controller
    Mapping - GET/POST/PUT/DELETE
    URI
@RestController
public class HelloWorldController {
//@RequestMapping(method = RequestMethod.GET, path = "/hello-world")
@GetMapping(path = "/hello-world")
public String helloWorld(){
return "Hello World";
}
    Invoke this url - http://localhost:8080/hello-world
    output: Hello World
Note : either we can use @GetMapping or @RequestMapping
6. Enhance Hello World to return a Bean
@GetMapping(path = "/hello-world-bean")
public HelloWorldBean helloWorldBean(){
return new HelloWorldBean("Hello World");
}
package com.vinay.rest.webservices.vinayrestfulwebservices;

public class HelloWorldBean {

private String message;

public HelloWorldBean(String message) {
this.message = message;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

@Override
public String toString() {
return "HelloWorldBean{" +
"message='" + message + '\'' +
'}';
}
}
output:

{
"message": "Hello World"}

Note: it's a JSON response
7. Quick View of Spring Boot Auto configuration
Lets first enable debug
logging.level.org.springframework = debug
In auto configuration, Servlets, ErrorMvc, Http etc are auto configured by spring boot which are in class path. 
Jackson Beans auto convert bean to json.
dispatcher servlet handles all the requests, aware of all the mappings, finds out the right one to execute, response mapping (Jackson).
8. Enhancing hello - world service with path variable
@GetMapping(path = "/hello-world/path/{name}")
public HelloWorldBean helloWorldPathVariable(@PathVariable String name){
return new HelloWorldBean(String.format("Hello World, %s", name));
}
output:

{
"message": "Hello World, vinay"}

9. Creating User Bean and User service
Let's use static arrays for now, once JPA is discussed we can use JPA's.
Let's create a User bean, UserDao(which is used for communicating with DB)
package com.vinay.rest.webservices.vinayrestfulwebservices.user;

import java.util.Date;

public class User {
private Integer id;
private String name;
private Date birthDate;

public User(Integer id, String name, Date birthDate) {
this.id = id;
this.name = name;
this.birthDate = birthDate;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Date getBirthDate() {
return birthDate;
}

public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", birthDate=" + birthDate +
'}';
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.user;

import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Component
public class UserDaoService {
private static List<User> users = new ArrayList<>();
private static int usersCount = 3;
static{
users.add(new User(1, "Alex", new Date()));
users.add(new User(2, "Rahim", new Date()));
users.add(new User(3, "Ram", new Date()));
}

public List<User> findAll(){
return users;
}

public User save(User user){
if(user.getId() == null){
user.setId(++usersCount);
}
users.add(user);
return user;
}

public User findOne(int id){
for(User user: users){
if(user.getId() == id){
return user;
}
}
return null;
}
}
9. Implementing GET methods for User Resource
Let's create UserResource which is a controller
package com.vinay.rest.webservices.vinayrestfulwebservices.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserResource {
@Autowired
private UserDaoService userDaoService;

@GetMapping(path = "/users")
public List<User> retrieveAllUsers(){
return userDaoService.findAll();
}

@GetMapping(path="/users/{id}")
public User retrieveUser(@PathVariable int id){
return userDaoService.findOne(id);
}
}
output: for /users

[
{"id": 1,"name": "Alex","birthDate": "2022-07-27T15:31:46.609+00:00"},{"id": 2,"name": "Rahim","birthDate": "2022-07-27T15:31:46.609+00:00"},{"id": 3,"name": "Ram","birthDate": "2022-07-27T15:31:46.609+00:00"}]

output: for /users/1

{
"id": 1,"name": "Alex","birthDate": "2022-07-27T15:31:46.609+00:00"}

10. Implementing POST method to create user resources
@PostMapping(path = "/users")
public User createUser(@RequestBody User user){
return userDaoService.save(user);
}
Make sure in bean to add default constructor
protected User(){}
Use Postman to work with GET and POST calls.
11. Let's enhance this POST method
Lets add status code and location to the header
@PostMapping(path = "/users")
public ResponseEntity<Object> createUser(@RequestBody User user){
User savedUser = userDaoService.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
After running post call
status code is 201 and in header, location parameter will be updated with new uri.
12. Implementing Exception handling
when user not found, let try to throw 404 not found for get user call
@GetMapping(path="/users/{id}")
public User retrieveUser(@PathVariable int id) throws UserNotFoundException {
User user = userDaoService.findOne(id);
if(user == null){
throw new UserNotFoundException("id-"+id);
}
return user;
}
package com.vinay.rest.webservices.vinayrestfulwebservices.user;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
13. Let's generalize exception handling so that it's consistent across all platforms
we can make use of ResponseEntityExceptionHandler
annotate with @RestController, as it sends response back
annotate with @ControllerAdvise, which is applicable to all controllers or resources.
package com.vinay.rest.webservices.vinayrestfulwebservices.exception;

import com.vinay.rest.webservices.vinayrestfulwebservices.user.UserNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.Date;

@ControllerAdvice
@RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request){
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}

@ExceptionHandler(UserNotFoundException.class)
public final ResponseEntity<Object> handleUserNotFoundExceptions(UserNotFoundException ex, WebRequest request){
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
}

}
output:
{
"timestamp": "2022-07-27T18:32:35.672+00:00",
"message": "id-100",
"details": "uri=/users/100"
}
14. Let's add Delete mapping
@DeleteMapping(path="/users/{id}")
public void deleteUser(@PathVariable int id) throws UserNotFoundException {
User user = userDaoService.deleteById(id);
if(user == null){
throw new UserNotFoundException("id-"+id);
}
}
public User deleteById(int id){
Iterator<User> iterator = users.iterator();
while(iterator.hasNext()){
User user = iterator.next();
if(user.getId() == id){
iterator.remove();
return user;
}
}
return null;
}
15. Adding dependency for validation
use validation dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
16. Implementing Validations for Restful Services
validate content
validate api and hibernate api has the details 
@Min (minimum number ), @Past (date should be a past date), which are defined in User class
@Size(min = 2, message = "Name should have atleast 2 characters")
private String name;

@Past
private Date birthDate;
implementation in Post call
@Valid annotation will validate your input request
@PostMapping(path = "/users")
public ResponseEntity<Object> createUser(@Valid @RequestBody User user){
User savedUser = userDaoService.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
implementation in Customized exception class
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "validation failed", ex.getBindingResult().toString());
return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST);
}
17. Implementing HATEOAS for restful services
HATEOAS - hypermedia as the engine of application state
allows you to return resources that are related to the endpoint
@GetMapping(path="/users/{id}")
public EntityModel<User> retrieveUser(@PathVariable int id) throws UserNotFoundException {
User user = userDaoService.findOne(id);
if(user == null){
throw new UserNotFoundException("id-"+id);
}
EntityModel<User> model = EntityModel.of(user);
WebMvcLinkBuilder linkToUsers = linkTo(methodOn(this.getClass()).retrieveAllUsers());
model.add(linkToUsers.withRel("all-users"));
return model;
}
output:
{
"id": 1,
"name": "Alex",
"birthDate": "2022-07-27T19:47:38.427+00:00",
"_links": {
"all-users": {
"href": "http://localhost:8080/users"
}
}
}
18. internationalization of restful services (i18n)
MessageSource provide by spring boot helps with reading data from message.properties
@GetMapping(path = "/hello-world-internationalized")
public String helloWorldInternationalized(@RequestHeader(name="Accept-Language", required = false)Locale locale){
return messageSource.getMessage("good.morning.message", null,
"default message", locale);//LocaleContextHolder.getLocale());
}
without passing request header, we can do it from LocaleContextHolder
messages.properties
good.morning.message=Good Morning
messages_fr.properties
good.morning.message=Bonjour
Depending on accept-Language from header, corresponding messages will print.
19. Content Negotiation
let's see xml representation
dependency
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
in headers, just specify Accept --> application/xml
response representation will be in xml (how cool is that :-)).
20. Configuring Auto Generation of Swagger documentation
we use open api
Resource: https://springdoc.org/
invoke url - http://localhost:8080/swagger-ui/index.html
which gives a details on the endpoints and schemas.
21. Introduction to swagger documentation Format
Api docs can be seen from here - http://localhost:8080/v3/api-docs
which contains info, server, path and components.
22. Monitoring API's with Spring boot Actuator
Have this dependency installed
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
invoke http://localhost:8080/actuator
output consists of health check, in addition we have other url's as well, which can be configured via application.properties
management.endpoints.web.exposure.include=*
now we can see additonal links, like bean, metrics, thread dumps, heap dumps etc.
Note: generally we use actuator to check health of the application, whether it's up or not.
23. Visualizing API's with HAL explorer
Earlier its called HAL browser, now it's explorer
have this dependency in your pom
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-explorer</artifactId>
</dependency>
invoke http://localhost:8080 --> which invokes HAL explorer
visual representation of API's, simpler way to explore API's
24. Implementing static filtering for Restful services
filter data that is being shown to Consumer
one way of doing is it by using @JsonIgnore on the field
other approaoch would use of @JsonIgnoreProperties
package com.vinay.rest.webservices.vinayrestfulwebservices.filtering;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(value = {"field1", "field2"})
public class SomeBean {
private String field1;
private String field2;
//@JsonIgnore
private String field3;

public SomeBean(String field1, String field2, String field3) {
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}

public SomeBean(){}

public String getField1() {
return field1;
}

public void setField1(String field1) {
this.field1 = field1;
}

public String getField2() {
return field2;
}

public void setField2(String field2) {
this.field2 = field2;
}

public String getField3() {
return field3;
}

public void setField3(String field3) {
this.field3 = field3;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.filtering;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
public class FilteringController {
@GetMapping(path = "/filtering")
public SomeBean retrieveSomeBean(){
return new SomeBean("value1", "value2", "value3");
}

@GetMapping(path = "/filtering-list")
public List<SomeBean> retrieveListOfSomeBean(){
return Arrays.asList(new SomeBean("value1", "value2", "value3"),
new SomeBean("value11", "value12", "value13"));
}
}
25. Implementing Dynamic Filtering for Restful Service
package com.vinay.rest.webservices.vinayrestfulwebservices.filtering;

import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
public class FilteringController {
@GetMapping(path = "/filtering")
public MappingJacksonValue retrieveSomeBean(){
SomeBean someBean = new SomeBean("value1", "value2", "value3");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");
FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(someBean);
mapping.setFilters(filters);
return mapping;
}

@GetMapping(path = "/filtering-list")
public MappingJacksonValue retrieveListOfSomeBean(){
List<SomeBean> list = Arrays.asList(new SomeBean("value1", "value2", "value3"),
new SomeBean("value11", "value12", "value13"));

SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field2", "field3");
FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(list);
mapping.setFilters(filters);
return mapping;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.filtering;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

//@JsonIgnoreProperties(value = {"field1", "field2"})
@JsonFilter("SomeBeanFilter")
public class SomeBean {
private String field1;
private String field2;
//@JsonIgnore
private String field3;

public SomeBean(String field1, String field2, String field3) {
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}

public SomeBean(){}

public String getField1() {
return field1;
}

public void setField1(String field1) {
this.field1 = field1;
}

public String getField2() {
return field2;
}

public void setField2(String field2) {
this.field2 = field2;
}

public String getField3() {
return field3;
}

public void setField3(String field3) {
this.field3 = field3;
}
}
Note: Make sure you're defining the filter @JsonFilter
26. Versioning Restful Services -  Basic Approach with URI's
How do we create different versions of same service. For an example, Person should return full name, For some clients it should return first name.
A basic approach can be adding getmapping with different URI's
package com.vinay.rest.webservices.vinayrestfulwebservices.versioning;

public class Person {
String name;

public Person() {
}

public Person(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.versioning;

public class PersonV2 {
private Name name;

public PersonV2() {
}

public PersonV2(Name name) {
this.name = name;
}

public Name getName() {
return name;
}

public void setName(Name name) {
this.name = name;
}
}
package com.vinay.rest.webservices.vinayrestfulwebservices.versioning;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PersonVersionController {
@GetMapping("v1/person")
public Person person(){
return new Person("Vinay Raghumanda");
}

@GetMapping("/v2/person")
public PersonV2 personV2(){
return new PersonV2(new Name("Vinay", "Raghumanda"));
}
}
output:
{
"name": {
"firstName": "Vinay",
"lastName": "Raghumanda"
}
}
{
"name": "Vinay Raghumanda"
}
27. Versioning Restful Services - using param
@GetMapping(value="/person/param", params = "version=1")
public Person paramV1(){
return new Person("Vinay Raghumanda");
}

@GetMapping(value="/person/param", params = "version=2")
public PersonV2 paramV2(){
return new PersonV2(new Name("Vinay", "Raghumanda"));
}
http://localhost:8080/person/param?version=1
http://localhost:8080/person/param?version=2
Using Header Versioning
@GetMapping(value="/person/header", headers = "X-API-VERSION=1")
public Person headerV1(){
return new Person("Vinay Raghumanda");
}

@GetMapping(value="/person/header", headers = "X-API-VERSION=2")
public PersonV2 headerV2(){
return new PersonV2(new Name("Vinay", "Raghumanda"));
}
http://localhost:8080/person/header
IN headers pass key and value as X-API-VERSION, 1 or 2
Using Produces
@GetMapping(value="/person/produces", produces = "application/vnd.company.app-v1+json")
public Person producesV1(){
return new Person("Vinay Raghumanda");
}

@GetMapping(value="/person/produces", produces = "application/vnd.company.app-v2+json")
public PersonV2 producesV2(){
return new PersonV2(new Name("Vinay", "Raghumanda"));
}
http://localhost:8080/person/produces
In headers pass key and values as ACCEPT, application/vnd.company.app-v2+json
Cons:
URI Pollution
28. Implementing basic authentication with spring security
basic authentication is by passing username and password
digest authentication --> where password is digested and that is sent across all
oauth/oauth2 authentication

Lets take a look at basic authentication
Add this dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
something like below will be generated after you restarting application
Using generated security password: 6398c32a-36df-4b79-88f2-d7703fb55e27
Now try running any of your endpoints, which will throw 401 unauthorized error.
now configure Authorization type --> Basic Auth --> user/generated password
Note: default username is user
you can even set your own username and password, by configuring application.properties
spring.security.user.name=user
spring.security.user.password=test
29. Create User Entity and Some test data (Focussing on JPA)
Let's disable security for a while by commenting spring boot starter security
Define @Entity on User class, primary key for @id
Add below properties to application.properties
spring.jpa.show-sql=true
spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true
spring.data.jpa.repositories.bootstrap-mode=default
on Running application, you should see 
Hibernate: create table custom_user (id integer not null, birth_date timestamp, name varchar(255), primary key (id))
let's create data.sql file to insert data into custom_user table
insert into user values(1, CURRENT_TIMESTAMP(), 'Block');
insert into user values(2, CURRENT_TIMESTAMP(), 'Chain');
insert into user values(3, CURRENT_TIMESTAMP(), 'Ethereum'); 
and navigate to http://localhost:8080/h2-console
make sure JDBC_URL is jdbc:h2:mem:testdb
connect to db and fire select * from user, table should contain above data.
30. Lets Update GET methods on User Resource to use JPA
Create UserJPAResource and UserRepository
package com.vinay.rest.webservices.vinayrestfulwebservices.user;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

}
Observe UserRepository is an interface that is extending one with User an entity and primary key type.
Let's take a look at Get mapping
@Autowired
private UserRepository userRepository;

@GetMapping(path = "/jpa/users")
public List<User> retrieveAllUsers(){
return userRepository.findAll();
}

@GetMapping(path="/jpa/users/{id}")
public EntityModel<User> retrieveUser(@PathVariable int id) throws UserNotFoundException {
Optional<User> customUser = userRepository.findById(id);
if(!customUser.isPresent()){
throw new UserNotFoundException("id-"+id);
}
EntityModel<User> model = EntityModel.of(customUser.get());
WebMvcLinkBuilder linkToUsers = linkTo(methodOn(this.getClass()).retrieveAllUsers());
model.add(linkToUsers.withRel("all-users"));
return model;
}
Observe the inbuilt methods that are being used findAll, findById, isPresent(), get()
31. Updating POST and DELETE methods on User Resource to use JPA
as simple as
@DeleteMapping(path="/jpa/users/{id}")
public void deleteUser(@PathVariable int id) throws UserNotFoundException {
userRepository.deleteById(id);
}

@PostMapping(path = "/jpa/users")
public ResponseEntity<Object> createUser(@Valid @RequestBody User customUser){
User savedCustomUser = userRepository.save(customUser);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(savedCustomUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
32. Creating Post Entity and many to one Relationships with User Entity
Let's create Entity called POST
A user can post many. it's a many to one relatioship.
package com.vinay.rest.webservices.vinayrestfulwebservices.user;

import javax.persistence.*;
import java.util.List;

@Entity
public class Post {

@Id
@GeneratedValue
private Integer Id;

private String description;

@ManyToOne(fetch = FetchType.LAZY)
private User user;

public Integer getId() {
return Id;
}

public void setId(Integer id) {
Id = id;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

@Override
public String toString() {
return "Post{" +
"Id=" + Id +
", description='" + description + '\'' +
'}';
}
}
Observe many to one annotation
in user it's one to many
@Entity
public class User {
@Id
@GeneratedValue
private Integer id;

@Size(min = 2, message = "Name should have atleast 2 characters")
private String name;

@Past
private Date birthDate;

@OneToMany(mappedBy = "user")
private List<Post> posts;
Observe One to many annotation
On running application, 2 tables will be created post and user
Hibernate: create table post (id integer not null, description varchar(255), user_id integer, primary key (id))
Hibernate: create table user (id integer not null, birth_date timestamp, name varchar(255), primary key (id))

Let's try to add some data
insert into user values(1001, CURRENT_TIMESTAMP(), 'Block');
insert into user values(1002, CURRENT_TIMESTAMP(), 'Chain');
insert into user values(1003, CURRENT_TIMESTAMP(), 'Ethereum');
insert into post values(11001, 'First Post', 1001);
insert into post values(11002, 'Second Post', 1001);
33. Implementing GET service to retrieve all Posts
in JPA resource
@GetMapping(path = "/jpa/users/{id}/posts")
public List<Post> retrieveAllPosts(@PathVariable int id){
Optional<User> userOp = userRepository.findById(id);
if(!userOp.isPresent()){
throw new UserNotFoundException("id-"+id);
}
return userOp.get().getPosts();
}
In Post , ignore User as it might become recursive
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnore
private User user;
34. Implementing Post service to create POST service for the user
@PostMapping(path = "/jpa/users/{id}/posts")
public ResponseEntity<Object> createPost(@PathVariable int id, @RequestBody Post post){
Optional<User> userOp = userRepository.findById(id);
if(!userOp.isPresent()){
throw new UserNotFoundException("id-"+id);
}
post.setUser(userOp.get());
postRepository.save(post);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(post.getId()).toUri();
return ResponseEntity.created(location).build();
}
package com.vinay.rest.webservices.vinayrestfulwebservices.user;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PostRepository extends JpaRepository<Post, Integer> {

}
35. Richardon maturity model
1. expose soap web services in rest style --> level 1
2. expose resources with proper URI --> level 2
3. level 2 + hateoas --> level 3

Quick Introduction to Micro services

1. Introduction to micro services
small autonomous services that work together.
in short,
a. exposed by REST
b. small and well chosen deployable units
c. cloud enabled
2. challengest with micro services
a. bounded content which is difficult (very difficult to identify boudaries)
b. configuration management (how many pods to be up and runnning)
c. dynamic scale up and scale down
d. visibility (finding logs is tricky)
e. pack of cards ( one built on another, very easy to collapse)
3. introduction to spring cloud
1. springCloudConfigServer helps to store all configs
2. dynamic scale up and down can be done
    a. naming server (Eureka)
    b. Spring cloud load balancer (Client side load balancer)
    c. Feign (easier rest clients)
3. visibility and monitoring
    a. zipkin distributed tracing
    b. netflix api gateway
4. Fault tolerance
    a. Hystrix (when service is down, hystrix gives us some common response)/Resilience4j
4. Advantages of micro services architecture
1. new technology and process adaptation
2. dynamic scaling
3. faster release cycles
5. Standardizing ports 
refer to Ports and Url's in https://github.com/in28minutes/spring-microservices/tree/master/03.microservices

Micro services with Spring cloud

1. Setting up limits microservice
a. Let's create a project from spring.io and dependencies for spring web, dev tools, actuator (to monitor), config client(which connects to spring config cloud server).
b. in application.properties add
spring.config.import=optional:configserver:http://localhost:8888
2. Creating hard coded limits service
package com.vinay.microservices.limitservice.controller;

import com.vinay.microservices.limitservice.bean.Limit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LimitController {
@GetMapping("/limits")
public Limit retrieveAllLimits(){
return new Limit(1, 1000);
}
}
package com.vinay.microservices.limitservice.bean;

public class Limit {
private Integer minimum;
private Integer maximum;

public Limit() {
}

public Limit(Integer minimum, Integer maximum) {
this.minimum = minimum;
this.maximum = maximum;
}

public Integer getMinimum() {
return minimum;
}

public void setMinimum(Integer minimum) {
this.minimum = minimum;
}

public Integer getMaximum() {
return maximum;
}

public void setMaximum(Integer maximum) {
this.maximum = maximum;
}

@Override
public String toString() {
return "Limit{" +
"minimum=" + minimum +
", maximum=" + maximum +
'}';
}
}
invoke GET call --> http://localhost:8080/limits
 output:
{
"minimum": 1,
"maximum": 1000
}
3. Enhance limits service - get configuration from application.properties
spring.config.import=optional:configserver:http://localhost:8888
limit-service.minimum=2
limit-service.maximum=998
package com.vinay.microservices.limitservice.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties("limit-service")
public class Configuration {
private int maximum;
private int minimum;

public int getMaximum() {
return maximum;
}

public void setMaximum(int maximum) {
this.maximum = maximum;
}

public int getMinimum() {
return minimum;
}

public void setMinimum(int minimum) {
this.minimum = minimum;
}
}
package com.vinay.microservices.limitservice.controller;

import com.vinay.microservices.limitservice.bean.Limit;
import com.vinay.microservices.limitservice.configuration.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LimitController {

@Autowired
private Configuration configuration;

@GetMapping("/limits")
public Limit retrieveAllLimits(){
return new Limit(configuration.getMinimum(), configuration.getMaximum());
}
}
4. Setting up spring cloud config server
Let's create spring cloud config server project from spring.io
add dependencies for dev tools and config server
add properties to application.properties
spring.application.name=spring-cloud-config-server
server.port=8888
spring-cloud-config-server runs in port 8888
5. Let's create a local git repository
create a folder
git init
add limit.properties file with
limit-service.minimum=4
limit-service.maximum=990
commit the changes
6. Connect spring cloud config server to local git
Add this to application properties of spring cloud config
spring.cloud.config.server.git.uri=file:///Users/vraghuma/FSE/git-localconfig-repo
annotate application with EnableConfigServer
@EnableConfigServer
@SpringBootApplication
public class SpringCloudConfigServerApplication {

public static void main(String[] args) {
SpringApplication.run(SpringCloudConfigServerApplication.class, args);
}

}
Run the application and invoke - http://localhost:8888/limit-service/default
output:
{
  "name": "limit-service",
  "profiles": [
    "default"
  ],
  "label": null,
  "version": "bbc112a18cc861b128447911c485ee6d7973136e",
  "state": null,
  "propertySources": [
    {
      "name": "file:///Users/vraghuma/FSE/git-localconfig-repo/limit-service.properties",
      "source": {
        "limit-service.minimum": "4",
        "limit-service.maximum": "990"
      }
    }
  ]
}
Now spring cloud connected to git successfully.
7. Connect limit service to spring cloud config server 
config client helps to talk to config server
in limit-service/application.properties
spring.application.name=limit-service
spring.config.import=optional:configserver:http://localhost:8888
limit-service.minimum=2
limit-service.maximum=998
invoke localhost:8080/limits
output:
{
  "minimum": 4,
  "maximum": 990
}
 
Lets take a look at logs
2022-08-03 00:11:48.585  INFO 7414 --- [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Fetching config from server at : http://localhost:8888
2022-08-03 00:11:48.585  INFO 7414 --- [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Located environment: name=limit-service, profiles=[default], label=null, version=bbc112a18cc861b128447911c485ee6d7973136e, state=null
 
properties of spring cloud take high priority than limit-service properties

Now that we have Centralized Configuration i.e. limit-service <-> spring-cloud-config-server <-> git repo
8. Configuring profiles for limit service
a. let's configure spring cloud for multiple environments
prepare qa and dev property files
limit-service-qa.properties
limit-service.minimum=5
limit-service.maximum=989
limit-service-dev.properties
limit-service.minimum=6
limit-service.maximum=999
Start spring cloud server invoke profiles
http://localhost:8888/limit-service/dev
http://localhost:8888/limit-service/qa
output for qa service
{
"name": "limit-service",
"profiles": [
"qa"
],
"label": null,
"version": "bbc112a18cc861b128447911c485ee6d7973136e",
"state": null,
"propertySources": [
{
"name": "file:///Users/vraghuma/FSE/git-localconfig-repo/limit-service-qa.properties",
"source": {
"limit-service.minimum": "5",
"limit-service.maximum": "989"
}
},
{
"name": "file:///Users/vraghuma/FSE/git-localconfig-repo/limit-service.properties",
"source": {
"limit-service.minimum": "3",
"limit-service.maximum": "1000"
}
}
]
}
output for dev service: 
{
"name": "limit-service",
"profiles": [
"dev"
],
"label": null,
"version": "bbc112a18cc861b128447911c485ee6d7973136e",
"state": null,
"propertySources": [
{
"name": "file:///Users/vraghuma/FSE/git-localconfig-repo/limit-service-dev.properties",
"source": {
"limit-service.minimum": "6",
"limit-service.maximum": "999"
}
},
{
"name": "file:///Users/vraghuma/FSE/git-localconfig-repo/limit-service.properties",
"source": {
"limit-service.minimum": "3",
"limit-service.maximum": "1000"
}
}
]
}
 
b. let's configure limit-service to be aware of these profiles
configure application.properties in limit-service
spring.profiles.active=qa
if above config not working, add this
spring.cloud.config.profile=qa
now run limit-service application and observe profiles
2022-08-05 18:15:21.872  INFO 37860 --- [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Fetching config from server at : http://localhost:8888
2022-08-05 18:15:21.872  INFO 37860 --- [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Located environment: name=limit-service, profiles=[dev], label=null, version=bbc112a18cc861b128447911c485ee6d7973136e, state=null 
output will be retured accordingly.
http://localhost:8080/limits
output:
{
"minimum": 6,
"maximum": 999
}
 
9. setting up currency exchange micro service
from start.spring.io with web, devtools, client config and actuator dependencies
in application.properties
spring.application.name=currency-exchange-service
spring.config.import=optional:configserver:http://localhost:8888
server.port=8000
10. Let's create a simple a simple hard coded  currency exchange microservice url
currency exchange service
http://localhost:8000/currency-exchange/from/USD/to/INR
 
currency conversion
http://localhost:8100/currency-conversion/from/USD/to/INR/quantity/10
 
package com.vinay.microservices.currencyexchangeservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

@RestController
public class CurrencyExchangeController {
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyExchange retrieveExchangeValue(@PathVariable String from,
@PathVariable String to){
return new CurrencyExchange(1000L, from, to, BigDecimal.valueOf(50));
}
}
package com.vinay.microservices.currencyexchangeservice;

import java.math.BigDecimal;

public class CurrencyExchange {
private Long id;
private String from;
private String to;
private BigDecimal conversionMultiple;

public CurrencyExchange() {
}

public CurrencyExchange(Long id, String from, String to, BigDecimal conversionMultiple) {
this.id = id;
this.from = from;
this.to = to;
this.conversionMultiple = conversionMultiple;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getFrom() {
return from;
}

public void setFrom(String from) {
this.from = from;
}

public String getTo() {
return to;
}

public void setTo(String to) {
this.to = to;
}

public BigDecimal getConversionMultiple() {
return conversionMultiple;
}

public void setConversionMultiple(BigDecimal conversionMultiple) {
this.conversionMultiple = conversionMultiple;
}

@Override
public String toString() {
return "CurrencyExchange{" +
"id=" + id +
", from='" + from + '\'' +
", to='" + to + '\'' +
", conversionMultiple=" + conversionMultiple +
'}';
}
}
GET - http://localhost:8000/currency-exchange/from/USD/to/INR
{
"id": 1000,
"from": "USD",
"to": "INR",
"conversionMultiple": 50
}
11. Setting up dynamic port in the response
Let's add new variable to CurrencyExchange
private String environment;
Use Environmet class to get port 
@Autowired
private Environment environment;

@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyExchange retrieveExchangeValue(@PathVariable String from,
@PathVariable String to){
CurrencyExchange currencyExchange = new CurrencyExchange(1000L, from, to, BigDecimal.valueOf(50));
currencyExchange.setEnvironment(environment.getProperty("local.server.port"));
return currencyExchange;
} 
http://localhost:8000/currency-exchange/from/USD/to/INR
{
"id": 1000,
"from": "USD",
"to": "INR",
"conversionMultiple": 50,
"environment": "8000"
}
http://localhost:8001/currency-exchange/from/USD/to/INR
{
"id": 1000,
"from": "USD",
"to": "INR",
"conversionMultiple": 50,
"environment": "8001"
}
 
Note: Observe how environment variable is fetching value. you can run your application using -Dserver.port=8001 in VM arguments. 
12. Configure JPA and initialize data 
we will use in memory database called H2
add this maven dependency to interact with JPA's
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Add this dependency to interact with H2 database
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
Let's create our own database rather depending on random database
Let's add these properties to application.properties
spring.jpa.show-sql=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.h2.console.enabled=true
Access H2 database with http://localhost:8000/h2-console
and connect to the database and create some tables
Let's make CurrencyExchange as an entity
@Entity
public class CurrencyExchange {
@Id
private Long id;
@Column(name="currency_from")
private String from;
@Column(name="currency_to")
private String to;
private BigDecimal conversionMultiple;
private String environment;
Observe @Column annotation, as from is a key word in sql, it throws an error, so we are renaming the column in database.
Let's how the table created in H2
ID  CONVERSION_MULTIPLE  ENVIRONMENT  CURRENCY_FROM  CURRENCY_TO  
camel case are converted to _, in db that's how they are represented and JPA will take care of it.
Let's add some sql some data will be added to table.
Make sure you're adding this property as the sql load happens before table creation, so we need to defer it.
spring.jpa.defer-datasource-initialization=true
data.sql
insert into currency_exchange
(id, currency_from, currency_to, conversion_multiple, environment)
values(10001, 'USD', 'INR', 65, '');
insert into currency_exchange
(id, currency_from, currency_to, conversion_multiple, environment)
values(10002, 'EUR', 'INR', 75, '');
insert into currency_exchange
(id, currency_from, currency_to, conversion_multiple, environment)
values(10003, 'AUD', 'INR', 25, '');
13. Update REST API to connect to our database
Let's create a JPA repository - CurrencyExchangeRepository
package com.vinay.microservices.currencyexchangeservice;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CurrencyExchangeRepository extends JpaRepository<CurrencyExchange, Long> {
}
Lets autowire it in CurrencyExchangeController
@RestController
public class CurrencyExchangeController {

@Autowired
private CurrencyExchangeRepository currencyExchangeRepository;
Let's create one method in Repository that filters data based on from and to values
CurrencyExchange findByFromAndTo(String from, String to);
JPA will provide the query
Let's modify GET mapping accordingly
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyExchange retrieveExchangeValue(@PathVariable String from,
@PathVariable String to){
CurrencyExchange currencyExchange = currencyExchangeRepository.findByFromAndTo(from, to);
currencyExchange.setEnvironment(environment.getProperty("local.server.port"));
return currencyExchange;
}
14. Let's try creating Currency Conversion Microservice
Lets create a new project from spring initialzr
project name be curency-conversion-microservice
Let's configure appication.properties
spring.appication.name=currency-conversion
server.port=8100
spring.config.import=optional:configserver:http://localhost:8888
Let's create controller
package com.vinay.micrservices.currencyconversionservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

@RestController
public class CurrencyConversionController {

@GetMapping("/currency-conversion/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion calculateCurrencyConversion(
@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity
){
return new CurrencyConversion(10001L, from, to, quantity, BigDecimal.ONE, BigDecimal.ONE, "" );
}
}
and a Page Object
package com.vinay.micrservices.currencyconversionservice;

import java.math.BigDecimal;

public class CurrencyConversion {
private Long id;
private String from;
private String to;
private BigDecimal quantity;
private BigDecimal conversionMultiple;
private BigDecimal totalCalculatedAmount;
private String environment;

public CurrencyConversion() {
}

public CurrencyConversion(Long id, String from, String to, BigDecimal quantity, BigDecimal conversionMultiple, BigDecimal totalCalculatedAmount, String environment) {
this.id = id;
this.from = from;
this.to = to;
this.conversionMultiple = conversionMultiple;
this.quantity = quantity;
this.totalCalculatedAmount = totalCalculatedAmount;
this.environment = environment;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getFrom() {
return from;
}

public void setFrom(String from) {
this.from = from;
}

public String getTo() {
return to;
}

public void setTo(String to) {
this.to = to;
}

public BigDecimal getConversionMultiple() {
return conversionMultiple;
}

public void setConversionMultiple(BigDecimal conversionMultiple) {
this.conversionMultiple = conversionMultiple;
}

public BigDecimal getQuantity() {
return quantity;
}

public void setQuantity(BigDecimal quantity) {
this.quantity = quantity;
}

public BigDecimal getTotalCalculatedAmount() {
return totalCalculatedAmount;
}

public void setTotalCalculatedAmount(BigDecimal totalCalculatedAmount) {
this.totalCalculatedAmount = totalCalculatedAmount;
}

public String getEnvironment() {
return environment;
}

public void setEnvironment(String environment) {
this.environment = environment;
}
}
15. Lets invoke currency exchange service from currency conversion service
@GetMapping("/currency-conversion/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion calculateCurrencyConversion(
@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity
) {
HashMap<String, String> uriVariables = new HashMap<>();
uriVariables.put("from", from);
uriVariables.put("to", to);
ResponseEntity<CurrencyConversion> responseEntity = new RestTemplate().getForEntity(
"http://localhost:8000/currency-exchange/from/USD/to/INR",
CurrencyConversion.class,
uriVariables
);
CurrencyConversion currencyConversion = responseEntity.getBody();
return new CurrencyConversion(
currencyConversion.getId(),
from,
to,
quantity,
currencyConversion.getConversionMultiple(),
quantity.multiply(currencyConversion.getConversionMultiple()),
currencyConversion.getEnvironment());
}
API: http://localhost:8100/currency-conversion/from/USD/to/INR/quantity/3
{
"id": 10001,
"from": "USD",
"to": "INR",
"quantity": 3,
"conversionMultiple": 65.00,
"totalCalculatedAmount": 195.00,
"environment": "8000"
}
16. Using Feign REST client for Service invocation
As above involves lot of coding
Add this dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Create a Proxy
@FeignClient(name = "currency-exchange-service", url = "localhost:8000")
public interface CurrencyExchangeProxy {

@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeValue(@PathVariable String from,
@PathVariable String to);
}
Update application to enable Feign clients
@SpringBootApplication
@EnableFeignClients
public class CurrencyConversionServiceApplication {

public static void main(String[] args) {
SpringApplication.run(CurrencyConversionServiceApplication.class, args);
}

}
In controller
@GetMapping("/currency-conversion-feign/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion calculateCurrencyConversionFeign(
@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity
) {
CurrencyConversion currencyConversion = currencyExchangeProxy.retrieveExchangeValue(from, to);
return new CurrencyConversion(
currencyConversion.getId(),
from,
to,
quantity,
currencyConversion.getConversionMultiple(),
quantity.multiply(currencyConversion.getConversionMultiple()),
currencyConversion.getEnvironment());
}
Make sure you're auto wiring currencyExchangeProxy;
17. Understanding Naming server and Setting up Eureka Server
In currency exchange, we are hardcoding proxy, which we should not do
Let's register all the services with a service register
create a naming-server project and add dependency for Eureka server which is a naming server
package com.vinay.microservices.namingserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class NamingServerApplication {

public static void main(String[] args) {
SpringApplication.run(NamingServerApplication.class, args);
}

}
Make sure to annoatate with EnableEurekaServer
Have below properties added to application.properties
spring.application.name=naming-server
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
Run the application and eureka server will be running on port 8761 --> localhost:8761
18. Connect currency conversion and currency exchange service to Eureka
just add this dependency in pom.xml of both currency conversion and currency exchange services
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
now both the services will be registered with Eureka naming server
under -Instances currently registered with Eureka
Have this property added in both the services
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
which will allow us to configure different eureka servers
19. Let's Load balance services with Eureka
Very simple, just remove URI attribute from @FeignClient of CurrencyExchangeProxy
package com.vinay.micrservices.currencyconversionservice;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

//@FeignClient(name = "currency-exchange-service", url = "localhost:8000")
@FeignClient(name = "currency-exchange-service")
public interface CurrencyExchangeProxy {

@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeValue(@PathVariable String from,
@PathVariable String to);
}
Let's try running feign service from 8000 and 8001 port and see the behavior and how it's dynamically handled by eureka server
{
"id": 10001,
"from": "USD",
"to": "INR",
"quantity": 3,
"conversionMultiple": 65.00,
"totalCalculatedAmount": 195.00,
"environment": "8000"
}
sometimes
{
"id": 10001,
"from": "USD",
"to": "INR",
"quantity": 3,
"conversionMultiple": 65.00,
"totalCalculatedAmount": 195.00,
"environment": "8001"
}
20. Setting up Spring cloud API gateway 
Lets create API gateway project with devtools, actuator, eureka config client, gateway dependencies
in application.properties
spring.application.name=api-gateway
server.port=8765
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
Start the server, api gateway will be registered with Eureka.
21. Enabling Discovery Locator with Eureka
invoke this endpoint
http://localhost:8765/CURRENCY-EXCHANGE-SERVICE/currency-exchange/from/USD/to/INR 
 you will get the result from currency exchange service, as API gateway is taking care of finding the domain from eureka.
Mae sure to add below in api gateway properties
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
for lowercase use above property--> CURRENCY-EXCHANGE-SERVICE --> currency-exchange-service which looks like below
http://localhost:8765/currency-exchange-service/currency-exchange/from/USD/to/INR
22. Exploring Routes with Spring Cloud Gateway
Create a class
package com.vinay.microservices.apigateway;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApiGatewayConfiguration {
@Bean
public RouteLocator gatewayRouter(RouteLocatorBuilder builder){
return builder.routes().route(
p -> p.path("/get")
.filters(f->f
.addRequestHeader("MyHeader", "MyURI")
.addRequestParameter("Param", "MyValue"))
.uri("http://httpbin.org:80"))
.build();
}
}
you can have your headers and Parameters added
http://localhost:8765/get
output:
{
"args": {
"Param": "MyValue"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Content-Length": "0",
"Forwarded": "proto=http;host=\"localhost:8765\";for=\"[0:0:0:0:0:0:0:1]:49385\"",
"Host": "httpbin.org",
"Myheader": "MyURI",
"Postman-Token": "74426836-b888-4c0a-90ce-c8177a874345",
"User-Agent": "PostmanRuntime/7.29.0",
"X-Amzn-Trace-Id": "Root=1-62f7ee02-474ceac40353493452713217",
"X-Forwarded-Host": "localhost:8765"
},
"origin": "0:0:0:0:0:0:0:1, 49.37.195.164",
"url": "http://localhost:8765/get?Param=MyValue"
}
Let's try to optimize our url's
make sure you disable discovery locators
#spring.cloud.gateway.discovery.locator.enabled=true
#spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
package com.vinay.microservices.apigateway;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApiGatewayConfiguration {
@Bean
public RouteLocator gatewayRouter(RouteLocatorBuilder builder){
return builder.routes().
route(p -> p.path("/get")
.filters(f->f
.addRequestHeader("MyHeader", "MyURI")
.addRequestParameter("Param", "MyValue"))
.uri("http://httpbin.org:80")).
route(p -> p.path("/currency-exchange/**")
.uri("lb://currency-exchange-service")).
route(p -> p.path("/currency-conversion/**")
.uri("lb://currency-conversion"))
.build();
}
}
for a input of currency-exchange, load balance and pick up the naming server name.
Now the url's look like
http://localhost:8765/currency-exchange/from/USD/to/INR
http://localhost:8765/currency-conversion-feign/from/USD/to/INR/quantity/3
http://localhost:8765/currency-conversion/from/USD/to/INR/quantity/10  
we can even redirect to other url's
route(p -> p.path("/currency-conversion-new/**")
.filters(f->f.rewritePath(
"/currency-conversion-new/(?<segment>.*)",
"/currency-conversion-feign/${segment}"
))
.uri("lb://currency-conversion"))
http://localhost:8765/currency-conversion-new/from/USD/to/INR/quantity/3
which gives output of feign 
23. Implementing Spring cloud gateway logging filter
Create a Logging Filter class
package com.vinay.microservices.apigateway;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class LoggingFilter implements GlobalFilter {

private Logger logger = LoggerFactory.getLogger(LoggingFilter.class);

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
logger.info("path of request received -> {}",exchange.getRequest().getPath());
return chain.filter(exchange);
}
}
you may see the logs like below
2022-08-14 00:41:55.203  INFO 18568 --- [ctor-http-nio-3] c.v.m.apigateway.LoggingFilter           : path of request received -> /currency-conversion-feign/from/USD/to/INR/quantity/3
2022-08-14 00:41:59.545  INFO 18568 --- [ctor-http-nio-3] c.v.m.apigateway.LoggingFilter           : path of request received -> /currency-conversion/from/USD/to/INR/quantity/10
2022-08-14 00:42:03.238  INFO 18568 --- [ctor-http-nio-3] c.v.m.apigateway.LoggingFilter           : path of request received -> /currency-exchange/from/USD/to/INR
24. Getting started with Circuit Breaker  - Resilience 4j
what if a microservice is slow, that will impact entire chain.
can I return fallback response? 
can we implement a circuit breaker pattern to reduce load?
can we retry requests in case of temporary failures?
can we implement rate limiting ? ( like limited number of calls)
Resilience 4J is a lightweight easy to use fault tolerance library inspired by Netflix Hystrix. (Circuit breaker)
Let's add these dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
</dependency>
Let's create a simple api to play with
package com.vinay.microservices.currencyexchangeservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CircuitBreakerController {

@GetMapping("/sample-api")
public String sampleAPI(){
return "Sample API";
}
}
25. Play with Resilience 4J - Retry and Fallback methods
Let's fail the API
and configure @Retry with default value (which is 3 times)
on invoking  http://localhost:8000/sample-api
package com.vinay.microservices.currencyexchangeservice;

import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class CircuitBreakerController {

private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);

@GetMapping("/sample-api")
@Retry(name = "default")
public String sampleAPI(){
logger.info("Retrying .. ");
ResponseEntity<String> forEntity = new RestTemplate().getForEntity("http://localhost:8080/dummy",
String.class);
return forEntity.getBody();
}
}
Logs:
2022-08-14 01:31:22.222  INFO 25811 --- [nio-8000-exec-1] c.v.m.c.CircuitBreakerController         : Retrying ..
2022-08-14 01:31:22.731  INFO 25811 --- [nio-8000-exec-1] c.v.m.c.CircuitBreakerController         : Retrying ..
2022-08-14 01:31:23.236  INFO 25811 --- [nio-8000-exec-1] c.v.m.c.CircuitBreakerController         : Retrying .. 
Lets try to have custom settings,which can be done from application.properties
package com.vinay.microservices.currencyexchangeservice;

import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class CircuitBreakerController {

private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);

@GetMapping("/sample-api")
@Retry(name = "sample-api", fallbackMethod = "hardCodedResponse")
public String sampleAPI(){
logger.info("Retrying .. ");
ResponseEntity<String> forEntity = new RestTemplate().getForEntity("http://localhost:8080/dummy",
String.class);
return forEntity.getBody();
}

public String hardCodedResponse(Exception ex){
return "fallback response";
}
}
we have a fallback response
spring.application.name=currency-exchange-service
spring.config.import=optional:configserver:http://localhost:8888
server.port=8000
spring.jpa.show-sql=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
resilience4j.retry.instances.sample-api.maxRetryAttempts=5
resilience4j.retry.instances.sample-api.waitDuration=1s
resilience4j.retry.instances.sample-api.enableExponentialBackOff=true
waitDuration is wait time for every retry
enableExponentialBackOff waits exponential in nature 1, 2, 3, 5, 8 ... (recommended)
26. Playing with Circuit Breaker Features of Resilience4j
when a microservice is erroring out, it will return fallback response, instead of making a call to it repeatedly.
you can simulate load using watch -n 0.1 http://localhost:8080/sample-api which simulates 10 users per second.
Observe the logs how they are behaving.
package com.vinay.microservices.currencyexchangeservice;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class CircuitBreakerController {

private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);

@GetMapping("/sample-api")
// @Retry(name = "sample-api", fallbackMethod = "hardCodedResponse")
@CircuitBreaker(name = "default", fallbackMethod = "hardCodedResponse")
public String sampleAPI(){
logger.info("Retrying .. ");
ResponseEntity<String> forEntity = new RestTemplate().getForEntity("http://localhost:8080/dummy",
String.class);
return forEntity.getBody();
}

public String hardCodedResponse(Exception ex){
return "fallback response";
}
}
How do i know when microservice is up?
Closed, Open, Half Open
Closed -> circuit is closed when microservice delivers output
Open --> circuit is open when it reaches failure threshold
Half Open --> circuit is half open when some percentage of traffic is diverted.
 
There are many configurations, please look into official documentation.
27. Exploring rate limiting and bulk head features of Resiliencej
Rate limiting is nothing allowing number of requests in some time
10s allow 1000 requests
if you fire using watch, some will return pass, others error
package com.vinay.microservices.currencyexchangeservice;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.github.resilience4j.retry.annotation.Retry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class CircuitBreakerController {

private Logger logger = LoggerFactory.getLogger(CircuitBreakerController.class);

@GetMapping("/sample-api")
// @Retry(name = "sample-api", fallbackMethod = "hardCodedResponse")
// @CircuitBreaker(name = "default", fallbackMethod = "hardCodedResponse")
@RateLimiter(name="default")
public String sampleAPI(){
/*logger.info("Retrying .. ");
ResponseEntity<String> forEntity = new RestTemplate().getForEntity("http://localhost:8080/dummy",
String.class);
return forEntity.getBody();*/
return "sample-api";
}

public String hardCodedResponse(Exception ex){
return "fallback response";
}
}
properties should be
resilience4j.ratelimiter.instances.default.limitForPeriod=2
resilience4j.ratelimiter.instances.default.limitRefreshPeriod=10s
which means allowing 2 requests over 10 seconds.
we can even set concurrent calls using BulkHead
resilience4j.bulkhead.instances.default.maxConcurrentCalls=10
@Bulkhead(name="default")
public String sampleAPI(){

Docker with Microservices

Introduction
1. Enterprises are heading towards microservices architecture
2. Deployments are complex? How can we have one way of deploying? Containarization is the solution.
3. Create Docker image for each microservice
    a. docker image contains everything a microservice needs to run
    b. can be run in any infrastructure (local, cloud)
1. installing docker
install docker from official portal
once installed , check your docker version using docker --version
2. Deploy a spring boot application
you can run the application by simply running a docker run command, no need to install any software or hardare. That's the magic of docker.
docker run -p 5000:5000 in28min/todo-rest-api-h2:1.0.0
-p 5000:5000 --> publish container ports to host ports 
-d will run it detached mode
invoke this url - http://localhost:5000/hello-world
output:
Hello World 
https://hub.docker.com/r/in28min/todo-rest-api-h2/tags
3. Docker concepts - registry, repository, Tag
hub.docker.com is a registry, where an image is downloaded from repository(in28min/todo-rest-api-h2) with a tag (1.0.0.RELEASE)
Ex: in28min/todo-rest-api-h2:1.0.0.RELEASE
image is like a class, Container is an object(on running image it becomes a container)
4. Playing with Docker Images and Containers
you can even run two instances of docker by just changing the port
to see list of container docker container ls
CONTAINER ID   IMAGE                                    COMMAND                  CREATED         STATUS         PORTS                    NAMES
b9f63b2f1b50   in28min/todo-rest-api-h2:1.0.0.RELEASE   "sh -c 'java $JAVA_O…"   4 minutes ago   Up 4 minutes   0.0.0.0:5000->5000/tcp   pedantic_swartz
to see list of docker image --> docker images
REPOSITORY                 TAG             IMAGE ID       CREATED        SIZE
in28min/todo-rest-api-h2   1.0.0.RELEASE   9d05dd98f4a4   2 months ago   143MB
docker container ls -a --> shows all containers which are running, exited
docker container stop <containerID> --> to stop the container 
5. Understanding Docker architecture - Docker Client and server
Docker Client -> Docker Daemon --> Containers
                                                       --> Local Images
                                                      --> Image Registry(nginx, mysql, eureka, your app)
6. Playing with Docker Images   
docker images
docker pull <imagename> --> only pulls the image, doesn't run
docker seach mysql --> searching for images mysql
docker image history <imagename/id>
docker image inspect <imagename/id>
docker image remove <imageid>
7. Playing with Docker containers
docker run -> docker container run
docker container pause <container id>
docker logs -f <container id>
docker container unpause <container id>
docker conatiner inspect <container id>
docker container prune --> removes all stopped containers
graceful shutdown --> container given some time to close all the processes.
-d will start in detached mode
docker container kill <container id> --> will stop abruptly 
--restart=always --. whenever docker restarts, container also start with it.
8. Playing with docker commands - stats, system
docker events --> which container connected, disconnected, volume etc
docker top --> top process running in a container
docker stats --> stats of the containers that are running
-m 512m --cpu-quota 5000 (total is 100000) --> you can configure ram and cpu quota
docker system df --> you can see different things docker manages like images, volumes, containers, cache
9. Introduction to distributed tracing
complex call chains
debug problems
how do you trace requests across microservices
 
Here is the scope for distributed tracing which saves info to database
10. Launching zipkin container using docker
https://hub.docker.com/r/openzipkin/zipkin/
vraghuma$ docker run -d -p 9411:9411 openzipkin/zipkin --> runs zipkin image which can be accessed at http://localhost:9411
11. Connecting Currency Exchange service with zipkin
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
which allows to create id that can be tracked across all services.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
all the services send info to distributed server, if ds is down, so that's why Rabbit MQ, which will queue and sends it to ds. 
Let's configure Sampling that allows not to sample all the requests if not its a performance issue.
spring.sleuth.sampler.probability=1.0
sampling every request
invoke currency exchane endpoint and "Run the query" in zipkin, which will show the request with trace id and all the details.
12. Connecting currency conversion microservice and API gateway with zipkin
Add above 3 dependencies and properties to projects and start
by default zipkin runs on 9411
spring.zipkin.baseUrl=http://localhost:9411/
13. Set up microservices for creating containers
in pom.xml, add configuration for the image you want to create.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>vinay/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
</plugin>
</plugins>
</build>
run the mvn command
mvn spring-boot:build-image -DskipTests --> -DskipTests is optional
on sucessful image creation, you should get a message like below
Successfully built image 'docker.io/vinay/mmv-currency-exchange-service:0.0.1-SNAPSHOT'
Let's try to run this image
docker run -p 8000:8000 vinay/mmv-currency-exchange-service:0.0.1-SNAPSHOT
now invoke currency exchange endpoints, which will get response from docker container
14. Getting started with Docker Compose
for defining and running multi container docker applications
Let's create a yaml file
version: '3.7'

services:
currency-exchange-service:
image: vinay/mmv-currency-exchange-service:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8000:8000"
spacing is very important, else it will throw an error.
Navigate to the folder where yaml is configured and fire docker-compose up
This should launch the instance of currency exchange service.
it's a good practice to add networks to yaml
version: '3.7'

services:
currency-exchange-service:
image: vinay/mmv-currency-exchange-service:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8000:8000"
networks:
- currency-network
networks:
currency-network:
15. Running Eureka Naming server with Docker Compose
edit pom.xml with configuration
<configuration>
<image>
<name>vinay/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
create a image by running mvn spring-boot:build-image -DskipTests
Successfully built image 'docker.io/vinay/mmv-naming-server:0.0.1-SNAPSHOT' 
configure yaml
version: '3.7'

services:
currency-exchange-service:
image: vinay/mmv-currency-exchange-service:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8000:8000"
networks:
- currency-network
depends_on:
- naming-server
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://localhost:8761/eureka

naming-server:
image: vinay/mmv-naming-server:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8761:8761"
networks:
- currency-network
networks:
currency-network:
 
Observe depends_on
invoke docker-compose up, which will bring up both the containers
it will error out, as currency exchange can't register with eureka due to localhost, changing it to naming-server
16. Running curency conversion microservice with docker compose
Create image and configure yaml 
version: '3.7'

services:
currency-exchange-service:
image: vinay/mmv-currency-exchange-service:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8000:8000"
networks:
- currency-network
depends_on:
- naming-server
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://naming-server:8761/eureka

currency-conversion:
image: vinay/mmv-currency-conversion-service:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8100:8100"
networks:
- currency-network
depends_on:
- naming-server
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://naming-server:8761/eureka

naming-server:
image: vinay/mmv-naming-server:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8761:8761"
networks:
- currency-network
networks:
currency-network:
17. Running spring API cloud gatewa with docker compose
Create image and configure yaml
api-gateway:
image: vinay/mmv-api-gateway:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8765:8765"
networks:
- currency-network
depends_on:
- naming-server
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://naming-server:8761/eureka
18. Running zipkin with docker compose
 
version: '3.7'

services:
currency-exchange-service:
image: vinay/mmv-currency-exchange-service:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8000:8000"
networks:
- currency-network
depends_on:
- naming-server
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://naming-server:8761/eureka
SPRING.ZIPKIN.BASEURL: http://zipkin-server:9411/

currency-conversion:
image: vinay/mmv-currency-conversion-service:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8100:8100"
networks:
- currency-network
depends_on:
- naming-server
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://naming-server:8761/eureka
SPRING.ZIPKIN.BASEURL: http://zipkin-server:9411/

api-gateway:
image: vinay/mmv-api-gateway:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8765:8765"
networks:
- currency-network
depends_on:
- naming-server
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://naming-server:8761/eureka
SPRING.ZIPKIN.BASEURL: http://zipkin-server:9411/

naming-server:
image: vinay/mmv-naming-server:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8761:8761"
networks:
- currency-network
zipkin-server:
image: openzipkin/zipkin
mem_limit: 300m
ports:
- "9411:9411"
networks:
- currency-network
networks:
currency-network:
19. Running zipkin and rabbit queue with docker compose
it's all about configration
version: '3.7'

services:
currency-exchange-service:
image: vinay/mmv-currency-exchange-service:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8000:8000"
networks:
- currency-network
depends_on:
- naming-server
- rabbitmq
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://naming-server:8761/eureka
SPRING.ZIPKIN.BASEURL: http://zipkin-server:9411/
RABBIT_URI: amqp://guest:guest@rabbitmq:5672
SPRING_RABBITMQ_HOST: rabbitmq
SPRING_ZIPKIN_SENDER_TYPE: rabbit

currency-conversion:
image: vinay/mmv-currency-conversion-service:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8100:8100"
networks:
- currency-network
depends_on:
- naming-server
- rabbitmq
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://naming-server:8761/eureka
SPRING.ZIPKIN.BASEURL: http://zipkin-server:9411/
RABBIT_URI: amqp://guest:guest@rabbitmq:5672
SPRING_RABBITMQ_HOST: rabbitmq
SPRING_ZIPKIN_SENDER_TYPE: rabbit

api-gateway:
image: vinay/mmv-api-gateway:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8765:8765"
networks:
- currency-network
depends_on:
- naming-server
- rabbitmq
environment:
EUREKA.CLIENT.SERVICEURL.DEFAULTZONE: http://naming-server:8761/eureka
SPRING.ZIPKIN.BASEURL: http://zipkin-server:9411/
RABBIT_URI: amqp://guest:guest@rabbitmq:5672
SPRING_RABBITMQ_HOST: rabbitmq
SPRING_ZIPKIN_SENDER_TYPE: rabbit

naming-server:
image: vinay/mmv-naming-server:0.0.1-SNAPSHOT
mem_limit: 700m
ports:
- "8761:8761"
networks:
- currency-network
zipkin-server:
image: openzipkin/zipkin
mem_limit: 300m
ports:
- "9411:9411"
networks:
- currency-network
environment:
RABBIT_URI: amqp://guest:guest@rabbitmq:5672
depends_on:
- rabbitmq
rabbitmq:
image: rabbitmq
mem_limit: 300m
ports:
- "5672:5672"
- "15672:15672"
networks:
- currency-network
networks:
currency-network:

Kubernates with microservices

1. Container Orchestration
I want 10 instances of microservice A and 15 instances of Microservice B.
Auto Scaling --> Scale containers based on thier demand
Service Discovery --> Help microservices find one another
Load Balancer --> Distribute load among multiple instances of a micro service
Self Healing --> Do health checks and replace failing instances
Zero Downtime Deployments --> Release new versions without downtime.
What are the options do we have for 
  1. AWS Elastic Container Service (ECS) 
  2.  Cloud Nuetral - Kubernates
 Kibernates can be run in AWS, AZure, GCP
EKS(Elastic Kubernetes Service)
AKS(Azure Kubernates Service)
GKE(Google Kubernetes Engine)
we will use GKE, since EKS/AKS doesn't have a free tier
2. Creating Google cloud account
https://cloud.google.com/
Create your own account
3. Create Kubernates cluster with GKE
Master Nodes manges cluster
Worker Noes runs your application
Search for Kubernates Engine, in this dashboard we have Clusters(which helps to manage nodes), Workloads (handles deployments),  Services and Ingress (external access to applications), Configuration, Storage
Create GKE cluster
4. Review Kubernates Cluster
K8S --> simply Kubernates
5. Deploy your first spring boot application to kubernates
Open your cluster
Activate Cloud shell
Connect to your cluster
kubectl --> kubernates cluster
type kubectl version --> which will show client and server versions
Ex:
WARNING: This version information is deprecated and will be replaced with the output from kubectl version --short.  Use --output=yaml|json to get the full version.
Client Version: version.Info{Major:"1", Minor:"24", GitVersion:"v1.24.3", GitCommit:"aef86a93758dc3cb2c658dd9657ab4ad4afc21cb", GitTreeState:"clean", BuildDate:"2022-07-13T14:30:46Z", GoVersion:"go1.18.3", Compiler:"gc", Platform:"linux/amd64"}
Kustomize Version: v4.5.4
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.10-gke.600", GitCommit:"2c921d7b040ed9c5a3a1f9407fb109b74d72d0a4", GitTreeState:"clean", BuildDate:"2022-06-02T09:20:24Z", GoVersion:"go1.16.15b7", Compiler:"gc", Platform:"linux/amd64"}
WARNING: version difference between client (1.24) and server (1.22) exceeds the supported minor version skew of +/-1
Lets try to deploy a simple hello-world application
kubectl create deployment hello-world-rest-api --image=in28min/hello-world-rest-api:0.0.1.RELEASE
Now this need to be exposed
kubectl expose deployment hello-world-rest-api --type=LoadBalancer --port=8080
Click on Services and Ingress, where you can see your application
Click on Endpoints and see your application
6. Quick Look at Kubernates concepts
What's happening behind the scenes?
kubectl get  events --> here you will see pod, replica set, deployment, service
kubectl get pods --> pods in running state
NAME                                   READY   STATUS    RESTARTS   AGE
hello-world-rest-api-9d764b4dc-gffb6   1/1     Running   0          17m

kubectl get replicaset
NAME                             DESIRED   CURRENT   READY   AGE
hello-world-rest-api-9d764b4dc   1         1         1       19m

kubectl get deployment
NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
hello-world-rest-api   1/1     1            1           21m
kubectl get service
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)          AGE
hello-world-rest-api   LoadBalancer   10.104.129.174   34.170.252.132   8080:30516/TCP   17m
kubernetes             ClusterIP      10.104.128.1     <none>           443/TCP          45m
when kubectl create deployment executed --> deployment, replicaset and pod are created
when kubectl expose deployment executed --> service is created.
7. Understanding pods in Kubernetes
pod is a smallest deployable unit in kubernates
Container lives inside a pod
pod lives on a node
Containers --> Pods --> Node
8. Understanding replicasets in kubernetes
docker get replicasets or docket get replicaset or docker get rs
replica set ensures a specific set of pods runs at all time.
kubectl get pods -o wide --> will give more details
even if you delete a pod, rs will spin up one more pod accordingly
kubectl scal deployment hello-world-rest-api --replica=3 --> which will create 3 pods under rs
Ex:
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl scale deployment hello-world-rest-api --replicas=3
deployment.apps/hello-world-rest-api scaled
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get pods -o wide
NAME                                   READY   STATUS    RESTARTS   AGE   IP             NODE                                                  NOMINATED NODE   READINESS GATES
hello-world-rest-api-9d764b4dc-6pw25   0/1     Pending   0          4s    <none>         <none>                                                <none>           <none>
hello-world-rest-api-9d764b4dc-gffb6   1/1     Running   0          32h   10.104.0.130   gk3-vinay-autopilot-clus-default-pool-203925f2-4fpg   <none>           <none>
hello-world-rest-api-9d764b4dc-hg948   0/1     Pending   0          4s    <none>         <none>                                                <none>           <none>

kubctl get events --> gives you more details
9. Understanding deployment in kubernetes
Let try deploying to pods
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get rs -o wide
NAME                             DESIRED   CURRENT   READY   AGE   CONTAINERS             IMAGES                                       SELECTOR
hello-world-rest-api-9d764b4dc   3         3         3       32h   hello-world-rest-api   in28min/hello-world-rest-api:0.0.1.RELEASE   app=hello-world-rest-api,pod-template-hash=9d764b4dc
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl set image deployment hello-world-rest-api hello-world-rest-api=DUMMY_IMAGE:TEST
deployment.apps/hello-world-rest-api image updated --> setting to wrong image image name
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get rs -o wide
NAME                             DESIRED   CURRENT   READY   AGE   CONTAINERS             IMAGES                                       SELECTOR
hello-world-rest-api-9697bb8f6   1         1         0       5s    hello-world-rest-api   DUMMY_IMAGE:TEST                             app=hello-world-rest-api,pod-template-hash=9697bb8f6
hello-world-rest-api-9d764b4dc   3         3         3       32h   hello-world-rest-api   in28min/hello-world-rest-api:0.0.1.RELEASE   app=hello-world-rest-api,pod-template-hash=9d764b4dc
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get pods
NAME                                   READY   STATUS    RESTARTS   AGE
hello-world-rest-api-9697bb8f6-zmv64   0/1     Pending   0          54s
hello-world-rest-api-9d764b4dc-6pw25   1/1     Running   0          19m
hello-world-rest-api-9d764b4dc-gffb6   1/1     Running   0          32h
hello-world-rest-api-9d764b4dc-hg948   1/1     Running   0          19m
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get pods -o wide
NAME                                   READY   STATUS              RESTARTS   AGE   IP             NODE                                                  NOMINATED NODE   READINESS GATES
hello-world-rest-api-9697bb8f6-zmv64   0/1     ContainerCreating   0          98s   <none>         gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   <none>           <none>
hello-world-rest-api-9d764b4dc-6pw25   1/1     Running             0          19m   10.104.0.194   gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   <none>           <none>
hello-world-rest-api-9d764b4dc-gffb6   1/1     Running             0          32h   10.104.0.130   gk3-vinay-autopilot-clus-default-pool-203925f2-4fpg   <none>           <none>
hello-world-rest-api-9d764b4dc-hg948   1/1     Running             0          19m   10.104.1.2     gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   <none>           <none>
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl describe pod hello-world-rest-api-9697bb8f6-zmv64
Name:         hello-world-rest-api-9697bb8f6-zmv64
Namespace:    default
Priority:     0
Node:         gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h/10.128.0.7
Start Time:   Wed, 17 Aug 2022 04:52:18 +0000
Labels:       app=hello-world-rest-api
              pod-template-hash=9697bb8f6
Annotations:  seccomp.security.alpha.kubernetes.io/pod: runtime/default
Status:       Pending
IP:           10.104.1.67
IPs:
  IP:           10.104.1.67
Controlled By:  ReplicaSet/hello-world-rest-api-9697bb8f6
Containers:
  hello-world-rest-api:
    Container ID:
    Image:          DUMMY_IMAGE:TEST
    Image ID:
    Port:           <none>
    Host Port:      <none>
    State:          Waiting
      Reason:       InvalidImageName
    Ready:          False
    Restart Count:  0
    Limits:
      cpu:                500m
      ephemeral-storage:  1Gi
      memory:             2Gi
    Requests:
      cpu:                500m
      ephemeral-storage:  1Gi
      memory:             2Gi
    Environment:          <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-pddkj (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True
Volumes:
  kube-api-access-pddkj:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   Guaranteed
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason            Age               From                                   Message
  ----     ------            ----              ----                                   -------
  Warning  FailedScheduling  2m34s             gke.io/optimize-utilization-scheduler  0/5 nodes are available: 5 Insufficient cpu, 5 Insufficient memory.
  Normal   TriggeredScaleUp  2m27s             cluster-autoscaler                     pod triggered scale-up: [{https://www.googleapis.com/compute/v1/projects/symbolic-voyage-359519/zones/us-central1-f/instanceGroups/gk3-vinay-autopilot-clus-default-pool-203925f2-grp 2->3 (max: 1000)}]
  Warning  FailedScheduling  72s               gke.io/optimize-utilization-scheduler  0/6 nodes are available: 1 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 5 Insufficient cpu, 5 Insufficient memory.
  Normal   Scheduled         69s               gke.io/optimize-utilization-scheduler  Successfully assigned default/hello-world-rest-api-9697bb8f6-zmv64 to gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h
  Warning  InspectFailed     7s (x6 over 55s)  kubelet                                Failed to apply default image tag "DUMMY_IMAGE:TEST": couldn't parse image reference "DUMMY_IMAGE:TEST": invalid reference format: repository name must be lowercase
  Warning  Failed            7s (x6 over 55s)  kubelet                                Error: InvalidImageName
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get events --sort-by=.metadata.creationTimestamp
LAST SEEN   TYPE      REASON                    OBJECT                                                     MESSAGE
21m         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled up replica set hello-world-rest-api-9d764b4dc to 3
21m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-hg948                   0/3 nodes are available: 2 Insufficient memory, 3 Insufficient cpu.
21m         Normal    SuccessfulCreate          replicaset/hello-world-rest-api-9d764b4dc                  Created pod: hello-world-rest-api-9d764b4dc-hg948
21m         Normal    SuccessfulCreate          replicaset/hello-world-rest-api-9d764b4dc                  Created pod: hello-world-rest-api-9d764b4dc-6pw25
21m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-6pw25                   0/3 nodes are available: 2 Insufficient memory, 3 Insufficient cpu.
21m         Normal    TriggeredScaleUp          pod/hello-world-rest-api-9d764b4dc-6pw25                   pod triggered scale-up: [{https://www.googleapis.com/compute/v1/projects/symbolic-voyage-359519/zones/us-central1-b/instanceGroups/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-grp 1->3 (max: 1000)}]
21m         Normal    TriggeredScaleUp          pod/hello-world-rest-api-9d764b4dc-hg948                   pod triggered scale-up: [{https://www.googleapis.com/compute/v1/projects/symbolic-voyage-359519/zones/us-central1-b/instanceGroups/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-grp 1->3 (max: 1000)}]
20m         Normal    NodeHasSufficientMemory   node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl status is now: NodeHasSufficientMemory
20m         Normal    Starting                  node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Starting kubelet.
20m         Normal    NodeHasSufficientPID      node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl status is now: NodeHasSufficientPID
20m         Normal    NodeHasNoDiskPressure     node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl status is now: NodeHasNoDiskPressure
20m         Normal    Starting                  node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Starting kubelet.
20m         Warning   InvalidDiskCapacity       node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   invalid capacity 0 on image filesystem
20m         Normal    NodeAllocatableEnforced   node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Updated Node Allocatable limit across pods
20m         Warning   InvalidDiskCapacity       node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   invalid capacity 0 on image filesystem
20m         Normal    NodeHasNoDiskPressure     node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 status is now: NodeHasNoDiskPressure
20m         Normal    NodeHasSufficientPID      node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 status is now: NodeHasSufficientPID
20m         Normal    NodeHasSufficientMemory   node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 status is now: NodeHasSufficientMemory
20m         Normal    NodeAllocatableEnforced   node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Updated Node Allocatable limit across pods
20m         Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl event: Registered Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl in Controller
20m         Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 event: Registered Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 in Controller
20m         Warning   KubeletStart              node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Started Kubernetes kubelet.
20m         Warning   DockerStart               node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Starting Docker Application Container Engine...
20m         Warning   ContainerdStart           node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Starting containerd container runtime...
20m         Warning   DockerStart               node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Starting Docker Application Container Engine...
20m         Warning   KubeletStart              node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Started Kubernetes kubelet.
20m         Warning   ContainerdStart           node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Starting containerd container runtime...
20m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-hg948                   0/5 nodes are available: 2 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 3 Insufficient cpu, 3 Insufficient memory.
20m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-6pw25                   0/5 nodes are available: 2 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 3 Insufficient cpu, 3 Insufficient memory.
19m         Normal    NodeReady                 node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 status is now: NodeReady
19m         Normal    TaintManagerEviction      pod/hello-world-rest-api-9d764b4dc-6pw25                   Cancelling deletion of Pod default/hello-world-rest-api-9d764b4dc-6pw25
19m         Normal    Scheduled                 pod/hello-world-rest-api-9d764b4dc-6pw25                   Successfully assigned default/hello-world-rest-api-9d764b4dc-6pw25 to gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5
19m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-hg948                   0/5 nodes are available: 1 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 4 Insufficient cpu, 4 Insufficient memory.
19m         Normal    Scheduled                 pod/hello-world-rest-api-9d764b4dc-hg948                   Successfully assigned default/hello-world-rest-api-9d764b4dc-hg948 to gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl
19m         Normal    NodeReady                 node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl status is now: NodeReady
19m         Normal    TaintManagerEviction      pod/hello-world-rest-api-9d764b4dc-hg948                   Cancelling deletion of Pod default/hello-world-rest-api-9d764b4dc-hg948
101s        Normal    UpdatedLoadBalancer       service/hello-world-rest-api                               Updated load balancer with new hosts
19m         Normal    Pulling                   pod/hello-world-rest-api-9d764b4dc-6pw25                   Pulling image "in28min/hello-world-rest-api:0.0.1.RELEASE"
19m         Normal    Pulling                   pod/hello-world-rest-api-9d764b4dc-hg948                   Pulling image "in28min/hello-world-rest-api:0.0.1.RELEASE"
19m         Normal    Pulled                    pod/hello-world-rest-api-9d764b4dc-6pw25                   Successfully pulled image "in28min/hello-world-rest-api:0.0.1.RELEASE" in 5.172381529s
19m         Normal    Pulled                    pod/hello-world-rest-api-9d764b4dc-hg948                   Successfully pulled image "in28min/hello-world-rest-api:0.0.1.RELEASE" in 4.171789491s
19m         Normal    Created                   pod/hello-world-rest-api-9d764b4dc-6pw25                   Created container hello-world-rest-api
19m         Normal    Created                   pod/hello-world-rest-api-9d764b4dc-hg948                   Created container hello-world-rest-api
19m         Normal    Started                   pod/hello-world-rest-api-9d764b4dc-hg948                   Started container hello-world-rest-api
19m         Normal    Started                   pod/hello-world-rest-api-9d764b4dc-6pw25                   Started container hello-world-rest-api
19m         Warning   FailedToUpdateEndpoint    endpoints/hello-world-rest-api                             Failed to update endpoint default/hello-world-rest-api: Operation cannot be fulfilled on endpoints "hello-world-rest-api": the object has been modified; please apply your changes to the latest version and try again
3m20s       Warning   FailedScheduling          pod/hello-world-rest-api-9697bb8f6-zmv64                   0/5 nodes are available: 5 Insufficient cpu, 5 Insufficient memory.
3m20s       Normal    SuccessfulCreate          replicaset/hello-world-rest-api-9697bb8f6                  Created pod: hello-world-rest-api-9697bb8f6-zmv64
3m20s       Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled up replica set hello-world-rest-api-9697bb8f6 to 1
3m13s       Normal    TriggeredScaleUp          pod/hello-world-rest-api-9697bb8f6-zmv64                   pod triggered scale-up: [{https://www.googleapis.com/compute/v1/projects/symbolic-voyage-359519/zones/us-central1-f/instanceGroups/gk3-vinay-autopilot-clus-default-pool-203925f2-grp 2->3 (max: 1000)}]
2m29s       Warning   InvalidDiskCapacity       node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   invalid capacity 0 on image filesystem
2m29s       Normal    NodeHasSufficientMemory   node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h status is now: NodeHasSufficientMemory
2m29s       Normal    NodeHasNoDiskPressure     node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h status is now: NodeHasNoDiskPressure
2m29s       Normal    NodeHasSufficientPID      node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h status is now: NodeHasSufficientPID
2m29s       Normal    NodeAllocatableEnforced   node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Updated Node Allocatable limit across pods
2m30s       Normal    Starting                  node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Starting kubelet.
2m23s       Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h event: Registered Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h in Controller
2m22s       Warning   KubeletStart              node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Started Kubernetes kubelet.
2m22s       Warning   ContainerdStart           node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Starting containerd container runtime...
2m22s       Warning   DockerStart               node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Starting Docker Application Container Engine...
118s        Warning   FailedScheduling          pod/hello-world-rest-api-9697bb8f6-zmv64                   0/6 nodes are available: 1 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 5 Insufficient cpu, 5 Insufficient memory.
118s        Normal    NodeReady                 node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h status is now: NodeReady
115s        Normal    Scheduled                 pod/hello-world-rest-api-9697bb8f6-zmv64                   Successfully assigned default/hello-world-rest-api-9697bb8f6-zmv64 to gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h
113s        Normal    TaintManagerEviction      pod/hello-world-rest-api-9697bb8f6-zmv64                   Cancelling deletion of Pod default/hello-world-rest-api-9697bb8f6-zmv64
0s          Warning   InspectFailed             pod/hello-world-rest-api-9697bb8f6-zmv64                   Failed to apply default image tag "DUMMY_IMAGE:TEST": couldn't parse image reference "DUMMY_IMAGE:TEST": invalid reference format: repository name must be lowercase
0s          Warning   Failed                    pod/hello-world-rest-api-9697bb8f6-zmv64                   Error: InvalidImageName
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl set image deployment hello-world-rest-api hello-world-rest-api=in28min/hello-world-rest-api:0.0.2.RELEASE --> VALID IMAGE NAME
deployment.apps/hello-world-rest-api image updated
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get pods
NAME                                   READY   STATUS        RESTARTS   AGE
hello-world-rest-api-67f8464cb-9k4hd   0/1     Pending       0          0s
hello-world-rest-api-67f8464cb-qbjqm   1/1     Running       0          9s
hello-world-rest-api-9d764b4dc-6pw25   1/1     Running       0          31m
hello-world-rest-api-9d764b4dc-gffb6   1/1     Running       0          32h
hello-world-rest-api-9d764b4dc-hg948   1/1     Terminating   0          31m
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get rs
NAME                              DESIRED   CURRENT   READY   AGE
hello-world-rest-api-67f8464cb    3         3         2       22s
hello-world-rest-api-6b7679fff7   0         0         0       7m12s
hello-world-rest-api-9697bb8f6    0         0         0       13m
hello-world-rest-api-9d764b4dc    1         1         1       32h
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get pods
NAME                                   READY   STATUS    RESTARTS   AGE
hello-world-rest-api-67f8464cb-9k4hd   1/1     Running   0          28s
hello-world-rest-api-67f8464cb-qbjqm   1/1     Running   0          37s
hello-world-rest-api-67f8464cb-rd6f8   1/1     Running   0          21s
vinay_raghu10@cloudshell:~ (symbolic-voyage-359519)$ kubectl get events --sort-by=.metadata.creationTimestamp
LAST SEEN   TYPE      REASON                    OBJECT                                                     MESSAGE
32m         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled up replica set hello-world-rest-api-9d764b4dc to 3
32m         Normal    SuccessfulCreate          replicaset/hello-world-rest-api-9d764b4dc                  Created pod: hello-world-rest-api-9d764b4dc-6pw25
32m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-hg948                   0/3 nodes are available: 2 Insufficient memory, 3 Insufficient cpu.
32m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-6pw25                   0/3 nodes are available: 2 Insufficient memory, 3 Insufficient cpu.
32m         Normal    SuccessfulCreate          replicaset/hello-world-rest-api-9d764b4dc                  Created pod: hello-world-rest-api-9d764b4dc-hg948
31m         Normal    TriggeredScaleUp          pod/hello-world-rest-api-9d764b4dc-6pw25                   pod triggered scale-up: [{https://www.googleapis.com/compute/v1/projects/symbolic-voyage-359519/zones/us-central1-b/instanceGroups/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-grp 1->3 (max: 1000)}]
31m         Normal    TriggeredScaleUp          pod/hello-world-rest-api-9d764b4dc-hg948                   pod triggered scale-up: [{https://www.googleapis.com/compute/v1/projects/symbolic-voyage-359519/zones/us-central1-b/instanceGroups/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-grp 1->3 (max: 1000)}]
31m         Normal    NodeAllocatableEnforced   node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Updated Node Allocatable limit across pods
31m         Normal    Starting                  node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Starting kubelet.
31m         Warning   InvalidDiskCapacity       node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   invalid capacity 0 on image filesystem
31m         Normal    Starting                  node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Starting kubelet.
31m         Normal    NodeHasSufficientMemory   node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl status is now: NodeHasSufficientMemory
31m         Warning   InvalidDiskCapacity       node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   invalid capacity 0 on image filesystem
31m         Normal    NodeHasSufficientPID      node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl status is now: NodeHasSufficientPID
31m         Normal    NodeHasNoDiskPressure     node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl status is now: NodeHasNoDiskPressure
31m         Normal    NodeAllocatableEnforced   node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Updated Node Allocatable limit across pods
31m         Normal    NodeHasNoDiskPressure     node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 status is now: NodeHasNoDiskPressure
31m         Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl event: Registered Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl in Controller
31m         Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 event: Registered Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 in Controller
31m         Normal    NodeHasSufficientPID      node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 status is now: NodeHasSufficientPID
31m         Normal    NodeHasSufficientMemory   node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 status is now: NodeHasSufficientMemory
31m         Warning   DockerStart               node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Starting Docker Application Container Engine...
31m         Warning   KubeletStart              node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Started Kubernetes kubelet.
31m         Warning   DockerStart               node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Starting Docker Application Container Engine...
31m         Warning   KubeletStart              node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Started Kubernetes kubelet.
31m         Warning   ContainerdStart           node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Starting containerd container runtime...
31m         Warning   ContainerdStart           node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Starting containerd container runtime...
31m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-6pw25                   0/5 nodes are available: 2 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 3 Insufficient cpu, 3 Insufficient memory.
31m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-hg948                   0/5 nodes are available: 2 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 3 Insufficient cpu, 3 Insufficient memory.
30m         Normal    NodeReady                 node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 status is now: NodeReady
30m         Normal    TaintManagerEviction      pod/hello-world-rest-api-9d764b4dc-6pw25                   Cancelling deletion of Pod default/hello-world-rest-api-9d764b4dc-6pw25
30m         Normal    Scheduled                 pod/hello-world-rest-api-9d764b4dc-6pw25                   Successfully assigned default/hello-world-rest-api-9d764b4dc-6pw25 to gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5
30m         Warning   FailedScheduling          pod/hello-world-rest-api-9d764b4dc-hg948                   0/5 nodes are available: 1 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 4 Insufficient cpu, 4 Insufficient memory.
30m         Normal    TaintManagerEviction      pod/hello-world-rest-api-9d764b4dc-hg948                   Cancelling deletion of Pod default/hello-world-rest-api-9d764b4dc-hg948
30m         Normal    Scheduled                 pod/hello-world-rest-api-9d764b4dc-hg948                   Successfully assigned default/hello-world-rest-api-9d764b4dc-hg948 to gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl
30m         Normal    NodeReady                 node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl status is now: NodeReady
12m         Normal    UpdatedLoadBalancer       service/hello-world-rest-api                               Updated load balancer with new hosts
30m         Normal    Pulling                   pod/hello-world-rest-api-9d764b4dc-6pw25                   Pulling image "in28min/hello-world-rest-api:0.0.1.RELEASE"
30m         Normal    Pulling                   pod/hello-world-rest-api-9d764b4dc-hg948                   Pulling image "in28min/hello-world-rest-api:0.0.1.RELEASE"
30m         Normal    Pulled                    pod/hello-world-rest-api-9d764b4dc-6pw25                   Successfully pulled image "in28min/hello-world-rest-api:0.0.1.RELEASE" in 5.172381529s
30m         Normal    Pulled                    pod/hello-world-rest-api-9d764b4dc-hg948                   Successfully pulled image "in28min/hello-world-rest-api:0.0.1.RELEASE" in 4.171789491s
30m         Normal    Created                   pod/hello-world-rest-api-9d764b4dc-6pw25                   Created container hello-world-rest-api
30m         Normal    Created                   pod/hello-world-rest-api-9d764b4dc-hg948                   Created container hello-world-rest-api
30m         Normal    Started                   pod/hello-world-rest-api-9d764b4dc-6pw25                   Started container hello-world-rest-api
30m         Normal    Started                   pod/hello-world-rest-api-9d764b4dc-hg948                   Started container hello-world-rest-api
30m         Warning   FailedToUpdateEndpoint    endpoints/hello-world-rest-api                             Failed to update endpoint default/hello-world-rest-api: Operation cannot be fulfilled on endpoints "hello-world-rest-api": the object has been modified; please apply your changes to the latest version and try again
13m         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled up replica set hello-world-rest-api-9697bb8f6 to 1
13m         Warning   FailedScheduling          pod/hello-world-rest-api-9697bb8f6-zmv64                   0/5 nodes are available: 5 Insufficient cpu, 5 Insufficient memory.
13m         Normal    SuccessfulCreate          replicaset/hello-world-rest-api-9697bb8f6                  Created pod: hello-world-rest-api-9697bb8f6-zmv64
13m         Normal    TriggeredScaleUp          pod/hello-world-rest-api-9697bb8f6-zmv64                   pod triggered scale-up: [{https://www.googleapis.com/compute/v1/projects/symbolic-voyage-359519/zones/us-central1-f/instanceGroups/gk3-vinay-autopilot-clus-default-pool-203925f2-grp 2->3 (max: 1000)}]
13m         Warning   InvalidDiskCapacity       node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   invalid capacity 0 on image filesystem
13m         Normal    NodeHasSufficientMemory   node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h status is now: NodeHasSufficientMemory
13m         Normal    NodeHasNoDiskPressure     node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h status is now: NodeHasNoDiskPressure
13m         Normal    NodeHasSufficientPID      node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h status is now: NodeHasSufficientPID
13m         Normal    Starting                  node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Starting kubelet.
13m         Normal    NodeAllocatableEnforced   node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Updated Node Allocatable limit across pods
12m         Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h event: Registered Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h in Controller
12m         Warning   KubeletStart              node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Started Kubernetes kubelet.
12m         Warning   ContainerdStart           node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Starting containerd container runtime...
12m         Warning   DockerStart               node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Starting Docker Application Container Engine...
12m         Warning   FailedScheduling          pod/hello-world-rest-api-9697bb8f6-zmv64                   0/6 nodes are available: 1 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate, 5 Insufficient cpu, 5 Insufficient memory.
12m         Normal    NodeReady                 node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h status is now: NodeReady
12m         Normal    Scheduled                 pod/hello-world-rest-api-9697bb8f6-zmv64                   Successfully assigned default/hello-world-rest-api-9697bb8f6-zmv64 to gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h
12m         Normal    TaintManagerEviction      pod/hello-world-rest-api-9697bb8f6-zmv64                   Cancelling deletion of Pod default/hello-world-rest-api-9697bb8f6-zmv64
10m         Warning   Failed                    pod/hello-world-rest-api-9697bb8f6-zmv64                   Error: InvalidImageName
9m55s       Warning   InspectFailed             pod/hello-world-rest-api-9697bb8f6-zmv64                   Failed to apply default image tag "DUMMY_IMAGE:TEST": couldn't parse image reference "DUMMY_IMAGE:TEST": invalid reference format: repository name must be lowercase
7m33s       Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled up replica set hello-world-rest-api-6b7679fff7 to 1
7m33s       Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled down replica set hello-world-rest-api-9697bb8f6 to 0
7m33s       Normal    SuccessfulDelete          replicaset/hello-world-rest-api-9697bb8f6                  Deleted pod: hello-world-rest-api-9697bb8f6-zmv64
7m32s       Warning   FailedScheduling          pod/hello-world-rest-api-6b7679fff7-7ls9r                  0/6 nodes are available: 6 Insufficient cpu, 6 Insufficient memory.
7m32s       Normal    SuccessfulCreate          replicaset/hello-world-rest-api-6b7679fff7                 Created pod: hello-world-rest-api-6b7679fff7-7ls9r
7m31s       Normal    Scheduled                 pod/hello-world-rest-api-6b7679fff7-7ls9r                  Successfully assigned default/hello-world-rest-api-6b7679fff7-7ls9r to gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h
5m13s       Warning   Failed                    pod/hello-world-rest-api-6b7679fff7-7ls9r                  Error: InvalidImageName
2m28s       Warning   InspectFailed             pod/hello-world-rest-api-6b7679fff7-7ls9r                  Failed to apply default image tag "0.0.2.RELEASE": couldn't parse image reference "0.0.2.RELEASE": invalid reference format: repository name must be lowercase
5m7s        Normal    EnsuringLoadBalancer      service/hello-world-rest-api                               Ensuring load balancer
5m6s        Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 event: Registered Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5 in Controller
5m6s        Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl event: Registered Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl in Controller
5m6s        Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-203925f2-r366   Node gk3-vinay-autopilot-clus-default-pool-203925f2-r366 event: Registered Node gk3-vinay-autopilot-clus-default-pool-203925f2-r366 in Controller
5m6s        Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h   Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h event: Registered Node gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h in Controller
5m6s        Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-n23l   Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-n23l event: Registered Node gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-n23l in Controller
5m6s        Normal    RegisteredNode            node/gk3-vinay-autopilot-clus-default-pool-203925f2-4fpg   Node gk3-vinay-autopilot-clus-default-pool-203925f2-4fpg event: Registered Node gk3-vinay-autopilot-clus-default-pool-203925f2-4fpg in Controller
5m6s        Normal    UpdatedLoadBalancer       service/hello-world-rest-api                               Updated load balancer with new hosts
5m2s        Normal    EnsuredLoadBalancer       service/hello-world-rest-api                               Ensured load balancer
43s         Normal    SuccessfulDelete          replicaset/hello-world-rest-api-6b7679fff7                 Deleted pod: hello-world-rest-api-6b7679fff7-7ls9r
43s         Warning   FailedScheduling          pod/hello-world-rest-api-67f8464cb-qbjqm                   0/6 nodes are available: 6 Insufficient cpu, 6 Insufficient memory.
43s         Normal    SuccessfulCreate          replicaset/hello-world-rest-api-67f8464cb                  Created pod: hello-world-rest-api-67f8464cb-qbjqm
43s         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled up replica set hello-world-rest-api-67f8464cb to 1
43s         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled down replica set hello-world-rest-api-6b7679fff7 to 0
41s         Normal    Scheduled                 pod/hello-world-rest-api-67f8464cb-qbjqm                   Successfully assigned default/hello-world-rest-api-67f8464cb-qbjqm to gk3-vinay-autopilot-clus-default-pool-203925f2-7j7h
40s         Normal    Pulling                   pod/hello-world-rest-api-67f8464cb-qbjqm                   Pulling image "in28min/hello-world-rest-api:0.0.2.RELEASE"
37s         Normal    Pulled                    pod/hello-world-rest-api-67f8464cb-qbjqm                   Successfully pulled image "in28min/hello-world-rest-api:0.0.2.RELEASE" in 3.238690384s
36s         Normal    Created                   pod/hello-world-rest-api-67f8464cb-qbjqm                   Created container hello-world-rest-api
35s         Normal    Started                   pod/hello-world-rest-api-67f8464cb-qbjqm                   Started container hello-world-rest-api
34s         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled up replica set hello-world-rest-api-67f8464cb to 2
34s         Normal    SuccessfulCreate          replicaset/hello-world-rest-api-67f8464cb                  Created pod: hello-world-rest-api-67f8464cb-9k4hd
34s         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled down replica set hello-world-rest-api-9d764b4dc to 2
34s         Normal    Killing                   pod/hello-world-rest-api-9d764b4dc-hg948                   Stopping container hello-world-rest-api
34s         Warning   FailedScheduling          pod/hello-world-rest-api-67f8464cb-9k4hd                   0/6 nodes are available: 6 Insufficient cpu, 6 Insufficient memory.
34s         Normal    SuccessfulDelete          replicaset/hello-world-rest-api-9d764b4dc                  Deleted pod: hello-world-rest-api-9d764b4dc-hg948
33s         Normal    Scheduled                 pod/hello-world-rest-api-67f8464cb-9k4hd                   Successfully assigned default/hello-world-rest-api-67f8464cb-9k4hd to gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-svnl
31s         Normal    Pulling                   pod/hello-world-rest-api-67f8464cb-9k4hd                   Pulling image "in28min/hello-world-rest-api:0.0.2.RELEASE"
29s         Normal    Created                   pod/hello-world-rest-api-67f8464cb-9k4hd                   Created container hello-world-rest-api
29s         Normal    Pulled                    pod/hello-world-rest-api-67f8464cb-9k4hd                   Successfully pulled image "in28min/hello-world-rest-api:0.0.2.RELEASE" in 1.970706801s
28s         Normal    Started                   pod/hello-world-rest-api-67f8464cb-9k4hd                   Started container hello-world-rest-api
27s         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled up replica set hello-world-rest-api-67f8464cb to 3
27s         Normal    Killing                   pod/hello-world-rest-api-9d764b4dc-6pw25                   Stopping container hello-world-rest-api
27s         Normal    SuccessfulDelete          replicaset/hello-world-rest-api-9d764b4dc                  Deleted pod: hello-world-rest-api-9d764b4dc-6pw25
27s         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled down replica set hello-world-rest-api-9d764b4dc to 1
27s         Warning   FailedScheduling          pod/hello-world-rest-api-67f8464cb-rd6f8                   0/6 nodes are available: 6 Insufficient cpu, 6 Insufficient memory.
27s         Normal    SuccessfulCreate          replicaset/hello-world-rest-api-67f8464cb                  Created pod: hello-world-rest-api-67f8464cb-rd6f8
26s         Normal    Scheduled                 pod/hello-world-rest-api-67f8464cb-rd6f8                   Successfully assigned default/hello-world-rest-api-67f8464cb-rd6f8 to gk3-vinay-autopilot-clus-default-pool-f6e2ce4c-shc5
23s         Normal    Pulling                   pod/hello-world-rest-api-67f8464cb-rd6f8                   Pulling image "in28min/hello-world-rest-api:0.0.2.RELEASE"
21s         Normal    Pulled                    pod/hello-world-rest-api-67f8464cb-rd6f8                   Successfully pulled image "in28min/hello-world-rest-api:0.0.2.RELEASE" in 2.01415275s
21s         Normal    Created                   pod/hello-world-rest-api-67f8464cb-rd6f8                   Created container hello-world-rest-api
20s         Normal    Started                   pod/hello-world-rest-api-67f8464cb-rd6f8                   Started container hello-world-rest-api
20s         Normal    ScalingReplicaSet         deployment/hello-world-rest-api                            Scaled down replica set hello-world-rest-api-9d764b4dc to 0
20s         Normal    SuccessfulDelete          replicaset/hello-world-rest-api-9d764b4dc                  Deleted pod: hello-world-rest-api-9d764b4dc-gffb6
18s         Normal    Killing                   pod/hello-world-rest-api-9d764b4dc-gffb6                   Stopping container hello-world-rest-api 

3pods deployment 
V2 deploy  in 1 pod then V1 with 2 pods, slowly sping up V and remove V1 pods.

Deployments ==> Replicasets --> Pods

Deployment can be rolling update like above, or 50% traffic to V1  and other to V2, and then 100% 
6. Understand Service in kubernetes 
A service allows to get traffic from a life time permanent address
if you a delete a pod and spin up a new pod, new ip address will be generated, but how it is managing, which is because of service.
pod is like a throw away unit.
kubectl get services --> gives you list of services 
All the above operations can be performed from UI as well
Workloads, select your host and you can see the options 
 bet way to do it is via console
7. Understanding Kubernetes architecture
Master Node contains below
Desired state is stored in distributed database(etcd) --> All configurations are stored here
kube-API server --> API's that helps to interact with clusters and others
Scheduler --> scheduling pods on to the nods
Controller manager --> manages overall health of the cluster 
Worker Node contains below
On a single node you might have several pods
Node Agent --> kubelet --> make sure it monitors and report back to nod
Networking Component --> kubeproxy
Container Run time --> ex: docker

Cluster contains both master(which manages your cluster) and worker node(Run your application)

kubectl get componentstatuses
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok
etcd-1               Healthy   {"health":"true"}
etcd-0               Healthy   {"health":"true"}
controller-manager   Healthy   ok
8. Installing GCloud
Have Gcloud installed on your machine
https://cloud.google.com/sdk/docs/downloads-interactive
gcloud auth login --> allow to login to gcloud.
9. Installing Kubectl
brew install kubectl --> should install kubectl
check for version --> kubectl version
Click on Cluster --> Connect
copy the cmmnad and paster it in our terminal, which allows to connect to your gcloud container.
This will allow you to connect gcloud server from local
10. Let's try to deploy applications
Let's make some changes to pom.xml
in currency-conversion-service, look for <!-- KUBERNETES CHANGE -->
<?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 https://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.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.vinay.micrservices</groupId>
<artifactId>currency-conversion-service</artifactId>
<version>0.0.11-SNAPSHOT</version> <!-- KUBERNETES CHANGE -->
<name>currency-conversion-service-kubernetes</name> <!-- KUBERNETES CHANGE -->
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>-->
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>vinay/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
</plugin>
</plugins>
</build>

</project>
in currency conversion 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 https://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.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.vinay.micrservices</groupId>
<artifactId>currency-conversion-service</artifactId>
<version>0.0.11-SNAPSHOT</version> <!-- KUBERNETES CHANGE -->
<name>currency-conversion-service-kubernetes</name> <!-- KUBERNETES CHANGE -->
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>-->
<!-- KUBERNETES CHANGE -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>vinay/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
</plugin>
</plugins>
</build>

</project>
in currency conversion controller, adding logging statement
//Change Kubernetes
logger.info("calculateCurrencyConversion called with {} to {} with {}", from, to, quantity);
in currency exchange service
package com.vinay.microservices.currencyexchangeservice;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CurrencyExchangeController {

private Logger logger = LoggerFactory.getLogger(CurrencyExchangeController.class);

@Autowired
private CurrencyExchangeRepository currencyExchangeRepository;

@Autowired
private Environment environment;

@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyExchange retrieveExchangeValue(@PathVariable String from,
@PathVariable String to){
logger.info("retrieveExchangeValue called with {} to {}", from, to);
CurrencyExchange currencyExchange = currencyExchangeRepository.findByFromAndTo(from, to);
if (currencyExchange == null) {
throw new RuntimeException("No records found");
}
// CHANGE KUBERNETES
String port = environment.getProperty("local.server.port");
String host = environment.getProperty("HOSTNAME");
String version = "v11";

currencyExchange.setEnvironment(port+" "+version+" "+host);
return currencyExchange;
}
}
in application.properties of curency exchange service and currency conversion service
#CHANGE KUBERNETES
management.endpoint.health.probes.enabled=true
management.health.livenessState.enabled=true
management.health.readinessState.enabled=true
in proxy have this change
package com.vinay.micrservices.currencyconversionservice;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

//@FeignClient(name = "currency-exchange-service", url = "localhost:8000")
@FeignClient(name = "currency-exchange-service", url = "${CURRENCY_EXCHANGE_SERVICE_HOST:http://localhost}:8000")
//@FeignClient(name = "currency-exchange-service")
public interface CurrencyExchangeProxy {

@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeValue(@PathVariable String from,
@PathVariable String to);
}
11. Container images for exchange and currency conversion
have your docker id in the configuration
<configuration>
<image>
<name><<docker_id>>/mmv-${project.artifactId}:${project.version}</name>
</image>
<pullPolicy>IF_NOT_PRESENT</pullPolicy>
</configuration>
build containers
docker login to your terminal cpy the image name
let's push the image to hub
docker push <<docker id>>/mmv-currency-conversion-service:0.0.11-SNAPSHOT
check you hub.docker.com, this image should be there.
12. Deploy microservices to kubernetes

kubectl create deployment currency-exchange --image=vinayraghu/mmv-currency-exchange-service:0.0.11-SNAPSHOT

W0819 11:42:46.609811   37955 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.

To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke

Warning: Autopilot set default resource requests for Deployment default/currency-exchange, as resource requests were not specified. See http://g.co/gke/autopilot-defaults.

deployment.apps/currency-exchange created

kubectl expose deployment currency-exchange --type=LoadBalancer --port=8000

W0819 11:43:49.893846   38128 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.

To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke

service/currency-exchange exposed

kubectl get services

W0819 11:46:46.265926   38654 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead.

To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke

NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)          AGE

currency-exchange      LoadBalancer   10.104.130.115   34.66.148.201    8000:30411/TCP   2m53s

hello-world-rest-api   LoadBalancer   10.104.129.174   34.170.252.132   8080:30516/TCP   3d10h

kubernetes             ClusterIP      10.104.128.1     <none>           443/TCP          3d10h

now invoke curl command to the endpoint replacing localhost with external ip
you should be able to get the response.
by default it will create project name _service_host i.e. CURRECNY_EXCHANGE_SERVICE_HOST
pod name you can get it via hostname environment variable 
13. Creating declarative configuration kubernetes YAML 
we can do the deployments using yaml as well, let's take a look at it.

kubectl get deployment currency-exchange -o yaml >> deployment.yaml

will create yaml
do the same thing for service as well

kubectl get service currency-exchange -o yaml >> service.yaml

we can keep both yaml in single file with  ---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
autopilot.gke.io/resource-adjustment: '{"input":{"containers":[{"name":"mmv-currency-exchange-service"}]},"output":{"containers":[{"limits":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"},"requests":{"cpu":"500m","ephemeral-storage":"1Gi","memory":"2Gi"},"name":"mmv-currency-exchange-service"}]},"modified":true}'
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2022-08-19T06:50:29Z"
generation: 1
labels:
app: currency-exchange
name: currency-exchange
namespace: default
resourceVersion: "2960943"
uid: 0d0b96d8-e8fc-44cf-b328-023098749058
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: currency-exchange
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: currency-exchange
spec:
containers:
- image: vinayraghu/mmv-currency-exchange-service:0.0.11-SNAPSHOT
imagePullPolicy: IfNotPresent
name: mmv-currency-exchange-service
resources:
limits:
cpu: 500m
ephemeral-storage: 1Gi
memory: 2Gi
requests:
cpu: 500m
ephemeral-storage: 1Gi
memory: 2Gi
securityContext:
capabilities:
drop:
- NET_RAW
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext:
seccompProfile:
type: RuntimeDefault
terminationGracePeriodSeconds: 30
status:
conditions:
- lastTransitionTime: "2022-08-19T06:50:29Z"
lastUpdateTime: "2022-08-19T06:50:33Z"
message: ReplicaSet "currency-exchange-55fb6ddfcc" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
- lastTransitionTime: "2022-08-19T07:44:00Z"
lastUpdateTime: "2022-08-19T07:44:00Z"
message: Deployment does not have minimum availability.
reason: MinimumReplicasUnavailable
status: "False"
type: Available
observedGeneration: 1
replicas: 1
unavailableReplicas: 1
updatedReplicas: 1
---
apiVersion: v1
kind: Service
metadata:
annotations:
cloud.google.com/neg: '{"ingress":true}'
creationTimestamp: "2022-08-19T06:52:18Z"
finalizers:
- service.kubernetes.io/load-balancer-cleanup
labels:
app: currency-exchange
name: currency-exchange
namespace: default
resourceVersion: "2929858"
uid: 573ba524-8f42-42ca-b46d-9a9ca40f03bf
spec:
allocateLoadBalancerNodePorts: true
clusterIP: 10.104.128.131
clusterIPs:
- 10.104.128.131
externalTrafficPolicy: Cluster
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- nodePort: 31970
port: 8000
protocol: TCP
targetPort: 8000
selector:
app: currency-exchange
sessionAffinity: None
type: LoadBalancer
status:
loadBalancer:
ingress:
        - ip: 35.193.100.85  

Let's updateate replica's to 2 
kubectl diff -f deployment.yaml
Let's apply
kubectl apply -f deployment.yaml
Now check for kubectl get pods
you can see 2 instances of curency-exchange service running
in layman terms, we are setting desired state in yaml
service balaning and load balancer are provided for free
14. clean up kubernetes yaml
15. Enable logging and tracing api's in GCP 
Let's enable API services
API services --> Enable API services --> Cloud logging API --> which should be already enabled, if not enable
Enable Stack driver API's as well
16. Let's deploy microservices using Yaml
let's delete all the deployments
kubectl delete all -l app=currency-exchange
pod "currency-exchange-55fb6ddfcc-4xtpv" deleted
service "currency-exchange" deleted
deployment.apps "currency-exchange" deleted
kubectl get all --> will list down everything
let's try deploying use yaml

kubectl apply -f deployment.yaml

which makes life easire by just configuring yaml
kubectl get svc --watch --> you can watch the live status of service
16. lets create environment variable to enable microservices
whenever you do any changes to project , generate a new image with snapshot
in proxy, instead of using CURRENCY_EXCHANGE_SERVICE_HOST, mark it to CURRENCY_EXCHANGE_URI and build images and push them
make the change in yaml for environment variable
containers:
- image: vinayraghu/mmv-currency-exchange-service:0.0.11-SNAPSHOT
imagePullPolicy: IfNotPresent
name: mmv-currency-exchange-service
env:
- name: CURRENCY_EXCHANGE_URI
value: http://currency-exchange
once check for the deployment logs
kubectl logs <<pod_name>>
17. Understanding centralized configuration in kubernetes
we can do this by creating a config map.
kubectl create configmap currency-conversion --from-literal=CURRENCY_EXCHANGE_URI=http://currency-exchange
you can the yaml that's created
kubectl get configmap currency-conversion -o yaml
let's save it into a file
kubectl get configmap currency-conversion -o yaml >> configmap.yaml
add this to your deployment.yaml and refer to it like below
envFrom:
       -configMapref:
            name: currency-conversion
you can view logs from GKE dashboard
you can fire queries and check for logs
you can check history of deployment to any service
kubectl rollout history deployment currency-conversion
we can undo rollout as well
kubectl rollout undo deployment currency-exchange --to-revision=1
there is a downtime when deployments are happening, how can we avoid it
18. Configuring liveness and Readiness probes for microservice
readiness probe is not successful, no traffic is sent
liveness probe not successful, pod is restarted
you can liveness and readiness from actuator
readinessProbe:
    httpGet:
        port: 8000
        path: /actuator/health/readiness
livenessProbe:
    httpGet:
        port: 8000
        path: /actuator/health/liveness
this will ensure there is no downtime
19. Autoscaling microservices with kubernetes
kubectl autoscale deployment currency-exchange --min=1 --max=3 --cpu-percent=5
kubectl get hpa

Resource 

Git: https://github.com/in28minutes/spring-microservices
Images: https://hub.docker.com/r/in28min/hello-world-rest-api/tags

Comments

Popular posts from this blog

TestNg - Test Automation Framework

React Js and Redux

Appium 7 - Android Test Automation