最近開始把腦筋動到每個月要吃掉我20鎂的linode,看看能不能用Google Compute Platform來取代掉單純的VM。GCP本身有提供Free Tier, 所以只要小心地控制用量,搬遷應該是可以省下一筆,而且也可以玩玩GCP的一些新功能。

需求分析

我這次先搬遷之前寫的一個爬蟲服務,這個爬蟲服務應該是我現有服務裡面最簡單的一個,首先先分析一下他有什麼特性:

  • 需要定時被喚醒(設計上大概10分鐘一次就夠了)
  • 需要一個地方存放爬下來的分析資料
  • 不需要實體上的Database
  • 程式本體相當小

舊有的方案以及缺點

之前的方案是一個純application,走infinite for loop,在裡面放ticker來計時10分鐘一次,然後把他包成docker image用daemon mode跑起來。

這個跑法算是滿傳統的,在非雲端時代也是一個很標準的做法。只是這樣跑了幾個月,發現了有以下這些問題:

  • 需要寫一個service script來確保host vm被重啟的時候,這個服務能夠在被拉起來。
  • 還是得佔著一塊不算小的記憶體,只為了10分鐘跑一次。
  • up了幾個月有時候會卡死,也許有bug也許是memory leak,但是從code其實看不太出問題,而且重現也滿困難的。
  • docker.io掛點過幾次,新的image推不上去拉不下來

這些問題其實都能以傳統手段解決,比方說最簡單的方式就是把它改成cron job,從OS層面來排程而非code層面,docker.io也不是很常掛點,真的掛掉的話改用其他免費的image registry也是可以的。

選擇Serverless

Google提供了三種不同的方法來作Serverless:

  • Google App Engine
    • 最老牌
    • 如果你用gin-gonic寫的話,需要的改動相當小
    • 說serverless….其實比較像是他幫你host一個server,只是這個server會自動按照需求啓閉而已,所以他可以是stateful
    • 支援PubSub,但是是以push的形式走http route進來
    • Debug/Deploy相比於Cloud Function都簡單一點
    • 按run quota付費,也就是可能會因為container跑在上面比預期的久,所以需要付的錢比預期多一點。
  • Cloud Functions
    • 最輕量
    • 很像AWS Lambda
    • 原生支援PUB/SUB
    • Deploy很難搞,command line頗複雜,雖然弄好以後不太會碰到
    • 不過deploy不需要by container去deploy,完全就是build from source code
  • Cloud Run
    • 很新的服務
    • 其實就是Cloud Functions的container版本
    • 可以想成是Knative的POD,跟k8s的pod設定可以通用

這個爬蟲本身吃的資源不多,也不需要http跟外界溝通,用pubsub也挺方便的,所以就選擇了用Cloud Functions來當作deploy的方法。重點是,Cloud Functions免費額度應該算是最彈性的,應該怎麼打都不太會超過免費額度上限,所以就用吧!

實作

結構

預期中的結構會是像這樣

  • PubSub定義一個message
  • SecretManager把Config放在裡面
  • Cloud Scheduler定期(十分鐘)發送這個message
  • Cloud Functions吃到這個PubSub就被喚醒執行

而爬蟲的config由於有credential,所以由Secret Manager來存,然後用mount的方式掛進Cloud Functions。

Scheduler

首先我們需要一個定時器。幸運的是,GCP本身就有提供Cloud Scheduler來提供定時的功能。

Frequency基本上就是Cron的format,比方說例子中的*/15 * * * *就是每15分鐘一次。Target Type選擇Pub/Sub,下面的message道是隨意,給個空Body {}就好。不過,這邊有個限制,就是message / message attributes至少要有一個,就隨便放一個就好。Pub/Sub topic則是直接選擇Create a topic就好。

Cloud Source Repositories

Functions有很多種deploy方式,不過最方便的方式應該是用git。但是,使用git的話,必須要用GCP自己的Cloud Source Repositories(CSR)託管。幸運的是,CSR是可以作為GitHub的mirror存在的,也就是說,當我們把code push到GitHub後,很快該commit就會被mirror到CSR。

首先,先進入CSR,按下右上角的Add Repository,然後選擇Connect External Repository

接下來就是行禮如儀,選擇Project,選擇GitHub當作Git Provider,然後經過一連串的Authorization後,把這個repo給Mirror進來就可以了。

