Fix space with bold markdown

This commit is contained in:
Baohua Yang
2026-02-21 17:39:37 -08:00
parent e69a140cc9
commit b9c9efe02b
64 changed files with 148 additions and 301 deletions
+4
View File
@@ -17,3 +17,7 @@ docker-compose.override.yml
.vscode/
.agent/
__pycache__/
# Check scripts
check_project_rules.py
+3 -3
View File
@@ -10,7 +10,7 @@
具体内容如下
```
```bash
周五下午 5:00
├── 开发者:代码写完了,本地测试通过,提交!🎉
├── 周一早上 9:00
@@ -30,7 +30,7 @@
具体内容如下
```
```bash
新同事入职
├── Day 1:领电脑,配环境
├── Day 2:继续配环境,遇到问题
@@ -43,7 +43,7 @@
具体内容如下
```
```bash
运维:"我们需要把服务迁移到新服务器"
开发:"旧服务器上的配置文档在哪?"
运维:"当时是一个已经离职的同事配的……"
+1 -1
View File
@@ -99,7 +99,7 @@ flowchart TD
2. 在容器层中进行修改
3. 原始镜像层保持不变
```
```bash
读取文件:直接从镜像层读取(共享,高效)
修改文件:复制到容器层,然后修改(只有这个容器能看到修改)
```
+2 -2
View File
@@ -55,7 +55,7 @@ flowchart TB
一个完整的 Docker 镜像名称由 Registry 地址用户名/组织名仓库名和标签组成了解其结构有助于我们更准确地定位镜像基本格式如下
```
```bash
[registry地址/][用户名/]仓库名[:标签]
```
@@ -190,7 +190,7 @@ $ docker pull localhost:5000/myapp:v1.0
如图 2-3 所示镜像从开发环境构建后推送到 Registry再由生产环境拉取并运行
```
```bash
开发者机器 Registry 生产服务器
│ │ │
│ docker build │ │
+5 -5
View File
@@ -26,21 +26,21 @@ $ brew install --cask docker
如同 macOS 其它软件一样安装也非常简单双击下载的 `.dmg` 文件然后将那只叫 [Moby](https://www.docker.com/blog/call-me-moby-dock/) 的鲸鱼图标拖拽到 `Application` 文件夹即可 (其间需要输入用户密码)。
![](../_images/install-mac-dmg.png)
![](../_images/install-mac-dmg.png)
### 运行
从应用中找到 Docker 图标并点击运行
![](../_images/install-mac-apps.png)
![](../_images/install-mac-apps.png)
运行之后会在右上角菜单栏看到多了一个鲸鱼图标这个图标表明了 Docker 的运行状态
![](../_images/install-mac-menubar.png)
![](../_images/install-mac-menubar.png)
每次点击鲸鱼图标会弹出操作菜单
![](../_images/install-mac-menu.png)
![](../_images/install-mac-menu.png)
之后你可以在终端通过命令检查安装后的 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 安装成功了。
![](../_images/install-mac-example-nginx.png)
![](../_images/install-mac-example-nginx.png)
要停止 Nginx 服务器并删除执行下面的命令
+2 -2
View File
@@ -28,11 +28,11 @@ $ winget install Docker.DockerDesktop
Windows 搜索栏输入 **Docker** 点击 **Docker Desktop** 开始运行
![](../_images/install-win-docker-app-search.png)
![](../_images/install-win-docker-app-search.png)
Docker 启动之后会在 Windows 任务栏出现鲸鱼图标
![](../_images/install-win-taskbar-circle.png)
![](../_images/install-win-taskbar-circle.png)
等待片刻当鲸鱼图标静止时说明 Docker 启动成功之后你可以打开 PowerShell 使用 Docker
-4
View File
@@ -91,10 +91,6 @@ Kubernetes 社区已将官方镜像地址从 `k8s.gcr.io` 迁移到 `registry.k8
一般情况下有如下对应关系
```bash
## 旧地址已迁移
## $ docker pull k8s.gcr.io/xxx
## 新地址推荐
$ docker pull registry.k8s.io/xxx
```
+1 -1
View File
@@ -14,7 +14,7 @@ docker pull [选项] [Registry地址/]仓库名[:标签]
Docker 镜像名称由 Registry 地址用户名仓库名和标签组成其标准格式如下
```
```bash
docker.io / library / ubuntu : 24.04
+1 -1
View File
@@ -58,7 +58,7 @@ Docker 镜像的大小可能与我们通常理解的文件大小有所不同,
由于镜像是分层存储不同镜像可能共享相同的层
```
```bash
ubuntu:24.04 nginx:latest redis:latest
+2 -2
View File
@@ -20,7 +20,7 @@ $ docker run --name webserver -d -p 80:80 nginx
直接用浏览器访问的话我们会看到默认的 Nginx 欢迎页面
![](../_images/images-mac-example-nginx.png)
![](../_images/images-mac-example-nginx.png)
现在假设我们非常不喜欢这个欢迎页面我们希望改成欢迎 Docker 的文字我们可以使用 `docker exec` 命令进入容器修改其内容
@@ -37,7 +37,7 @@ exit
现在我们再刷新浏览器的话会发现内容被改变了
![](../_images/images-create-nginx-docker.png)
![](../_images/images-create-nginx-docker.png)
我们修改了容器的文件也就是改动了容器的存储层我们可以通过 `docker diff` 命令看到具体的改动
+1 -1
View File
@@ -47,7 +47,7 @@ RUN echo "hello" > world.txt # 文件在根目录!
如下代码块所示展示了相关示例
```
```dockerfile
RUN cd /app
启动容器 cd /app仅内存变化 提交镜像层 容器销毁
+1 -1
View File
@@ -26,7 +26,7 @@ HEALTHCHECK NONE
**引入 HEALTHCHECK **
Docker 定期执行指定的检查命令根据返回值判断容器是否 健康
```
```bash
容器状态转换
Starting 成功> Healthy 失败N次> Unhealthy
+2 -2
View File
@@ -64,7 +64,7 @@ FROM my-node-base
如下代码块所示展示了相关示例
```
```bash
基础镜像构建
Dockerfile ( ONBUILD) build> 基础镜像 (记录了 ONBUILD 触发器)
(指令未执行)
@@ -147,7 +147,7 @@ ONBUILD COPY dist/ /usr/share/nginx/html/
建议在镜像标签中添加 `-onbuild` 后缀明确告知使用者该镜像包含触发器
```
```bash
node:20-onbuild
python:3.12-onbuild
```
+1 -1
View File
@@ -60,7 +60,7 @@ COPY src/ /app/src/
> **注意**复制目录时复制的是目录的 **内容**不包含目录本身
```
```bash
构建上下文 镜像内
src/ /app/src/
index.js index.js
+1 -1
View File
@@ -68,7 +68,7 @@ ADD ubuntu-noble-core-cloudimg-amd64-root.tar.gz /
如下代码块所示展示了相关示例
```
```bash
ADD app.tar.gz /app/
识别 .tar.gz 格式
+2 -2
View File
@@ -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"]
+2 -2
View File
@@ -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
+1 -1
View File
@@ -26,7 +26,7 @@ ARG <参数名>[=<默认值>]
| **适用场景** | 构建参数 (版本号等)| 应用配置 |
| **可见性** | `docker history` 可见 | `docker inspect` 可见 |
```
```dockerfile
构建时 运行时
ARG VERSION=1.0 ARG 已消失
ENV APP_ENV=prod APP_ENV=prod仍存在
+1
View File
@@ -58,6 +58,7 @@ $ docker run --dns-search=example.com myapp
```
#### 3. --hostname (-h)
设置容器的主机名
```bash
-4
View File
@@ -324,7 +324,3 @@ services:
* `--no-up` 不自动启动服务
* `--quiet` 静默模式
### 参考资料
* [官方文档](https://docs.docker.com/compose/reference/overview/)
-5
View File
@@ -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)
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -8,7 +8,7 @@ WordPress 是全球最流行的内容管理系统 (CMS)。使用 Docker Compose
如下代码块所示展示了相关示例
```
```bash
wordpress/
compose.yaml
.env # 环境变量敏感信息
+1
View File
@@ -399,6 +399,7 @@ $ cosign verify --key cosign.pub $IMAGE
```
### 3. SLSA (Supply-chain Levels for Software Artifacts)
遵循 SLSA 框架确保构建过程的完整性例如使用 GitHub Actions 等受控环境进行构建而非在开发者本地机器上构建发布
---
+1 -1
View File
@@ -6,7 +6,7 @@
`etcdctl` 支持如下的命令大体上分为数据库操作和非数据库操作两类后面将分别进行解释
```
```bash
NAME:
etcdctl - A simple command line client for etcd3.
+1 -1
View File
@@ -2,7 +2,7 @@
[Kubernetes Dashboard](https://github.com/kubernetes/dashboard) 是基于网页的 Kubernetes 用户界面。
![](https://d33wubrfki0l68.cloudfront.net/349824f68836152722dab89465835e604719caea/6e0b7/images/docs/ui-dashboard.png)
![](https://d33wubrfki0l68.cloudfront.net/349824f68836152722dab89465835e604719caea/6e0b7/images/docs/ui-dashboard.png)
### 部署
+1 -1
View File
@@ -6,7 +6,7 @@
Docker Desktop 设置页面点击 `Kubernetes`选择 `Enable Kubernetes`稍等片刻看到左下方 `Kubernetes` 变为 `running`Kubernetes 启动成功
![](https://github.com/docker/docs/raw/main/assets/images/desktop/settings-kubernetes.png)
![](https://github.com/docker/docs/raw/main/assets/images/desktop/settings-kubernetes.png)
> 注意Kubernetes 的镜像存储在 `registry.k8s.io`如果国内网络无法直接访问可以在 Docker Desktop 配置中的 `Docker Engine` 处配置镜像加速器或者利用国内云服务商的镜像仓库手动拉取镜像并 retag
+1 -1
View File
@@ -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
```
+1 -1
View File
@@ -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
+9 -6
View File
@@ -27,11 +27,11 @@ graph LR
Docker 的内部架构如同洋葱一样分层每一层专注解决特定问题
#### 1Docker CLI (客户端)
#### 1. Docker CLI (客户端)
用户与 Docker 交互的主要方式它将用户命令 ( `docker run`) 转换为 API 请求发送给 dockerd
#### 2Dockerd (守护进程)
#### 2. Dockerd (守护进程)
Docker 的大脑
@@ -39,7 +39,7 @@ Docker 的大脑。
- 管理 Docker 对象 (镜像容器网络)
- 编排下层组件完成工作
#### 3Containerd (高级运行时)
#### 3. Containerd (高级运行时)
行业标准的容器运行时 (CNCF 毕业项目)
@@ -48,7 +48,7 @@ Docker 的大脑。
- **不包含** 复杂的与容器无关的功能 (如构建API)
- Kubernetes 也可以直接使用 containerd (跳过 Docker)
#### 4Runc (低级运行时)
#### 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 和生命周期监控
+1 -1
View File
@@ -124,7 +124,7 @@ MNT Namespace 负责文件系统挂载点的隔离,确保容器看到独立的
如下代码块所示展示了相关示例
```
```bash
宿主机文件系统 容器内看到的
/ / 容器的根目录
bin/ bin/
+1 -1
View File
@@ -88,7 +88,7 @@ $ git push origin master
打开我们部署好的 `Drone` 网站或者 Drone Cloud即可看到构建结果
![](../../../_images/drone-build.png)
![](../../../_images/drone-build.png)
当然我们也可以把构建结果上传到 GitHubDocker Registry云服务商提供的对象存储或者生产环境中
+1 -1
View File
@@ -18,7 +18,7 @@
登录 GitHub https://github.com/settings/applications/new 新建一个应用。
![](https://docs.drone.io/screenshots/github_application_create.png)
![](https://docs.drone.io/screenshots/github_application_create.png)
接下来查看这个应用的详情记录 `Client ID` `Client Secret`之后配置 Drone 会用到
+1 -1
View File
@@ -7,7 +7,7 @@
1. 图号格式统一为`图 <章号>-<序号> <图题>`例如 `图 10-2 Rails + PostgreSQL 的 Compose 架构`
2. 图号在同一章内按出现顺序连续编号不重复不跳号
3. 正文引用图片统一写法`如图 <章号>-<序号> 所示`不使用 下图/上图/示意图如下
4. 所有图片必须提供有意义的 alt 文本不使用空 alt (`![](...)`)
4. 所有图片必须提供有意义的 alt 文本不使用空 alt (`![](...)`)
5. 图题单独成行放在图片下方
### 章节风格规范
+1 -1
View File
@@ -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% /
+3 -3
View File
@@ -2,9 +2,9 @@
下图直观地展示了本节内容
[![](https://img.shields.io/github/stars/yeasy/docker_practice.svg?style=social&label=Stars)](https://github.com/yeasy/docker_practice) [![](https://img.shields.io/github/release/yeasy/docker_practice/all.svg)](https://github.com/yeasy/docker_practice/releases) [![](https://img.shields.io/badge/Based-Docker%20Engine%20v29.x-blue.svg)](https://docs.docker.com/engine/release-notes/) [![](https://img.shields.io/badge/Docker%20%E6%8A%80%E6%9C%AF%E5%85%A5%E9%97%A8%E4%B8%8E%E5%AE%9E%E6%88%98-jd.com-red.svg)][1]
[![](https://img.shields.io/github/stars/yeasy/docker_practice.svg?style=social&label=Stars)](https://github.com/yeasy/docker_practice) [![](https://img.shields.io/github/release/yeasy/docker_practice/all.svg)](https://github.com/yeasy/docker_practice/releases) [![](https://img.shields.io/badge/Based-Docker%20Engine%20v29.x-blue.svg)](https://docs.docker.com/engine/release-notes/) [![](https://img.shields.io/badge/Docker%20%E6%8A%80%E6%9C%AF%E5%85%A5%E9%97%A8%E4%B8%8E%E5%AE%9E%E6%88%98-jd.com-red.svg)][1]
**v1.5.5**
**v1.5.6**
[Docker](https://www.docker.com) 是个划时代的开源项目,它彻底释放了计算虚拟化的威力,极大提高了应用的维护效率,降低了云计算应用开发的成本!使用 Docker,可以让应用的部署、测试和分发都变得前所未有的高效和轻松!
@@ -78,7 +78,7 @@ npx honkit serve
下图直观地展示了本节内容
[![](https://github.com/yeasy/docker_practice/raw/master/_images/docker_primer4.jpg)][1]
[![](https://github.com/yeasy/docker_practice/raw/master/_images/docker_primer4.jpg)][1]
[Docker 技术入门与实战][1]已更新到第 4 讲解最新容器技术栈知识欢迎大家阅读并反馈建议
-148
View File
@@ -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 "![" in line and "](" in line:
for k in range(j-1, -1, -1):
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
View File
@@ -1 +0,0 @@
Total issues found: 0