Trial an error during chat server dockerize

Youngoh Kim
Spoon Radio
Published in
11 min readApr 25, 2021

채팅서버 Dockerize 과정에서의 로깅 관련 고민과 시행착오 회고록

스푼 라디오 메시징 팀에서 채팅 서버 개발을 담당하고 있는 Elliott입니다. 메시징 서버 Dockerize 과정에서의 고민들과 시행착오 과정과 결과를 공유드립니다.

Background

  • 현재 스푼라디오의 메시징 서버는 EC2 인스턴스 위에서 동작하고 있다.
  • 전체적인 서비스 구조를 MSA로 가져가면서 메시징 서버도 이에 발맞춰 Dockerize를 진행하는 중에 고려해야 했던 사항들을 통해 콘셉트를 정의했다.
  • 콘셉트에 맞게 AWS 리소스를 구성 가능 여부와 성능에 대하여 조사 및 검증을 진행했다.
  • 이번 블로깅의 주된 내용은 로깅 시스템에 집중됐다.

Docker-based Architecture Requirements

  • 빠른 빌드와 배포가 가능한 CI & CD 환경
  • 실시간 로그 모니터링 가능한 환경

Legacy

Dockerize 요구사항 중 실시간 로그가 모니터링이 가능해야 한다는 것이 매우 중요했는데 메시징 서버의 특성상 API 대비 로그가 매우 많이 생산되며 장애 상황에서 직접적으로 로그를 모니터링하는 경우가 많기 때문이다.

현재의 로그 모니터링 구조

현재는 위와 같은 형태로 직접 인스턴스에 접근하여 tail & grep 을 통해서 로그를 모니터링 하고 있고, 다른 서드 파티나 S3로 백업, 스트리밍하고 있다.

이상상황 발생 시 실무적인 관점에서 몇 가지 문제점이 있었다.

  • 이상상황 발생 시 백업 로그 열람 승인을 얻어 접근해야 하기 때문에 시간이 오래 걸린다.
  • 스트리밍 된 데이터는 Debug-Level의 로그를 전송하지 않을 뿐 아니라 실시간이 아닌 Low-latency의 영역이기 때문에 긴급한 상황에서 빠르고 정확하게 모니터링하기 어렵다.

일반적으로 컨테이너에서 생성된 로그는 CloudWatch나 다른 서비스를 통해서 처리를 하고 있다.

하지만 CloudWatch는 사용자 친화적이지 못한 UI로 사용하기 매우 불편하고 시간당 한 개의 인스턴스에서 최대 약 10GB나 되는 로그가 생성되는 시스템의 로그를 CloudWatch로 이용하겠다는 건 전기 요금 폭탄을 맞고 싶다는 것과 같은 얘기였다.

Try and Fail

First Concept

Container logging with CloudWatch

최초로 생각했던 모습은 위와 같은데 컨테이너의 STD output을 CloudWatch를 이용하여 스트리밍하고 필요한 경우에 Kinesis, Fluentd를 이용할 예정이었지만 CloudWatch는 비용이 문제가 되고 우리가 원하는 실시간 수준의 인터페이스로 사용하기에는 사용성이 너무 떨어졌다.

Second Concept

Container Logging with EFS

다음으로 주목하게 된 것은 AWS EFS였는데 일종의 플렉서블 한 NFS(Network File System)라고 보면 된다. 다행히(?) Fargate Task Definition에는 Volume mount를 제공하는데 EFS type도 마운트를 할 수 있었다.

위 구조의 사용흐름은 아래와 같다.

  1. Container의 로그가 저장될 경로를 EFS에 마운트 한다.
  2. EFS를 마운트 한 EC2 또는 Kinesis를 통하여 S3에 로그를 백업한다.
  3. 컨테이너에서 생성된 로그를 Low-latency 서비스를 이용하여 외부로 스트리밍 한다.
  4. 스트리밍 된 데이터를 GUI 서비스를 통하여 모니터링한다.
  5. 실시간 모니터링은 EFS를 마운트 한 인스턴스를 사용한다.

