全部产品
云市场

为函数安装第三方依赖

更新时间:2019-06-21 18:51:24

函数在执行时常会用到第三方依赖库,函数计算的运行环境内置了一些常用的依赖库,您可以直接在函数中引用。

各个 runtime 内置的依赖库可以参考:

您也可以使用自定义的依赖库,这需要您将依赖库与函数代码一起打包上传至函数计算。本文对为函数安装第三方依赖库的步骤进行详细介绍。有关打包的进阶教程请参考文章 函数计算安装依赖库方法小结

打包要求

函数中使用第三方依赖时,您需要将依赖库下载到函数代码所在的文件夹下,并将所有代码和依赖项打包上传至函数计算平台。在创建函数时指定代码包。您可以通过 Fun 或控制台的方式直接上传代码包到函数计算平台,也可以将代码包上传至 OSS ,通过 OSS 导入到函数计算平台。

上传时,您可以上传压缩包,也可以指定函数所在文件夹,函数计算后台会自动为您压缩。

如果您上传的是压缩包,请注意:

打包时,需要针对文件进行打包,而不是针对代码整体目录进行打包;打包完成后,入口函数文件需要位于包内的根目录。

  • 在 Windows 下打包时,可以进入函数代码目录,全选所有文件以后,单击鼠标右键,选择“压缩为 zip 包”,生成代码包。
  • 在 Linux 下打包时,通过调用 zip 命令时,将源文件指定为代码目录下的所有文件,实现生成部署代码包,例如 zip code.zip /home/code/*。

打包示例

打包需要注意两点:

  • 将依赖库下载到函数所在的文件夹下;
  • 打包是针对文件进行打包,而不是针对代码整体目录进行打包。

您可以通过控制台上传代码包,在代码执行页面,您可以选择 【OSS 上传】、【代码包上传】或【文件夹上传】。如果您是 Windows 用户,建议您使用控制台上传。

示例中使用 Fun 上传代码包。

Nodejs 打包示例

打包第三方库 mysql

下面通过 Fun 来介绍添加 mysql 模块 mysql 的步骤(Fun 的安装文档请参考):

  1. 建立一个目录,用于存放代码和依赖模块:

    1. mkdir /tmp/code
  2. 在 /tmp/code 目录下安装依赖:

    1. cd /tmp/code
    2. npm install mysql
  3. 新建代码文件,例如 /tmp/code/index.js,在代码中使用 mysql :

    1. var mysql = require('mysql');
    2. exports.handler = function(event, context, callback) {
    3. var connection = mysql.createConnection({
    4. host : 'localhost',
    5. user : 'me',
    6. password : 'secret',
    7. database : 'my_db'
    8. });
    9. connection.connect();
    10. connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
    11. if (error) return callback(error);
    12. console.log('The solution is: ', results[0].solution);
    13. callback(null, results[0].solution);
    14. });
    15. connection.end();
    16. };
  4. 新建 template.yml 文件,例如 /tmp/code/template.yml,内容为:

    1. ROSTemplateFormatVersion: '2015-09-01'
    2. Transform: 'Aliyun::Serverless-2018-04-03'
    3. Resources:
    4. FunDemo:
    5. Type: 'Aliyun::Serverless::Service'
    6. nodejsdemo:
    7. Type: 'Aliyun::Serverless::Function'
    8. Properties:
    9. Handler: index.handler
    10. Runtime: nodejs8
    11. CodeUri: './'

    这个 template.yml 的含义如下:声明一个名为 FunDemo 的 服务,并在这个服务下,再声明一个名为 nodejsdemo 的 函数,配置函数入口为 index.handler,以及函数的 runtime 为 nodejs8。并且,我们指定了 CodeUri 为当前目录。在部署时,Fun 会将 CodeUri 指定的目录打包上传。更多的配置规则 请参考

    文件保存后,/tmp/code 目录的内容应该是这样:

    1. ls -l /tmp/code
    2. -rw-r--r-- 1 tan wheel 522B Jun 18 14:50 index.js
    3. drwxr-xr-x 13 tan wheel 416B Jun 18 14:49 node_modules
    4. -rw-r--r-- 1 tan wheel 297B Jun 18 14:58 template.yml
  5. 使用 Fun 部署:

    1. fun deploy

    执行成功时,会看到相关日志:

    1. using region: cn-hangzhou
    2. using accountId: ***********3557
    3. using accessKeyId: ***********r3Ra
    4. using timeout: 300
    5. Waiting for service FunDemo to be deployed...
    6. Waiting for function nodejsdemo to be deployed...
    7. Waiting for packaging function nodejs code...
    8. package function nodejs code done
    9. function nodejsdemo deploy success
    10. service FunDemo deploy success

    登陆控制台,即可看到相关的服务、函数被创建成功,且触发执行可以可以返回正确的结果。

    这里简单介绍下 fun deploy 所做的事情:使用 Fun 部署时,Fun 会根据这个 template.yml 的配置,为我们创建一个名为 FunDemo 的服务,然后再在该服务下创建一个运行环境为 nodejs8、名称为 nodejsdemo 的函数,并且会将当前目录(CodeUri 指定的目录)打包,作为函数的代码上传到函数计算。因为我们打包的代码包含了 node_modules 目录,而我们安装的库都在这个目录中,所以函数计算就可以直接使用这个库了。

调用可执行文件

您的函数可能会用到一些工具,而这些工具并不是用 Nodejs 写的(例如 shell 脚本或 C++ 或者 go 编译出来的可执行文件)。您仍然可以将它们与代码一起打包,然后在函数中通过运行外部命令的方法来使用它们。

例如,您需要运行一个 shell 脚本 script.sh,您需要将它一同打包到 code 目录下,通过 process.env['FC_FUNC_CODE_PATH'] 命令可以拿到当前代码的路径。

下面的例子中演示了如何运行一个 shell 脚本:

  1. var exec = require('child_process');
  2. exports.handler = function (event, context, callback) {
  3. var scriptPath = process.env['FC_FUNC_CODE_PATH'] + '/script.sh';
  4. exec.exec('bash ' + scriptPath, {}, function (err, stdout, stderr) {
  5. if (err) return callback(err);
  6. console.log(stdout, stderr);
  7. callback(null, stdout);
  8. });
  9. };

需要注意的是,使用 C / C++ / go 编译出来的可执行文件,需要与函数计算的运行环境兼容。函数计算的 Nodejs 运行环境是:

  • Linux 内核版本:Linux 4.4.24-2.al7.x86_64
  • docker 基础镜像:docker pull node:6.10

Python 打包示例

使用自定义模块,需要将它们与代码一起打包。下面通过 Fun 来介绍添加访问 mysql 模块 PyMySQL 的步骤:

  1. 建立一个目录用于存放代码和依赖模块:

    1. mkdir /tmp/code
  2. 新建代码文件,例如 /tmp/code/index.py ,在代码中使用 pymysql:

    1. import pymysql.cursors
    2. # Connect to the database
    3. connection = pymysql.connect(host='localhost',
    4. user='user',
    5. password='passwd',
    6. db='db',
    7. charset='utf8mb4',
    8. cursorclass=pymysql.cursors.DictCursor)
    9. def handler(event, context):
    10. with connection.cursor() as cursor:
    11. # Read a single record
    12. sql = "SELECT count(*) FROM `users`"
    13. cursor.execute(sql)
    14. result = cursor.fetchone()
    15. print(result)
    16. return result
  3. 新建 template.yml 文件,例如 /tmp/code/template.yml,内容为:

    1. ROSTemplateFormatVersion: '2015-09-01'
    2. Transform: 'Aliyun::Serverless-2018-04-03'
    3. Resources:
    4. FunDemo:
    5. Type: 'Aliyun::Serverless::Service'
    6. pythondemo:
    7. Type: 'Aliyun::Serverless::Function'
    8. Properties:
    9. Handler: index.handler
    10. Runtime: python3
    11. CodeUri: './'
  4. 在 /tmp/code 目录下安装依赖。

    不可以直接使用 pip install PyMySQL,这样安装的库文件会散落到系统的很多目录里,我们的目的是将依赖的库文件下载到当前目录,一同打包上传。

    如果没有 docker 环境,且不涉及动态链接库(.so)、编译二进制程序等,只是安装语言依赖,那么可以直接使用 pip install -t . PyMySQL 的方式进行安装。这种方式不管函数计算的执行环境中是否安装了这些 python 库,都会下载下来,会增加代码包的大小。

    但是如果有 docker 环境(PS:如果是 windows 系统,要求使用 docker for windows),那么推荐您总是使用 Fun 安装第三方库本地运行调试发布函数,这样能避免由于开发环境和运行环境不一致所引起的一些问题。特别是当您的函数依赖二进制、动态链接库文件时,请务必使用 Fun 编译相关依赖。

    使用 Fun 安装 PyMySQL 的方法很简单,只需要在项目目录下(本例是 /tmp/code)执行下面的命令:

    1. fun install --runtime python3 --package-type pip PyMySQL

    使用 Fun 注意事项

    1. Fun 命令要求您的机器上已安装 docker。docker 的具体安装步骤,请参阅 相关文档
    2. Fun 用到的镜像存储在 docker 官方镜像库上,国内用户访问速度较慢。建议您使用阿里云镜像加速服务,具体设置请参阅 相关文档
    3. 在 linux 下使用 docker,可能要求 root 权限。所以您需要使用 sudo 的方式启动命令行工具;或者您可以参照 相关文档 设置,以非 root 用户管理 docker。

    安装完成之后,/tmp/code目录的内容应该是这样:

    1. $ ls -al /tmp/code
    2. drwxr-xr-x 3 tan wheel 96 Jun 18 17:08 .fun
    3. -rw-r--r-- 1 tan wheel 560 Jun 18 16:13 index.py

    可以看到,多了一个 .fun 目录,该目录就存放了我们刚才安装的 PyMySQL 的依赖。

    如果安装遇到了问题,通过指定 -v 参数,可以看到更详尽的日志:

    1. $ fun install -v --runtime python3 --package-type pip PyMySQL
    2. skip pulling image aliyunfc/runtime-python3.6:build-1.5.3...
    3. Task => [UNNAMED]
    4. => PYTHONUSERBASE=/code/.fun/python pip install --user PyMySQL
    5. Looking in indexes: http://mirrors.aliyun.com/pypi/simple/
    6. Collecting PyMySQL
    7. Downloading http://mirrors.aliyun.com/pypi/packages/ed/39/15045ae46f2a123019aa968dfcba0396c161c20f855f11dea6796bcaae95/PyMySQL-0.9.3-py2.py3-none-any.whl (47kB)
    8. Installing collected packages: PyMySQL
    9. Successfully installed PyMySQL-0.9.3

    fun isntall 的更多用法,请参考这篇文章

  5. 使用 Fun 进行部署

    1. fun deploy

    提示:如果需要安装动态链接库(.so 文件),也可以使用 fun install 来完成,比如安装 libzbar0,可以直接执行:

    1. fun install --runtime python3 --package-type apt libzbar0

    命令执行完成后,依赖依旧是会被安装到 .fun 目录下,只需要通过 fun 进行本地调试、部署即可使用。

    注意: 因为依赖被安装在了.fun 目录,请确保 template.yml 中的 CodeUri 包含该目录,且必须是顶级子目录。

调用外部命令

用户的函数可能会用到一些工具,而这些工具并不是用 Python 写的(例如 shell 脚本 或 C++ 或者 go 编译出来的可执行文件)。您仍然可以将它们与代码一起打包,然后在函数中通过运行外部命令的方法来使用它们。

例如,您需要运行一个 shell 脚本 script.sh,您需要将它一同打包到 code 目录下,通过 process.env['FC_FUNC_CODE_PATH'] 命令可以拿到当前目录的路径。

下面的例子中演示了如何运行一个 shell 脚本:

  1. import os
  2. import subprocess
  3. def my_handler(event, context):
  4. script_path = os.environ.get('FC_FUNC_CODE_PATH') + '/script.sh'
  5. ret = subprocess.check_output(['bash', script_path])
  6. return ret

需要注意的是,使用 C / C++ / go 编译出来的可执行文件,需要与函数计算的运行环境兼容。函数计算的 Python 运行环境是:

  • Linux内核版本:Linux 4.4.24-2.al7.x86_64
  • docker基础镜像:docker pull python:2.7 ; docker pull python:3.6

Php 打包示例

下面将演示通过 Fun 添加一个 Humble HTTP request library package requests 的步骤:

  1. 建立一个目录用于存放代码和依赖模块:

    1. mkdir /tmp/code
  2. 新建代码文件,例如 /tmp/code/index.php ,在代码中使用requests:

    1. <?php
    2. require_once __DIR__ . "/vendor/autoload.php";
    3. function handler($event, $context){
    4. $headers = array('Accept' => 'application/json');
    5. $options = array('auth' => array('user', 'pass'));
    6. $request = Requests::get('https://www.baidu.com', $headers, $options);
    7. var_dump($request->status_code);
    8. // int(200)
    9. var_dump($request->headers['content-type']);
    10. // string(31) "application/json; charset=utf-8"
    11. var_dump($request->body);
    12. // string(26891) "[...]"
    13. }
  3. 在 /tmp/code 目录下安装依赖:

    • 编辑一个文件取名叫 composer.json

      1. {
      2. "require": {
      3. "rmccue/requests": ">=1.0"
      4. }
      5. }
    • 执行命令composer install --no-dev,安装依赖

      1. cd /tmp/code
      2. composer install --no-dev
    • 安装完成之后,/tmp/code 目录的内容应该是这样:

      1. ls -l /tmp/code
      2. -rw-r--r-- 1 rsong staff 72 8 22 09:54 composer.json
      3. -rw-r--r-- 1 rsong staff 2165 8 22 09:54 composer.lock
      4. -rw-r--r-- 1 rsong staff 523 8 22 09:54 index.php
      5. drwxr-xr-x 5 rsong staff 160 8 22 09:54 vendor
  4. 编写 template.yml,并发布

    • /tmp/code 编写一个名为 template.yml 的文件

      1. ROSTemplateFormatVersion: '2015-09-01'
      2. Transform: 'Aliyun::Serverless-2018-04-03'
      3. Resources:
      4. FunDemo:
      5. Type: 'Aliyun::Serverless::Service'
      6. phpdemo:
      7. Type: 'Aliyun::Serverless::Function'
      8. Properties:
      9. Handler: index.handler
      10. Runtime: php7.2
      11. CodeUri: './'

      这个 template.yml 的含义如下:声明一个名为 FunDemo 的 服务,并在这个服务下,再声明一个名为 phpdemo 的 函数,配置函数入口为 index.handler,以及函数的 runtime 为 nodejs8。并且,我们指定了 CodeUri 为当前目录。在部署时,Fun 会将 CodeUri 指定的目录打包上传。更多的配置规则 请参考

    • 执行 fun deploy 进行发布

      1. fun deploy

      可以看到发布成功的日志:

      1. using region: cn-hangzhou
      2. using accountId: ***********3557
      3. using accessKeyId: ***********r3Ra
      4. using timeout: 300
      5. Waiting for service FunDemo to be deployed...
      6. Waiting for function phpdemo to be deployed...
      7. Waiting for packaging function phpdemo code...
      8. package function phpdemo code done
      9. function phpdemo deploy success
      10. service FunDemo deploy success

      登陆控制台,即可看到相关的服务、函数被创建成功,且触发执行可以可以返回正确的结果。

      这里简单介绍下 fun deploy 所做的事情:使用 Fun 部署时,Fun 会根据这个 template.yml 的配置,为我们创建一个名为 FunDemo 的服务,然后再在该服务下创建一个运行环境为 php7.2、名称为 phpdemo 的函数,并且会将当前目录(CodeUri 指定的目录)打包,作为函数的代码上传到函数计算。因为我们打包的代码包含了 vendor 目录,而我们安装的库都在这个目录中,所以函数计算就可以直接使用这个库了。

如果想打包自定义的package, 请参考Php Runtime 使用自定义package

Java 打包示例

使用 Java 开发时,需要将代码和依赖打成 jar 包上传到函数计算。使用依赖包时,只需要写入 pom.xml 文件,再打成 jar 包即可。您可以使用 maven 打包,也可以使用 IDEA 打包。

使用 maven 打包

例如,使用 OSS Java SDK在 pom.xml 中添加 OSS Java SDK。

  1. <dependencies>
  2. <dependency>
  3. <groupId>com.aliyun.fc.runtime</groupId>
  4. <artifactId>fc-java-core</artifactId>
  5. <version>1.0.0</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.aliyun.oss</groupId>
  9. <artifactId>aliyun-sdk-oss</artifactId>
  10. <version>2.6.1</version>
  11. </dependency>
  12. </dependencies>

在 pom.xml 中添加添加 maven-assembly-plugin 插件,maven-assembly-plugin 插件是为了将依赖打入 jar 包。

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <artifactId>maven-assembly-plugin</artifactId>
  5. <version>3.1.0</version>
  6. <configuration>
  7. <descriptorRefs>
  8. <descriptorRef>jar-with-dependencies</descriptorRef>
  9. </descriptorRefs>
  10. <appendAssemblyId>false</appendAssemblyId> <!-- this is used for not append id to the jar name -->
  11. </configuration>
  12. <executions>
  13. <execution>
  14. <id>make-assembly</id> <!-- this is used for inheritance merges -->
  15. <phase>package</phase> <!-- bind to the packaging phase -->
  16. <goals>
  17. <goal>single</goal>
  18. </goals>
  19. </execution>
  20. </executions>
  21. </plugin>
  22. <plugin>
  23. <groupId>org.apache.maven.plugins</groupId>
  24. <artifactId>maven-compiler-plugin</artifactId>
  25. <configuration>
  26. <source>1.8</source>
  27. <target>1.8</target>
  28. </configuration>
  29. </plugin>
  30. </plugins>
  31. </build>

使用 IDEA 打包

  • 【 Project Struction】->【Artifacts】->【Add Jar】->【From modules with dependencies】-> 【Apply】->【OK】
  • 【Build】->【Build Artifacts】->【build】
  • 请参考 IDEA 官方文档 Packaging a Module into a JAR File

相关链接