按产品分类浏览文章 关于本站
- 目录 -

构建Ubuntu 24.04多架构容器镜像并推送到Amazon ECR和EKS 1.36

本文介绍如何基于Ubuntu 24.04构建非root、多架构容器镜像,将其推送到Amazon ECR,并在启用restricted级别Pod Security Admission的Amazon EKS 1.36集群中完成部署验证。

更新:兼容EKS 1.36,镜像升级到Ubuntu 24.04

一、背景

本文介绍如何基于 Ubuntu 24.04 构建同时支持 x86_64 与 arm64 的 PHP 和 Apache HTTP Server 容器镜像,将镜像推送到 Amazon Elastic Container Registry(Amazon ECR)私有仓库,并在 Amazon Elastic Kubernetes Service(Amazon EKS)1.36 集群中完成部署与安全验证。

1、原有文档面临的技术变化

本文初版写于 2019 年,当时使用 Amazon Linux 2、AWS Command Line Interface(AWS CLI)版本 1 的 aws ecr get-login 命令,以及以 root 用户监听 80 端口的容器。到 2026 年,这些实现已经出现以下变化:

  • Amazon Linux 2 已于 2026 年 6 月 30 日结束支持,不应继续作为新镜像的基础操作系统。
  • AWS CLI 版本 2 已删除 aws ecr get-login,应使用 aws ecr get-login-password,并通过标准输入把临时凭证传给 Docker。
  • Amazon EKS 1.36 使用符合开放容器倡议(Open Container Initiative,OCI)规范的镜像,并由 containerd 运行时拉取和启动容器。
  • Kubernetes 的 Pod Security Admission(PSA)已经稳定。面向生产环境的应用应按照 Pod Security Standards(PSS)中的 restricted 级别配置非 root 用户、Linux capabilities、Seccomp 和权限提升限制。
  • EKS 集群可能同时使用基于 x86_64 处理器的节点与基于 AWS Graviton arm64 处理器的节点。多架构镜像可以让 kubelet 根据节点架构自动选择正确的镜像变体。
  • Amazon ECR 的扫描配置已转向私有注册表级别。基础扫描默认可用,增强扫描由 Amazon Inspector 提供并产生额外费用。

从第一性原理看,EKS 版本与镜像内部采用哪一种 Linux 发行版没有直接绑定关系。EKS 负责 Kubernetes 控制面,节点上的 containerd 负责解析 OCI 镜像;Ubuntu 24.04 位于镜像用户空间。兼容性的关键是镜像架构、OCI 清单、应用监听端口和 Pod 安全上下文,而不是让镜像内的 Linux 版本与节点操作系统相同。

整体过程如下:

应用源代码
Ubuntu 24.04 Dockerfile
    │  Docker buildx + QEMU
linux/amd64 + linux/arm64 镜像
    │  OCI Image Index
Amazon ECR 私有仓库(新加坡区域)
    │  EKS 节点角色获取拉取权限
Amazon EKS 1.36 + containerd
    │  PSA restricted 校验
Pod(1个PHP容器)→ 本地8080端口 → HTTP 200

2、本文的验证环境与目标产出

本文于 2026 年 9 月 22 日在 AWS 账号的真实环境中完成验证,环境如下:

组件 实测配置
构建主机 AWS Cloud9 管理的 Amazon EC2,Ubuntu 22.04.5 LTS,x86_64
容器基础镜像 ubuntu:24.04
Docker Engine 29.8.1
Docker buildx 0.37.1,BuildKit 0.32.2
Amazon ECR 区域 新加坡区域 ap-southeast-1
Amazon EKS 集群 eksworkshop,Kubernetes 1.36,EKS 平台版本 eks.13
EKS 节点 Amazon Linux 2023,Kubernetes 1.36.4,containerd 2.2.7,x86_64
镜像架构 linux/amd64linux/arm64
Pod 安全策略 PSA restricted:latest

注意:构建主机操作系统与容器基础镜像是两个不同概念。本文实际构建主机为 Ubuntu 22.04,但 Dockerfile 的 FROM ubuntu:24.04 决定了应用容器的用户空间是 Ubuntu 24.04。

下面转向构建环境和容器镜像设计。

二、开发机的环境准备和常见Docker操作命令介绍

