Serverless Custom (Container) Runtime

本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰! javascript

這是第 106 篇不摻水的原創,想獲取更多原創好文,請搜索公衆號關注咱們吧~ 本文首發於政採雲前端博客:Serverless Custom (Container) Runtimephp

雪霽.png

背景

咱們知道 Serverless 能夠理解爲 Serverless = FaaS + BaaS 。Serverless 應用中,對於服務端業務邏輯代碼,開發者是以函數的形式去實現的,即 FaaS(函數即服務)。( Serverless 相關文章能夠看下團隊結合阿里雲 FC 談談我對 FaaS 的理解)html

對於雲廠商的 FaaS 平臺,雖然他們支持多種編程語言及版本的標準運行環境,但畢竟仍是有限的。因此,爲了知足用戶更多個性化開發語言及版本的函數實現需求,他們提供了 Custom Runtime 服務,即可定製化運行環境,支持用戶用任何編程語言編寫的函數。前端

以阿里雲函數計算 FC 爲例,這是它所支持的開發語言列表:java

支持語言 運行環境
Node.js Node.js 6.10(runtime=nodejs6)、
Node.js 8.9.0(runtime=nodejs8)、
Node.js 10.15.3(runtime=nodejs10)
Node.js 12.16.1(runtime=nodejs12)
Python Python 2.7(runtime = python2.7)
Python 3.6(runtime = python3)
PHP PHP 7.2.7(Runtime=php7.2)
Java Java OpenJDK 1.8.0(runtime=java8)
C# .NET Core 2.1(runtime=dotnetcore 2.1)
Go Go Custom Runtime
Ruby Ruby Custom Runtime
PowerShell PowerShell Custom Runtime
TypeScript TypeScript Custom Runtime
F# F# Custom Runtime
C++ C++ Custom Runtime
Lua Lua Custom Runtime
Dart Dart Custom Runtime
其餘語言 Custom Runtime

能夠看出,對於咱們前端工程師,若是想使用阿里雲 FC 平臺,並不能爲所欲爲的使用 Node.js 和 TypeScript 。由於 Node.js,只支持表格中的四種版本,而 TypeScript ,FC 平臺自身徹底不支持。因此要想使用 Node.js 的其它版本和 TypeScript,就須要自定義運行時。node

那麼什麼是 Custom Runtime 呢?python

概念

運行時( Runtime )指函數代碼在運行時所依賴的環境,包括任何庫、代碼包、框架或平臺。Custom Runtime 就是徹底由用戶自定義函數的運行環境。git

FaaS 平臺經過開放實現自定義函數運行時,支持根據需求使用任意開發語言的任意版本來編寫函數。web

做用

阿里雲官方文檔中說到,基於 Custom Runtime 咱們能夠實現這兩件事:docker

  • 定製個性化語言(例如 Go、Lua、Ruby )和各類語言的小版本(例如 Python 3.七、Node.js 14)的執行環境,打造屬於您的運行環境。
  • 一鍵遷移現有的 Web 應用或基於傳統開發的 Web 項目到函數計算平臺,不用作任何改造。

實現 Custom Runtime

本文將以阿里雲 FC 爲例,實現一個 Custom Runtime。其它平臺好比騰訊雲 SCF 等,原理和過程也都大體相同。

工做原理

Custom Runtime 本質上是一個 HTTP Server,代碼裏面包含一個名爲 bootstrap 的啓動文件,以後這個 HTTP Server 接管了函數計算平臺的全部請求,包括事件調用或者 HTTP 函數調用等。

現在 Typescript 在 Node 中的應用已經愈來愈普遍,因此筆者將實現一個能夠運行 TS 代碼的 TypeScript 運行時。

操做步驟

準備工做

爲了更快更好地玩轉 Serverless 應用,須要先安裝阿里雲的一個 Fun工具,它是一個用於支持 Serverless 應用部署的工具,能幫助咱們便捷地管理函數計算、API 網關、日誌服務等資源。它經過一個資源配置文件(template.yml),協助咱們進行開發、構建、部署操做。

