对AWS Lambda的容器镜像支持

52 阅读7分钟

AWS Lambda很容易使用和管理;执行环境在已知的环境上有一个特定的运行时间,你只需向它发送代码,它就会运行。很好!多年来,这为我们提供了良好的服务。然而,这种现状的最大问题是,当你想在这些预先确定的环境之外实现一个用例。也许你想用一个默认不包括在lambda环境中的库来进行某种形式的处理?或者甚至使用你自己的运行时间,而这是不提供的?

AWS试图通过引入Lambda层来帮助解决其中的一些问题,这很有用,但仍然相当有限。真正的问题是,虽然Lambda在默认情况下很适合拿起和运行,几乎不需要维护,但为了实现这种简单性,牺牲了灵活性。

2020年12月,我们发布了一些基本的Docker容器支持,最近我们在此基础上进行了扩展,使用户更容易利用这个新功能。容器完全封装了你的Lambda函数(库、处理程序代码、操作系统、运行时等),这样你之后需要做的就是将一个事件指向它来触发它。

而无服务器框架让这一切变得异常简单:

service: example-service

provider:
  name: aws
  ecr:
    # In this section you can define images that will be built locally and uploaded to ECR
    images:
      appimage:
        path: ./

functions:
  hello:
    image:
      name: appimage

因为我们指向的是一个现有的容器定义,其中包含了Lambda需要执行的所有内容,包括处理程序代码,整个打包过程现在是在容器的上下文中进行的。AWS使用你的docker配置来构建、优化和准备你的容器以便在Lambda中使用。请记住,这不仅仅是在后台的 "专有K8s"。这在很大程度上仍然是Lambda的微型虚拟机架构,你的容器,虽然完全是自定义的,但以一种方式打包,以准备和优化它在该环境中的使用,就像一个普通的Lambda。

AWS声称冷启动时间应该没有明显的影响,但我认为可以安全地假设,有可能以这样一种方式配置东西,使冷启动时间更长,所以可能需要小心和彻底测试。特别是由于容器镜像的大小可以达到10GB;我们已经看到,在过去,包的大小可以影响冷启动时间。 而这带来了使用自己的docker容器的最大缺点。虽然这个新功能是绝对需要的,并将为平台和一般的Serverless开发提供大量的灵活性,但它确实应该被看作是最后手段。为什么呢?

无服务器开发的一大卖点是,你可以吐出一个解决方案,而底层管理服务为你管理一切;从基础设施到网络,从操作系统到运行时。现在有了docker的支持,你可以把它往后拉一拉,收回对操作系统和运行时的管理,这在某些情况下可能是必须的。但如果你能使用预建的、准备好的环境,还是建议你这样做,以减少你在管理这些环境时可能需要的工作量;这也是我们大多数人开始用无服务器构建应用程序的原因之一。

让框架完成所有繁重的工作

如果你想利用docker的支持,但又想让框架为你做很多工作,那么我们可以为你提供帮助。我们最近增加了这样的功能:你可以定义一个Docker文件,在你的serverless.yml中指向它,并让无服务器框架做所有的工作,确保容器在ECR中可用,并根据Lambda的需要进行设置和配置。

在我们开始之前的一个前提条件是,我们需要确保我们的本地机器上安装了docker CLI。你可以在Docker自己的文档中找到为你自己的环境做这个的说明。

为了开始工作,让我们使用添加的启动模板来使事情变得更简单。

serverless create --template aws-nodejs-docker --path aws-nodejs-docker-demo

这将生成一个模板,在我们的serverless.yml中已经为我们配置了一些基本设置。让我们来看看一些关键部分。在提供者部分,你应该看到一些新东西:

provider:
  name: aws
  ecr:
    # In this section you can define images that will be built locally and uploaded to ECR
    images:
      appimage:
        path: ./

它的作用是告诉框架我们可以在配置中的其他地方使用的图像参考名称(appimage ),以及docker图像的内容在哪里(path );某种类型的Docker文件应该驻留在指定的文件夹中。我们的Dockerfile现在做的工作是为我们的函数指定可执行代码的位置:

FROM public.ecr.aws/lambda/nodejs:12

COPY app.js ./

# You can overwrite command in `serverless.yml` template
CMD ["app.handler"]