1、配置Docker准备环境

构建主机建议至少具有 2 个虚拟中央处理器、4 GiB 内存和 20 GiB 可用磁盘空间。多架构构建会同时保存多个平台的中间层,实际磁盘需求高于单架构构建。

本文在 macOS 工作站上不直接构建容器,而是通过 AWS Systems Manager Session Manager 操作云端构建机。这样既避免了本机架构差异,也提高了向 Amazon ECR 上传镜像层的稳定性。

执行如下命令检查环境:

uname -m
docker --version
docker buildx version
aws --version

返回结果如下:

x86_64
Docker version 29.8.1, build 4a63305
github.com/docker/buildx v0.37.1 0b265a9f62db554fa9aba6dd19e1bd5704bc7d8a
aws-cli/2.34.50 Python/3.14.4 Linux/6.8.0-1063-aws exe/x86_64.ubuntu.22

2、安装Docker Engine与buildx插件

在 Ubuntu 构建主机上,应通过 Docker 官方软件仓库安装 Docker Engine、Docker CLI、containerd 与 buildx 插件。安装完成后,将日常构建用户加入 docker 组,并重新登录使组成员身份生效。

注意:docker 组能够控制 Docker daemon,实际上等同于主机 root 权限。多人共享的构建主机应改用隔离的持续集成运行器或 rootless 模式,不应向不受信任的用户授予该组权限。

本文后续假定 Docker Engine、buildx、Git 和 AWS CLI 版本 2 已经安装,并且调用 AWS CLI 的身份拥有目标 ECR 仓库的最小推送权限。

3、构建应用程序

执行如下命令下载演示应用:

git clone --depth 1 https://github.com/awslabs/ecs-demo-php-simple-app
cd ecs-demo-php-simple-app

本示例继续使用原文的 PHP 演示页面,但把 Dockerfile 更新为 Ubuntu 24.04、非 root 运行和 8080 非特权端口:

# syntax=docker/dockerfile:1

FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive \
    APACHE_RUN_USER=www-data \
    APACHE_RUN_GROUP=www-data \
    APACHE_RUN_DIR=/run/apache2 \
    APACHE_PID_FILE=/run/apache2/apache2.pid \
    APACHE_LOCK_DIR=/run/apache2 \
    APACHE_LOG_DIR=/var/log/apache2

RUN apt-get update \
 && apt-get upgrade -y \
 && apt-get install -y --no-install-recommends \
      apache2 \
      libapache2-mod-php \
      ca-certificates \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

RUN rm -rf /var/www/html
COPY src/ /var/www/html/

RUN sed -i 's/^Listen 80$/Listen 8080/' /etc/apache2/ports.conf \
 && sed -i 's/:80>/:8080>/' /etc/apache2/sites-available/000-default.conf \
 && sed -i 's#^ErrorLog .*#ErrorLog /dev/stderr#' /etc/apache2/apache2.conf \
 && sed -i 's#ErrorLog .*#ErrorLog /dev/stderr#; s#CustomLog .*#CustomLog /dev/stdout combined#' \
      /etc/apache2/sites-available/000-default.conf \
 && echo 'ServerName localhost' > /etc/apache2/conf-available/servername.conf \
 && a2enconf servername \
 && a2disconf other-vhosts-access-log \
 && mkdir -p /run/apache2 \
 && chown -R 33:33 /run/apache2 /var/www/html

EXPOSE 8080
USER 33:33

CMD ["/usr/sbin/apache2", "-D", "FOREGROUND"]

这个 Dockerfile 包含以下安全决策:

  • 使用 ubuntu:24.04 长期支持版本,并在构建时安装可用的软件更新。
  • 使用 --no-install-recommends 减少不必要的软件包和攻击面。
  • 删除 APT 索引,避免把构建缓存写入最终镜像层。
  • 使用数字用户和组标识 33:33,即 Ubuntu 中的 www-data,而不是以 root 身份运行 Apache。
  • 监听 8080 端口。Linux 普通用户不能直接绑定低于 1024 的特权端口,改用 8080 后不需要增加 NET_BIND_SERVICE capability。
  • 将 Apache 访问日志和错误日志写到标准输出与标准错误,以便 Kubernetes 和 CloudWatch 收集。

