AWS Lambda로 Excel -> PDF 변환 기능 개발 후기

요즘엔 서버리스로 개발하는 회사에서 근무중이다. 쓸 수록 느끼지만 서버리스는 간단한 메시지 전달은 좋지만 점점 서비스가 커지면 느껴지는 제약들이 많다. 그 때는 정말 '아 이거 서버 놓고 하면 금방인데 ㅠㅠ' 하는 생각이 절로든다.. 거기다가 Amplify가 아직 성숙하지 않아서 (=버그가 많다) 버전업하면 갑자기 배포가 안되고 그러는 경우도 종종 있다.

아무튼 이번엔 서버리스 환경에서 (=AWS Lambda) 에서 PDF를 Excel로 변환하는 업무가 주어져서 많은 삽질 끝에 해내게 되었다.

즉 이 글은 AWS Lambda에서 Libreoffice를 이용해 xlsx 파일을 pdf로 변환하는 기능을 구현하던 중 겪은 어려움과 주의사항에 대해 기술한다. 실제 구현 방법은 아니고 겪은 Trouble이니 실제 구현을 기대한다면 미리 기대에 어긋난다고 이야기 하고 싶다.


문제 1.람다에서 Streaming으로 Response를 제공할 수 없다.

많은 사람들이 아마 API Gateway와 Lambda를 연동해서 사용할 것이다. 우리도 역시 그렇게 활용중인데 Lambda의 특징은 응답을 한번 보내면 종료된다는 것이다. 

일반적으로 웹에서 파일을 다운로드 받을 때는 Streaming으로 받아서 현재 다운로드 되는 현황을 구경할 수 있는데 Lambda로는 그렇게 구현하는 방법을 찾지 못했다.

따라서 서버에서 컨버팅 작업을 모두 끝내고 파일이 생성되면 해당 파일의 Binary를 String으로 변환하고, 이 String을 Response의 Body에 담아 보내게 했다.

Client는 받은 String을 다시 Binary로 변환해서 이를 다운로드 하게 만들었다. 당연히 파일의 수가 많아질수록 변환하는데 시간은 더 걸릴 것이다. 

API Gateway를 잘 customize하면 Streaming도 대응할 수 있는 것 같은데 확실하지 않다. 그리고 중요한 것. Lambda에서 타임아웃을 길게 걸어놔도 Request를 받은 API Gateway는 응답 지연이 29초가 넘어가면 무조건 Client에게 Fail response를 보냈다. 

즉, API Gateway에 요청을 보냈을 때 29초가 넘어가는 경우 Lambda가 아닌 API Gateway에서 Fail Response를 보내니 주의!


2. LibreOffice 설치문제


사실 LibreOffice를 설치하는게 문제다. 서버가 있다면 맘껏 설치해놓고 사용할 수 있지만 서버리스의 특성상 요청마다 새로운 PC에서 작업한다고 생각하면 편하다. 즉, 힘겹게 LibreOffice를 깔아놔도 Native 상태로는 그걸 재활용을 할 수가 없다...


1)LibreOffice 바이너리를 람다에서 설치할 수 없다.

로컬환경 과 달리 Lambda에서는 패키지 설치가 안되거나 좀 까다로웠다 (Amazon Linux2 이미지 기준). 

예를들어 파이썬 Subprocess로 yum install PACKAGE 해도 안된다. 즉, LibreOffice를 파이썬 Subprocess로 설치할 수 없고 설치할 수 있다 해도 정식버전의 용량이 2기가 이상이기 때문에 분명 설치하다가 뻗을 것이다. 

이리저리 찾아보던 중 인터넷의 누군가도 람다로 LibreOffice를 사용하려는 시도를 했었고 그 사람이 LibreOffice 바이너리 중 Essential 한 파일들만 추출해서 gz로 컴파일 한 버전을 올려둬서 (Thank god) 이를 사용했다. 다운받으라고 올려놓은 파일은 에러가 났고 그 사람이 올린 다른 포스팅을 뒤져보다가 거기 올려 둔 링크를 따라가니 제대로 된 걸 다운 받을 수 있었음..

2) LibreOffice 바이너리는 구했지만…

LibreOffice를 최대한 작게 압축한 파일의 용량이 100MB는 넘었다. 그리고 우리는 S3만을 Storage로 사용하고 있었다. 매 요청마다 S3에서 다운을 받는다? 그리고 이를 설치한다? 거의 불가능에 가깝고 요금 폭탄을 맞을 수 있을 것이다 ㄷㄷ 따라서 LibreOffice를 재활용 하기 위해 AWS EFS를 사용했다. EFS는 AWS의 서비스에 붙어있는 외장하드라고 생각하면 편하다

거기에 깔린 데이터는 영구적으로 저장되며 람다에서 /mnt/efs(폴더명은 변경가능) < 이런 식으로 접근해서 EFS에 저장 된 파일을 사용할 수 있기 때문에 여기에 아까 찾은 LibreOffice 압축 파일과 이를 unzip한 파일을 두고 사용하기로 했다. 

난 (어차피 무료라서) EFS를 택했지만 Lambda의 Layer안에 libreOffice를 압축해제한 파일을 포함해서 배포해도 될것 같다. Layer에 올린 패키지들은 Lambda에서 /var/task < 경로에 저장이 되고 접근이 가능했던 것 같다. (경로는 확실치 않으나 가능함)


3) 람다에 EFS를 붙이자 람다에서 S3에 접근을 못함

