函數計算|如何使用層解決依賴包問題?

簡介: 在使用阿里雲函數計算平臺時,如果您曾經遇到過以下問題,本文應該會對您有所幫助: 第三方依賴包太大,每次更新代碼都非常耗時,甚至會出現超過代碼包限制的情況,我該怎麼辦? 安裝第三方依賴包後,可以在本地運行成功,上傳到阿里雲函數計算平臺上就會報錯,這是什麼情況? 有很多常用的依賴包,很多用戶應該都會用到,阿里雲函數計算官方不能直接內置到運行時環境中麼? 我在多個函數中有相同的依賴包,我該如何管理這些相同的依賴包?

在使用阿里雲函數計算平臺時,如果您曾經遇到過以下問題,本文應該會對您有所幫助:

 

  1. 第三方依賴包太大,每次更新代碼都非常耗時,甚至會出現超過代碼包限制的情況,我該怎麼辦?
  2. 安裝第三方依賴包後,可以在本地運行成功,上傳到阿里雲函數計算平臺上就會報錯,這是什麼情況?
  3. 有很多常用的依賴包,很多用戶應該都會用到,阿里雲函數計算官方不能直接內置到運行時環境中麼?
  4. 我在多個函數中有相同的依賴包,我該如何管理這些相同的依賴包?

 

提供了集中管理,跨多個功能且能夠共享代碼和數據的方法。

 

2021 年 1 月,阿里雲函數計算髮布了 “自定義層”功能,讓用戶可以自定義層,並支持跨函數共享。2022 年 8 月,阿里雲函數計算髮布“公共層”功能,提供了官方公共層,供用戶直接使用,進一步提升了用戶體驗。

接下來我們先介紹下 “自定義層” 的功能和作用。

 

自定義層

 

在層功能發佈之前,必須將代碼與代碼的依賴項一起打包和部署,這些依賴項在不同函數中可能是相同的,很多情況下這些依賴項的大小,遠遠大於代碼的大小。

但是在層功能發佈後,我們可以將代碼的依賴項,或者多個函數中共享的部分打包成 Zip 壓縮文件,並作爲函數計算的自定義層發佈,不同函數都可以使用該自定義層。

阿里雲函數計算會在調用時將層與函數代碼一起加載。可參考文末文檔:創建自定義層[1]在函數中配置自定義層[2]

 

爲什麼使用自定義層?

 

使用自定義層有以下優勢:

 

(1)跨函數複用代碼

 

將多個函數中的通用代碼或數據提取出來,打包成 Zip 包,做成自定義層,供不同函數引用,避免了在多個地方維護通用的代碼或數據。

 

與此同時,也實現了依賴項和業務邏輯的分離,用戶可以專注於核心的業務邏輯。

 

(2)使代碼包更小

 

函數的代碼包越來越大時,部署速度也會越來越慢,導致函數的維護和測試愈加困難。

 

此外函數代碼包大小也有限制,比如阿里雲函數計算的代碼包限制爲 500MB (2022 年 9 月),層是突破該限制的方法之一。層也有大小限制,目前單個層的代碼包大小限制爲 500MB,單個函數最多可配置 5 個層,總大小不能超過 2GB。

 

(3)加速代碼部署,簡化函數管理

 

函數代碼包越小,代碼包的部署就越快。尤其是一些大型依賴項時,核心功能代碼可能只有幾兆字節,但依賴項可能有幾百兆。比如 Puppeteer 依賴包超過 100MB,阿里雲的 DataX 依賴包超過 800MB。

 

一般來講,這些依賴項很少修改,因此將他們打包成層後,可以避免在覈心代碼修改時頻繁修改這些大型依賴項。對這些依賴項也可以拆分成多個層,每次修改一個功能時,只需要更新其中一個層。

 

比如我們實現了自定義運行時 Python3.10 以及該運行時兼容的科學計算庫 SciPy,可以將自定義運行時和依賴包拆成兩個層,當需要更新依賴包時,只需要更新依賴包的層,而自定義運行時的層保持不變。

 

自定義層的困境

 

(1)製作層有一定門檻

 

層的 Zip 包有一定的格式規範,用戶需要按照該規範進行製作。以 Python 的 requests 庫爲例,依賴打包後的文件結構爲:

 

my-layer-code.zip
└── python
    └── requests

爲什麼有這種要求呢?這個涉及到不同運行時在搜索第三方依賴包的實現邏輯,以 Python 爲例,Python 運行時會在 sys.path 路徑下搜索依賴包,上面的 Zip 包會解壓到函數實例的 /opt 目錄下,解壓後 requests 這個包就放到了 /opt/python 目錄下。

 