安裝配置過程以下:

(1)安裝:

// 安裝命令
$ npm install @alicloud/fun -g

// 執行 fun --version 檢查安裝是否成功
$ fun --version

3.6.21
複製代碼

(2)安裝好後,使用fun config命令配置帳戶信息(配置文檔),按照提示依次配置 Account ID、AccessKey ID、AccessKey Secret、Default Region Name。

配置完成後,先在本地建立一個 TypeScript 項目 custom-runtime-typescript,並安裝相關依賴。

npm i typescript ts-node @types/node
複製代碼

接下來,開始 Custom Runtime 的開發流程,一步一步打造屬於本身的自定義運行環境。

1.搭建一個具備監聽端口的 HTTP Server

  • 須要注意的是,這個服務必定要監聽0.0.0.0:CAPort*:CAPort端口,默認是 9000。若是使用127.0.0.1:CAPort端口,會致使請求超時

用 TS 編寫一個 HTTP Server 文件 server.ts 以下:

注意:在開發函數具體的邏輯以前,通常會確認開發的函數是事件函數仍是 HTTP 函數

import * as http from 'http';

// 建立一個 HTTP Server
const server = http.createServer(function (req: http.IncomingMessage, res: http.ServerResponse): void {
  var rid = req.headers["x-fc-request-id"];
  console.log(`FC Invoke Start RequestId: ${rid}`);
  
  var rawData = "";
  req.on('data', function (chunk) {
    rawData += chunk;
  });
  
  req.on('end', function () {
    // 處理業務邏輯 ……
    console.log(rawData);
    
    res.writeHead(200);
    res.end(rawData);
    console.log(`FC Invoke End RequestId: ${rid}`);
  });
});

server.timeout = 0; // never timeout
server.keepAliveTimeout = 0; // kee palive, never timeout

// 啓動 HTTP 服務並監聽 0.0.0.0:9000 端口
server.listen(9000, '0.0.0.0', function () {
  console.log('FunctionCompute typescript runtime inited.');
});


複製代碼

編寫完成後,能夠先在本地測試該服務是否啓動成功,經過安裝在項目中的 ts-node 命令來運行上述代碼:

# 啓動 HTTP 服務

$ ./node_modules/.bin/ts-node server.ts
複製代碼

啓動後,在另外一個終端中使用 curl 命令測試:

$ curl 0.0.0.0:9000 -X POST -d "hello world" -H "x-fc-request-id:123" 

hello world
複製代碼

若服務已正常啓動,說明它能夠在接收 HTTP 請求後處理業務邏輯,而後將處理結果再以 HTTP 響應的形式返回給 FaaS 平臺。

2.建立一個啓動目標 Server 的可執行文件 bootstrap

函數計算冷啓動 Custom Runtime 時,會默認調用 bootstrap 文件啓動自定義的 HTTP Server。而後這個 HTTP Server 接管了函數計算系統的全部請求。

  • bootstrap 是運行時入口引導程序文件,它會告訴 FaaS 如何啓動你的自定義運行時。Custom Runtime 加載函數時會固定檢索 bootstrap 同名文件,並執行該程序來啓動 Custom Runtime 運行時。
  • bootstrap 需具有 777 或 755 可執行權限
  • 若是是 shell 腳本,必定要添加#!/bin/bash

建立 bootstrap 文件以下:

#!/bin/bash
./node_modules/.bin/ts-node server.ts
複製代碼

3.編寫資源配置文件 template.yaml

在當前目錄下編寫一份用於部署到函數計算的資源配置文件 template.yaml:

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  custom-runtime: # 服務名稱
    Type: 'Aliyun::Serverless::Service' 
    Properties:
      Description: 'helloworld'
    custom-runtime-ts: # 函數名稱
      Type: 'Aliyun::Serverless::Function' 
      Properties:
        Handler: index.handler # Handler 在此時沒有實質意義,填寫任意的一個知足函數計算 Handler 字符集約束的字符串便可, 例如 index.handler
        Runtime: custom # custom 表明自定義運行時
        MemorySize: 512
        CodeUri: './'
