亚马逊AWS官方博客

快速构建基于 Lambda 容器镜像的 OCR 应用

摘要

AWS Lambda函数现已支持打包和部署容器镜像,开发者通过官方提供或自己构建镜像文件,可以非常方便利用现有的开发工具,工作流轻松构建基于AWS Lambda的应用程序。基于容器打包的应用通过AWS Lambda可以实现更为简便的操作部署,相比EC2有着更为快速的启动时间 ,更为强大的并发扩展以及高可用,同时无缝与140余种AWS服务集成。

本文将展示如何基于自建镜像(public.ecr.aws/bitnami/python:3.7),利用AWS官方提供的运行时接口客户端(RIC)和运行时接口仿真器(RIE),构建运行在AWS Lambda上的OCR应用。

 

前言

对于机器学习,图像处理等依赖库构建复杂且文件较大的应用,AWS Lambda支持最大10GB的容器镜像,开发者可以直接使用熟悉的容器开发工具(docker)在本地构建测试,并将容器镜像推送到Amazon ECR(全托管的容器注册表),之后通过指定Amazon ECR镜像来部署Lambda函数,免去了以往Lambda Layer构建流程,也无需受限于Lambda Layer的大小限制(250MB)。

伴随AWS Lambda对容器镜像支持的特性发布,AWS官方提供了一组Lambda基础镜像,可在Amazon ECR(gallery.ecr.aws/lambda)和Docker Hub(amazon/aws-lambda-python)上获取,该基础镜像预装了包括Node.js,Python,Java等语言的Lambda运行时,必要组件以及构建基础镜像的dockerfile 。同时AWS官方还开源了运行时接口客户端(RIC)和运行时接口仿真器(RIE),方便用户构建同Lambda兼容的容器镜像并进行本地测试。

OCR应用我们基于tesseract(最早由HP Lab开发并于2005年开源)实现,其中软件依赖如pillow,libtesseract我们利用AWS  进行本地安装,或者用户也可以选取官方镜像public.ecr.aws/lambda编译以尽可能保证同Lambda兼容。

流程概览

本文构建的OCR应用会利用到Python,Shell作为开发语言,PIP/Docker作为开发工具,构建流程分成如下部分:1)软件依赖库的构建,包括pillow,libtesseract编译;2)Lambda兼容镜像的构建,包括RIC/RIE的安装配置,Lambda业务代码的打包;3)Lambda业务代码实现,通过简单的代码调用生成的pytesseract库返回图片识别结果;4)本地调试验证,通过RIE实现本地功能调试和迭代。

其中构建容器镜像的软件依赖库有以下几个途径:方案一,直接利用已经包含软件依赖库的容器镜像,其dockerfile示例如下:

FROM public.ecr.aws/myrepo/shared-lib-layer:1 AS shared-lib-layer
# Layer code
WORKDIR /opt
COPY --from=shared-lib-layer /opt/ .

方案二,利用已经构建好的Lambda Layer,通过curl的形式拉取到新的镜像当中,其dockerfile示例如下:

ARG AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-"cn-northwest-1"}
ARG AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-""}
ARG AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-""}
ENV AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}

RUN apk add aws-cli curl unzip

RUN mkdir -p /opt

RUN curl $(aws lambda get-layer-version-by-arn --arn arn:aws:lambda:us-east-1:1234567890123:layer:shared-lib-layer:1 --query 'Content.Location' --output text) --output layer.zip
RUN unzip layer.zip -d /opt
RUN rm layer.zip

方案三,完全从零开始的用户可以考虑直接在容器里面构建软件依赖库,其dockerfile示例如下:

FROM python:3.8-alpine AS installer
#Layer Code
COPY extensionssrc /opt/
COPY extensionssrc/requirements.txt /opt/
RUN pip install -r /opt/requirements.txt -t /opt/extensions/lib

FROM scratch AS base
WORKDIR /opt/extensions
COPY --from=installer /opt/extensions .

接下来的OCR方案考虑到tesseract的依赖构建相对复杂,为了构建流程的独立和依赖库的共享,我们将采用方案二,即先利用Shell和Docker构建Lambda业务代码调用的所有依赖,再将构建完毕后的zip包存放到Lambda Layer中供后续Lambda镜像构建调用。

创建步骤

软件依赖库的构建

首先安装pillow,创建requirements文件,写入以下内容。

pillow

接着创建shell脚本(build_py37_pkgs.sh),写入以下内容并执行,执行完毕后会在相同目录下生成pythonlibs-layer.zip文件。

set -e
rm -rf pythonlibs-layer.zip exit 0
rm -rf python/ exit 0
docker run -v "$PWD":/var/task "lambci/lambda:build-python3.7" /bin/sh -c "pip install -r requirements.txt -t python/lib/python3.7/site-packages/; exit"
chmod 777 python/
zip -r pythonlibs-layer.zip python > /dev/null
rm -rf python/

