Micro Service/mSVC개발

[SC05] Spring Cloud Bus 란 ?

Happy@Cloud 2021. 2. 14. 03:14

https://happycloud-lee.tistory.com/212?category=902419

Spring Cloud bus 이해

WHY ?

config가 변경되면 각 마이크로서비스는 최신 값을 갖고 오기 위해 POST로 http[s]://{microservice host}/actuator/refresh를 해줘야 합니다. 물론 이 작업을 자동화할 수도 있겠으나, config server가 각 마이크로서비스의 주소를 모두 관리해야 하니 비효율적입니다.

Spring cloud bus는 동적으로 config 변경을 적용하기 위한 MQ(Message Queue) Handler입니다.

HOW ?

아래 그림처럼 Config server와 마이크로서비스 간에 메시지 Queue(rabbitMQ 또는 kafka)를 통해 설정값을 참조하도록 합니다.

Spring Cloud bus는 아래 3가지 기능을 합니다.

- MQ(Message Queue)에 Publisher(=config server)와 Subscriber(마이크로서비스)를 등록

- config 변경 정보를 MQ에 전송(아래 3. Publish Message)

- 각 마이크로서비스에서 config 동적 반영(5. Reload Config)


동작 순서는 아래와 같습니다.

아래 그림에서 빨강색 box가 Spring Cloud Bus이고, 파란색 box가 Spring Cloud Config Monitor입니다. 

- 1. Update Config Info: Git의 설정값이 갱신됨. git repository에서 파일을 직접 변경하거나, git push함

- 2. Notify Change Via Git Web Hook: webhook이 POST로 http[s]://{config server host}/actuator/bus-refresh를 실행함.
      사전에 git에서 기본으로 제공하는 webhook에 bus-refresh를 수행하는 프로그램을 등록해야 함
      Spring Cloud Config Monitor를 이용할 때는 http[s]://{config server host}/monitor로 등록함(Spring Cloud Config Monitor참조)

- 3. Publish Message: config server는 Git에서 변경된 내용을 읽어 MQ에 Publish함

- 4. Notify: MQ는 각 마이크로서비스에 설정값 갱신이 필요함을 통지

- 5. Reload Config: 각 마이크로서비스는 config server를 통해 갱신된 설정값을 읽어 반영함

Spring Cloud bus 적용

아래와 같이 작업하면 됩니다.

1. bus-amqp라이브러리 추가: Config Server, Application에 모두 적용

- Dependency 'spring-cloud-starter-bus-amqp'추가 in pom.xml

- spring.rabbitmq 설정 in application.yaml 또는 bootstrap.yaml

spring: 
  rabbitmq:
    host:${mq_host:169.56.84.41}
    port: ${mq_port:31060}
    username: ${mq_id:guest}
    password: ${mq_pw:guest}

 

2. end point 'bus-refresh' 설정: Config Server에만 적용

actuator의 end point로 bus-refresh 추가. 위 3번 Publish Message를 위해 필요합니다.

management.endpoints.web.exposure.include: bus-refresh

 

 

3. end point 'refresh' 설정: Application에만 적용

actuator의 end point로 refresh 추가. 5. Reload config를 위해 필요합니다.

management.endpoints.web.exposure.include: refresh

 

webhook 개발

configmng git repository 변경 시 webhook으로 http://{config server host}/actuator/bus-refresh를 POST로 수행하도록 설정합니다.

github에서 config server로 직접 POST하면 에러가 발생하기 때문에, 저는 이 처리를 담당할 webhook이라는 어플리케이션을 제작하여 사용할것입니다.

이 어플리케이션에는 config server에 변경 통지를 하는 API뿐 아니라, Spring cloud bus를 테스트 하기 위한 API도 추가하도록 하겠습니다.

앞으로 개발하는 어플리케이션은 config server와 연동하고, cloud bus를 이용하여 동적으로 config변경이 적용되도록 합니다.