複製代碼

4.部署、調用測試、完成

(1)使用fun deploy -y 命令將咱們的自定義運行時和業務邏輯代碼全部資源部署到阿里雲。

image-20210505220309647

(2)使用命令調用部署函數,驗證

$ fun invoke -e "hello,my custom runtime"  
複製代碼

image-20210505220520916

看到成功輸出,就表明咱們的 custom runtime 大功告成了!它能夠直接運行咱們寫的 TS 代碼了。

image-20210505223345696

實現 Custom Container Runtime

TS 的運行環境問題能夠用 Custom Runtime 解決,可是 Node 某些版本平臺不支持的問題,就不能用一樣的辦法了。由於 Node 是全局安裝的,依賴系統環境。

FC 平臺已經爲咱們想好了此類問題的解決辦法,爲咱們提供了 Custom Container Runtime (自定義容器運行環境)的能力。FaaS 平臺有這種能力,是由於它的底層實現原理是 Docker 容器,因此它經過運用容器技術,把咱們的應用代碼和運行環境打包爲 Docker 鏡像,保持環境一致性。實現一次構建,處處運行。

工做原理

Custom Container Runtime 工做原理與Custom Runtime 基本相同:

  • 函數計算系統初始化執行環境實例前會扮演該函數的服務角色,得到臨時用戶名和密碼並拉取鏡像
  • 拉取成功後根據指定的啓動命令 Command、參數 Args 及 CAPort 端口(默認 9000 )啓動自定義的 HTTP Server。
  • 而後這個 HTTP Server 接管了函數計算系統的全部請求,包括來自事件函數調用及 HTTP 函數調用。

下面咱們自定義一個 Node v16.1.0 版本的容器運行環境。

操做步驟

1.自定義 HTTP Server

這一步和 Custom Runtime 相同,使用 Node.js Express 自定義一個 Http 服務 server.js,GET 和 POST 方法分別路由至不一樣的 Handler:

// server.js 文件
'use strict';

const express = require('express');

// Constants
const PORT = 9000;
const HOST = '0.0.0.0';

// HTTP 函數調用
const app = express();
app.get('/*', (req, res) => {
  res.send(`Hello FunctionCompute, http function, runtime is : Node ${process.version}\n`);
});

// 事件函數調用
app.post('/invoke', (req, res) => {
  res.send(`Hello FunctionCompute, event function,runtime is : Node ${process.version}\n`);
});

// 啓動 HTTP 服務並監聽 9000 端口
var server = app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);

server.timeout = 0; // never timeout
server.keepAliveTimeout = 0; // keepalive, never timeout

複製代碼

啓動服務,本地測試一下:

# 啓動 HTTP 服務
$ node server.js
複製代碼
# 新開一個終端,經過 curl 命令測試
$ curl http://0.0.0.0:9000
Hello FunctionCompute, http GET, this runtime is : Node v11.5.0     # 這是我本地的 Node 版本,後面在自定義容器中會輸出 v16.1.0 
複製代碼

驗證經過。

2.構建鏡像並上傳

一樣的,須要先作兩個準備工做:

接下來,先編寫 Dockerfile,再構建包含咱們 Node 指定版本運行環境和應用代碼的鏡像,最後上傳到本身的鏡像倉庫。

