Series/스프링 마이크로서비스 코딩 공작소

스프링 마이크로서비스 코딩 공작소 (2)

Hyunec 2022. 10. 6. 02:38
 

GitHub - Hyune-s-lab/optima-growth: 스프링 마이크로서비스 코딩 공작소

스프링 마이크로서비스 코딩 공작소. Contribute to Hyune-s-lab/optima-growth development by creating an account on GitHub.

github.com

Phase 1 
3. 스프링 부트로 마이크로서비스 구축하기
4. 도커
https://hyune-c.tistory.com/53
Phase 2
5. 스프링 클라우드 컨피그 서버로 구성 관리
6. 서비스 디스커버리
이 글입니다.

 


 

eureka-server 구현

유레카는 클라이언트의 생존 여부를 체크하여 API 게이트웨이의 라우팅을 돕습니다.

eureka-server

// build.gradle.kts
dependencies {
    implementation("org.springframework.cloud:spring-cloud-starter-netflix-eureka-server")
}


// EurekaServerApplication.kt
@EnableEurekaServer
@SpringBootApplication
class EurekaServerApplication

fun main(args: Array<String>) {
    runApplication<EurekaServerApplication>(*args)
}

// application.yml
server:
  port: 8761

eureka:
  instance:
    appname: eureka-server
  client:
    register-with-eureka: false
    fetch-registry: false

licensing-service

// build.gradle.kts
dependencies {
    implementation("org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:3.1.4")
}


// LicensingServiceApplication.kt
@EnableEurekaClient
@SpringBootApplication
class LicensingServiceApplication

fun main(args: Array<String>) {
    runApplication<LicensingServiceApplication>(*args)
}


// application.yml
server:
  port: 8080

eureka:
  instance:
    appname: licensing-service
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka/

http://localhost:8761/

유레카는 클라이언트를 알지 못합니다.
클라이언트가 유레카의 url 을 알고 정보를 보내줍니다. (application.yml 의 defaultZone)
유레카는 클라이언트 application 의 생존 여부를 알 수 있습니다. (LICENSING-SERVICE)

http://localhost:8761/eureka/apps

유레카에서 관리되는 application 을 볼 수 있습니다. json 형태도 가능합니다.
instanceId 의 ip 를 보면 172 대역대로 사설망임을 확인할 수 있습니다.

API 호출

ip 로 api 요청 시 성공합니다!

 

docker setting

docker 는 시행 착오가 조금 있었습니다. 코드 소개 후 아래에서 설명 합니다.

Dockerfile

# docker/eureka-server/Dockerfile
FROM openjdk:17-jdk-slim-buster
ARG JAR_FILE=/eureka-server/build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]


# docker/eureka-server/licensing-service
FROM openjdk:17-jdk-slim-buster
ARG JAR_FILE=/licensing-service/build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

기존 root 경로에 있던 Dockerfile 을 service 에 맞는 폴더로 분리했습니다.
docker setting 후 이어질 환경 설정 분리에서 좀 더 활용해봅니다.

docker-compose.yml

version: "3"
services:
  eureka-server:
    image: eureka-server
    ports:
      - "8761:8761"
  licensing-service:
    image: licensing-service
    ports:
      - "8080:8080"
    links:
      - eureka-server

run.sh

# eureka-server
cd eureka-server
./gradlew bootjar
cd ..
docker build -f docker/eureka-server/Dockerfile -t eureka-server .

# licensing-service
cd licensing-service
./gradlew bootjar
cd ..
docker build -f docker/licensing-service/Dockerfile -t licensing-service .

# 백그라운드로 실행
nohup docker-compose up 1>/dev/null 2>&1 &

기존에는 서비스가 1개였기에 바로 실행했습니다.
하지만 이제는 서비스가 2개인만큼 백그라운드로 실행하게 하고 로그를 남기지 않게 했습니다.

./run.sh

docker dashboard 에서 8761, 8080 포트로 정상실행 된 것을 볼 수 있습니다.

(개별 서비스로 실행했을 때와 달리 docker 로 실행 시 30초 정도의 지연이 걸릴 수 있습니다.)
정상적으로 클라이언트를 찾은 것 처럼 보이지만 IP 가 보였던 아까와는 달리 해시 값이 보입니다?
API 호출도 되지 않습니다???

 

시행 착오 1 - IP 가 왜 안 나올까?

docker dashboard 를 보면 licensing-service-1 의 컨테이너 ID 와 유레카의 해시 값이 같음을 알 수 있습니다.
하지만 IP 의 형태가 아니고 API 호출이 정상적으로 되지 않습니다.

처음에는 설정의 문제로 생각해서 eureka.instance.preferIpAddress=true 도 설정했지만 결과는 같았습니다.

그러다 책을 다시 보니 흐름상 클라이언트의 요청은 API 게이트웨이를 통해 서비스로 전달되고 있었습니다.
그리고 8장에서 서비스 라우팅에 대해 나오는 것을 볼 때 굳이 지금 더 찾아봐야 될 이유가 없을 것 같아 보류했습니다.

 

