AWS ALB 헬스 체크(상태 검사) 간격 및 경로 수정을 통한 서비스 최적화
나는 MSA 아키텍처에서 스프링 부트 서비스를 SNS, SQS를 이용해 서버 간 연동을 시도했다. 그리고 서버 간 연동된 로그를 추적하기 위해 프로젝트에 Zipkin을 사용했다. 그리고 로그 테스트를 진행하다 보니, 내가 직접 테스트한 URL보다 ALB에서 상태 검사를 위한 요청이 더 많이 눈에 띄었다. 그래서 ALB에서 상태 검사하는 주기를 수정하기로 했다. (기존에는 5초로 지정되어 있었다.)
1. AWS ALB에서 헬스 체크 요청하는 건 어떤 프로세스인가
AWS ALB (Application Load Balancer)에서 상태 검사(Health Check)는 ALB가 백엔드 서버들의 상태를 모니터링하는 방법이다. 이 기능은 ALB가 트래픽을 전송하기 전에 타겟(예: EC2 인스턴스, 컨테이너, IP 주소 등)이 정상적으로 작동하고 있는지 확인하는 데 사용된다.
헬스 체크는 주로 HTTP 또는 HTTPS 요청을 통해 이루어진다. ALB는 설정된 주기에 따라 타겟에 HTTP(S) 요청을 보내고, 타겟이 성공적으로 응답하면 그 타겟을 '건강한, 정상' 상태로 간주한다. 반대로, 타겟이 응답하지 않거나 오류 응답을 보내면 '비건강한, 비정상' 상태로 간주하고, ALB는 그 타겟으로의 트래픽을 중단한다.
2. 상태 검사 간격 수정하기
헬스 체크의 주기를 조정하려면 ALB의 Target Group 설정을 수정해야 한다. 다음 단계를 따라서 헬스 체크 설정을 변경할 수 있다:
2-1. EC2 대시보드로 이동한 후, "로드 밸런서" 섹션을 클릭한다.
2-2. 사용 중인 로드 밸런서를 선택한다.
2-3. [리스너 및 규칙] 탭에서 관련된 "대상 그룹"을 클릭한다.
- 로드 밸런서 상세 화면에서 스크롤 내리면 [리스너 및 규칙] 탭이 보인다. 거기서 대상 그룹을 클릭한다.
2-4. 이동된 대상 그룹 화면에서 [상태 검사] 탭을 클릭하고 [편집]을 클릭한다.
2-5. 편집 화면에서 고급 상태 검사 설정 항목을 클릭한다.
2-5. 고급 상태 검사 설정에서 [간격]을 수정한다.
- 상태 검사 포트는 ALB가 상태 검사를 위해 요청을 보낼 때 사용하는 타겟의 포트다. 예를 들어, 애플리케이션이 8080 포트에서 실행 중이라면, 이 포트를 상태 검사 포트로 설정할 수 있다.
- [트래픽 포트]를 선택했을 때는 ALB가 상태 검사를 위해 타겟 그룹에 정의된 동일한 포트를 사용한다. 즉, ALB가 클라이언트 트래픽을 전송하는 데 사용하는 포트와 동일한 포트에서 상태 검사를 수행한다. 예를 들어, 타겟 그룹이 80 포트로 설정되어 있다면, 상태 검사도 80 포트에서 이루어진다.
- [재정의]를 선택했을 때는 상태 검사를 위한 특정 포트를 지정할 수 있다. 이는 ALB가 클라이언트 트래픽을 처리하는 포트와 다른 포트에서 상태 검사를 수행하게 한다. 예를 들어, 타겟 그룹이 80 포트에서 클라이언트 트래픽을 처리하지만, 상태 검사는 8080 포트에서 수행하도록 설정할 수 있다.
- 정상 임계 값은 타겟이 '정상' 상태로 간주되기 위해 연속으로 성공해야 하는 상태 검사의 최소 횟수다. 예를 들어, 이 값을 3으로 설정하면, 타겟은 연속 3번의 상태 검사에서 성공적인 응답을 해야 정상, healthy 상태로 간주된다.
- 비정상 임계값은 타겟이 '비정상' 상태로 간주되기 전에 연속으로 실패해야 하는 상태 검사의 최소 횟수다. 예를 들어, 이 값을 2로 설정하면, 타겟은 연속 2번의 상태 검사에서 실패해야 비정상, unhealthy 한 상태로 간주된다.
- 제한 시간은 ALB가 상태 검사 요청에 대한 응답을 기다리는 최대 시간이다. 예를 들어, 이 값을 5초로 설정하면, ALB는 상태 검사 요청 후 5초 동안 응답을 기다린다. 5초 안에 응답이 없으면 요청은 실패한 것으로 간주된다.
- 간격은 상태 검사 요청 사이의 시간 간격을 의미한다. 예를 들어, 이 값을 30초로 설정하면, ALB는 매 30초마다 상태 검사를 수행한다.
- 성공 코드는 상태 검사 요청에 대한 성공적인 응답으로 간주될 HTTP 상태 코드를 정의한다. 예를 들어, 일반적으로 웹 서버의 정상 작동을 나타내는 '200 OK'를 성공 코드로 설정할 수 있다.
기존에는 30초로 설정되어 있던 [간격]을 최대 시간인 300초로 수정해 주고 [변경 내용 저장]을 클릭했다.
2-6. 이후 다시 Zipkin에서 확인해 보면 서버로 요청이 들어오는 간격이 5분인걸 확인할 수 있다.
3. 상태 검사할 때 상태 검사 경로 수정하기
일단 상태 검사 간격은 5분으로 늘렸지만 그래도 계속 Zipkin UI에 ALB에서 상태 검사용으로 보내는 요청이 추적되는 게 보기 싫어 일단 상태 검사 경로를 root 경로인 / 로 수정했다.
3-1. 상태 검사 경로 수정
그러고 나서 대상 그룹을 살펴보니 비정상으로 떠있는 모습을 확인했다,,
3-2. Zipkin UI 확인
이게 무슨 일인가 싶어 Zipkin UI를 확인해 보니 이상한 일이 벌어졌다. 내가 상태 검사 경로를 / 로 수정하고 나니까 그냥 루트 경로로 get 요청을 보내는 게 계속 누적되는 모습이다. 여기서 일단 가장 위에 있는 로그의 [SHOW]를 클릭했다.
일단 ALB에서 루트 경로인 /로 요청을 보내는 건 확인할 수 있었다.
그리고 에러가 난 부분을 클릭하면 아래와 같이 상세 태그들이 보인다.
이 태그에 쓰인 코드를 해석해 보면 Spring Security에서 발생한 접근 거부(Access Denied) 상황을 나타내고 있다.
각 태그를 살펴보자면,
error - Access Denied는 사용자가 특정 자원에 접근하려 했으나, Spring Security의 보안 정책에 의해 접근이 거부되었음을 나타낸다.
spring.security.authentication.authorities - [ROLE_ANONYMOUS]는 현재 사용자의 권한이 ROLE_ANONYMOUS임을 나타낸다. 즉, 사용자가 인증되지 않았거나 익명 사용자로 간주되고 있다는 것을 의미한다.
spring.security.authentication.type - AnonymousAuthenticationToken는 현재 사용자의 인증 타입이 AnonymousAuthenticationToken임을 나타낸다. 이는 사용자가 익명 사용자로 인식되고 있음을 다시 한번 확인시켜 준다.
spring.security.authorization.decision - false는 인가 결정이 false로 나타나고 있다. 이는 사용자가 요청한 작업을 수행할 권한이 없음을 의미한다.
spring.security.authorization.decision.details - AuthorizationDecision [granted=false]는 인가 결정의 상세 정보를 나타낸다. granted=false는 요청된 작업에 대한 권한 부여가 거부되었음을 의미한다.
spring.security.object - request는 이 에러가 발생한 객체가 HTTP 요청임을 나타낸다.
3-3. 접근 제한 해결 방법
이 문제를 해결하기 위해 3 가지 해결 방법이 있다.
첫 번째로, Spring Security에서 / 에 대한 접근을 허용하는 방법이다.
@Bean
public SecurityFilterChain filterChain(
HttpSecurity http
) throws Exception {
return http
// 기타 보안 설정
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(
new AntPathRequestMatcher("/"), // 상태 검사 경로 허용
// 다른 경로에 대한 보안 설정
).permitAll()
.anyRequest().authenticated()
)
// 기타 설정
.build();
}
그러나 이 방법은 보안에 너무 좋지 않아서 보류하겠다.
💡 Spring Security에서 루트 경로(/)에 대한 접근을 허용하는 것은 여러 보안 문제를 야기할 수 있다. 루트 경로는 웹 애플리케이션의 핵심 부분을 나타내는 경우가 많기 때문에, 이를 무제한으로 개방하는 것은 중요한 정보나 기능이 노출될 위험을 증가시킨다.
또한, 인증되지 않은 사용자나 악의적인 공격자에게 시스템의 취약점을 제공할 수 있어, 보안상 좋지 않다.
Spring Security의 주요 목적 중 하나는 인증과 권한 부여를 통해 애플리케이션을 보호하는 것이다. / 경로에 대한 접근을 제한 없이 허용하면, 이러한 보안 메커니즘의 효과가 크게 저하될 수 있다. 이는 보안 정책의 일관성을 해치고, 다른 중요한 경로들에 대한 보안도 약화시킬 수 있다.
결국, 루트 경로에 대한 접근을 너무 쉽게 허용하는 것은 보안 정책을 관리하는 데 있어 혼란을 초래할 수 있으며, 잠재적인 보안 구멍을 만들 수 있다. 따라서, 이러한 방법은 피하는 것이 바람직하며, 보다 안전한 대안을 찾는 것이 중요하다.
두 번째로, 상태 검사 요청이 Zipkin에 추적되지 않도록 설정을 조정하는 것이다. 이 방법은 이전에 시도해 봤는데 아직 성공을 못해서 잠시 보류하겠다.
세 번째로는 상태 검사 경로 변경이 있다. / 대신 다른 경로를 사용하고, 해당 경로에 대해 Spring Security에서 인증을 요구하지 않도록 설정할 수 있다. 예를 들어, /health와 같은 경로를 사용하고, 이 경로에 대한 보안 요구 사항을 완화할 수 있다.
나는 이 중에서 세 번째 방법인 상태 검사 경로 변경으로 해결해보려고 한다.
4. 상태 검사 경로를 / 말고 다른 것으로 수정
상태 경로를 /health로 만든다고 가정해 보자. 상태 검사 경로를 /health로 설정하는 것은 애플리케이션의 건강 상태를 확인하는 데 중요한 역할을 한다. 이 경로에 대한 처리 방식은 두 가지로 나뉠 수 있다.
첫 번째 방법은 실제로 /health 경로를 처리하는 컨트롤러를 구현하는 것이다. 이 방법은 상태 검사 요청이 애플리케이션에 도달하고, 애플리케이션이 정상적으로 작동하고 있음을 보다 확실하게 확인할 수 있다. 예를 들어, 간단한 컨트롤러를 만들어 "Healthy"라는 응답을 반환하게 할 수 있다. 이렇게 하면, 상태 검사가 애플리케이션의 실제 상태를 더 정확하게 반영할 수 있다.
@RestController
public class HealthCheckController {
@GetMapping("/health")
public ResponseEntity<String> healthCheck() {
return ResponseEntity.ok("Healthy");
}
}
하지만, /health 경로를 처리하는 컨트롤러가 없는 경우에도, Spring Security 설정에서 이 경로에 대한 접근을 허용하면 상태 검사가 가능하다. 이 경우, ALB는 해당 경로에 대한 요청을 보내고, Spring Security가 이를 허용하면 HTTP 200 OK 응답을 받게 된다. 그러나 이 방법은 애플리케이션이 실제로 정상적으로 작동하고 있는지를 덜 정확하게 판단할 수 있다는 단점이 있다.
상태 검사 경로에 대해 실제 컨트롤러를 구현하는 것이 일반적으로 권장된다. 이렇게 하면 상태 검사가 애플리케이션의 실제 상태를 더 정확하게 반영할 수 있으며, 필요한 경우 추가적인 상태 확인 로직을 구현할 수도 있다. 예를 들어, 데이터베이스 연결 상태나 다른 외부 서비스의 상태를 확인하는 로직을 추가할 수 있다.
이때 나는 '그럼 5분마다 ALB에서 요청을 날린 텐데 너무 불필요한 리소스가 낭비되는 게 아닌가?'라는 생각을 했다. 그러나 상태 검사는 서버의 가용성과 건강 상태를 확인하는 중요한 기능이기 때문에, 일정 리소스 사용은 필수적이라고 볼 수 있다.
상태 검사는 서버의 가용성과 건강 상태를 확인하는 중요한 기능이지만, 리소스 낭비를 최소화하는 것도 중요하다. 이를 위해 경량화된 상태 검사 로직을 구현하거나, 상태 검사 간격을 적절히 조정할 수 있다. 또한, 상태 검사 경로의 보안 설정을 최소화하거나, 상태 검사와 실제 트래픽 처리를 분리하는 방법도 고려할 수 있다.
결국, 상태 검사는 서비스의 가용성을 보장하고 장애를 빠르게 감지하기 위한 필수적인 메커니즘이며, 이를 위한 리소스 사용은 필요한 부분이다. 그러나 상태 검사 로직의 복잡성과 빈도를 적절히 관리함으로써 리소스 낭비를 최소화할 수 있다.
그래서 나는 실제 /health 경로를 처리하는 컨트롤러를 만들기로 했다.
4-1. /health 처리하는 컨트롤러 생성
나는 최상단 디렉토리에 전용 헬스 체크 패키지를 만들었다. 그리고 그 안에 HealthCheckController를 생성했다.
package com.recipia.member.health;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* ALB에서 상태 검사 요청용 URL
*/
@RestController
public class HealthCheckController {
@GetMapping("/health")
public ResponseEntity<String> healthCheck() {
return ResponseEntity.ok("Healthy");
}
}
4-2. Spring Security Filter 수정
그리고 스프링 시큐리티 설정파일에서 해당 /health 경로를 허용하는 코드를 추가해 줬다.
@Bean
public SecurityFilterChain filterChain(
HttpSecurity http,
CustomAuthenticationFilter customAuthenticationFilter,
JwtAuthorizationFilter jwtAuthorizationFilter
) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(
new AntPathRequestMatcher("/resources/**"),
new AntPathRequestMatcher("/login"),
new AntPathRequestMatcher("/member/*"),
new AntPathRequestMatcher("/feign/member/*"), // feign 으로 들어온 접근을 허용
new AntPathRequestMatcher("/health") // ALB에서 상태 검사용으로 들어온 '/health' 경로에 대한 접근을 허용
).permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(customAuthenticationFilter, JwtAuthorizationFilter.class)
.build();
}
그리고 최종적으로 다시 로드 밸런스에서 상태 검사 경로를 /health로 수정해 줬다.
5. Zipkin UI 확인
아래처럼 /health로 잘 던지고 에러도 안나는 모습을 볼 수 있다.
안에 상세 로그를 확인해도 에러 발생 없이 성공한 모습을 볼 수 있다.
혹시 몰라 AWS 콘솔에서도 대상 그룹을 확인해 보면 정상으로 잘 나온다.
6. 마무리
사실 상태 검사 간격만 수정하고 경로만 수정해서 되게 간단하게 끝날 문제였다고 생각했는데 갑자기 상태 검사 경로에서 에러가 발생하면서 조금 이야기가 길어졌다.
그래도 이번 기회에 AWS ALB의 헬스 체크 기능이 서비스의 가용성과 건강 상태를 모니터링하는데 얼마나 중요한지 다시 한번 깨달았고 이 설정들을 조정해 서비스의 안정성을 유지하면서 리소스 낭비를 줄일 수 있어서 좋았다.
이 포스트는 Team chillwave에서 사이드 프로젝트 중 적용했던 부분을 다시 공부하며 기록한 것입니다.
시간이 괜찮다면 팀원 '개발자의 서랍'님의 블로그도 한번 봐주세요 :)
'AWS' 카테고리의 다른 글
DynamoDB 테이블 생성하기 (1) | 2024.02.26 |
---|---|
[AWS] Lambda로 RDS 시작 및 중지 설정 (0) | 2023.12.04 |
[AWS] ECS에서 Zipkin을 통한 스프링 부트 서비스 트레이싱 구축하기 (0) | 2023.11.19 |
[AWS] SNS, SQS 연동하기 (2) - Spring Boot 3.X.X 버전과 SNS, SQS 연동하기 (2) | 2023.11.06 |
[AWS] SNS, SQS 연동하기 (1) - SNS, SQS 생성하기 (0) | 2023.11.06 |