티스토리 뷰

Cloud Run, a managed Knative service, is GA

 

2019년 11월 GA 된 Cloud Run에 대한 이야기를 해볼까 합니다. GCP에는 이미 서버리스에 한축을 담당하는 Cloud Functions가 GA 되었음에도 여전히 부족한 게 사실입니다. 자체적으로는 꽤 쓸만할지 모르겠으나 타사의 서버리스를 사용해본 유저라면 여러 가지로 불편한 게 사실이죠. 더욱이 앞질러가고 있는 서비스(이를테면 AWS)를 흉내 내는 듯한 모양새는 어쩔 수 없는 후발주자의 모습이었습니다. - 관련 글 : Cloud Functions, 이래서 베타다

그러던중 Cloud Next 2019에서 Cloud Run이 소개되었고 서버리스의 새로운 장을 여는 느낌이었습니다. 컨테이너 기반으로 동작하기 때문에 이식성이 좋고 기존에 도커와 같은 툴에 익숙하다면 진입장벽이 한없이 낮습니다. Cloud Functions도 내부를 들여다보면 결국 컨테이너지만 사용자 입장에서는 그것이 컨테이너 기반인지 뭔지 알 필요가 없었습니다. 말 그대로 함수 단위로 등록해서 사용하는 느낌이니까요. 반면 Cloud Run은 동작시키기 위해서는 컨테이너 환경이라는 점을 알아야 합니다. Dockerfile을 생성하고 docker image를 빌드해야 하기 때문이죠. 즉, 환경에 상관없이 동작시킬 수 있다는 의미입니다. 물론 로컬 환경에서도 docker image로 돌려보는 게 가능합니다. 관련된 설정도 꽤 직관적입니다. 아래에서 차차 살펴보도록 하죠


 

아래와 같이 GCP 콘솔에 접속해서 CREATE SERVICE를 통해 Cloud Run을 생성하는 것도 가능하지만 여기서는 CLI 터미널 위주로 확인해봅니다. 

Cloud Run

 

터미널의 작업은 크게 1) 코드 작성 2) Cloud Build 3) deploy 이렇게 세 단계로 나뉩니다. 이건 서버리스 모든 제품군에 동일하게 적용이 되는 단계입니다.

 

# 1) 코드 작성

공식적으로 Node.js, Go, Python, Java 등 도커 컨테이너로 돌릴 수 있는 모든 언어를 지원하는데 Python 예제를 보도록 합니다. 예제를 돌리기 위해 새로운 디렉터리를 생성하고 작업을 해줍니다. 이렇게 하는 이유는 디렉터리를 통째로 컨테이너 이미지로 만들게 되기 때문입니다. 혹시 디렉터리에 다른 파일이 섞이지 않도록 주의해 줍니다. 자, 아래는 Flask 기반의 Python 예제 코드입니다.

# app.py
import os

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    target = os.environ.get('TARGET', 'World')
    return 'Hello {}!\n'.format(target)

if __name__ == "__main__":
    app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))

내용은 흔한 Flask 샘플과 같습니다. 8080 포트를 열고 '/'로 접속하는 경우 응답하게 됩니다. 이 예제 코드를 두고 컨테이너로 만들기 위해 아래와 같이 Dockerfile을 생성해 줍니다.

# Use the official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.7-slim

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

# Install production dependencies.
RUN pip install Flask gunicorn

# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app

이 Dockerfile은 앞으로 어떤 Cloud run을 생성하던 대부분 비슷한 모양새로 만들어지게 될겁니다. 이제 두 가지 부분만 신경 써주면 되는데 디펜던시를 추가해주는 RUN 부분과 컨테이너가 올라올 때 실행되는 명령어인 CMD 부분입니다. gunicorn을 통해 멀티 프로세스로 동작시켜주도록 합시다. 다음으로는 .dockerignore 파일을 아래와 같은 내용을 담아 생성해줍니다. 도커 이미지 파일의 크기는 저장소 요금과도 직결되므로 애플리케이션을 실행하는데 필요 없는 파일은 모두 담아주도록 합니다.

Dockerfile
README.md
*.pyc
*.pyo
*.pyd
__pycache__

이제 코드 작성은 끝났습니다.

 

# 2) Cloud Build

아래와 같이 한 줄 명령어면 Cloud Build는 끝입니다. PROJECT-ID 부분을 본인 프로젝트 아이디로 변경해주고, helloworld 부분을 적당한 이름으로 변경해주면 됩니다. 명령어가 실행되면 도커 이미지가 Google Container Registry에 등록됩니다. 혹시 gcloud 명령어가 로컬에 없거나 뭔지 모르신다면 여기 글을 참고하세요. 

gcloud builds submit --tag gcr.io/PROJECT-ID/helloworld

 

# 3) deploy

이제 생성한 이미지를 Cloud Run에 배포해 봅시다. 마찬가지로 PROJECT-IDhelloworld 부분을 변경해서 진행해 줍니다.  

gcloud run deploy --image gcr.io/PROJECT-ID/helloworld --platform managed

배포할 때 보면 리전을 선택하는 게 나오는데 Cloud Run은 현재(2020-07-05) 아시아 기준으로 타이완(asia-east1)과 도쿄(asia-northeast1)에만 제공됩니다. platform을 managed로 지정한 것은 Anthos를 통해 배포가 가능한데 아래 그림을 보시죠. Cloud Run을 배포하는 두 가지 방식인 Cloud Run for Anthos와 managed에 대해 보여주고 있는데 상당히 직관적입니다. 우리가 지금 진행하고 있는 배포 방식은 CLI가 되는 거고, GCP 콘솔을 통한 UI 방식과 YAML 방식도 제공되는 것을 알 수 있습니다.