그런데, 왜 configmng의 webhook 주소로 http://{config server host}/actuator/bus-refresh를 지정하면 될텐데, 굳이 별도의 프로그램을 개발하는것일까요 ?

github에서 직접 http://{config server host}/actuator/bus-refresh를 호출하면 전송되는 header정보를 config서버가 인식하지 못하여 처리가 안됩니다.

그래서 github에서는 별도로 개발한 http://{webhook host}/bus-refresh를 호출하고, webhook 앱이 http://{config server host}/actuator/bus-refresh을 호출하도록 했습니다.

또 다른 방법은 Spring cloud config monitor를 이용하는 방법입니다.

Spring cloud에서는 git webhook을 지원하기 위해 Config monitor 라이브러리를 제공합니다.

Config monitor를 사용하는 방법은 [SC06] Spring cloud Config monitor를 참조하십시오.

 

1. Application 개발 및 git push

1) 프로젝트 생성

Dependency는 Actuator, Config Client, Eureka Discovery Client, OpenFeign, Spring Web을 선택합니다.

OpenFeign은 Java Interface만 작성하면 자동으로 HttpClient를 만들어주는 라이브러리입니다.

Spring Web은 web으로 API통신(Get, Post 등)을 하기 위한 라이브러리입니다.

2) Main class 수정

Eureka server와 통신하기 위해 @EnableDiscoveryClient를 추가하고, OpenFeign을 이용하여 HttpClient를 자동으로 만들기 위해 @EnableFeignClients를 추가합니다.

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class WebhookApplication { 
	public static void main(String[] args) { 
    	SpringApplication.run(WebhookApplication.class, args); 
    } 
}

3) Spring cloud config 설정

src/main/resources 밑에 bootstrap.yaml파일을 추가 합니다.

bootstrap.yaml

server:
  port: ${service_port:8000}