Pillow构建完毕后,开始构建tesseract依赖,创建dockerfile(Dockerfile-tess4),文件内容可以直接参考这里

FROM lambci/lambda-base:build

#Proxy setup if exists
#ENV http_proxy 'http://ip:port'
#ENV https_proxy 'https://ip:port'

ARG LEPTONICA_VERSION=1.78.0
ARG TESSERACT_VERSION=4.1.0-rc4
ARG AUTOCONF_ARCHIVE_VERSION=2017.09.28
ARG TMP_BUILD=/tmp
ARG TESSERACT=/opt/tesseract
ARG LEPTONICA=/opt/leptonica
ARG DIST=/opt/build-dist
# change OCR_LANG to enable the layer for different languages
ARG OCR_LANG=chi_sim
# change TESSERACT_DATA_SUFFIX to use different datafiles (options: "_best", "_fast" and "")
ARG TESSERACT_DATA_SUFFIX=""
ARG TESSERACT_DATA_VERSION=4.0.0

后续省略
……

创建shell脚本(build_tesseract4.sh),写入以下内容并执行,执行完毕后会在相同目录下生成tesseract-layer.zip

set -e
rm -rf tesseract-layer.zip exit 0
rm -rf configs exit 0
rm -rf tessconfigs exit 0

# Download tessconfigs folder
git clone https://github.com/tesseract-ocr/tessconfigs.git tesseractconfigs
mv tesseractconfigs/configs .
mv tesseractconfigs/tessconfigs .
rm -rf tesseractconfigs
# Build Docker image containing Tesseract
docker build -t tess_layer -f Dockerfile-tess4 .
# Copy Tesseract locally
CONTAINER=$(docker run -d tess_layer false)
docker cp $CONTAINER:/opt/build-dist layer
docker rm $CONTAINER
# # Zip Tesseract
cd layer/
zip -r ../tesseract-layer.zip .
# Clean
cd ..
rm -rf layer/
rm -rf tessconfigs/
rm -rf configs/

将前面步骤生成的zip文件(pythonlibs-layer.zip/tesseract-layer.zip)通过AWS Console或者AWS命令行的方式上传至Lambda Layer,并记录下对应的ARN,类似arn:aws-cn:lambda:cn-northwest-1:xxxxxxxx:layer:ocrTesseract:1。

Lambda兼容镜像的构建

运行时接口客户端(RIC)作为AWS开源项目,实现了Lambda的运行时API,包括调用事件检索,调用响应返回,调用错误处理和初始化错误等功能实现Lambda能正确接收处理调用并返回结果。运行时接口仿真器(RIE)实际是一个轻量级的web服务器,代理Lambda的运行时和扩展API,使开发者可以在本地通过Docker,CURL进行本地测试而不用将Lambda容器镜像部署上云。

接下来我们基于自建镜像(public.ecr.aws/bitnami/python:3.7)构建Lambda容器的dockerfile并针对其中部分操作进行解释。

创建entry.sh文件,用作容器在云上部署和本地调试的自动切换,内容如下:

if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
    exec /usr/local/bin/aws-lambda-rie /usr/local/bin/python -m awslambdaric $1

else
    exec /usr/local/bin/python -m awslambdaric $1
fi

创建dockerfile(dockerfile-custom-tesseract),内容摘录如下,原文件参见这里

安装必要工具

ARG LAYER_DIR="/opt"

FROM public.ecr.aws/bitnami/python:3.7 as build-image

RUN apt-get update && \
  apt-get install -y \
  g++ \
  make \
  cmake \
  unzip \
  libcurl4-openssl-dev
RUN pip install opencv-python-headless
RUN apt-get install -y libpng-dev

安装运行时接口客户端(RIC

RUN mkdir -p ${LAYER_DIR}
RUN pip install \
        --target ${LAYER_DIR} \
        awslambdaric

取前面步骤生成的Lambda Layer

ARG AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-"cn-northwest-1"}
ARG AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-"xxxx"}
ARG AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-"xxxx"}
ENV AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}

RUN apt-get install -y curl unzip

RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
RUN unzip awscliv2.zip
RUN ./aws/install

# ocrTesseract
RUN curl $(aws lambda get-layer-version-by-arn --arn arn:aws-cn:lambda:cn-northwest-1:xx:layer:ocrTesseract:3 --query 'Content.Location' --output text) --output pythonlibs-layer.zip
# COPY pythonlibs-layer.zip .
RUN unzip pythonlibs-layer.zip -d ${LAYER_DIR}
RUN rm pythonlibs-layer.zip

# pythonlibs-layer
RUN curl $(aws lambda get-layer-version-by-arn --arn arn:aws-cn:lambda:cn-northwest-1:xx:layer:pythonlibs-layer:1 --query 'Content.Location' --output text) --output tesseract-layer.zip
# COPY tesseract-layer.zip .
RUN unzip tesseract-layer.zip -d ${LAYER_DIR}
RUN rm tesseract-layer.zip

