demo nsenter approach to execute command in another container
This commit is contained in:
commit
673f096f25
3
fastapi-nsenter/.dockerignore
Normal file
3
fastapi-nsenter/.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
.venv
|
||||
__pycache__
|
||||
data
|
3
fastapi-nsenter/.gitignore
vendored
Normal file
3
fastapi-nsenter/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.venv
|
||||
__pycache__
|
||||
data
|
21
fastapi-nsenter/Dockerfile
Normal file
21
fastapi-nsenter/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
FROM alpine:3.21 AS base
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && apk upgrade
|
||||
|
||||
FROM base AS soffice
|
||||
RUN apk add --no-cache libreoffice musl-locales font-wqy-zenhei
|
||||
VOLUME /data
|
||||
CMD /bin/sh -c "echo \$\$ > /data/libreoffice.pid; while true; do sleep 1000000; done"
|
||||
|
||||
FROM base AS venv-builder
|
||||
RUN apk add python3-dev uv gcc musl-dev linux-headers
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN uv venv -i https://mirrors.aliyun.com/pypi/simple && uv pip install -i https://mirrors.aliyun.com/pypi/simple -r requirements.txt
|
||||
|
||||
FROM base AS service
|
||||
RUN apk add python3
|
||||
COPY --from=venv-builder /app/.venv /app/.venv
|
||||
COPY ./ /app/
|
||||
WORKDIR /app
|
||||
VOLUME /data
|
||||
CMD /app/entrypoint.sh
|
24
fastapi-nsenter/compose.yml
Normal file
24
fastapi-nsenter/compose.yml
Normal file
@ -0,0 +1,24 @@
|
||||
services:
|
||||
libreoffice:
|
||||
build:
|
||||
target: soffice
|
||||
restart: always
|
||||
volumes:
|
||||
- data:/data
|
||||
container_name: libreoffice
|
||||
service:
|
||||
build: .
|
||||
restart: always
|
||||
depends_on:
|
||||
- libreoffice
|
||||
volumes:
|
||||
- data:/data
|
||||
environment:
|
||||
- DATA_DIR=/data
|
||||
pid: container:libreoffice
|
||||
cap_add:
|
||||
- CAP_SYS_ADMIN
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
data:
|
8
fastapi-nsenter/entrypoint.sh
Executable file
8
fastapi-nsenter/entrypoint.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
set -x
|
||||
|
||||
NSENTER_PID=$(cat /data/libreoffice.pid)
|
||||
export UNO_COMMAND="nsenter -t ${NSENTER_PID} -m soffice --convert-to {outputfmt} --outdir {outputdir} {inputfilename}"
|
||||
. .venv/bin/activate
|
||||
exec fastapi run
|
59
fastapi-nsenter/main.py
Normal file
59
fastapi-nsenter/main.py
Normal file
@ -0,0 +1,59 @@
|
||||
from fastapi import FastAPI, staticfiles, UploadFile
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
import aiofile
|
||||
from asyncio import subprocess
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import glob
|
||||
import enum
|
||||
|
||||
class OutputFormat(enum.Enum):
|
||||
pdf = "pdf"
|
||||
|
||||
data_dir = os.path.realpath(os.environ.get("DATA_DIR", "./data"))
|
||||
soffice_data_dir = os.path.realpath(os.environ.get("UNO_DATA_DIR", data_dir))
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
soffice_command = os.environ.get("UNO_COMMAND", "soffice --convert-to {outputfmt} --outdir {outputdir} {inputfilename}")
|
||||
|
||||
app = FastAPI()
|
||||
app.mount("/static", staticfiles.StaticFiles(directory="static"), name="static")
|
||||
|
||||
@app.get("/")
|
||||
async def get_index():
|
||||
return FileResponse('static/index.html')
|
||||
|
||||
@app.post("/")
|
||||
async def convert_file(file: UploadFile, output_format: OutputFormat | None = None):
|
||||
output_format = output_format or OutputFormat.pdf
|
||||
|
||||
outpath = tempfile.mkdtemp(dir=data_dir)
|
||||
inputfile = os.path.join(outpath, file.filename)
|
||||
|
||||
uno_path = os.path.join(soffice_data_dir, os.path.relpath(outpath, data_dir))
|
||||
uno_inputfile = os.path.join(uno_path, file.filename)
|
||||
|
||||
async with aiofile.AIOFile(inputfile, mode="wb+") as fp:
|
||||
try:
|
||||
while True:
|
||||
content = await file.read(1024*1024*4)
|
||||
if not content:
|
||||
break
|
||||
await fp.write(content)
|
||||
except EOFError:
|
||||
pass
|
||||
else:
|
||||
print(f"file written to {inputfile}")
|
||||
|
||||
command = soffice_command.format(outputfmt=output_format.value, outputdir=uno_path, inputfilename=uno_inputfile)
|
||||
|
||||
result_process = await subprocess.create_subprocess_shell(command)
|
||||
assert await result_process.wait() == 0, f"expect return code 0, got {result_process.returncode}"
|
||||
outfiles = glob.glob(f"{uno_path}/*.{output_format.value}")
|
||||
assert len(outfiles) == 1, f"expect one output file, got {outfiles}"
|
||||
|
||||
return FileResponse(outfiles[0])
|
||||
|
||||
|
5
fastapi-nsenter/requirements.txt
Normal file
5
fastapi-nsenter/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
fastapi[standard]==0.115.8
|
||||
starlette==0.45.3
|
||||
uvicorn==0.34.0
|
||||
python-multipart==0.0.20
|
||||
aiofile==3.9.0
|
19
fastapi-nsenter/static/index.html
Normal file
19
fastapi-nsenter/static/index.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>convert to pdf</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<label for="file">choose file<input type="file" name="file" id="file" accept=".pptx,.docx"></label>
|
||||
<label for="file">Output type
|
||||
<select name="output_format">
|
||||
<option value="pdf">PDF</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
34
simple-docker/compose.yaml
Normal file
34
simple-docker/compose.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
services:
|
||||
sometools:
|
||||
# 对于 docker compose 来说,没法指定容器来共享 pid,需要固定容器名称,来给下面的另一个容器使用
|
||||
container_name: sometools
|
||||
# demo 用的,这里刻意区分了debian版本
|
||||
image: debian:buster
|
||||
# 核心是 echo $$ > /shared-pid/tool-container.pid, $$ 是自身 PID
|
||||
# echo 以后就闲置了,开始昏睡
|
||||
# 四个$是因为,yaml中两个$表示一个$,我们需要$$
|
||||
command: bash -c 'set -x; echo $$$$ > /shared-pid/tool-container.pid; while true; do sleep 100000; done'
|
||||
volumes:
|
||||
# 这个卷两个容器共享
|
||||
- shared-pid:/shared-pid
|
||||
do-something-here:
|
||||
image: debian:bookworm
|
||||
# 核心是 nsenter,进入到另一个容器的命名空间去执行命令
|
||||
# 应该会看到另一个容器的系统版本
|
||||
command: bash -c 'set -x; nsenter -t $(cat /shared-pid/tool-container.pid) -m cat /etc/os-release; echo 'exec into this container and run nsenter'; while true; do sleep 10000; done'
|
||||
volumes:
|
||||
# 这个卷两个容器共享
|
||||
- shared-pid:/shared-pid
|
||||
# 共用上面容器的 PID 命名空间
|
||||
pid: container:sometools
|
||||
# 在上面这个容器启动后再启动,因为需要等待新建 PID 命名空间
|
||||
depends_on:
|
||||
- sometools
|
||||
# 对于有一些情况,需要特权
|
||||
cap_add:
|
||||
- CAP_SYS_ADMIN
|
||||
# 实在不行就开特权
|
||||
# privileged: true
|
||||
|
||||
volumes:
|
||||
shared-pid:
|
Loading…
x
Reference in New Issue
Block a user