(有須要的同窗能夠先看下這篇文章如何把一個 Node.js web 應用程序給 Docker 化

(1) 編寫 Dockerfile:

# 基於基礎鏡像 node:16.1.0-alpine3.11 構建咱們本身的鏡像
FROM node:16.1.0-alpine3.11

# 設置容器工做目錄
WORKDIR /usr/src/app

# 將 package.json 和 package-lock.json 都拷貝到工做目錄
COPY package*.json ./

# 安裝依賴
RUN npm install

# 將當前目錄下的全部文件拷貝到容器工做目錄中
COPY . .

# 暴露容器 8080 端口
EXPOSE 8080

# 在容器中啓動應用程序
ENTRYPOINT [ "node", "server.js" ]

複製代碼

(2)安裝啓動 Docker,登陸阿里雲鏡像服務,構建並上傳:

# 登陸
$ sudo docker login --username=xxx registry.cn-hangzhou.aliyuncs.com
複製代碼

登陸成功後,先構建 Docker 鏡像:

# 指定ACR鏡像地址:其中 my_serverless 爲你本身的容器命名空間;nodejs 爲你本身的鏡像倉庫名稱;v16.1.0 爲鏡像版本號
$ export IMAGE_NAME="registry.cnhangzhou.aliyuncs.com/my_serverless/nodejs:v16.1.0"
複製代碼
# 構建鏡像
# -t 給鏡像取名字打標籤,一般 name:tag 或者 name 格式
$ docker build -t $IMAGE_NAME .
複製代碼

再啓動容器,本地打開瀏覽器 http://localhost:9000/ 看是否能夠正常響應,來驗證咱們的自定義鏡像是否能夠運行成功:

# 啓動容器: 將容器的 9000 端口映射到主機的 9000 端口
$ docker run -p 9000:9000 -d $IMAGE_NAME
複製代碼
image-20210514150709489

驗證經過後,最後上傳鏡像:

# 上傳鏡像
$ docker push $IMAGE_NAME  
複製代碼

上傳成功後,能夠在阿里雲鏡像服務中看到咱們的鏡像。後面就可使用它啦!

image-20210509153919228

3.定義 template.yaml

建立一個 template.yaml文件以下:

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  CustomContainerRuntime: # 服務名稱
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Policies:
        - AliyunContainerRegistryReadOnlyAccess
      InternetAccess: true
    nodejs-express-http: # 函數名稱
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Description: 'HTTP function powered by nodejs express'
        Runtime: custom-container # 表示自定義容器
        Timeout: 60
        CAPort: 9000 # 注意!這裏Custom Container Runtime使用的監聽端口必定要和HTTP Server監聽的端口保持一致,不然會出現錯誤
        Handler: not-used
        MemorySize: 1024
        CodeUri: ./   # Root directory for the function or the Dockerfile path
        CustomContainerConfig: # 容器鏡像配置
          # Sample image value: registry-vpc.cn-shenzhen.aliyuncs.com/fc-demo/nodejs-express:v0.1 使用同地域的VPC鏡像地址加速
          Image: 'registry.cn-hangzhou.aliyuncs.com/my_serverless/nodejs:v16.1.0'
          Command: '[ "node"]'
          Args: '["server.js"]'
      Events:
        http-trigger-test:
          Type: HTTP
          Properties:
              AuthType: ANONYMOUS
              Methods: ['GET', 'POST', 'PUT']
複製代碼
4.部署測試
# 使用命令部署到 FC 
$ fun deploy -y
複製代碼
image-20210509163842880

部署成功後,咱們去 FC 平臺上進行測試。

由於咱們在template.yaml 中配置的觸發器是 http 觸發器,因此咱們點擊「執行」按鈕進行調試,發現正常運行,返回結果爲 runtime is : Node v16.1.0,說明咱們的自定義容器運行環境也成功實現了!

image-20210509163945647

小結

Custom Runtime 爲咱們打破了 FaaS 平臺對語言的限制;Custom Container Runtime 讓開發者能夠將應用代碼和運行環境打包成容器鏡像做爲函數的交付物,優化開發者體驗、提高開發和交付效率。

自定義(容器)運行時讓咱們開發者使用 Serverless 的自由度更高,經過它們可讓咱們無需代碼改造,一鍵遷移咱們的 Web 應用。

參考資料

what-is-runtime

爲阿里雲 serverless 打造 Deno 運行時

Custom Runtime 說明

fun 工具

函數計算支持容器鏡像-加速應用 Serverless 進程

Custom Runtime - 打破雲函數語言限制

推薦閱讀

Vite 特性和部分源碼解析

我在工做中是如何使用 git 的

15 分鐘學會 Immutable

開源做品

  • 政採雲前端小報

開源地址 www.zoo.team/openweekly/ (小報官網首頁有微信交流羣)

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章
相關標籤/搜索