我的目标是构建一个 AWS Lambda 层,为运行 python 3.8 的 AWS Lambda 提供 mariadb 模块。
按照这些说明,我需要在与 AWS Lambda 平台兼容的实例上安装 mariadb 模块。像这样安装模块...
python3 -m pip install mariadb --target lambda-layer/python/lib/python3.8/site-packages
...失败并显示错误消息...
mariadb_config not found.
Please make sure, that MariaDB Connector/C is installed on your system, edit the
configuration file 'site.cfg' and set the 'mariadb_config'
noption, which should point to the mariadb_config utility.
我需要什么来解决这个问题?
我假设安装“MariaDB Connector/C”是先决条件?但如何做到这一点?以及如何以可以将其打包成可用于定义 AWS Lambda 层的 zip 的方式进行操作?
一些上下文
我在一个 Amazon 实例上运行了它,我认为它与 Lamba 主机相同,以显示“Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type - ami-02769748522663066”。cat /etc/os-release
返回...
NAME="Amazon Linux AMI"
VERSION="2018.03"
ID="amzn"
ID_LIKE="rhel fedora"
VERSION_ID="2018.03"
PRETTY_NAME="Amazon Linux AMI 2018.03"
ANSI_COLOR="0;33"
CPE_NAME="cpe:/o:amazon:linux:2018.03:ga"
HOME_URL="http://aws.amazon.com/amazon-linux-ami/"
不同的实例 AMI 是否更适合打包 python3.8 Lambda 层?
我用这个设置了python3(盒子里没有python 3.8):
sudo yum install python36 -y
更新和自我回答
所以简短的回答是,目前这是不可能的。MariaDb 连接器不支持亚马逊风格的 linux。但是,有一个备用 python 库 PyMySQL 将连接到 mariadb 服务器。
这是一个 python3.8 AWS Lambda 函数的内容,它为 Lambda 制造 AWS 函数层。
工厂 (AWS Lambda) 函数
import json, boto3
from pathlib import Path
import os, sys, subprocess, shutil
from zipfile import ZipFile
def run( commandLine, captures, workingDir):
options = {}
if workingDir is not None:
options['cwd'] = workingDir
if 'standard-output' in captures:
options['stdout'] = subprocess.PIPE
if 'error-output' in captures:
options['stderr'] = subprocess.PIPE
proc = subprocess.run( commandLine, **options)
outputLines = None
errorLines = None
if 'standard-output' in captures:
outputLines = proc.stdout.decode('UTF-8').strip().split( os.linesep)
if 'error-output' in captures:
errorLines = proc.stderr.decode('UTF-8').strip().split( os.linesep)
if outputLines == ['']:
outputLines = None
if errorLines == ['']:
errorLines = None
return proc.returncode, outputLines, errorLines
def lambda_handler(event, context):
request = json.loads( event['body'])
sitePackages = '/tmp/aws-lambda-layer/lambda-layer/python/lib/python3.8/site-packages'
Path( sitePackages).mkdir(parents=True, exist_ok=True)
lines = request['requirements']
print( lines)
bucket = request['bucket']
packageKey = request['key']
ExtraArgs = request.get('ExtraArgs')
layerName = request['layer-name']
licenceInfo = request.get('license-info')
requiresFN = '/tmp/aws-lambda-layer/requirements.txt'
origin = os.environ.get( 'ORIGIN', '*')
with open( requiresFN, 'w') as file:
file.writelines( lines)
returncode, outputLines, errorLines = run(
[sys.executable, '-m', 'pip', 'install', '-r', requiresFN, '--target', sitePackages],
['standard-output','error-output'], '/tmp/aws-lambda-layer')
base = '/tmp/aws-lambda-layer/lambda-layer'
zipFN = '/tmp/aws-lambda-layer/package.zip'
l = len( base) + 1
with ZipFile( zipFN, 'w') as package:
for folderName, subfolders, filenames in os.walk( base):
for filename in filenames:
filePath = os.path.join( folderName, filename)
path = filePath[l:]
if os.path.basename( filePath) != 'yum.log':
package.write( filePath, path)
if ExtraArgs is None:
ExtraArgs = {}
ExtraArgs['ContentType'] = 'application/zip'
runtime_region = os.environ.get('AWS_REGION')
parms = {}
if runtime_region is not None:
parms['region_name'] = runtime_region
s3Client = boto3.client( 's3' , **parms)
lambdaClient = boto3.client( 'lambda', **parms)
s3Client.upload_file( zipFN, bucket, packageKey, ExtraArgs)
try:
shutil.rmtree( '/tmp/aws-lambda-layer', ignore_errors=True)
except:
print( 'Errors in cleaning up /tmp/aws-lambda-layer')
description = 'python 3.8 requirements:' + '\n'.join( lines)
parms = {'LayerName' : layerName,
'Description': description,
'Content': { 'S3Bucket': bucket, 'S3Key': packageKey},
'CompatibleRuntimes': ['python3.8']}
if licenceInfo is not None:
parms['LicenseInfo'] = licenceInfo
try:
response = lambdaClient.publish_layer_version( **parms)
response = response['layer']['LayerVersionArn']
except:
print('Error in publishing.')
response = None
try:
lambdaClient.delete_object( Bucket=bucket, Key=packageKey)
except:
print( 'Failure to delete s3 staging package.')
statusCode = 200
response = {'pip-return-code': returncode, 'out': outputLines,
'err': errorLines, 'layer': response}
ret = {'statusCode': statusCode,
'body': json.dumps( response),
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Origin': origin}}
return ret
在客户端,可以像这样调用(再次使用python脚本)......工厂函数函数接受python pip冻结要求并制造一个层,而不是为指定的模块提供服务。就我而言, requirements.txt 将包括 PyMySQL 。
import json
import boto3
# Config section. Adapt as required.
lambdaRegion = 'ap-southeast-2'
bucket = 'some-bucket'
key = 'provisioning-workspace/layer-package.zip'
layerName = 'someLayerName'
filenm = 'some-path\\requirements.txt'
functionName = 'layer-factory-p38'
# End config section.
parms = {}
if lambdaRegion is not None:
parms['region_name'] = lambdaRegion
client = boto3.client( 'lambda', **parms)
def invoke( functionName, qualifier, inData):
parms = {'FunctionName' : functionName,
'InvocationType': 'RequestResponse',
'LogType' : 'None',
'Payload' : str.encode( json.dumps( {'body': json.dumps( inData)}))}
if qualifier is not None:
parms['Qualifier'] = str( qualifier)
response = client.invoke( **parms)
response = json.loads( response['Payload'].read().decode('utf-8'))
try:
statusCode = int( response['statusCode'])
except:
statusCode = 0
try:
response = json.loads( response['body'])
except:
print( response)
response = {}
return statusCode, response
def buildLayer( factoryFunctionName, requirementsFN, layerName, bucket, key):
with open( requirementsFN, 'r') as file:
lines = file.readlines()
statusCode, response = invoke( factoryFunctionName, None, {
'requirements': lines, 'bucket': bucket, 'key': key, 'layer-name': layerName})
return response['layer']
layerArn = buildLayer( functionName, filenm, layerName, bucket, key)
print( layerArn)