mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-11 04:14:38 +00:00
Fix space with bold markdown
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -17,3 +17,7 @@ docker-compose.override.yml
|
||||
.vscode/
|
||||
|
||||
.agent/
|
||||
__pycache__/
|
||||
|
||||
# Check scripts
|
||||
check_project_rules.py
|
||||
|
||||
@@ -4,7 +4,7 @@ Docker 是彻底改变了软件开发和交付方式的革命性技术。本节
|
||||
|
||||
### 一句话理解 Docker
|
||||
|
||||
> **Docker 是一种轻量级的虚拟化技术,它让应用程序及其依赖环境可以被打包成一个标准化的单元,在任何地方都能一致地运行。**如果用一个生活中的类比:**Docker 之于软件,就像集装箱之于货物**。
|
||||
> **Docker 是一种轻量级的虚拟化技术,它让应用程序及其依赖环境可以被打包成一个标准化的单元,在任何地方都能一致地运行。** 如果用一个生活中的类比:**Docker 之于软件,就像集装箱之于货物**。
|
||||
|
||||
在集装箱发明之前,货物的运输是一件麻烦的事情——不同的货物需要不同的包装、不同的装卸方式,换一种运输工具就要重新装卸。集装箱的出现改变了这一切:无论里面装的是什么,集装箱的外形是标准的,可以用同样的方式装卸、堆放和运输。
|
||||
|
||||
@@ -44,7 +44,7 @@ flowchart LR
|
||||
|
||||
### Docker vs 虚拟机
|
||||
|
||||
很多人第一次接触 Docker 时会问:**“这不就是虚拟机吗?”**答案是:**不是,而且差别很大。**
|
||||
很多人第一次接触 Docker 时会问:**“这不就是虚拟机吗?”** 答案是:**不是,而且差别很大。**
|
||||
|
||||
#### 传统虚拟机
|
||||
|
||||
@@ -123,4 +123,4 @@ Docker 的发展历程:
|
||||
- **2015 年**:成立[开放容器联盟 (OCI)](https://opencontainers.org/),推动容器标准化
|
||||
- **至今**:[GitHub 项目](https://github.com/moby/moby)超过 7 万星标
|
||||
|
||||
Docker 的成功推动了整个容器生态的发展,催生了 Kubernetes、Podman 等众多相关项目。笔者认为,Docker 最大的贡献不仅是技术本身,更是它**让容器技术从系统管理员的工具变成了每个开发者都能使用的标准工具**。
|
||||
Docker 的成功推动了整个容器生态的发展,催生了 Kubernetes、Podman 等众多相关项目。笔者认为,Docker 最大的贡献不仅是技术本身,更是它 **让容器技术从系统管理员的工具变成了每个开发者都能使用的标准工具**。
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
具体内容如下:
|
||||
|
||||
```
|
||||
```bash
|
||||
周五下午 5:00
|
||||
├── 开发者:代码写完了,本地测试通过,提交!🎉
|
||||
├── 周一早上 9:00
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
具体内容如下:
|
||||
|
||||
```
|
||||
```bash
|
||||
新同事入职
|
||||
├── Day 1:领电脑,配环境
|
||||
├── Day 2:继续配环境,遇到问题
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
具体内容如下:
|
||||
|
||||
```
|
||||
```bash
|
||||
运维:"我们需要把服务迁移到新服务器"
|
||||
开发:"旧服务器上的配置文档在哪?"
|
||||
运维:"当时是一个已经离职的同事配的……"
|
||||
@@ -81,7 +81,7 @@ flowchart TD
|
||||
|
||||
#### 1。环境一致性
|
||||
|
||||
Docker 镜像包含了应用运行所需的**一切**:代码、运行时、系统工具、库、配置。这意味着:
|
||||
Docker 镜像包含了应用运行所需的 **一切**:代码、运行时、系统工具、库、配置。这意味着:
|
||||
|
||||
- ✅ 开发环境和生产环境完全一致
|
||||
- ✅ 不会再有 “在我机器上能跑” 的问题
|
||||
@@ -99,7 +99,7 @@ $ docker compose up
|
||||
|
||||
#### 2。秒级启动
|
||||
|
||||
传统虚拟机启动需要几分钟 (引导操作系统),而 Docker 容器启动通常只需要**几秒甚至几百毫秒**。
|
||||
传统虚拟机启动需要几分钟 (引导操作系统),而 Docker 容器启动通常只需要 **几秒甚至几百毫秒**。
|
||||
|
||||
笔者实测数据:
|
||||
|
||||
@@ -159,7 +159,7 @@ flowchart LR
|
||||
|
||||
使用 [Dockerfile](../04_image/4.5_build.md) 定义镜像构建过程,使得:
|
||||
|
||||
- 构建过程**可重复、可追溯**
|
||||
- 构建过程 **可重复、可追溯**
|
||||
- 任何人都能从代码重建完全相同的镜像
|
||||
- 配合 [GitHub Actions](../15_cases/ci/actions/README.md) 等 CI 系统实现自动化
|
||||
|
||||
@@ -172,7 +172,7 @@ Docker 可以在几乎任何平台上运行:
|
||||
- ✅ 私有云和自建数据中心
|
||||
- ✅ 边缘设备和 IoT
|
||||
|
||||
**同一个镜像,在任何地方运行结果都一致。**这让应用迁移变得前所未有的简单。
|
||||
**同一个镜像,在任何地方运行结果都一致。** 这让应用迁移变得前所未有的简单。
|
||||
|
||||
#### 6。微服务架构的基石
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## 本章小结
|
||||
|
||||
- Docker 是一种轻量级虚拟化技术,核心价值是**环境一致性**
|
||||
- Docker 是一种轻量级虚拟化技术,核心价值是 **环境一致性**
|
||||
- 与虚拟机相比,Docker 更轻量、更快速、资源利用率更高
|
||||
- Docker 基于 Linux 内核的 Namespace、Cgroups 和 Union FS 技术
|
||||
- Docker 推动了容器技术的标准化 (OCI) 和生态发展
|
||||
|
||||
Docker 的核心价值可以用一句话概括:**让应用的开发、测试、部署保持一致,同时极大提高资源利用效率。**笔者认为,对于现代软件开发者来说,Docker 已经不是 “要不要学” 的问题,而是**必备技能**。无论你是前端、后端、运维还是全栈开发者,掌握 Docker 都能让你的工作更高效。
|
||||
Docker 的核心价值可以用一句话概括:**让应用的开发、测试、部署保持一致,同时极大提高资源利用效率。** 笔者认为,对于现代软件开发者来说,Docker 已经不是 “要不要学” 的问题,而是 **必备技能**。无论你是前端、后端、运维还是全栈开发者,掌握 Docker 都能让你的工作更高效。
|
||||
|
||||
@@ -4,11 +4,11 @@ Docker 镜像作为容器运行的基石,其设计理念和实现机制至关
|
||||
|
||||
### 一句话理解镜像
|
||||
|
||||
> **Docker 镜像是一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量和配置文件。**如果用一个类比:**镜像就像是一张光盘或 ISO 文件**。你可以用同一张光盘在不同电脑上安装系统,而光盘本身不会被修改。同样,一个镜像可以创建多个容器,而镜像本身保持不变。
|
||||
> **Docker 镜像是一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量和配置文件。** 如果用一个类比:**镜像就像是一张光盘或 ISO 文件**。你可以用同一张光盘在不同电脑上安装系统,而光盘本身不会被修改。同样,一个镜像可以创建多个容器,而镜像本身保持不变。
|
||||
|
||||
### 镜像与操作系统的关系
|
||||
|
||||
我们都知道,操作系统分为**内核**和**用户空间**:
|
||||
我们都知道,操作系统分为 **内核** 和 **用户空间**:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
@@ -25,9 +25,9 @@ flowchart TD
|
||||
UserSpace --- KernelSpace
|
||||
```
|
||||
|
||||
对于 Linux 而言,内核启动后会挂载 `root` 文件系统来提供用户空间支持。**Docker 镜像**本质上就是一个 `root` 文件系统。
|
||||
对于 Linux 而言,内核启动后会挂载 `root` 文件系统来提供用户空间支持。**Docker 镜像** 本质上就是一个 `root` 文件系统。
|
||||
|
||||
例如,官方镜像 `ubuntu:24.04` 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统——但**不包含 Linux 内核** (因为容器共享宿主机的内核)。
|
||||
例如,官方镜像 `ubuntu:24.04` 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统——但 **不包含 Linux 内核** (因为容器共享宿主机的内核)。
|
||||
|
||||
### 镜像包含什么?
|
||||
|
||||
@@ -40,9 +40,9 @@ Docker 镜像是一个特殊的文件系统,包含:
|
||||
| **配置文件** | nginx.conf、my.cnf 等 |
|
||||
| **环境变量** | PATH、LANG 等预设值 |
|
||||
| **元数据**| 启动命令、暴露端口、数据卷定义 |**关键特性**:
|
||||
- ✅ 镜像是**只读**的
|
||||
- ✅ 镜像**不包含**动态数据
|
||||
- ✅ 镜像构建后**内容不会改变**
|
||||
- ✅ 镜像是 **只读** 的
|
||||
- ✅ 镜像 **不包含** 动态数据
|
||||
- ✅ 镜像构建后 **内容不会改变**
|
||||
|
||||
### 分层存储:镜像的核心设计
|
||||
|
||||
@@ -110,7 +110,7 @@ flowchart TD
|
||||
|
||||
#### 分层存储的 “陷阱”
|
||||
|
||||
> ⚠️ **笔者特别提醒**:理解这一点可以帮你避免构建出臃肿的镜像。**关键原理**:每一层的文件变化会被记录,但**删除操作只是标记,不会真正减小镜像体积**。
|
||||
> ⚠️ **笔者特别提醒**:理解这一点可以帮你避免构建出臃肿的镜像。**关键原理**:每一层的文件变化会被记录,但 **删除操作只是标记,不会真正减小镜像体积**。
|
||||
|
||||
```docker
|
||||
## 错误示范 ❌
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### 一句话理解容器
|
||||
|
||||
> **容器是镜像的运行实例。如果把镜像比作程序,那么容器就是进程。**用面向对象编程的术语来说:**镜像是类 (Class),容器是对象 (Instance)**。
|
||||
> **容器是镜像的运行实例。如果把镜像比作程序,那么容器就是进程。** 用面向对象编程的术语来说:**镜像是类 (Class),容器是对象 (Instance)**。
|
||||
|
||||
- 一个镜像可以创建多个容器
|
||||
- 每个容器相互独立,互不影响
|
||||
@@ -78,7 +78,7 @@ flowchart TD
|
||||
|
||||
#### 镜像层 + 容器层
|
||||
|
||||
当容器运行时,Docker 会在镜像的只读层之上创建一个**可写层** (容器存储层):
|
||||
当容器运行时,Docker 会在镜像的只读层之上创建一个 **可写层** (容器存储层):
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
@@ -95,11 +95,11 @@ flowchart TD
|
||||
|
||||
当容器需要修改镜像层中的文件时:
|
||||
|
||||
1. Docker 将该文件**复制**到容器存储层
|
||||
1. Docker 将该文件 **复制** 到容器存储层
|
||||
2. 在容器层中进行修改
|
||||
3. 原始镜像层保持不变
|
||||
|
||||
```
|
||||
```bash
|
||||
读取文件:直接从镜像层读取(共享,高效)
|
||||
修改文件:复制到容器层,然后修改(只有这个容器能看到修改)
|
||||
```
|
||||
@@ -125,7 +125,7 @@ $ docker rm abc123
|
||||
|
||||
#### 正确的数据持久化方式
|
||||
|
||||
按照 Docker 最佳实践,容器存储层应该保持**无状态**。需要持久化的数据应该使用:
|
||||
按照 Docker 最佳实践,容器存储层应该保持 **无状态**。需要持久化的数据应该使用:
|
||||
|
||||
| 方式 | 说明 | 适用场景 |
|
||||
|------|------|---------|
|
||||
@@ -142,7 +142,7 @@ $ docker run -v mydata:/var/lib/mysql mysql
|
||||
$ docker run -v /host/path:/container/path nginx
|
||||
```
|
||||
|
||||
这些位置的读写**会跳过容器存储层**,直接写入宿主机,性能更好,也不会随容器删除而丢失。
|
||||
这些位置的读写 **会跳过容器存储层**,直接写入宿主机,性能更好,也不会随容器删除而丢失。
|
||||
|
||||
### 容器的生命周期
|
||||
|
||||
|
||||
@@ -48,14 +48,14 @@ flowchart TB
|
||||
| 概念 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **Registry** | 存储镜像的服务 | Docker Hub、ghcr.io |
|
||||
| **Repository (仓库) ** | 同一软件的镜像集合 | `nginx`、`mysql`、`mycompany/myapp` |
|
||||
| **Tag (标签) ** | 仓库内的版本标识 | `latest`、`1.25`、`alpine` |
|
||||
| **Repository (仓库)** | 同一软件的镜像集合 | `nginx`、`mysql`、`mycompany/myapp` |
|
||||
| **Tag (标签)** | 仓库内的版本标识 | `latest`、`1.25`、`alpine` |
|
||||
|
||||
#### 镜像的完整名称
|
||||
|
||||
一个完整的 Docker 镜像名称由 Registry 地址、用户名/组织名、仓库名和标签组成。了解其结构有助于我们更准确地定位镜像。基本格式如下:
|
||||
|
||||
```
|
||||
```bash
|
||||
[registry地址/][用户名/]仓库名[:标签]
|
||||
```
|
||||
|
||||
@@ -128,7 +128,7 @@ $ docker push username/myapp:v1.0
|
||||
|
||||
### 镜像加速器
|
||||
|
||||
由于网络原因,在国内直接访问 Docker Hub 可能会很慢。可以配置**镜像加速器** (Registry Mirror) 来加速下载。配置示例如下:
|
||||
由于网络原因,在国内直接访问 Docker Hub 可能会很慢。可以配置 **镜像加速器** (Registry Mirror) 来加速下载。配置示例如下:
|
||||
|
||||
```json
|
||||
// /etc/docker/daemon.json
|
||||
@@ -190,7 +190,7 @@ $ docker pull localhost:5000/myapp:v1.0
|
||||
|
||||
如图 2-3 所示,镜像从开发环境构建后推送到 Registry,再由生产环境拉取并运行。
|
||||
|
||||
```
|
||||
```bash
|
||||
开发者机器 Registry 生产服务器
|
||||
│ │ │
|
||||
│ docker build │ │
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **Registry** | 存储和分发镜像的服务 |
|
||||
| **仓库 (Repository) ** | 同一软件的镜像集合 |
|
||||
| **标签 (Tag) ** | 版本标识,默认为 latest |
|
||||
| **仓库 (Repository)** | 同一软件的镜像集合 |
|
||||
| **标签 (Tag)** | 版本标识,默认为 latest |
|
||||
| **Docker Hub** | 默认的公共 Registry |
|
||||
| **私有 Registry** | 企业内部使用,推荐 Harbor |
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## 3.10 开启实验特性
|
||||
|
||||
一些 docker 命令或功能仅当**实验特性**开启时才能使用,请按照以下方法进行设置。
|
||||
一些 docker 命令或功能仅当 **实验特性** 开启时才能使用,请按照以下方法进行设置。
|
||||
|
||||
### Docker CLI 的实验特性
|
||||
|
||||
|
||||
@@ -26,21 +26,21 @@ $ brew install --cask docker
|
||||
|
||||
如同 macOS 其它软件一样,安装也非常简单,双击下载的 `.dmg` 文件,然后将那只叫 [Moby](https://www.docker.com/blog/call-me-moby-dock/) 的鲸鱼图标拖拽到 `Application` 文件夹即可 (其间需要输入用户密码)。
|
||||
|
||||

|
||||

|
||||
|
||||
### 运行
|
||||
|
||||
从应用中找到 Docker 图标并点击运行。
|
||||
|
||||

|
||||

|
||||
|
||||
运行之后,会在右上角菜单栏看到多了一个鲸鱼图标,这个图标表明了 Docker 的运行状态。
|
||||
|
||||

|
||||

|
||||
|
||||
每次点击鲸鱼图标会弹出操作菜单。
|
||||
|
||||

|
||||

|
||||
|
||||
之后,你可以在终端通过命令检查安装后的 Docker 版本。
|
||||
|
||||
@@ -57,7 +57,7 @@ $ docker run -d -p 80:80 --name webserver nginx
|
||||
|
||||
服务运行后,可以访问 [http://localhost](http://localhost),如果看到了 “Welcome to nginx!”,就说明 Docker Desktop for Mac 安装成功了。
|
||||
|
||||

|
||||

|
||||
|
||||
要停止 Nginx 服务器并删除执行下面的命令:
|
||||
|
||||
|
||||
@@ -28,11 +28,11 @@ $ winget install Docker.DockerDesktop
|
||||
|
||||
在 Windows 搜索栏输入 **Docker** 点击 **Docker Desktop** 开始运行。
|
||||
|
||||

|
||||

|
||||
|
||||
Docker 启动之后会在 Windows 任务栏出现鲸鱼图标。
|
||||
|
||||

|
||||

|
||||
|
||||
等待片刻,当鲸鱼图标静止时,说明 Docker 启动成功,之后你可以打开 PowerShell 使用 Docker。
|
||||
|
||||
|
||||
@@ -84,17 +84,13 @@ Registry Mirrors:
|
||||
|
||||
### Kubernetes 官方镜像地址迁移
|
||||
|
||||
可以登录[阿里云容器镜像服务](https://www.aliyun.com/product/acr?source=5176.11533457&userCode=8lx5zmtu&type=copy)**镜像中心**->**镜像搜索**查找。
|
||||
可以登录[阿里云容器镜像服务](https://www.aliyun.com/product/acr?source=5176.11533457&userCode=8lx5zmtu&type=copy)**镜像中心**->**镜像搜索** 查找。
|
||||
|
||||
Kubernetes 社区已将官方镜像地址从 `k8s.gcr.io` 迁移到 `registry.k8s.io`。建议优先使用新地址。
|
||||
|
||||
一般情况下有如下对应关系:
|
||||
|
||||
```bash
|
||||
## 旧地址(已迁移)
|
||||
## $ docker pull k8s.gcr.io/xxx
|
||||
|
||||
## 新地址(推荐)
|
||||
$ docker pull registry.k8s.io/xxx
|
||||
```
|
||||
|
||||
@@ -112,6 +108,6 @@ $ docker pull registry.k8s.io/xxx
|
||||
|
||||
### 云服务商
|
||||
|
||||
某些云服务商提供了**仅供内部**访问的镜像服务,当您的 Docker 运行在云平台时可以选择它们。
|
||||
某些云服务商提供了 **仅供内部** 访问的镜像服务,当您的 Docker 运行在云平台时可以选择它们。
|
||||
|
||||
* [腾讯云 `https://mirror.ccs.tencentyun.com`](https://cloud.tencent.com/act/cps/redirect?redirect=10058&cps_key=3a5255852d5db99dcd5da4c72f05df61)
|
||||
|
||||
@@ -14,7 +14,7 @@ docker pull [选项] [Registry地址/]仓库名[:标签]
|
||||
|
||||
Docker 镜像名称由 Registry 地址、用户名、仓库名和标签组成。其标准格式如下:
|
||||
|
||||
```
|
||||
```bash
|
||||
docker.io / library / ubuntu : 24.04
|
||||
────┬──── ───┬─── ──┬─── ──┬──
|
||||
│ │ │ │
|
||||
@@ -91,7 +91,7 @@ docker.io/library/ubuntu:24.04
|
||||
|
||||
#### 分层下载
|
||||
|
||||
从输出可以看到,镜像是**分层下载**的:
|
||||
从输出可以看到,镜像是 **分层下载** 的:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
|
||||
@@ -58,7 +58,7 @@ Docker 镜像的大小可能与我们通常理解的文件大小有所不同,
|
||||
|
||||
由于镜像是分层存储,不同镜像可能共享相同的层:
|
||||
|
||||
```
|
||||
```bash
|
||||
ubuntu:24.04 nginx:latest redis:latest
|
||||
│ │ │
|
||||
└───────┬───────┘ │
|
||||
|
||||
@@ -20,7 +20,7 @@ $ docker run --name webserver -d -p 80:80 nginx
|
||||
|
||||
直接用浏览器访问的话,我们会看到默认的 Nginx 欢迎页面。
|
||||
|
||||

|
||||

|
||||
|
||||
现在,假设我们非常不喜欢这个欢迎页面,我们希望改成欢迎 Docker 的文字,我们可以使用 `docker exec` 命令进入容器,修改其内容。
|
||||
|
||||
@@ -37,7 +37,7 @@ exit
|
||||
|
||||
现在我们再刷新浏览器的话,会发现内容被改变了。
|
||||
|
||||

|
||||

|
||||
|
||||
我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 `docker diff` 命令看到具体的改动。
|
||||
|
||||
@@ -130,6 +130,6 @@ docker run --name web2 -d -p 81:80 nginx:v2
|
||||
|
||||
首先,如果仔细观察之前的 `docker diff webserver` 的结果,你会发现除了真正想要修改的 `/usr/share/nginx/html/index.html` 文件外,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,将会导致镜像极为臃肿。
|
||||
|
||||
此外,使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为**黑箱镜像**,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。
|
||||
此外,使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为 **黑箱镜像**,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。
|
||||
|
||||
而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 `docker commit` 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
从刚才的 `docker commit` 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
|
||||
|
||||
Dockerfile 是一个文本文件,其内包含了一条条的**指令 (Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
|
||||
Dockerfile 是一个文本文件,其内包含了一条条的 **指令 (Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
|
||||
|
||||
### 使用 docker init 快速创建 (推荐)
|
||||
|
||||
@@ -37,7 +37,7 @@ RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
|
||||
|
||||
### FROM 指定基础镜像
|
||||
|
||||
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 `nginx` 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 `FROM` 就是指定**基础镜像**,因此一个 `Dockerfile` 中 `FROM` 是必备的指令,并且必须是第一条指令。
|
||||
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 `nginx` 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 `FROM` 就是指定 **基础镜像**,因此一个 `Dockerfile` 中 `FROM` 是必备的指令,并且必须是第一条指令。
|
||||
|
||||
在 [Docker Hub](https://hub.docker.com/search?q=&type=image&image_filter=official) 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 [`nginx`](https://hub.docker.com/_/nginx/)、[`redis`](https://hub.docker.com/_/redis/)、[`mongo`](https://hub.docker.com/_/mongo/)、[`mysql`](https://hub.docker.com/_/mysql/)、[`httpd`](https://hub.docker.com/_/httpd/)、[`php`](https://hub.docker.com/_/php/)、[`tomcat`](https://hub.docker.com/_/tomcat/) 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 [`node`](https://hub.docker.com/_/node)、[`openjdk`](https://hub.docker.com/_/openjdk/)、[`python`](https://hub.docker.com/_/python/)、[`ruby`](https://hub.docker.com/_/ruby/)、[`golang`](https://hub.docker.com/_/golang/) 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。
|
||||
|
||||
@@ -72,9 +72,9 @@ Dockerfile 中每一个指令都会建立一层,`RUN` 也不例外。每一个
|
||||
>
|
||||
> 每一个 `RUN` 指令都会产生一个新的镜像层。为了减少镜像体积和层数,我们通常会将多个命令合并到一个 `RUN` 指令中执行。
|
||||
>
|
||||
> 更多关于 `RUN` 指令的详细用法、最佳实践 (如清理缓存、使用 pipefail 等) 及 `Union FS` 的层数限制等内容,请参阅**[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)**中的** [RUN 指令](../07_dockerfile/7.1_run.md)**小节。
|
||||
> 更多关于 `RUN` 指令的详细用法、最佳实践 (如清理缓存、使用 pipefail 等) 及 `Union FS` 的层数限制等内容,请参阅 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 中的 **[RUN 指令](../07_dockerfile/7.1_run.md)** 小节。
|
||||
|
||||
要想编写优秀的 `Dockerfile`,必须了解每一条指令的作用和副作用。在**[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)**中,我们将对 `COPY`,`ADD`,`CMD`,`ENTRYPOINT` 等指令进行详细讲解。
|
||||
要想编写优秀的 `Dockerfile`,必须了解每一条指令的作用和副作用。在 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 中,我们将对 `COPY`,`ADD`,`CMD`,`ENTRYPOINT` 等指令进行详细讲解。
|
||||
|
||||
### 构建镜像
|
||||
|
||||
@@ -106,7 +106,7 @@ docker build [选项] <上下文路径/URL/->
|
||||
|
||||
### 镜像构建上下文
|
||||
|
||||
如果注意,会看到 `docker build` 命令最后有一个 `.`。`.` 表示当前目录,而 `Dockerfile` 就在当前目录,因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定**上下文路径**。那么什么是上下文呢?
|
||||
如果注意,会看到 `docker build` 命令最后有一个 `.`。`.` 表示当前目录,而 `Dockerfile` 就在当前目录,因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定 **上下文路径**。那么什么是上下文呢?
|
||||
|
||||
首先我们要理解 `docker build` 的工作原理。Docker 在运行时分为 Docker 引擎 (也就是服务端守护进程) 和客户端工具。Docker 的引擎提供了一组 REST API,被称为 [Docker Remote API](https://docs.docker.com/develop/sdk/),而如 `docker` 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 `docker` 功能,但实际上,一切都是使用的远程调用形式在服务端 (Docker 引擎) 完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
|
||||
|
||||
@@ -120,7 +120,7 @@ docker build [选项] <上下文路径/URL/->
|
||||
COPY ./package.json /app/
|
||||
```
|
||||
|
||||
这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`,也不是复制 `Dockerfile` 所在目录下的 `package.json`,而是复制**上下文 (context)** 目录下的 `package.json`。
|
||||
这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`,也不是复制 `Dockerfile` 所在目录下的 `package.json`,而是复制 **上下文 (context)** 目录下的 `package.json`。
|
||||
|
||||
因此,`COPY` 这类指令中的源文件的路径都是*相对路径*。这也是初学者经常会问的为什么 `COPY ../package.json /app` 或者 `COPY /opt/xxxx /app` 无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Docker 镜像是怎么实现增量的修改和维护的?为什么容器启动
|
||||
|
||||
Docker 镜像并不是一个单纯的文件,而是由一组文件系统叠加构成的。
|
||||
|
||||
最底层的镜像称为**基础镜像 (Base Image)**,通常是各种 Linux 发行版的 root 文件系统,如 Ubuntu、Debian、CentOS 等。
|
||||
最底层的镜像称为 **基础镜像 (Base Image)**,通常是各种 Linux 发行版的 root 文件系统,如 Ubuntu、Debian、CentOS 等。
|
||||
|
||||
当我们在基础镜像之上构建新的镜像时 (例如安装了 Nginx),Docker 并不是复制一份基础镜像,而是在基础镜像之上,**新建一个层 (Layer)**,并在该层中仅记录为了安装 Nginx 而发生的文件变更 (添加、修改、删除)。
|
||||
|
||||
@@ -23,7 +23,7 @@ Docker 镜像并不是一个单纯的文件,而是由一组文件系统叠加
|
||||
|
||||
那么,既然镜像只读,容器为什么能写文件呢?
|
||||
|
||||
当容器启动时,Docker 会在镜像的最上层,添加一个新的**可写层 (Writable Layer)**,通常被称为**容器层**。
|
||||
当容器启动时,Docker 会在镜像的最上层,添加一个新的 **可写层 (Writable Layer)**,通常被称为 **容器层**。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
@@ -40,7 +40,7 @@ flowchart TD
|
||||
```
|
||||
|
||||
* **读取文件**:当容器需要读取文件时,Docker 会从最上层 (容器层) 开始向下层 (镜像层) 寻找,直到找到该文件为止。
|
||||
* **修改文件**:当容器需要修改某个文件时,Docker 会从下层镜像中将该文件复制到上层的容器层,然后对副本进行修改。这被称为**写时复制 (Copy-on-Write,CoW)** 策略。
|
||||
* **修改文件**:当容器需要修改某个文件时,Docker 会从下层镜像中将该文件复制到上层的容器层,然后对副本进行修改。这被称为 **写时复制 (Copy-on-Write,CoW)** 策略。
|
||||
* **删除文件**:当容器删除某个文件时,Docker 并不是真的去下层删除它 (因为下层是只读的),而是在容器层创建一个特殊的 “白障 (Whiteout)” 文件,用来标记该文件已被删除,从而在容器视图中隐藏它。
|
||||
|
||||
这就是为什么:
|
||||
@@ -60,6 +60,6 @@ Docker 镜像的每一层都有一个唯一的 ID,这个 ID 是根据该层的
|
||||
|
||||
Docker 使用联合文件系统 (Union FS) 来实现这种分层挂载。常见的驱动包括 `overlay2` (目前推荐)、`aufs` (早期使用)、`btrfs`、`zfs` 等。
|
||||
|
||||
虽然实现细节不同,但它们都遵循上述的**分层 + CoW** 模型。
|
||||
虽然实现细节不同,但它们都遵循上述的 **分层 + CoW** 模型。
|
||||
|
||||
> 想要深入了解 Overlay2 等文件系统的具体实现原理,包括 WorkDir、UpperDir、LowerDir 等底层细节,请阅读**[第十四章底层实现](../14_implementation/README.md)**中的**[联合文件系统](../14_implementation/14.4_ufs.md)**章节。
|
||||
> 想要深入了解 Overlay2 等文件系统的具体实现原理,包括 WorkDir、UpperDir、LowerDir 等底层细节,请阅读 **[第十四章底层实现](../14_implementation/README.md)** 中的 **[联合文件系统](../14_implementation/14.4_ufs.md)** 章节。
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- **前台运行**:程序占用当前终端,输出直接显示,关闭终端程序就停止
|
||||
- **后台运行**:程序在后台执行,不占用终端,终端关闭也不影响程序
|
||||
|
||||
Docker 容器默认是**前台运行**的。使用 `-d` (detach) 参数可以让容器在后台运行。
|
||||
Docker 容器默认是 **前台运行** 的。使用 `-d` (detach) 参数可以让容器在后台运行。
|
||||
|
||||
### 基本使用
|
||||
|
||||
@@ -51,7 +51,7 @@ $ docker run -d ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep
|
||||
|
||||
### 深入理解:容器为什么会 “立即退出”?
|
||||
|
||||
> **这是初学者最常遇到的困惑。**理解这个问题,你就理解了 Docker 的核心设计理念。
|
||||
> **这是初学者最常遇到的困惑。** 理解这个问题,你就理解了 Docker 的核心设计理念。
|
||||
|
||||
很多人尝试这样启动容器:
|
||||
|
||||
@@ -84,7 +84,7 @@ flowchart TD
|
||||
|
||||
**关键理解**:
|
||||
|
||||
- ❌ `-d` 参数**不是**让容器 “一直运行”
|
||||
- ❌ `-d` 参数 **不是** 让容器 “一直运行”
|
||||
- ✅ `-d` 参数是让容器 “在后台运行”,能运行多久取决于主进程
|
||||
|
||||
#### 常见的 “立即退出” 场景
|
||||
@@ -163,7 +163,7 @@ $ docker run -d -p 6379:6379 redis
|
||||
|
||||
#### 2。调试时先用前台模式
|
||||
|
||||
当容器启动有问题时,**去掉 `-d` 参数**可以直接看到输出和错误:
|
||||
当容器启动有问题时,**去掉 `-d` 参数** 可以直接看到输出和错误:
|
||||
|
||||
```bash
|
||||
## 有问题的容器,先前台运行看看发生了什么
|
||||
|
||||
@@ -135,7 +135,7 @@ $ docker attach 容器名
|
||||
|
||||
#### 工作原理
|
||||
|
||||
`attach` 会附加到容器的**主进程** (PID 1) 的标准输入输出:
|
||||
`attach` 会附加到容器的 **主进程** (PID 1) 的标准输入输出:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -181,7 +181,7 @@ CONTAINER ID IMAGE STATUS NAMES
|
||||
|
||||
#### 安全退出 attach
|
||||
|
||||
使用 `Ctrl+P` 然后 `Ctrl+Q` 可以从 attach 会话中**分离**,而不停止容器:
|
||||
使用 `Ctrl+P` 然后 `Ctrl+Q` 可以从 attach 会话中 **分离**,而不停止容器:
|
||||
|
||||
```bash
|
||||
$ docker attach myubuntu
|
||||
|
||||
@@ -47,7 +47,7 @@ RUN echo "hello" > world.txt # 文件在根目录!
|
||||
|
||||
如下代码块所示,展示了相关示例:
|
||||
|
||||
```
|
||||
```dockerfile
|
||||
RUN cd /app
|
||||
↓
|
||||
启动容器 → cd /app(仅内存变化)→ 提交镜像层 → 容器销毁
|
||||
|
||||
@@ -80,7 +80,7 @@ USER 1001:1001
|
||||
|
||||
### 用户必须已存在
|
||||
|
||||
`USER` 指令只能切换到**已存在**的用户:
|
||||
`USER` 指令只能切换到 **已存在** 的用户:
|
||||
|
||||
```docker
|
||||
## ❌ 错误:用户不存在
|
||||
|
||||
@@ -17,7 +17,7 @@ HEALTHCHECK NONE
|
||||
|
||||
### 为什么需要 HEALTHCHECK
|
||||
|
||||
在没有 HEALTHCHECK 之前,Docker 只能通过**进程退出码**来判断容器状态。**问题场景**:
|
||||
在没有 HEALTHCHECK 之前,Docker 只能通过 **进程退出码** 来判断容器状态。**问题场景**:
|
||||
|
||||
- Web 服务死锁,无法响应请求,但进程仍在运行
|
||||
- 数据库正在启动中,尚未准备好接受连接
|
||||
@@ -26,7 +26,7 @@ HEALTHCHECK NONE
|
||||
**引入 HEALTHCHECK 后**:
|
||||
Docker 定期执行指定的检查命令,根据返回值判断容器是否 “健康”。
|
||||
|
||||
```
|
||||
```bash
|
||||
容器状态转换:
|
||||
Starting ──成功──> Healthy ──失败N次──> Unhealthy
|
||||
▲ │
|
||||
@@ -211,6 +211,6 @@ HEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1
|
||||
|
||||
#### 4。只检查核心依赖
|
||||
|
||||
健康检查应主要关注**当前服务**是否可用,而不是检查其下游依赖 (数据库等)。下游依赖的检查应由应用逻辑处理。
|
||||
健康检查应主要关注 **当前服务** 是否可用,而不是检查其下游依赖 (数据库等)。下游依赖的检查应由应用逻辑处理。
|
||||
|
||||
---
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
ONBUILD <其它指令>
|
||||
```
|
||||
|
||||
`ONBUILD` 是一个特殊的指令,它后面跟的是其它指令 (如 `RUN`,`COPY` 等),这些指令**在当前镜像构建时不会执行**,只有当以当前镜像为基础镜像去构建下一级镜像时才会被执行。
|
||||
`ONBUILD` 是一个特殊的指令,它后面跟的是其它指令 (如 `RUN`,`COPY` 等),这些指令 **在当前镜像构建时不会执行**,只有当以当前镜像为基础镜像去构建下一级镜像时才会被执行。
|
||||
|
||||
---
|
||||
|
||||
### 为什么需要 ONBUILD
|
||||
|
||||
`ONBUILD` 主要用于制作**语言栈基础镜像**或**框架基础镜像**。
|
||||
`ONBUILD` 主要用于制作 **语言栈基础镜像** 或 **框架基础镜像**。
|
||||
|
||||
#### 场景:维护 Node.js 项目
|
||||
|
||||
@@ -64,7 +64,7 @@ FROM my-node-base
|
||||
|
||||
如下代码块所示,展示了相关示例:
|
||||
|
||||
```
|
||||
```bash
|
||||
基础镜像构建:
|
||||
Dockerfile (含 ONBUILD) ──build──> 基础镜像 (记录了 ONBUILD 触发器)
|
||||
(指令未执行)
|
||||
@@ -119,15 +119,15 @@ ONBUILD COPY dist/ /usr/share/nginx/html/
|
||||
|
||||
#### 1。继承性限制
|
||||
|
||||
`ONBUILD` 指令**只会继承一次**。
|
||||
`ONBUILD` 指令 **只会继承一次**。
|
||||
|
||||
- 镜像 A (含 ONBUILD)
|
||||
- 镜像 B (FROM A) -> 触发 ONBUILD
|
||||
- 镜像 C (FROM B) -> **不会**再次触发 ONBUILD
|
||||
- 镜像 C (FROM B) -> **不会** 再次触发 ONBUILD
|
||||
|
||||
#### 2。构建上下文
|
||||
|
||||
子镜像构建时,`ONBUILD COPY . .` 中的 `.` 指的是**子项目**的构建上下文,而不是基础镜像的上下文。
|
||||
子镜像构建时,`ONBUILD COPY . .` 中的 `.` 指的是 **子项目** 的构建上下文,而不是基础镜像的上下文。
|
||||
|
||||
#### 3。不允许级联
|
||||
|
||||
@@ -147,7 +147,7 @@ ONBUILD COPY dist/ /usr/share/nginx/html/
|
||||
|
||||
建议在镜像标签中添加 `-onbuild` 后缀,明确告知使用者该镜像包含触发器。
|
||||
|
||||
```
|
||||
```bash
|
||||
node:20-onbuild
|
||||
python:3.12-onbuild
|
||||
```
|
||||
|
||||
@@ -110,7 +110,7 @@ RUN echo "Using sh again"
|
||||
|
||||
### 对其他指令的影响
|
||||
|
||||
`SHELL` 影响的是所有使用 **shell 格式**的指令:
|
||||
`SHELL` 影响的是所有使用 **shell 格式** 的指令:
|
||||
|
||||
| 指令格式 | 是否受 SHELL 影响 |
|
||||
|---------|-------------------|
|
||||
|
||||
@@ -172,7 +172,7 @@ $ docker run -dit --rm --network=laravel -p 8080:80 my/nginx
|
||||
|
||||
### 生产环境优化
|
||||
|
||||
本小节内容为了方便测试,将配置文件直接放到了镜像中,实际在使用时**建议**将配置文件作为 `config` 或 `secret` 挂载到容器中,请读者自行学习 `Kubernetes` 的相关内容。
|
||||
本小节内容为了方便测试,将配置文件直接放到了镜像中,实际在使用时 **建议** 将配置文件作为 `config` 或 `secret` 挂载到容器中,请读者自行学习 `Kubernetes` 的相关内容。
|
||||
|
||||
由于篇幅所限本小节只是简单列出,更多内容可以参考 https://github.com/khs1994-docker/laravel-demo 项目。
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ RUN <command>
|
||||
RUN ["executable", "param1", "param2"]
|
||||
```
|
||||
|
||||
`RUN` 指令是 Dockerfile 中最常用的指令之一。它在**当前镜像层**之上创建一个新层,执行指定的命令,并提交结果。
|
||||
`RUN` 指令是 Dockerfile 中最常用的指令之一。它在 **当前镜像层** 之上创建一个新层,执行指定的命令,并提交结果。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -58,9 +58,9 @@ COPY src/*.js /app/src/
|
||||
COPY src/ /app/src/
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:复制目录时,复制的是目录的**内容**,不包含目录本身。
|
||||
> ⚠️ **注意**:复制目录时,复制的是目录的 **内容**,不包含目录本身。
|
||||
|
||||
```
|
||||
```bash
|
||||
构建上下文: 镜像内:
|
||||
src/ /app/src/
|
||||
├── index.js → ├── index.js
|
||||
|
||||
@@ -68,7 +68,7 @@ ADD ubuntu-noble-core-cloudimg-amd64-root.tar.gz /
|
||||
|
||||
如下代码块所示,展示了相关示例:
|
||||
|
||||
```
|
||||
```bash
|
||||
ADD app.tar.gz /app/
|
||||
│
|
||||
├─ 识别 .tar.gz 格式
|
||||
|
||||
@@ -57,7 +57,7 @@ CMD echo $HOME
|
||||
CMD ["sh", "-c", "echo $HOME"]
|
||||
```
|
||||
|
||||
**优点**:可以使用环境变量、管道等 shell 特性**缺点**:主进程是 sh,信号无法正确传递给应用
|
||||
**优点**:可以使用环境变量、管道等 shell 特性 **缺点**:主进程是 sh,信号无法正确传递给应用
|
||||
|
||||
---
|
||||
|
||||
@@ -109,7 +109,7 @@ $ docker run -it ubuntu # 进入 bash
|
||||
$ docker run ubuntu cat /etc/os-release # 覆盖为 cat 命令
|
||||
```
|
||||
|
||||
```
|
||||
```bash
|
||||
Dockerfile: docker run 命令:
|
||||
CMD ["/bin/bash"] + cat /etc/os-release
|
||||
│ │
|
||||
@@ -138,7 +138,7 @@ CMD service nginx start
|
||||
|
||||
如下代码块所示,展示了相关示例:
|
||||
|
||||
```
|
||||
```bash
|
||||
1. CMD service nginx start
|
||||
↓ 被转换为
|
||||
2. CMD ["sh", "-c", "service nginx start"]
|
||||
@@ -170,8 +170,8 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
| 指令 | 用途 | 运行时行为 |
|
||||
|------|------|-----------|
|
||||
| **CMD**| 默认命令 | `docker run` 参数会**覆盖**它 |
|
||||
| **ENTRYPOINT**| 入口点 | `docker run` 参数会**追加**到它后面 |
|
||||
| **CMD**| 默认命令 | `docker run` 参数会 **覆盖** 它 |
|
||||
| **ENTRYPOINT**| 入口点 | `docker run` 参数会 **追加** 到它后面 |
|
||||
|
||||
#### 单独使用 CMD
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### 什么是 ENTRYPOINT
|
||||
|
||||
`ENTRYPOINT` 指定容器启动时运行的入口程序。与 CMD 不同,ENTRYPOINT 定义的命令不会被 `docker run` 的参数覆盖,而是**接收这些参数**。
|
||||
`ENTRYPOINT` 指定容器启动时运行的入口程序。与 CMD 不同,ENTRYPOINT 定义的命令不会被 `docker run` 的参数覆盖,而是 **接收这些参数**。
|
||||
|
||||
> **核心作用**:让镜像像一个可执行程序一样使用,`docker run` 的参数作为这个程序的参数。
|
||||
|
||||
@@ -141,7 +141,7 @@ HTTP/1.1 200 OK
|
||||
|
||||
如下代码块所示,展示了相关示例:
|
||||
|
||||
```
|
||||
```bash
|
||||
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
|
||||
│
|
||||
docker run myip -i
|
||||
@@ -199,7 +199,7 @@ exec "$@"
|
||||
|
||||
如下代码块所示,展示了相关示例:
|
||||
|
||||
```
|
||||
```bash
|
||||
docker run redis docker run redis bash
|
||||
│ │
|
||||
▼ ▼
|
||||
|
||||
@@ -26,7 +26,7 @@ ARG <参数名>[=<默认值>]
|
||||
| **适用场景** | 构建参数 (版本号等)| 应用配置 |
|
||||
| **可见性** | `docker history` 可见 | `docker inspect` 可见 |
|
||||
|
||||
```
|
||||
```dockerfile
|
||||
构建时 运行时
|
||||
├─ ARG VERSION=1.0 │ (ARG 已消失)
|
||||
├─ ENV APP_ENV=prod │ APP_ENV=prod(仍存在)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
EXPOSE <端口> [<端口>/<协议>...]
|
||||
```
|
||||
|
||||
`EXPOSE` 声明容器运行时提供服务的端口。这是一个**文档性质的声明**,告诉使用者容器会监听哪些端口。
|
||||
`EXPOSE` 声明容器运行时提供服务的端口。这是一个 **文档性质的声明**,告诉使用者容器会监听哪些端口。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 什么是 Dockerfile
|
||||
|
||||
Dockerfile 是一个文本文件,其內包含了一条条的**指令 (Instruction)**,每一条指令构建一层,therefore 每一条指令的内容,就是描述该层应当如何构建。
|
||||
Dockerfile 是一个文本文件,其內包含了一条条的 **指令 (Instruction)**,每一条指令构建一层,therefore 每一条指令的内容,就是描述该层应当如何构建。
|
||||
|
||||
在[第四章](../04_image/README.md)中,我们通过 `docker commit` 学习了镜像的构成。但是,手动 `commit` 只能作为临时修补,并不适合作为生产环境镜像的构建方式。
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### 什么是绑定挂载
|
||||
|
||||
Bind Mount (绑定挂载) 将**宿主机的目录或文件**直接挂载到容器中。容器可以读写宿主机的文件系统。
|
||||
Bind Mount (绑定挂载) 将 **宿主机的目录或文件** 直接挂载到容器中。容器可以读写宿主机的文件系统。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
Docker 容器的 DNS 配置有两种情况:
|
||||
|
||||
1. **默认 Bridge 网络**:继承宿主机的 DNS 配置 (`/etc/resolv.conf`)。
|
||||
2. **自定义网络** (推荐):使用 Docker 嵌入式 DNS 服务器 (Embedded DNS),支持通过**容器名**进行服务发现。
|
||||
2. **自定义网络** (推荐):使用 Docker 嵌入式 DNS 服务器 (Embedded DNS),支持通过 **容器名** 进行服务发现。
|
||||
|
||||
---
|
||||
|
||||
@@ -58,6 +58,7 @@ $ docker run --dns-search=example.com myapp
|
||||
```
|
||||
|
||||
#### 3. --hostname (-h)
|
||||
|
||||
设置容器的主机名。
|
||||
|
||||
```bash
|
||||
@@ -99,7 +100,7 @@ $ docker run -h myweb nginx
|
||||
|
||||
**现象**:`ping db` 提示 `bad address 'db'`。**原因**:
|
||||
|
||||
- 你可能在使用**默认的 bridge 网络**。默认 bridge 网络**不支持**通过容器名进行 DNS 解析 (这是一个历史遗留设计)。
|
||||
- 你可能在使用 **默认的 bridge 网络**。默认 bridge 网络 **不支持** 通过容器名进行 DNS 解析 (这是一个历史遗留设计)。
|
||||
- **解决**:使用自定义网络 (`docker network create ...`)。
|
||||
|
||||
---
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
- **宿主机访问容器**:可以通过容器 IP 访问。
|
||||
- **外部网络访问容器**:❌ 默认无法直接访问。
|
||||
|
||||
为了让外部 (如你的浏览器、其他局域网机器) 访问容器内的服务,我们需要将容器的端口**映射**到宿主机的端口。
|
||||
为了让外部 (如你的浏览器、其他局域网机器) 访问容器内的服务,我们需要将容器的端口 **映射** 到宿主机的端口。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**BuildKit** 是下一代的镜像构建组件,在 https://github.com/moby/buildkit 开源。
|
||||
|
||||
> **重要**:自 Docker 23.0 起,BuildKit 已成为**默认稳定构建器**,无需手动启用。Docker Engine v29 进一步将 Containerd 镜像存储设为默认,提升与 Kubernetes 的互操作性。
|
||||
> **重要**:自 Docker 23.0 起,BuildKit 已成为 **默认稳定构建器**,无需手动启用。Docker Engine v29 进一步将 Containerd 镜像存储设为默认,提升与 Kubernetes 的互操作性。
|
||||
|
||||
目前,Docker Hub 自动构建已经支持 BuildKit,具体请参考 https://github.com/docker-practice/docker-hub-buildx
|
||||
|
||||
@@ -48,7 +48,7 @@ COPY --from=builder /app/dist /app/dist
|
||||
|
||||
使用多阶段构建,构建的镜像中只包含了目标文件夹 `dist`,但仍然存在一些问题,当 `package.json` 文件变动时,`RUN npm i && rm -rf ~/.npm` 这一层会重新执行,变更多次后,生成了大量的中间层镜像。
|
||||
|
||||
为解决这个问题,进一步的我们可以设想一个类似**数据卷**的功能,在镜像构建时把 `node_modules` 文件夹挂载上去,在构建完成后,这个 `node_modules` 文件夹会自动卸载,实际的镜像中并不包含 `node_modules` 这个文件夹,这样我们就省去了每次获取依赖的时间,大大增加了镜像构建效率,同时也避免了生成了大量的中间层镜像。
|
||||
为解决这个问题,进一步的我们可以设想一个类似 **数据卷** 的功能,在镜像构建时把 `node_modules` 文件夹挂载上去,在构建完成后,这个 `node_modules` 文件夹会自动卸载,实际的镜像中并不包含 `node_modules` 这个文件夹,这样我们就省去了每次获取依赖的时间,大大增加了镜像构建效率,同时也避免了生成了大量的中间层镜像。
|
||||
|
||||
`BuildKit` 提供了 `RUN --mount=type=cache` 指令,可以实现上边的设想。
|
||||
|
||||
|
||||
@@ -324,7 +324,3 @@ services:
|
||||
* `--no-up` 不自动启动服务。
|
||||
|
||||
* `--quiet` 静默模式。
|
||||
|
||||
### 参考资料
|
||||
|
||||
* [官方文档](https://docs.docker.com/compose/reference/overview/)
|
||||
|
||||
@@ -564,8 +564,3 @@ MONGO_VERSION=3.6
|
||||
```
|
||||
|
||||
执行 `docker compose up` 则会启动一个 `mongo:3.6` 镜像的容器。
|
||||
|
||||
### 参考资料
|
||||
|
||||
* [官方文档](https://docs.docker.com/compose/compose-file/)
|
||||
* [awesome-compose](https://github.com/docker/awesome-compose)
|
||||
|
||||
@@ -209,7 +209,7 @@ $ docker compose run --rm web django-admin startproject mysite .
|
||||
|
||||
生成的目录结构:
|
||||
|
||||
```
|
||||
```bash
|
||||
django-docker/
|
||||
├── compose.yaml
|
||||
├── Dockerfile
|
||||
@@ -266,7 +266,7 @@ $ docker compose up
|
||||
2. 启动 db 服务,等待健康检查通过
|
||||
3. 启动 web 服务
|
||||
|
||||
```
|
||||
```bash
|
||||
db-1 | PostgreSQL init process complete; ready for start up.
|
||||
db-1 | LOG: database system is ready to accept connections
|
||||
web-1 | Watching for file changes with StatReloader
|
||||
@@ -340,7 +340,7 @@ $ sudo chown -R $USER:$USER .
|
||||
|
||||
### 开发 vs 生产:关键差异
|
||||
|
||||
笔者特别提醒,本节的配置是**开发环境**配置。生产环境需要以下调整:
|
||||
笔者特别提醒,本节的配置是 **开发环境** 配置。生产环境需要以下调整:
|
||||
|
||||
| 配置项 | 开发环境 | 生产环境 |
|
||||
|--------|---------|---------|
|
||||
|
||||
@@ -202,7 +202,7 @@ $ docker compose up
|
||||
|
||||
输出示例:
|
||||
|
||||
```
|
||||
```bash
|
||||
db-1 | PostgreSQL init process complete; ready for start up.
|
||||
db-1 | LOG: database system is ready to accept connections
|
||||
web-1 | => Booting Puma
|
||||
|
||||
@@ -8,7 +8,7 @@ WordPress 是全球最流行的内容管理系统 (CMS)。使用 Docker Compose
|
||||
|
||||
如下代码块所示,展示了相关示例:
|
||||
|
||||
```
|
||||
```bash
|
||||
wordpress/
|
||||
├── compose.yaml
|
||||
├── .env # 环境变量(敏感信息)
|
||||
|
||||
@@ -399,6 +399,7 @@ $ cosign verify --key cosign.pub $IMAGE
|
||||
```
|
||||
|
||||
### 3. SLSA (Supply-chain Levels for Software Artifacts)
|
||||
|
||||
遵循 SLSA 框架,确保构建过程的完整性,例如使用 GitHub Actions 等受控环境进行构建,而非在开发者本地机器上构建发布。
|
||||
|
||||
---
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
`etcdctl` 支持如下的命令,大体上分为数据库操作和非数据库操作两类,后面将分别进行解释。
|
||||
|
||||
```
|
||||
```bash
|
||||
NAME:
|
||||
etcdctl - A simple command line client for etcd3.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
Kubernetes (常简称为 K8s) 是 Google 开源的容器编排引擎。如果说 Docker 解决了 “如何打包和运送集装箱” 的问题,那么 Kubernetes 解决的就是 “如何管理海量集装箱的调度、运行和维护” 的问题。
|
||||
|
||||
它不仅仅是一个编排系统,更是一个**云原生应用操作系统**。
|
||||
它不仅仅是一个编排系统,更是一个 **云原生应用操作系统**。
|
||||
|
||||
> **名字由来**:Kubernetes 在希腊语中意为 “舵手” 或 “飞行员”。K8s 是因为 k 和 s 之间有 8 个字母。
|
||||
|
||||
@@ -71,7 +71,7 @@ Kubernetes 的最小调度单位。一个 Pod 可以包含一个或多个紧密
|
||||
|
||||
### 架构
|
||||
|
||||
Kubernetes 也是 C/S 架构,由 **Control Plane (控制平面) **和** Worker Node (工作节点)** 组成:
|
||||
Kubernetes 也是 C/S 架构,由 **Control Plane (控制平面)** 和 **Worker Node (工作节点)** 组成:
|
||||
|
||||
- **Control Plane**:负责决策 (API Server,Scheduler,Controller Manager,etcd)
|
||||
- **Worker Node**:负责干活 (Kubelet,Kube-proxy,Container Runtime)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[Kubernetes Dashboard](https://github.com/kubernetes/dashboard) 是基于网页的 Kubernetes 用户界面。
|
||||
|
||||

|
||||

|
||||
|
||||
### 部署
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
在 Docker Desktop 设置页面,点击 `Kubernetes`,选择 `Enable Kubernetes`,稍等片刻,看到左下方 `Kubernetes` 变为 `running`,Kubernetes 启动成功。
|
||||
|
||||

|
||||

|
||||
|
||||
> 注意:Kubernetes 的镜像存储在 `registry.k8s.io`,如果国内网络无法直接访问,可以在 Docker Desktop 配置中的 `Docker Engine` 处配置镜像加速器,或者利用国内云服务商的镜像仓库手动拉取镜像并 retag。
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ sudo k3s kubectl get nodes
|
||||
```
|
||||
|
||||
输出类似:
|
||||
```
|
||||
```bash
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
k3s-master Ready control-plane,master 1m v1.35.1+k3s1
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
`kubeadm` 提供了 `kubeadm init` 以及 `kubeadm join` 这两个命令,作为快速创建 `Kubernetes` 集群的最佳实践。
|
||||
|
||||
> ⚠️ **重要说明**:自 Kubernetes 1.24 起,内置 `dockershim` 已被移除,Kubernetes 默认不再直接使用 Docker Engine 作为容器运行时 (CRI)。因此,**更推荐参考**同目录下的《[使用 kubeadm 部署 Kubernetes (CRI 使用 containerd)](kubeadm.md)》。
|
||||
> ⚠️ **重要说明**:自 Kubernetes 1.24 起,内置 `dockershim` 已被移除,Kubernetes 默认不再直接使用 Docker Engine 作为容器运行时 (CRI)。因此,**更推荐参考** 同目录下的《[使用 kubeadm 部署 Kubernetes (CRI 使用 containerd)](kubeadm.md)》。
|
||||
>
|
||||
> 本文档主要用于历史环境/学习目的:如果你确实需要在较新版本中继续使用 Docker Engine,通常需要额外部署 `cri-dockerd` 并在 `kubeadm init/join` 中指定 `--cri-socket`。
|
||||
|
||||
@@ -175,7 +175,7 @@ kubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \
|
||||
|
||||
#### node 工作节点
|
||||
|
||||
在**另一主机**重复**部署**小节以前的步骤,安装配置好 kubelet。根据提示,加入到集群。
|
||||
在 **另一主机** 重复 **部署** 小节以前的步骤,安装配置好 kubelet。根据提示,加入到集群。
|
||||
|
||||
```bash
|
||||
$ kubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \
|
||||
|
||||
@@ -22,7 +22,7 @@ $ sudo yum install containerd.io
|
||||
|
||||
新建 `/etc/systemd/system/cri-containerd.service` 文件
|
||||
|
||||
```
|
||||
```bash
|
||||
[Unit]
|
||||
Description=containerd container runtime for kubernetes
|
||||
Documentation=https://containerd.io
|
||||
@@ -399,7 +399,7 @@ kubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \
|
||||
|
||||
#### node 工作节点
|
||||
|
||||
在**另一主机**重复**部署**小节以前的步骤,安装配置好 kubelet。根据提示,加入到集群。
|
||||
在 **另一主机** 重复 **部署** 小节以前的步骤,安装配置好 kubelet。根据提示,加入到集群。
|
||||
|
||||
```bash
|
||||
$ systemctl enable cri-containerd
|
||||
|
||||
@@ -27,11 +27,11 @@ graph LR
|
||||
|
||||
Docker 的内部架构如同洋葱一样分层,每一层专注解决特定问题:
|
||||
|
||||
#### 1。Docker CLI (客户端)
|
||||
#### 1. Docker CLI (客户端)
|
||||
|
||||
用户与 Docker 交互的主要方式。它将用户命令 (如 `docker run`) 转换为 API 请求发送给 dockerd。
|
||||
|
||||
#### 2。Dockerd (守护进程)
|
||||
#### 2. Dockerd (守护进程)
|
||||
|
||||
Docker 的大脑。
|
||||
|
||||
@@ -39,16 +39,16 @@ Docker 的大脑。
|
||||
- 管理 Docker 对象 (镜像、容器、网络、卷)
|
||||
- 编排下层组件完成工作
|
||||
|
||||
#### 3。Containerd (高级运行时)
|
||||
#### 3. Containerd (高级运行时)
|
||||
|
||||
行业标准的容器运行时 (CNCF 毕业项目)。
|
||||
|
||||
- 管理容器的完整生命周期 (启动、停止)
|
||||
- 镜像拉取与存储
|
||||
- **不包含**复杂的与容器无关的功能 (如构建、API)
|
||||
- **不包含** 复杂的与容器无关的功能 (如构建、API)
|
||||
- Kubernetes 也可以直接使用 containerd (跳过 Docker)
|
||||
|
||||
#### 4。Runc (低级运行时)
|
||||
#### 4. Runc (低级运行时)
|
||||
|
||||
用于创建和运行容器的 CLI 工具。
|
||||
|
||||
@@ -99,9 +99,12 @@ flowchart TD
|
||||
Shim -.-> |8. Monitor IO/Exit| Container
|
||||
```
|
||||
|
||||
1. **CLI** 发送请求给 **Dockerd**2。**Dockerd** 解析请求,调用 **Containerd**3。**Containerd** 准备镜像,转换为 OCI Bundle
|
||||
1. **CLI** 发送请求给 **Dockerd**
|
||||
2. **Dockerd** 解析请求,调用 **Containerd**
|
||||
3. **Containerd** 准备镜像,转换为 OCI Bundle
|
||||
4. **Containerd** 创建 **Shim** 进程
|
||||
5. **Shim** 调用 **Runc**6。**Runc** 与系统内核交互,创建 Namespaces 和 Cgroups
|
||||
5. **Shim** 调用 **Runc**
|
||||
6. **Runc** 与系统内核交互,创建 Namespaces 和 Cgroups
|
||||
7. **Runc** 启动 nginx 进程后退出
|
||||
8. **Shim** 接管容器 IO 和生命周期监控
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 什么是 Namespace
|
||||
|
||||
> **Namespace 是 Linux 内核提供的资源隔离机制,它让容器内的进程仿佛运行在独立的操作系统中。**Namespace 是容器技术的核心基础之一。它回答了一个关键问题:**如何让一个进程 “以为” 自己独占整个系统?**
|
||||
> **Namespace 是 Linux 内核提供的资源隔离机制,它让容器内的进程仿佛运行在独立的操作系统中。** Namespace 是容器技术的核心基础之一。它回答了一个关键问题:**如何让一个进程 “以为” 自己独占整个系统?**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -124,7 +124,7 @@ MNT Namespace 负责文件系统挂载点的隔离,确保容器看到独立的
|
||||
|
||||
如下代码块所示,展示了相关示例:
|
||||
|
||||
```
|
||||
```bash
|
||||
宿主机文件系统: 容器内看到的:
|
||||
/ / ← 容器的根目录
|
||||
├── bin/ ├── bin/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### 什么是控制组
|
||||
|
||||
控制组 (Control Groups,简称 cgroups) 是 Linux 内核的一个特性,用于**限制、记录和隔离**进程组的资源使用 (CPU、内存、磁盘 I/O、网络等)。
|
||||
控制组 (Control Groups,简称 cgroups) 是 Linux 内核的一个特性,用于 **限制、记录和隔离** 进程组的资源使用 (CPU、内存、磁盘 I/O、网络等)。
|
||||
|
||||
> **核心作用**:让多个容器公平共享宿主机资源,防止单个容器耗尽系统资源。
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### 什么是联合文件系统
|
||||
|
||||
联合文件系统 (UnionFS) 是一种**分层、轻量级**的文件系统,它将多个目录 “联合” 挂载到同一个虚拟目录,形成一个统一的文件系统视图。
|
||||
联合文件系统 (UnionFS) 是一种 **分层、轻量级** 的文件系统,它将多个目录 “联合” 挂载到同一个虚拟目录,形成一个统一的文件系统视图。
|
||||
|
||||
> **核心思想**:将多个只读层叠加,最上层可写,形成完整的文件系统。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# CI/CD
|
||||
|
||||
**持续集成 (Continuous integration)**是一种软件开发实践,每次集成都通过自动化的构建 (包括编译,发布,自动化测试) 来验证,从而尽早地发现集成错误。**持续部署 (continuous deployment)** 是通过自动化的构建、测试和部署循环来快速交付高质量的产品。
|
||||
**持续集成 (Continuous integration)** 是一种软件开发实践,每次集成都通过自动化的构建 (包括编译,发布,自动化测试) 来验证,从而尽早地发现集成错误。**持续部署 (continuous deployment)** 是通过自动化的构建、测试和部署循环来快速交付高质量的产品。
|
||||
|
||||
|
||||
与 `Jenkins` 不同的是,基于 Docker 的 CI/CD 每一步都运行在 Docker 容器中,所以理论上支持所有的编程语言。
|
||||
|
||||
@@ -88,7 +88,7 @@ $ git push origin master
|
||||
|
||||
打开我们部署好的 `Drone` 网站或者 Drone Cloud,即可看到构建结果。
|
||||
|
||||

|
||||

|
||||
|
||||
当然我们也可以把构建结果上传到 GitHub,Docker Registry,云服务商提供的对象存储,或者生产环境中。
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
登录 GitHub,在 https://github.com/settings/applications/new 新建一个应用。
|
||||
|
||||

|
||||

|
||||
|
||||
接下来查看这个应用的详情,记录 `Client ID` 和 `Client Secret`,之后配置 Drone 会用到。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Debian Ubuntu
|
||||
|
||||
`Debian` 和 `Ubuntu` 都是目前较为流行的 **Debian 系**的服务器操作系统,十分适合研发场景。`Docker Hub` 上提供了官方镜像,国内各大容器云服务也基本都提供了相应的支持。
|
||||
`Debian` 和 `Ubuntu` 都是目前较为流行的 **Debian 系** 的服务器操作系统,十分适合研发场景。`Docker Hub` 上提供了官方镜像,国内各大容器云服务也基本都提供了相应的支持。
|
||||
|
||||
### Debian 系统简介
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||

|
||||
|
||||
|
||||
`Debian` 是由 `GPL` 和其他自由软件许可协议授权的自由软件组成的操作系统,由 **Debian 计划 (Debian Project)**组织维护。**Debian 计划**是一个独立的、分散的组织,由 `3000` 人志愿者组成,接受世界多个非盈利组织的资金支持,`Software in the Public Interest` 提供支持并持有商标作为保护机构。`Debian` 以其坚守 `Unix` 和自由软件的精神,以及其给予用户的众多选择而闻名。现时 `Debian` 包括了超过 `25,000` 个软件包并支持 `12` 个计算机系统结构。
|
||||
`Debian` 是由 `GPL` 和其他自由软件许可协议授权的自由软件组成的操作系统,由 **Debian 计划 (Debian Project)** 组织维护。**Debian 计划** 是一个独立的、分散的组织,由 `3000` 人志愿者组成,接受世界多个非盈利组织的资金支持,`Software in the Public Interest` 提供支持并持有商标作为保护机构。`Debian` 以其坚守 `Unix` 和自由软件的精神,以及其给予用户的众多选择而闻名。现时 `Debian` 包括了超过 `25,000` 个软件包并支持 `12` 个计算机系统结构。
|
||||
|
||||
`Debian` 作为一个大的系统组织框架,其下有多种不同操作系统核心的分支计划,主要为采用 `Linux` 核心的 `Debian GNU/Linux` 系统,其他还有采用 `GNU Hurd` 核心的 `Debian GNU/Hurd` 系统、采用 `FreeBSD` 核心的 `Debian GNU/kFreeBSD` 系统,以及采用 `NetBSD` 核心的 `Debian GNU/NetBSD` 系统。甚至还有利用 `Debian` 的系统架构和工具,采用 `OpenSolaris` 核心构建而成的 `Nexenta OS` 系统。在这些 `Debian` 系统中,以采用 `Linux` 核心的 `Debian GNU/Linux` 最为著名。
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
1. 图号格式统一为:`图 <章号>-<序号> <图题>`,例如 `图 10-2 Rails + PostgreSQL 的 Compose 架构`。
|
||||
2. 图号在同一章内按出现顺序连续编号,不重复、不跳号。
|
||||
3. 正文引用图片统一写法:`如图 <章号>-<序号> 所示`,不使用 “下图/上图/示意图如下”。
|
||||
4. 所有图片必须提供有意义的 alt 文本,不使用空 alt (``)。
|
||||
4. 所有图片必须提供有意义的 alt 文本,不使用空 alt (``)。
|
||||
5. 图题单独成行,放在图片下方。
|
||||
|
||||
### 章节风格规范
|
||||
|
||||
@@ -124,7 +124,7 @@ $ docker run --network=my-net --ip=172.25.3.3 -itd --name=my-container busybox
|
||||
|
||||
例如,如下操作将默认存储位置迁移到 /storage/docker。
|
||||
|
||||
```
|
||||
```bash
|
||||
[root@s26 ~]# df -h
|
||||
Filesystem Size Used Avail Use% Mounted on
|
||||
/dev/mapper/VolGroup-lv_root 50G 5.3G 42G 12% /
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
[CentOS](https://en.wikipedia.org/wiki/CentOS) 是流行的 Linux 发行版,其软件包大多跟 RedHat 系列保持一致。
|
||||
|
||||
> ⚠️ **重要提示**:CentOS 8 已于 2021 年 12 月 31 日停止维护 (EOL),CentOS 7 也已于 2024 年 6 月 30 日**完全结束支持**。Docker Hub 上的 CentOS 官方镜像**已停止更新**且存在未修复的安全漏洞。
|
||||
> ⚠️ **重要提示**:CentOS 8 已于 2021 年 12 月 31 日停止维护 (EOL),CentOS 7 也已于 2024 年 6 月 30 日 **完全结束支持**。Docker Hub 上的 CentOS 官方镜像 **已停止更新** 且存在未修复的安全漏洞。
|
||||
>
|
||||
> 2026 年了,对于任何新项目,**强烈建议**使用以下生产级替代方案:
|
||||
> 2026 年了,对于任何新项目,**强烈建议** 使用以下生产级替代方案:
|
||||
> - [Rocky Linux](https://hub.docker.com/_/rockylinux):CentOS 原创始人发起的社区驱动项目,目前主流为 Rocky Linux 9。
|
||||
> - [AlmaLinux](https://hub.docker.com/_/almalinux):由 CloudLinux 支持的企业级发行版,提供长期支持。
|
||||
> - [CentOS Stream](https://hub.docker.com/r/centos/centos):RHEL 的上游开发分支 (适合开发测试,不建议用于生产环境)。
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
下图直观地展示了本节内容:
|
||||
|
||||
[](https://github.com/yeasy/docker_practice) [](https://github.com/yeasy/docker_practice/releases) [](https://docs.docker.com/engine/release-notes/) [][1]
|
||||
[](https://github.com/yeasy/docker_practice) [](https://github.com/yeasy/docker_practice/releases) [](https://docs.docker.com/engine/release-notes/) [][1]
|
||||
|
||||
**v1.5.5**
|
||||
**v1.5.6**
|
||||
|
||||
[Docker](https://www.docker.com) 是个划时代的开源项目,它彻底释放了计算虚拟化的威力,极大提高了应用的维护效率,降低了云计算应用开发的成本!使用 Docker,可以让应用的部署、测试和分发都变得前所未有的高效和轻松!
|
||||
|
||||
@@ -78,7 +78,7 @@ npx honkit serve
|
||||
|
||||
下图直观地展示了本节内容:
|
||||
|
||||
[][1]
|
||||
[][1]
|
||||
|
||||
《[Docker 技术入门与实战][1]》已更新到第 4 版,讲解最新容器技术栈知识,欢迎大家阅读并反馈建议。
|
||||
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
|
||||
def check_file(filepath, verbose=False):
|
||||
violations = []
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
lines = content.split('\n')
|
||||
|
||||
filename = os.path.basename(filepath)
|
||||
is_readme = filename.lower() == 'readme.md'
|
||||
is_summary = filename.lower() == 'summary.md'
|
||||
is_section = re.match(r'^\d+\.\d+_.*\.md$', filename)
|
||||
|
||||
# 1.1 Bold Text: No spaces inside the bold markers
|
||||
for i, line in enumerate(lines):
|
||||
if '** ' in line or ' **' in line:
|
||||
# We must be careful: '** ' might be a bold start with space inside, but it could also be regular text.
|
||||
# Let's use a simpler, line-bound regex line by line
|
||||
if re.search(r'\*\*\s+[^*]+\*\*|\*\*[^*]+\s+\*\*', line):
|
||||
violations.append(f"1.1 Bold Text: Space inside bold markers at line {i+1}")
|
||||
|
||||
# 1.4 Trailing Newline
|
||||
if not content.endswith('\n') or content.endswith('\n\n'):
|
||||
if content: # ignore empty files
|
||||
violations.append("1.4 Trailing Newline: File must end with exactly one newline character")
|
||||
|
||||
headers = []
|
||||
for i, line in enumerate(lines):
|
||||
m = re.match(r'^(#{1,6})\s+(.*)', line)
|
||||
if m:
|
||||
level = len(m.group(1))
|
||||
text = m.group(2)
|
||||
headers.append({'line': i, 'level': level, 'text': text})
|
||||
|
||||
# 1.2 Header Spacing
|
||||
if i + 1 < len(lines):
|
||||
next_line = lines[i+1].strip()
|
||||
if next_line != '':
|
||||
violations.append(f"1.2 Header Spacing: Header at line {i+1} not followed by a blank line")
|
||||
if i + 2 < len(lines):
|
||||
if lines[i+1].strip() == '' and lines[i+2].strip() == '':
|
||||
violations.append(f"1.2 Header Spacing: Header at line {i+1} followed by multiple blank lines")
|
||||
|
||||
# 1.3 Header Hierarchy
|
||||
for j in range(len(headers) - 1):
|
||||
curr_level = headers[j]['level']
|
||||
next_level = headers[j+1]['level']
|
||||
if next_level > curr_level + 1:
|
||||
violations.append(f"1.3 Header Hierarchy: Skipped header level from H{curr_level} to H{next_level} at line {headers[j+1]['line']+1}")
|
||||
|
||||
# 2.2 File Header Levels
|
||||
if headers:
|
||||
first_header_level = headers[0]['level']
|
||||
if is_readme and first_header_level != 1:
|
||||
violations.append("2.2 File Header Levels: README.md first header must be level 1")
|
||||
if is_summary and first_header_level != 2:
|
||||
violations.append("2.2 File Header Levels: SUMMARY.md first header must be level 2")
|
||||
if is_section and first_header_level != 2:
|
||||
violations.append("2.2 File Header Levels: Section file first header must be level 2")
|
||||
|
||||
# 2.2 No English Parentheses in Headers unless very common terminologies
|
||||
for h in headers:
|
||||
text = h['text']
|
||||
i = h['line']
|
||||
if re.search(r'([A-Za-z\s]+)', text):
|
||||
violations.append(f"2.2 No English Parentheses: Header at line {i+1} contains English in parentheses: {text}")
|
||||
|
||||
# 2.3 Single Child Headers
|
||||
for j in range(len(headers)):
|
||||
level = headers[j]['level']
|
||||
children = 0
|
||||
for k in range(j+1, len(headers)):
|
||||
if headers[k]['level'] <= level:
|
||||
break
|
||||
if headers[k]['level'] == level + 1:
|
||||
children += 1
|
||||
if children == 1:
|
||||
violations.append(f"2.3 Single Child Headers: Header at line {headers[j]['line']+1} has exactly 1 child")
|
||||
|
||||
# 2.5 Bridge Text
|
||||
for j in range(len(headers)):
|
||||
level = headers[j]['level']
|
||||
child_line = -1
|
||||
for k in range(j+1, len(headers)):
|
||||
if headers[k]['level'] <= level:
|
||||
break
|
||||
if headers[k]['level'] == level + 1:
|
||||
child_line = headers[k]['line']
|
||||
break
|
||||
if child_line != -1:
|
||||
text_between = '\n'.join([l.strip() for l in lines[headers[j]['line']+1:child_line] if l.strip()])
|
||||
if not text_between:
|
||||
violations.append(f"2.5 Bridge Text: Header at line {headers[j]['line']+1} is followed by a sub-header without introductory text")
|
||||
|
||||
# 3.2 Content Introduction
|
||||
in_code_block = False
|
||||
for j, line in enumerate(lines):
|
||||
if line.startswith('```'):
|
||||
if not in_code_block:
|
||||
for k in range(j-1, -1, -1):
|
||||
if lines[k].strip():
|
||||
if lines[k].startswith('#'):
|
||||
violations.append(f"3.2 Content Introduction: Code block at line {j+1} immediately follows a header")
|
||||
break
|
||||
in_code_block = not in_code_block
|
||||
elif ":
|
||||
if lines[k].strip():
|
||||
if lines[k].startswith('#'):
|
||||
violations.append(f"3.2 Content Introduction: Image at line {j+1} immediately follows a header")
|
||||
break
|
||||
|
||||
return violations
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-v', '--verbose', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
md_files = []
|
||||
for root, dirs, files in os.walk('.'):
|
||||
for f in files:
|
||||
if f.endswith('.md') and '.git' not in root and 'node_modules' not in root and '.vuepress' not in root and 'book_rule.md' not in f and 'rules_result.txt' not in f:
|
||||
md_files.append(os.path.join(root, f))
|
||||
|
||||
total_violations = 0
|
||||
for f in md_files:
|
||||
try:
|
||||
violations = check_file(f, args.verbose)
|
||||
if args.verbose:
|
||||
print(f"Scanned {f}")
|
||||
if violations:
|
||||
print(f"\\nViolations in {f}:")
|
||||
for v in violations:
|
||||
print(f" - {v}")
|
||||
total_violations += len(violations)
|
||||
except Exception as e:
|
||||
print(f"Error reading {f}: {e}")
|
||||
|
||||
if total_violations == 0:
|
||||
print("No violations found!")
|
||||
else:
|
||||
print(f"\\nTotal violations: {total_violations}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1 +0,0 @@
|
||||
Total issues found: 0
|
||||
Reference in New Issue
Block a user