https://cloud.google.com/blog/products/serverless/new-features-in-cloud-run-for-anthos-ga

 

# 확인

배포가 완료되면 Cloud Run의 End point 주소를 알 수 있습니다. 이 주소를 직접 사용하는 것도 가능하지만 manage custom domains를 사용도 가능합니다. 엔드포인트 주소가 외부(사용자)에게 직접 노출되는 경우에는 custom domains을 사용하는 게 좋겠죠. 

엔드포인트 주소

 

정상적으로 호출되는 것이 확인 됩니다. 기본적으로 제공되는 엔드포인트 주소는 역시 내부 주소용으로 사용하는 게 좋겠습니다.

 

# 관리

이렇게 배포가 끝나면 몇 가지 신경써줘야 하는 관리 포인트가 있습니다. 첫 번째는 GCR 이미지 정리 입니다. 

클라우드 빌드를 돌리다보면 이미지가 쌓인다

 

이 이미지들도 결국 저장소 용량을 차지하기 때문에 비용이 지불됩니다. 그렇기 때문에 사용하지 않는 이미지는 주기적으로 제거해주는 것이 좋습니다. 

두 번째는 Cloud Run의 스펙 변경입니다. 기본적으로 아래와 같은 설정이 되어 있을 텐데 사용하는 애플리케이션의 종류에 따라 Capacity를 변경해줘야 합니다. 메모리가 큰 작업이 필요하다면 메모리 할당량을 올려줘야 할 것이고 컨테이너 당 처리할 수 있는 요청 개수의 제한을 둬야 할 수도 있습니다. 여기서 또 알 수 있는 정보는 스케일링이 0부터 확장됩니다. 즉, App Engine과는 다르게 호출이 있을 때만 컨테이너를 올려서 응답을 한다는 것이죠. 그렇기 때문에 Cold Start[1] 개념이 존재합니다.

Capacity 조정

 

세 번째는 트래픽 제어 입니다. 배포 버전이 잘못되었거나 의도적으로 트래픽을 분할해야 하는 경우에 사용하면 좋습니다. 가령 새로운 버전을 배포하기 전에 일부 트래픽만 신규 버전으로 흘려보내서 정상적으로 애플리케이션이 동작하는지 확인하는 용도로 사용 가능합니다.

Manage traffic

 

네 번째는 권한 관리입니다. 엔드포인트 주소를 퍼블릭하게 열지, 아니면 인프라 내부적으로만 사용할지 결정할 수 있습니다. 아래와 같이 allUsers에게 Cloud Run Invocker 권한을 주면 엔드포인트는 퍼블릭하게 접근 가능한 주소가 됩니다.

권한 조정

 

다섯 번째는 로깅입니다. 서버리스를 사용할 때 주의해야 하는 것이 있는데 의도치 않은 재귀 호출입니다. 코드 상의 결함일 수도 있고, 연결되어 있는 서비스에 정상적인 response를 주지 못해서 재호출이 일어날 수도 있죠. 서버리스의 비용 체계는 호출과 러닝타임으로 계산되는데 재귀가 동작하게 되면 요금폭탄을 피할 수 없게 됩니다. 그래서 테스트할 때 꼭 지속적인 모니터링을 해주고 리커시브 하게 동작하는 경우에 대한 대비를 해줘야겠습니다.

로그 관리

다른 서비스의 로그와 연계해서 보려면 Stackdriver를 통해 확인하면 됩니다. 

 

# 마무리

이번에 상용 프로덕트에 Cloud Run을 통해 두 개의 서비스 API를 출시했는데 결론은 1) 편하다 2) 강력하다. 두 가지로 정리되었습니다. 신규 서비스(Cloud Run)를 처음 진입하는 입장으로 봤을 때 "이론상 이렇게 동작하겠지"하는 부분에서 벗어나는 것 없이 정확하게 동작해줬고 우려했던 Cold Start는 Cloud Functions에 비해 훨씬 적었습니다. 아래 글을 통해 Cloud Run과 Cloud Functions의 Cold Start에 대해 더 깊게 알아볼 수 있습니다. GCP Cloud Run VS Cloud Function Cold Starts

Anthos를 통해 GKE에 배포도 가능하지만 managed로 구글 인프라를 사용해서 컨테이너 기반의 운영을 할 수 있다는 건 정말 매력적인 요소입니다. K8S에 진입장벽이 조금이라도 느껴지는 사용자들, 그리고 Cloud Functions에 지쳐있는 사용자들에게 Cloud Run은 서버리스의 맏형으로서 큰 역할을 해줄 것으로 예상되네요 :-) 

Note: 제일 늦게 출시된 서버리스 Cloud Run을 맏형이라고 부른건 한국 우스갯 소리에 있는 "돈 많으면 형"과 같은 느낌의 표현입니다. Game Changer가 될지는 앞으로 계속 더 지켜봐야겠습니다.


[1] : 일정시간 트래픽이 없으면 컨테이너를 종료하고, 트래픽이 들어오면 컨테이너를 올려서 정의된 명령을 수행합니다. 내려가있던 컨테이너가 다시 올라오는데 걸리는 시간을 콜드 스타트라고 부릅니다. 트래픽이 어느정도 시간까지 없을 때 컨테이너를 내린다는 부분은 공식적으로 정의된 부분은 없습니다. 한편 콜드 스타트의 반대말은 웜 스타트(warm start)라고 부릅니다.

댓글
댓글쓰기 폼