[Serverless Framework with Python] 정보 모음 - opencv-python 라이브러리를 aws lambda에 업로드하기
최근에 업무에 쓰기 위해,
opencv-python을 aws lambda에 이식하려고
난리를 쳤습니다.
길진 않지만 약 이틀 정도 각고의 노력을 한 끝에
겨우 이식하긴 했습니다만,
아무래도 패키지 종속 (해당 lambda 패키지에서만 사용 가능)이
발생할 것 같아 레이어 방식보다는 단점이 있는 것 같습니다.
그래도 opencv-python 등지의 라이브러리를
굳이 Docker 키고 가상 환경에서 설치하는 번거로움을 거치지 않고
서버리스 프레임워크만을 활용해 바로 업로드하는 등
(물론 서버리스 프레임워크 상에서 Docker를 실행하는 거지만요)
다양한 방법으로 활용할 수 있을 것 같아 내용을 정리합니다.
( 해당 작업은
pycharm
python 3.8
serverless Framework 1.79
에서 진행되었습니다. )
※ 이번 포스트의 내용을 진행하려면 꼭 docker를 설치한 상태여야 합니다!
서버리스 프레임워크를 쓰면서,
별 생각없이 라이브러리를 설치 한 후
(파이썬 같은 경우엔 pip 방식이 되겠죠)
코드를 냅다 올리려고 하면
aws lambda 상에서 라이브러리를 찾을 수 없다고 뜨는 경우를 꽤나 자주 겪었습니다.
이는 저의 개발 환경의 운영체제와 아마존 클라우드 서비스 상의 운영체제가
다르기 때문에 주로 발생하는 문제인데요,
아마존 클라우드 서비스의 운영체제는 linux이고
제가 쓰는 개발 환경 운영체제는 주로 윈도우나 맥 OS 이기 때문에,
만일 pip install 블라블라.. 이런식으로 별 조치 없이 라이브러리를 설치하게 될 경우
제가 사용하는 운영체제 기반으로 라이브러리가 설치되며,
막상 aws lambda로 코드가 올라갔을 경우
라이브러리를 찾지 못해 에러를 뱉어내게 됩니다.
아무래도 라이브러리 작동 방식이나 경로 등이
OS 별로 다르게 작동하기에 발생하는 문제라고 알고 있습니다.
이러한 문제를 해결하려면 다음의 과정을 거쳐야 합니다.
- Docker 등의 가상 환경 구축 프로그램을 켜서,
- linux 환경을 구축한 후 해당 라이브러리 설치,
- 라이브러리 파일만 따로 가져와서
- s3에 업로드 후 소스코드와 연동하거나, aws lambda Layer에 해당 파일을 올려두는 겁니다.
특히 aws Lambda Layer에 라이브러리를 업로드하게 될 경우
- 패키지 별로 함수(혹은 라이브러리)를 관리할 수 있고,
- Lambda 배포 시 용량을 줄일 수 있으며,
- 다른 Lambda 함수에서도 해당 라이브러리를 가져와 적용할 수 있다는 이점이 있습니다.
대신 requirements.txt를 통해 특정 패키지만 docker 가상 환경으로 설치를 해주는
'serverless-python-requirements' 플러그인을 손봐
해당 라이브러리를 설치해보겠습니다.
선행으로 해야할 일은 serverless Framework를 통해
파이썬 개발환경을 구축하는 것입니다.
serverless Framework를 설치하는 법에 대해서는 아래 링크에서 확인할 수 있습니다.
[Serverless Framework with Python] 환경 구축에서 간단한 API 개발까지 (3) -
Hello Serverless!
serverless Framework 파이썬 환경 구축은 다음 과정을 거치시면 되겠습니다.
1. aws-python3 기반 서버리스 프로젝트 생성
cmd(터미널)에서 진행합니다. 프로젝트 명은 opencv-tutorial로 하겠습니다.
> sls create -t aws-python3 -p opencv-tutorial
_______ __ | _ .-----.----.--.--.-----.----| .-----.-----.-----. | |___| -__| _| | | -__| _| | -__|__ --|__ --| |____ |_____|__| \___/|_____|__| |__|_____|_____|_____| | | | The Serverless Application Framework | | serverless.com, v1.79.0 -------' Serverless: Successfully generated boilerplate for template: "aws-python3"
2. virtualenv로 가상환경 설정(python3.8 기반)
프로젝트 경로(opencv-tutorial 디렉토리)로 cd 명령어를 통해 들어가, 내부에서 virtualenv로 가상환경을 구축합니다.
※ 기본 파이썬 베이스 버전에 따라 명령어가 다를 수 있습니다. 파이썬을 여러 버전으로 사용하시는 분들은 python 명령어를 사용하실 때 앞에 버전을 붙여야 하는 경우도 있습니다. 저의 경우, 맥에서 프로젝트 진행 시 'python3.8' 이라는 명령어로 가상환경을 구축했습니다.
> python -m virtualenv env
created virtual environment CPython3.8.2.final.0-64 in 3761ms creator CPython3Windows(dest=C:\Users\tjlee\Desktop\opencv-tutorial\env, clear=False, global=False) seeder FromAppData(download=False, pip=latest, setuptools=latest, wheel=latest, via=copy, app_data_dir=C:\Users\tjlee\AppData\Local\pypa\virtualenv\seed-app-data\v1.0.1) activators BashActivator,BatchActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
3. package.json 생성
Pycharm에서 프로젝트 폴더 설정 후 터미널을 조작합시다. 터미널 조작은 pycharm 하단부에서 할 수 있습니다.
가상환경이 제대로 구축되었으면, pycharm으로 프로젝트 폴더 진입 시 터미널 내 커맨드 맨 앞에 (env)라고 표기될 것입니다. |
터미널에 아래의 명령어를 입력합니다. 만일 터미널 상에서 pakage.json 내용을 설정해주고 싶으면, -y 키워드를 빼고 입력하시면 되겠습니다.
> npm init -y
. . . "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
4. serverless-python-requirements 플러그인 설치
계속 터미널 상에서 serverless-python-requirements 플러그인을 설치합니다. 해당 플러그인은 파이썬의 requirements.txt를 참고하여 필요한 라이브러리 리스트를 구축, Docker를 통해 aws 환경에서 해당 라이브러리를 사용할 수 있도록 패키지화 해주는 플러그인입니다.
플러그인에 대한 자세한 정보는 documentation에서 확인하실 수 있습니다.
(출처 : serverless-python-requirements Documentation)
> npm install --save-dev serverless-python-requirements
npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN opencv-tutorial@1.0.0 No description npm WARN opencv-tutorial@1.0.0 No repository field. + serverless-python-requirements@5.1.0 added 68 packages from 41 contributors and audited 68 packages in 3.034s 5 packages are looking for funding run `npm fund` for details
5. opencv 라이브러리 설치
파이썬에서 새로운 외부 라이브러리를 설치하듯, pip을 통해서 설치해줍니다. 이 또한 터미널에서 진행합니다.
> pip install opencv-python
. . . Downloading opencv_python-4.4.0.42-cp38-cp38-win_amd64.whl (33.5 MB) |████████████████████████████████| 33.5 MB 6.4 MB/s Collecting numpy>=1.17.3 Using cached numpy-1.19.1-cp38-cp38-win_amd64.whl (13.0 MB) Installing collected packages: numpy, opencv-python Successfully installed numpy-1.19.1 opencv-python-4.4.0.42 WARNING: You are using pip version 20.1.1; however, version 20.
6. requirements.txt 생성
pip freeze 명령어를 통해 requirements.txt를 생성합니다. 해당 파일을 통해, 플러그인이 docker로 패키지화 할 라이브러리를 선정합니다.
> pip freeze > requirements.txt
이렇게 개발 환경 구축은 마무리 되었습니다.
다음은 serverless.yml 파일을 수정하겠습니다.
serverless.yml 파일을 아래와 같이 수정합니다.
profile 요소는 이전에 개인이 설정한 값으로 지정해주어야 하니 이점 유의해주세요.
(저는 default라는 명칭으로 aws credential profile을 설정했습니다)
service: opencv-tutorial provider: name: aws runtime: python3.8 stage: dev profile: default region: ap-northeast-2 plugins: - serverless-python-requirements custom: pythonRequirements: dockerizePip: non-linux ########### BEGIN ########### dockerFile: Dockerfile dockerExtraFiles: - /lib64/libgthread-2.0.so.0 - /lib64/libglib-2.0.so.0 - /lib64/libSM.so.6 - /lib64/libICE.so.6 - /lib64/libXrender.so.1 - /lib64/libXext.so.6 - /lib64/libX11.so.6 - /lib64/libuuid.so.1 - /lib64/libxcb.so.1 - /lib64/libXau.so.6 - /lib64/libGL.so.1 - /lib64/libGLX.so.0 - /lib64/libGLdispatch.so.0 ########### END ########### package: exclude: - env/** - node_modules/** - package.json - package-lock.json functions: hello: handler: handler.hello events: - http: path: "" method: get
custom 부분이 굉장히 긴데, 저 코드의 의미를 하나씩 설명하자면 다음과 같습니다.
- dockerizePip: non-linux
- 해당 플러그인 사용 시, 설치된 패키지 들을 docker 가상 환경으로 설치 후 업로드할 지 여부를 결정합니다. 기본적으로 true, false 값이 입력되나, non-linux의 경우 '리눅스 기반이 아닌 라이브러리만 docker를 통해 패키지 화 하라'는 말입니다. true로 설정하시면 requirements.txt의 모든 패키지를 docker로 설치합니다.
- dockerFile: Dockerfile
- 기본 셋팅된 docker 이미지(쉽게 설명하자면 가상환경 패키지 화 방식을 말합니다) 대신 커스텀한 'Dockerfile'을 통해 docker 이미지를 구축합니다. Dockerfile만 적어 놓으면 해당 서버리스 프로젝트 위치에서 읽어오며, 디렉토리를 두어 관리하고 싶을 경우 '/<dir>/Dockerfile' 식으로 지정하면 됩니다.
- dockerExtraFiles: ...
- 핵심 코드입니다. docker로 패키지 화를 진행할 시, 특정 파일들을 수동으로 추가하는 코드입니다.
- 이후 Dockerfile 코드를 보시면 아시겠지만, 위에 기입된 파일들을 확보하기 위한 코드가 기입되어 있습니다.
- 해당 파일들은 opencv를 aws 리눅스 환경에서 구동하기 위한 필수 파일 들입니다. 타 os 상에서 opencv를 설치했을 경우, 해당 파일들이 os에 맞게 대체되어 있거나 누락되어있을 겁니다(뇌피셜...). 이 문제를 해결하기 위해 위의 작업을 진행했습니다.
- deploy 후에 에러가 발생하는 경우가 있습니다. 이는 몇몇 so파일이 누락되었다는 의미이며, 이는 aws lambda에서 테스트를 통해 어떤 파일이 누락되었는 지 확인할 수 있습니다. 이 경우 Dockerfile을 수정해서 추가로 라이브러리를 설치하고, 이 곳에 누락된 파일을 추가하시면 됩니다.
참고로 package의 exclude는 패키지 업로드 시 제외할 파일/폴더 목록입니다.
opencv가 워낙 크다보니, 해당 목록을 올리게 되면
lambda 업로드 용량 한계(250mb)에 금방 도달하므로 API를 사용할수 없게 됩니다.
물론 exclude 목록은 패키지 업로드 시
쓰이지 않는 더미 파일이기도 하구요.
yml을 마쳤으면 handler.py를 수정하기 전에,
핵심인 Dockerfile을 프로젝트 폴더에 생성하고 다음과 같이 코드를 입력합니다.
FROM lambci/lambda:build-python3.8 RUN yum -y install libXext libSM libXrender libGL libGLX libGLdispatch
RUN은 해당 코드를 실행한다는 명령어입니다. 자세히 보시면 어떤 라이브러리들을 마구마구 설치하는 것을 볼 수 있습니다.
이는 opencv-python을 구동하기 위한 필수 라이브러리들이며, 설치 시 yml에서 설정한 so. 파일이 함께 설치되게 됩니다.
위의 from 소스코드의 경우, 간단히 설명하자면 docker 가상 환경 구축시 어떤 프로그래밍 언어 기준으로 빌드할 지를 설정하는 코드입니다.
자세한 설명은 aws에 잘 나와있으니 링크해두겠습니다.
(출처 : Docker와 함께 시뮬레이션된 Lambda 환경을 사용하여 Lambda 계층을 생성하려면 어떻게 해야 합니까?)
이제 거의 끝났습니다. handler.py만 수정하고 바로 코드를 deploy 해 테스트해보도록 합시다.
handler.py에 내용을 다음과 같이 수정합니다. 지금은 cv 라이브러리를 직접 사용하지는 않고, import만 해 보고 제대로 소스코드가 구동되는 지 확인해보는 시간을 갖겠습니다.
import cv2 def hello(event, context): response = { "statusCode": 200, "body": "opencv 설치되었다!".encode("utf-8") } return response
수정되었으면 deploy를 진행합니다.
deploy가 완료되면 endpoint로 url을 하나 던져줄 것입니다.
> sls deploy
. . . service: opencv-tutorial stage: dev region: ap-northeast-2 stack: opencv-tutorial-dev resources: 10 api keys: None endpoints: . . .
해당 url을 클릭하면 인터넷 창이 뜨면서, 다음과 같이 화면이 뜰 것입니다.
화면 내에 오류없이 텍스트가 떴다면, 설치에 성공했다는 의미입니다!
이대로 끝내면 확실히 설치가 되었는 지 모르기 때문에,
opencv를 사용하는 간단한 API를 만들어보겠습니다.
- 사진 url을 받게 되면
- 회색 사진으로 바꿔서
- s3에 바꾼 이미지를 업로드 하고
- s3 url을 반환해주는 API입니다.
먼저 s3 퍼블릭 버킷을 생성해야 하는데, 이 부분은 스킵 하겠습니다. 글이 너무 길어질 것 같습니다.
구글에 검색하셔서 생성하시면 되고, 만약 기회가 된다면 정리해서 포스팅 하겠습니다.
먼저 필요한 라이브러리를 더 설치하겠습니다. s3를 연동하기위해 boto3, 외부 url 이미지를 가져올 requests를 설치합니다.
>pip install requests >pip install boto3
설치를 하셨다면 pip freeze로 requirements.txt를 업데이트해주세요.
다음 handler.py 내용을 수정하겠습니다. 아래의 코드로 수정해주세요.
import cv2 import numpy as np import boto3 import requests import json BUCKET = <자신이 연동할 bucket명> S3 = boto3.resource("s3") AWS_URL = f"https://{BUCKET}.s3.ap-northeast-2.amazonaws.com/" def upload_s3(data, img_ext): img_key = f"refine.{img_ext}" aws_url = AWS_URL + f"refine.{img_ext}" obj = S3.Object( bucket_name=BUCKET, key=img_key ) obj.put(Body=data, ContentType="image/png") return aws_url def process_img(img_url, img_ext): # read image resp = requests.get(img_url) encoded_img = np.frombuffer(resp.content, dtype=np.uint8) img = cv2.imdecode(encoded_img, cv2.IMREAD_COLOR) # process gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # image to byte processed_img = cv2.imencode("."+img_ext, gray_img)[1].tobytes() return processed_img def hello(event, context): data = json.loads(event["body"]) img_ext = data["extension"] img_url = data["url"] processed_img = process_img(img_url=img_url, img_ext=img_ext) aws_url = upload_s3(data=processed_img, img_ext=img_ext) response = { "statusCode": 200, "body": aws_url } return response
serverless yml파일을 수정합니다. hello 함수에 대해 호출 방식을 get에서 post로 바꿔주세요.
functions: hello: handler: handler.hello events: - http: path: "" method: post
준비는 다 끝났습니다. sls deploy를 하여 다시 API를 생성합시다.
> sls deploy
생성된 API url을 postman을 통해 테스트해봅시다.
코드를 보면 아시겠지만,
보내줘야할 json 값은 두 개 입니다.
이미지의 URL과 확장자입니다.
아래의 사진처럼 보내줘 봅시다. api url은 생성된 url을 입력하시면 됩니다.
문제 없으면 이미지 링크를 하나 반환해줍니다.
저는 우주 이미지 링크를 구글에서 찾아서 가져왔습니다. 결과를 확인해보죠.
회색으로 잘 보정된 것을 확인할 수 있습니다. opencv가 aws lambda 상에서 잘 돌아가네요.
정리한 내용은 여기까지 입니다.
다음에도 서버리스 관련 괜찮은 정보가 있으면 포스팅하겠습니다.
댓글
댓글 쓰기