然後,函數計算平臺會將一些特定的目錄放到運行時語言的依賴搜索路徑上,比如 Python 運行時就會將 /opt/python 放到 sys.path 中,這樣,代碼中就可以直接引用 requests 庫了。其他運行時的使用方法可參考文末文檔-創建自定義層當然,你也可以不按照這個格式規範來製作層,此時就需要在代碼中添加對應的搜索路徑了,具體方法可參考文末文檔-如何在 Custom Runtime 中引用層中的依賴?[3]

 

需要在指定操作系統和處理器架構下製作層。有一些依賴是與操作系統和處理器架構有依賴關係的,比如 Python 的科學計算庫 NumPy,假如你在 M1 芯片的 MacOS 下安裝,其版本爲:

 

numpy-1.23.3-cp39-cp39-macosx_11_0_arm64

可看到兼容的操作系統爲 mac os, 處理器架構爲 arm64。但在函數計算平臺的實例環境爲 Linux x86_64,操作系統目前使用的發行版爲 Debian 9,因此在 M1 Mac 下安裝的 NumPy 庫不能在阿里雲函數計算平臺使用。我們推薦在 Debian 9 系統下進行安裝,但用戶本地可能沒有該環境,您可以使用在線構建依賴庫或者使用函數計算官方運行時鏡像來構建,此處不再贅述。

 

層需要包含新增的共享動態庫。有些依賴庫需要安裝額外的共享動態庫,在構建層的 Zip 包時也需要包含這些共享動態庫。例如 Nodejs 的依賴庫 Puppeteer,需要額外安裝二十多個共享動態庫(如 libxss1,libnspr4 等),這些依賴庫都要打包到層 Zip 包中。如何成功的安裝 Puppeteer 庫並不是簡單的事情。共享動態庫推薦放到 Zip 包的 lib 目錄下,函數計算平臺會將/opt/lib 目錄添加到 LD_LIBRARY_PATH(僅限於內置運行時)

(2)無法跨賬號共享

自定義層默認只能在同賬號同地域的不同函數之間共享,無法進行跨賬號共享。因此,用戶 A 創建的自定義層無法給用戶 B 使用,這不僅給用戶帶來了重複的工作量,也不利於宿主機上相同層的複用。

 

公共層

 

由於自定義層的這些痛點,阿里雲函數計算在 2022 年 8 月發佈了公共層功能。實現層跨賬號共享,並提供了一些官方公共層[6]供用戶直接使用,方便用戶快速開發示例原型。阿里雲函數計算平臺主要提供了三類官方公共層:

 

  • 自定義運行時(如 Python 3.10、Nodejs17、PHP 8.1、Java17、.NET 6 等)
  • 常用依賴庫(如 PyTorch、Scipy、Puppeteer 等)
  • 阿里雲 SDK(如 Aliyun DataX ) 

 

詳情可參考文末官方文檔-在函數中配置官方公共層[4]目前官方公共層仍在持續補充,如果您有需要的運行時或者依賴庫想通過官方公共層的方式使用,可通過釘釘答疑羣(釘釘羣號:11721331)與我們聯繫,也可以直接在 Github[5]提交 issue。

 

如何公開自定義層?

 

目前,層公開功能在內測中,如有需求可以通過釘釘聯繫我們。同時,我們也非常歡迎大家貢獻公共層到倉庫,我們很快會在該倉庫提供公共層貢獻的方法和示例。

 

示例

 

官方公共層的最新版本和使用說明可參考 Github,下面我們介紹一些使用官方公共層的典型示例。

 

示例一、基於 Nodejs16 + Puppeteer 實現網頁截圖示例程序

 

Puppeteer 是一個 Nodejs 庫,它提供了高級的 API 並通過 DevTools 協議來控制 Chrome(或 Chromium)。通俗來說就是一個 headless chrome 瀏覽器,可以使用它完成很多自動化的事情,比如:

 

  • 生成網頁截圖或者 PDF
  • 做表單的自動提交、UI 的自動化測試、模擬鍵盤輸入等
  • more...
     

本示例使用 Puppeteer 完成一個網頁截圖示例程序。

 

首先,我們使用內置運行時 Nodejs16 創建一個函數 start-puppeteer,其中請求處理程序類型選擇“處理 HTTP 請求”。

 

然後,在高級配置中將內存規格設置爲 1GB,示例程序的內存使用大概在550MB左右。

創建成功後,在控制檯上打開 index.js文件,將下面的代碼拷貝並覆蓋該文件,點擊部署按鈕。

const fs = require('fs');
const puppeteer = require('puppeteer');