Secret Manager

由於我們的config包含了一些credential,所以比較適合存放在Secret Manager內。這東西使用起來非常直觀,就建立一個key,把config塞進去就好

他可以upload檔案,所以甚至可以把gcp credential(JSON)塞進去。

Cloud Functions

他基本原理還滿簡單的,不過有些眉角:

  1. 他的package name要是FQDN。比方說AppleTwNCCSpy不行,github.com/Rayer/AppleTwNCCSpy 才行。
  2. 目前來講放置EntryPoint的.go 必須要在最上層。如果是放子目錄的話,無論該子目錄有沒有獨立的go.mod, 都會在fetch dependency的階段有錯誤。這問題很隱晦,如果不想浪費時間的話,請務必把它放在最上層。
  3. 我會建議先推一個絕對可以過的版本再慢慢改。Functions Deploy非常麻煩,而且所有輸入的數據都要成功deploy才會儲存——亦即,如果你deploy failed,所有東西都要重打!超煩的 orz

首先,建立一個任何名字的.go檔案,裡面放一個這種簽名的function : func Func(ctx context.Context, m PubSubMessage) error ,記得函數名稱開頭要大寫export,不然會讀取不到。事實上,他一開始建立的範例也會提供這個簽名給你參考。我個人是建議開一個新的branch把它給push上去,接下來就可以開始設定了。

然後進入Cloud Functions,選Create Function。

Basics沒啥好注意的,就Trigger Type選擇剛建立好的Pub/Sub以及Topic選擇剛建立好的Topic即可。

Runtime倒是隨意,要是工作loading很低的話建議把Memory選低一點。像是我這種Crawler的話,下面的Autoscaling的min設定0,max設定1是最好的。注意,如果min不是設定0的話,會一直有一個instance長在上面,很貴!有這種需求的話App Engine或者VM會是更好的選擇。

Secrets就是重頭戲了,我們會把Config寫成yaml放在Secret裡面。舉例來說,我們Secret是剛剛設定的DemoConfig Reference Method選擇Mounted as volumn(建議不要用env expose),Mount Path/secrets, 最後的path選擇/secrets/DemoConfig.yml 這樣設定的話,我們在function就可以這樣得到這個值:

	confByte, err := ioutil.ReadFile("/secrets/DemoConfig.yml")
	if err != nil {
		return err
	}

接下來看你要用json parse, yaml parse還是raw text就都可以了。

最後,最麻煩的地方來了

Runtime當然選Go 1.16,Source Code我是先建議先選擇Inline Editor然後存一次,再回來改,不然deploy的話你剛打的東西都要重打一次!存好回來以後,Source Code選擇Cloud Source Repository,右邊的Repository的名字…請多開一個窗去CSR,把repository name拷貝回來貼上,這邊沒有自動完成 …

還記得我建議嗎?把function寫在根目錄,多開一個branch,所以這邊就行裡如宜的選Branch,填入你剛寫好的branch name,然後Directory with source code就維持/,最後Entry Point填入function name,按下deploy! 順利的話應該就會建立出來了。

Re-Deploy

目前來講command line還缺了一些東西,所以強烈建議先用UI Deploy一次後,再用command line redeploy。以我自己的例子來講 ,如果要deploy from git:

gcloud functions deploy apple-ncc-crawler-dev --region=us-central1 --entry-point CrawlAndAnalyze --memory 128MB --runtime go116 --source "https://source.developers.google.com/projects/iris-303620/repos/github_rayer_appletwnccspy/moveable-aliases/master/paths//"

如果要從local deploy,拿掉–source標籤即可。其實deploy from local應該比較方便,只是command line缺不少設定,比方說目前secret->mount沒辦法從command line去設定(但是很妙的是,command line會告訴你相關訊息)。

Command deploy有些要注意的地方,最坑的就是--source 這個parameter。他看起來很像是一個https開頭的網址,事實上不是。比方說,他是https://source.developers.google.com開頭,不是CSR的網址https://source.cloud.google.com 。所以最保險的方法就是拷貝這條一個個自己改:

https://source.developers.google.com/projects/<ProjectID>/repos/<Repository名字>/moveable-aliases/<Branch>/paths//

這樣應該就可以大功告成了。建議把deploy指令寫在project裡面一個.sh裡面方便以後使用。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。