全部产品

Python 运行环境

更新时间:2019-04-16 15:13:54

函数计算目前支持以下 Python 运行环境:

  • Python 2.7 ( runtime = python2.7 )
  • Python 3.6 ( runtime = python3 )

本文分以下几个部分对 Python 的运行环境进行介绍:

使用logging

函数向 stdout 打印的内容会被收集到创建 service 时指定的 Logstore 中,有两种打 log 的方法:

  1. 直接使用print: 这会把内容原样地输出到日志中。

    1. def my_handler(event, context):
    2. print 'hello world'
    3. return 'done'

    上面的代码输出的日志内容是:

    1. message:hello world
  2. 使用 logging 模块: 这会在每条日志中包含时间 / requestId / 日志级别等信息。

    1. import logging
    2. def my_handler(event, context):
    3. logger = logging.getLogger()
    4. logger.info('hello world')
    5. return 'done'

    上面的代码输出的日志内容是:

    1. message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad89118e5 [INFO] hello world

推荐使用 logging 模块打印 log,自动包含 requestId 能信息方便在出错的时候定位问题日志。

使用内置模块

除了 Python 的标准模块,函数计算的 Python 运行环境中还包含了一些常用模块,用户可以直接引用,目前包含的模块有:

模块名称 模块介绍 相关链接
oss2 2.6.0 OSS SDK https://github.com/aliyun/aliyun-oss-python-sdk
tablestore 4.6.0 表格存储 SDK https://github.com/aliyun/aliyun-tablestore-python-sdk
aliyun-fc2 2.1.0 函数计算 SDK https://github.com/aliyun/fc-python-sdk
aliyun-python-sdk-ecs 4.10.1 云服务器 SDK https://github.com/aliyun/aliyun-openapi-python-sdk/tree/master/aliyun-python-sdk-ecs
aliyun-python-sdk-vpc 3.0.2 专用网络 SDK https://github.com/aliyun/aliyun-openapi-python-sdk/tree/master/aliyun-python-sdk-vpc
aliyun-python-sdk-rds 2.1.4 云数据库 SDK https://github.com/aliyun/aliyun-openapi-python-sdk/tree/master/aliyun-python-sdk-rds
aliyun-python-sdk-kms 2.5.0 密钥管理服务 SDK https://github.com/aliyun/aliyun-openapi-python-sdk/tree/master/aliyun-python-sdk-kms
pydatahub 2.11.2 DataHub SDK https://github.com/aliyun/aliyun-datahub-sdk-python
aliyun-mns 1.1.5 消息服务 https://github.com/gikoluo/aliyun-mns
aliyun-python-sdk-cdn 2.6.2 CDN 服务 https://github.com/aliyun/aliyun-openapi-python-sdk/tree/master/aliyun-python-sdk-cdn
aliyun-python-sdk-ram 3.0.0 访问控制 RAM https://github.com/aliyun/aliyun-openapi-python-sdk/tree/master/aliyun-python-sdk-ram
aliyun-python-sdk-sts 3.0.0 访问控制 STS https://github.com/aliyun/aliyun-openapi-python-sdk/tree/master/aliyun-python-sdk-sts
aliyun-log-python-sdk 0.6.38 日志服务 SLS https://github.com/aliyun/aliyun-log-python-sdk
wand 0.4.4 图片处理库 http://docs.wand-py.org/en/0.4.4/
opencv 3.3.0.10 计算机视觉库 http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_setup/py_intro/py_intro.html
numpy 1.13.3 科学计算库 http://www.numpy.org/
scipy 1.0.0 科学计算库 https://www.scipy.org/
matplotlib 2.0.2 绘图库 https://matplotlib.org/
scrapy 1.4.0 数据抓取库 https://scrapy.org/
注意:之前使用 aliyun-python-sdk-iot 6.1.0 内置版本的函数, 请尽快使用第三方打包的形式,使用 aliyun-python-sdk-iot 最新版本。

例如使用 wand 进行图片翻转的函数如下:

  1. from wand.image import Image
  2. def my_handler(event, context):
  3. with Image(blob=event) as img:
  4. print img.size
  5. with img.clone() as i:
  6. i.rotate(180)
  7. return i.make_blob()

注意:上面的函数直接使用 event 作为图片的二进制数据并且直接把生成的图片作为二进制数据返回。

tips: 还有其他第三方库使用的小 demo 可以查看这里 fc-python-demo

使用自定义的模块

