본문 바로가기

IT/Programming

Cloud Native (Spring Cloud) 기반 Micro Service 만들기 - 첫번째

아래 Josh Long의 github에 있는 워크샵을 기반으로, Cloud Native 기반의 Reservation Microservice를 만들어 보도록 하겠다.


https://github.com/joshlong/cloud-native-workshop



1. reservation-service 프로젝트 생성하기


(1) Spring Initializr를 이용하여 Web, JPA, H2, Actuator, Lombok, Cloud Contract Verifier, Integration을 선택하여 Maven 프로젝트 <reservation-service> 를 생성한다.


(2) Reservation이라는 이름의 entity 객체를 생성한다. 필드는 id와 reservationName이 있다. Lombok을 사용하여 Setter/Getter/Constructer의 코드를 프레임워크에서 생성할 수 있도록 한다. - Lombok은 컴파일 단에서만 작동하는 프레임워크로, 일단 컴파일이 되면 런타임에서는 메뉴얼로 생성한 코드와 똑같이 작동하므로, 퍼포먼스의 영향은 없이 필요한 로직에만 집중할 수 있도록 해준다.

(3) ReservationRepository라는 JPA repository를 생성하여 준다. method의 이름으로 query를 정의하는 것을 query method라고 한다.

(참고) https://docs.spring.io/spring-data/jpa/docs/1.7.0.RELEASE/reference/html/#jpa.query-methods


(4) Main Application에 아래와 같은 Bean을 추가하여 기본적으로 테스트용의 예약자 이름 리스트가 추가될 수 있도록 하자.

(5) 좀더 프로덕션 환경에 맞는 서비스를 만들기 위해 다음을 추가하자.

  • Maven Dependency에 org.springframework.boot:spring-boot-starter-actuator를 추가한다.
  • Custom HealthIndicator를 추가한다.
  • HAL browser를 추가한다. - org.springframework.data:spring-data-rest-hal-browser 그리고 이것을 통해 Actuator의 endpoint를 볼 수 있다.
  • @RepositoryEventHandler와 @Component Annotation을 가진 새로운 Class를 추가한다. @HandleAfterCreate에 대한 handler를 클래스 안에서 제공한다.

(6) http://localhost:8000/reservations REST API가 정상적으로 작동하는 것을 확인하자..

(7) http://localhost:8000/browser/index.html 를 브라우저에서 열어보면 아래와 같이 HAL Browser의 화면이 로드되는 것을 확인할 수 있다.





2. config-service 프로젝트 생성하기


12 Factor App에서는 여러 환경에서 변경될 수 있는 부분을 어플리케이션에서 외부화 하는 것에 대해서 말하고 있다. Spring Boot는 이런 패턴을 서포트하는 방법을 제공하는데, Spring Cloud를 이용해서 중앙에서 관리가 가능하고, 외부화하면서, 동적으로 업데이트가 가능한 application의 configuration을 구성하는 방법을 구성해 보도록 한다.


(1) Spring Initializr를 이용하여 Config Server를 선택하여 Maven 프로젝트<config-service>를 생성한다.

(2) 다음 Git 저장소를 Git Clone하여 Local에 저장한다. - https://github.com/joshlong/bootiful-microservices-config

(3) Config Server의 application.properties에 port 8888을 사용하여 서버가 구동될 수 있도록 한다. (server.port=8888) 그리고 git clone된 configuration의 root directory안에서  Git repository를 관리할 수 있도록 한다. (spring.cloud.config.server.git.uri=...)


config-service의 Main Class에 @EnableConfigServer Annotation을 추가한다.


reservation-service를 config client로 만들어 주기 위해 pom.xml의 dependency management block 에 다음을 추가한다.


다음 org.springframework.cloud:spring-cloud-starter-config를 reservation-service의 dependency block 에 추가한다.


src/main/resources/application.properties를 삭제하고, 대신 src/main/resources/bootstrap.properties 파일을 생성한다. spring.cloud.config.uri 에는 Config Server의 주소를, spring.application.name에는 Config Server에서 가져올 configuration의 id를 입력해야 한다.


reservation-service에 다음과 같은 REST Controller를 추가하여 Config Server에서 받아온 message를 return하는 방법을 알아보자.

@RefreshScope Annotation을 주면, Config Server에서 변경된 Configuration에 대해 refresh 해줄 수 있다.

이렇게 해서 config-service와 reservation-service를 시작해 주도록 하자. mvn spring-boot:start


정상적으로 시작이 되었다면, 두 service다 에러없이 시작되고, reservation-service는 config-service를 통해서, local에 clone된 configuration "reservation-service.properties"를 load해야 한다.

다음과 같이 message end point에 대한 REST request에 정상적으로 반응하는 것을 볼 수 있다.

