AWS Lambda中ffmpeg-static需何权限?错误码126问题排查
我在AWS Lambda上部署了一段代码,功能为下载图片、生成视频并上传至S3。执行以下命令打包上传:
npm install --production zip -r my-lambda-function.zip ./
但出现错误码126:
2023-02-17T09:27:55.236Z 5c845bb6-02c1-41b0-8759-4459591b57b0 INFO Error: ffmpeg exited with code 126 at ChildProcess.<anonymous> (/var/task/node_modules/fluent-ffmpeg/lib/processor.js:182:22) at ChildProcess.emit (node:events:513:28) at ChildProcess._handle.onexit (node:internal/child_process:291:12) 2023-02-17T09:27:55.236Z 5c845bb6-02c1-41b0-8759-4459591b57b0 INFO Error: ffmpeg exited with code 126 at ChildProcess.<anonymous> (/var/task/node_modules/fluent-ffmpeg/lib/processor.js:182:22) at ChildProcess.emit (node:events:513:28) at ChildProcess._handle.onexit (node:internal/child_process:291:12)
请问是否需要为ffmpeg设置特定权限?我遗漏了什么?
代码实现
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3' import { fromNodeProviderChain } from '@aws-sdk/credential-providers' import axios from 'axios' import pathToFfmpeg from 'ffmpeg-static' import ffmpeg from 'fluent-ffmpeg' import fs from 'fs' ffmpeg.setFfmpegPath(pathToFfmpeg) const credentials = fromNodeProviderChain({ clientConfig: { region: 'eu-central-1', }, }) const client = new S3Client({ credentials }) export const handler = async (event, context) => { try { let body let statusCode = 200 const query = event?.queryStringParameters if (!query?.imgId && !query?.video1Id && !query?.video2Id) { return } const imgId = query?.imgId const video1Id = query?.video1Id const video2Id = query?.video2Id console.log( `Parameters received, imgId: ${imgId}, video1Id: ${video1Id}, video2Id: ${video2Id}` ) const imgURL = getFileURL(imgId) const video1URL = getFileURL(`${video1Id}.mp4`) const video2URL = getFileURL(`${video2Id}.mp4`) const imagePath = `/tmp/${imgId}` const video1Path = `/tmp/${video1Id}.mp4` const video2Path = `/tmp/${video2Id}.mp4` const outputPath = `/tmp/${imgId}.mp4` await Promise.all([ downloadFile(imgURL, imagePath), downloadFile(video1URL, video1Path), downloadFile(video2URL, video2Path), ]) await new Promise((resolve, reject) => { console.log('Input files downloaded') ffmpeg() .input(imagePath) .inputFormat('image2') .inputFPS(30) .loop(1) .size('1080x1080') .videoCodec('libx264') .format('mp4') .outputOptions([ '-tune animation', '-pix_fmt yuv420p', '-profile:v baseline', '-level 3.0', '-preset medium', '-crf 23', '-movflags +faststart', '-y', ]) .output(outputPath) .on('end', () => { console.log('Output file generated') resolve() }) .on('error', (e) => { console.log(e) reject() }) .run() }) await uploadFile(outputPath, imgId + '.mp4') .then((url) => { body = JSON.stringify({ url, }) }) .catch((error) => { console.error(error) statusCode = 400 body = error?.message ?? error }) console.log(`File uploaded to S3`) const headers = { 'Content-Type': 'application/json', 'Access-Control-Allow-Headers': 'Content-Type', 'Access-Control-Allow-Origin': 'https://tikex.com, https://borespiac.hu', 'Access-Control-Allow-Methods': 'GET', } return { statusCode, body, headers, } } catch (error) { console.error(error) return { statusCode: 500, body: JSON.stringify('Error fetching data'), } } } const downloadFile = async (url, path) => { try { console.log(`Download will start: ${url}`) const response = await axios(url, { responseType: 'stream', }) if (response.status !== 200) { throw new Error( `Failed to download file, status code: ${response.status}` ) } response.data .pipe(fs.createWriteStream(path)) .on('finish', () => console.log(`File downloaded to ${path}`)) .on('error', (e) => { throw new Error(`Failed to save file: ${e}`) }) } catch (e) { console.error(`Error downloading file: ${e}`) } } const uploadFile = async (path, id) => { const buffer = fs.readFileSync(path) const params = { Bucket: 't44-post-cover', ACL: 'public-read', Key: id, ContentType: 'video/mp4', Body: buffer, } await client.send(new PutObjectCommand(params)) return getFileURL(id) } const getFileURL = (id) => { const bucket = 't44-post-cover' const url = `https://${bucket}.s3.eu-central-1.amazonaws.com/${id}` return url }
Lambda权限策略
已添加AWSLambdaBasicExecutionRole-16e770c8-05fa-4c42-9819-12c468cb5b49权限,策略如下:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "arn:aws:logs:eu-central-1:634617701827:*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": [ "arn:aws:logs:eu-central-1:634617701827:log-group:/aws/lambda/promo-video-composer-2:*" ] }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::example-bucket", "arn:aws:s3:::example-bucket/*" ] }, { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": [ "arn:aws:logs:*:*:*" ] }, { "Effect": "Allow", "Action": [ "ec2:DescribeNetworkInterfaces" ], "Resource": [ "*" ] }, { "Effect": "Allow", "Action": [ "sns:*" ], "Resource": [ "*" ] }, { "Effect": "Allow", "Action": [ "cloudwatch:*" ], "Resource": [ "*" ] }, { "Effect": "Allow", "Action": [ "kms:Decrypt" ], "Resource": [ "*" ] } ] }
本地ffmpeg信息
janoskukoda@Janoss-MacBook-Pro promo-video-composer-2 % ls -l $(which ffmpeg) lrwxr-xr-x 1 janoskukoda admin 35 Feb 10 12:50 /opt/homebrew/bin/ffmpeg -> ../Cellar/ffmpeg/5.1.2_4/bin/ffmpeg
错误码126表示无法执行ffmpeg二进制文件,核心原因是Lambda运行环境(Amazon Linux 2)中,ffmpeg-static安装的二进制文件没有可执行权限,或者本地打包时的二进制文件与Lambda架构不兼容。
具体修复步骤:
修复ffmpeg二进制文件权限
打包前,确保node_modules/ffmpeg-static目录下的二进制文件拥有可执行权限。在本地执行:chmod +x node_modules/ffmpeg-static/ffmpeg然后再重新打包:
zip -r my-lambda-function.zip ./确认架构兼容性
Lambda默认使用x86_64架构,如果你在Apple Silicon(arm64)Mac上安装ffmpeg-static,会下载arm架构的二进制文件,放到x86_64的Lambda环境中无法运行。解决方法:- 强制安装x86_64版本的ffmpeg-static:
npm install ffmpeg-static --arch=x64 --platform=linux - 或者在Lambda控制台中将函数架构切换为
arm64(对应Graviton2处理器)。
- 强制安装x86_64版本的ffmpeg-static:
修正S3权限匹配问题
你的Lambda策略中S3权限指向的是example-bucket,但代码中实际使用的是t44-post-cover桶,需要修改策略中的Resource字段,替换为实际桶的ARN:{ "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::t44-post-cover", "arn:aws:s3:::t44-post-cover/*" ] }修复下载函数的异步逻辑
当前downloadFile函数使用pipe但没有正确返回Promise,可能导致文件未完全下载就执行ffmpeg命令,引发错误。修改downloadFile:const downloadFile = async (url, path) => { try { console.log(`Download will start: ${url}`) const response = await axios(url, { responseType: 'stream', }) if (response.status !== 200) { throw new Error(`Failed to download file, status code: ${response.status}`) } return new Promise((resolve, reject) => { response.data .pipe(fs.createWriteStream(path)) .on('finish', () => { console.log(`File downloaded to ${path}`) resolve() }) .on('error', (e) => { reject(new Error(`Failed to save file: ${e}`)) }) }) } catch (e) { console.error(`Error downloading file: ${e}`) throw e } }
额外建议
- 可以考虑使用Lambda层来管理ffmpeg,避免每次打包都包含二进制文件,提升部署效率。
- 测试时可以在Lambda中添加日志,打印
pathToFfmpeg的值,确认路径正确,同时检查该路径下的文件权限。
内容的提问来源于stack exchange,提问作者János

