参考教程1:Docker 入门教程
一、Docker解决什么问题?
解决应用程序运行环境一致性的问题。
二、Docker跟虚拟机的区别
相较于Docker,虚拟机虽然也能还原原始环境,但是有如下弊端:虚拟机资源占用多(虚拟机本身要占用不少内存),冗余步骤多(虚拟机是完整的操作系统,一些系统级命令无法跳过),启动慢(我在我破电脑上VMware上装了个Ubuntu24.24启动了10分钟还没进去)。
但是 Docker 不是虚拟机,Docker 不需要自己的内核和驱动,Docker 共享主机的内核,Docker 中只需要基础的操作系统 + 程序及依赖 + 运行环境如 Python 就够了。
三、Linux 容器
由于虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。
Linux容器非完整的操作系统,而是对进程进行隔离。对正常的进程外套了一层保护壳,对于容器内的进程来说,它接触到的资源都是虚拟的,从而实现与底层系统的隔离。Linux容器相较于虚拟机有如下优点:
- 启动快:启动容器相当于启动本机的一个进程
- 资源占用少:多个容器可以共享资源,虚拟机都是独享资源。
- 体积小:容器只要包含用到的组件即可,而虚拟机是整个操作系统的打包
四、Docker 是什么?
**Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。**是目前最流行的Linux容器解决方案。
Docker 会把应用和依赖打包成一个"镜像"(Image),"镜像"(Image)不是单一文件,而是由多层文件系统组成,在使用时看起来就像一个独立的打包文件。运行镜像就会生成容器,容器内运行的程序就像在真实系统中运行一样。
五、Docker 的用途
- 提供一次性环境:比如测试他人软件时。
- 提供弹性云服务:因Docker容器可随时开关,适合动态扩容和缩容。
- 组件微服务架构:通过多个容器,一台机器就可以跑多个服务,因此本机就可以模拟出微服务架构。
六、Docker 安装(停服解决)
Docker 是一个开源的商业产品,有两个版本:
- 社区版(Community Edition,缩写为 CE)
- 企业版(Enterprise Edition,缩写为 EE)。
企业版包含了一些收费服务,个人开发者一般用不到。下面的介绍都针对社区版。
Docker CE 的安装 请参考官方文档:
Ubuntu24.04 (LTS) 安装 Docker 步骤:
因为从04年开始,因为中国开始强力执行《数据安全法》,Docker国内服务器停服了。具体原因及解决办法参见:https://github.com/dongyubin/DockerHub ,所以要想在国内安装Docker还真不能按照官网的方式了。可以参考这个网站的方法试试。
安装完成后,运行下面的命令,验证是否安装成功。
shell
$ docker version
# 或者
$ docker info
Docker 需要用户具有 sudo 权限,为了避免每次命令都输入sudo,可以把用户加入 Docker 用户组(官方文档)。
bash
$ sudo usermod -aG docker $USER
Docker 是服务器----客户端架构。命令行运行docker命令的时候,需要本机有 Docker 服务。如果这项服务没有启动,可以用下面的命令启动(官方文档)。
shell
# service 命令的用法
$ sudo service docker start
# systemctl 命令的用法
$ sudo systemctl start docker
!IMPORTANT
Docker 是 "服务器 -客户端" 架构如何理解?
Docker Daemon(服务端)
- Docker daemon(
dockerd)是一个长期运行在宿主机上的后台服务(daemon),负责管理 Docker 对象(镜像、容器、网络、卷等)并响应客户端(如docker命令)的请求。- 提供一个 REST API (默认通过 Unix Socket
/var/run/docker.sock或 TCP 端口)供客户端调用。Docker CLI(客户端)
- 想要用客户端,服务端得先启动
- 你在命令行输入的
docker run ...、docker ps等命令,其实是一个客户端程序。- 它不会直接操作容器,而是通过 API 请求与 Docker Daemon 通信,告诉它要执行什么操作。
当你运行
docker run hello-world:
docker命令(client)解析参数- 通过 Unix socket (默认
/var/run/docker.sock)发送请求给dockerddockerd调用内核功能创建容器- 容器的 stdout/stderr 通过同一个 socket 流式返回给 client,显示在你的终端
七、Image文件
Docker 把应用程序及其依赖打包在 Image 文件里,Image是二进制文件 ,只有通过 Image文件 才能生成 Docker容器。
- Image文件是容器的模板,Docker根据Image生成容器的实例。故一个Image文件可以生成多个容器。
- 实际开发中,通常在 Image 基础上添加一些个性化配置形成自己的 Image(比如可以在Ubuntu的Image文件基础上加入Apache服务器形成自己的Image)
Docker 中操作 Image 文件的命令:
shell
# 列出本机所有Image,注意:看到的不是一个单独文件,而是一堆目录组合起来的"逻辑镜像")
# 但是通过 docker save image:tag -o myimage.tar 就可以得到一个真正意义上的单文件
$ docker image ls
# 删除 image 文件
$ docker image rm [imageName]
Image文件是通用的,一台机器的Image放到另一台机器上,他们都装上docker,那他们可以用同一个Image。
制作Image不是从零开始搞,而是基于别人的Image搞。Docker 的官方镜像仓库 Docker Hub (支持无限公共仓库、私有库、自动构建等功能。)是最重要、最常用的 image 仓库。你也可以出售自己制作的Image。
八、实例:hello world
下面,我们通过最简单的 image 文件"hello world",感受一下 Docker。
!IMPORTANT
国内连接 Docker 的官方仓库很慢,还会断线,需要将默认仓库改成国内的镜像网站。
首先,运行下面的命令,将 image 文件从仓库抓取到本地。
shell
docker image pull library/hello-world
-
上述的
docker image pull是抓取image文件的意思。 -
library/hello-world是image文件在仓库里的位置,其中library是image文件所在的组,hello-world是image文件的名字。!NOTE
仓库:Docker Hub 是 Docker 官方维护的公共镜像仓库,地址是
https://hub.docker.com,镜像实际存储在 Docker 官方的云服务器上。当然,你可以配置docker从自定义仓库 拉取,如私有仓库 (公司内部搭建的 Harbor、Nexus 等)或 国内镜像加速器 (阿里云、腾讯云、DaoCloud 等)。这样docker pull会从你指定的仓库拉取而非 Docker Hub了。[Docker国内服务器停服了。具体原因及可用的镜像加速器参见:https://github.com/dongyubin/DockerHub ]组:组的官方叫法是命名空间,比如官方镜像 通常放在
library命名空间下,用户或组织上传的镜像会放在他们自己的命名空间下,比如mycompany/myapp。)
因为library 命名空间下为默认明空间,所以可以省略,上面的命令还可写成:
shell
docker image pull hello-world
如果抓取成功,就可以在本机用下面的命令看到这个image文件了:
shell
docker image ls
然后运行这个image文件:
docker container run hello-world (旧的简写命令:docker run hello-world)
上面这条命令会依据image文件生成一个正在运行的容器实例。(注意,docker container run命令具有 自动抓取 image 文件 的功能。如果发现本地没有指定的 image 文件,就会从仓库自动抓取。因此,前面的docker image pull命令并不是必需的步骤。)
若运行成功,屏幕上会输出:
$ docker container run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
... ...
输出这段提示以后,hello-world 容器就会停止运行,容器自动终止。
有些容器不会自动终止,因为提供的是服务,比如,安装运行 <container_name> 的 image, 就可以在命令行体验Ubuntu系统:
shell
$ docker container run -it <container_name> /bin/bash
参数解释:
-it参数:容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。ubuntu:image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。
对于不自动终止的容器,必须用 docker container kill [containID]手动终止。
九、Container 文件(由Image文件生成)
**image 文件生成的容器实例,本身也是一个文件,称为容器文件。**也就是说,一旦容器生成,就会同时存在两个文件:
- image 文件
- 容器文件
而且关闭容器并不会删除容器文件,只是容器停止运行而已。如果要彻底删除容器文件,需要如下命令:
shell
$ docker container rm [containerID] # 老的简写命令 docker rm
可以通过下面的命令查看 containerID,且docker container kill [containID]需要的容器ID也这样得到:
shell
# 列出本机正在运行的容器, 输出结果之中,包括容器的 ID。
$ docker container ls # 老的简写命令 docker ls
# 列出本机所有容器,包括终止运行的容器, 输出结果之中,包括容器的 ID。
$ docker container ls --all # 注意all前面必须两个杠杠
终止运行的 container 容器文件会占用磁盘空间,可以用如下命令删除:
shell
$ docker container rm [containerID]
十、Dockerfile 文件
概括 Dockerfile 与 Docker image 与 Docker Contianer 的关系:
- Dockerfile : 需要 写明"要用哪些材料(基础镜像、依赖)""按什么步骤执行(RUN、COPY、CMD)",可以根据该文件生成二进制的 Image 文件。
- Docker Image: 一个打包好的、可随时运行的软件环境快照。(Image 是不可变(immutable)的,因此能保证同一个 Image 可以创建无数个一致的容器,给用户的就是这个)
- Docker Container: 基于 Image 启动后的"实例",里面的程序真正运行在这里。
参考 制作自己的Docker容器 的十、实例:制作自己的 Docker 容器章节生成了Image,体验了一把,主要步骤如下:
shell
$ git clone https://github.com/ruanyf/koa-demos.git
$ cd koa-demos
新建 .dockerignore 文件存储不打包到 image 的文件
新建 Dockerfile 文件,怎么写后面看我的研究
docker image build -t my-demo:0.0.1 . #根据当前目录下的 Dockerfile 创建Image文件,如果成功 docker image ls 可以看到
# -t 参数用来指定 image 文件的名字,后面还可以用冒号指定标签
# 最后一个 "." 很重要:指定了 Dockerfile .dockerignore 在哪找
docker container run -p 8000:3000 -it koa-demo:0.0.1 /bin/bash #从 image 文件生成容器
#-p参数:容器的 3000 端口映射到本机的 8000 端口。用户也想从宿主机访问时需要
# i参数:(interactive):保持 STDIN 打开,即使容器没有连接到终端。这让你可以和容器进行交互(比如输入命令)。
# t参数:(tty):为容器分配一个伪终端(pseudo-TTY),这样你看到的提示符、光标、颜色等就像在普通终端里一样。
# 所以 -it 参数作用是:以交互终端的方式运行容器,常用于需要手动操作容器内部的场景(比如调试、运行 shell)。
#koa-demo:0.0.1:image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。
#/bin/bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。
终止容器:按下 Ctrl + d (或者输入 exit)退出容器。此外,也可以用 docker container kill 终止容器运行。
docker container rm [containerID] #删除容器文件
# 或用 docker container run --rm -p 8000:3000 -it koa-demo /bin/bash 使得容器终止运行后自动删除。
!NOTE
docker run 的基本格式是:
docker run [DOCKER-OPTIONS] IMAGE [COMMAND] [ARGS...]
-it属于 DOCKER-OPTIONS(Docker 自身的运行选项)/bin/bash是容器启动后要执行的 COMMAND所以
-it只能放到 IMAGE 前,/bin/bash必须要有 -it 才能用,必须要在 IMAGE 之后。
10.1 最常用指令
FROM:选择基础镜像(必需)
Tips:
尽量用官方镜像
用 slim 减少体积
不建议使用 latest(不可控)
WORKDIR:设置容器内后续命令(如 RUN, COPY, CMD 等)的工作目录,类似于你在 Linux 里执行 cd /app,如果该目录不存在,Docker 会自动创建它。
COPY: 复制文件(最常用)
RUN:在Image文件的构建阶段执行命令,结果都会被打包进入Image文件
Tips:
每一条 Dockerfile 指令(FROM、RUN、COPY、EXPOSE ......)都会生成一个镜像层(layer)。
多个 shell 命令建议合并减少 layer 数量
CMD:容器启动后执行的命令(运行程序)
【注】:CMD 有两种写法:shell形式和Exec形式,Exec形式命令遵循JSON格式,绕过shell,启动更快,为了解决主进程不是PID1无法优雅退出的问题。
【注】:一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令
【注】:CMD 不能被多次写入,只有最后一个生效。指定了 CMD 命令以后,docker container run 后面就不能附加命令了,否则它会覆盖CMD命令
ENTRYPOINT:固定容器启动方式
ENV:设置环境变量
EXPOSE:文档性声明端口, 不影响功能,仅用于给人类或工具(如 docker-compose)一个"文档提示",告诉别人容器 可能 会监听这个端口。
VOLUME:指定挂载点
USER:切换用户
关于ENTRYPOINT 与CMD怎么用,详细可参考博客:Dockerfile 中 CMD 和 ENTRYPOINT 指令
!IMPORTANT
补充一个对于 主进程是否 PID1 的说明:
如果一个容器正在运转,通过执行
docker container exec -it <container_ID> ps aux,显示的是容器内部视角的进程列表 。其中exec含义为在已经运行的容器中执行新指令。如果一个容器正在运转,可以通过
docker container exec -it 725f075625e2 /bin/sh的方式"进入"container。(这不是"进入"原进程,而是在容器内启动一个新进程(新的 shell),但它共享容器的文件系统、网络、PID 命名空间等,所以看起来就像"进入了容器")
下面是是一个生产级的 Dockerfile 模板:
dockerfile
FROM python:3.11-slim AS base
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m appuser
USER appuser
EXPOSE 8000 # 文档性声明端口, 不影响功能,仅用于给人类或工具(如 docker-compose)一个"文档提示",告诉别人容器 可能 会监听这个端口。
CMD ["python", "app.py"]
10.1.1 理解基础镜像分类
Docker 世界里,基础镜像主要分 3 类:
-
语言运行时镜像
适用于 Python/Node/Go/Java/Rust 等语言:
python:3.11-slimnode:20-bullseyegolang:1.22rust:1.75
这类镜像通常内部是:
一个精简 Linux(Debian/Alpine/Ubuntu)+ 对应语言运行环境
👉 做应用开发(LangGraph、Ollama API 服务)大多数情况选 语言镜像 更快更稳。
-
操作系统镜像(你想要特定 OS 时用)
比如说:
- Ubuntu 22.04
对应镜像就是:
ubuntu:22.04这是纯操作系统 rootfs,没有 Python/Node,你需要自己安装语言环境。
-
极小基础镜像(高级场景)
如:
alpine(5MB)scratch(0MB,全空)
适合做超小镜像,但兼容性不如 Debian/Ubuntu。
而很多第三方会提供自己的Docker镜像,如Ollama官方就提供了基础的Docker镜像。
10.1.2 例子:docker内实现 ping 命令
当前目录有如下文件:
Dockerfile ping_test.sh
Dockerfile内容:
FROM alpine
WORKDIR /app
COPY ping_test.sh .
CMD ["ping", "10.193.20.180"]
然后执行(环境支持sudo, 支持docker, 还得尝试多次)如下命令生成image:
docker image build -t ping-test:0.1.1 ./
然后运行container容器:
docker container run ping-test:0.1.1
然后直接就会有ping的log反馈到宿主机的stdout。
!IMPORTANT
为啥 "
docker container run ping-test:0.1.1"后,原本 container 中的 stdout 会出现在宿主机的stdout?答:命令执行后,将容器 PID 1 进程的 stdout 和 stderr 通过管道(pipe)连接到宿主机当前 shell 的 stdout/stderr ,所以你在宿主机终端看到的内容,其实是容器内进程的输出"流"被实时转发过来的。如果你不想实时显示日志,可以通过增加
-d(表示 detached(后台运行))参数:docker container run -d ping-test:0.1.1然后用docker container logs [containerID]来看该容器的所有历史输出。
10.2 发布 image 文件
容器运行成功后就确认了 image 文件是有效的。这时可以把image文件分享到网上,让其他人使用。
首先,去 hub.docker.com 或 cloud.docker.com 注册一个账户。然后,用下面的命令登录。
bash$ docker login
接着,为本地的 image 标注用户名和版本。
bash$ docker image tag [imageName] [username]/[repository]:[tag] # 实例 $ docker image tag koa-demos:0.0.1 ruanyf/koa-demos:0.0.1
也可以不标注用户名,重新构建一下 image 文件。
bash$ docker image build -t [username]/[repository]:[tag] .
最后,发布 image 文件。
bash$ docker image push [username]/[repository]:[tag]
发布成功以后,登录 hub.docker.com,就可以看到已经发布的 image 文件。
10.3 Docker 层缓存机制
-
目的 :为了在修改原工程内容后,执行
docker image build后加速image的构建过程。(类似make时,只有依赖更新了命令才被执行,加快编译过程) -
原理 :每个 Docker image 镜像由多个只读层(layers) 叠加而成,Docker在构建时,会依据Dockerfile中的每个指令(如
COPY、RUN)的结果缓存到一个层,下次构建时,如果遇到相同指令且指令对应的输入未变(输入未变:计算 内容哈希 未变),直接复用该层,跳过实际执行,避免重复劳动,加速构建过程。( 一旦某一层缓存失效,其后所有层缓存全部失效!) -
验证缓存是否生效:
下面是个例子,我构建了个docker,dockerfile如下:
# cat Dockerfile FROM alpine WORKDIR /app COPY ping_test.sh . CMD ["ping", "10.193.20.180"]观察我在修改ping_test.sh文件前后执行
docker build -t ping-test .的差别:shell# ping_test.sh文件没变化,执行 docker build -t ping-test: => [1/3] FROM docker.io/library/alpine:latest@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 0.0s => [internal] load build context 0.0s => => transferring context: 68B 0.0s => CACHED [2/3] WORKDIR /app 0.0s => CACHED [3/3] COPY ping_test.sh . # 修改ping_test.sh文件后,执行 docker build -t ping-test: => [1/3] FROM docker.io/library/alpine:latest@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 0.0s => [internal] load build context 0.0s => => transferring context: 87B 0.0s => CACHED [2/3] WORKDIR /app 0.0s => [3/3] COPY ping_test.sh . # 看到了吗,这行没有 CACHED 了 -
人工干预层缓存
shell# 强制不使用缓存 docker build --no-cache -t myapp . # 仅从某一步开始重建 docker build --from=3 -t myapp . # 从第3步开始(实验性) # 使用 .dockerignore 避免无关文件干扰缓存 如果你有 COPY . . 指令,那 .dockerignore 必不可少 -
常见反模式(破坏缓存的行为)及建议
建议 :在写Dockerfile时把变化频率低的操作写前头,多阶段构建。
错误做法 后果 COPY . .在RUN pip install之前每次改代码都要重装依赖 把敏感信息(如 .env)打进镜像不仅安全风险,还导致缓存频繁失效 在 RUN中混合 下载 + 安装 + 清理无法复用下载缓存
十二、其他有用的命令
-
启动已经生成、已经停止运行的容器文件
docker container start [containerID] -
容器自行进行收尾清理工作后停止
docker container stop [containerID]该命令相当于对进程发出 SIGTERM 信号,而
docker container kill相当于发出SIGKILL信号。 -
查看 docker 容器的输出:
docker container logs [containerID] -
在正在运行的 docker 容器中执行一条命令:
docker container exec -it [containerID] <cmd>docker container exec -it [containerID] /bin/bash可以另启动一个 shell 进入容器。 -
从正在运行的 Docker 容器里面,将文件拷贝到本机
docker container cp [containID]:[/path/to/file] . # 拷贝到当前目录成功后会有
Successfully copied打印。 -
搜索 docker 仓库是否有官方维护的镜像
# docker search langgraph NAME DESCRIPTION STARS OFFICIAL justinkook/langgraph Self-hosted instance of LangGraph 1 viajandee/langgraph 0上面这个例子中,列出的都不是官方维护的,官方为何的在OFFICIAL一列不会是空的。
十三、docker 与 docker-compose 关系
Docker CE(Community Edition)
- 核心作用:提供 Docker 引擎(Docker Engine),用于运行容器,属于基操,docker 是 docker-compose 的基础。
- 安装 Docker CE 后,你可以:
- 拉取镜像(
docker pull) - 创建和管理容器(
docker run、docker ps) - 基本容器生命周期操作。
- 拉取镜像(
Docker Compose
- 核心作用 :用于编排多个容器 ,通过一个
docker-compose.yml文件定义服务、网络、卷等。必须先安装 Docker Engine,Compose 才能正常工作。 - 适用于:
- 多容器应用(例如 Web + DB + Cache)
- 一键启动/停止整个应用栈(
docker compose up/down)
为满足兼容性,他们两者是有一定版本对应关系的。安装Docker后,确定配套的Docker compose的步骤如下:
- 确定Docker版本:
docker -v - 访问 Docker compose 的 github 仓库 [https://github.com/docker/compose/releases\],查找 release notes。查找与docker板子对应相关的信息,也可能看不到。
然后去官网下载对应的 Docker compose 版本。
十四、Docker Volume(卷)
13.1 Volume 的作用:
让容器内的数据"活"在容器之外,即使容器删了,数据还在!
比如,你新建了个数据库容器,里面存储了100条用户数据,但是你docker rm db && docker run ... 重建容器 → 数据全丢! 。所以volume的作用就是它把数据从容器的生命周期中解耦出来。
13.2 Volume 的三种类型
| 类型 | 命令示例 | 特点 |
|---|---|---|
| 1. 命名卷(Named Volume) | -v mydata:/app/data |
Docker 管理,路径自动分配,跨平台兼容好 |
| 2. 绑定挂载(Bind Mount) | -v /host/path:/container/path |
直接挂载主机目录,灵活但依赖主机路径 |
| 3. tmpfs 卷 | --tmpfs /app/cache |
数据只存内存,容器停就丢(用于临时缓存) |
a. 命名卷
命名卷是 Docker 自己管理的一个数据存储区域,你给它起个名字(如
ollama),Docker 负责在后台创建和维护它。
-
命名卷创建方式:命名卷分为显式创建和隐式创建(说白了显式创建就是先创建再用,隐式创建就是第一次-v指定后如果不存在则默认创建):
# 方式1:显式创建(可选) docker volume create ollama # 方式2:隐式创建(更常见)→ 就是你用的方式! docker run -v ollama:/root/.ollama ...
存储位置(Linux 默认)
/var/lib/docker/volumes/ollama/_data/
-
优点:
与容器生命周期解耦:删容器不影响卷。
跨容器共享:多个容器可挂同一个卷。
备份/迁移方便 :可用
docker run --rm -v ollama:/data -v $(pwd):/backup alpine tar czf /backup/ollama.tar.gz -C /data .备份。安全、权限隔离:Docker 自动处理权限(比 bind mount 更安全)。
跨平台一致:在 Windows/macOS/Linux 行为一致(bind mount 则不然)
问题:你指出,卷的默认存储位置为/var/lib/docker/volumes/ollama/_data/,为啥?是那条命令决定的?可以调整为自定义的目录吗?
-
常用 Volume 管理命令
命令 作用 docker volume ls列出所有卷 docker volume inspect <volume_name>查看卷详情(包括真实路径) docker volume create <volume_name>手动创建卷 docker volume rm <volume_name>删除卷(⚠️ 数据永久丢失!) docker volume prune删除所有未被容器使用的卷(清理垃圾) 注意:只有当没有任何容器引用某个卷时,它才是"未使用"状态
b. 绑定挂载
还没接触,暂时没学。
13.3 卷机制
1. Docker 的"卷机制"是什么?
2. local 驱动:卷机制的"默认实现"
十五、Docker Container 之间通信
方法1:Docker Network (也叫bridge网络)
总结:让多个容器加入同一个 Docker Network,通过容器名互相解析并进行 HTTP 通信。
前提 :本地已安装 Docker,并已提前构建好 fastapi_server:0.1 和 fastapi_client:latest 两个镜像。
先创建 Docker Network:(注:docker network list能看到创建的网络)
shell
docker network create fastapi_net # Docker 默认会自动给容器分配可通过 DNS 解析的名字。
然后再启动想要实现互相通信的 docker image 的命令时,增加使用刚刚定义的网络,比如实现fastapi_server:0.1 和 fastapi_client:latest 两的通信:
shell
# 启动 server:
docker run -p 8080:8080 --rm -d --name fastapi-server --network fastapi_net fastapi_server:0.1
# 启动 client:
docker run -p 8080:8080 -it --rm --name fastapi-client \
--network fastapi_net \
-e http_proxy= -e https_proxy= -e HTTP_PROXY= -e HTTPS_PROXY= \
-e NO_PROXY="fastapi-server,localhost,127.0.0.1" -e no_proxy="fastapi-server,localhost,127.0.0.1" \
fastapi_client:latest /bin/sh
# 上述参数解释:
# -p 8080:8080 容器间通信不需要, 但如果用户也想从宿主机访问,就需要.
# --rm :容器停止后自动删除
# -e http_proxy= -e... :由于某些环境(如公司网络)默认设置了代理,可能会导致容器内部访问同网络容器失败,因此这里显式清空代理。
!NOTE
通过
docker network inspect <docker网络名>命令可以查看该网络下的所有容器及其 IP、别名等。无需重启容器,也可把正在运行的容器加入某 docker 网络:
docker network connect fastapi_net <正在运行的容器名或ID>,但是 ,如果容器是用--network host启动的,不能再connect到自己创建的 docker 网络,需要重启容器并用非 host 网络运行。
然后 server 必须在容器内部监听 0.0.0.0,比如 FastAPI server 一侧uvicorn服务器启动要写类似:
uvicorn.run("app.main:app", host="0.0.0.0", port=8080)
注意:不能写 127.0.0.1 ,因为127.0.0.1 是容器自己的 loopback,另一个容器访问不到。如果 uvicorn 监听 127.0.0.1,那么 Docker 的端口映射和容器间通信都无法访问它。
这样两个 docker 就可以通过 HTTP协议 方式通信了:
shell
# client 里访问 server 的 health 方法:
curl http://fastapi-server:8080/api/run_script
a. 关于代理的坑
server 配不配置代理都不影响 client → server 的访问,client 一旦配置代理,就根本无法通过 Docker 网络访问 server,为啥啊?机制是什么?
如果client不配置代理:curl 发送的是普通 HTTP 请求格式,网络行为:
curl → Docker_DNS 解析 fastapi-server → Container IP(172.*.*.*)→ FastAPI Server
server 完全不知道 client 是否配置代理,因为流量根本没经过代理。
如果client配置了代理:
curl → 代理 → 找不到 fastapi-server → 返回 503
为什么 server 配置代理对这个没有任何影响?因为 server 的代理配置根本不会参与这个上面这个过程。server 的代理环境变量只会影响 server 容器内部的进程(例如当 server 想要访问外部API, server 执行 apt、curl 等命令时。)
总结 :代理只影响出站流量(outbound) ,不影响入站流量(inbound)。
!IMPORTANT
什么是网络代理?
网络代理是个中间人(信使)。它可以替你完成访问你想访问的目标网站的任务然后把内容返回给你。功能如下:
- 隐藏身份,保护隐私:目标网站只知道"信使"的IP,而不知道你真实的IP。
- 突破限制,访问无界:如果这位"信使"身处美国,你就可以通过它,访问那些只对美国用户开放的网站。
- 提升安全,充当防火墙:所有网络流量都经过代理服务器,它可以帮助过滤掉一些恶意软件和不安全的内容。
十六、Docker Container 与宿主机通信
举例,因为我在宿主机部署了ollama框架,如果不用docker,一般在运行 OLLAMA_HOST=0.0.0.0:11434 ollama serve启动Ollama服务后,在客户端LangGraph框架仅需要使用"http://127.0.0.1:11434"即可访问Ollama server。但是现在的情景是客户端LangGraph框架被放到了Docker中,ollama serve仍位于宿主机。如何让他们继续通信?下面仅为我使用的几种方法。
方法1:把该主机名映射到宿主机网关:
- 将docker中的Base_url调整为使用 "http://host.docker.internal:11434"
- 在运行container时(在
docker run或docker-compose里加一行)增加--add-host=host.docker.internal:host-gateway参数,该参数会在容器内增加一条静态解析记录,把域名host.docker.internal映射到一个特殊的 IP------宿主机在 Docker 网桥网络中的网关地址(host-gateway是个占位符,Docker 会在运行时替换为正确的 IP)。
方法2:使用 --network=host(仅限 Linux,且影响范围大)
让容器与宿主机共享网络命名空间:docker run --network=host your-image,这样容器中的 localhost:11434 就是宿主机的 11434,但不适合生产(会失去网络隔离),且在 macOS/Windows 上不可用。