Cloud Native Buildpack for AWS Lambda PHP (Bref) - technical detail

Cloud Native Buildpack for AWS Lambda PHP (Bref) - Technical Details

In the first post of this series, I introduced my Cloud Native Buildpack for AWS Lambda PHP (Bref). Here I want to provide some technical details on how it works so far. 在本系列的第一篇文章中,我介绍了用于 AWS Lambda PHP (Bref) 的 Cloud Native Buildpack (CNB)。在此,我想提供一些关于其工作原理的技术细节。

Lambda’s /var/task problem

Lambda 的 /var/task 问题

CNB places the application at /workspace. Lambda requires it at /var/task. And you can’t override LAMBDA_TASK_ROOT, as it’s a reserved environment variable. The buildpack runs as a non-root user during certain CNB phases, so it can’t write to /var/task either. The solution is a thin flatten step after the CNB build that copies /workspace to /var/task and restores the Bref entrypoint. This is handled automatically by make lambda. Is it elegant? Not perfectly. But it works, and the developer never sees it. CNB 将应用程序放置在 /workspace 目录下,但 Lambda 要求其位于 /var/task。由于 LAMBDA_TASK_ROOT 是保留环境变量,你无法覆盖它。此外,构建包在某些 CNB 阶段以非 root 用户身份运行,因此也无法写入 /var/task。解决方案是在 CNB 构建之后执行一个轻量级的扁平化步骤,将 /workspace 复制到 /var/task 并恢复 Bref 入口点。这一过程由 make lambda 自动处理。这优雅吗?算不上完美,但它确实有效,且开发者无需感知这一过程。

Extension installation: three fallback strategies

扩展安装:三种回退策略

Bref’s extra PHP extensions are published as Docker images (e.g., bref/extra-gd-php-84:3). Each image is a FROM scratch layer containing just the .so file and its dependencies under /opt. The buildpack uses a three-tier strategy to install extensions: Bref 的额外 PHP 扩展以 Docker 镜像形式发布(例如 bref/extra-gd-php-84:3)。每个镜像都是一个 FROM scratch 层,仅包含 /opt 下的 .so 文件及其依赖项。构建包采用三层策略来安装扩展:

  • Method 1: crane. It uses go-containerregistry to pull and extract the extension image directly from Docker Hub, no Docker daemon needed. Works great in CI. However, bref-extra only publishes x86_64 images (ARM64 is not supported by the bref-extra project). 方法 1:crane。 它使用 go-containerregistry 直接从 Docker Hub 拉取并提取扩展镜像,无需 Docker 守护进程。在 CI 环境中表现良好。然而,bref-extra 仅发布 x86_64 镜像(bref-extra 项目不支持 ARM64)。

  • Method 2: docker create + cp. It falls back to the Docker daemon if crane fails. Same limitation: x86 only. 方法 2:docker create + cp。 如果 crane 失败,则回退到 Docker 守护进程。同样存在限制:仅支持 x86。

  • Method 3: Compile from source. When neither method works (ARM deployments), the buildpack compiles extensions from source using the Bref build image’s toolchain. This is the same approach used in multi-stage Dockerfiles but automated via build recipes. The recipes file (lib/extension-recipes.sh) defines how to build each extension: what system packages to install, what configure flags to pass, where to download external source. Currently supports gd, redis, imagick, amqp, soap, ftp, gmp, pgsql, uuid, yaml, mongodb, calendar, and exif. 方法 3:从源码编译。 当上述两种方法都不可用时(例如 ARM 部署),构建包会使用 Bref 构建镜像的工具链从源码编译扩展。这与多阶段 Dockerfile 中使用的方法相同,但通过构建配方(build recipes)实现了自动化。配方文件 (lib/extension-recipes.sh) 定义了如何构建每个扩展:需要安装哪些系统包、传递哪些配置标志、以及从何处下载外部源码。目前支持 gd、redis、imagick、amqp、soap、ftp、gmp、pgsql、uuid、yaml、mongodb、calendar 和 exif。

How bref-extra extensions are built

bref-extra 扩展是如何构建的

Looking at the bref-extra repo, each extension uses a two-stage Dockerfile: 查看 bref-extra 仓库,每个扩展都使用一个两阶段的 Dockerfile:

FROM bref/build-php-$PHP_VERSION:$BREF_VERSION AS ext
# compile the extension...
RUN cp "$(php-config --extension-dir)/redis.so" /tmp/redis.so
RUN php /bref/lib-copy/copy-dependencies.php /tmp/redis.so /tmp/extension-libs

FROM scratch
COPY --from=ext /tmp/redis.so /opt/bref/extensions/redis.so
COPY --from=ext /tmp/ext.ini /opt/bref/etc/php/conf.d/ext-redis.ini
COPY --from=ext /tmp/extension-libs /opt/lib

The final published image is literally just /opt with the compiled artifacts. That’s what crane extracts during our build. And for ARM, our recipes replicate that first stage at build time. 最终发布的镜像实际上仅仅是包含编译产物的 /opt 目录。这就是我们在构建过程中通过 crane 提取的内容。对于 ARM 架构,我们的配方在构建时会复刻上述第一阶段的操作。