注意:/run/apache2 必须在构建阶段创建并授权给 www-data。如果仍使用默认的 /run/apache2.pid,容器将因非 root 用户不能写入该文件而退出,实测错误如下:

(core:error) (13)Permission denied: AH00099: could not create /run/apache2.pid
AH00100: apache2: could not log pid to file /run/apache2.pid

4、在开发机构建Docker Image并保存在本机

执行如下命令构建供当前 x86_64 主机验证的镜像:

docker build --platform linux/amd64 -t phpdocker:local .
docker image ls phpdocker

返回结果如下:

IMAGE             ID             DISK USAGE   CONTENT SIZE
phpdocker:local   7f6ee12af928        314MB         73.8MB

以下截图来自本文早期版本,仅用于展示 Docker 构建完成后的历史界面。随着 Docker、Amazon ECR 和 Amazon ECS 服务升级,控制台与命令行界面可能发生变化,应以当前控制台显示为准。如下截图。

5、在本机启动Docker并登录到容器内测试

执行如下命令启动容器并把主机 18080 端口映射到容器 8080 端口:

docker run -d --name phptest -p 18080:8080 phpdocker:local
docker ps --filter name=phptest
docker exec phptest id
curl -i http://127.0.0.1:18080/

返回结果如下:

CONTAINER ID   IMAGE             STATUS         PORTS
D59cf5a1b26d   phpdocker:local   Up 6 seconds   0.0.0.0:18080->8080/tcp
uid=33(www-data) gid=33(www-data) groups=33(www-data)
HTTP/1.1 200 OK
Server: Apache/2.4.58 (Ubuntu)
Content-Type: text/html

测试结果证明应用能够以非 root 用户运行,并通过 8080 端口返回 HTTP 200。

6、其他Docker常用命令

执行如下命令查看镜像和容器详情:

docker image inspect phpdocker:local
docker logs phptest
docker exec -it phptest /bin/bash

测试结束后执行如下命令停止并删除本地容器与镜像:

docker rm -f phptest
docker image rm phpdocker:local

以上步骤完成了 Ubuntu 24.04 单架构镜像的功能检查,下一章创建用于保存镜像的 ECR 私有仓库。

三、创建ECR私有镜像仓库

1、如何选择公有和私有仓库

Amazon ECR Public 使用 public.ecr.aws 域名,适合公开发布开源镜像。Amazon ECR 私有仓库位于 AWS 账号和区域内,推送与拉取操作都需要 AWS Identity and Access Management(AWS IAM)授权,适合 EKS 和 Amazon Elastic Container Service(Amazon ECS)工作负载。

对比 ECR公有仓库 ECR私有仓库
典型场景 开源镜像、公开分发 企业应用、EKS和ECS工作负载
匿名拉取 支持,但有配额 不支持
IAM权限 发布时需要 推送和拉取都需要
地址格式 public.ecr.aws/<alias>/<repo> <account-id>.dkr.ecr.<region>.amazonaws.com/<repo>
数据边界 面向互联网公开 由账号、仓库策略和IAM控制

本文选择私有仓库,因为应用将部署到同一 AWS 账号的新加坡区域 EKS 集群。

注意:ECR 不会在首次推送时自动创建仓库,必须先创建仓库再推送。ECR 仓库名称可以包含 /,例如 team/phpdocker,但必须预先创建完整名称为 team/phpdocker 的仓库;不能只创建 team 后把 /phpdocker 当作文件系统子目录使用。

2、镜像标签不可变性与生命周期策略的选择

容器部署可以使用可移动标签、不可变标签或镜像摘要:

  • 选择可移动标签的场景:开发环境需要反复覆盖 latest,操作简单,但相同部署清单在不同时间可能取得不同镜像。
  • 选择不可变版本标签的场景:发布流程使用 1.02026.09.22 等版本标签,能够防止意外覆盖。
  • 选择镜像摘要的场景:生产环境需要精确复现和供应链审计,应使用 repository@sha256:<digest>
  • 组合场景:允许 latest 移动,其他标签保持不可变;构建阶段使用标签,EKS Pod 固定到摘要。本文采用这种组合。