[dev@reservation-service]$ curl http://localhost:8000/message -i
HTTP/1.1 200;
X-Application-Context: reservation-service:8000
Content-Type: text/plain;charset=UTF-8
Content-Length: 9
Date: Tue, 12 Sep 2017 01:49:40 GMT
Hello DR!
[dev@reservation-service]
참고로, Config Server의 변경된 Configuration을 반영하기 위해서는, 다음과 같이 /bootiful-microservices-config/reservation-service.properties에서 Configuration을 변경하고 (Hello DR! 을 Hello DR! Especially you are very welcome!!!!으로 변경했다.)


다음 git commit을 해준다.


[dev@reservation-service]$ git add reservation-service.properties
[dev@reservation-service]$ git commit



reservation-service에 대해 /refresh End Point를 POST로 요청하면 Config Server에서 Configuration이 Refresh 된다. (Git의 Change가 발생될 때 자동으로 Refresh되면 좋겠지만 안타깝게도 지원하지 않는다.)


[dev@reservation-service]$ curl http://localhost:8000/refresh -i -XPOST
HTTP/1.1 200
X-Application-Context: reservation-service:8000
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 12 Sep 2017 02:12:20 GMT

[dev@reservation-service]$ curl http://localhost:8000/message -i
HTTP/1.1 200
X-Application-Context: reservation-service:8000
Content-Type: text/plain;charset=UTF-8
Content-Length: 45
Date: Tue, 12 Sep 2017 02:12:27 GMT

Hello DR! Especially you are very welcome!!!!



3. 서비스의 등록과 발견


클라우드 환경에서는 서비스의 운영 주기가 짧은 편이다. 그래서 서비스의 호스트 이름이나 포트를 알지 못해도 추상적으로 이 서비스들을 운영하고 접근할 수 있는 것이 필요하다. 이를 위해 DNS와 같은 방법을 생각해 볼 수 있겠지만, DNS는 서비스를 발견하거나, 정교한 로드 밸런싱이 필요한 서비스 환경에서는 적합하지 않다. 또한 필요이상으로 Hoping하는 등의 부작용도 있기에 보다 적합한 서비스의 등록과 발견의 방법이 필요하다. 이를 위해 서비스를 등록과 Spring Cloud의 DisvoeryClient 추상화를 사용하기로 한다.


(1) Spring Initializr를 이용하여 Eureka Server를 선택하여 Maven 프로젝트<eureka-service>를 생성한다.

(2) Main Class에 @EnableEurekaServer annotation을 붙여준다.

(3) config-service 에서 configuration을 불러올 수 있도록 org.springframework.cloud : spring-cloud-starter-config 를 Maven Dependency에 추가하여 준다.

(4) org.springframework.cloud : spring-cloud-starter-eureka 를 reservation-service의 Maven Dependency에 추가하여 준다.

(5) reservation-service의 Main Class에 @EnableDiscoveryClient annotation을 붙여준다.

(6) eureka-service를 시작하고, reservation-service를 재시작 하여, http://localhost:8761 를 웹브라우저에서 접속하여 아래와 같이 reservation-service에 대한 상태에 대한 Eureka 서버의 내용이 나오는 것을 확인한다.



4. API Gateway : Edge Service


Edge service는 클라이언트 (스마트폰, HTML5 Application 등) 와 서비스 사이에서 중개하는 역할을 하는 서비스이다. edge service는 시큐리티, API Translation, Protocol Translation과 같은 클라이언트가 필요로 하는 요구사항들을 집어 넣을 수 있는 논리적 장소이다. 또한 이런 로직들을 각 서비스 단에서 구현할 필요가 없도록 도와주는 역할을 하는 mid tier 서비스이다.

(1) Spring Initializr를 이용하여 Web, Lombok, Feig, Zuul, Hystrix, Eureka Discovery, Config Client 를 선택하여 Maven 프로젝트<reservation-client>를 생성한다.

(2) bootstrap.properties를 생성하여, 다른 프로젝트와 같지만, 이름을 reservation-client로 입력한다.

(3) DiscoveryClient를 사용하여 다른 서비스들을 찾도록 CommandLineRunner를 작성한다.

(4) org.springframework.cloud:spring-cloud-starter-zuul 를 Maven Dependency에 추가하고, @EnableZuulProxy를 Main Class에 추가한다.

(5) reservation-client 서비스를 시작하고, 웹브라우저를 열어서 http://localhost:9999/reservation-service/reservations 를 접속해서, http://localhost:8000/reservations 로 proxy 되는 것을 확인한다.



5. API Tranaslation

API gateway는 스마트폰이나 HTML5와 같은 클라이언트에서 사용될 수 있는 데, 데이터의 양을 줄이거나, 데이터에 대한 변경된 형태를 필요로 할 때 API translation의 방법으로 사용될 수 있다.