如果用户需要使用自定义的模块,则需要将它们与代码一起打包。下面将演示通过 fcli 添加一个访问 mysql 的模块 PyMySQL 的步骤:

  1. 建立一个目录用于存放代码和依赖模块:
    1. mkdir /tmp/code
  2. 新建代码文件,例如 /tmp/code/main.py,在代码中使用 pymysql:

    1. import pymysql.cursors
    2. connection = None
    3. # my_initializer: initializer function
    4. def my_initializer(context):
    5. global connection
    6. connection = pymysql.connect(host='localhost',
    7. user='user',
    8. password='passwd',
    9. db='db',
    10. charset='utf8mb4',
    11. cursorclass=pymysql.cursors.DictCursor)
    12. def my_handler(event, context):
    13. global connection
    14. with connection.cursor() as cursor:
    15. # Read a single record
    16. sql = "SELECT count(*) FROM `users`"
    17. cursor.execute(sql)
    18. result = cursor.fetchone()
    19. print(result)
    20. return result
    • 为什么将连接数据库的操作放入 initializer 中?

      Initializer 保证在函数实例的生命周期内成功执行且只执行一次。建立数据库连接的操作属于应用层面的冷启动开销,为维持此连接在函数实例的生命周期内有效,且不因后续多次请求而重复建立连接带来的冷启动开销,故将此操作放在 initializer 中,避免了应用层冷启动开销影响函数性能。

    • 为什么使用 initializer 比使用 global variable 方式来初始化更好?

      Initializer 可以保证在 invoke 请求到来之前执行成功且在函数实例生命周期内仅执行一次,使用 global variable 方式只保证了后一点,在请求触发新的函数实例/代码升级/FC系统周期性回收或更新容器时仍避免不了冷启动开销的影响。

  3. 在 /tmp/code 目录下安装依赖。

    • 简单粗暴法

      1. cd /tmp/code
      2. pip install -t . PyMySQL

      方法简单有效,但是利用 pip install -t . PyMySQL, 会把 PyMySQL 依赖的 python 库都会下载下来,不管函数计算的执行环境中是否安装了这些 python 库,会增加代码包的大小。

    • 优雅法

      1. cd /tmp
      2. fcli shell
      3. sbox -t python2.7 -d code
      4. pip install --install-option="--install-lib=$(pwd)" PyMySQL

      建议有优先使用 install-option 参数的方法,

      a. 不会在本地重复下载执行环境中已有的 python 库,会大大减少本地第三方包的大小, 如下图所示(红线框住的本地不会重新下载):py-install

      b. 和线上执行环境保持了一致,尤其是一些库依赖一些库so文件的时候,能提前准备好相关的so文件

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

    1. ls -l /tmp/code
    2. drwxr-xr-x 9 rockuw staff 306 Jul 5 16:48 PyMySQL-0.7.11.dist-info
    3. -rw-r--r-- 1 rockuw staff 74 Jul 5 16:02 main.py
    4. drwxr-xr-x 26 rockuw staff 884 Jul 5 16:48 pymysql
  4. 使用 fcli 创建函数并调用。

    1. ./fcli shell
    2. mkf my-func -h main.my_handler -i main.my_initializer --runtime python2.7 -d /tmp/code
    3. invk my-func
需要注意的是,如果引用的 module 使用 C / C++ / go 编译出来的可执行文件或者库文件,请查看 sbox

调用外部命令

用户的函数可能会用到一些工具,而这些工具并不是用 Python 写的(例如 shell 脚本 / C++ 或者 go 编译出来的可执行文件)。用户仍然可以将它们与代码一起打包,然后在函数中通过运行外部命令的方法来使用它们。下面的例子中演示了如何运行一个 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

推荐使用 fcli 工具 sbox 命令,下面以 runtime 为 python2.7,安装 mysql-python (含有 .so 文件)为例:

  1. 执行 sbox -d code -t python2.7 ,其中 -d 参数指定代码所在目录,它将被挂载到沙盒环境的 “/code” 位置;-t 参数指定语言类型;(注:第一次执行这个命令由于要 pull image ,可能要耗费不少时间,请耐心等待…)。

  2. 进入沙盒环境后,运行 pip install -t . mysql-python 安装依赖库。

  3. 完成后执行 exit 退出沙盒环境。此时 mysql-python 库 (含有 _mysql.so ) 已经安装到了 code 目录下。

注意

  1. sbox 命令要求您的机器上已安装 docker。docker 的具体安装步骤,请参阅 相关文档

  2. sbox 用到的镜像存储在 docker 官方镜像库上,国内用户访问速度较慢。建议您使用阿里云镜像加速服务,具体设置请参阅 相关文档

  3. 在 linux 下使用 docker,要求有 root 权限。所以您需要使用 sudo fcli shell 的方式启动命令行工具;或者您可以参照 相关文档 设置,以非 root 用户管理 docker。

  4. 推荐您总是在沙盒环境中打包第三方库,测试函数和调查问题,这样能避免由于开发环境和运行环境不一致所引起的一些问题。特别是当您的函数依赖二进制文件时,请在沙盒环境中编译相关依赖。

  1. songluo@demo $ fcli shell
  2. Welcome to the function compute world. Have fun!
  3. >>> sbox -d code -t python2.7
  4. Entering the container. Your code is in the /code direcotry.
  5. root@c5adc6ffd861:/code# pip install -t . mysql-python
  6. Collecting mysql-python
  7. Downloading MySQL-python-1.2.5.zip (108kB)
  8. 100% |████████████████████████████████| 112kB 440kB/s
  9. Building wheels for collected packages: mysql-python
  10. Running setup.py bdist_wheel for mysql-python ... done
  11. Stored in directory: /root/.cache/pip/wheels/38/a3/89/ec87e092cfb38450fc91a62562055231deb0049a029054dc62
  12. Successfully built mysql-python
  13. Installing collected packages: mysql-python
  14. Successfully installed mysql-python-1.2.5
  15. root@c5adc6ffd861:/code# exit

如果您的依赖中包含 .so 等动态链接文件,需要将其放到当前目录的 lib 目录下,因为函数计算的执行环境会将 lib 目录放到 path 中

错误处理

函数在执行过程如果抛出异常,那么函数计算会把异常捕获并将异常信息返回。例如下面的代码:

  1. def my_handler(event, context):
  2. raise Exception('something is wrong')

调用时收到的响应为:

  1. {
  2. "errorMessage": "something is wrong",
  3. "errorType": "Exception",
  4. "stackTrace": [
  5. [
  6. "File \"/code/main.py\"",
  7. "line 2",
  8. "in my_handler",
  9. "raise Exception('something is wrong')"
  10. ]
  11. ]
  12. }

发生异常时,函数调用的响应的 HTTP header 中会包含 X-Fc-Error-Type: UnhandledInvocationError。关于函数计算的错误类型请参考文章 错误类型

您在使用过程中遇到任何问题请 联系我们