构建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/amd64 与 linux/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_SERVICEcapability。 - 将 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.0、2026.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 读取索引后,根据节点的 os 和 architecture 选择 linux/amd64 或 linux/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 pipefail 与 command | 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.0 和 latest 在本次构建中均指向索引摘要 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 update、apt-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。为保持示例简洁,本文不再配置仅用于记录的 audit 和 warn 模式。
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 运行时安全与 Seccomp:
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 扫描配置:
Amazon Linux 生命周期信息:
Docker 多平台构建文档:
最后修改于 2026-09-22