function autoScroll(page) {
  return page.evaluate(() => {
      return new Promise((resolve, reject) => {
          var totalHeight = 0;
          var distance = 100;
          var timer = setInterval(() => {
              var scrollHeight = document.body.scrollHeight;
              window.scrollBy(0, distance);
              totalHeight += distance;
              if (totalHeight >= scrollHeight) {
                  clearInterval(timer);
                  resolve();
              }
          }, 100);
      })
  });
}

module.exports.handler = function (request, response, context) {
  console.log('Node version is: ' + process.version);
  (async () => {
    const browser = await puppeteer.launch({
      headless: true,
      args: [
        '--disable-gpu',
        '--disable-dev-shm-usage',
        '--disable-setuid-sandbox',
        '--no-first-run',
        '--no-zygote',
        '--no-sandbox'
      ]
    });

    let url = request.queries['url'];

    if (!url) {
      url = 'https://www.serverless-devs.com';
    }

    if (!url.startsWith('https://') && !url.startsWith('http://')) {
      url = 'http://' + url;
    }

    const page = await browser.newPage();

    await page.emulateTimezone('Asia/Shanghai');
    await page.goto(url, {
      'waitUntil': 'networkidle2'
    });
    await page.setViewport({
      width: 1200,
      height: 800
    });
    await autoScroll(page)

    let path = '/tmp/example';
    let contentType = 'image/png';
    await page.screenshot({ path: path, fullPage: true, type: 'png' });
    await browser.close();

    response.setStatusCode(200);
    response.setHeader('content-type', contentType);
    response.send(fs.readFileSync(path))
  })().catch(err => {
    response.setStatusCode(500);
    response.setHeader('content-type', 'text/plain');

    response.send(err.message);
  });
};

簡要介紹一下上述代碼的核心邏輯,首先代碼會解析 query 參數獲取需要截圖的 url 地址(如果解析失敗則默認使用 Serverless Devs 官網主頁),然後使用 Puppeteer 對該網頁進行截圖,並保存到運行實例的 /tmp/example文件中,然後將該文件作爲HTTP請求的返回體直接返回。

 

然後,我們需要配置 Puppeteer 公共層,在函數配置中找到層,點擊編輯,選擇添加官方公共層。

 

選擇官方公共層 Puppeteer17x,目前最新的層版本爲1。

參考官方公共層 Nodejs-Puppeteer17x README 添加環境變量,對於版本1,需要添加 LD_LIBRARY_PATH=/opt/lib/x86_64-linux-gnu:/opt/lib環境變量。

 

最後,使用觸發器管理中的測試地址進行測試驗證。

 

測試結果如下所示,已成功將 Serverless Devs 官方進行截圖。

 

示例二、基於公共層快速實現 .NET 6 自定義運行時

首先,通過控制檯創建 .NET 6 自定義運行時。在最上層選擇 “使用自定義運行時創建”,選擇“處理 HTTP 請求”,選擇 .NET 6運行時,其他配置使用默認值。

創建成功後,可以通過 WebIDE 看到示例代碼 Program.cs

 

示例代碼中需要注意四個部分:

 

 

首先,我們直接使用觸發器管理頁面中的測試地址進行測試,此時不添加任何 PATH 信息,結果如下圖所示:

 

然後,我們測試添加 /invoke路徑進行測試,因爲該路由方法爲 POST,我們直接使用 curl -XPOST測試:

 

 

同樣,我們用這種方法測試一下/initialize

 

注意:此處只是做測試,初始化回調函數不需要主動調用,函數計算平臺會在實例啓動後自動調用該回調方法(不要忘記在配置裏啓用 initializer 回調程序)

 

最後,我們再做一個小測試,在觸發器管理頁面將HTTP觸發器刪除,刪除後該函數類型會轉換成事件請求處理程序,在函數配置中,將 Initializer 回調程序啓用

 

 

在控制檯上測試該函數,結果如下圖所示:

 

點擊實時日誌按鈕,可以看到在該請求執行前,已經執行了 Initialize 回調方法。

 

層的最佳實踐是什麼?

 

前文介紹了什麼是自定義層,爲什麼使用自定義層,什麼是公共層,並介紹了兩個官方公共層的示例。但我們對層的使用仍然還有一些疑惑,比如什麼場景下推薦使用層?層與代碼包有什麼區別?有沒有與層相似的功能?與這些相似功能相比,層的優缺點是什麼?接下來嘗試回答一下這些問題。

 

什麼場景下推薦使用層?

 

