【玩轉騰訊雲】Serverless/Egg.js/騰訊雲 COS 構建圖片上傳應用

  • 2020 年 3 月 31 日
  • 筆記

從「建站」開始

以前,當朋友知道我的職業是一名前端工程師的時候,他們總喜歡問一個問題:那你能幫我修一下電腦,不,建一個網站嗎?

image.png

他們當然不知道「建站」除了編寫基礎的業務代碼外,還包括「服務器購買和 LAMP 等相關環境搭建」「負載均衡」「容器部署」「CDN」「監控」「網絡」「容災備份」「圖片視頻媒體處理」… 等一堆眼花繚亂的東西,沒有一個合理的預算以及整體的技術架構能力,很難把這些事情通通處理好。雖然現在常用到的「Docker」「k8s」等已經極大的幫助我們對基礎設施的管理,但 Serverless 架構的出現才似乎真正的將業務開發者從這些繁瑣的事情中抽離出來。

0202 年了,建站是否還是一件麻煩的事情?

試試用 Serverless 部署一個靜態網站

安裝 Serverless cli 和創建一個簡單的 html 項目。

# 安裝 serverless cli  $ npm install -g serverless  # 創建 website 目錄並進入  $ mkdir website && cd website  # 創建 serverlss 的配置文件  $ touch serverless.yml  # 將靜態網站資源放置到 public 文件夾下面  $ mkdir public && echo 'hello serverless' >> public/index.html

配置 Serverless:這裡使用了 tencent-website Serverless 組件,指定 public 文件夾做為輸入目錄。

# serverless.yml  myWebsite:    component: '@serverless/tencent-website'    inputs:      code:        src: ./public        index: index.html        error: index.html      region: ap-guangzhou      bucketName: hello-serverless

部署

至此,一個很基礎的 serverless 靜態網站配置已經完成,在項目根目錄下命令行執行 serverless(也可以用 sls 縮寫),在部署的過程中掃描命令行中輸出的二維碼登錄到騰訊雲,等待片刻即可完成部署。

image.png

訪問一下命令行中輸出的 http 地址。

image.png

搞定!一個靜態網站就這樣便完成了全部的部署(當然你還可以自定義域名、配置 CDN 等,但現在先不考慮這些)

至此,你可能會說這看起來僅僅是把剛才的 index.html 上傳到了騰訊雲,甚至還有可能覺得索然無味?

當然不是,Serverless 架構除了部署,還幫我們搞定了在開篇提到的那一大堆的基礎服務設施。也不用擔心流量突增要如何擴容,因為它是自動伸縮的,並且根據使用情況付費!這顯然可以節約很多的常規服務費用。而做為一線開發者,只需要考慮具體業務如何開發,這極大地提升了開發效率。

現在回過頭來回答一下「建站」的問題,好像又不是那麼困難了呢。

image.png

那麼,Serverless 是一個什麼東西?讓我先從官網抄一份作業:

Serverless 簡介

Serverless 是開發者和企業用戶共同推動的,它可以使開發者在構建和運行應用時無需管理服務器等基礎設施,將構建應用的成本進一步降低,函數是部署和運行的基本單位。用戶只為實際使用的資源付費。這些代碼完全由事件觸發(event-trigger),平台根據請求自動平行調整服務資源,擁有近乎無限的擴容能力,空閑時則沒有任何資源在運行。代碼運行無狀態,可以更加簡單的實現快速迭代、極速部署。Serverless的最終目標,是希望開發者可以將開發重點關注到更有價值的業務代碼(而不是浪費時間在其他事情上)。簡單的Linux發行版無法為開發者帶來更具價值的場景,Kubernetes集群也無法達到輕量化的目標。

一句話:Serverless 可以使開發者只關注自己的代碼,而無需重複構建服務器和環境等基礎設施。

現在,回到文章標題,我這裡會使用 Serverless + Egg.js + 騰訊雲 COS 創建一個圖片上傳服務示例

