vault backup: 2026-05-27 22:18:59
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
---
|
||||
tags:
|
||||
- CI/CD
|
||||
- Slidev
|
||||
- Docker
|
||||
- Nginx
|
||||
- Gitea Actions
|
||||
create time: 2026-05-27 19:30
|
||||
---
|
||||
|
||||
# CICD 在 Slidev 中的应用
|
||||
|
||||
## 概述
|
||||
|
||||
本文档记录使用 Gitea Actions 实现 Slidev 演示文稿项目的自动化构建与部署流程:代码推送到 `main` 分支后自动构建所有 deck,并将产物部署到基于 Docker 的 Nginx 容器。
|
||||
|
||||
> [!TIP] 本文件夹包含以下子文档:
|
||||
> - [[CICD 在 Slidev 中的应用/workflow-configuration]] — Workflow YAML 逐块详解(触发条件、运行环境、依赖管理)
|
||||
> - [[CICD 在 Slidev 中的应用/deployment-strategy]] — 部署架构深入分析(DooD 模式、Nginx 运作、设计决策)
|
||||
> - [[CICD 在 Slidev 中的应用/Docker-in-Container 部署模式详解]] — DooD vs SID vs DinD vs Kaniko 方案对比
|
||||
|
||||
## 项目背景
|
||||
|
||||
本项目是一个基于 [Slidev](https://sli.dev/) 的多 deck 演示文稿平台,使用 Markdown 编写幻灯片内容。项目中包含多个独立的演示文稿(每个称为一个 **deck**),统一由 `index.html` 的 landing page 进行导航分发。
|
||||
|
||||
**技术栈选型:**
|
||||
|
||||
| 组件 | 选择 | 理由 |
|
||||
|------|------|------|
|
||||
| 演示文稿框架 | Slidev | Markdown + Vue 驱动,适合技术分享场景 |
|
||||
| 代码托管 | Gitea(自部署) | 团队内部私有实例,支持 Actions 兼容语法 |
|
||||
| CI/Runner | Gitea Act Runner(阿里云) | 自建 runner,避免公有 cloud runner 的配额限制 |
|
||||
| 构建工具 | pnpm + Slidev build | 多 deck 批量构建,输出纯静态站点 |
|
||||
| 部署方式 | Docker DooD + Nginx Alpine | 无状态部署,每次重建保证干净环境 |
|
||||
|
||||
**为什么需要 CI/CD?**
|
||||
|
||||
Slidev 的演示文稿本质是静态站点,传统手动部署流程为:本地 `build` → 拷贝文件到服务器 → 重启 Nginx。这种方式的痛点在于:
|
||||
|
||||
- **人工遗漏**:新增 deck 后忘记重新构建或遗漏文件复制
|
||||
- **环境不一致**:本地构建产物可能在服务器上因依赖版本差异出现问题
|
||||
- **协作困难**:多人同时修改不同 deck 时无法自动化集成
|
||||
|
||||
通过 Gitea Actions + Docker 实现自动化后,开发者只需关注 `.md` 内容的编写和提交,构建、部署全流程自动完成——**push 即上线**。
|
||||
|
||||
## 正文
|
||||
|
||||
### CI/CD 流水线概览
|
||||
|
||||
对于一个 Slidev 项目,典型的发布流程可以拆解为以下几个阶段:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["📝 push 到 main"] --> B["Gitea Actions 触发"]
|
||||
B --> C["安装系统依赖"]
|
||||
C --> D["检出代码"]
|
||||
D --> E["安装 pnpm + 项目依赖"]
|
||||
E --> F["构建 slides"]
|
||||
F --> G["启动 Nginx 容器"]
|
||||
G --> H["复制产物到容器"]
|
||||
H --> I["网站上线"]
|
||||
```
|
||||
|
||||
### 架构总览
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "本地开发"
|
||||
DEV["开发者\n编辑 .md"] --> PUSH["git push → main"]
|
||||
end
|
||||
|
||||
subgraph "Gitea Runner (阿里云)"
|
||||
ACTION["Gitea Actions Runner"]
|
||||
BUILD["node:22-alpine 容器\n构建 slides → dist/"]
|
||||
end
|
||||
|
||||
subgraph "宿主机 Docker"
|
||||
NGINX["slides-nginx 容器\n8080 → 80"]
|
||||
HTML["/usr/share/nginx/html/\n← dist/ 内容"]
|
||||
end
|
||||
|
||||
USER["访客浏览器"]
|
||||
PUSH --> ACTION
|
||||
ACTION --> BUILD
|
||||
BUILD --> COPY["docker cp"]
|
||||
COPY --> NGINX
|
||||
NGINX --> HTML
|
||||
NGINX --> USER
|
||||
```
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[CICD 在 Slidev 中的应用/workflow-configuration]]
|
||||
- [[CICD 在 Slidev 中的应用/deployment-strategy]]
|
||||
- [[CICD 在 Slidev 中的应用/Docker-in-Container 部署模式详解]]
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
tags: [CI/CD, Docker, DooD, Deployment]
|
||||
create time: 2026-05-27 19:30
|
||||
---
|
||||
|
||||
# Docker-in-Container 部署模式详解
|
||||
|
||||
## 概述
|
||||
|
||||
当 CI/CD 流水线需要在容器化环境中启动和操控另一个容器时(例如 Gitea Actions 中构建产物后部署 Nginx),有两种主流方案:**DooD (Docker-out-of-Docker)** 与 **SID (Socket-In-Docker)**。本文对比两种模式的原理、配置和取舍。
|
||||
|
||||
## 核心问题
|
||||
|
||||
Workflow 的 `run:` 命令在某个容器(如 `node:22-alpine`)内执行,但你需要指挥宿主机上的 Docker daemon 来操作其他容器。问题是:**容器里没有 Docker,怎么发命令?**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph HOST["宿主机"]
|
||||
DAEMON["Docker Daemon\n(:2375 or /var/run/docker.sock)"]
|
||||
end
|
||||
|
||||
subgraph CONTAINER["workflow 运行容器\n(node:22-alpine)"]
|
||||
CLI["docker-cli ?"]
|
||||
end
|
||||
|
||||
DAEMON <-->|通信方式见下文| CLI
|
||||
|
||||
style DAEMON fill:#e8f4fd
|
||||
style CONTAINER fill:#fff4e8
|
||||
```
|
||||
|
||||
## 方案一:DooD — Docker-out-of-Docker
|
||||
|
||||
### 原理
|
||||
|
||||
在容器内部安装完整的 `docker` CLI 包,通过某种方式连接到宿主机的 Docker daemon。
|
||||
|
||||
```yaml
|
||||
# .github/workflows/example.yml
|
||||
steps:
|
||||
- name: Install Docker CLI
|
||||
run: |
|
||||
sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
apk add --no-cache docker-cli
|
||||
|
||||
- name: Build and Deploy
|
||||
run: |
|
||||
docker build -t myapp .
|
||||
docker run --name web nginx:alpine
|
||||
docker cp dist/. web:/usr/share/nginx/html/
|
||||
```
|
||||
|
||||
连接宿主机 daemon 的方式通常有两种:
|
||||
|
||||
| 方式 | 实现 | 特点 |
|
||||
|------|------|------|
|
||||
| 环境变量 + TCP | 宿主机暴露 `DOCKER_HOST=tcp://host.docker.internal:2375` | 需要开放端口,可能有安全风险 |
|
||||
| Socket 共享 + CLI 并存 | 挂载 `/var/run/docker.sock` 且容器内装了 docker-cli | 最常用,兼容性好 |
|
||||
|
||||
### 优点
|
||||
|
||||
- **功能完整** — 容器内有完整的 docker CLI,任何 `docker` 命令都能用
|
||||
- **调试友好** — 可以直接 `docker exec` 进入其他容器查日志
|
||||
- **无需额外配置** — runner 内置支持或简单环境变量即可
|
||||
|
||||
### 缺点
|
||||
|
||||
- **安装体积大** — docker-cli 本身约 40-60MB( Alpine 版本)
|
||||
- **权限较宽** — 通常需要加入 `docker` 用户组才能免 sudo 操作
|
||||
- **安全面更大** — 容器内能直接操控宿主机所有容器和资源
|
||||
|
||||
## 方案二:SID — Socket-In-Docker
|
||||
|
||||
### 原理
|
||||
|
||||
不安装任何 CLI,直接把宿主机的 Docker socket 文件挂载进容器。容器内的进程通过这个 socket 文件和宿主机 daemon 通信。
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: Deploy with socket mount
|
||||
# 关键:runner 配置中将 docker.sock 挂载进工作容器
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
run: |
|
||||
# 容器内不需要 docker,只需把 docker socket 挂载进来
|
||||
docker run -d --name web nginx:alpine # 直接用!
|
||||
docker cp dist/. web:/usr/share/nginx/html/
|
||||
```
|
||||
|
||||
在 Gitea Actions Runner 层面,需要通过环境变量或配置文件将 socket 挂载:
|
||||
|
||||
```bash
|
||||
# Runner 启动时的挂载参数
|
||||
docker run \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
node:22-alpine \
|
||||
pnpm build && docker run ...
|
||||
```
|
||||
|
||||
### 优点
|
||||
|
||||
- **镜像更轻** — 容器内不需要安装 docker-cli,节省空间和时间
|
||||
- **资源占用少** — 无额外二进制、无额外进程
|
||||
- **简洁干净** — workflow 文件看起来更像"普通脚本"
|
||||
|
||||
### 缺点
|
||||
|
||||
- **调试困难** — 没有独立 CLI,出错时排查路径更长
|
||||
- **安全性极低** — **这是致命缺陷**。拥有 `/var/run/docker.sock` 等价于拥有 root 权限(可轻易逃逸到宿主机)
|
||||
- **兼容性限制** — 某些需要完整 CLI 特性的操作可能不支持
|
||||
|
||||
### ⚠️ SID 安全警告
|
||||
|
||||
```
|
||||
mount /var/run/docker.sock → 容器内进程 = root@宿主机
|
||||
```
|
||||
|
||||
攻击者只需一行代码就能从容器逃逸到宿主机:
|
||||
|
||||
```bash
|
||||
docker run -v /:/host alpine chroot /host sh
|
||||
```
|
||||
|
||||
**结论:仅在完全可信的私有 CI/CD 环境中使用 SID,生产环境或有外部贡献者的项目必须用 DooD。**
|
||||
|
||||
## 方案对比总结
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Docker-in-Container 方案对比
|
||||
x-axis "低安全性" --> "高安全性"
|
||||
y-axis "轻量便捷" --> "功能强大"
|
||||
SID: [0.2, 0.8]
|
||||
"DooD (socket+CLI)": [0.6, 0.9]
|
||||
DinD (Daemon inside): [0.9, 0.4]
|
||||
Kaniko: [0.95, 0.3]
|
||||
```
|
||||
|
||||
| 维度 | DooD (CLI) | SID (Socket Only) | DinD (Daemon Inside) | Kaniko |
|
||||
|------|-----------|-------------------|---------------------|--------|
|
||||
| 镜像大小 | ~60MB 额外 | +0MB | ~80MB 额外 | 自包含 |
|
||||
| 调试体验 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
|
||||
| 安全性 | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| 配置复杂度 | 低 | 最低 | 高 | 低 |
|
||||
| 适用场景 | 通用推荐 | 纯可信私有环境 | 需要完整 Docker 特性 | 仅构建镜像 |
|
||||
|
||||
## 选择建议
|
||||
|
||||
| 你的场景 | 推荐方案 |
|
||||
|---------|---------|
|
||||
| Slidev 部署(当前项目) | DooD,简单够用 |
|
||||
| 多人在跑 CI 的开源项目 | DinD 或 Kaniko |
|
||||
| 极致追求镜像体积的私有环境 | SID(但要评估风险) |
|
||||
| 只需要构建镜像不部署 | Kaniko(无需 docker daemon) |
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[CICD 在 Slidev 中的应用]]
|
||||
- [[deployment-strategy]]
|
||||
- [[workflow-configuration]]
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
tags:
|
||||
- CI/CD
|
||||
- Docker
|
||||
- Nginx
|
||||
- 部署策略
|
||||
create time: 2026-05-27 19:30
|
||||
---
|
||||
|
||||
# 部署策略深入
|
||||
|
||||
## 概述
|
||||
|
||||
深入分析 Slidev 项目的部署架构,包括 Docker-out-of-Docker 通信模式、Nginx 静态文件服务机制、以及 Pipeline 步骤间的依赖关系。
|
||||
|
||||
> [!NOTE] 如需了解 Workflow YAML 逐块配置说明,详见 [[workflow-configuration]]。
|
||||
|
||||
## 正文
|
||||
|
||||
### 为什么需要 docker-cli?DooD vs SID
|
||||
|
||||
> [!QUESTION] 思考:workflow 已经在 `node:22-alpine` 容器里运行了,为什么还要装 Docker 相关工具?这不是"套娃"吗?
|
||||
|
||||
**关键理解**:你的命令在 **container A(构建容器)** 中执行,但你要操控的是 **宿主机的 Docker daemon** 来启动 container B(Nginx)。这是经典的 **Docker-out-of-Docker (DooD)** 模式。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
HOST["宿主机\nDocker Daemon"] <-->|通信| CLI["container A 中的\ndocker-cli 命令"]
|
||||
CLI --> OP["docker run nginx → 新建 container B"]
|
||||
|
||||
style HOST fill:#e8f4fd
|
||||
style CLI fill:#fff4e8
|
||||
style OP fill:#e8ffe8
|
||||
```
|
||||
|
||||
这比另一种方案 **SID (Socket-In-Docker)** 更安全:SID 直接把 `/var/run/docker.sock` 挂载进容器,等于给了 root 权限——攻击者一行代码就能逃逸到宿主机。而 DooD 通过独立的 docker-cli 通信,安全边界更清晰。
|
||||
|
||||
> [!TIP] 想深入了解两种方案的完整对比?详见 [[Docker-in-Container 部署模式详解]]。
|
||||
|
||||
### Nginx 作为静态文件服务器的运作方式
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
REQ["浏览器请求 :8080/index.html"] --> NGINX["Nginx 监听端口 80\n(容器内映射到宿主机 8080)"]
|
||||
NGINX --> FS["读取文件系统<br/>/usr/share/nginx/html/"]
|
||||
FS --> FILES["HTML / CSS / JS / 图片<br/>(来自 dist/ 的构建产物)"]
|
||||
FILES --> RESP["返回给浏览器渲染"]
|
||||
|
||||
style REQ fill:#e8f4fd
|
||||
style RESP fill:#e8ffe8
|
||||
```
|
||||
|
||||
核心机制其实非常简单——**Nginx 不做任何处理,只是当个"搬运工"**:
|
||||
|
||||
| 概念 | 说明 |
|
||||
|------|------|
|
||||
| 为什么选 Nginx | 业界标准、极致轻量(alpine 版仅 ~4MB)、零配置开箱即用 |
|
||||
| 为什么每次重建 | 无状态设计:删掉旧容器 = 彻底清除历史,避免残留配置或缓存问题 |
|
||||
| 为什么用 `docker cp` | `cp dist/. nginx:/usr/share/nginx/html/` 把构建产物直接铺进容器的网页根目录 |
|
||||
| 为什么不重启 Nginx | `cp` 操作不涉及进程热重载,Nginx 自动读最新文件 |
|
||||
|
||||
关键参数 `-p 8080:80`:容器内 Nginx 监听 80 端口,将其映射到宿主机的 8080。访客访问 `http://服务器IP:8080` 即可看到 slides。这里没有直接用 80 端口,是为了避免和宿主机上其他可能的 HTTP 服务冲突。
|
||||
|
||||
### Workflow 步骤顺序:谁保证了执行的先后?
|
||||
|
||||
答案很直接:**YAML 中 `steps` 的顺序就是执行顺序**。Gitea Actions(与 GitHub Actions 一致)对每个 job 内的 steps 按从上到下 **串行执行**,且某一步失败后后续步骤自动跳过。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
S1[Install tools] --> S2[Checkout]
|
||||
S2 --> S3[Install pnpm]
|
||||
S3 --> S4[pnpm install]
|
||||
S4 --> S5[Build]
|
||||
S5 --> S6[Deploy]
|
||||
|
||||
S1 -.→|.git (代码可用)| S2
|
||||
S2 -.→|pnpm CLI| S3
|
||||
S3 -.→|node_modules/| S4
|
||||
S4 -.→|dist/ 产物| S5
|
||||
S5 -.→|dist/ 产物| S6
|
||||
|
||||
style S1 fill:#fff4e8
|
||||
style S2 fill:#fff4e8
|
||||
style S3 fill:#fff4e8
|
||||
style S4 fill:#fff4e8
|
||||
style S5 fill:#fff4e8
|
||||
style S6 fill:#e8ffe8
|
||||
```
|
||||
|
||||
每一步的输出自然成为下一步的输入——不需要额外的依赖声明,文件系统天然承载了这一步到那一步的数据传递。这正是 CI/CD pipeline "线性流水线"的设计哲学。
|
||||
|
||||
### 部署命令拆解
|
||||
|
||||
```yaml
|
||||
- name: Deploy to nginx
|
||||
run: |
|
||||
docker rm -f slides-nginx 2>/dev/null || true
|
||||
docker run -d \
|
||||
--name slides-nginx \
|
||||
--restart unless-stopped \
|
||||
-p 8080:80 \
|
||||
nginx:alpine
|
||||
docker cp dist/. slides-nginx:/usr/share/nginx/html/
|
||||
```
|
||||
|
||||
这一阶段做了三件事:
|
||||
|
||||
1. **清理旧容器** — `docker rm -f` 强制删除可能存在的同名容器,`2>/dev/null || true` 确保不存在时不报错退出
|
||||
2. **启动新容器** — 基于 `nginx:alpine` 镜像创建轻量级 web 服务器
|
||||
3. **复制构建产物** — 将 `dist/` 下的内容拷贝到 Nginx 的默认网页目录 `/usr/share/nginx/html/`
|
||||
|
||||
### 关键设计决策
|
||||
|
||||
| 决策 | 选择 | 理由 |
|
||||
|------|------|------|
|
||||
| 包管理器 | pnpm | 依赖安装速度快,磁盘占用小 |
|
||||
| 容器基础镜像 | Alpine | 镜像仅几 MB,适合快速构建 |
|
||||
| Web 服务器 | Nginx (每次重建) | 无状态、零配置,构建产物即静态文件 |
|
||||
| 端口映射 | 8080:80 | 避免宿主机占用标准 HTTP 端口 80 |
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[CICD 在 Slidev 中的应用]]
|
||||
- [[workflow-configuration]]
|
||||
- [[Docker-in-Container 部署模式详解]]
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
tags:
|
||||
- CI/CD
|
||||
- Gitea Actions
|
||||
- Slidev
|
||||
create time: 2026-05-27 19:30
|
||||
---
|
||||
|
||||
# Workflow 配置详解
|
||||
|
||||
## 概述
|
||||
|
||||
本文档对 `CICD 在 Slidev 中的应用` 中的 Workflow YAML 进行逐块解析,涵盖从触发条件到构建部署的每一步配置逻辑。
|
||||
|
||||
> [!NOTE] 如需了解部署架构和容器通信模式,详见 [[deployment-strategy]]。
|
||||
|
||||
## 正文
|
||||
|
||||
### 1. 触发条件
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
```
|
||||
|
||||
向 `main` 分支推送代码或提交 PR 时都会触发。这意味着开发者通过特性分支提 PR,CI 会先验证构建是否通过——只有合并到主干后才执行部署。
|
||||
|
||||
> [!TIP] PR 事件只适合跑验证性步骤(build、lint),实际部署仍保留在 `push: main` 中,因为 PR 上下文通常没有写入权限。如果 Gitea Actions 的 PR 事件无法读取 secret,可改用 `pull_request_target`,但需避免直接使用 PR 代码中的敏感操作以防止 token 泄露。
|
||||
|
||||
> [!QUESTION] 思考:如果你的 Slidev 项目需要同时支持 staging 和 production 环境,你会如何设计触发规则?
|
||||
|
||||
### 2. 运行环境与容器
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: aliyun
|
||||
container: node:22-alpine
|
||||
```
|
||||
|
||||
- **`runs-on: aliyun`** — 使用了自定义的 Gitea runner,运行在一台阿里云机器上(而非官方 GitHub-hosted runner)。
|
||||
- **`container: node:22-alpine`** — 在 Alpine Linux 上的 Node.js 22 容器中执行,体积小、启动快,适合纯前端构建场景。
|
||||
|
||||
### 3. 安装系统工具
|
||||
|
||||
```yaml
|
||||
- name: Install tools
|
||||
run: |
|
||||
sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
apk add --no-cache docker-cli git
|
||||
```
|
||||
|
||||
Alpine 默认的软件源在海外,先用 `sed` 替换为阿里云镜像源加速下载。然后安装三个必要的工具:
|
||||
|
||||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| `docker-cli` | 指挥宿主机 Docker daemon,管理其他容器的生命周期 |
|
||||
| `git` | 版本控制操作所需 |
|
||||
| `apk` | Alpine 的包管理器(已内置) |
|
||||
|
||||
> [!NOTE] 这里只安装了 `docker-cli`(客户端),没有安装完整的 `docker`。完整的 Docker 包包含 daemon 本身(约 80MB),而我们需要的是轻量级 CLI(约 50MB on Alpine)——因为 daemon 已经在宿主机上运行了。
|
||||
|
||||
### 4. 检出代码 (Checkout)
|
||||
|
||||
```yaml
|
||||
- name: Checkout
|
||||
uses: http://47.121.181.112:3000/wonder/checkout@v4
|
||||
with:
|
||||
github-server-url: http://47.121.181.112:3000
|
||||
```
|
||||
|
||||
使用私有 Gitea 实例的 checkout action,指向地址 `http://47.121.181.112:3000`。通过 `github-server-url` 参数告知 action 正确的 Gitea 服务器地址。
|
||||
|
||||
#### Checkout 到底做了什么?
|
||||
|
||||
很多开发者误以为 workflow 启动后工作目录里已经有代码了——实际上 runner 初始环境是**空的**。Checkout action 会在幕后执行一系列 git 操作:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Runner 启动<br/>空工作目录"] --> B["git clone 仓库"]
|
||||
B --> C["git fetch 指定 commit 的引用"]
|
||||
C --> D["git reset --hard 精确 SHA"]
|
||||
D --> E[".git 隐藏目录<br/>pnpm install 可用"]
|
||||
```
|
||||
|
||||
| 步骤 | Git 命令等价物 | 作用 |
|
||||
|------|---------------|------|
|
||||
| Clone | `git clone <repo-url>` | 拉取全量历史到 action 工作区 |
|
||||
| Fetch | `git fetch origin <commit-sha>` | 获取触发 workflow 的具体 commit 引用 |
|
||||
| Reset | `git reset --hard <commit-sha>` | 将工作树精确还原到目标提交(而非默认分支最新) |
|
||||
|
||||
> [!TIP] PR 场景下尤其要注意:如果 workflow 被 pull_request 事件触发,Checkout 会自动检出 **PR 合并后的结果**(即 `refs/pull/<number>/merge`),而不是 PR 分支本身。这确保了 CI 验证的是"合并后能否构建",而非"分支本身能否构建"。
|
||||
|
||||
#### 常用配置参数
|
||||
|
||||
实际项目中经常需要调整 checkout 的行为,以下是高频参数及适用场景:
|
||||
|
||||
| 参数 | 默认值 | 用途 |
|
||||
|------|--------|------|
|
||||
| `ref` | 触发 workflow 的 commit SHA | 指定检出的分支、标签或 commit |
|
||||
| `clean` | `true` | 检出前是否清理工作目录残留文件 |
|
||||
| `persist-credentials` | `true` | 是否在后续步骤中保持 git 认证信息 |
|
||||
| `fetch-depth` | `1` | 只拉取最近一次提交(fast clone) |
|
||||
|
||||
> [!QUESTION] 什么时候应该设置 `fetch-depth: 0`?
|
||||
>
|
||||
> 答案:当你需要完整的 git 历史时——比如用 `git log` 生成 changelog、执行语义化版本计算 (`conventional-changelog`),或者需要 `git blame` 追溯代码来源。`fetch-depth: 1` 是默认值,因为它更快更省空间;只有确实需要历史记录时才改为 `0`。
|
||||
|
||||
> [!WARNING] 安全注意:CI 中的 checkout 会拿到完整的 repo token。如果 workflow 包含不可信的第三方 action(如开源社区提供的 step),建议设置 `persist-credentials: false`,防止 token 被泄露给非官方代码。
|
||||
|
||||
|
||||
### 5. 安装依赖与构建
|
||||
|
||||
```yaml
|
||||
- name: Install pnpm
|
||||
run: npm install -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build all decks
|
||||
run: pnpm run build
|
||||
```
|
||||
|
||||
三步完成从包管理到构建的过程:
|
||||
|
||||
1. **全局安装 pnpm** — 容器初始状态只有 Node.js,需要先装包管理器
|
||||
2. **安装项目依赖** — `--frozen-lockfile` 确保依赖版本精确锁定(详见下方说明)
|
||||
3. **执行构建** — `pnpm run build` 对应 `slidev build`,将所有 `.md` deck 编译为静态站点
|
||||
|
||||
> [!TIP] Slidev 的 `build` 命令输出目录默认为 `dist/`,每个 slide deck 一个子目录。这就是为什么最后一步可以直接 `cp dist/. nginx:/usr/share/nginx/html/`。
|
||||
|
||||
#### --frozen-lockfile 的 CI 哲学
|
||||
|
||||
```bash
|
||||
# 不加参数:lockfile 被悄悄更新 → 下次构建可能用不同版本
|
||||
pnpm install
|
||||
|
||||
# 加 --frozen-lockfile:严格一致,否则立即失败
|
||||
pnpm install --frozen-lockfile
|
||||
```
|
||||
|
||||
| 场景 | 不加 `--frozen-lockfile` | 加上 `--frozen-lockfile` |
|
||||
|------|------------------------|-------------------------|
|
||||
| lockfile 正常 | ✅ 安装 | ✅ 安装 |
|
||||
| lockfile 缺失 | 🔧 自动生成一个新的 | ❌ **报错退出** |
|
||||
| lockfile 与 package.json 不匹配 | 🔧 悄悄更新 lockfile | ❌ **报错退出** |
|
||||
|
||||
CI 环境追求的是 **构建可复现性 (Build Reproducibility)**。同一个 commit,不同的人、不同的时间、不同的机器上构建出来的结果必须完全一致——否则线上 bug 将无法定位是"代码问题"还是"环境差异"。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["开发者修改 package.json"] --> B{"push"}
|
||||
B --> C["CI: frozen-lockfile 检查"]
|
||||
C -->|lockfile 已同步| D["✅ 构建通过"]
|
||||
C -->|lockfile 未同步| E["❌ 立即失败<br/>提示手动运行 pnpm install"]
|
||||
|
||||
style E fill:#ffe8e8
|
||||
style D fill:#e8ffe8
|
||||
```
|
||||
|
||||
> [!TIP] 正确的工作流:开发者改完依赖 → `pnpm install` 自动更新 lockfile → 同时提交两个文件。CI 只负责验证一致性,不负责修锁。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[CICD 在 Slidev 中的应用]]
|
||||
- [[deployment-strategy]]
|
||||
Reference in New Issue
Block a user