90 lines
4.5 KiB
Python
90 lines
4.5 KiB
Python
"""Fixed original-image clients. No user-provided Docker flags or GPU access."""
|
|
import json
|
|
from pathlib import Path, PurePosixPath
|
|
import re
|
|
import subprocess
|
|
|
|
|
|
def name(identity):
|
|
if not re.fullmatch(r'[a-f0-9]{32}', identity):
|
|
raise ValueError('原环境任务编号不合法')
|
|
return 'adamwen-native-run-' + identity
|
|
|
|
|
|
def mounts(profile):
|
|
values = profile['docker']['mounts']
|
|
for mount in values:
|
|
for key in ('source', 'target'):
|
|
value = mount[key]
|
|
if not value.startswith('/') or ',' in value or '\x00' in value or '..' in PurePosixPath(value).parts:
|
|
raise ValueError('原镜像挂载路径不合法')
|
|
if mount['target'] in ('/', '/out', '/tmp'):
|
|
raise ValueError('原镜像挂载不能覆盖任务输出目录')
|
|
return values
|
|
|
|
|
|
def command(profile, source, arguments, directory, identity):
|
|
image = profile['docker']['image']
|
|
if not re.fullmatch(r'sha256:[a-f0-9]{64}', image):
|
|
raise ValueError('原镜像必须登记不可变摘要')
|
|
container = name(identity) if identity != '{run_id}' else 'adamwen-native-run-{run_id}'
|
|
args = ['/usr/bin/docker', 'run', '--rm', '--init', '--name', container, '--label', 'xas.native-run=' + identity,
|
|
'--runtime', 'runc', '--network', 'host', '--user', '0:0', '--cap-drop', 'ALL',
|
|
'--security-opt', 'no-new-privileges', '--read-only', '--memory', '4g', '--cpus', '2', '--pids-limit', '128',
|
|
'--tmpfs', '/tmp:rw,nosuid,nodev,size=268435456', '--env', 'HOME=/tmp',
|
|
'--env', 'PYTHONUNBUFFERED=1', '--env', 'PYTHONDONTWRITEBYTECODE=1',
|
|
'--env', 'OMP_NUM_THREADS=2', '--env', 'OPENBLAS_NUM_THREADS=2', '--env', 'MKL_NUM_THREADS=2',
|
|
'--env', 'NUMBA_NUM_THREADS=2', '--env', 'NUMBA_CACHE_DIR=/tmp/numba']
|
|
entry = None
|
|
for mount in mounts(profile):
|
|
args.extend(['--mount', f"type=bind,src={mount['source']},dst={mount['target']},readonly"])
|
|
try:
|
|
relative = PurePosixPath(Path(source).as_posix()).relative_to(PurePosixPath(mount['source']))
|
|
entry = str(PurePosixPath(mount['target']) / relative)
|
|
except ValueError:
|
|
pass
|
|
if entry is None:
|
|
raise ValueError('原入口未包含在只读挂载中')
|
|
output = str(PurePosixPath(str(directory)) / 'work')
|
|
args += ['--mount', f'type=bind,src={output},dst=/out', '--workdir', '/out',
|
|
'--entrypoint', profile['interpreter'], image, '-u', entry, *arguments]
|
|
return args
|
|
|
|
|
|
def checks(profile):
|
|
result = []
|
|
for mount in mounts(profile):
|
|
path = Path(mount['source'])
|
|
valid = path.exists() and not any(p.is_symlink() for p in (path, *path.parents))
|
|
result.append({'name': '原镜像只读依赖', 'ok': valid, 'message': mount['source']})
|
|
try:
|
|
value = subprocess.run(['/usr/bin/docker', 'image', 'inspect', '--format', '{{.Id}}', profile['docker']['image']],
|
|
capture_output=True, text=True, timeout=5)
|
|
valid = value.returncode == 0 and value.stdout.strip() == profile['docker']['image']
|
|
except (OSError, subprocess.TimeoutExpired):
|
|
valid = False
|
|
result.append({'name': '原依赖镜像', 'ok': valid, 'message': '使用已登记镜像,不拉取或升级环境'})
|
|
return result
|
|
|
|
|
|
def cleanup(identity):
|
|
container = name(identity)
|
|
value = subprocess.run(['/usr/bin/docker', 'inspect', container], capture_output=True, text=True, timeout=8)
|
|
if value.returncode:
|
|
if 'no such object:' in value.stderr.lower() or 'no such container:' in value.stderr.lower():
|
|
return
|
|
raise OSError('本次原镜像客户端状态未知')
|
|
item = json.loads(value.stdout)[0]
|
|
if item.get('Name') != '/' + container or item.get('Config', {}).get('Labels', {}).get('xas.native-run') != identity:
|
|
raise ValueError('原镜像客户端归属核验失败')
|
|
identity_value = item.get('Id', '')
|
|
if not re.fullmatch(r'[a-f0-9]{64}', identity_value):
|
|
raise ValueError('原镜像客户端标识不合法')
|
|
value = subprocess.run(['/usr/bin/docker', 'rm', '-f', identity_value], capture_output=True, timeout=15)
|
|
if value.returncode:
|
|
# --rm may delete the verified container between inspect and removal.
|
|
remaining = subprocess.run(['/usr/bin/docker', 'inspect', identity_value], capture_output=True, text=True, timeout=8)
|
|
if remaining.returncode and ('no such object:' in remaining.stderr.lower() or 'no such container:' in remaining.stderr.lower()):
|
|
return
|
|
raise OSError('本次原镜像客户端收尾未确认')
|