전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]

티스토리 뷰

Config server 이해

1) WHY ?

첫째, 마이크로서비스의 어떠한 설정(환경변수값, Spring cloud 설정 등)이 변경되었을때 서버 재시작 없이 동적으로 적용하기 위해서입니다.

둘째, 마이크로서비스가 배포될때 제반 설정값들을 배포 대상 환경(개발계, 검증계, 운영계 등)에 맞게 적용하기 위함입니다.

셋째, 마이크로서비스를 Stateless하게 개발하기 위해서입니다. Stateless하게 만들어야 스케일링(마이크로서비스 인스턴스 서버 - 즉, 컨테이너의 증감)과 부담없는 재시작이 가능하기 때문입니다.

두번째와 세번째는 MSA 12Factors에서도 요구되는 특성입니다.

MSA특성과 MSA 12 Factors 이해를 참조하십시오.

 

2) HOW ?

마이크로서비스에서 설정 파일들을 분리하여, git 또는 File server에서 관리합니다.

Spring Cloud config server는 각 마이크로서비스에게 git 또는 File server의 설정값들을 제공합니다.

그림출처: https://meteorkor.tistory.com/19

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

그래서 나온 것이 Spring cloud bus입니다.

Spring Cloud bus는 1) MQ(Message Queue)에 Publisher(=config server)와 Subscriber(마이크로서비스)를 등록하고, 2) 갱신된 설정 값을 받으면 각 마이크로서비스에 통지해 줍니다.

통지를 받은 각 마이크로서비스는 config server를 통해 갱신된 값을 받아 config를 업데이트하게 됩니다.

Spring cloud bus에 대해서는 '05. Cloud bus'에서 좀 더 자세하게 다룹니다.

 

Config server 개발

그럼 Spring cloud config server와 cloud bus를 이용하여 Config server를 만들어 보도록 하겠습니다.

실습은 아래와 같은 순서입니다.

1. config 관리용 git repository 만들기

2. rabbitmq 배포하기

3. spring config server 어플리케이션 개발 및 git repo에 푸시

4. CI/CD


1. config 관리용 git repository 만들기

config git repository의 구조는 아래와 같이 표준화합니다.

즉, 모든 마이크로서비스에서 공통으로 사용하는 환경변수는 application.properties에서 관리하고, 공통으로 사용하는 Spring cloud 설정은 application.yaml[yml]에서 관리합니다.

그리고, 각 마이크로서비스의 CI/CD관련, Spring cloud, Secret설정은 각 서브 디렉토리에서 관리합니다.

※ application.properties나 application.yaml을 application-dev.properties나 application-prod.properties처럼 대상 서버 환경에 따라 관리하는 건 지원하지 않습니다.

※ 환경변수(kubernetes ConfigMap으로 생성됨)관리는 각 어플리케이션 소스에서 개발자가 합니다.

아래 cicd디렉토리 밑에 있는 3개의 파일을 이용합니다.

cicd 디렉토리와 그 하위 파일들의 이름은 CI/CD shell에서 지정된 이름이기 때문에 바꾸시면 안됩니다.

 

중요: Config  적용 우선 순위

Config파일은 로컬 Project의 resources디렉토리 하위에 만들 수도 있고 Config 저장소인 git repository에도 만들 수 있습니다. 

만약 동일한 이름의 config가 중복해서 있다면 우선순위는 어떻게 될까요 ?

Config를 읽는 우선순위는 아래와 같습니다. 

로컬 bootstrap -> 로컬 application -> Config 저장소 application

-> 로컬 bootstrap-{profile} -> 로컬 application-{profile}

-> Config 저장소 {application name}/{application name}-{profile} 

뒤에 읽히는 파일에 같은 이름의 config가 있다면 덮어 쓰게 됩니다.

 

예를 들어 'greeting'이라는 환경변수가 로컬의 appliation.yaml, application-local.yaml에  있고,

Config저장소의 application.yaml, application.properties, greeting-local.properties에도 있다고 가정해 봅시다. 

로컬 application.yaml -> Config 저장소 application.yaml -> Config 저장소 application.properties