打包Lambda业务代码(代码逻辑下一小节会提到)和entry.sh

# Multi-stage build: grab a fresh copy of the base image, use custom image instead of official one
FROM public.ecr.aws/bitnami/python:3.7

# Include global arg in this stage of the build
ARG LAYER_DIR
# Copy in the build image dependencies
WORKDIR ${LAYER_DIR}
COPY --from=build-image ${LAYER_DIR} .

COPY app.py .

COPY entry.sh /
RUN chmod 755 /entry.sh

ENV LD_LIBRARY_PATH="/opt:/opt/lib:${LD_LIBRARY_PATH}"
ENV PATH="/opt:/opt/bin:${PATH}"

# Production env
ENTRYPOINT [ "/entry.sh" ]

开始构建镜像并推送到ECR,至此OCR业务的Lambda镜像构建完毕

docker build -t local-lambda-python3.8-custom-ocr --build-arg AWS_DEFAULT_REGION=cn-northwest-1 --build-arg AWS_ACCESS_KEY_ID=xx --build-arg AWS_SECRET_ACCESS_KEY=xx -f dockerfile-custom-tesseract .

docker tag local-lambda-python3.8-custom-ocr:latest xx---dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/local-lambda-python3.8-custom-ocr

aws ecr get-login-password --region cn-northwest-1 | docker login --username AWS --password-stdin xx---dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn

docker push xx---dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/local-lambda-python3.8-custom-ocr:latest

Lambda业务代码实现

代码的调用逻辑如下,handler接收传入的图片文件(u64编码)并调用pytesseract实现图片中文字的识别并返回。

import sys
import os
sys.path.append('/opt/python/lib/python3.7/site-packages')
# sys.path.append('/opt/python/lib/python3.7/site-packages/pytesseract')
# sys.path.append('/opt/python/lib/python3.7/site-packages/PIL')
# sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import base64
import pytesseract
import cv2

def write_to_file(save_path, data):
  with open(save_path, "wb") as f:
    f.write(base64.b64decode(data))

def handler(event, context=None):

    write_to_file("/tmp/photo.jpg", event['body'])
    img = cv2.imread("/tmp/photo.jpg")
    
    ocr_text = pytesseract.image_to_string(img, config = "eng")

    # Return the result data in json format
    return {
      "statusCode": 200,
      "body": ocr_text
    }

本地调试验证

本地安装RIE,尽量减少Lambda镜像需要安装的文件

mkdir -p ~/.aws-lambda-rie && curl -Lo ~/.aws-lambda-rie/aws-lambda-rie \
https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie \
&& chmod +x ~/.aws-lambda-rie/aws-lambda-rie

本地运行容器并通过curl测试结果,其中helloWorld文件为显示有hello world字样图片对应的u64编码文件,成功的话我们可以看到输出的hello world。

docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 --entrypoint /aws-lambda/aws-lambda-rie  local-lambda-python3.8-custom-ocr:latest /usr/local/bin/python -m awslambdaric app.handler

curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" -d @helloWorld

待功能测试成功后我们可以将该镜像最终部署上云,无缝对接其他AWS服务实现更加丰富的功能。

对接其他服务

Lambda镜像构建完毕后,用户可以结合API Gateway实现HTTP前端调用来整合我们后端的OCR能力,通过SAM(Serverless Application Model)模版快速构建一个无服务器架构的OCR应用,示例模版部分内容如下所示:

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
      ImageUrl: ‘xxxx---dkr---ecr---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/local-lambda-python3.8-custom’
  ImageConfig:
      Command:
        - "app.handler"
      EntryPoint:
        - "/entry.sh"
      WorkingDirectory: "/opt"
    Metadata:
      DockerTag: python3.x-v1
      DockerContext: ./hello-world
      Dockerfile: Dockerfile

之后通过SAM CLI实现AWS API Gateway,Lambda以及对应IAM的编译,调试和部署,有关SAM的具体的操作参见这里。待服务部署完毕后,用户可通过调用类似curl –request POST -H “Content-Type: image/png” –data-binary “@/path/ocrimage.png” https://xxxx---execute-api---cn-northwest-1.amazonaws.com.rproxy.goskope.com.cn/prod/upload

命令获取OCR识别结果。

写在最后

Lambda针对容器镜像的支持,将无服务器化,容器这两个热门的技术领域进行了完美结合,用户在原有的容器开发环境基础上利用无服务器化架构的低运维,高扩展,高可用等特性,可以更加便捷的构建和开发诸如机器学习,图像识别等数据密集型负载应用。

 

本篇作者

易珂

AWS解决方案架构师,开源项目和新兴技术爱好者,负责AWS云服务在电信运营商的推广和落地,拥有多年数据通信研发及技术团队管理经验,他的主要专业方向包括无服务器架构,分布式网络和存储架构