위와 같은 콘셉트로 구성하게 되면 비용도 절감하고 실시간으로 생성되는 로그를 접근하기도 용이할 것으로 보였기 때문에 본격적으로 구성을 시작했다.

Fargate Task definition에서 Volume을 생성하고 Container에 생성된 볼륨을 마운트 할 수 있는데 설정된 정보는 아래와 같다.

AWS ECS -> Create Task Definition -> Container, Volume setting

Created Volume by Task Definition
Task Definition’s container mount point

위와 같이 설정 후 컨테이너를 실행시키게 되면 EFS를 마운트 한 EC2에 접근하여 컨테이너에서 생성된 로그 파일을 확인할 수 있다.

EC2 console

컨테이너마다 생성된 로그파일을 관리하기 위해 추가적으로 Task ID로 디렉터리를 생성하는 과정을 추가했고, 각 컨테이너마다 독립된 경로로 EFS에 마운트 된다.

Load Test

기본적인 세팅을 완료했지만 메시징 서버가 오토스케일링 과정을 가지면서 생성되는 로그가 EFS의 성능에 얼마나 영향을 주는지도 검증을 해봐야 했다.

아래의 지표들은 내부 부하 테스트 용으로 개발한 테스팅 툴을 이용하여 약 5 만명에 가까운 사용자를 이용하여 시나리오 테스트를 진행한 결과이다.

EFS Resources

EFS에는 Burst Creadit이라는 것이 존재하는데 처리량이 급격히 증가하게 되면 기본 2.31TiB를 기준으로 크레딧이 소모되고 해당 크레딧이 0으로 소진되면 처리 성능이 저하된다.

위의 지표에서 얻을 수 있는 인사이트로는 현재 테스트 수준의 로그 생성량으로는 EFS의 성능을 떨어뜨릴만한 처리량을 발생시키기 어려우며 기대했던 것보다 안정적으로 서비스가 운영되고 있다는 것이다.

ECS Fargate Resources

ECS의 지표를 확인해봐도 부하테스트에 의해 CPU, Memory 사용량이 높은 것을 제외하면 특이점은 보이지 않고 테스트도 정상적으로 잘 진행되었다. 메모리 사용량이 유달리 높은 이유는 컨테이너에 할당된 메모리를 JVM이 많이 차지하고 있기 때문이다.

EFS

하지만 EFS를 이용하는 방식에도 문제점이 있었다.

  • EFS에서 장애가 발생하는 경우 Container들의 R/W 작업이 마운트 된 경로의 EFS로 반영되지 못하여 컨테이너들이 전체적으로 Lock이 걸릴 수도 있다.
  • 마운트 된 EC2에서 로그를 읽을 때 생각보다 속도가 매우 느리다.

위와 같은 위험성으로 인하여 EFS도 사용하지 못하게 됐다…

SSM Agent

모든 시행착오와 히스토리를 글로 쓰기에는 너무 길고 내부적인 내용이라 공유하기는 어렵지만 수차례의 시행착오를 거치면서 운영 레벨에 필요한 실시간 로깅 접근 인터페이스를 고민하던 중

한 레퍼런스를 얻게 됐고 해당 레퍼런스에서는 Fargate 컨테이너를 Session Manager로 접근할 수 있는 것을 확인 후 이것을 구현하고 테스트하는 과정을 진행했다.

Role Settings

ECS Task Definition

ECS Task Definition에는 Task를 실행할 때 필요한 권한을 가지고 있는 Role이 존재하는데 이 Role을 아래와 같이 수정해 줘야 한다.

task.yml

ecsTaskExecutionRole 권한에 위와 같은 권한을 설정하면 컨테이너에 AssumeRole 권한을 통하여 role이 승계되기 때문에 별도의 키를 발급받지 않고 Task Role의 권한을 사용할 수 있다.

ssm.yml

