当前位置: 首页 > article >正文

[k8s理论知识]5.docker基础(四)Dockerfile构建

容器镜像

前面我们已经介绍了容器的rootfs文件系统。在传统的手动制作roofts的过程中,需要执行以下步骤:

  • 下载基础操作系统的用户态文件
  • 配置文件系统中的各种目录和文件
  • 手动安装依赖、配置环境

这个过程不仅繁琐且容易出错,特别是在需要创建多个不同的环境或频繁更新镜像的场景下。Dockerfile提供了一种声明式的方式来构建和定制容器的根文件系统(rootfs)。通过定义一系列步骤(指令),结合 Docker 的镜像分层机制,能够自动化地构建出一个完整的 rootfs,并确保每次构建时的一致性。

以下是一个示例Dockerfile。用docker build -t my_custom_image .来构建一个容器镜像。注意这里有一个.。这个点代表当前目录下所有文件,和Dockerfile中的COPY .是一个意思。

# 使用官方提供的Python开发镜像作为基础镜像
FROM python:3.9-slim

# 将工作目录切换为/app
WORKDIR /app

# 将当前目录下的所有内容复制到/app下
ADD . /app

# 使用pip命令安装这个应用所需要的依赖
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# 允许外界访问容器的80端口
EXPOSE 80

# 设置环境变量
ENV NAME World

# 设置容器进程为:python app.py,即:这个Python应用的启动命令
CMD ["python", "app.py"]

 CMD的意思是 Dockerfile 指定 python app.py 为这个容器的进程。这里,app.py 的实际路径是 /app/app.py。所以,CMD ["python", "app.py"]等价于"docker run <image> python app.py"。

在使用 Dockerfile 时,你可能还会看到一个叫作 ENTRYPOINT 的原语。实际上,它和 CMD 都是 Docker 容器进程启动所必需的参数,完整执行格式是:“ENTRYPOINT CMD”。但是,默认情况下,Docker 会为你提供一个隐含的 ENTRYPOINT,即:/bin/sh -c。所以,在不指定 ENTRYPOINT 时,比如在我们这个例子里,实际上运行在容器里的完整进程是:/bin/sh -c "python app.py",即 CMD 的内容就是 ENTRYPOINT 的参数。

创建一个应用程序:

[root@master flask-app]# cat app.py 
from flask import Flask
import socket
import os

app = Flask(__name__)

@app.route('/')
def hello():
    html = "<h3>Hello {name}!</h3>" \
           "<b>Hostname:</b> {hostname}<br/>"           
    return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname())
    
if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

上面的dockerfile有规定需要的依赖,因此还需要写一个requirements.txt文件。

[root@master flask-app]# cat requirements.txt 
Flask

现在完事具备,进行镜像构建。我们构建了一个叫helloworld的镜像。

[root@master flask-app]# ls
app.py  Dockerfile  requirements.txt
[root@master flask-app]# docker build -t helloworld .
[+] Building 5.9s (9/9) FINISHED                                                                                                      docker:default
 => [internal] load build definition from Dockerfile                                                                                            0.0s
 => => transferring dockerfile: 519B                                                                                                            0.0s
 => [internal] load metadata for docker.io/library/python:3.9-slim                                                                              0.0s
 => [internal] load .dockerignore                                                                                                               0.0s
 => => transferring context: 2B                                                                                                                 0.0s
 => [1/4] FROM docker.io/library/python:3.9-slim                                                                                                0.0s
 => [internal] load build context                                                                                                               0.0s
 => => transferring context: 109B                                                                                                               0.0s
 => CACHED [2/4] WORKDIR /app                                                                                                                   0.0s
 => [3/4] ADD . /app                                                                                                                            0.0s
 => [4/4] RUN pip install --trusted-host pypi.python.org -r requirements.txt                                                                    5.7s
 => exporting to image                                                                                                                          0.1s 
 => => exporting layers                                                                                                                         0.1s 
 => => writing image sha256:b93293d8c593c917c8f49f6ad1c25b9cdc7e7609bf629a64c3f311cc5d4593ee                                                    0.0s 
 => => naming to docker.io/library/helloworld                                                                                                   0.0s 

