限制镜像构建资源并清理失败中间容器
This commit is contained in:
Vendored
+3
-3
@@ -40,10 +40,10 @@ pipeline {
|
||||
steps {
|
||||
withEnv(["PYTHON_BASE_IMAGE=${params.PYTHON_BASE_IMAGE}", "DEBIAN_MIRROR=${params.DEBIAN_MIRROR ?: 'https://deb.debian.org'}"]) {
|
||||
sh '''set -eu
|
||||
docker build --target test --build-arg PYTHON_BASE_IMAGE="$PYTHON_BASE_IMAGE" --build-arg DEBIAN_MIRROR="$DEBIAN_MIRROR" -t "$CHAT_IMAGE-test" .
|
||||
docker build --target production --build-arg PYTHON_BASE_IMAGE="$PYTHON_BASE_IMAGE" --build-arg DEBIAN_MIRROR="$DEBIAN_MIRROR" \
|
||||
sh deploy/build-image.sh --target test --build-arg PYTHON_BASE_IMAGE="$PYTHON_BASE_IMAGE" --build-arg DEBIAN_MIRROR="$DEBIAN_MIRROR" -t "$CHAT_IMAGE-test" .
|
||||
sh deploy/build-image.sh --target production --build-arg PYTHON_BASE_IMAGE="$PYTHON_BASE_IMAGE" --build-arg DEBIAN_MIRROR="$DEBIAN_MIRROR" \
|
||||
--label "org.opencontainers.image.revision=$SOURCE_COMMIT" -t "$CHAT_IMAGE" .
|
||||
docker build --target certbot --build-arg PYTHON_BASE_IMAGE="$PYTHON_BASE_IMAGE" --build-arg DEBIAN_MIRROR="$DEBIAN_MIRROR" \
|
||||
sh deploy/build-image.sh --target certbot --build-arg PYTHON_BASE_IMAGE="$PYTHON_BASE_IMAGE" --build-arg DEBIAN_MIRROR="$DEBIAN_MIRROR" \
|
||||
--label "org.opencontainers.image.revision=$SOURCE_COMMIT" -t "$CERTBOT_IMAGE" .
|
||||
'''
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# 构建前至少保留 1GiB 给业务服务,避免在宿主机内存不足时继续启动构建。
|
||||
available_kib=$(awk '$1 == "MemAvailable:" { print $2 }' /proc/meminfo)
|
||||
case "$available_kib" in
|
||||
''|*[!0-9]*) echo 'Cannot read host available memory; build refused.' >&2; exit 69 ;;
|
||||
esac
|
||||
if [ "$available_kib" -lt 4194304 ]; then
|
||||
echo 'Docker build requires at least 4GiB available host memory; retry after resources recover.' >&2
|
||||
exit 75
|
||||
fi
|
||||
|
||||
# 当前节点使用 Docker 26 的经典构建器。显式固定后端,确保资源参数实际生效。
|
||||
# 限制覆盖每个 RUN 容器,失败时也清理中间容器,超时由宿主机独立终止。
|
||||
exec env DOCKER_BUILDKIT=0 timeout --signal=TERM --kill-after=30s 15m \
|
||||
docker build --memory=3g --memory-swap=3g \
|
||||
--cpu-period=100000 --cpu-quota=200000 --force-rm "$@"
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
`java-builder` 标签代理须有 Docker、Bash、Python 3、Node.js 18+、OpenSSL、SSH,并能下载所选 Python 基础镜像和 PyPI 依赖。标签不表示固定物理节点。
|
||||
|
||||
三个镜像目标均通过 `deploy/build-image.sh` 构建。当前 Docker 26 节点显式使用经典构建器,每个构建容器限制为 3GiB 内存及内存/交换总额、2 CPU;宿主机可用内存不足 4GiB 或无法读取时拒绝启动。单次构建由 GNU timeout 限制为 15 分钟,TERM 后 30 秒强制终止,流水线总上限仍为 30 分钟。正常构建失败也移除中间容器;服务器重启留下的退出容器只占磁盘。构建节点保持单执行器,须提供 GNU timeout 和 awk,切换构建后端时重新验证硬限制。离线回归使用 `node --test tests/frontend/build_resources.test.cjs`。
|
||||
|
||||
以下参数由私有灰度配置提供,实际地址和凭据值不写入源码:
|
||||
|
||||
| 参数 | 含义 |
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } = require('node:fs')
|
||||
const { tmpdir } = require('node:os')
|
||||
const { dirname, join, resolve } = require('node:path')
|
||||
const { spawnSync } = require('node:child_process')
|
||||
const test = require('node:test')
|
||||
|
||||
const root = resolve(__dirname, '../..')
|
||||
const wrapper = join(root, 'deploy/build-image.sh')
|
||||
const bash = process.platform === 'win32' ? 'C:/Program Files/Git/bin/bash.exe' : 'bash'
|
||||
const shellAvailable = process.platform !== 'win32' || existsSync(bash)
|
||||
|
||||
// 使用替身验证参数、内存预检和退出码,不启动 Docker 或访问部署环境。
|
||||
function runBuild(availableMemory, childStatus = '0') {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'frontend-build-resource-'))
|
||||
try {
|
||||
writeFileSync(join(directory, 'awk'), '#!/bin/sh\nprintf "%s\\n" "$TEST_AVAILABLE_MEMORY"\n', { mode: 0o755 })
|
||||
writeFileSync(join(directory, 'timeout'), '#!/bin/sh\nprintf "%s\\n" watchdog "$@"\nshift 3\nexec "$@"\n', { mode: 0o755 })
|
||||
writeFileSync(join(directory, 'docker'), '#!/bin/sh\nprintf "%s\\n" "builder=$DOCKER_BUILDKIT" "$@"\nexit "$TEST_CHILD_STATUS"\n', { mode: 0o755 })
|
||||
return spawnSync(bash, ['-c',
|
||||
'testbin=$(cd "$1" && pwd); shift; PATH="$testbin:$PATH"; export PATH; exec sh "$@"',
|
||||
'fixture', directory.replaceAll('\\', '/'), wrapper.replaceAll('\\', '/'),
|
||||
'--build-arg', 'VALUE=with space', '-t', 'web:test', '.'], {
|
||||
encoding: 'utf8', timeout: 10000,
|
||||
env: { ...process.env, DOCKER_BUILDKIT: '1', TEST_AVAILABLE_MEMORY: availableMemory, TEST_CHILD_STATUS: childStatus }
|
||||
})
|
||||
} finally {
|
||||
assert.equal(dirname(resolve(directory)), resolve(tmpdir()))
|
||||
rmSync(directory, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
test('all image targets use the resource gate', () => {
|
||||
const pipeline = readFileSync(join(root, 'Jenkinsfile'), 'utf8')
|
||||
for (const target of ['test', 'production', 'certbot']) {
|
||||
assert.ok(pipeline.includes(`sh deploy/build-image.sh --target ${target} `))
|
||||
}
|
||||
assert.doesNotMatch(pipeline, /docker build\s/)
|
||||
assert.match(pipeline, /timeout\(time: 30, unit: 'MINUTES'\)/)
|
||||
})
|
||||
|
||||
test('build forwards quoted arguments, enforces container budgets and propagates failure', { skip: !shellAvailable }, () => {
|
||||
const result = runBuild('4194304', '23')
|
||||
assert.equal(result.status, 23, result.stderr)
|
||||
assert.match(result.stdout, /watchdog\r?\n--signal=TERM\r?\n--kill-after=30s\r?\n15m\r?\ndocker\r?\nbuild/)
|
||||
assert.match(result.stdout, /builder=0\r?\nbuild/)
|
||||
for (const argument of ['--memory=3g', '--memory-swap=3g', '--cpu-period=100000', '--cpu-quota=200000', '--force-rm']) {
|
||||
assert.ok(result.stdout.split(/\r?\n/).includes(argument), argument)
|
||||
}
|
||||
assert.match(result.stdout, /--build-arg\r?\nVALUE=with space\r?\n-t\r?\nweb:test\r?\n\./)
|
||||
assert.equal(runBuild('8388608').status, 0)
|
||||
assert.equal(runBuild('8388608', '124').status, 124)
|
||||
})
|
||||
|
||||
test('low or unreadable host memory refuses to start Docker', { skip: !shellAvailable }, () => {
|
||||
for (const [memory, status] of [['4194303', 75], ['', 69], ['unknown', 69]]) {
|
||||
const result = runBuild(memory)
|
||||
assert.equal(result.status, status, result.stderr)
|
||||
assert.equal(result.stdout, '')
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user