You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在AWS Lambda运行自定义运行时的正确方式及Haskell部署问题咨询

在AWS Lambda上运行Haskell性能测试的问题与解决方案

问题概述

目标是在AWS Lambda上运行Haskell代码做性能测试,希望尽可能贴近原生AWS运行时,代码逻辑仅需封装为单函数。当前使用多阶段Docker打包function.zip作为自定义运行时,测试时能输出Hello, Haskell!但触发Runtime.ExitError,且代码执行两次;对Docker和AWS不熟悉,此前参考的自定义运行时指南已过时,配置stack/cabal时曾遇问题。

当前错误根源分析

Lambda自定义运行时要求程序持续运行并监听Lambda Runtime API,但现有Haskell代码仅打印字符串后直接退出,未与Lambda的运行时API交互:

  1. Lambda初始化阶段启动程序,程序退出触发Runtime.ExitError,Lambda尝试重启
  2. 调用阶段程序再次退出,最终返回错误日志
  3. 日志中两次执行的记录,分别对应初始化和调用阶段的重启行为

修复当前实现的步骤

1. 修改Haskell代码适配Lambda规范

Lambda自定义运行时需遵循以下流程:

  • 从环境变量AWS_LAMBDA_RUNTIME_API获取API地址
  • 循环拉取请求、处理请求、返回结果
  • 保持进程持续运行,不主动退出

简化版适配代码(依赖http-conduit和aeson处理API交互):

module Main where

import Network.HTTP.Simple
import Data.Aeson (object, (.=), encode)
import System.Environment (getEnv)
import Data.ByteString.Lazy.Char8 (unpack)

main :: IO ()
main = do
    runtimeApi <- getEnv "AWS_LAMBDA_RUNTIME_API"
    let baseUrl = "http://" ++ runtimeApi ++ "/2018-06-01/runtime"
    loop baseUrl
  where
    loop baseUrl = do
        -- 拉取下一个请求
        nextReq <- parseRequest $ baseUrl ++ "/invocation/next"
        nextResp <- httpLBS nextReq
        let requestId = getResponseHeader "Lambda-Runtime-Aws-Request-Id" nextResp !! 0
            -- 自定义处理逻辑
            responseBody = encode $ object ["message" .= ("Hello, Haskell!" :: String)]
        
        -- 返回处理结果
        respReq <- parseRequest $ baseUrl ++ "/invocation/" ++ unpack requestId ++ "/response"
        httpLBS $ setRequestBodyLBS responseBody respReq
        
        -- 循环处理后续请求
        loop baseUrl

2. 更新依赖配置与Dockerfile

在package.yaml中添加必要依赖:

dependencies:
- base >= 4.14 && < 5
- http-conduit
- aeson
- bytestring

修改Dockerfile确保依赖正确安装:

FROM haskell:latest as build

RUN mkdir -p /aws_lambda_runtime_wjs
WORKDIR /aws_lambda_runtime_wjs

COPY stack.yaml package.yaml ./
COPY src/ ./src/

RUN stack setup && stack build --install-ghc --copy-bins

RUN BINARY_PATH=$(stack path --local-install-root)/bin/aws-lambda-runtime && \
    cp $BINARY_PATH /aws_lambda_runtime_wjs/aws-lambda-runtime-wjs

FROM amazonlinux:2023

RUN yum -y update && \
    yum -y install zlib gmp ncurses-libs xz zip

RUN mkdir -p /var/task

COPY --from=build /aws_lambda_runtime_wjs/aws-lambda-runtime-wjs /var/task/aws-lambda-runtime-wjs
COPY --from=build /aws_lambda_runtime_wjs/bootstrap /var/task/bootstrap

RUN chmod +x /var/task/bootstrap /var/task/aws-lambda-runtime-wjs

WORKDIR /var/task
RUN zip -r9 function.zip bootstrap aws-lambda-runtime-wjs

ENTRYPOINT ["/var/task/bootstrap"]

3. 保留现有Bootstrap脚本

当前Bootstrap脚本已正确执行Haskell二进制,无需修改,exec命令确保二进制成为进程PID 1,维持持续运行状态。

更贴近原生运行时的替代方案

方案1:静态二进制+运行时层

  • 构建静态编译的Haskell二进制:stack build --static,避免依赖系统库
  • 将静态二进制和Bootstrap脚本打包为Lambda层(zip格式)
  • 在Lambda函数中选择Amazon Linux 2原生运行时,设置处理程序为Bootstrap脚本

方案2:Lambda容器镜像(更易维护)

直接构建符合Lambda规范的容器镜像,无需打包zip:

FROM haskell:latest as build

RUN mkdir -p /aws_lambda_runtime_wjs
WORKDIR /aws_lambda_runtime_wjs

COPY stack.yaml package.yaml ./
COPY src/ ./src/

RUN stack setup && stack build --install-ghc --copy-bins --static

RUN BINARY_PATH=$(stack path --local-install-root)/bin/aws-lambda-runtime && \
    cp $BINARY_PATH /aws_lambda_runtime_wjs/aws-lambda-runtime-wjs

FROM public.ecr.aws/lambda/provided:al2

COPY --from=build /aws_lambda_runtime_wjs/aws-lambda-runtime-wjs /var/task/aws-lambda-runtime-wjs
COPY --from=build /aws_lambda_runtime_wjs/bootstrap /var/task/bootstrap

RUN chmod +x /var/task/bootstrap /var/task/aws-lambda-runtime-wjs

ENTRYPOINT ["/var/task/bootstrap"]

推送镜像到AWS ECR后,直接在Lambda中选择该镜像作为运行环境即可。

总结

  • 当前实现的核心问题是Haskell代码未遵循Lambda自定义运行时的API规范,导致进程异常退出
  • 修复后可正常运行,满足性能测试需求
  • 若追求更贴近原生运行时的体验,优先选择静态二进制+运行时层或Lambda容器镜像方案,后者维护成本更低

内容的提问来源于stack exchange,提问作者william

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 13:36:10