-> 로컬 application-local.yaml -> Config 저장소 greeting/greeting-local.properties의 순서로 읽습니다.  

따라서 최종적으로 읽히는 Config저장소의 greeting/greeting-local.properties파일에 있는 'greeting'환경변수값이 적용 됩니다. 

 

 

1.1 Server git Repository 생성

새로 만든 organization하위에 'configmng'라는 이름으로 만듭니다.

 

1.2. Local git repository생성 및 푸시

1) 작업하는 PC 또는 VM에 configmng디렉토리를 만들고 이동

$ mkdir -p ~/Documents/springboot/sc-hklee/configmng

$ cd ~/Documents/springboot/sc-hklee/configmng

2) server git repository로 푸시

echo "# configmng" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main

git remote add origin https://happycloudpak@github.com/sc-hklee/configmng.git
git push -u origin main

※ sever git 주소는 https://{github username}@github.com/<organization>/<repository name>.git

TIP) STS(Spring Tool Suite)에 configmng를 추가하여 작업

STS에 project로 configmng를 추가하여 작업하는게 편합니다.

Spring Tool Suite, Eclipse에서 git commit & push 하기를 참조하여 configmng를 작업영역에 추가하고 Local git repository도 추가하십시오.

 

1.3. 공통 config file 생성 및 푸시

1) application.properties 파일 작성

개발과 운영 관련 환경변수들을 정의합니다.

CI/CD할때 'deploy'라는 shell에서 현재 배포 대상 프로파일이 'dev'이면 '_dev'가 붙어 있는 변수들만 환경변수로 생성하고, 'prod'이면 '_prod'가 붙어 있는 변수들만 환경변수로 생성합니다.

아래 변수의 의미는 앞으로 각 Spring cloud component를 만들면서 설명할것입니다.

수정될 내용은 없으니, Local의 'configmng'디렉토리 밑에 application.properties파일을 만듭니다.

# DEV SERVER
profile_dev=dev

eureka_servers_dev=http://eureka-0.eureka:8761/eureka
config_servers_dev=http://config:9001
mq_host_dev=rabbitmq
mq_port_dev=5672
mq_id_dev=guest

# PROD SERVER
profile_prod=prod

eureka_servers_prod=http://eureka-0.eureka:8761/eureka,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/
config_servers_prod=http://config:9001
mq_host_prod=rabbitmq
mq_port_prod=5672
mq_id_prod=guest

 

2) application.yaml 파일 작성

공통 Spring cloud 설정을 합니다.

아래 설정의 의미는 앞으로 각 Spring cloud component를 만들면서 설명할것입니다.

수정될 내용은 없으니, Local의 'configmng'디렉토리 밑에 application.yaml파일을 만듭니다.

eureka:
  client: 
    register-with-eureka: true 
    fetch-registry: true 
    service-url:
      defaultZone: ${eureka_servers:http://localhost:8761/eureka}

ribbon:
  eureka:
    enabled: true
  MaxAutoRetries: 3                 #동일 서비스 연결시도 횟수: 최소 1번 이상으로 지정  
  MaxAutoRetriesNextServer: 3       #다음 서비스 연결 시도 횟수: 대상 서비스 수 이상으로 지정    
  OkToRetryOnAllOperations: true    #Retry 사용 여부: Failover하려면 true로 지정 
  ConnectTimeout: 5000
  ReadTimeout: 5000  
  RetryableStatusCodes: 503, 408, 500, 404
  
spring:
  rabbitmq:
    host: ${mq_host:localhost}
    port: ${mq_port:5672}
    username: ${mq_id:guest}
    password: ${mq_pw:guest}

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

 

3) push to git server

git add . && git commit -m "add common config" && git push

2. rabbitmq 배포하기

NFS서버로 접속한 후, 'hklee' user로 전환합니다.

'work'라는 작업 디렉토리를 만들고, 아래 내용으로 deploy-mq.yaml파일을 만듭니다.

[root@nfs ~]# su - hklee
마지막 로그인: 화  1월 12 01:54:53 CST 2021 일시 pts/1
[hklee@nfs ~]$ mkdir -p work
[hklee@nfs ~]$ cd work