圖片上傳服務實踐

首先準備一下資源用來放置圖片:在騰訊雲對象存儲控制台新建一個用來上傳圖片的雲對象存儲 COS(Cloud Object Storage) 桶

image.png

創建完存儲桶以後接着開始在本地新建一個 Egg.js 應用

$ mkdir egg-example && cd egg-example  $ npm init egg --type=simple  $ npm i  $ npm run dev

此時打開 http://127.0.0.1:7001/ 就可以看到 Egg.js 已經在正常工作了。在 public 目錄下新建一個 html 文件,用來做上傳操作

$ touch public/index.html
<!-- index.html -->  <input type="file" id="input">  <script>    const handleInputChange = async(e) => {      const formdata = new FormData()      formdata.append('image', e.target.files[0])      const result = await fetch('/upload', {        method: 'post',        withCredentials: true,        body: formdata,      })      console.log(result)    }    document.querySelector('#input').addEventListener('change', handleInputChange)  </script>

接着寫後端上傳代碼,在 controller/home.js 中新增一個 putObject 的方法

import * as cos from './cos'  import path from 'path'    async putObject() {    const { ctx } = this    const file = ctx.request.files[0]    const name = 'egg-multipart-test/' + path.basename(file.filename)    ctx.body = await cos.putObject(name, file.filepath)  }

其中在 cos.js 執行了最終的上傳邏輯。這裡使用了騰訊雲 COS Nodejs SDKSecretIdSecretKeyAPI 密鑰管理中可以查看到,Bucket 即為剛才創建的存儲桶名稱。

import COS from 'cos-nodejs-sdk-v5'  import fs from 'fs'  import { sha1 } from 'crypto-hash'  import path from 'path'    const cos = new COS({    SecretId: 'AKIDjJfbxxxxx',    SecretKey: 'B1crvhExxxxx',  })    export const putObject = async function(filename, filepath) {    const fileBody = await fs.readFile(filepath)    const hash = await sha1(fileBody)    const fileName = hash + path.extname(filename).toLowerCase()    return new Promise(resolve => {      cos.putObject({        Bucket: 'qingting-1257197216',        Region: 'ap-guangzhou',        Key: fileName,        StorageClass: 'STANDARD',        Body: fileBody,      }, function(err, data) {        resolve(data)      })    })  }

本地測試一下,已成功將圖片上傳至 COS 桶中

image.png

接下來開始將服務部署至騰訊雲,新建 serverless.yaml 文件,使用 tencent-egg 組件,並將整個項目部署至 nodejs 服務環境中

# serverless.yml    MyComponent:    component: '@serverless/tencent-egg'    inputs:      region: ap-guangzhou      functionName: egg-function      code: ./      functionConf:        timeout: 10        memorySize: 128        environment:          variables:            TEST: vale        vpcConfig:          subnetId: ''          vpcId: ''      apigatewayConf:        protocols:          - https        environment: release

部署成功以後打開輸出 url,整個項目便可以 work 了

image.png

試着上傳一張圖片(it』s worked! 如果你像我一樣愛好攝影,可以基於此做一個攝影相冊了)

image.png
image.png

本文內容到這裡已結束,感謝閱讀。最後羅列幾個自己在騰訊雲使用 Serverless 中遇到的問題:

問題

  1. 靜態網站發佈後,默認輸出是 http 地址,如果你試圖訪問 https 地址你將會看到地址會從 https 301 到 http…,雖然去 COS 桶中開啟強制 https 選項修復掉。但這還是一個很不「技術」的 bug。
  2. 在部署 Egg.js 應用前,serverless cli 會將整個項目打包成一個 zip 然後上傳(是的,node_modules 也被打包),這就導致 serverless cli 很容易會處於「卡死」狀態,可以在部署前執行 npm i --production 來 hack 這個問題,但依然是一種很不好的體驗。相信騰訊雲團隊後面會改成忽略 node_modules 並在上傳後執行 install npm 包的方式。