分别查看基础镜像python:3.9-slim的镜像和helloword的镜像。 

[root@master flask-app]# docker inspect python:3.9-slim
[                                                                                                                                                    
    ...
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:e0781bc8667fb5ebf954df4ae52997f6f5568ec9f07e21e5db7c9d324ed41e1f",
                "sha256:cc2286334a7b859aa0ec5587bed4005b2fae27a61f5e4843dd01dfee6ecf185b",
                "sha256:d42276be00b539521cf3a61a5e7b7559f570ca9086b8292bc9e24086a137b524",
                "sha256:f71fab544a97b53995586065bf69aa01f29f3872a311209f03300db323ff23aa",
                "sha256:ec6949936a528cbd8ad7cac0f8bf5373d741c0e16155ea68dbee7238410dd93c"
            ]
        },
        "Metadata": {
            "LastTagTime": "2024-10-18T14:51:00.297312684+08:00"
        }
    }
]
[root@master flask-app]# docker inspect helloworld
[
   ...
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:e0781bc8667fb5ebf954df4ae52997f6f5568ec9f07e21e5db7c9d324ed41e1f",
                "sha256:cc2286334a7b859aa0ec5587bed4005b2fae27a61f5e4843dd01dfee6ecf185b",
                "sha256:d42276be00b539521cf3a61a5e7b7559f570ca9086b8292bc9e24086a137b524",
                "sha256:f71fab544a97b53995586065bf69aa01f29f3872a311209f03300db323ff23aa",
                "sha256:ec6949936a528cbd8ad7cac0f8bf5373d741c0e16155ea68dbee7238410dd93c",
                "sha256:28ed04d7ed6da2e98aa6a62a782506046585764b56b5493a65ff38a61ddf111e",
                "sha256:7bc81f71f3f328c2a6b7072c516b296c175a2a56ce858aaec31126b658fee12d",
                "sha256:49715b59dd2acbf3053e3a43c9d891d32d83b9e46346c79664f57dbce1487ab9"
            ]
        },
        "Metadata": {
            "LastTagTime": "2024-10-18T14:53:06.746455214+08:00"
        }
    }
]

 这是因为dockerfile中每写一条指令就增加一层。这里有三个指令就增加了三层。RUN 指令会创建一层,因为它执行了系统命令并修改了文件系统(比如安装软件包)。COPY 指令会创建一层,因为它将主机文件系统中的内容复制到了容器文件系统。WORKDIR 虽然不直接改变文件系统,但它可能也会生成一层,特别是如果它创建了新的目录。

FROM python:3.9-slim
# 指令1:安装依赖
RUN apt-get update && apt-get install -y some-package
# 指令2:复制应用代码
COPY . /app
# 指令3:设置工作目录
WORKDIR /app

但是,如果你使用了多个 RUN 指令,这将会创建两层。

RUN apt-get update
RUN apt-get install -y requirements.txt
启动容器

用下面命令创建一个基于hellowold的容器。可以看到容器进程的PID。

[root@master flask-app]# docker run -d -p 4000:80 helloworld
[root@master flask-app]# docker ps | grep helloworld
a12905d273a8   helloworld                                          "python app.py"           11 seconds ago   Up 10 seconds   0.0.0.0:4000->80/tcp   sweet_diffie
[root@master flask-app]# curl http://localhost:4000
<h3>Hello World!</h3><b>Hostname:</b> a12905d273a8<br/>
[root@master flask-app]# docker inspect --format "{{.State.Pid}}" a12905d273a8
60170

按照上一节的方式查看容器的挂载点。 