deploy-mq.yaml 파일 내용: namespace와 serviceAccountName은 본인것으로 변경하세요.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: rabbitmq
  namespace: hklee
  labels:
    app: rabbitmq
spec:
  serviceName: rabbitmq
  selector:
    matchLabels:
      app: rabbitmq
  replicas: 1
  template:
    metadata:
      name: rabbitmq
      labels:
        app: rabbitmq
    spec:
      serviceAccountName: sa-hklee
      containers:
      - name: rabbitmq
        image: rabbitmq:management
        imagePullPolicy: IfNotPresent
        env:
        - name: RABBITMQ_DEFAULT_USER
          value: guest
        - name: RABBITMQ_DEFAULT_PASS
          value: guest
        ports:
        - name: containerport
          containerPort: 5672
        - name: consoleport
          containerPort: 15672
        resources:
          requests:
            cpu: 128m
            memory: 128Mi
          limits:
            cpu: 1024m
            memory: 1024Mi
---
apiVersion: v1
kind: Service
metadata:
  name: rabbitmq
  namespace: hklee
spec:
  type: NodePort
  selector:
    app: rabbitmq
  ports:
  - name: port1
    port: 5672
    targetPort: 5672
  - name: port2
    port: 15672
    targetPort: 15672

 

rabbitmq를 설치합니다.

[hklee@nfs work]$ kubens -c
hklee
[hklee@nfs work]$ k apply -f deploy-mq.yaml
statefulset.apps/rabbitmq created
service/rabbitmq created

 

pod가 실행될때가지 기다립니다.

[hklee@nfs work]$ k get po
NAME         READY   STATUS    RESTARTS   AGE
rabbitmq-0   1/1     Running   0          35s

 

k8s service를 확인하고, 15672포트에 바인딩된 외부 노출 포트 번호를 확인합니다.

config server를 사용하는 application들은 configmng git repository의 config파일들을 이용하게 됩니다.

configmng의 root에 있는 application.properties와 application.yaml은 모든 application이 참조하게 되는 파일들입니다.

configmng의 application.properties의 mq_host_dev[prod]는 위 k8s service name이고,

mq_port_dev[prod]는 첫번째 내부 PORT번호입니다.

나중에 배포될 config server Pod안에서 rabbitMQ Pod를 접근할 때는 동일한 namespace이므로, rabbitMQ의 service name으로 접근할 수 있습니다.

그래서 appliation.properties에 아래와 같이 정의되어 있습니다.

application.yaml

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

# DEV SERVER
...
mq_host_dev=rabbitmq
mq_port_dev=5672
mq_id_dev=guest

# PROD SERVER
...
mq_host_prod=rabbitmq
mq_port_prod=5672
mq_id_prod=guest

 

웹브라우저에서 http://{master OR worker node ip}:{external port}로 접근합니다. node  IP는 아래와 같이 확인합니다.

 

그리고 로그인합니다. ID/PW는 guest입니다.

 

config git repository의 application.yaml을 변경합니다.

이는 Local에서 수행할때도 rabbitMQ를 이용하기 위해서입니다.

As-Is To-Be
...
spring:
  rabbitmq:
    host: ${mq_host:localhost}
    port: ${mq_port:5672}
    username: ${mq_id:guest}
    password: ${mq_pw:guest}
...
spring:
  rabbitmq:
    host: ${mq_host:169.56.84.41}
    port: ${mq_port:31060}
    username: ${mq_id:guest}
    password: ${mq_pw:guest}

rabbitmq.host는 k8s cluster내 아무 node의 IP던 지정하면 됩니다.

rabbitmq.port는 아래와 같이 rabbitmq 서비스의 5672포트와 바인딩된 외부 노출 포트를 지정하십시오.

변경된 application.yaml을 서버에 push합니다.

git add . && git commit -m "change mq_host, port" && git push

3. spring config server 어플리케이션 개발 및 git repo에 푸시

Local에서 Spring Tool Suite를 실행합니다.

Workspace는 ~/Documents/springboot/sc-hklee로 변경합니다.

앞으로 작성할 Java application은 Java8을 사용하고, Package는 'com.springcloud'로 통일하겠습니다.

