개요
사내 아키텍처에서는 어플리케이션의 역할을 명확히 구분하기 위해 MSA(MicroService Architecture) 를 도입하고 있다. 여기서, RabbitMQ와 Kafka를 활용해 EDA(Event Driven Architecture)를 구성하고 있으며, 각각의 메시지 큐는 어플리케이션의 역할에 따라 선택하였다. 예를 들어, RabbitMQ는 순서 보장과 Queue 단위로 이벤트를 처리하기 수월한 도메인과 Kafka는 확장성과 처리량이 중요한 도메인에 적용하고 있다.
현재 메시지 큐는 AmazonMQ-RabbitMQ와 Amazon MSK-Kafka를 사용하고 있는데, 운영 환경에 MSK를 사용하는 신규 어플리케이션을 배포한 이후, 갑작스럽게 MSK 커넥션이 종료되는 문제가 발생하게 되었다. 이에 따라 몇 시간 동안 장애 상황이 지속되었고, 어플리케이션 로직 이슈 및 MSK 인증 방식 등 여러 복합적인 요인으로 인해 발생한 것을 확인하게 되었다.
해당 이슈를 해결하기 위해 장애 발생 당시와 이후의 회고 과정에서 정말 많은 시간을 소요하게 되었는데, 해당 이슈의 근본적인 원인을 해결하기 위해 고생한 나를 위해 겪었던 상황을 정리해 보고자 한다.
🛠️ 아키텍처
현재 서버 어플리케이션은 Nest.js 프레임워크로 구성되어 있으며, Amazon MSK를 사용하여 EDA(Event Driven Architecture)를 구성하고 있다.
- Producer 서버는 Event를 MSK에 발행(Produce)하여 이벤트를 전달
- Consumer 서버는 MSK에 발행된 Topic을 소비(Consume)하여 비즈니스 로직을 수행
해당 서버들은 EKS 환경에 배포되어 있으며, MSA(MicroService Architecture) 형태로 구성되어 있다. 각 서버가 MSK와 연결 및 인증을 수행하기 위해서 IRSA(IAM Role for Service Account)를 이용하여 일시적으로 권한을 취득하는 방식으로 인증을 수행하고 있다.
💊 이슈 디버그
해당 이슈는 운영 환경에 어플리케이션을 배포한 이후 발생하게 되었다. 배포 당시 개발망과 운영망은 서로 다른 인증 방식을 사용하고 있었는데, 개발망에서는 Worker Node에 Role을 Attach 하여 인증을 수행하였지만, 운영망에서는 IRSA 기반 인증 방식으로 개선된 상태였다. 이는, 여러 작업이 함께 이루어지던 중 환경 통합이 되지 않았던 상황이었고, 운영망과 개발망 간의 마이그레이션이 완료되지 않던 상황에서 신규 어플리케이션이 배포되면서 문제가 발생하게 되었다.
여기서, 신규 어플리케이션은 개발 환경에서 스트레스 테스트 와 평균 부하 테스트를 진행했을 때는 MSK 커넥션이 안정적으로 유지되면서 정상적인 동작을 수행하였으나, 운영 환경에서는 MSK 커넥션이 갑작스럽게 종료되는 문제가 발생하게 되었다. Terraform으로 인프라를 구성하여 Security Group과 IAM Role과 같은 보안 및 권한 부분에서는 변경 점이 없었으나, IRSA를 도입한 것만이 유일한 차이점이었다.
왜 이런 이슈가 발생하게 되었는지 장애 발생 당시를 재구성해 보면서 해결 방법을 도출하는 의식의 흐름을 정리해 보도록 하자.
1️⃣ 신규 어플리케이션을 운영 환경에 배포하였다.
MSK를 사용하는 Producer와 Consumer의 역할을 담당하는 신규 어플리케이션을 운영 환경에 배포하였다. 배포 후 내부 테스트에서 모든 어플리케이션이 정상적으로 동작하는 것을 확인한 뒤 서버 모니터링을 종료하게 되었는데, 몇 시간이 지난 후 이벤트가 정상적으로 처리되지 않는 이슈가 발생하게 되었다.
해당 이슈를 확인하기 위해 Producer와 Consumer 서버의 로그를 점검해 보았는데, Producer 서버는 MSK와 정상적으로 연결되어 있었으나, Consumer 서버에서는 Cannot change principals during re-authentication
과 같은 에러가 발생한 것을 확인하게 되었다.
여기서, 엎친 데 덮친 격으로 Consumer 서버의 Retry 로직이 잘못 구성되어 있어, Consumer 서버는 종료되지도 않고 MSK를 사용할 수 없는 상태로 멈춰있었으며, 이벤트가 발행되더라도 이벤트를 처리하지 못하고 있는 상황이 발생하게 된 것이다.
2️⃣ MSK Retry 로직을 개선하여 Hotfix 버전을 배포하였다.
해당 이슈를 빠르게 해결하기 위해, Consumer 서버의 로직을 개선하게 되었는데, MSK와 연결이 종료되었을 경우, 일정 횟수 내에서 재시도를 요청하도록 수정했으며, 재시도가 실패하면 프로세스가 종료되도록 로직을 추가하게 되었다. 여기서, 모든 어플리케이션들은 EKS 상에서 실행 중이므로, 서버가 종료되더라도 ReplicaSet이 자동으로 서버를 재실행하면서 Consumer 서버가 MSK와의 연결을 자동으로 복원할 수 있게 되어 이슈가 해결될 것으로 판단하게 되었다.
Hotfix 버전 배포 후, Consumer 서버는 연결이 종료되더라도 자동으로 복구되면서 이벤트 처리가 정상적으로 이루어지는 것을 확인하게 되었다.
3️⃣ 발생한 에러를 상세하게 분석해 보았다.
Hotfix 버전을 배포하여 Consumer 서버가 멈추며 이벤트를 처리하지 않는 상황은 해결하게 되었으나, 근본적으로 MSK와 연결이 끊기게 된 원인에 대해서는 아직 파악하지 못한 상태였다.
해당 문제를 분석하기 위해 에러가 발생한 서버의 로그를 확인해 보았는데, 서버가 실행된 지 1시간 후, Cannot change principals during re-authentication
이라는 에러가 발생하며 MSK와 연결이 해제된 것을 확인하게 되었다.
[SaslAuthenticator-OAUTHBEARER] SASL OAUTHBEARER authentication failed: Cannot change principals during re-authentication from IAM.arn:aws:sts::<AWS_ACCOUNT_ID>:assumed-role/MskSaslIRSARole/aws-sdk-js-session-1733388958898: IAM.arn:aws:sts::<AWS_ACCOUNT_ID>:assumed-role/MskSaslIRSARole/aws-sdk-js-session-1733392551401","broker":"b-1.msksasl.8b7568.c3.kafka.<REGION>.amazonaws.com:9098"}
에러를 더 상세하게 분석해 본 결과, 문제의 원인은 '최초 MSK 연결 시 인증한 세션과 갱신 후 세션 이름이 달라져 발생'한 것으로 확인되었다.
왜 최초 MSK를 인증한 세션과 갱신 후 세션 이름이 달라지는 걸까?
AWS STS는 Assume Role 기반으로 세션을 발행하는데, 세션 이름에는 밀리초 단위의 타임스탬프가 추가되어 구성하게 된다. 예를 들어, aws-sdk-js-session-<MS_TIMESTAMP>
와 같은 형식으로 발급된다.
여기서, STS는 기본적으로 1시간의 세션 유지 시간을 가지는데, 서버가 처음 1시간 동안 정상적으로 동작했지만, 세션 갱신 시 타임스탬프가 포함된 새로운 이름의 세션이 발급되면서 MSK 최초 인증 세션과 갱신된 세션 간에서 세션 이름 불일치로 인해 MSK 연결이 실패하게 되는 것으로 확인되었다.
4️⃣ aws-sdk 세션 이름을 고정해 보았다.
MSK 최초 인증 시점과 세션 갱신 시점에서 이름이 달라지는 문제가 원인이라면, STS 갱신 시 세션 이름이 변경되지 않도록 설정하면 문제를 해결할 수 있다고 판단하였다. aws-sdk는 내부적으로 이러한 기능을 지원 하는데, AWS_ROLE_SESSION_NAME
환경 변수를 서버에 추가하게 된다면 Assume Role 요청 시와 세션 갱신 시 동일한 세션 이름을 사용할 수 있는 것을 확인하게 되었다.
확인한 것과 같이, Consumer 서버에 환경 변수를 적용해 보았다.
환경 변수를 추가한 후, Consumer 서버를 실행하여 테스트를 진행해 보았는데, 서버 실행 1시간 후 세션이 갱신되었음에도 문제가 발생하지 않았으며, 서버를 1일 이상 실행한 결과 이전과 같은 이슈는 더 이상 나타나지 않게 되었다.
이로써, AWS_ROLE_SESSION_NAME
환경 변수를 사용해 세션 이름을 고정하는 방식으로 MSK 커넥션 문제가 완전히 해결되게 되었다.
🤔 정말로 MSK 커넥션 문제는 해결된 것일까?
위와 같은 의사 결정으로 인해, AWS_ROLE_SESSION_NAME
환경 변수를 통해 MSK를 사용하는 모든 서버에서 세션 이름을 고정하여 문제를 해결하고 있는 상황이다. 하지만, 이런 방법은 단기적인 해결 방법일 뿐 근본적인 문제를 해결한 방법은 아니라고 생각했다.
나는 이번 이슈를 해결하기 위해서는 2가지의 방안이 필요하다고 생각했다.
첫 번째로, Assume Role의 세션 이름을 라이브러리 설정에서 지정할 수 있도록 개선이 필요하다.
이번 이슈의 해결 방안의 가장 큰 문제는 어플리케이션의 문제를 인프라 단에서 해결하는 것이다. 만약, 인프라 담당자가 변경되거나, 실수로 환경변수를 개발자가 삭제하는 상황을 가정해 보자. 그렇게 된다면, 서버의 어플리케이션은 정상적으로 배포되겠지만, 1시간이 지난 후 세션이 갱신되었을 때 인증 문제가 발생하게 될 것이다. 이는, 더욱 많은 피해와 이슈를 분석하는 시간이 소요되게 될 것이다.
이와 같은 문제가 발생하지 않기 위해, 인프라가 아닌 라이브러리 내의 설정을 통해 세션 이름을 지정할 수 있도록 개선이 필요하다고 생각했다.
두 번째로, 라이브러리 내에서 세션 갱신 시 이름이 변경되더라도 문제가 발생하지 않도록 개선이 필요하다.
이번 이슈의 가장 근본적인 원인은 최초 인증 시점과 세션 갱신 시점에서 세션 이름이 달라져 인증 문제가 발생한 것이다. 이 문제를 해결하기 위해, 가장 근본적인 문제인 세션 갱신 시 이름이 변경되는 상황이 발생하더라도 인증이 성공할 수 있도록 개선하는 것이다. 이 방법은 보안 측면에서는 좋지 않은 방법이라고 생각하는데, 별도의 라이브러리 내의 설정을 통해 세션 이름이 다르더라도, 인증이 성공할 수 있도록 설정하는 방법이 추가되면 특정 환경에서 유용하게 사용할 수 있지 않을까 생각한다.
🥕 이슈 제출
해당 이슈에 대해 나와 동일한 문제를 겪은 사람들은 많았지만, 명확한 해결 방안을 제시한 사례는 없는 것을 확인하였고, 이를 오픈소스 이슈로 제출하기로 마음먹었다.
현재 MSK에서 SASL_OAUTHBEARER 메커니즘을 사용해 인증하려면 AWS Document에서 가이드 하는 것과 같이 aws-msk-iam-sasl-signer-js 라이브러리를 활용해야 한다. 이 라이브러리는 MSK와 IAM 기반 인증을 간소화하지만, 이번 세션 갱신 시 이름 불일치와 같은 상황에서는 이슈가 발생하게 된다.
이를 해결하기 위해, 내가 생각한 해결 방법에 대한 설명과 함께 장애 상황을 재현할 수 있는 Nest.js Application, Terraform, Kubernetes 예제를 추가하여 이슈를 제출하였다.
만약, 해당 이슈를 재구성한 프로젝트가 궁금하다면, 아래의 링크를 참조하길 바란다.
Nest.js: Repository Link
Terraform: Repository Link
Kubernetes Apps: Repository Link
마지막으로
이번 이슈의 근본적인 원인을 완전히 해결했는가? 라는 질문에는 답변하기 힘든 상황인 것 같다. MSK와 aws-sdk, SASL 인증 방식, 그리고 Assume Role에 대한 깊은 이해가 아직은 부족하다고 느끼고 있기 때문이다. 또한, 제안한 2가지의 방안 역시 인증 방식에 대한 이해도가 부족해 확신을 갖기 어려운 상태이다.
과거 Severless Freamwork에 컨트리뷰션하던 때와 같이 코드 레벨에서 문제를 수정했더라면, AWS의 세션 인증 방식을 더 깊이 이해하면서 학습을 진행했을 것이라는 아쉬움과 함께, 이번 오픈소스는 1년 이상 유지보수가 이루어지지 않고 있다 보니, 추후 문제가 해결될 가능성도 불투명한 상황이다. 그렇기 때문에 안타까움이 많이 남은 상황에서 마무리를 짓는 것이 아쉬울 뿐이다.
'분석과 탐구' 카테고리의 다른 글
Terraform으로 생성한 IAM Role은 비정상일까? (0) | 2024.10.13 |
---|---|
글또 8기 회고와 9기의 목표 (0) | 2023.12.10 |
제로부터 시작하는 Prisma와 Nest.js (0) | 2023.07.16 |
아키텍처 패턴 그리고 헥사고날 아키텍처 (0) | 2023.07.02 |
제로부터 시작하는 DDD를 위한 이벤트스토밍 (0) | 2023.06.18 |