fix(ci): 改用原生 arm64 runner 分架构构建,根治 QEMU apt OOM

QEMU 模拟下 arm64 的 apt-get update 反复 OOM(getline: Cannot allocate memory),
让实例镜像 1.4.1/1.4.3 都发不出去;源精简只降概率不治本(1.4.3 又栽在 snapshot InRelease)。
公开仓库可用免费 ubuntu-24.04-arm 原生 runner——彻底不走 QEMU:
- 每个 (镜像 × 架构) 在各自原生 runner 上按 digest 推送到 GHCR;
- merge job 从两个 arch digest 合成多架构 manifest,一条 imagetools create 打全部 tag
  (blob 由 GHCR 复制到 Docker Hub,双仓库一并覆盖);
- 冒烟门禁保留在 wechat+amd64(原生 x86 可 docker run)。
actionlint 通过。
This commit is contained in:
Gloridust
2026-07-08 03:25:10 +08:00
parent 9981e9a5dc
commit fe964925c1

View File

@@ -1,7 +1,13 @@
name: release
# 推送 vX.Y.Z 标签或在 GitHub 上发布 Release 时,构建并推送多架构镜像GHCR。
# 也可手动触发(workflow_dispatch只打 latest,便于验证流水线。
# 推送 vX.Y.Z 标签或发布 Release 时,构建并推送多架构镜像GHCR + 可选 Docker Hub
# workflow_dispatch 可手动触发(只打 latest),用于验证流水线本身
#
# 关键v1.4.3 之后重写):改用【原生 arm64 runner】分架构构建不再用 QEMU 模拟。
# 教训QEMU 下 arm64 的 apt-get update 会 OOMgetline: Cannot allocate memory
# 让实例镜像反复发不出去1.4.1 / 1.4.3 均折在这)。原生 runner 从根上消除该问题,也更快。
# 模式:每个 (镜像 × 架构) 在各自原生 runner 上按 digest 推送 → merge job 合成多架构 manifest 打 tag。
on:
push:
tags:
@@ -12,34 +18,25 @@ on:
env:
REGISTRY: ghcr.io
WECHAT_IMAGE: ${{ github.repository_owner }}/wechat-on-cloud
PANEL_IMAGE: ${{ github.repository_owner }}/woc-panel
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- name: wechat
image: wechat-on-cloud
context: ./docker
dockerfile: ./docker/Dockerfile
- name: panel
image: woc-panel
context: ./panel
dockerfile: ./panel/Dockerfile
- { name: wechat, image: wechat-on-cloud, context: ./docker, dockerfile: ./docker/Dockerfile, platform: linux/amd64, arch: amd64, runner: ubuntu-latest }
- { name: wechat, image: wechat-on-cloud, context: ./docker, dockerfile: ./docker/Dockerfile, platform: linux/arm64, arch: arm64, runner: ubuntu-24.04-arm }
- { name: panel, image: woc-panel, context: ./panel, dockerfile: ./panel/Dockerfile, platform: linux/amd64, arch: amd64, runner: ubuntu-latest }
- { name: panel, image: woc-panel, context: ./panel, dockerfile: ./panel/Dockerfile, platform: linux/arm64, arch: arm64, runner: ubuntu-24.04-arm }
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -50,19 +47,6 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 可选:同时推 Docker Hub。仅当仓库设置里加了 vars.DOCKERHUB_USERNAME 时启用,
# 让 fork 用户开箱即可走 GHCR想加 Docker Hub 双推只需配 1 个 Variable + 1 个 Secret。
# 配置方法repo Settings → Secrets and variables → Actions
# · Variables → DOCKERHUB_USERNAME = 你的 Docker Hub 用户名
# · Secrets → DOCKERHUB_TOKEN = Docker Hub Access Tokenhub.docker.com → Account Settings → Personal access tokens
- name: Log in to Docker Hub
if: vars.DOCKERHUB_USERNAME != ''
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# 解析烤进面板镜像的版本号:打 tag / 发 Release 时用 tag 名vX.Y.Z手动触发则用 dev-<短SHA>。
- name: Resolve build version
id: ver
run: |
@@ -72,52 +56,29 @@ jobs:
echo "version=dev-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
fi
- name: Docker metadata (tags + labels)
id: meta
uses: docker/metadata-action@v5
with:
# metadata-action 接受多行 images每行一个目标 registrybuild-push-action 会同时推。
# Docker Hub 那行只在配了 DOCKERHUB_USERNAME 时出现;否则只剩 GHCR 一行(与旧行为一致)。
images: |
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.image }}
${{ vars.DOCKERHUB_USERNAME != '' && format('docker.io/{0}/{1}', vars.DOCKERHUB_USERNAME, matrix.image) || '' }}
# 语义化标签vX.Y.Z → X.Y.Z / X.Y / X默认分支额外打 latest
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
# ⚠️ 产物门禁doc/dev/发布门禁.md先构建 amd64 到本地并跑冒烟探针,全过才推送。
# 教训v1.4.0 本地门禁全绿,但 CI 现场 apt 拉到更新的 Chromium(150) —— 产物与本地验证的
# 不是同一个东西,坏镜像直发全体用户。门禁必须跑在「即将发布的那个产物」上。
- name: Build amd64 for smoke test (instance image)
if: matrix.name == 'wechat'
# ── 产物门禁:仅在 wechat + amd64原生 x86可 docker run上跑冒烟探针 ──
# 「验证的东西和发布的东西必须是同一个 artifact」DPI/CEF 类问题与架构无关amd64 探针即可覆盖。
- name: Build amd64 image for smoke test (instance only)
if: matrix.name == 'wechat' && matrix.arch == 'amd64'
uses: docker/build-push-action@v6
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
platforms: linux/amd64
push: false
load: true
tags: woc-probe:ci
cache-from: type=gha,scope=${{ matrix.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.name }}
cache-from: type=gha,scope=${{ matrix.name }}-${{ matrix.arch }}
- name: Smoke probes (CEF/DPI/crash-loop/wechat-mode)
if: matrix.name == 'wechat'
- name: Smoke probes (DPI / CEF 两次启动 / 崩溃循环 / 微信模式)
if: matrix.name == 'wechat' && matrix.arch == 'amd64'
run: |
set -eu
fail() { echo "::error::$1"; docker logs probe 2>&1 | tail -40 || true; exit 1; }
docker run -d --name probe --security-opt seccomp=unconfined --shm-size=1g \
-e PUID=1000 -e PGID=1000 -e WOC_APP_TYPE=chromium woc-probe:ci
# 等桌面会话就绪
for i in $(seq 1 30); do docker exec probe pgrep xsettingsd >/dev/null 2>&1 && break; sleep 3; done
# ① XSETTINGS DPI 单位96 DPI = 98304写 96 会让一切 CEF 崩溃)
docker exec probe bash -c 'DISPLAY=:1 dump_xsettings | grep -q "Xft/DPI 98304"' || fail "DPI 探针失败"
echo "① DPI ✓"
# ② CEF 探针连跑两次:第 2 次 = 非首次启动路径Chromium 150 正是死在这里)
docker exec probe bash -c '
for round in 1 2; do
DISPLAY=:1 timeout 8 chromium --no-sandbox --disable-gpu --user-data-dir=/tmp/probe-profile about:blank >/dev/null 2>&1 &
@@ -126,33 +87,109 @@ jobs:
kill -9 $p 2>/dev/null; pkill -9 chromium 2>/dev/null; sleep 1
done' || fail "CEF 探针崩溃(第 1 或第 2 次启动)"
echo "② CEF 两次启动 ✓"
# ③ autostart 崩溃循环检测:干净 30s 窗口零退出
sleep 30
exits=$(docker logs --since 30s probe 2>&1 | grep -c '已退出' || true)
[ "$exits" -eq 0 ] || fail "autostart 崩溃循环30s 内退出 $exits 次"
echo "③ 零崩溃 ✓"
docker rm -f probe
# ④ 微信模式走到「等待安装」
docker run -d --name probe --security-opt seccomp=unconfined --shm-size=1g \
-e PUID=1000 -e PGID=1000 -e WOC_APP_TYPE=wechat woc-probe:ci
ok=0
for i in $(seq 1 30); do
docker logs probe 2>&1 | grep -q "尚未安装" && ok=1 && break; sleep 3
done
for i in $(seq 1 30); do docker logs probe 2>&1 | grep -q "尚未安装" && ok=1 && break; sleep 3; done
[ "$ok" = "1" ] || fail "微信模式未到达等待安装"
echo "④ 微信模式 ✓"
docker rm -f probe
- name: Build and push (amd64 + arm64)
# ── 按 digest 推送到 GHCR不打 tag。同一内容跨仓库 digest 相同merge 时再复制到 Docker Hub ──
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
platforms: linux/amd64,linux/arm64
# 仅面板镜像消费 WOC_VERSION实例镜像不展示版本号避免「未使用的 build-arg」告警。
platforms: ${{ matrix.platform }}
build-args: ${{ matrix.name == 'panel' && format('WOC_VERSION={0}', steps.ver.outputs.version) || '' }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.name }}
cache-from: type=gha,scope=${{ matrix.name }}-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.name }}-${{ matrix.arch }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.image }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digest-${{ matrix.name }}-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- { name: wechat, image: wechat-on-cloud }
- { name: panel, image: woc-panel }
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digest-${{ matrix.name }}-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Docker Hub
if: vars.DOCKERHUB_USERNAME != ''
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker metadata (tags + labels)
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.image }}
${{ vars.DOCKERHUB_USERNAME != '' && format('docker.io/{0}/{1}', vars.DOCKERHUB_USERNAME, matrix.image) || '' }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
# 从两个 arch 的 digest都在 GHCR合成多架构 manifest一条命令打上所有 tag含 Docker Hub
# imagetools 会把 blob 从 GHCR 复制到 Docker Hub
- name: Create and push multi-arch manifest
working-directory: /tmp/digests
run: |
set -eux
tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
srcs=""
for f in *; do srcs="$srcs ${REGISTRY}/${{ github.repository_owner }}/${{ matrix.image }}@sha256:$f"; done
docker buildx imagetools create $tags $srcs
- name: Verify multi-arch manifest
run: |
set -eux
docker buildx imagetools inspect "${REGISTRY}/${{ github.repository_owner }}/${{ matrix.image }}:${{ steps.meta.outputs.version }}"