spring:
  application:
    name: webhook
  profiles:
    active: ${profile:local}
    include: common 
  cloud: 
    config:
      uri: ${config_servers:http://localhost:9001}
      searchPaths: ${spring.application.name}
      default-label: main

- spring.profiles 이해

spring.profiles.active에는 application의 실행환경 profile값을 지정합니다. 보통 local, dev, prod중 하나를 지정합니다. 물론 이 profile값은 바꿀 수 있습니다. 

spring.profiles.include에는 항상 적용될 실행환경 profile값을 지정합니다. 쉼표로 구분하여 여러개를 등록할 수 있습니다.  

spring.profiles의 더 이해하기 위해 아래 예제를 참고 하십시오.

- spring.application.name: webhook

- spring.profiles.active: dev

- spring.profiles.include: common

- configmng git repository

   - root directory에 application.properties, application.yaml파일과 webhook디렉토리 존재

   - webhook디렉토리에 webhook-common.yaml, webhook-dev.yaml, webhook-prod.yaml파일 존재

 

위와 같은 경우, configmng에서 참조되는 config파일은 아래와 같습니다. 

webhook-dev.yaml, webhook-common.yaml, application.properties, application.yaml 


config server의 REST API를 통해 확인할 수도 있습니다. 

http://<config server ingress host>/webhook/dev,common

- spring.cloud.config 이해

spring:
  ...
  cloud: 
    config:
      uri: ${config_servers:http://localhost:9001}
      searchPaths: ${spring.application.name}
      default-label: main

config server의 주소, config를 읽을 디렉토리 위치(searchPaths), git branch명(default-label)을 지정합니다.

이렇게 설정하면 config server를 통해 configmng의 설정 파일들을 참조할 수 있게 됩니다.

 

- configmng/application.yaml 이해

configmng의 application.yaml에는 아래와 같이 eureka에 대한 설정, rabbitmq정보, actuator end point설정이 되어 있습니다.

application.properties에 yaml에서 사용하는 변수값이 정의되어 있습니다

eureka:
  client: 
    register-with-eureka: true 
    fetch-registry: true 
    service-url:
      defaultZone: ${eureka_servers:http://localhost:8761/eureka}
... 
spring:
  rabbitmq:
    host: ${mq_host:169.56.84.41}
    port: ${mq_port:31060}
    username: ${mq_id:guest}
    password: ${mq_pw:guest}

management:
  security: 
    enabled: false
  endpoints:
    web:
      exposure:
        include: '*'

 

application-local.yaml

configmng의 webhook/webhook-local.yaml파일을 만들어도 되고, 아래와 같이 project resources밑에 application-local.yaml파일을 만들어도 됩니다.  config파일을 먼저 project src의 resources밑에서 먼저 찾기 때문입니다.

greeting: Hello from LOCAL

 

4) dependency, finalName 설정

cloud bus 사용을 위해 bus-amqp를 추가합니다.

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>           
        </dependency>   

swagger3.0 사용을 위해 springfox-boot-starter를 추가합니다.

		<!-- swagger 3 -->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-boot-starter</artifactId>
			<version>3.0.0</version>
		</dependency>

finalName을 application name과 동일하게 설정합니다.

	<build>
		<finalName>webhook</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

 

5) Application 개발

SwaggerConfig.java

Swagger설정: http://<app host>/swagger-ui/ 를 통해 API page 접근 가능하게 해줌

@Configuration
public class SwaggerConfig {
	@Bean
	public Docket api() {
		ApiInfo apiinfo = new ApiInfoBuilder()
				.title("Webhook")
				.description("Notify changes of configmng")
				.version("1.0")
				.build();
		
		Docket docket = new Docket(DocumentationType.SWAGGER_2)
				.apiInfo(apiinfo)
				.select()
				.apis(RequestHandlerSelectors.basePackage("com.springcloud"))
				.paths(PathSelectors.any())
				.build();
		
		return docket;
	}
}

 

IWebhookController.java

Openfeign을 이용하여 HttpClient를 자동 생성하기 위한 interface를 작성합니다.

아래 예에서는 /bus-refresh라는 API가 만들어지고, 호출 시 POST로 http://<config_server_host>/actuator/bus-refresh를 실행 합니다.

@RestController
@FeignClient( name="webhook", url="${config_server_refresh_uri:http://localhost:9001/actuator}" )
public interface IWebhookController {
    @PostMapping("/bus-refresh")
    @ApiOperation(value="Notify config changes to config server")
    public String notifyConfigChanges();
}

http://<webhook host>/bus-refresh를 POST로 호출하면,

http://<config server host>/actuator/bus-refresh를 POST로 호출하게 됩니다.

config_server_refresh_uri는 7) 환경변수 파일 생성에서 정의합니다.

 

Controller.java

config의 동적 적용을 테스트하기 위해 'echo' API를 만듭니다.

@RestController
@RefreshScope
public class Controller {
    @Value("${greeting:Hi}")
    private String greeting;
   
    @GetMapping("/greeting/{message}")
    @ApiOperation(value="Test Spring cloud bus")
    public String echo(@PathVariable String message) {
        return greeting + " => " + message;
    } 
}

 

6) Local 테스트

config server, eureka server를 구동하고, webhook을 실행합니다.

webhook앱이 eureka 에 등록되었는지 http://locahost:8761/ 또는 http://localhost:8762로 확인합니다.

swagger page를 접근합니다.

greeting API가 정상적으로 수행되는지 확인합니다.

Local에서는 여기까지만 테스트하고, config 동적 적용은 k8s에 배포후에 합니다.

 

7) 환경변수 파일 생성

cicd디렉토리를 만들고, cm-common.env, cm-dev.env, cm-prod.env파일을 만듭니다.

cm-common.env의 내용은 없고, cm-dev.env와 cm-prod.env의 내용은 아래와 같습니다.

config server의 k8s service name을 지정하면 됩니다.

config_server_refresh_uri=http://config:9001/actuator

 

8) git repository 생성 및 Push

github에 sc-hklee/webhook으로 git repository를 만듭니다.

그리고 push합니다.