对比 方案A:可移动标签 方案B:不可变标签或摘要
可复现性 较低
覆盖发布 方便 被拒绝,需要新版本
回滚审计 依赖额外记录 能精确定位镜像
推荐把 latest 仅用于人工查看和开发,将版本标签设为不可变,并在 EKS Pod 中固定 OCI 镜像索引摘要。

推荐把 latest 仅用于人工查看和开发,将版本标签设为不可变,并在 EKS Deployment 中固定 OCI 镜像索引摘要。

3、创建私有镜像仓库

设置变量后创建仓库:

export AWS_REGION=ap-southeast-1
export AWS_ACCOUNT_ID=<your-aws-account-id>
export ECR_REPOSITORY=phpdocker

aws ecr create-repository \
  --region ${AWS_REGION} \
  --repository-name ${ECR_REPOSITORY} \
  --image-tag-mutability IMMUTABLE_WITH_EXCLUSION \
  --image-tag-mutability-exclusion-filters '[{"filter":"latest","filterType":"WILDCARD"}]' \
  --image-scanning-configuration scanOnPush=true \
  --encryption-configuration encryptionType=AES256

本文实测返回结果如下:

{
  "repository": {
    "repositoryArn": "arn:aws:ecr:ap-southeast-1:133129065110:repository/phpdocker",
    "repositoryName": "phpdocker",
    "repositoryUri": "133129065110.dkr.ecr.ap-southeast-1.amazonaws.com/phpdocker",
    "imageTagMutability": "IMMUTABLE_WITH_EXCLUSION",
    "imageTagMutabilityExclusionFilters": [
      {"filterType": "WILDCARD", "filter": "latest"}
    ],
    "imageScanningConfiguration": {"scanOnPush": true},
    "encryptionConfiguration": {"encryptionType": "AES256"}
  }
}

也可以在 ECR 控制台中选择 Create repository 完成创建。以下为早期版本的控制台截图;随着 Amazon ECR 和 Amazon ECS 服务升级,界面可能已经变化,应以当前控制台为准。如下截图。

建议同时配置生命周期策略,限制旧版本和无标签镜像持续占用存储空间。多架构构建会生成无标签的平台清单和证明材料,不能在推送完成后立即删除,否则可能破坏 OCI 索引引用;本文将无标签镜像的保留期设置为 7 天。

下一章配置 ECR 身份认证并推送单架构镜像。

四、从开发机登录到ECR服务

1、通过AWS CLI完成对ECR镜像仓库的身份验证

构建身份至少需要 ecr:GetAuthorizationToken,以及目标仓库上的分层上传、ecr:PutImage 和查询权限。不要为了构建一个仓库而授予整个账号的管理员权限。

AWS CLI 版本 2 应执行如下命令:

aws ecr get-login-password --region ${AWS_REGION} \
  | docker login \
      --username AWS \
      --password-stdin \
      ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com

返回结果如下:

Login Succeeded

ECR 授权令牌有效期为 12 小时。--password-stdin 避免把令牌写入 shell 历史或进程参数,但 Docker 仍可能把凭证写入当前用户的 ~/.docker/config.json。共享构建机应配置 Docker credential helper,构建结束后也可执行 docker logout <registry>

以下截图展示早期版本的登录结果;随着 Amazon ECR 和 Amazon ECS 服务升级,界面可能已经变化。如下截图。

2、对开发机上要上传到ECR的镜像打tag

执行如下命令为本地镜像添加完整仓库标签:

export ECR_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}
docker tag phpdocker:local ${ECR_URI}:1.0

标签只是指向本地镜像对象的引用,不会复制镜像层。版本标签应采用可追踪规则,例如语义版本、Git 提交标识或构建编号。

3、将image push到ECR上

执行如下命令推送单架构镜像:

docker push ${ECR_URI}:1.0

返回结果包含各镜像层的上传状态和最终摘要。以下截图展示早期版本的上传结果以及 ECR 控制台中的镜像列表;随着 Amazon ECR 和 Amazon ECS 服务升级,界面可能已经变化。如下截图。

4、从ECR上下载镜像到本地(可选)

在另一个已完成 ECR 登录的开发环境执行如下命令:

docker pull ${ECR_URI}:1.0

如果目标主机架构与镜像架构不同,单架构镜像可能无法运行。下一章通过 buildx 创建能够让 EKS 自动选择架构的 OCI 多架构镜像。

