打算用AWS Lambda来做一个图片转视频的python微服务,但是发现相关的库如moviepy/opencv都很大,超过了Lambda 256M的package大小限制。因此,尝试着用Docker来实现这个Lambda,只要不超过500M就可以免费用AWS一年。本文适合希望了解通过Lambda+Docker搭建微服务的朋友。因为有些坑还是花了不少时间解决的,所以短时间内有一定的参考价值。
装Docker
我之前没有用过Docker,所以花了一些时间准备,有类似需要的朋友可以参考(https://blog.csdn.net/yingjil/article/details/130677662)
装AWS CLI
brew install awscli
配置AWS CLI
后面用来连接docker和AWS
jak@jphome TravelVideoAPI % aws configure
AWS Access Key ID [****************WKF4]:
AWS Secret Access Key [****************KasP]:
Default region name [us-west-2]:
Default output format [None]:
准备AWS ECR
希望用CLI命令行的朋友可以参考官方文档。这里我用UI演示一下。
登录AWS Console,选择ECR
点击Create repository(注意,不要选Public。我在这里浪费了不少时间。Public没有Private仓库 500M的限制,但是Lambda目前不支持Public。)
自定义Image的名字(我这个名字有点长了,因为是CDK创建的)。
选中新创建的Repo,点击View push commands
本文后面的操作基本就是按照这个顺序,这里的命令已经根据你的账号信息适配,你直接运行即可。
准备Lambda代码
更完整的目录结构如下(省去了和本文无关的内容)
jak@jphome TravelVideoAPI % tree
.
├── README.md
├── app.py
├── cdk.json
├── my_lambda
│ ├── Dockerfile
│ ├── lambda_handler.py
│ └── requirements.txt
├── requirements.txt
└── tests
├── data
│ ├── lambda_event_sample.json
│ └── test_image1.png
├── requirements-dev.txt
└── test_lambda.py
准备docker image
因为是用CDK生成的,image的名字有点长,如果你手动做就可以自定义一个更可读的名字。和docker相关的内容都在my_lambda目录。
cd my_lambda
Dockerfile内容
这里的Key都是假的,帮助理解。把Key写在code里不是好习惯,后面会改进。
FROM public.ecr.aws/lambda/python:3.9
# Install the function's dependencies using file requirements.txt
# from your project folder.
COPY requirements.txt ./
RUN pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"
# Copy function code
COPY *.py ${LAMBDA_TASK_ROOT}
ENV OPENAI_API_KEY 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
ENV DOUYIN_SECRET_ttbaedcc5025d2e24301 c40894832c3158146603501235defba5f80baf
ENV AWS_ACCESS_KEY_ID AKIAU43UQ62MUDWKF4
ENV AWS_SECRET_ACCESS_KEY WcqoLjBdtutydAIOIInJ/mKe7WscNt5JYF3oKasP
ENV AWS_REGION us-west-2
# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "lambda_handler.handler" ]
授权你的docker cli可以访问你的ECR
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 336833330237.dkr.ecr.us-west-2.amazonaws.com
构建你的Image
docker build -t cdk-hnb659fds-container-assets-336833330237-us-west-2 .
踩过的坑
ERROR: failed to solve: public.ecr.aws/lambda/python:3.9: pulling from host public.ecr.aws failed with status code [manifests 3.9]: 403 Forbidden
解决办法是运行以下命令,从而授权docker去访问ecr-public。具体参考这里。
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/lambda
本地测试
本地把Lambda跑起来
docker run -p 9000:8080 cdk-hnb659fds-container-assets-336833330237-us-west-2
调用一下
def test_api_locally():
# read from test_image1.png from data folder
with open("data/test_image1.png", 'rb') as f:
image1_bytes = f.read()
data = {"body": "{\"text1\":\"一家三口\",\"image1\":\"" + base64.b64encode(image1_bytes).decode('utf-8') + "\"}"}
res = requests.post(f"http://localhost:9000/2015-03-31/functions/function/invocations", json=data)
r = json.loads(res.text)
with open("test_output_locally.mp4", 'wb') as f:
f.write(base64.b64decode(r['body'].encode('utf-8')))
assert res.status_code == 200
生成效果mp4。本地测试通过,接下来就要上传AWS了。
这里踩过一个坑
curl -XPOST “http://localhost:9000/2015-03-31/functions/function/invocations” -d ‘{}’
报错
{“errorMessage”: “Unable to import module ‘lambda_handler’: cannot import name ‘DEFAULT_CIPHERS’ from ‘urllib3.util.ssl_’ (/var/task/urllib3/util/ssl_.py)”, “errorType”: “Runtime.ImportModuleError”, “requestId”: “9c9bce02-eb70-45de-b287-43fbb7c388c9”, “stackTrace”: []}%
解决办法在(https://github.com/psf/requests/issues/6443)找到,调整requirements.txt如下
requests < 2.30.0
提交Image到ECR
docker tag cdk-hnb659fds-container-assets-336833330237-us-west-2:latest 336833330237.dkr.ecr.us-west-2.amazonaws.com/cdk-hnb659fds-container-assets-336833330237-us-west-2:latest
docker push 336833330237.dkr.ecr.us-west-2.amazonaws.com/cdk-hnb659fds-container-assets-336833330237-us-west-2:latest
AWS上创建Lambda使用ECR里的Image
TBD
远程测试Lambda
def test_api_remotely():
# read from test_image1.png from data folder
with open("data/test_image1.png", 'rb') as f:
image1_bytes = f.read()
# data = {"body": "{\"text1\":\"一家三口\",\"image1\":\"" + base64.b64encode(image1_bytes).decode('utf-8') + "\"}"}
data = {"text1": "一家三口", "image1": base64.b64encode(image1_bytes).decode('utf-8')}
res = requests.post("https://tterehkxbzlu6thhgac6owrm0kmjha.lambda-url.us-west-2.on.aws/", json=data)
with open("test_output_remotely.mp4", 'wb') as f:
f.write(base64.b64decode(res.text.encode('utf-8')))
assert res.status_code == 200
生成效果mp4。