시행 착오 2 - 도커 컨테이너에서 localhost 를 쓰면 어떻게 될까?

기존 설정은 그대로 둔 채 docker 세팅만 완료한 후 기동 했을 때, 기동은 되었지만 유레카에서 보이지 않았습니다?
이상해서 로그를 보니 유레카의 url 을 찾지 못하고 있었습니다!

관련 자료를 찾고 다시 생각해보니 당연한 것이었습니다.
docker 는 컨테이너 단위로 동작함으로 port 만으로는 접근이 불가능합니다!

찾아보니 host 의 ip 에 접근할 수 있는 방법이 있어 설정을 바꾸니 정상 접근되었습니다.

# licensing-service 
# application.yml
eureka:
  instance:
    appname: licensing-service
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      # defaultZone: http://localhost:8761/eureka/ # localhost 기동시
      defaultZone: http://host.docker.internal:8761/eureka/ # docker 기동시

 

환경 변수 관리 by Dockerfile

책에서는 Spring Cloud Config 의 장점을 말합니다.
하지만 저는 강한 필요성을 느끼지 못했기에 Dockerfile 로 접근하였습니다.

서비스와 config 를 분리할 수 있다.

# application.yml
server:
  port: ${SERVER_PORT}
  

# Dockerfile
FROM openjdk:17-jdk-slim-buster
ENV SERVER_PORT=8080 # 환경 변수 주입
ARG JAR_FILE=/licensing-service/build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

제가 가장 좋아하는 방식으로 Dockerfile 을 활용하여 환경 변수를 관리합니다.
dev, stg, prod 별로 Dockerfile 을 관리할 수도 있고, 작은 프로젝트에서는 서비스 안에 둘 수도 있습니다.

평소에는 설정 정보만 모아두는 git repository 를 운영합니다.
그리고 배포 스크립트 작성 시 (git action) 서비스에 해당하는 Dockerfile 을 가져와 실행할 수 있습니다.

개인적으로 사이드 프로젝트할 때는 배포에 elastic beanstalk 를 활용합니다. 
elastic beanstalk 을 활용하면 AWS 에서 안전하게 환경 변수를 관리할 수 있어 참 편했습니다.

사용해보지는 않았지만 AWS Systems Manager, Secrets Manager 역시 관련된 솔루션으로 알고 있습니다.

서비스 인터페이스로의 접근 방식을 추상화할 수 있다.

# application.yml
spring:
  datasource:
    url: ${JDBC_URL:jdbc:h2:mem:testdb}
    driverClassName: org.h2.Driver
    username: ${JDBC_USERNAME:sa}
    password: ${JDBC_PASSWORD:}

이것 역시 Dockerfile 을 통해 관리할 수 있습니다.
기본 값을 활용하면 local 기동시 편하고 안전해 유용하게 사용하고 있습니다.

 

config 를 중앙 관리할 수 있다.

별도의 git repository 에 Dockerfile 을 관리하는 방법으로 대체할 수 있습니다.
이력 관리도 돼서 유용합니다.

 

config 를 변경 감지할 수 있다.

이것은 불가능합니다.

하지만 개인적인 경험으로 볼 때 config 가 바뀌면 코드의 변경은 필연적이었습니다. 
(네트워크 설정이 바뀔 때는 코드의 변경이 없어도 되지만, 그런 경우는 AWS 의 영역이었습니다.)
그렇기 때문에 변경 감지가 저에게는 그다지 유용한 기능으로 와닿지 않았습니다.

 

마치며

이번 Phase 를 진행하면서 전 회사 생각이 많이 났습니다.

전 회사에서는 docker 를 적극적으로 쓰긴 했지만, 대부분 팀장님이 세팅한 것을 받아쓰기만 했었습니다.

하지만 이번에 docker 를 세팅해보면서 이렇게 좋고 편한 걸 왜 그동안 안쓴거지? 라는 생각이 많이 들었습니다.
이미 현대의 배포는 모두 수평 확장을 기반하고 있고 컨테이너 기반으로 움직이기에 유용할 것 같습니다.
좀 더 잘 쓸 수 있도록 공부해야겠습니다!

config 에 대해서도 그렇습니다.

Dockerfile 로 관리되긴 했지만 서비스와 같은 repository 에 있었기에 보안 지적을 받았기에, s3 로 이관하고 배포 스크립트에서 가져와 활용하는 것을 지원했었습니다.
그때도 말한 내용이지만 s3 로 이관되면 이력 관리도 힘들어지고, 어차피 private repository 가 털린 상황이면 s3 도 위험하긴 마찬가지다. 라고 생각합니다. 별도의 git repository 로 이관하는 것 까지도 말했지만.. 결국은 s3 로 결정된 슬픈 기억이 떠올랐습니다. 보안은 참 어렵네요.

spring cloud config 가 좋은 기술인 건 분명하지만, 아직 저에게는 와닿지 않고 있습니다.
복잡도가 높아져 꼭 도입해야만 하는 시점이 오면 좋겠다는 생각이 들었습니다.