익숙해서 Azure Functions 를 사용하려고 했는데, Google Cloud Functions 를 사용하는 것도 간단하고 괜찮은 것 같아서 한번 해봤다.
Google Cloud Functions 사용은 콘솔에서 하는 것도 간단하고 편하게 되어 있지만, 하는 김에 최대한 Visual Studio Code 에서 처리하는 방식으로 해보자.
일단, Visual Studio Code 에서 "Cloud Code" extension을 먼저 설치한다.
Google Cloud SDK 가 설치되면 하단의 "Connect to Google Cloud" 를 클릭해서 Google Cloud 계정에 로그인한다.
Google Cloud 계정에 로그인이 끝나면 왼쪽 메뉴에 프로젝트 선택, function 생성 버튼이 나타난다.
"Select a project" 버튼을 누른 후에 미리 만들어놓은 프로젝트를 선택하거나 새 프로젝트를 만든다.
(Cloud Functions를 사용하기 위해서는 무료 범위에서 사용하더라도 결제 계정을 등록해두어야 한다.)
클라우드 콘솔에서 미리 만들어두었던 프로젝트를 선택하면 Cloud Functions 에 필요한 API를 승인해야 한다는 안내가 나오는데, enable 해주면 된다.
하지만 내 경우에는 이것을 눌러도 반응이 없었다.
이럴 때는 https://cloud.google.com/functions/docs/create-deploy-gcloud 페이지에서 "API 사용 설정" 을 클릭해서 클라우드 콘솔에서 설정하면 된다.
VSCode에서 refresh 하면 짜잔
VSCode에서 Google Cloud Functions 를 사용할 준비가 되었다.
"Click here set up your currentworkspace for Cloud Function development!" 를 클릭해보자.
이름을 넣고, GEN_2 를 선택한다. GEN_1, GEN_2 는 Cloud Function 의 세대. GEN_1, GEN_2 차이는 https://cloud.google.com/functions/docs/concepts/version-comparison?hl=ko 참조.
entry point 는 실행될 function name이다. "webhook" 정도로 넣어보자.
이렇게 하면 로컬에 workspace가 생성되지만, 사실 .vscode/launch.json 파일만 생긴다. 기본적인 파일들을 만들어보자.
<./vscode/launch.json>
{
"configurations": [
{
"name": "Deploy Cloud Function",
"type": "cloudcode.cloudfunctions",
"request": "launch",
"functionName": "bot46",
"gen": "GEN_2",
"revision": "",
"entryPoint": "webhook",
"testInput": ""
},
{
"name": "Python functions_framework",
"type": "python",
"request": "launch",
"module": "functions_framework",
"args": [
"--target",
"webhook",
"--debug"
],
"env": {
"TELEGRAM_TOKEN": "REPLACE YOUR TELEGRAM TOKEN"
}
}
]
}
기본적으로 만들어지는 "Deploy Cloud Function" 다음에 "Python functions_framework" 를 추가했다.
functions-framework 패키지를 이용해서 로컬에서 실행하기 위한 설정이고, --target=webhook 부분은 main.py 의 webhook() 함수를 실행하도록 설정한다. "env" 부분에서 os.environ["TELEGRAM_TOKEN"] 으로 불러올 수 있는 텔레그램 토큰을 설정한다.
<main.py>
# main.py
import os
import telegram
import asyncio
async def echo(request):
bot = telegram.Bot(token=os.environ["TELEGRAM_TOKEN"])
if request.method == "POST":
update = telegram.Update.de_json(request.get_json(force=True), bot)
chat_id = update.message.chat.id
# Reply with the same message
await bot.sendMessage(chat_id=chat_id, text=update.message.text)
return "ok"
def webhook(request):
return asyncio.run(echo(request))
특별히 지정하지 않으면 main.py 에서 시작점을 찾는다. HTTP request가 들어오면 webhook 이 실행되고, python-telegram-bot 의 sendMessage 를 async로 실행하기 위해 echo() 를 async로 분리하고 asyncio.run()으로 실행한다.
<requirements.txt>
functions-framework
python-telegram-bot
requirements.txt 에는 기본적으로 필요한 두 개의 패키지 functions-framework, python-telegram-bot 만 명시한다.
추가한 launch configuration 으로 실행하면 아래와 같은 메시지가 나오면서 로컬 웹서버가 실행된다.
* Serving Flask app 'webhook'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:8080
* Running on http://10.65.93.193:8080
Press CTRL+C to quit
* Restarting with watchdog (fsevents)
* Debugger is active!
* Debugger PIN: 534-746-393
127.0.0.1 - - [02/Feb/2023 15:33:29] "GET / HTTP/1.1" 200 -
http://127.0.0.1:8080 을 브라우저에서 호출해서 OK 가 나오면 일단 로컬에서 돌아가도록 잘 설정된 것이다.
"Deploy function" 을 통해 로컬 워크스페이스를 Google Cloud에 deploy 해본다.
원하는 리전을 선택해서 deploy 완료하면 Cloud Console 에서도 deploy된 함수를 확인할 수 있다.
deploy 된 함수의 URL을 실행해보면 403 Forbidden 에러가 나올텐데,
이것은 https://cloud.google.com/functions/docs/securing/managing-access-iam?hl=ko#console_6 의 설명을 따라 Cloud Run 설정에서 인증되지 않은 호출 허용 을 선택해주면 해결된다.
GEN_2 함수는 Cloud Run 기반으로 동작하기 때문에 Cloud Run 항목에서 설정해주지만, GEN_1 으로 만들었다면 설정해주는 방법이 다르다. 위의 링크 참조.
이렇게 해주면 잘 되어야겠지만, 하나 빠뜨린 것이 있다.
main.py 에서 사용하는 os.environ["TELEGRAM_TOKEN"] 이 정의되지 않았기 때문에 500 에러가 나올 것이다.
으악. ㅠㅠ 아무리 찾아도 launch.json 을 이용해서 --set-env-vars 파라미터를 가지고 deploy 하는 방법이 없다. 어쩔 수 없이 gcloud 이용해서 콘솔로 한번 설정해준다.
gcloud functions deploy <bot46: function 이름> --region=asia-northeast3 --allow-unauthenticated --gen2 --project=<gcbot46: Google Cloud 프로젝트명> --runtime=python39 --trigger-http --set-env-vars="TELEGRAM_TOKEN=<TELEGRAM_TOKEN: 발급받은 텔레그램 토큰>" --entry-point=<webhook: 처음 실행될 함수명>
이렇게 한번 실행해주면 그 다음부터는 VSCode 에서 deploy 해도 환경 변수가 변경되지 않고 남아 있게 된다.