ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 서버리스로 이미지 컨텐츠 서비스하기
    Infra/ncloud 2023. 8. 4. 00:55

     Ncloud의 Cloud Functions와 API Gateway 서비스를 이용해서 json 뿐만 아니라 html, svg 등 다른 웹 컨텐츠를 무료로 응답하는 서버리스 환경을 구축할 수 있을까? 인터넷에 검색하면 Cloud Functions와 API Gateway를 통해 action을 생성하고 URL을 만들어 JSON 형식으로 응답을 받아오는 방법에 대한 가이드는 많았지만 일반 액션이 아닌 웹 액션에 대한 사용방법과 가이드는 부족해서 뭔가 굉장히 아쉬움이 많았는데요.

     

     마침 제가 토이 프로젝트로 진행해오던 간단한 파이썬 애플리케이션 서버를 이전하려는 계획을 갖고 있었는데 이번 기회에 Ncloud의 Cloud Functions와 API Gateway를 활용하여 이미지 컨텐츠를 제공하는 서버를 서버리스로 구축해 보면 재밌을 것 같아서 도전해 보았습니다.

     

     간단할거라 생각했는데 실제로 직접 구축해 보니 생각보다 많은 시간을 쏟게 되어서 이 글을 통해 많은 분들의 시간을 절약할 수 있었으면 좋겠다는 생각에 글을 작성하게 되었습니다. 과연 html과 이미지 등의 컨텐츠는 어떤 식으로 응답해야 하는 걸까요? 🤔

     

    *웹 액션 : 웹 형식의 Response를 구성할 수 있는 Cloud Functions의 액션 서비스, Ncloud Functions는 기본 액션과 웹 액션 중에서 선택하여 구성할 수 있다.

     


     

    🚀 서버리스를 사용하게 된 배경

     먼저 서버리스 구축에 사용할 파이썬 애플리케이션을 잠깐 소개해드리자면 notion의 달력 view를 이미지로 만들어서 응답해주는 간단한 파이썬 프로젝트입니다.(이하 notion2svg) 이 프로젝트를 진행하면서 마음속 한편으로 계속 문제라고 생각해 왔던 것 중 하나가 바로 서버 응답속도 였는데요.

     

     달력을 이미지로 만들어야 하는 동적 이미지 생성 처리 기능 때문에 서버 응답 속도가 굉장히 중요한데 여태까지 써왔던 서버들은 전부 해외 서버(헤로꾸, Koyeb) 였기 때문에 원치 않는 딜레이가 발생할 때가 굉장히 많았고 심지어 이미지가 깨질 때도 정말 많았습니다.🥹 (대신 헤로꾸와 Koyeb은 github 연동으로 SaaS 제품을 간편하게 배포할 수 있다는 게 장점입니다.)

     

     그래서 헤로꾸, Koyeb에 이어 또다시 세 번째 서버 이전을 결단하게 되었고 확실한 국내 리전을 보유하고 있고 커뮤니티도 잘 형성되어 있는 대규모 클라우드 플랫폼 기업인 네이버클라우드의 NCloud 서비스들을 사용해 보기로 하였습니다.

     

     원래 처음 계획했던 것은 제일 작은 인스턴스인 micro 서버를 구매하여 서버 비용을 아끼려고 했으나 가만 생각해보니 서버리스로 구현하면 더 싸게 사용할 수 있지 않을까? 하는 생각이 문득 들어서 Ncloud 제품 중에 Cloud Functions와 API Gateway를 활용하여 저의 애플리케이션 notion2svg를 배포하기로 결정하였습니다.

     

    *micro 서버 : 1vCPU, 1GB Mem, 50GB Disk [g1] : NCloud에서 가장 싸게 구매할 수 있는 깡통 서버

     


     

    ✍🏻 Cloud Functions 와 API Gateway 개념 짚고 넘어가기

     앞서 제가 계속 Cloud Functions와 API Gateway를 언급했지만 그래서 도대체 Cloud Functions와 API Gateway가 뭔데!라는 말이 하고 싶을 수도 있을 것 같은데요😅 각 서비스에 대한 설명은 네이버클라우드 공식 홈페이지에 잘 설명이 되어있기 때문에 이곳에서는 간단하게만 짚고 넘어가도록 하겠습니다.

     

    Cloud Functions

    서버 관리나 프로비저닝 필요 없이 비즈니스 로직을 실행할 수 있는 서비스
    * 프로비저닝 : IT 인프라를 생성하고 설정하는 프로세스

     

    API Gateway

    HTTP Request 요청을 받을 리소스 및 메서드를 생성하고, 비즈니스 로직 결과를 처리할 Cloud Functions - Action과 연결시켜 서버리스 환경을 구성하는 서비스.

     

     Cloud Functions를 사용하면 클라우드 환경에서 코드가 실행된 만큼만 과금되기 때문에 효율적인 운영이 가능하며, 갑작스러운 요청에도 유연하게 대처할 수 있다는 장점이 있고 API Gateway를 사용하면 호출량을 제한하여 과도한 트래픽이 인입되는 것을 제한하거나 캐시를 설정하여 백엔드 서비스로 인입되는 트래픽을 제어할 수 있다는 장점이 있습니다.

     

     아마 국내에서 B2C 사업을 하는 수 많은 기업들이나 학생분들이 공부용으로 AWS를 사용하는 것으로 알고 있는데요. Ncloud의 Cloud Functions는 AWS의 람다(lambda)를 생각하시면 조금 더 친숙하게 접근하실 수 있으실 것 같습니다.

     


     

    🧩 Github의 Python 소스코드를 Cloud Functions에 맞게 변경하기

     그럼 이제 실제로 서버리스 환경을 구축해보겠습니다. Cloud Functions에 파이썬 프로젝트를 올리려면 프로젝트 구조를 전반적으로 수정해야 합니다. 기존에 헤로꾸나 Koyeb을 사용하셨던 분들이라면 github 연동만 해놓으면 간편하게 빌드, 배포할 수 있었기 때문에 이 점은 조금 불편하게 작용하실 수 있을 것 같습니다.

     

    # 로컬 또는 Server-based 패키지 구조
    
    |- notion2svg
      |- app
        |- modules
          |- __init__.py
          |- calendar.py   # 캘린더 모듈
          |- notion_api.py # 노션 API 모듈
        |- main.py         # 메인함수
      |- .gitignore
      |- LICENSE
      |- Procfile
      |- README.md
      |- requirements.txt

     

     

    # Ncloud Cloud Functions 에 배포하기 위해 커스텀한 패키지 구조
    
    |- project.zip
      |- __main__.py            # 메인함수
      |- modules
          |- __init__.py
          |- calendar.py        # 캘린더 모듈
          |- notion_api.py      # 노션 API 모듈
      |- requirements.txt

     

    Ncloud Cloud Functions에 프로젝트를 올리기 위해서는 깃허브에 올려놓은 파일구조에서 불필요한 파일들은 전부 제거하고 main함수와 필요한 모듈만을 따로 빼서 zip파일로 묶어야 합니다. 특히 메인함수의 파일명을 반드시 __main__.py로 만들어야 합니다.

     

    추가로 웹에서 다운받아야 하는 모듈들 역시 자동으로 설치가 안되기 때문에 별도로 받아서 zip파일로 함께 묶어야 하는데 수동으로 작업하면 굉장히 불편하므로 docker를 이용해서 빌드하면 비교적 간편하게 작업할 수 있습니다.

     


     

    🚨 Docker로 Python 프로젝트 빌드시 주의사항

     저는 Windows 환경에서 작업했기 때문에 도커데스크톱을 설치해서 cmd에서 도커를 사용할 수 있도록 환경을 구축했는데요. 공식가이드 문서에 나와있는 Window에서 Docker 명령어 사용가이드에 몇 가지 문제가 있어서 정리해드리려고 합니다.

     

    네이버클라우드 가이드문서에 나와있는 도커를 활용한 빌드 가이드

     

    1. VM 마운트시 윈도우 경로 오류
    docker: Error response from daemon: \\\\C%!(EXTRA string=is not a valid Windows path).
    

    해당 오류는 윈도우 파일 경로를 잘못 표기하여 발생하는 오류로 다음과 같이 경로를 수정해 주면 됩니다.

    docker run --rm -v "C:/WorkingDir:/data" python:3.7.4-slim-buster /bin/sh
    

     

    2. 싱글코테이션 오류

    workdir: 1: workdir: Syntax error: Unterminated quoted string
    

    해당 오류는 컨테이너 내에서 실행할 명령어를 지정할 때 싱글 코테이션 사용 시 일종의 버그가 있는 것 같습니다. 따라서 다음과 같이 더블 코테이션을 사용하면 가능합니다.

    docker run --rm -v C:/WorkingDir:/data python:3.7.4-slim-buster /bin/sh -c "mkdir workdir"
    

     

     

    3. tmp 폴더 마운트 오류

    Get:1 <http://deb.debian.org/debian> buster InRelease [122 kB]
    Get:2 <http://security.debian.org/debian-security> buster/updates InRelease [34.8 kB]
    Get:3 <http://deb.debian.org/debian> buster-updates InRelease [56.6 kB]
    Reading package lists...
    E: Unable to determine file size for fd 9 - fstat (2: No such file or directory)
    E: The repository '<http://security.debian.org/debian-security> buster/updates InRelease' provides only weak security information.
    E: Unable to determine file size for fd 9 - fstat (2: No such file or directory)
    E: The repository '<http://deb.debian.org/debian> buster InRelease' provides only weak security information.
    E: Unable to determine file size for fd 9 - fstat (2: No such file or directory)
    E: The repository '<http://deb.debian.org/debian> buster-updates InRelease' provides only weak security information.
    

    이건 저도 정말 고생해서 찾았는데요.🥹 python 공식 이미지가 debian 서버를 사용하는데 /tmp 폴더를 마운트 하면 apt-update를 진행할 수 없다는 이슈가 있었네요. 그래서 다음과 같이 /tmp 폴더를 /data로 변경하셔서 사용하시면 됩니다.

     

     버그가 많아서 조금 장황하고 길게 쓰긴 했지만 최종적으로는 아래 명령어를 복사해서 사용하시면 됩니다. docker 버전이나 파이썬 이미지에 따라 명령어가 계속해서 달라질 수 있기 때문에 아래 명령어도 언젠가는 안될 수도 있습니다. 그럴 때를 대비해 항상 계속해서 공식 홈페이지의 가이드를 주시해 주시면 좋을 것 같습니다.

     

    docker run --rm -v "C:/WorkingDir:/data" python:3.7.4-slim-buster /bin/sh -c "mkdir workdir && cp -R /data/* /workdir/ && rm -rf /workdir/virtualenv && pip install virtualenv && apt -y update && apt -y install zip && cd /workdir && virtualenv virtualenv && . virtualenv/bin/activate && pip install -r requirements.txt && zip -r /data/project.zip ./*"

     

    ❗주의할 점

    이건 버그는 아니고 주의하셔야 하는 부분이 있어서 가이드 드립니다. 위의 명령어를 계속 실행하면 WorkingDir 폴더 안에 project.zip이 사라지지 않고 계속 중첩됩니다. 그러면 계속 프로젝트 사이즈가 점점 커지게 되는데 이 프로젝트를 Cloud Functions에 그대로 넣을 경우 페이지 속도도 엄청 느려지게 되고 메모리도 많이 잡아먹기 때문에 항상 조심하셔야 합니다.(왜 오류가 안나는지는 의문) 따라서 docker 명령어를 실행하기 전에 WorkingDir 폴더 내의 project.zip 파일을 필수로 제거해 준 뒤 실행하셔야 합니다.

     


     

    🪚 python 메인 함수 뜯어고치기

     이제 docker를 통해 project.zip파일을 받았으니 Cloud Functions에 배포해야할 차례인데요. 배포에 한 번에 성공할 수도 있겠지만 실상은 갖가지 오류가 발생할 수 있습니다. 저 역시도 로그를 통해 많은 버그들을 검출해 낼 수 있었는데요.

     

    Cloud Functions에서 Action에 대한 대시보드와 로그를 확인할 수 있다.

     

     그 중 가장 말썽이었던 녀석은 역시나 메인 함수 였습니다. 저는 코드를 아래와 같이 변경하여 프로젝트를 빌드할 수 있었습니다. 코드를 보며 하나씩 자세히 설명해 드리도록 하겠습니다.

     

    # Heroku, Koyeb에서 사용하는 Main 함수
    
    from dotenv import load_dotenv
    import uvicorn
    
    from fastapi import FastAPI, Response
    
    from app.modules.calendar import NotionCalendar
    
    load_dotenv()
    
    app = FastAPI()
    cal = NotionCalendar()
    
    
    @app.get('/')
    def show_calendar():
        return Response(content=cal.get_calendar(), headers={"Cache-Control": "max-age=0"}, media_type="image/svg+xml")
    
    
    if __name__ == '__main__':
        uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True, debug=True)

     

    # Ncloud Cloud Functions에서 사용하는 Main 함수
    
    import base64
    
    import notion_calendar
    
    cal = notion_calendar.NotionCalendar()
    
    def main(args):
    
        body = cal.get_calendar(args.get("db"), args.get("auth"))
        encodedBody = base64.b64encode(body.encode('utf-8'))
    
        return {
            "statusCode": 200,
            "headers": { "Content-Type": "image/svg+xml" , "Cache-Control": "max-age=0"},
            "body": encodedBody.decode('utf-8'),
        }

     

     먼저 함수는 반드시 def main(args):로 정의해야 합니다. args도 필수로 받도록 해야지 오류 없이 배포가 진행됩니다. args는 CloudFunctions에서 기본 정보에 있는 디폴트 파라미터(JSON)에 해당하는 값인데요. 반드시 다음과 같이 JSON 형태로 만들어야 합니다.

     

    Cloud Functions 디폴트 파라미터

     

     db와 auth는 제가 노션에서 발급받은 API KEY인데요. 프로젝트 zip파일에 넣고 함께 배포하면 유출 가능성이 있지 않을까 생각했는데 마침 디폴트 파라미터라는 게 있어서 민감정보인 키 관리를 편하게 할 수 있어서 좋았습니다. 그리고 추 후에 API키가 변경되더라도 소스코드를 재 업로드 하지 않아도 된다는 점이 유지보수 측면에서도 좋았습니다.

     

    다시 코드로 돌아와서 두 코드를 비교해보면 헤로꾸와 Koyeb에서 사용했던 코드에서는 uvicorn과 FastAPI 모듈을 import 해서 직접 웹 서비스 환경을 구성한 반면 Cloud Functions에서는 main 함수의 return 값으로 HTTP request를 JSON 형태로 보내주기만 하면 웹 액션이 알아서 웹 서비스 기능들을 처리해 줍니다. 불필요한 모듈들을 설치하지 않고 그만큼 프로젝트 사이즈를 줄일 수 있어서 이런 부분들은 정말 편리했습니다. 그리고 코드가 더 깔끔해진다는 점에서 굉장히 만족스러웠습니다.

     

    하지만 다소 아쉬운 면들도 조금씩 있었습니다. 첫 번째로는 FastAPI의 Response 객체 같은 경우 별도의 인코딩 없이도 알아서 Body내용을 해석해 Content-Type을 잡아줬는데 NCloud의 Cloud Functions는 Content-Type을 제대로 잡지 못해서 버그 찾기가 다소 힘들었습니다. 예를 들어 svg 포맷을 인코딩 작업 없이 문자열 그대로 보내는 경우 Content-Type이 갈피를 못 잡고 계속 application/json으로 응답하는 현상이 있었습니다.

     

    두 번째로는 제가 calendar.py라는 네이밍을 모듈로 사용하고 있었는데 파이썬 내장 라이브러리 이름과 충돌하여 배포시 오류가 발생했습니다. 이런 현상은 로컬에서 테스트할 때나 헤로꾸, Koyeb에서는 나타나지 않았기 때문에 많이 당황했었습니다.

     

    세 번째로는 형상관리를 따로 해야된다는점도 단점으로 작용할 것 같습니다. Ncloud Cloud Functions에서만 사용할 수 있는 소스코드라서 어딘가에 저장해 놓기가 애매한 상황이 생기는 건 조금 아쉬움이 남습니다.

     


     

    🚪 API Gateway 구축하기

    Cloud Functions에 프로젝트를 성공적으로 배포하고 나면 이제는 프로젝트의 웹 서비스에 접속할 수 있는 URL을 만들어야 합니다. Ncloud에서는 AWS에서와 동일하게 API Gateway라는 네이밍의 서비스를 제공하고 있고 이를 통해 외부에 프로젝트를 공개할 수 있게 됩니다.

     

    Cloud Functions 액션에서 아래 이미지와 같이 [외부 연결 주소 생성]이라는 버튼이 있어 해당 버튼을 클릭하면 자연스럽게 API Gateway로 이동되도록 만들 수 있습니다. 아니면 API Gateway 서비스에서 직접 만드셔도 됩니다. 저는 경로에 일종의 hash값(API ID)이 붙는 것이 싫어서 그냥 직접 만들었습니다.

     

    API Gateway 설정방법

     

    API Gateway를 통해 API를 생성할 때 웹 액션이랑 연동시 반드시 {type+} 경로를 사용해야 합니다. {type+}은 일종의 Path Variable 같은 건데 Cloud Functions의 Web Action을 사용하는 경우 {type} 변수는 예약어로 처리되어 Content extensions을 정의합니다.

     

    type에는 /json, /http, /svg, /html,/text 다섯가지 중에서 사용할 수 있습니다. 하지만 이 다섯 가지 endpoint를 그냥 붙인다고 해서 사용할 수 있는 게 아니라 서버에서 제대로 된 응답을 받으려면 Cloud Functions 웹 액션에서 return값으로 지정한 header의 Content-Type 값과 Body값이 type에 맞는 Content-Type을 가지는지 확인해야 합니다.

     

    문제는 Content-Type만 지정하면 원하는 type으로 응답이 안온다는 점입니다. 저 같은 경우에는 svg로 리턴을 해야 되는 상황이었는데요. 앞서 Cloud Functions에서도 언급했지만 Request Body를 svg 포맷 String으로 바로 넘기면 인식이 안되고 Base64 인코딩을 해야지 /http 엔드포인트를 통해 이미지 컨텐츠를 서비스할 수 있었습니다.

     

    한 가지 더 아쉬웠던 점은 /svg 엔드포인트를 사용하고 싶었는데 Cloud Functions나 API Gateway 어딘가에서 정확한 인식을 못하는지 잘 안되었습니다. 공식 가이드에 따르면 Akka Http 프레임 워크를 사용해서 바이너리와 일반 텍스트 Content-Type을 판단한다고 나와있는데 참고하시면 좋을 것 같습니다.

     


     

    📖 Ncloud 가이드 문서 활용방법

    아마 많은 분들이 아래의 가이드센터의 문서를 통해 서버리스 서비스를 구축 하셨을 것 같은데요. 가이드가 잘 나와있기 때문에 그대로 따라 하시면 문제없이 잘 동작하는 서버리스 서비스를 구축할 수 있지만 한 가지 아쉬웠던 점은 진행 순서에 대해서만 가이드되어있기 때문에 언뜻 보고 서비스를 제대로 활용하기는 어려웠습니다.

     

     

    NAVER CLOUD PLATFORM

    cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification

    www.ncloud.com

     

    저도 처음에 진행 순서에 따라 url에 {type+}를 넣고 json을 입력했는데 “어 나는 json 안쓰고 svg리턴해야 하는데..” 의심은 들었지만 일단 시키는 대로 따라 했으나 역시나 다를까 제 상황에 맞는 가이드가 아니었기 때문에 배포 오류가 발생하였습니다.

     

    그래서 결국 type이 뭔지, 왜 json을 넣어야 하는지에 대해서 알아야 했는데 관련 정보는 나와있지 않기 때문에 가이드 문서를 한참 헤매다가 찾을 수 있었는데요. type 개념에 대한 정보는 아래의 글들을 통해 확인할 수 있었습니다.

     

    https://guide.ncloud-docs.com/docs/apigw-myproducts

    https://guide.ncloud-docs.com/docs/cloudfunctions-run-classic

    https://guide.ncloud-docs.com/docs/cloudfunctions-webaction-classic

     

    여기저기 흩어져 있어서 찾기 다소 힘들었는데 만약 type에 링크가 걸려있었거나 했으면 좀 더 좋지 않았을까 하는 아쉬움이 들기는 하였습니다. 만약에 Ncloud 서비스를 이용하여 서버리스를 구축하고자 한다면 첫 번째로 가이드 센터를 통해 사용방법을 익히고 ncloud-docs에서 부족한 정보를 찾아서 공부하시면 도움이 많이 될 것 같습니다.😉

     


     

    🌠 회고하기

    동적 이미지 컨첸츠를 제공하는 서버리스 구축이라는 생소한(?) 시도 덕분에 생각보다 많은 시간을 허비했지만 정말 많이 배웠고 뜻깊은 시간이었습니다. 그래도 정말 좋았던 점은 제가 목표했던 서버 응답속도 개선과 비용면에서 어느 정도 효과가 있었다는 점입니다.

     

    Koyeb과 Ncloud Cloud Functions 응답속도 비교

     

    Ncloud 서버리스 사용 결과 거의 2배 정도의 응답시간을 줄일 수 있는 퍼포먼스를 냈고 추가적으로 트래픽이 거의 없으므로(이건 좀 슬픈가..) 과금도 들지 않는다는 점이 좋았습니다. API Gateway는 1,000,000건 이하까지는 무료이니 과금이 들면 드는 대로 기분이 좋을 것 같습니다.

     

    Cloud Functions 사용 시 자주 사용되지 않는 액션의 경우 컨테이너가 cold 상태로 전환된다고 하는데 해당 제한조건을 테스트해보지는 못해서 그건 조금 아쉬움으로 많이 남는 것 같습니다. 서버리스 구축하는 게 생각보다 재밌었기 때문에 파이썬뿐만 아니라 java를 통한 jar배포도 해보고 싶어서 기회가 된다면 다음 컨텐츠에서 한번 다뤄보도록 하겠습니다.

    댓글

Designed by Tistory.