위의 권한 정보는 컨테이너에서 AWS에서 관리되는 Fargate container가 실행되는 인스턴스에서 SSM Agent를 통하여 세션 매니저에 연결을 등록하는 과정을 위한 Role 설정 정보다.

Docker Settings

Dockerfile

권한 설정이 끝났다면 컨테이너 측 설정을 해줘야 한다.

Docker 이미지 빌드 시 주의사항

도커 이미지 빌드 시 Systemctl, SSM Agent 관련 사용 주의사항이 있다.

  • systemctl의 경우 컨테이너 내부에서 일반적으로 사용하는 방식으로는 사용할 수 없고 systemctl.py COPY 하여 명령어로 사용하는 방식으로 우회해서 사용해야 한다.
  • 테스트 결과 latest로 SSM Agent를 설치하게 되면 Fargate container가 실행될 AWS 측 인스턴스에서 latest 버전을 지원하지 않아 세션을 시작할 수가 없다. 필자는 `2.3.68.0` 버전을 사용했다.
entrypoint.sh

이제 컨테이너 런타임 레벨에서 실행할 스크립트를 작성하는데 여기서 중요한 부분은 ssm agent를 내가 지정한 리전의 세션 매니저에 등록하는 과정이 필요하다. create activation 명령어를 통해서 인스턴스를 등록하고 에이전트를 등록하는데 필요한 Credential을 받아서 에이전트 등록에 사용하도록 한다.

필요한 작업들은 끝났다.

이제 컨테이너를 실행시키게 되면 아래와 같은 로그를 확인할 수 있다.

Container’s cloud watch log

파란색 박스에 있는 로그는 뭔가 잘못 설정된 느낌이 있는데 해당 로그가 뜨더라도 사용하는 데는 전혀 지장이 없다.

View Results

AWS SystemManager → SessionManager → Start Session
  • 세션 매니저에 컨테이너 위에서 동작하는 SSM Agent를 통하여 등록된 세션을 확인할 수 있다.
  • 해당 세션을 통해서 연결하게 되면 우리가 알고 있던 터미널을 통해서 컨테이너 내부에 있는 로그파일에 직접 접근하여 모니터링할 수 있게 된다.

SSM Agent를 컨테이너 위에서 등록하여 사용하는 방식의 세션 관리는 단점이 있는데 컨테이너가 오토스케일링 되는 경우 이미 등록된 후 컨테이너가 사라지더라도 세션 매니저에 해당 세션은 여전히 등록되어 있다는 것이다.

AWS System Manager -> Fleet Manager

Garbage sessions

Fleet Manager 메뉴로 들어가면 세션을 삭제할 수 있는데 문제는 하나씩 삭제해야한다…

그래서 AWS에서 권장하는 것으로는 Lambda를 통하여 주기적으로 다운된 컨테이너의 세션을 정리하는 과정을 처리하도록 안내하고 있다.

명령어는 아래처럼 사용할 수 있다.

aws ssm deregister-managed-instance 2 --instance-id "mi-08ab247cdfEXAMPLE"

마치면서…

기존에 인스턴스 기반으로 실행되던 애플리케이션을 Dockerize 하면서 실 서비스 환경에서 어떤 것들이 고려되어야 하는지 좀 더 명확하게 알 수 있었다.

원래 도커라는게 쓰고 버리는 형태로 사용이 되기 때문에 로깅 시스템의 경우는 스트리밍으로 쓰는 것이 일반적인 것으로 알고는 있었지만..

메시징 서버의 경우 일반적인 API와는 다르게 대량의 로그를 생산하기 때문에 실무적인 요구사항을 수용하면서 현실적인 문제(비용) 등을 어떻게 다른 방식으로 해결할 수 있을지 많은 고민을 했다.

Back-up, Low-latency, Real-time 로깅의 인터페이스와 수집 방식을 다각화해서 장애 대응에 더 민첩하게 대응할 수 있는 점도 충분히 고려해야 한다는 것이 중요하다.

--

--