root@master flask-app]# docker inspect a12905d273a8
[
    ...
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/b59a08ebe30ade52611f94365f3556bae223b37d88d0cb7592679afe69f5d8ce-init/diff:/var/lib/docker/overlay2/uit1drj7ap3e8sfd1qomx1mm0/diff:/var/lib/docker/overlay2/jr5fk1tl1ax31hdzhw8t7uib5/diff:/var/lib/docker/overlay2/ta41v10i47ncwxttxy68wq8j1/diff:/var/lib/docker/overlay2/a0ed33e4dde514234341f448746c0d9f0212dd2d36fa6547e903778ac6ac4a4f/diff:/var/lib/docker/overlay2/190310e299528d08415447d03851c32851d7ff87d5abaec51d3568f000938a43/diff:/var/lib/docker/overlay2/50e5422941afaa40a3aeb189c92e2e1d54edc84395139301db3ddef2cc5d6c7e/diff:/var/lib/docker/overlay2/5a2e03241015ce3d3a229d07192bfc2aee321bbdac1493edea090bc19cf979e8/diff:/var/lib/docker/overlay2/ceb41f31ae5d549df537ca2069762f289e0560205a4ff509ede50393c1c2d457/diff",
                "MergedDir": "/var/lib/docker/overlay2/b59a08ebe30ade52611f94365f3556bae223b37d88d0cb7592679afe69f5d8ce/merged",
                "UpperDir": "/var/lib/docker/overlay2/b59a08ebe30ade52611f94365f3556bae223b37d88d0cb7592679afe69f5d8ce/diff",
                "WorkDir": "/var/lib/docker/overlay2/b59a08ebe30ade52611f94365f3556bae223b37d88d0cb7592679afe69f5d8ce/work"
            },
            "Name": "overlay2"
        },

进入容器,查看他的cgroups设置:

[root@master ~]# docker exec -it a12905d273a8 /bin/bash
root@a12905d273a8:/app# cat /proc/self/cgroup
11:pids:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
10:memory:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
9:perf_event:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
8:cpuset:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
7:net_prio,net_cls:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
6:devices:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
5:blkio:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
4:cpuacct,cpu:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
3:hugetlb:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
2:freezer:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope
1:name=systemd:/system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope

 根据cgroups设置找到容器的cpu控制组,查看procs文件,果然发现该容器的PID。

[root@master]# cd /sys/fs/cgroup/cpu
[root@master cpu]# cd system.slice/docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope/
[root@master docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope]# ls
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks
[root@master docker-a12905d273a8ee6c8994d234620f7cc7d888ee44b812b5b173776d9fe09bd3f3.scope]# cat cgroup.procs 
60170
73678

查看该PID的namespace,可以看到,一个进程的每种 Linux Namespace,都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件上。

[root@master flask-app]# ls -l /proc/60170/ns
总用量 0
lrwxrwxrwx 1 root root 0 10月 18 15:05 ipc -> ipc:[4026533030]
lrwxrwxrwx 1 root root 0 10月 18 15:05 mnt -> mnt:[4026533028]
lrwxrwxrwx 1 root root 0 10月 18 15:03 net -> net:[4026533033]
lrwxrwxrwx 1 root root 0 10月 18 15:05 pid -> pid:[4026533031]
lrwxrwxrwx 1 root root 0 10月 18 15:05 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 10月 18 15:05 uts -> uts:[4026533029]
setns()函数

当执行docker exec命令的时候我们可以进入容器的内部。而这个操作所依赖的是setns() 的 Linux 系统调用。它的示例如下:

#define _GNU_SOURCE
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE);} while (0)

int main(int argc, char *argv[]) {
    int fd;
    
    fd = open(argv[1], O_RDONLY);
    if (setns(fd, 0) == -1) {
        errExit("setns");
    }
    execvp(argv[2], &argv[2]); 
    errExit("execvp");
}

这段代码是一个简单的C程序,其功能是通过Linux系统调用来实现进程加入到指定的Namespace中。它接收两个参数,第一个是要加入的Namespace文件的路径,比如/proc/25686/ns/net;第二个参数是要在这个Namespace里运行的进程,比如/bin/bash。代码通过open()系统调用打开指定的Namespace文件,然后将这个文件的描述符传递给setns()函数来实现将当前进程加入到指定的Namespace中。最后使用execvp()函数来执行指定的进程。

编译这段程序,然后执行。传入的参数为容器的PID 60170 和要执行的命令/bin/bash。

gcc -o set_ns set_ns.c 
./set_ns /proc/60170/ns/net /bin/bash 

