서버 자동 재시작(self- healing)
bash와 crontab
health Check를 위한 쉘스크립트
function isRunning {
if[-s $HITUP_PID_FILE]
then
ps -p $(cat $HITUP_PID_FILE) > dev/null
return $?
else
return 1
fi
}
JVM 실행옵션
OutOfMemoryError에러 발생 시 바로 킬하는 옵션
장애 원인을 남기기위해서 heapdump남기는 옵션
java- Xmx10x10m \\
-XX:+HeapDumOnOutOfMemoryError \\
-XX:+ExitOnOutOfMemoryError
-jar may_cause_oom.jar
첫번째 OutOfMemoryError 에러가 나면 프로세스가 바로 다운이되고
어떤 요청을 처리하다가 프로세스가 죽었는지에 대한 로그가 안남는다.
-XX:OnOutOfMemoryError introduced in 1.4.2 update 12.6
java- Xmx10x10m \\
-XX:+HeapDumOnOutOfMemoryError \\
-XX:OnOutOfMemoryError='kill -9 %p'\\
-jar may_cause_oom.jar
XX:OnOutOfMemoryError는 에러가 났을 때
우리가 실행할 수 있는 스크립트를 담을 수 있다.
%p는 pid가 떨어지는 내용이다.
JVM에서만 던져주는 옵션이기 때문에 쉘에서는 조회할 수 없다.
해당서버에서 문제가 생기면 로드밸런서 out시키는 옵션을 추가
충분히 로그가 남도록 30초 기다리는 옵션을 추가
그러고나서 서버 다운
java- Xmx10x10m \\
-XX:+HeapDumOnOutOfMemoryError \\
-XX:OnOutOfMemoryError='lb_out.sh; sleep 30; kill -9 %p'\\
-jar may_cause_oom.jar
그러고 나서 서버가 다운되면
크론탭에 의해서 다시 시작(자동재실행)되는
그런처리를 할 수 있는 상태가 된다.
OutOfMemoryError만 다루는것이 충분한가?
주요커넥션풀이들이 제대로 처리가 되지않으면
서비스가 거의 동작하지않는 이슈가 있다.
k8s의 LivenessProbe의 기능을 참조
실행하는 서버 어플리케이션은 POD안에 있다.
서비스라는 객체는 약간 로드밸런서와같은 역할을한다 .
서비스 통해서 사용자 리퀘스트가 전달이된다.
각각의 노드를 안에보면 kubelet이라는
Demon이 돌고 있다.
kubelet은 마스터 서버에 지령을 받아서
각각의 노드에서 행동을하는 행동대원이라고 생각하면되는데
kubelet이 POD의 상태를 계속 체크해준다.
POD의 상황을 파악하기 위해서 LivenessProbe이라는 url을 호출한다.
LivenessProbe으로 체크한 후에
만약에 제일 마지막 POD에서 LivenessProbe fail이 나면
해당 POD를 삭제하고 새로운 POD를 올리게 되었다.
Spring-Boot Actuator HealthCheck EndPoint
K8s Liveness Probe, Readiness Probe로 잘 사용하는 Endpoint
주요 기능 점검 기능
- Disk 사이즈
- DataSource
- RabbitMQ 연동
- Redis 연동
function isHealthy{
health=$(curl -I <http://localhost:9999/actuator/health> 2>/dev/null| head -n 1 |cut -d$ '' -f2
if [$health -eq "200"]
then
return 0
else
return 1
fi
}
효과
- 일시적인 장애는 익일 확인 후 대응가능
- 무중단 서비스 유지
과부하를 처리하는 방법
보통 MSA 에서 과도한 부하가 발생하면 결과이든 원인이든 cascading failures라고하는 장애가 발생한다.
보통 서버 특정한대가 문제인데 그 서버와 연결된 모든 서버들이 연쇄적으로 문제가 생기는 그런 현상이다.
- 소수 서버의 문제가 다른 서버의 문제로 이어지면서 이슈가 점진적으로 연결된 서버에 오류 유발
- 원인
- - 일부서버의 과부하 - 자원의 부족 - 서버 Crash
cascading failures 시나리오
쓰레드풀 소진
addressing cascading failures
지표의 측정(가시화)
SpringBoot 의 Actuator 활용
- Metric과 Prometheus Endpoint 활성화
Grafana 연동
- 측정 항목
- CPU 사용률, Active Thread, 분당 Request 수
NHN dooray 팀 경우에는
장애 시 Tomcat의 Thread Pool을 모두 사용함
메일의 크기가 큰 경우에는 더 큰 부하가 발생함 (메일의 크기는 40MiB까지)
동시에 너무 많은 메일 수신 (Traffic Spike )
아휴 나중에 결제해주세요~ 나중에 일지 써주세요 ~ 하면
누가 우리 앱을 쓰겠는가?
대응 방안 :
후보1: Scale Up/ Out (돈으로 해결하는 것)
- 확보하기엔 너무 많은 비용 이슈가 발생
- CPU 사용률을 모니터링 했을 때 최번시에 급증 (몰릴 때만 몰리기 때문에 몰리지 않는 시간에는 낭비임)
후보2: Message Broker로 지연 처리 (수신 지연을 처리)
- 메일 수신의 보장 Role을 클라이언트(SMTP)가 담당하고 있는 구조
- - 클라이언트가 수신을 확인할 수 없음 - 처리 완료될 때까지 Retry - Role이 중복되는 문제 - SMTP가 수신여부를 보장할 방법이 없음
후보3: laaS, k8s의 Auto Scale 사용
(클라우드 서비스나 쿠버네티스를 보면 만약 부하가 발생했을 때 auto scale이라는 방법도 있지않을까? )
급하게 트래픽(Traffic Spike)이 들어오는 요청과 같은 이슈들은
Auto Scale이 서비스가 재기동될 때까지 기다려주지않는다.
그 사이에 트래픽이 발생되면 사용자들은 이미 어려움을 겪고있다.
- 단시간에 폭층하는 요청을 Auto Scale이 Cover할 수 없다.Scale Out과 다를 바없는 비용 이슈
- - managed K8s가 아닌 환경에서 HorizentalPodAutoscaler를 구성하는 것은
후보4: Circuit Breaker Pattern(cascading failures 장애라고하면 제일 유명한 방법)
- Circuit Breaker (서버A가 서버로 호출했을 때 지속적으로 Read TimeOur Error가 발생하면
- 더 이상 추가 요청했을 때는 서버A가 Fallback처리하는 그런 방법이다.
- Cascading Failure의 대표적인 해결법
- 이미 서버에 부하가 발생한 후에 동작
- 간헐적인 일시적으로 사용자에게 노출
- 좋은 방법이지만 폭증하는 요청구간에 에러를 조금씩 뿌리게된다.
후보5: 수신거부(Http Status Code 429) (리퀘스트가 너무 많으면 아예 거절해버리자. )
- Too Many Request(429)tomcat.threads.config.max
- tomcat.threads.busy
- http status code
- 기준을 초과한 상황에서 응답거부
- 사용자에게 노출되지않는 API에 적용 (사용자와 interaction이 없는 API)
- 환경과 기준
- - 적용가능한 API 선정 - Too Many Request를 처리할 기준(Threshold)을 찾아야함
- Too Many Request(429) 처리가 가능한 API
- 사용자 interaction이 없는 API
- 사용자 interaction이 있더라도 빈도가 높지않으면 고려할 수 있음
- 비용이 높은 API
- 클라이언트의 Retry가 구현된 API
- 요청 거부한 이후에 다시 시도하는 로직필요
- Retry의 주기에 대한 전략도 필요
- Too Many Request(429) 응답하기 위한 Threshold기준
후보1 :초당 요청량
- 장점: 구현이 쉽다.
- 단점: 서버의 처리량(TPS)를 일정하게 제한한다.
후보2: CPU 사용량
- 연계서버의 응답지연으로 백엔드 시스템에서 병목이 발생하는 경우 오히려 cascade Failures유발
후보3: Active Thread 수
- 연계서버의 지연을 반영하는 지표로 사용가능
- 중요한 API 서비스를 제공하기 위한 Thread Pool의 여분을 안정적으로 확보하는데 유리
Active Thread 수 비율 사용
- SpringBoot Aactuator의 MetricsEndPoint 활용
- 스레드풀 전체 수 대비 사용중인 스레드 수가 85% 넘어가면 응답 거부
- tomcat.threads.config.max
- tomcat.threads.busy
(tomcat.threads.config.max / tomcat.threads.busy) X 100
ThrottlingByThread.java
private static final double MAX_BUSY_THREAD_RATIO_RECIEVE = 85;
private static MetricsEndpoint metricsEndpoint //Dependancy Injection으로 받아온다
//metric라는 메소드를 사용해서 tomcat.threads* 정보 받아오기
public void checkBusyThread(){
MetricResponse busyThreadMetric = metricEndPoinnt.metric("tomcat.threads.busy", null);
MetricResponse busyThreadMetric = metricEndPoinnt.metric("tomcat.threads.config.max", null);
--- 생략 --
double busyRatio = busyThreadCount / maxThreadCount * 100;
//기준이 85% 넘어가면 Exception을 던진다.
if (busyRatio > MAX_BUSY_THREAD_RATIO_FOT_MAIL_RECEIVE){
throw new TooManyRequestException("busy:" + busyThreadCount+ ",ratio" + busyRatio);
}
}
//메서드는 차단을 할 수있는 API에 초기 호출시에 체크를 할 수 있도록 수신한다.
그러고나서 거부량을 지표로 측정했을 때 메일이 급증했을 때 거부량도 급증하는걸 확인할 수 있었다.
To many Request 처리 효과
- CPU 사용룰
- 한꺼번에 처리하지않고 응답거부를 함으로써 Retry에 부하 분산
- Active Thread
- Request Queue가 쌓이지않고 170 + a 로 유지
**********NHN FORWARD 기술컨퍼런스에서 참고한글입니다***********
'DevOps' 카테고리의 다른 글
[MLOps] RunPod 사용하여 A-LLMRec 모델 훈련 및 Inference 테스트 + colab pro+ 비용 및 성능 분석 (1) | 2024.11.26 |
---|---|
[Helm] Helm Chart 생성하기 1편 (0) | 2024.01.24 |
[Kafka] 빅데이터 플랫폼: hadoop, spark, kafka의 역할과 데이터 아키텍쳐 (0) | 2023.05.08 |