(1) reservation-client에 Reservation이라는 이름으로 DTO Class를 생성한다. 이 Class는 reservation-service로 부터 데이터를 받아서 임시로 보관하는 역할을 한다. 클라이언트와 서비스 간에 긴밀히 연결되는 것을 방지하기 위해 이런 방법을 사용하자.

(2) org.springframework.boot:spring-boot-starter-hateoas 를 reservation-client Maven Dependency에 추가하자.

(참고) Hateoas는 Hypermedia as the Engine of Application State의 약자로서, 호출된 REST API와 관련된 hyper media link를 제공하여 어디로 갈지 안내해 주는 역할을 한다. 자세한 것은 이 링크를 참조하자. https://spring.io/understanding/HATEOAS

(3) ReservationApiGatewayRestController 라는 REST Controller Class를 reservation-client에 추가하자. 그리고 @Autowired @LoadBalanced Annotation을 붙이 RestTemplate rt 을 field 변수로 추가하자. 이렇게 하면 Neflix의 Ribbon이라는 기술을 사용하여 레지스트리에 있는 서비스에 대해 load balance된 서비스 호출을 할 수 있다.

(4) Controller Classs는 /reservations에 매핑하고, getReservationNames라는 새로운 Controller 핸들러 메소드를 생성하여 /names에 매핑한다.

(5) getReservationNames 핸들러는 http://reservation-service/reservations RestTemplate#exchange method를 이용하여 REST API를 호출한다. 이때 return type을 exchange method의 argument로 ParameterizedTypeReference를 전달하여 구체화하도록 한다.

(6) 서비스를 시작하여, http://localhost:9999/reservations/names 가 정상적으로 names를 리턴하는지 확인하도록 하자.


[dev@localhost ~]$ curl http://localhost:9999/reservations/names -i
HTTP/1.1 200 
X-Application-Context: reservation-client:9999
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 15 Sep 2017 04:21:54 GMT

["Josh","Heidi","Cameron","Saritha","Balaji","Soumya","Steve","Kelsey"][dev@localhost ~]$ 



5. Circuit Breaker

API Gateway인 reservation-client가 reservation-service를 잘 호출해서 정상적으로 작동하는 것을 볼 수 있었다. 하지만 이것은 reservation-service가 항상 살아있고 request에 반응하는 것을 전제로 한다. 하지만 실제 프로덕션 환경에서는 좀 더 방어적으로 대응할 필요가 있다 .

우리는 reservation-client가 reservation-service에 접속할 수 없는 상황이 생길 때 fallback을 활용하여 에러를 내지 않고 원래 결과를 대신할 수 있는 방법을 circuit-breaker를 사용하여 해보도록 하겠다.

(1) org.springframework.boot:spring-boot-starter-actuator 와 org.springframework.cloud:spring-cloud-starter-hystrix 를 reservation-client의 Maven Dependency에 추가하도록 하자.

(2) @EnableCircuitBreaker Annotation을 Main Class에 붙이고, 서비스 대 서비스와 같이 잠재적인 불안 요소가 있는 (접속이 제대로 되지 않는 등의) 메소드에 @HystrixCommand Annotation을 붙인다. 그리고 이 메소드의 인자로 빈 콜렉션을 리턴하는 fallback method를 지정하도록 하자.

(3) reservation-service를 중지하고 /reservations/names API를 다시 요청하여 정상적으로 작동하는 지 확인하자.

[dev@localhost ~]$ curl http://localhost:9999/reservations/names -i
HTTP/1.1 200 
X-Application-Context: reservation-client:9999
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 15 Sep 2017 05:06:41 GMT

[]
[dev@localhost ~]$ 

(4) Spring Initializr를 이용하여 hystrix-dashboard라는 새로운 서비스를 생성하도록 하자. 이 서비스에는 Eureka Discovery, Config Client, Hystrix Dashboard를 선택하자.

(5) hystrix-dashboard 서비스에 bootstrap.properties를 생성하고, 이름은 hystrix-dashboard로 지정하자. 그리고 다른 서비스와 같이 config server를 지정하도록 하자.

(6) Main Class에 @EnableHystrixDashboard 를 추가하고, 서비스를 시작시키자.

(7) http://localhost:8010/hystrix.html 를 접속하여 정상적으로 Hystrix Dashboard화면이 로드되는 것을 확인하자.

(8) 이 Hystrix Dashboard에서는 Circute Breaker가 사용된 서비스에 대해서 Heart Beat (살아있는지 확인하는 메시지)를 설정할 수 있는데, reservation-client라는 이름으로, 주소는 http://localhost:9999/hystrix.stream로 monitor stream을 추가한다.




이상으로 reservation service라는 서비스를 Spring Cloud기반으로 실제 프로덕션 기반에서 어떻게 구성할 수 있는지 기본적인 환경을 셋업해 보았다. 이에 조금씩 살을 붙여서 조금더 활용할 수 있는 방법을 알아보도록 하겠다. 


참고 : https://github.com/joshlong/cloud-native-workshop