Spring Boot 버전은 '2.3.7.RELEASE' 또는 '2.3.8.RELEASE', Spring Cloud 버전은 'Hoxton.SR9'를 사용합니다.

※ Spring Boot '2.4.X', Spring Cloud '2020.0.x'는 zuul, hystrix 등 지원되지 않는 컴포넌트가 있어 사용하지 않습니다.

 

1) 새 프로젝트 시작

File -> New -> Spring starter project

Spring Boot는 2.3.7, Dependency는 'Config Server'만 지정합니다.

 

2) Main Application 수정

Annotation '@EnableConfigServer'을 추가합니다.

@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {

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

}

 

3) Application 설정파일 application.yml 작성

아래와 같이 application.properties파일을 application.yml로 변경합니다.

사실, 그냥 파일명을 변경해도 됩니다.

파일 내용은 아래와 같이 합니다.

spring.cloud.config.server.git.uri, username, password는 본인걸로 변경하셔야 합니다.

rabbitmq.host와 rabbitmq.port는 config git repository의 application.yaml과 동일하게 하면 됩니다. 

IP는 k8s cluster의 아무 node ip를 지정하면되나, port는 rabbitmq가 배포될때마다 바뀌니 확인하고 지정하십시오.  

server:
  port: ${service_port:9001}
spring:
  application: 
    name: config

# Actuator endpoints
management.endpoints.web.exposure.include: health, metrics, bus-refresh
            