目前,使用層的場景主要有兩類,一類是自定義運行時,另一類是各種語言的依賴庫。強烈推薦通過層來構建並使用自定義運行時,但對於各類語言的依賴庫,可以參考下面這些建議:

 

  • 推薦優先使用官方公共層
  • 非編譯型語言的依賴庫推薦使用層來管理,對編譯行語言需要根據實際情況進行判斷(比如,對自定義運行時,如果使用JAR包的方式運行 Java 程序,則無法引入層中的依賴,可參考文檔 如何在Custom Runtime中引用層中的依賴?
  • 如果依賴庫較大,並且沒有超過層的限制大小,推薦使用層
  • 如果依賴庫需要額外安裝共享動態庫,推薦使用層(如果構建比較複雜,可聯繫函數計算團隊製作)
  • 如果在多個函數、多個賬號之間有共享代碼或數據的需求,推薦使用層

 

層與代碼包有什麼區別?

 

直觀上看,層就是把原來代碼包的一部分內容拆分出來,再重新建一個代碼包而已,那爲什麼又建立一個層的概念呢?這裏的主要區別是層與代碼包的設計理念不同。

 

  • 層有更簡潔的版本管理方案

層的版本是從 1 開始自動遞增的,目前一個層最大支持 100 個可用版本(不包括已刪除的版本);而對代碼包來講沒有版本的概念,只有在服務層面上有版本概念,相對層的版本會更加複雜。

 

  • -層版本是隻讀的,不可變的

一個層的版本在創建後內容是無法改變的(權限除外),如果想修改層的內容,只能發佈一個新的版本。層版本的只讀特性能夠避免層的改動對函數的影響。

 

  • 層的共享能力

層可以跨函數、跨賬號進行共享,而代碼包不支持。

 

  • 層版本的軟刪除策略

層版本刪除後,不會影響已經配置改層版本的函數的正常運行。因爲在層版本刪除是,阿里雲函數計算平臺並不會直接將層版本的代碼刪掉,而是先進行一次軟刪除操作,避免新的函數使用已刪除的層版本,當該層版本沒有函數引用時,纔會徹底刪除該層版本。

 

函數計算中有沒有與層相似的功能?與相似功能相比,層的優缺點是什麼?

 

在阿里雲函數計算平臺中,與層類似的功能是服務配置中的“掛載NAS文件系統”和“掛載 OSS 對象存儲”功能,層與掛載NAS/OSS在功能和應用場景上有一些明顯的差異:

 

image.png

 

簡單總結一下,如果代碼或數據的大小超過層的限制,則推薦使用掛載 NAS/OSS 的方式;如果代碼或數據會鏡像改動,或者有運行時修改數據的需求,那麼這裏也推薦使用掛載 NAS/OSS 的方式。

 

結語

 

在阿里雲函數計算中,層的定位是一種不可變的基礎設施,通過層版本的只讀特性保證層的一致性和可靠性。本文首先介紹了自定義層的特點和困境,然後介紹了近期發佈的公共層功能,詳細陳述了基於官方公共層實現的兩個示例程序,最後探討了層的最佳實踐是什麼,希望通過本文能讓讀者更好的理解層的概念及其應用場景。

層的功能仍在持續完善中,接下來我們會在一下幾個方向進行重點優化:

  • 完善官方公共層體驗,補充更多的常用依賴庫或自定義運行時作爲官方公共層,並提供完善的應用示例。
  • 提供公共層貢獻的方法和示例,促進公共層的開源共建。

 

如果對層的使用有任何的疑惑或者建議,歡迎搜索(羣號:11721331)進入阿里雲函數計算釘釘羣聯繫我們。

 

更多內容關注 Serverless 微信公衆號(ID:serverlessdevs),彙集 Serverless 技術最全內容,定期舉辦 Serverless 活動、直播,用戶最佳實踐。

 

More:

 

https://help.aliyun.com/document_detail/193057.html

https://help.aliyun.com/document_detail/193058.html

https://help.aliyun.com/document_detail/71142.htm?spm=a2c4g.11186623.0.0.2c77481ftZBbfb#task-1881232

  • 在函數中配置官方公共層

https://help.aliyun.com/document_detail/451191.html

  • 阿里雲函數計算 公共層github:

github.com/awesome-fc/awesome-layers

  • 官方公共層

Nodejs-Puppeteer17x README

https://help.aliyun.com/document_detail/425055.html#section-ffl-tm3-txg

  • Custom Runtime 事件請求處理程序

https://help.aliyun.com/document_detail/191342.html

  • Custom Runtime 函數實例生命週期回調

https://help.aliyun.com/document_detail/425056.html

原文鏈接:https://click.aliyun.com/m/1000362244/

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章