Skip to content

Commit b2b62c3

Browse files
committed
📝 docs: refine description of docker-out-of-docker implementation
1 parent 4fe74b8 commit b2b62c3

File tree

1 file changed

+29
-26
lines changed

1 file changed

+29
-26
lines changed

docs/0.technology.md

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -386,32 +386,6 @@ graph TD
386386

387387
**实现原理**: 我们通过 `-javaagent` 的方式,为每个微服务无侵入式地挂载了 SkyWalking 的探针。该探针会自动拦截所有进出的 HTTP 请求、JDBC 调用、MQ 消息等,并生成和传递一个全局唯一的 `TraceID`,最终将整条调用链的数据上报给 SkyWalking OAP 服务器进行分析和存储。
388388

389-
### 亮点八:自动化CI/CD流水线 (GitHub Actions)
390-
391-
### 亮点九:生产级的容器化部署实践
392-
393-
将一个复杂的微服务应用正确地部署到容器中,会遇到一系列经典的网络与文件系统挑战。本项目通过优雅的工程实践,解决了两大核心难题,确保了部署的健壮性和可移植性。
394-
395-
- **优雅的 Nginx 服务发现与路径重写**:
396-
- **问题**: 在 Docker 环境中,当后端服务(如网关)重启并获得新 IP 时,Nginx 因 DNS 缓存会连接到旧的、已失效的 IP,导致 `Connection Refused`。同时,前端的 `/api` 路径前缀也需要被正确剥离。
397-
- **解决方案**: 我们采用了 `upstream` + `resolver` + `rewrite` 的黄金组合。通过 `upstream` 块定义服务池,并利用 `resolver` 指令强制 Nginx 动态解析其中的域名,彻底解决了 DNS 缓存问题。同时,使用 `rewrite` 指令清晰地处理路径重写,将职责完美分离,保证了配置的健壮与可读性。
398-
399-
- **巧妙的 Docker-out-of-Docker 路径转换**:
400-
- **问题**: `sandbox-service`(判题服务)自身在容器中运行,但它需要调用宿主机的 Docker 来创建新的判题容器。此时,如果在 `docker run` 命令中直接使用容器内部的文件路径作为挂载卷,会因宿主机 Docker 守护进程不认识该路径而导致 `Mounts denied` 错误。
401-
- **解决方案**: 我们实现了一套巧妙的路径转换机制。首先,在 `docker-compose.yml` 中,通过 `${PWD}` 变量将宿主机的当前目录绝对路径注入为环境变量 `HOST_CODE_PATH`。然后,在 `SandboxService.java` 代码中,动态地将容器内路径(如 `/app/static/codes/...`)替换为对应的宿主机路径(如 `/path/on/host/static/codes/...`),最后才将这个转换后的、宿主机守护进程能够理解的路径,用于 `docker run``-v` 参数。这一实践完美解决了跨容器边界的文件系统挂载难题。
402-
403-
为了实现快速、可靠、一致的软件交付,我们基于 GitHub Actions 构建了一套完整的自动化 CI/CD (持续集成/持续部署) 流水线。开发者只需将代码推送到 `main` 分支,后续的测试、构建、打包、推送镜像、服务器部署等所有繁琐的流程都将自动完成。
404-
405-
- **并行构建 (`Matrix Strategy`)**: 流水线利用 GitHub Actions 的 `strategy.matrix` 特性,为每一个微服务(`user-service`, `problem-service` 等)创建一个独立的构建任务。这些任务在云端并行执行,极大地缩短了所有服务的总构建时间。
406-
407-
- **优化的多阶段构建 (`Dockerfile`)**: 我们为每个微服务都编写了优化的多阶段 `Dockerfile`。在“构建阶段”,使用包含完整 Maven 环境的镜像来编译代码和下载依赖;在“运行阶段”,仅将编译好的 `.jar` 文件复制到一个轻量的 JRE 镜像中。这使得最终生成的生产镜像体积最小化,并能充分利用 Docker 的构建缓存机制,提升后续构建的速度。
408-
409-
- **职责分离的 Jobs (`build-and-push` & `deploy`)**:
410-
1. **`build-and-push` Job**: 负责编译所有代码,构建所有微服务的 Docker 镜像,并将其推送到 Docker Hub 镜像仓库进行统一的版本管理。
411-
2. **`deploy` Job**: 该任务依赖于 `build-and-push` 的成功。它会通过 SSH 安全地连接到生产服务器,执行预设的部署脚本,自动拉取最新的镜像并以 `docker-compose` 的方式重启所有服务,完成部署的“最后一公里”。
412-
413-
通过这套流水线,我们实现了从“代码提交”到“服务上线”的全流程自动化,确保了每次部署都是可重复、可预测和高度可靠的。
414-
415389
#### 实际应用场景与查询指南
416390