五、构建同时支持x86_64与arm64的多架构镜像(可选)

1、为什么EKS集群需要多架构镜像

多架构镜像不是把两种中央处理器指令集放入同一个文件系统层,而是由一个 OCI Image Index(OCI 镜像索引)引用多个平台清单。EKS 节点上的 containerd 读取索引后,根据节点的 osarchitecture 选择 linux/amd64linux/arm64 清单。

这样,一个 Deployment 可以使用同一个镜像地址同时调度到 Intel、AMD 和 AWS Graviton 节点。代价是构建时间和 ECR 存储量增加,而且每个平台变体都需要单独进行功能验证和漏洞扫描。

2、配置QEMU与buildx构建器

x86_64 构建机需要 Quick Emulator(QEMU)执行 arm64 构建阶段。执行如下命令安装 arm64 的 binfmt 处理程序:

docker run --privileged --rm tonistiigi/binfmt --install arm64

注意:--privileged 会修改构建主机的 binfmt 配置,仅应运行经过审核的镜像。安全要求较高时,应分别使用原生 x86_64 和 arm64 构建节点,避免特权容器与模拟执行。

返回结果如下:

{
  "supported": ["linux/amd64", "linux/amd64/v2", "linux/amd64/v3", "linux/amd64/v4", "linux/arm64", "linux/386"],
  "emulators": ["qemu-aarch64"]
}

创建使用 docker-container 驱动的 buildx 构建器:

docker buildx create \
  --name eksbuilder \
  --driver docker-container \
  --bootstrap
docker buildx use eksbuilder
docker buildx inspect --bootstrap

返回结果如下:

Name:          eksbuilder
Driver:        docker-container
Status:        running
BuildKit version: v0.32.2
Platforms:     linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/arm64, linux/386

调试脚本时还需注意,set -o pipefailcommand | head 组合可能使上游进程收到 SIGPIPE,并让脚本被误判为失败。应直接执行命令,或明确处理该管道的退出状态。

3、一次构建并推送多架构镜像清单

多架构结果不能通过默认 Docker image store 同时载入本地,应使用 --push 直接发送到 ECR:

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --provenance=mode=max \
  --sbom=true \
  -t ${ECR_URI}:1.0 \
  -t ${ECR_URI}:latest \
  --push .

其中,--provenance=mode=max 生成构建来源证明,--sbom=true 生成 Software Bill of Materials(SBOM,软件物料清单)。这些证明材料会作为额外 OCI 清单保存,所以检查索引时可能看到平台为 unknown/unknown 的 attestation manifest。这不是错误。

本文还在 x86_64 构建主机上通过 QEMU 拉取并运行 arm64 变体。执行如下命令:

docker run -d \
  --platform linux/arm64 \
  --name phptest-arm64 \
  -p 18081:8080 \
  ${ECR_URI}:1.0

docker exec phptest-arm64 uname -m
docker exec phptest-arm64 id
curl -sS -o /dev/null -w 'HTTP_CODE=%{http_code}\n' http://127.0.0.1:18081/

返回结果如下:

aarch64
uid=33(www-data) gid=33(www-data) groups=33(www-data)
HTTP_CODE=200

4、校验远端镜像清单格式

执行如下命令检查远端 OCI 索引:

docker buildx imagetools inspect ${ECR_URI}:1.0

本文实测返回结果如下:

Name:      133129065110.dkr.ecr.ap-southeast-1.amazonaws.com/phpdocker:1.0
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:c7f567df2fb7263a2a18ec8c0cc31796ba529561bfc3b50bd95318901ce55dd4
Manifests:
  MediaType: application/vnd.oci.image.manifest.v1+json
  Platform:  linux/amd64
  Digest:    sha256:ee8cd228737c3c277d52c0581d4f45fb560ad026653b3ffe52194285cbbad1e9
  MediaType: application/vnd.oci.image.manifest.v1+json
  Platform:  linux/arm64
  Digest:    sha256:f429ee8b915651154b1309f307e18f6ed3087bca4f5791b430e965e6a7e73438

1.0latest 在本次构建中均指向索引摘要 sha256:c7f567...。生产部署应记录完整摘要,而不是依赖可移动的 latest 标签。