EFS를 사용하려면 람다와 EFS가 같은 AWS VPC 안에 있어야 한다. 따라서 Lambda를 VPC 안으로 옮겼더니 S3에 접근을 못하게 됐다 ㅠㅠ.

이유를 간략히 설명하면 기본적으로 S3는 VPC 외부, 즉 AWS 서비스 영역에 있다. 그리고 Lambda도 원래는 S3처럼 AWS 서비스 영역에 있는데 이를 VPC 안으로 데리고 무작정 들어오니 VPC 밖에 있는 AWS 서비스영역과 소통할 수 없게 된 것이다. 

따라서 VPC 안의 AWS 서비스가 S3에 접근할 수 있도록 해줘야 한다.
(이 내용은 제가 이해/추측한 내용을 바탕으로 쓴거라 틀릴 수도 있습니다. 틀린 부분이 보이면 Correct 해주세요)

Default 상태 (S3, 람다가 AWS 서비스 영역에 있는 경우)를 그림으로 보면 아래와 같다. (AWS, Function(람다), S3, VPC만 보면 됨.)


찾아보니 AWS VPC에 Endpoint를 만들어주면 된다고 해서 S3에 접근하는 Endpoint를 붙였더니 S3에 접근 가능해졌다. 

구현은 간단하다. VPC메뉴에서 Endpoint => Attach new Endpoint하면 됐던걸로 기억!


4) LibreOffice.gz 파일을 압축해제 하기 전에 Timeout이 발생


LibreOffice.gz를 람다(EFS)에서 압축해제하는데 시간이 꽤 걸린다. 아까 언급한 것처럼 Lambda에서 limit duration을 길게 잡아놔도 29초만 되면 API Gateway가 Timeout Response를 보낸다. 

하지만 API Gateway에서 Timeout Response를 보낸다고 해도 Lambda가 바로 종료 되는 것은 아닌 것 같다. 후에 로그를 보니 람다의 End Request duration이 2분정도인 기록을 봤고 압축도 잘 풀려있기 때문. 

어쨌든 Request는 API Gateway를 타면 29초에 무조건 Timeout Response를 보내지만 람다는 limit duration까지 살아서 제 역할을 하고 종료되어서 다행히 압축해제는 잘 되었다.


5) LibreOffice로 변환하려고 하니 로컬에서는 잘 됐는데 람다에선 또 오류가 발생…


로컬에선 잘 동작하던 것이 람다에 올린 후 사용하니 /tmp/instdir/program/oosplash: error while loading shared libraries: libX11.so.6: cannot open shared object file: No such file or directory 이런 에러를 내뱉는다 ㅠㅠ

에러 해결방법을 찾다보니 libX11.so를 깔아야겠다는 결론에 도달했는데 저 라이브러리는 패키지 인스톨 방식 `yum install PACKAGE` 처럼 설치를 해야했다. 

그런데 위에 언급한대로 Lambda 안에서는 패키지 인스톨이 잘 되지 않았다. (Python3.8 이미지라서 다른 버전/언어는 잘될수도 있음!) 별별 짓을 다 해봤지만 안 되다가….인터넷 어떤 글에서 Amazon Linux 2엔 Amazon Linux 1에 있는 일부 패키지가 없다는 글을 봤다. (ㄷㄷ)

혹시 지금 사용하는 람다도 그럴까 싶어서 람다 컨테이너에서 사용하는 이미지를 Amazon Linux 1로 바꿔봤더니… 된다!! 

Amazon Linux 1,2를 고를 수 있는 것은 아니고 선택하는 Runtime의 Python버전에 따라 Lambda의 이미지가 결정된다. (Python 3.8은 Amazon Linux2, 그 이하는 1이다.) 따라서 이 Lambda는 런타임을 그냥 파이썬 버젼을 3.8 => 3.7로 내리는 방식을 택했다. 

만약 버전 다운그레이드가 심했으면 또 한참을 찾아 헤맸을텐데 다행히 0.1차이고 3.8 문법을 사용하고 있지도 않아서 괜찮았다.


6) LibreOffice 폰트 문제

람다에서 LibreOffice로 변환 시도까지 다 했으나 한글이 모두 깨져서 □ < 이렇게 나왔다. 처음에는 인코딩 이슈인 줄알고 여러 방법을 취해봤는데 그냥 운영체제에 Font가 없어서 발생한 문제였다;;

Lamdba에 Font를 설치해주려 했으나 이것도 여의치 않고 (Lamdba는 기본적으로 /tmp, /var/task, /opt 이외의 디렉토리는 못 건드린다고 보면 될 것 같음.)

여러 방법을 시도해보다가 실행환경이 아닌 libreOffice unzip한 폴더의 폰트 경로에 해당 Font를 넣어주라는 포스팅을 봤고 그렇게 시도해보니 정상적으로 출력 되었다.

디렉토리 경로는 instdir\share\fonts\truetype이었다. 여기에 폰트만 넣어주면 OK.


Reference

똑같이 구현한 서비스 소개 포스팅: https://medium.com/hackernoon/how-to-run-libreoffice-in-aws-lambda-for-dirty-cheap-pdfs-at-scale-b2c6b3d069b4

해당 서비스 Demo: https://vladholubiev.com/serverless-libreoffice/

댓글

이 블로그의 인기 게시물

로컬 Tomcat 서버 실행속도 개선

2019.05.23 - SQLAlchemy 의 객체 상태 관리 (expire, refresh, flush, commit) 에 관한 이해

2020.02.17 Python의 multiprocessing 중 Pool.map(), chunksize에 관한 내용