프레임워크 변경도전기 (Apex to AWS Chalice)
김기욱
2021-12-01
Overview
오픈소스화된 Private 프레임워크를 통해 개발자들은 편리하게 API를 개발할 수 있고, 개발된 API를 다양한 서비스와의 확장 및 연결하는 데도 도움을 받을 수 있습니다. 문제는 어디까지나 개인의 제작물인 만큼 제작자가 언제든지 지원종료 및 레포지토리를 폐쇄할 수 있고, 이 경우 이미 해당 프레임워크를 통해 서비스를 제공하고 있다면 꽤나 곤란한 상황이 발생할 수 있습니다. 이번 글은 실제로 서비스 종료된 프레임워크인 Apex에서 AWS Chalice로 변경하는 작업을 통해 이런 난감한 상황을 극복했던 과정을 써보도록 하겠습니다.
Contents
- 브랜디의 CI/CD 방식
- 모듈 단위의 람다함수 관리
- 당면한 문제
- 왜 찰리스였는가?
- 변경 과정
- 마무리
브랜디의 CI/CD 방식
브랜디에서는 AWS에서 제공하는 다양한 서비스들을 활용해서 CI/CD를 구축하고 있습니다.
간단하게 해당 과정을 도식화하자면 다음과 같습니다.
- AWS CodeCommit: 로컬에서 개발자들의 작업물들이 검수를 마치고 최종적으로 master branch에 merge가 되고 이를 remote origin branch에 push하게 되면, AWS CodeCommit은 GitHub와 마찬가지로 AWS 클라우드 내에서 API의 버전 관리를 서비스해줍니다.
- AWS CodeBuild: 최신화된 소스코드를 컴파일하고 테스트를 실행하며, 테스트를 통과하면 사전 패키징된 빌드환경(YML사용)을 통해 신속하게 빌드작업을 진행해줍니다.
- AWS CodeDeploy: 소스코드의 빌드가 완료되었다면 이를 실제로 상용에서 서비스를 제공하고 있는 람다함수에 배포를 해야 되는데 이 과정을 CodeDeploy가 자동으로 대신해줍니다.
- AWS CodePipeLine: CI/CD 프로세스 전 과정을 감시해서 각각의 단계가 끝난 경우 자동으로 다음 단계(ex: 코드커밋에서 코드빌드) 진행시키기 위해선 코드파이프라인을 정확하게 구축 해두고 각각의 서비스를 연결시켜주는 작업이 반드시 선행되어야 합니다. 이를 통해 로컬에서
git push
한번으로 버전관리→빌드→배포까지 전 과정을 코드파이프라인이 상태변경을 감지해 자동으로 진행시켜주게 됩니다.
이렇게 브랜디가 서비스하고 있는 대부분의 API들은 네 가지 AWS 서비스를 활용해 간편하고 유기적으로 CI/CD를 진행하고 있습니다. 다만 모든 람다함수들이 코드 파이프라인을 통해 빌드/배포되는 건 아닙니다.
모듈단위 람다함수 관리
API를 구성하는데 쓰이지 않는 단순한 모듈단위의 람다함수의 경우 코드파이프라인을 쓰기에는 아무래도 비용적인 문제가 떠오를 수밖에 없습니다. 크기도 크기지만, 지속적통합의 빈도가 높지 않은 모듈의 경우에는 위처럼 네 가지 서비스를 모두 사용해 달마다 각각의 비용 청구를 감수하기에는 아무래도 아깝다는 생각이 듭니다.
이런 이유에서 AWS SQS에서 전달받은 내용들을 지정된 템플릿에 파싱해서 알림톡 전송서비스인 비즈엠에 보내는 알림톡 모듈과 같은 간단한 람다함수들은 코드파이프라인을 따로 구축하지 않고 오픈소스 프레임워크인 Apex를 통해 간단하게 CI/CD를 진행해오고 있었습니다.
형상 관리만 코드커밋을 통해 해주고, 빌드와 배포는 Apex를 통해 로컬에서 커맨드라인으로 간단한 배포명령을 내려주면 이후의 과정은 Apex가 대신해 주는 형식입니다. 형상관리와 빌드/배포를 따로따로 해주는 약간의 수고로움은 있지만 네 개의 AWS 서비스를 한 개로 줄이니 당연히 비용은 적게 들 수 밖에 없습니다.
당면한 문제
문제는 이 편리한 프레임워크인 Apex가 개인이 만든 오픈소스 프레임워크였다는 점입니다. Apex는 제작자가 Up이라는 새로운 프로젝트를 진행하기 위해 지원 및 서비스 종료가 결정되었고 해당 레포지토리는 폐쇄되었습니다. 결과적으로 이전에 Apex 프레임워크를 다운로드한 팀원들 외에는 저처럼 서비스 종료가 결정된 후 입사한 인원들 누구도 Apex 프레임워크를 사용할 수 없게 되었고, Apex로 배포관리되는 알림톡 모듈을 리팩토링하거나 추가할 사항이 생길 경우에는 코드커밋에 푸시한 이후 Apex가 설치 되어있는 팀원에게 배포를 부탁드려야 했습니다.
더 큰 문제는 알림톡 모듈을 작업했던 인원들 중 대부분이 퇴사, 포맷 등의 이유로 Apex 프레임워크를 소실하게 되었고, 급기야는 단 한 명을 제외하고는 Apex를 가진 사람이 없는 상황이 펼쳐졌습니다. 미래를 생각했을 때 람다 배포용 프레임워크를 하루속히 바꿔야 했습니다.
왜 찰리스였는가?
Serverless, Zappa, Architect, AWS Chalice 등 다양한 배포용 프레임워크가 존재하였지만, 그 중 AWS Chalice를 선택한 이유는 다음과 같습니다.
→ Apex처럼 하루아침에 지원종료가 될 가능성이 매우 낮아 보임.
→ 브랜디 랩스는 개발언어로 파이썬을 채택하고 있으므로 호환성이 좋음
→ EC2가 아닌 람다를 사용해 서버리스 아키텍처를 지향하는 브랜디의 개발환경에 적합
→ Flask틱한 구성환경, 간편한 명령어, 가볍다 등등...
이런 이유들을 통해 최종적으로 AWS Chalice로 기존 알림톡 모듈을 컨버전하기로 결정하였습니다.
변경과정
구조분석
컨버전할 프레임워크가 결정되었으니 우선 Apex와 Chalice의 기본 프로젝트의 구조를 확인하여 어떤 식으로 배치해야 할지, 알림톡 모듈의 컨버전을 진행하면서 추가나 수정할 사항이 있는지 확인부터 하기로 했습니다.
한눈에 봐도 기본 폴더와 파일 개수가 차이 나는 것을 확인하실 수 있습니다. functions
안에 폴더 하나하나가 각각 람다함수가 되는 Apex의 기본프로젝트 구조와 다르게 Chalice의 경우 하나의 프로젝트가 하나의 람다함수가 되는 구조를 갖추고 있었습니다. 환경설정용 json과 배포용 로그를 남기는 .chalice
폴더, 람다함수 실행 시 메인 함수 외에 헬퍼 함수나 sql파일 그외 여러 필요한 파일이나 폴더들을 담아두는 chalicelib
, 테스트용 파이썬파일을 담는 test
폴더, 여기에 빌드 시 필요한 패키지를 설치할 수 있게 지원해주는 requitemets.txt
까지 비교적 친절하게 세분화되어있는 기본 구조를 제공하는 Chalice와 달리 Apex는 유저들이 입맛에 맞춰 프로젝트 구조를 커스터마이징 할 수 있게 최소한의 파일만 제공하고 있었습니다.
결론적으로 자유도가 높은 Apex 프로젝트 특성상 프로젝트 구조가 만드는 사람에 따라 각양각색일 가능성이 높았고, 전후 프레임워크들의 기본프로젝트 구조를 살펴본 후엔 어떤 구조로 현재의 프로젝트가 구축되어있는지 그 구조를 파악하고 분석하는게 중요하다는 생각이 들었습니다. 아래의 그림은 기존 프로젝트 분석을 끝낸 후 저 나름대로 정리한 프로젝트의 구조 및 용도별 분류도와 실제 서비스 프로세스가 코드 내에서 어떻게 진행되는지 보여주는 플로우 차트입니다.
그림에서 보다시피 자체 환경변수 및 템플릿, 실행파일, 환경설정용 json, 필요한 패키지 설치용 파일까지 총 네 가지 카테고리로 프로젝트 안에 파일들을 분류할 수 있으며, 소스코드를 분석해 다음과 같은 과정을 거쳐 알림톡 모듈이 실행된다는 사실을 확인할 수 있습니다.
config.py
에서 각각 파트별 환경변수들을 파싱(브랜디, 하이버, FMS 등등)- main.py에서 SQS 넘겨받은 데이터들을 유효성 검증을 진행하고, 유효성 검증이 끝난 후 happy_sender.py로 전송 happy_sender에서 유효성 검증을 한 번 더 진행한 후 이상이 없다면 알림톡 발송 업체에게 해당 메시지를 알맞은 템플릿에 파싱해서
json_data
로 알림톡 발송업체에 전송 → 이후 실패/성공 여부에 따라 콜백 메시지를 SQS에 한 번 더 보내줌 - 메인 함수들 실행과정에서 일종의 헬퍼 역할을 하는 함수들은
[core.py](http://core.py)
로 관리하며 import를 통해 필요시점에 적절하게 사용 - 스테이징 및 상용 서버에 맞게 각각의 람다함수로 배포할 수 있게
functions.{실행환경}.json
으로 함수설정을 달리하고,project.{실행환경}.json
으로 실행환경별로 따로 배포 가능하게 처리
프로젝트 재구성
이제 분석을 통해 서비스프로세스와 기존 프로젝트 구조를 알았으니 이제 서비스프로세스를 AWS Chalice에 맞게 적절하게 재배치 및 테스트를 해주면 로컬에서의 프레임워크 컨버젼이 끝나게 됩니다.
위의 그림은 재배치 및 일부 수정을 통해 컨버젼을 마친 신규 AWS Chalice 프로젝트입니다. 프로젝트의 구조를 너무 크게 바꾸면 모듈 임포트 경로 설정 시 일일이 조정해야 되는 번거로움도 있고, 추후 작업자들이 작업할 때도 불편함을 느낄 가능성을 고려해 최대한 기존 프로젝트와 비슷한 프로젝트 구조를 유지하려고 노력했습니다.
다만 함수설정과 환경설정에 json 파일이 일일히 존재하는 것 보다는 하나의 json에 depth를 두어 관리하는게 좀 더 간편해 보여 일원화 작업을 진행했고, 메인 함수 외 함수들을 메인 함수와 똑같은 경로에 두기보다는 chalicelib/service
폴더 아래 두어서 용도 별로 함수를 분리해보았습니다. 유닛테스트용 테스트파일도 하나 추가 해두었고, requirements
폴더를 그대로 생성해도 되는 Apex와 달리 AWS Chalice의 경우 vendor
라는 명칭의 폴더 아래에 requirements
폴더를 두어야 정상적으로 람다 함수에 requirements
폴더가 생성되는 특징이 있기 때문에 이점을 고려해 vendor
폴더를 생성하고 빌드 시 Hook으로 설치되는 패키지 설치경로를 vendor/requirements
폴더로 수정해두었습니다.
배포테스트
프로젝트 재배치를 마치고 유닛테스트 및 로컬테스트 시 모듈의 작동에 문제가 없다면 이제는 권한설정을 하고 실제 AWS Lambda에 배포 테스트를 진행해야 합니다. AWS Chalice의 경우 app.py
내 app_name + (stage를 설정해놓았을경우) 환경명 + 이벤트 컨텍스트 함수명
이 실제 람다함수명이 됩니다. 이 점 유의하면서 배포를 진행합니다.
#app.py
app = Chalice(app_name='chalice-send-allimtalk')
@app.lambda_function()
def handle(event, context): # 함수명은 chalice-send-allimtalk-staging-handle이 된다.
로컬에서 다음과 같이 _init_.py
에 주석을 수정해서 배포를 진행해보겠습니다.
# chalice-send-allimtalk/chalicelib/_init_.py
# -*- coding: utf-8 -*-
# 블로그용 테스트
AWS Chalice의 배포명령어는 기본적으론 chalice deploy
입니다. 하지만 저는 config.json
파일로 stage를 두어 staging과 master를 다르게 구성하였으므로 chalice deploy --stage staging
이란 명령어로 스테이징용 AWS lambda에 배포를 진행하였습니다.
배포가 성공하게 되면 콘솔에 다음과 같은 메시지를 볼 수 있습니다.
실제 AWS lambda에서도 확인해보시면 해당 내용이 잘 반영된걸 볼 수 있습니다.
제가 컨버젼한 알림톡 모듈의 경우 SQS를 통해 API와 연결되는 모듈이므로 SQS에 연결되어있는 기존의 Apex함수를 disabled
처리하고 이번에 신규로 추가한 Chalice기반 함수로 람다트리거를 바꿔주었습니다.
모든 과정이 끝나고 스테이징서버에서 알림톡 발송을 시도하고 실제 휴대폰에서 받아본 내역입니다.
이로써 프레임워크 컨버젼이 성공적으로 끝났습니다! ☺️
Conclusion
2016년도에 전 세계 Node.js 기반 수천 개의 프로그램이 한순간에 먹통이 되는 일이 발생했습니다. 사건의 원인은 캘리포니아 한 개발자가 NPM 커뮤니티에서 삭제한 11줄의 코드였습니다. 수천 개의 프로젝트들이 개인 개발자의 11줄짜리 패키지에 의존하고 있었고 해당 코드가 삭제되자 관련된 모든 프로그램이 셧다운이 된 것입니다. 오픈소스화된 프레임워크나 패키지는 편리한데다가 강력하지만 이렇듯 역린 또한 존재합니다.
갑작스러운 지원종료라는 이슈도 마주하고 이를 통해 프레임워크 컨버젼을 실제로 진행하면서, 개발자도 예기치 못한 사태에도 대응할 수 있는 상황판단력과 더 나아가 오픈소스라는 편리함 없이도 자유자재로 개발이 가능한 실력을 어떻게 키워야 하는지 고민하게 되는 좋은 계기가 된 것 같습니다. 긴 글 봐주셔서 감사합니다!