---
spring:
  cloud:
    config:
      server:        
        git:
          uri: https://${git_host:github.com}:${git_port:443}/${git_org:sc-hklee}/${git_repo:configmng.git}
          username: ${git_id:happycloudpak}
          password: ${git_pw:passw0rd$}                            
          search-paths: /*                       #base directory to search profiles
          default-label: ${git_branch:main}      #git branch
          
  rabbitmq:
    host: ${mq_host:169.56.84.41}
    port: ${mq_port:31060}
    username: ${mq_id:guest}
    password: ${mq_pw:guest}

yaml 작성 TIP) management.endpoints.web.exposure.include와 같이 각 항목을 새로운 줄에 쓸 필요 없이 한줄에 길게 써도 됩니다.

환경변수 지정) ${service_port:9001}과 같이 환경 변수가 없을때 사용할 default값을 지정할 수 있습니다.

 

4) pom.xml 수정

Spring boot version, Spring Cloud version이 맞는지 체크하고 틀리면 변경합니다.

 

<build>하위에 finalName을 추가하고, spring.application.name과 동일하게 지정합니다.

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

 

Spring cloud bus를 이용하기 위해 dependency에 bus-amqp를 추가하십시오.

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

 

5) CI/CD 설정, 환경변수, Secret변수 설정

Project root 밑에 cicd라는 폴더를 만들고, 설정 파일을 작성합니다.

CI/CD shell에서 container build 및 배포 시 사용하는 파일들입니다.

cicd-common.properties

image_project, namespace, serviceaccount, service_host는 본인걸로 바꾸십시오.

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

# resources
req_cpu=128m
req_mem=128Mi
limit_cpu=1024m
limit_mem=1024Mi

cicd-dev.properties

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

# Service info
service_target_port=9001
service_port=9001
service_host=config.169.56.84.41.nip.io
service_replicas=1

image_pull_policy=Always

cicd-prod.properties

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

# Service info
service_target_port=9001
service_port=9001
service_host=config.169.56.84.41.nip.io
service_replicas=2

image_pull_policy=Always

cm-common.env

git_org, git_id는 본인걸로 바꾸십시오.

# git info
git_host=github.com
git_port=443
git_org=sc-hklee
git_repo=configmng.git
git_branch=main
git_id=happycloudpak

# message queue info
mq_host=rabbitmq
mq_port=5672
mq_id=guest

※ 개발과 운영의 설정이 다르다면, cm-common.env, cm-dev.env, cm-prod.env로 나누어 설정할 수도 있습니다.

secret-common.env

git_pw는 본인걸로 변경하십시오.

git_pw=passw0rd$
mq_pw=guest

※ 개발과 운영의 설정이 다르다면, secret-common.env, secret-dev.env, secret-prod.env로 나누어 설정할 수도 있습니다.

 

6) Local에서 테스트

왼쪽 하단에서 config를 선택하고, 실행 아이콘을 클릭합니다.

웹브라우저에서 http://localhost:9001/a/common이라고 입력합니다. (/a/common은 임의의 값으로 아무값이나 입력해도 됩니다.)

아래와 같이 config git repo에 정의한 파일들의 내용이 나오면 성공입니다.

 

http://localhost:9001/actuator 결과에 bus-refresh가 있어야 합니다.

없다면, pom.xml의 dependency에 spring-clous-starter-bus-amqp가 누락된것입니다.

 

7) git repository 생성 및 push

github.com에 sc-hklee/config.git을 생성합니다.

<HOME>/Documents/springboot/sc-hklee/config로 이동하여 수행합니다.

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

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


 

4. CI/CD

마지막으로 jar와 container image를 만들고 kubernetes상에 배포합니다.

NFS서버에서 hklee유저로 수행하며, 미리 설치한 run-cicd 툴을 이용합니다. 

1) NFS서버 접근하여 hklee 유저로 변경

[root@nfs ~]# su - hklee
마지막 로그인: 화  1월 12 02:45:40 CST 2021 일시 pts/2

 

2) config 소스를 다운로드

[hklee@nfs ~]$ cd work

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

[hklee@nfs work]$ cd config/

 

3) run CI/CD

NFS서버의 ~/work/config디렉토리에서 수행합니다.

username, password는 본인걸로 변경하시고, config server ingress name은 그냥 Enter를 치십시오.

[hklee@nfs config]$ 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:
************** RUN ALL ***************

configserver=>
NOT using config-server

 

pod실행 확인

 

config server의 ingress host를 구하십시오.

[hklee@nfs config]$ k get ing
NAME     CLASS    HOSTS                        ADDRESS        PORTS   AGE
config   <none>   config.169.56.84.41.nip.io   169.56.84.41   80      10m

웹브라우저에서 http://{config server ingress host}/a/common을 입력하여 정상적으로 config정보를 갖고 오는지 테스트 하십시오.

 

TIP) config server REST API

http://{config server host}/{application}/{profile}/{label}

- {application} : spring.application.name 속성값으로 configmng의 디렉토리명과 동일해야 함

- {profile} : configuration profile의 명칭으로, comma(,)로 구분하여 여러개 지정할 수 있습니다.  파일 내용이 없으면 그 파일은 리턴 값에서 제외됩니다.

- {label} : 필수가 아닌 옵션이며, git branch의 이름을 나타냅니다. 생략하면, git repository의 기본 branch입니다.

※ configmng의 config 파일은 {application}-{profile}.properties 또는 {application}-{profile}.yaml[또는 yml]형식이어야 합니다.   

예를 들어 설명해 보겠습니다. 아래와 같이 configmng에 config file들이 등록되어 있다고 가정해 봅시다.

 

예1) /webhook/dev,common

webhook-common.yaml은 파일 내용이 없어 제외됨.

예2) /webhook/cicd-common, cicd-dev,dev,secret-common,secret-dev

 

TIP) 소스 수정 후 재배포 하기

- Local에서 git repo에 push

~/Documents/springboot/sc-hklee/config main*
❯ git add . && git commit -m "delete exposure all" && git push

- NFS서버에서 git pull

[hklee@nfs config]$ git pull
remote: Enumerating objects: 13, done.
remote: Counting objects: 100% (13/13), done.

- CI/CD 수행: 위와 같이 run-cicd실행 후 각 값을 입력해도 되고, 아래 처럼 각 값을 파라미터로 넘겨서 실행해도 됨

[hklee@nfs config]$ run-cicd hklee passw0rd . dev . java
************** RUN ALL ***************

configserver=>
NOT using config-server
...
CI/CD를 위해 임시 생성한 deploy디렉토리를 삭제하시겠습니까(y/n)? y
************** END ALL ***************

맨 마지막에 deploy디렉토리는 debug를 하시려면 유지하십시오.

댓글

전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]