下一章检查镜像漏洞扫描结果和多架构扫描限制。

六、镜像安全扫描与软件版本维护(可选)

1、ECR基础扫描与增强扫描的区别

Amazon ECR 提供基础扫描和增强扫描。两种方案的适用场景如下:

  • 选择基础扫描的场景:需要低成本检查镜像操作系统软件包中的已知漏洞,并在推送时或手动触发扫描。
  • 选择增强扫描的场景:需要 Amazon Inspector 持续更新漏洞结果、覆盖操作系统和编程语言软件包,并汇总到 Inspector 控制台;该方案会产生额外费用。
  • 组合管理场景:开发账号使用基础扫描,生产账号对关键仓库启用增强扫描,并通过 Security Hub 或流水线设置发布门禁。
对比 方案A:ECR基础扫描 方案B:Amazon Inspector增强扫描
默认状态 私有注册表默认使用 需要显式启用
扫描频率 推送时或手动 推送时或持续扫描
覆盖范围 主要是操作系统软件包 操作系统与支持的编程语言软件包
成本 包含在ECR能力中 产生Inspector扫描费用
结果更新 再次扫描时更新 新漏洞发布后持续更新

本文仅使用基础扫描,不启用增强扫描,避免改变账号内其他 ECR 仓库的注册表级扫描策略和产生额外费用。

以下截图来自文章早期版本,用于展示历史扫描界面和漏洞详情。当前 Ubuntu 24.04 镜像的实测扫描结果为 0 项发现,且随着 Amazon ECR、Amazon ECS 和 Amazon Inspector 服务升级,控制台界面可能已经变化。如下截图。

2、对多架构镜像清单执行扫描的限制

ECR 基础扫描不能直接扫描 OCI Image Index。对标签 1.0 发起扫描时,本文实测返回如下错误:

UnsupportedImageTypeException: An artifact with media type
'application/vnd.oci.image.index.v1+json' cannot be scanned.

原因是索引本身不包含 Ubuntu 软件包文件系统,它只引用 amd64、arm64 和证明清单。应先通过 docker buildx imagetools inspect 取得各平台清单摘要,再按摘要查询扫描结果:

aws ecr describe-image-scan-findings \
  --region ${AWS_REGION} \
  --repository-name ${ECR_REPOSITORY} \
  --image-id imageDigest=sha256:<platform-manifest-digest>

amd64 变体的实测结果如下:

{
  "imageScanStatus": {
    "status": "COMPLETE",
    "description": "The scan was completed successfully."
  },
  "findingSeverityCounts": {}
}

空对象表示本次基础扫描没有发现可报告漏洞,不表示应用在所有层面不存在安全风险。基础扫描无法替代源代码依赖扫描、机密信息检测、镜像签名、运行时防护和渗透测试。

3、在Dockerfile中固化安全更新

本 Dockerfile 在同一个 RUN 指令中执行 apt-get updateapt-get upgrade -y、安装依赖和清理索引。这样可避免 APT 索引过期,也不会把索引保留在最终镜像中。

RUN apt-get update \
 && apt-get upgrade -y \
 && apt-get install -y --no-install-recommends apache2 libapache2-mod-php ca-certificates \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

以下三张截图展示早期版本中重新构建、查看和推送修复后镜像的过程。当前流程已经改为 Ubuntu 24.04、buildx 与 OCI 多架构索引,随着 Docker、Amazon ECR 和 Amazon ECS 服务升级,界面可能已经变化。如下截图。

以下截图展示早期版本中扫描通过的状态,当前结果应以 ECR API 和控制台的实时数据为准。如下截图。

需要注意,重新执行构建不一定会自动取得新的基础镜像。Dockerfile 中使用 ubuntu:24.04 标签时,应定期执行带 --pull 的全量重建,并在验证后发布新版本标签:

docker buildx build \
  --pull \
  --no-cache \
  --platform linux/amd64,linux/arm64 \
  -t ${ECR_URI}:<new-version> \
  --push .

安全更新是持续过程,不应把某一次“0 项发现”视为长期结论。下一章在 EKS 1.36 集群中验证镜像拉取、调度和 PSA restricted 策略。

七、在EKS 1.36集群上部署并验证镜像