417391
**场景**: 用户反馈“提交代码后,页面卡了很久才显示结果”。
@@ -434,6 +408,35 @@ graph TD
434408
435409
3. **发现根因**: 从上图可以一目了然地发现,整个请求的耗时主要集中在 `submission-service` 对 `sandbox-service` 的 Feign 调用上。这立刻将我们的排查范围缩小到了 `sandbox-service` 内部,而不是去怀疑数据库或 Redis。这正是链路追踪的巨大价值所在。
436410
411+
412+
### 亮点八:自动化CI/CD流水线 (GitHub Actions)
413+
414+
为了实现快速、可靠、一致的软件交付,我们基于 GitHub Actions 构建了一套完整的自动化 CI/CD (持续集成/持续部署) 流水线。开发者只需将代码推送到 `main` 分支,后续的测试、构建、打包、推送镜像、服务器部署等所有繁琐的流程都将自动完成。
415+
416+
- **并行构建 (`Matrix Strategy`)**: 流水线利用 GitHub Actions 的 `strategy.matrix` 特性,为每一个微服务(`user-service`, `problem-service` 等)创建一个独立的构建任务。这些任务在云端并行执行,极大地缩短了所有服务的总构建时间。
417+
418+
- **优化的多阶段构建 (`Dockerfile`)**: 我们为每个微服务都编写了优化的多阶段 `Dockerfile`。在“构建阶段”,使用包含完整 Maven 环境的镜像来编译代码和下载依赖;在“运行阶段”,仅将编译好的 `.jar` 文件复制到一个轻量的 JRE 镜像中。这使得最终生成的生产镜像体积最小化,并能充分利用 Docker 的构建缓存机制,提升后续构建的速度。
419+
420+
- **职责分离的 Jobs (`build-and-push` & `deploy`)**:
421+
1. **`build-and-push` Job**: 负责编译所有代码,构建所有微服务的 Docker 镜像,并将其推送到 Docker Hub 镜像仓库进行统一的版本管理。
422+
2. **`deploy` Job**: 该任务依赖于 `build-and-push` 的成功。它会通过 SSH 安全地连接到生产服务器,执行预设的部署脚本,自动拉取最新的镜像并以 `docker-compose` 的方式重启所有服务,完成部署的“最后一公里”。
423+
424+
通过这套流水线,我们实现了从“代码提交”到“服务上线”的全流程自动化,确保了每次部署都是可重复、可预测和高度可靠的。
425+
426+
### 亮点九:生产级的容器化部署实践
427+
428+
将一个复杂的微服务应用正确地部署到容器中,会遇到一系列经典的网络与文件系统挑战。本项目通过优雅的工程实践,解决了两大核心难题,确保了部署的健壮性和可移植性。
429+
430+
- **优雅的 Nginx 服务发现与路径重写**:
431+
- **问题**: 在 Docker 环境中,当后端服务(如网关)重启并获得新 IP 时,Nginx 因 DNS 缓存会连接到旧的、已失效的 IP,导致 `Connection Refused`。同时,前端的 `/api` 路径前缀也需要被正确剥离。
432+
- **解决方案**: 我们采用了 `upstream` + `resolver` + `rewrite` 的黄金组合。通过 `upstream` 块定义服务池,并利用 `resolver` 指令强制 Nginx 动态解析其中的域名,彻底解决了 DNS 缓存问题。同时,使用 `rewrite` 指令清晰地处理路径重写,将职责完美分离,保证了配置的健壮与可读性。
433+
434+
- **轻量级的 Docker-out-of-Docker 判题沙箱**:
435+
- **目标**: `sandbox-service`(判题服务)自身运行在容器中,但它需要具备动态创建和管理其他“一次性”判题容器的能力。
436+
- **核心实现**: 我们采用了业界标准的 “Docker-out-of-Docker” 模式,而非在容器内再嵌套一个完整 Docker 环境的 “Docker-in-Docker” 重模式。其实现包含两个关键点:
437+
1. **共享通信套接字 (`docker.sock`)**: 我们通过 `volumes` 将宿主机的 Unix Socket 文件 `/var/run/docker.sock` 挂载到 `sandbox-service` 容器内部。这个 `sock` 文件是本地 Docker 客户端与 Docker 守护进程之间进行通信的 IPC 通道。通过共享它,我们巧妙地让容器内的进程能够直接与宿主机上的 Docker 守护进程对话。
438+
2. **安装 Docker 客户端**: 我们在 `sandbox-service` 的 `Dockerfile` 中,通过 `apt-get` 安装了 `docker.io` 包。这为容器提供了 `docker` 命令行客户端。当服务在代码中执行 `docker run` 命令时,这个客户端就会通过挂载进来的 `docker.sock` 文件,将指令发送给宿主机的守护进程去执行,从而实现了“在容器内编排和管理宿主机上的其他容器”这一强大功能。
439+
437440
---
438441
439442
## 附录:关键技术与设计决策

0 commit comments

Comments
 (0)