Container
Container
类是对本地容器的封装。
实例属性
attrs
dict
类型,内容与 docker inspect
的结果一致。
此属性的结果是可缓存的:多次调用 .attrs
获取的是同一个对象,可通过 id()
函数验证。当调用 .reload()
方法时 .attrs
字典对象会发生变化:
import docker
client = docker.from_env()
z = client.containers.get("zealous_wiles")
z.attrs ## {'Id': 'b3a13392baa3aec99e7fab29cf551ee654ce6d8b11fe7de9e0e47ca3cb383c30', ...
type(z.attrs) ## <class 'dict'>
id(z.attrs) ## 140522907583936
id(z.attrs) ## 140522907583936
z.reload()
id(z.attrs) ## 140522907594816
id
64 位完整 ID
z.id ## 'b3a13392baa3aec99e7fab29cf551ee654ce6d8b11fe7de9e0e47ca3cb383c30'
short_id
12 位短 ID
z.short_id ## 'b3a13392baa3'
name
z.name ## 'zealous_wiles'
image
容器镜像,docker.models.images.Image
实例。
z.image ## <Image: 'ubuntu:focal'>
type(z.image) ## <class 'docker.models.images.Image'>
labels
容器标签,dict
类型。
z.labels ## {'org.opencontainers.image.ref.name': 'ubuntu', 'org.opencontainers.image.version': '20.04'}
status
容器状态,str
类型。未做封装,直接返回服务端的原始值。
z.status ## 'running'
实例方法
attach(**kwargs)
Attach to this container. (似乎与 .logs()
方法差不多,后者参数更全面)
stdout
(bool): 返回结果包含 stdoutstderr
(bool): 返回结果包含 stderrstream
(bool): 是一次性返回容器的所有输出,还是返回一个迭代器增量获取输出logs
(bool): 返回结果包含之前的输出
返回结果取决于 stream
参数的取值:bytes | Iterator[bytes]
服务端可能会抛出 docker.errors.APIError
异常。
attach_socket(**kwargs)
Like attach()
, but returns the underlying socket-like object for the HTTP request.
params
(dict): 这个参数字典会传递给attach()
方法ws
(bool): Use websockets instead of raw HTTP.
服务端可能会抛出 docker.errors.APIError
异常。
暂时不知道这个方法的具体用法。
z.attach_socket() ## <socket.SocketIO object at 0x7fce09e20fd0>
z.attach_socket(ws=True) ## ValueError: scheme http+docker is invalid
commit(...)
将容器运行时保存为镜像,与 docker commit
相似。
repository
(str): 镜像包名称(docker.barwe.cn/ubuntu:focal 中的 docker.barwe.cn/ubuntu)tag
(str): 镜像标签(docker.barwe.cn/ubuntu:focal 中的 focal)message
(str): 提交日志author
(str): 作者pause
(bool): 在保存镜像前是否暂停容器运行changes
(str): Dockerfile instructions to apply while committingconf
(dict): The configuration for the container. See the Engine API documentation for full details.
服务端可能会抛出 docker.errors.APIError
异常。
diff()
审查容器内文件系统的变化。返回值类型为:None | { Path: str; Kind: int; }[]
先在容器内新建一个目录:
root@b3a13392baa3:/# mkdir xxx
然后审查:
z.diff() ## [{'Path': '/xxx', 'Kind': 1}]
exec_run(...)
在容器内执行命令,与 docker exec
相似。
def exec_run(
self,
cmd: str | list[str], # 要执行的命令
stdout=True,
stderr=True,
stdin=False,
privileged=False,
user='root', # 指定执行该命令的用户
detach=False,
tty=False, # 交互式命令需要启用伪终端
stream=False, # 交互式命令需要启用流式输出
socket=False,
environment: list[str] | dict=None, # 形如 ["PASSWORD=xxx"] 或者 {"PASSWORD":"xxx"}
workdir:str=None, # 执行此命令的工作目录
demux=False, # 分开返回 stdout & stderr
) -> docker.models.containers.ExecResult
该方法返回一个 docker.models.containers.ExecResult
实例,该实例有两个属性:
exit_code
(int): 非交互式命令退出状态码为整数,交互式命令为None
output
: 输出结果- 非交互式命令返回的是 字节串 或者 字节串元组
- 交互式命令返回的是
docker.types.daemon.CancellableStream
可迭代对象 socket=True
时返回的是socket.SocketIO
对象
demux | 非交互式命令 | 交互式命令 |
---|---|---|
False | bytes | CancellableStream[bytes] |
True | Tuple[bytes|None, bytes|None] 分别对应 stdout, stderr | CancellableStream[Tuple[bytes|None, bytes|None]] |
返回值类型测试:
1、默认情况下返回字节串:
result = container.exec_run("pwd")
print(result, type(result), sep="\n")
## ExecResult(exit_code=0, output=b'/\n')
## <class 'docker.models.containers.ExecResult'>
result = container.exec_run("pwd", demux=True)
print(result, type(result), sep="\n")
## ExecResult(exit_code=0, output=(b'/\n', None))
## <class 'docker.models.containers.ExecResult'>
2、top
这类 交互式命令 不能直接获取输出:
result = container.exec_run("top")
print(result, type(result), sep="\n")
## ExecResult(exit_code=1, output=b'TERM environment variable not set.\n')
## <class 'docker.models.containers.ExecResult'>
提示未设置 TERM
环境变量,根本原因是缺少交互的(伪)终端。启用伪终端:
result = container.exec_run("top", tty=True)
这行代码会阻塞主进程,我们将看不到任何输出结果。还需要启用 流式输出:
result = container.exec_run("top", tty=True, stream=True)
print(result, type(result), sep="\n")
## ExecResult(exit_code=None, output=<docker.types.daemon.CancellableStream object at 0x7f28e526e630>)
## <class 'docker.models.containers.ExecResult'>
所以对于 终端交互式命令,需要同时启用 伪终端 和 流式输出 才能不阻塞主进程,立即返回调用。
交互式命令返回的是一个 docker.types.daemon.CancellableStream
对象,我们可以对其进行迭代:
for chunk in result.output:
print(type(chunk), len(chunk))
## <class 'bytes'> 13
## <class 'bytes'> 2933
## ...
迭代会阻塞主进程,它不会结束,我们需要在循环内部手动 break 或者 return。
交互式命令启用 demux 时:
result = container.exec_run("top", tty=True, stream=True, demux=True)
for stdout, stderr in result.output:
print("stdout", type(stdout), len(stdout), "| stderr", stderr)
break
## stdout <class 'bytes'> 13 | stderr None
3、socket=True
print(container.exec_run("pwd", socket=True))
## ExecResult(exit_code=None, output=<socket.SocketIO object at 0x7f03f5ccc1c0>)
print(container.exec_run("top", socket=True, tty=True, stream=True))
## ExecResult(exit_code=None, output=<socket.SocketIO object at 0x7f03f5ccc9d0>)
综上,此方法的返回值实际上有五种(或者更多):
- 非交互式命令:
bytes
- 非交互式命令 + demux:
Tuple[bytes, bytes | None]
- 交互式命令:
CancellableStream[bytes]
- 交互式命令 + demux:
CancellableStream[Tuple[bytes, bytes | None]]
- socket=True:
socket.SocketIO
object
export(chunk_size=2097152)
将容器的文件系统导出为 .tar 文件。默认分片大小为 2 MB。
返回值是一个 Generator[bytes]
。
with open("xxxx.tar", "wb") as f:
for chunk in container.export():
f.write(chunk)
get_archive(...)
将容器中的某个文件或者文件夹导出为 .tar 文件。默认分片大小为 2 MB。
def get_archive(
self,
path: str, # 文件或者文件夹的路径
chunk_size=2097152, # 分片大小,默认2MB
encode_stream=False, # 数据在传输过程中是否编码(目前没发现特殊用途)
) -> Tuple[
Generator[bytes],
{ name:str; size:int; mode:int; mtime:str; linkTarget:str; }
] or raise docker.errors.APIError: ...
bits, stat = container.get_archive("/")
with open("root.tar.gz", "wb") as f:
for chunk in bits:
f.write(chunk)
put_archive(path, data)
将外部的 .tar 文件内容复制到容器中的指定位置。
容器内路径必须存在。
Returns True
or raise docker.errors.APIError.
kill(signal=SIGKILL)
from signal import SIGKILL
container.kill(SIGKILL)
logs(**kwargs)
提取容器的日志,类似于 docker logs
命令。
def logs(
self,
stdout=True,
stderr=True,
stream=False, # 流式输出
timestamps=False, # 显示时间戳
tail:str|int='all', # 只返回末尾指定数目的行,或者 'all'
since:datetime|int|float=None, # 只返回指定时间点之后的内容
follow=False, # Follow log output
until:datetime|int|float=None, # 只返回指定时间点之前的内容
) -> bytes | Generator[bytes]
or raise docker.errors.APIError: ...
result = container.logs()
print("[DEBUG]", type(result))
## [DEBUG] <class 'bytes'>
result = container.logs(stream=True)
print("[DEBUG]", type(result))
## [DEBUG] <class 'docker.types.daemon.CancellableStream'>
pause()
暂停容器内的所有进程。
Returns None
or raise docker.errors.APIError.
reload()
从服务端重新加载容器信息,刷新 .attrs
属性。
rename(name)
容器重命名,类似于 docker rename
命令。
新的名称不能与旧的名称相同。
Returns None
if ok or raise docker.errors.APIError.
resize(height, width)
Resize the tty session.
在脚本中似乎用不到这个方法。
stats(**kwargs)
与 docker stats
命令相似,流式输出容器的资源统计数据。
def stats(
self,
stream=True, # 流式输出(Generator[bytes|dict]),或者只输出一次(bytes|dict)
decode=False, # 是否将输出的字节串直接解码成「字典」,仅在 stream=True 时生效
) -> (bytes|dict) | (Generator[bytes|dict])
or raise docker.errors.APIError: ...
由 stream 和 decode 两个参数控制返回值的格式。
1、Returns dict
:
r = container.stats(stream=False)
print(type(r), r, sep="\n")
## <class 'dict'>
## {'read': '2024-11-28T07:23:47.6695828Z', ...
2、Returns Generator[bytes]
:
r = container.stats()
print(type(r), r, sep="\n")
for x in r:
print(type(x))
break
## <class 'generator'>
## <generator object APIClient._stream_helper at 0x7f809b762f40>
## <class 'bytes'>
3、Returns Generator[dict]
:
r = container.stats(decode=True)
print(type(r), r, sep="\n")
for x in r:
print(type(x))
break
## <class 'generator'>
## <generator object APIClient._stream_helper at 0x7fe2670cef40>
## <class 'dict'>
容器详细信息:
type int = number;
type str = string;
type datetime_str = string;
interface ContainerStats {
id: str;
name: str;
read: datetime_str;
preread: datetime_str;
pids_stats: { current: int; limit: int };
blkio_stats: {
io_service_bytes_recursive: {
major: int;
minor: int;
op: "read" | "write";
value: int;
}[];
io_serviced_recursive: unknown;
io_queue_recursive: unknown;
io_service_time_recursive: unknown;
io_wait_time_recursive: unknown;
io_merged_recursive: unknown;
io_time_recursive: unknown;
sectors_recursive: unknown;
};
num_procs: int;
storage_stats: {};
cpu_stats: {
cpu_usage: {
total_usage: int;
usage_in_kernelmode: int;
usage_in_usermode: int;
};
system_cpu_usage: int;
online_cpus: int;
throttling_data: {
periods: int;
throttled_periods: int;
throttled_time: int;
};
};
precpu_stats: {
cpu_usage: {
total_usage: int;
usage_in_kernelmode: int;
usage_in_usermode: int;
};
system_cpu_usage: int;
online_cpus: int;
throttling_data: {
periods: int;
throttled_periods: int;
throttled_time: int;
};
};
memory_stats: {
usage: int;
stats: {
active_anon: int;
active_file: int;
anon: int;
anon_thp: int;
file: int;
file_dirty: int;
file_mapped: int;
file_writeback: int;
inactive_anon: int;
inactive_file: int;
kernel_stack: int;
pgactivate: int;
pgdeactivate: int;
pgfault: int;
pglazyfree: int;
pglazyfreed: int;
pgmajfault: int;
pgrefill: int;
pgscan: int;
pgsteal: int;
shmem: int;
slab: int;
slab_reclaimable: int;
slab_unreclaimable: int;
sock: int;
thp_collapse_alloc: int;
thp_fault_alloc: int;
unevictable: int;
workingset_activate: int;
workingset_nodereclaim: int;
workingset_refault: int;
};
limit: int;
};
networks: {
[name: str]: {
rx_bytes: int;
rx_packets: int;
rx_errors: int;
rx_dropped: int;
tx_bytes: int;
tx_packets: int;
tx_errors: int;
tx_dropped: int;
};
};
}
start(**kwargs)
启动容器,类似于 docker start
命令,但是不支持 attach 功能。
Returns None
if ok or raise docker.errors.APIError.
restart(**kwargs)
重启容器,类似于 docker restart
命令。
def restart(
self,
timeout=10, # 尝试stop容器,超过这个秒数会强制kill容器,之后再restart容器
) -> None
or raise docker.errors.APIError: ...
stop(**kwargs)
停止运行容器,类似于 docker stop
命令。
timeout
: 超时后直接发送 SIGKILL 给服务端,默认 10 秒
Returns None
if ok or raise docker.errors.APIError.
remove(**kwargs)
移除容器,类似于 docker rm
命令。
def remove(
self,
v:bool, # 同时移除此容器关联的数据卷
link:bool, # Remove the specified link and not the underlying container
force:bool, # 强制移除正在运行的容器(SIGKILL)
) -> None
or raise docker.errors.APIError: ...
top(**kwargs)
获取容器当前运行的进程信息,类似于 ps
命令。
ps_args
(str): 传递给ps
命令的参数字符串
返回一个字典:{ Processes: str[][]; Titles: str[]; }
Titles
: 表格列名Processes
: 进程列表,每个进程信息表示为一个字符串数组,长度与Titles
一致
默认参数:
r = container.top()
print(type(r), list(r.keys()), r["Titles"], r["Processes"][0], sep="\n")
## <class 'dict'>
## ['Processes', 'Titles']
## ['UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD']
## ['root', '43205', '43183', '0', '11月27', 'pts/0', '00:00:00', 'bash']
自定义参数:
r = container.top(ps_args="-aux")
print(r["Titles"], r["Processes"][0], sep="\n")
## ['USER', 'PID', '%CPU', '%MEM', 'VSZ', 'RSS', 'TTY', 'STAT', 'START', 'TIME', 'COMMAND']
## ['root', '43205', '0.0', '0.0', '4116', '3328', 'pts/0', 'Ss+', '11月27', '0:00', 'bash']
unpause()
恢复所有进程。
update(**kwargs)
更新容器的资源配置。
def update(
self,
blkio_weight: int, # Block IO 的相对权重,取值 10 ~ 1000
cpu_period: int, # CPU 时间片大小
cpu_quota: int, # 限制最大核数
cpu_shares: int, # 容器之间竞争优先级
cpuset_cpus: str, # 限制物理CPU
cpuset_mems: str, # 限制NUMA内存节点
mem_limit: int | str, # 物理内存硬限制 --memory
mem_reservation: int | str, # 物理内存软限制,同 --memory-reservation
memswap_limit: int | str, # 物理+虚拟内存限制,同 --memory-swap
kernal_memory: int | str, # 内核内存限制(区别于用户空间的应用内存)
restart_policy: dict, # 容器重启策略
):
Completely Fair Scheduler: 完全公平排程器。
--cpu-period
决定排程器每个执行任务的时间片大小,需要结合任务类型、系统负载、内存网络等综合设置,经验值为 100 ms。
--cpu-quota
在多核系统中决定容器在一个 CPU 时间片内最多可以使用的 CPU 时间(等效于 CPU 核数)。
--cpu-shares
表示在多个容器之间分配 CPU 资源的相对权重,它不直接限制容器的 CPU 使用率,而是定义容器在 CPU 资源紧张时竞争 CPU 的优先级。默认情况下,每个容器的--cpu-shares
为 1024,给关键容器设置更高的--cpu-shares
以保证它们在 CPU 资源紧张时仍然相对流畅的运行。
--cpuset-cpus
用于精确控制容器只能在哪些物理 CPU 上运行,例如--cpuset-cpus="0,1"
将容器限制在前两个物理 CPU 上。
--cpuset-mems
用于精确控制容器只能在指定的内存节点(Non-Uniform Memory Access, NUMA)上分配资源。将特定的任务隔离到特定的内存节点中。
--memory
限制容器使用的内存,超过此限制时容器可能会被杀掉,或者回收其内存。 (硬性限制)
--memory-reservation
对内存进行软限制:内存充足时,无视这个限制;内存不足时,此容器占用的内存会被优先降低到限制之下(不是强制的)。
--memory-swap
物理内存+交换空间上限,一般交换空间大小等于物理内存大小,所以此选项默认值为--memory
的两倍。
wait(**kwargs)
阻塞主进程,知道容器停止,返回退出状态码。类似于 docker wait
命令。
timeout
(int): 超时后抛出requests.exceptions.ReadTimeout
异常condition
(str): 等待容器变成该状态,默认为 not-running, 还可以是 next-exit, removed
评论区