CMD 属性定义了一个名为app.js 的文件,其中有一个名为handler 的函数。如果你看一下我们的服务目录的内容,我们有一个叫做app.js的文件,里面有那个确切的函数名称。到目前为止都很好。然而,我们仍然需要配置将在Lambda中创建的函数本身,以及将触发它的事件:

functions:
  hello:
    image:
      name: appimage

请注意,我们在上面使用的image.name ,与我们在定义图像时使用的值相同;appimage 。只要你使用相同的值来引用它,它可以是任何你想要的东西。你也可以把你需要的任何事件附加到这个基于容器的版本上,它将像非容器版本一样工作。Tada!

为多个函数重新使用同一个容器

有时,你可能真的想为你的serverless.yml中定义的多个函数使用同一个函数容器。你可以将所有的函数处理程序存储在一个容器中,然后在serverless.yml中单独引用它们,根据需要有效地重写CMD 属性:

functions:
  greeter:
    image:
      name: appimage
      command:
        - app.greeter
      entryPoint:
        - '/lambda-entrypoint.sh'

通过添加command 属性,我们告诉框架,对于这个特定的函数,代码仍在app.js 文件中,但函数名称是greeter 。我们也有entryPoint 属性。这与我们在Docker文件中引用的基础镜像有关。再次看一下我们的Dockerfile的第一行:

FROM public.ecr.aws/lambda/nodejs:12

我们的基础镜像,我们的容器是由AWS构建的。如果我们使用这个作为基础镜像,那么我们将永远有:

      entryPoint:
        - '/lambda-entrypoint.sh'

如果你在自己的dockerfile中使用一个不同的基础镜像,那么请确保使用正确的entryPoint 值。

除此以外,就是这样了!我们现在能够生成我们的容器,将它们部署到ECR并执行功能。不过,如果你想在无服务器框架之外集中创建docker镜像,只需在serverless.yml中引用它们,也是可以做到的。

为Lambda手动构建我们的docker容器

我们可以提前为Lambda构建docker容器,并在serverless.yml中引用它。首先,让我们列出一个小小的要求清单:

  1. 确保Docker CLI已经安装:https://docs.docker.com/get-docker/

  2. 确保AWS CLI已安装:https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html

我们需要使用Docker本身来准备docker容器,然后使用AWS CLI来推送我们新造的容器到AWS的ECR服务,以便在Lambda中使用。只需按照下面的步骤操作即可。

登录Docker到AWS ECR
$ aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com

只需根据需要替换正确的地区和账户ID,你就会看到 "登录成功 "的消息。

设置一个准备好的Docker镜像的lambda

最简单的方法是依靠AWS提供的基础镜像。检查AWS ECR Gallery,查看所有可用的镜像列表。

你可以通过以下方式获取所选择的镜像:

$ docker pull <image-url>

例如,Node.js镜像(在写这篇文章的时候)可以通过以下方式提取:

$ docker pull public.ecr.aws/lambda/nodejs:12

该图像的基本配置如下:

FROM <image-url>
ARG FUNCTION_DIR="/var/task"

# Create function directory
RUN mkdir -p ${FUNCTION_DIR}

# Copy handler function and package.json
COPY index.js ${FUNCTION_DIR}
COPY package.json ${FUNCTION_DIR}

# Install NPM dependencies for function
RUN npm install

# Set the CMD to your handler
CMD [ "index.handler" ]

现在我们可以建立我们的图像了:

注意:对于要被Serverless引用的镜像,我们建议采用以下的镜像命名规则。<service>-<stage>-<functionName>

$ docker build -t <image-name>
在AWS ECR服务中为相应的lambda镜像创建一个资源库

创建存储库的命令是针对图像的,将存储其所有版本。我们建议将存储库的命名与图像的命名相同:

$ aws ecr create-repository --repository-name <repository-name> --image-scanning-configuration scanOnPush=true
将本地图像链接到AWS ECR资源库并推送
$ docker tag <image-name>:latest <account>.dkr.ecr.<region>.amazonaws.com/<repository-name>:latest
$ docker push <account>.dkr.ecr.<region>.amazonaws.com/<repository-name>:latest

这里注意返回的图像摘要。我们将需要在我们的服务配置中引用该图像

将lambda指向AWS ECR图像

最后,在我们的serverless.yml中,我们通过引用上次docker push命令返回的uri和digest,将lambda指向推送的镜像:

functions:
  someFunction:
    image: <account>.dkr.ecr.<region>.amazonaws.com/<repository>@<digest>