클라우드 환경에서 개발하다보면 개발 서버의 ip가 자주 바뀌곤한다. 특히 MSA 환경에서 서버 ip 가 바뀔때마다 ip 관련 환경설정을 바꿔야 한다면 굉장히 귀찮은 일이 될 수 있다. 이를 보완하기 위해 현재 쿠버네티스 같은 MSA 환경을 위해 나온 기술들은 대부분 service discovery 기능을 지원한다.
우리 회사에서는 아직 운영중이지 않은 프로토타입의 서비스를 대상으로 Spring Cloud 기술을 사용해 적용해보기로 했다.
이번 포스팅은 AWS + Spring Cloud 환경을 구축하면서 맞이했던 문제 중 하나인 AWS 의 IP 문제를 다뤄보려고한다.
Eureka는 기존 호출하는 쪽에서 서버 ip 를 가지고 있는게아닌, 서비스 스스로 자신의 ip를 등록해 다른 서버들이 서비스 ip가 아닌 서비스 이름만으로 서비스를 찾을 수 있게 만들어준다.
DNS 서버를 생각해보면 이해하기가 쉽다. 서버에 내 ip와 이어지는 이름(도메인)을 등록해 두고, 다른 서비스가 이름(도메인)만 입력해도 찾을 수 있게 해주는 것이다. 다른 서비스들은 Eureka(DNS)서버에서 이름(도메인)으로 ip를 찾아 해당하는 리소스를 찾아간다. 같은 도메인에 여러 ip를 매칭할 수 있듯, 유레카도 마찬가지로 해당 방식으로 로드밸런싱까지 지원해준다.
서버는 본인의 ip를 쉽게 획득할 수 있다. 로컬 서버나 고정 ip환경의 온프레미스 서버에서는 이런 문제가 전혀없다. 하지만 AWS는 외부 ip 와 내부 ip라는 개념이 존재한다. 외부에서 접근할 수 있는 ip와 내가 실제 서버 콘솔에 접근해서 ifconfig를 찍었을 때 ip가 다르게 나타난다.
외부 서비스에서는 외부 ip로 해당 서비스에 접근해야 하는데, 스프링 Eureka에서 등록되는 ip는 내부 ip가 등록된다.
외부에서 내부 ip로 접근하려고 하면 당연히 접근할 수가 없다. 따라서 내부에서 외부 ip를 알아내는 기능을 추가로 구현해야한다.
https://cloud.spring.io/spring-cloud-netflix/reference/html/#using-eureka-on-aws
Spring Cloud Netflix
This project provides Netflix OSS integrations for Spring Boot apps through autoconfiguration and binding to the Spring Environment and other Spring programming model idioms. With a few simple annotations you can quickly enable and configure the common pat
cloud.spring.io
다행히도 스프링 진영에서는 이와같은 문제를 해결하기 위해 미리 추상화된 기능을 만들어뒀다.
해당 코드는 다음과 같다
1. EurekaInstanceConfigBean은 Erueka 설정을 동적으로 변경하거나 할당할때 쓰는 빈이다
2. InetUtils는 각종 hostname, ip 등 각종 서버 정보가 들어있다.
3. AmazonInfo는 Aws의 외부서버 ip 및 여러 메타정보들을 가져오는 역할을 한다.
4. 해당 정보를 바탕으로 ConfigBean을 다시 재설정해 서버를 올린다
5. 과연 해결이 되었을까?
=> 나의 경우는 아니었다.
저기요, 공식문서만 따라하면 되는거 아닌가요? ㅠ
eureka는 몇번이나 다시 올려도 aws의 외부 ip를 찾지 못했다. 대부분의 자료들이 외부 ip를 하드 코딩하는걸로 해결했는데 나는 그러고 싶지 않았다.
어떻게든 해결해보자 싶어 AmazonInfo를 살펴보기로 했다.
AmazonInfo 내부를 들어가보면 다음과 같다
이 클래스가 하는 역할은 단순하다. http://169.254.169.254/{awsVersion}/meta-data/{메타데이터 path}/ ..이하 생략 으로 get요청을 날려서 응답을 받아온다.
http://169.254.169.254/ 는 AWS에서 제공하는 메타데이터를 가져오는 url이다
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
해당 내용은 AWS 공식문서에서 확인할 수 있다.
서버 콘솔에서 저걸 그대로 복사 붙여넣기하면 외부 ip를 정상적으로 받아오는 걸 확인할 수 있었다.
여기서 AmazonInfo 에는 없는 부분을 찾을 수 있는데 바로 Put ~ /token 부분이다. AWS는 2019년부터 보안 강화정책으로 메타데이터를 가져오려면 토큰을 발급해 헤더에 넣어서 요청해야 한다. 사내 aws는 2023버전이라 token이 없어 메타데이터 get 요청에 실패했던 것이다.
그래서 직접 업그레이드된 버전에 맞춰 해당 클래스를 구현해보기로 했다.
@Component
class AwsMetadataTemplate {
companion object {
const val META_URL = "http://169.254.169.254"
const val TOKEN_TTL_HEADER = "X-aws-ec2-metadata-token-ttl-seconds"
const val TOKEN_HEADER = "X-aws-ec2-metadata-token"
const val TOKEN_TTL = "3600" //60분
const val VERSION = "latest"
}
private val log: Logger = LoggerFactory.getLogger(javaClass)
private val restTemplate = RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(2))
.setReadTimeout(Duration.ofSeconds(2))
.build()
fun createToken(): String? {
val headers = HttpHeaders()
headers.add(TOKEN_TTL_HEADER, TOKEN_TTL)
val request = HttpEntity(null, headers)
return try {
restTemplate.exchange(
"$META_URL/$VERSION/api/token",
HttpMethod.PUT,
request,
String::class.java
)
.body
} catch (ex: Exception) {
log.warn("aws 토큰 정보를 생성하는데 실패했습니다.")
null
}
}
fun getPublicIp(token: String): String? {
val headers = HttpHeaders()
headers.add(TOKEN_HEADER, token)
val request = HttpEntity(null, headers)
return try {
return restTemplate.exchange(
"$META_URL/$VERSION/meta-data/public-ipv4",
HttpMethod.GET,
request,
String::class.java
)
.body
} catch (ex: Exception) {
log.warn("aws ip 메타정보를 가져오는데 실패했습니다. ip가 내부 값으로 설정됩니다.")
null
}
}
}
필요 로직은 간단하다.
내가 필요한 건 public 주소 뿐이기 때문에
1. token 을 발급한 뒤
2. 해당하는 토큰을 헤더에 넣어 get 요청을 보내기만 해주면 된다.
토큰은 서버가 올라갈 때 잠깐만 필요하기 때문에 짧게 설정해줘도 상관 없다.
@Bean
fun eurekaInstanceConfig(
inetUtils: InetUtils?, awsMetadataTemplate: AwsMetadataTemplate
): EurekaInstanceConfigBean? {
if (inetUtils == null) {
return null
}
val bean = EurekaInstanceConfigBean(inetUtils)
awsMetadataTemplate.createToken()
?.let { awsMetadataTemplate.getPublicIp(it) }
?.let {
log.debug("publicIp: $it")
bean.hostname = it
}
return bean
}
그 다음 EurekaInstanceConfigBean의 hostname을 재설정해주면 된다.
이렇게 서버를 올리는 경우 로컬 환경에서는 hostname이 재설정 되지 않기 때문에 local ip 가 올라간다.
aws 환경에서는 해당 정보를 바탕으로 구해온 publicIp 가 제대로 등록되는 걸 확인할 수 있었다!
이직하기 전 회사에서는 온프레미스 환경에 데브옵스가 나뉘어져있어서 서버 관련한 지식을 쌓을 겨를이 없었는데 이번 기회에 많이 배운것 같다.
'Spring' 카테고리의 다른 글
[Spring Security] 전역 에러 처리하기 (0) | 2025.02.03 |
---|