echo "# webhook" > README.md
git init
git checkout -b main
git remote add origin https://happycloudpak@github.com/sc-hklee/webhook.git
git add . && git commit -m "first commit" && git push -u origin main

TIP) Spring Tool Suite, Elipse에서 git commit & push 하기

 

 

2. CI/CD

1) configmng에 webhook 설정 파일 추가

- configmng를 local에 clone

git clone https://github.com/sc-hklee/configmng.git

- webhook디렉토리 만들고, 아래 9개 파일 생성

- 각 파일의 내용 입력

cicd변수 설정

cicd-common, dev, prod순서

# Container Image info
image_registry=harbor.io
image_project=sc-hklee
image_repository=webhook
image_tag=0.0.1

# resources
req_cpu=128m
req_mem=128Mi
limit_cpu=1024m
limit_mem=1024Mi
# namespace, sa
namespace=hklee
serviceaccount=sa-hklee

# Service info
service_target_port=8000
service_port=8000
service_host=webhook.169.56.84.41.nip.io
service_replicas=1

image_pull_policy=Always

# namespace, sa
namespace=hklee
serviceaccount=sa-hklee

# Service info
service_target_port=8000
service_port=8000
service_host=webhook.169.56.84.41.nip.io
service_replicas=2

image_pull_policy=Always

application 환경 설정

dev, prod순서. webhook-common.yaml은 내용 없음

greeting: Hello from DEV 
greeting: Hello from PROD

secret 변수 설정

dev, prod순서. webhook-secret-common.properties는 내용 없음

mq_pw=guest

mq_pw=guest

 

- server에 푸시

git add . && git commit -m "add webhook config" && git push -u origin main

 

2) git clone

NFS서버에서 hklee 유저로 webhook git repository를 clone합니다.

[hklee@nfs ~]$ cd ~/work
[hklee@nfs work]$ git clone https://github.com/sc-hklee/webhook.git
Cloning into 'webhook'...
remote: Enumerating objects: 30, done.
remote: Counting objects: 100% (30/30), done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 30 (delta 0), reused 30 (delta 0), pack-reused 0
Unpacking objects: 100% (30/30), done.
[hklee@nfs work]$
[hklee@nfs work]$ cd webhook/

 

3) run cicd

run-cicd의 마지막 parameter로 config server의 ingress 명을 입력하십시오.

[hklee@nfs work]$ cd ~/work/webhook/
[hklee@nfs webhook]$
[hklee@nfs webhook]$ run-cicd
# container image registry 로그인 username: hklee
# container image registry 로그인 password: passw0rd
# kubernetes context name(현재 context는 .): .
# 배포대상 프로파일(dev/prod): dev
# base directory(현재 directory는 .): .
# 개발언어(java/react): java
# config server ingress name: config

또는, 아래와 같이 파라미터를 지정하여 실행시켜도 됩니다.

run-cicd hklee passw0rd . dev . java config

 

4) 배포 확인

Pod가 실행될때까지 기다렸다가, ingress 주소로 swagger page를 접근합니다.

http://<ingress host>/swagger-ui/

 

아래와 같이 config 동적 적용을 테스트한다.

- API /greeting 수행

- configmng의 webhook/webhook-dev.yaml 파일에서 greeting값을 변경하고 push

greeting: Hi from DEV

다시 /greeting API를 실행해도 메시지 변경 안되는거 확인

- API /actuator/bus-refresh 수행

다시 /greeting API를 실행하여 변경된 메시지가 리턴되는것 확인

 

5) webhook 등록 및 확인

configmng의 webhook으로 /bus-refresh를 등록하여, 동적으로 config가 적용되는것을 테스트합니다.

Content type은 application/json으로 변경하십시오.

 

- configmng의 webhook/webhook-dev.yaml을 열어 greeting값을 변경합니다. 그리고, git push합니다.

- swagger page에서 /greeting API를 다시 실행해 봅니다.

변경된 greeting값이 표시되면 성공입니다.