以下假设您已经有创建好的 EKS 集群,这里为拉取镜像的测试。

1、节点角色拉取ECR镜像所需的IAM权限

EKS 节点上的 kubelet 通过节点 IAM 角色获取 ECR 授权并拉取镜像。推荐给节点角色附加 AmazonEC2ContainerRegistryPullOnly AWS 托管策略,而不是授予推送权限。

执行如下命令检查节点组使用的角色:

aws eks describe-nodegroup \
  --region ap-southeast-1 \
  --cluster-name eksworkshop \
  --nodegroup-name podsubnet-ng \
  --query 'nodegroup.nodeRole' \
  --output text

本文实测节点角色已经包含如下策略:

AmazonEC2ContainerRegistryPullOnly

同一账号、同一区域的 ECR 私有仓库通常不需要创建 imagePullSecrets。跨账号拉取还需要在 ECR 仓库策略中授权节点角色;私有子网还需要 NAT Gateway,或者 ECR API、ECR Docker Registry 和 Amazon S3 的相应 VPC 端点。

2、启用Pod Security Admission的restricted配置

Amazon EKS 1.36 的 PodSecurity 准入控制器已经启用,但默认集群级策略是 privileged。这意味着“使用 EKS 1.36”并不会自动拒绝高权限 Pod,必须通过命名空间标签启用更严格的策略。

创建 phpdemo 命名空间并启用 restricted

apiVersion: v1
kind: Namespace
metadata:
  name: phpdemo
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest

enforce 会拒绝不符合 restricted 标准的新 Pod。为保持示例简洁,本文不再配置仅用于记录的 auditwarn 模式。

3、编写符合restricted规范的工作负载清单

本文只创建一个 PHP Pod,不创建 Deployment、Service、探针、资源限制、额外卷或监控注解。由于命名空间启用了 restricted,仍需保留该策略强制要求的最小安全字段。

把 Namespace 和 Pod 保存为 phpdemo-eks136.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: phpdemo
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
---
apiVersion: v1
kind: Pod
metadata:
  name: phpdocker
  namespace: phpdemo
spec:
  automountServiceAccountToken: false
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: php
      image: 133129065110.dkr.ecr.ap-southeast-1.amazonaws.com/phpdocker@sha256:c7f567df2fb7263a2a18ec8c0cc31796ba529561bfc3b50bd95318901ce55dd4
      ports:
        - containerPort: 8080
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]

这个 Pod 只有一个名为 php 的容器:

  • runAsNonRoot: true 要求容器不能以 root 用户运行。镜像 Dockerfile 已设置 USER 33:33
  • seccompProfile.type: RuntimeDefault 使用 containerd 的默认系统调用过滤配置。
  • allowPrivilegeEscalation: false 禁止进程获得更高权限。
  • capabilities.drop: ["ALL"] 删除全部 Linux capabilities。
  • automountServiceAccountToken: false 避免向不需要访问 Kubernetes API 的 PHP 应用挂载集群凭证。

清单不包含任何监控注解。实测创建的 Pod 没有初始化容器,只有 PHP 应用容器。

4、部署与访问验证

执行如下命令部署 Pod 并等待其进入 Ready 状态:

aws eks update-kubeconfig \
  --region ap-southeast-1 \
  --name eksworkshop
kubectl apply -f phpdemo-eks136.yaml
kubectl -n phpdemo wait \
  --for=condition=Ready \
  pod/phpdocker \
  --timeout=180s
kubectl -n phpdemo get pod phpdocker -o wide

本文实测返回结果如下:

pod/phpdocker created
pod/phpdocker condition met
NAME        READY   STATUS    RESTARTS   IP
phpdocker   1/1     Running   0          100.64.2.116

检查容器用户、初始化容器和应用响应:

kubectl -n phpdemo exec phpdocker -- id
kubectl -n phpdemo get pod phpdocker \
  -o jsonpath='containers={.spec.containers[*].name}{"\n"}initContainers={.spec.initContainers[*].name}{"\n"}'
kubectl -n phpdemo exec phpdocker -- \
  php -r '$h=get_headers("http://127.0.0.1:8080/"); echo $h[0], PHP_EOL;'

返回结果如下:

uid=33(www-data) gid=33(www-data) groups=33(www-data)
containers=php
initContainers=
HTTP/1.1 200 OK

initContainers 为空,证明 Pod 中只有 PHP 应用容器,没有注入 Java、Node.js、Python 或 .NET 组件。如果需要从个人计算机浏览器临时访问,可执行如下命令并打开 http://127.0.0.1:8080/

kubectl -n phpdemo port-forward pod/phpdocker 8080:8080

port-forward 只适合调试,终止命令后转发即停止。生产环境应使用 Deployment 和 Service,本节仅用于最小化验证 ECR 镜像能够在 EKS 1.36 中运行。

5、不合规工作负载的拒绝行为

执行如下服务端试运行命令,故意提交没有安全上下文的 Pod:

kubectl -n phpdemo run badpod \
  --image=${ECR_URI}:1.0 \
  --restart=Never \
  --dry-run=server

返回结果如下:

Error from server (Forbidden): pods "badpod" is forbidden:
violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false,
unrestricted capabilities,
runAsNonRoot != true,
seccompProfile must be "RuntimeDefault" or "Localhost"

该负向测试证明最小 PHP Pod 的安全字段是 restricted 策略所必需,而不是额外的监控配置。下一章清理演示资源并避免继续产生费用。

八、清理演示资源

注意:下列命令会删除 Kubernetes 工作负载和 ECR 仓库中的镜像,属于不可逆操作。确认不再需要演示数据后再执行,并保留构建日志、镜像摘要和部署清单作为审计记录。

删除 EKS 演示命名空间:

kubectl delete namespace phpdemo

删除 ECR 仓库及其中镜像:

aws ecr delete-repository \
  --region ap-southeast-1 \
  --repository-name phpdocker \
  --force

如果为构建机临时添加了推送策略,应删除该策略:

aws iam delete-role-policy \
  --role-name <build-instance-role-name> \
  --policy-name <temporary-ecr-push-policy-name>

执行 Docker 登出并停止不再使用的构建实例:

docker logout ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
aws ec2 stop-instances \
  --region <build-instance-region> \
  --instance-ids <build-instance-id>

ECR 按镜像存储量和数据传输计费,EKS 节点与构建 EC2 实例按运行时间计费。测试完成后及时删除临时 Pod、仓库和权限,并停止构建实例,可以避免持续产生费用。

小结

本文把原有的 Amazon Linux 2 单架构镜像流程升级为 Ubuntu 24.04 多架构镜像流程。实测镜像以 www-data 用户运行、监听 8080 端口,并以 OCI Image Index 同时发布 amd64 和 arm64 变体。ECR 基础扫描对 Ubuntu 24.04 平台镜像的扫描状态为 COMPLETE,未发现可报告漏洞,但 OCI 索引本身不能直接扫描,需要按平台清单摘要检查结果。

在 Amazon EKS 1.36 环境中,节点通过 AmazonEC2ContainerRegistryPullOnly 权限拉取固定摘要的镜像。命名空间启用 PSA restricted:latest 后,单个 PHP Pod 正常运行,并且实测只有一个 php 容器、没有初始化容器或监控注入;非 root、Seccomp、禁止权限提升和 capability 删除配置通过验证。不合规 Pod 则被服务端拒绝。生产环境建议继续采用不可变版本标签、摘要固定、定期 --pull 重建和持续漏洞管理。

参考文档

Amazon EKS 1.36 支持公告:

Amazon EKS and Amazon EKS Distro now supports Kubernetes version 1.36

Amazon EKS 平台版本说明:

View Amazon EKS platform versions for each Kubernetes version

Amazon EKS 节点 IAM 角色与 ECR 拉取权限:

Amazon EKS node IAM role

Amazon EKS 运行时安全与 Seccomp:

Runtime security

Amazon ECR 私有注册表身份认证:

Private registry authentication in Amazon ECR

AWS CLI 版本 2 中 ECR 登录命令的变化:

New features and changes in the AWS CLI version 2

Amazon ECR 镜像清单格式:

Container image manifest format support in Amazon ECR

Amazon ECR 扫描配置:

Scanning configuration

Amazon Linux 生命周期信息:

Amazon Linux

Docker 多平台构建文档:

Multi-platform builds


最后修改于 2026-09-22

- 目录 -