执行完这些代码之后,我们就已经进入了这个容器的内部,具体表现为,查看网卡ifconfig,发现只有一个网卡,而宿主机是有四个网卡的,另一点是,当执行exit后,发现真的退出了。此时执行ifconfig,发现可以显示宿主机全部网卡。

[root@master flask-app]# ./set_ns /proc/60170/ns/net /bin/bash 
[root@master flask-app]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 9  bytes 632 (632.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 7  bytes 650 (650.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@master flask-app]# exit
exit
[root@master flask-app]# ifconfig
cali09721b04d08: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1480
        ether ee:ee:ee:ee:ee:ee  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

calibefc89337fa: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1480
        ether ee:ee:ee:ee:ee:ee  txqueuelen 0  (Ethernet)
        RX packets 224212076  bytes 41421978547 (38.5 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 224212076  bytes 41421978547 (38.5 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

calid0c8c564fc3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1480
        ether ee:ee:ee:ee:ee:ee  txqueuelen 0  (Ethernet)
        RX packets 1102176  bytes 371634074 (354.4 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 731015  bytes 62569826 (59.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

calif5aeba59a79: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1480
        ether ee:ee:ee:ee:ee:ee  txqueuelen 0  (Ethernet)
        RX packets 7  bytes 650 (650.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 9  bytes 632 (632.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:34:93:20:be  txqueuelen 0  (Ethernet)
        RX packets 532  bytes 37600 (36.7 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 720  bytes 993730 (970.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.244.128  netmask 255.255.255.0  broadcast 192.168.244.255
        inet6 fe80::d245:ea72:a2e1:17d9  prefixlen 64  scopeid 0x20<link>
        inet6 fe80::f1be:e2dd:e05c:eb75  prefixlen 64  scopeid 0x20<link>
        inet6 fe80::74a7:3914:ebcb:c52e  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:19:01:f6  txqueuelen 1000  (Ethernet)
        RX packets 1102176  bytes 371634074 (354.4 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 731015  bytes 62569826 (59.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 224212076  bytes 41421978547 (38.5 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 224212076  bytes 41421978547 (38.5 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

tunl0: flags=193<UP,RUNNING,NOARP>  mtu 1480
        inet 10.244.219.64  netmask 255.255.255.255
        tunnel   txqueuelen 1000  (IPIP Tunnel)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth7ebbc12: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        ether 56:0c:a3:6b:57:e5  txqueuelen 0  (Ethernet)
        RX packets 7  bytes 650 (650.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 9  bytes 632 (632.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

执行setns()可以进入容器内部,可以通过另一个实验来验证。查看当前容器的namespace与我们执行的set_ns的namespace。执行ps -eo pid,ppid可以查看pid以及他的父pid。通过pid之间的关联,判断36219是执行的./set_ns 的进程,而95158是该setns进入容器后的bin/bash进程。查看95158的命名空间和容器60170的命名空间,发现是一样的。这可以从另一个方面说明setns进入了容器的内部。

[root@master ~]# ps -eo pid,ppid,cmd | grep /bin/bash
 36219  36218 /bin/bash
 95158  36219 /bin/bash
 96815  16207 grep --color=auto /bin/bash
[root@master ~]# ls -l /proc/95158/ns/net
lrwxrwxrwx 1 root root 0 10月 18 15:40 /proc/95158/ns/net -> net:[4026533033]
[root@master ~]# ls -l /proc/60170/ns/net
lrwxrwxrwx 1 root root 0 10月 18 15:03 /proc/60170/ns/net -> net:[4026533033]

既然知道他们进入了同一个命名空间,那么可以通过指定命名空间的方式让另一个进程进入这个容器的内部。

docker run -it --net container:a12905d273a8 busybox: v1 ifconfig

此时执行ifconfig,和上面setns执行的结果是一样的。

而如果我指定–net=host,就意味着这个容器不会为进程启用 Network Namespace。这就意味着,这个容器拆除了 Network Namespace 的“隔离墙”,所以,它会和宿主机上的其他普通进程一样,直接共享宿主机的网络栈。这就为容器直接操作和使用宿主机网络提供了一个渠道。

docker exec

docker exec 命令就是通过setns()函数进入容器内部的。

现在我们运行一个exec命令。已知一个容器的pid 1为主进程,在我们的例子中是app.y,而docker exec实际上是把bin/bash进程放入了和容器相同的命名空间里面,所以当我们查看进程的时候,会有一个进程编号为17的进程。

[root@master flask-app]# docker exec -it a12905d273a8 /bin/bash
root@a12905d273a8:/app# apt-get update
...
root@a12905d273a8:/app# apt-get install -y procps
...
root@a12905d273a8:/app# ps
   PID TTY          TIME CMD
    17 pts/0    00:00:00 bash
   187 pts/0    00:00:00 ps
root@a12905d273a8:/app# ps -e -o pid,tty,time,cmd
   PID TT           TIME CMD
     1 ?        00:00:03 python app.py
    17 pts/0    00:00:00 /bin/bash
   188 pts/0    00:00:00 ps -e -o pid,tty,time,cmd

此时在宿主机上查看,可以看到docker exec本身就是一个进程,对应之前的set_ns本身就是一个进程。通过docker top <containerID>查看pid,可以更清晰的看到,这里的bin/bash命令在宿主机上的进程id为43646。而docker exe 启动的bin/bash进程和容器本身都有相同的父进程,为docker的守护进程dockerd。

[root@master ~]# ps aux | grep bin/bash
root      36219  0.0  0.0 115756  2272 pts/1    S    10:33   0:00 /bin/bash
root      43581  1.1  0.4 1518912 16424 pts/1   Sl+  20:59   0:00 docker exec -it a12905d273a8 /bin/bash
root      43646  0.5  0.0   4592  2052 pts/0    Ss+  20:59   0:00 /bin/bash
root      43717  0.0  0.0 112824   988 pts/2    S+   20:59   0:00 grep --color=auto bin/bash
[root@master ~]# ps -eo pid,ppid,cmd | grep /bin/bash
 36219  36218 /bin/bash
 43581  36219 docker exec -it a12905d273a8 /bin/bash
 43646  60148 /bin/bash
 44721  16207 grep --color=auto /bin/bash
[root@master ~]# docker top a12905d273a8
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                43646               60148               0                   20:59               pts/0               00:00:00            /bin/bash
root                60170               60148               0                   15:03               ?                   00:00:03            python app.py


http://www.kler.cn/news/362185.html

相关文章:

  • 使用QueryWrapper中IN关键字超过1000个参数后如何处理
  • Postman使用-基础篇
  • 关于jmeter中没有jp@gc - response times over time
  • el-table在某些条件下禁止选中
  • 超详细的 Stable Diffusion Webui入门教程(二)基础操作
  • 电脑视频剪辑大比拼,谁更胜一筹?
  • 微信支付V3 yansongda/pay 踩坑记录
  • 【数组知识的扩展①】
  • 一个基于Vue3开源免费的可快速开发中后台的框架,方便易用,业务没有瓶颈期!(附地址)
  • MySQL 数据库迁移至达梦 DM8 常见问题
  • Jmeter监控服务器性能
  • React源码03 - React 中的更新
  • grafana 配置prometheus
  • 界面控件DevExtreme中文教程 - 如何与Amazon S3和Azure Blob存储集成?
  • Spring XML配置方式和Spring Boot注解方式的详细对照关系
  • Docker-Consul概述以及集群环境搭建
  • GMT绘图笔记:高亮特定的研究区域
  • 【消息队列】RabbitMQ实现消费者组机制
  • 计算机毕业设计 基于Python的校园个人闲置物品换购平台的设计与实现 Python毕业设计 Python毕业设计选题【附源码+安装调试】
  • Java中的Vector,看着陌生?
  • API接口测试与优化:确保应用稳定性的必要步骤
  • Python 深度Q网络(DQN)算法详解与应用案例
  • 计算机网络考研笔记
  • 力扣题51~70
  • 动手学深度学习9.7. 序列到序列学习(seq2seq)-笔记练习(PyTorch)
  • 如何在verilog设计的磁盘阵列控制器中实现不同RAID级别(如RAID 0、RAID 1等)的切换?