<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Edward Chen</title><description>构建事物的笔记</description><link>https://edwardchen.com/</link><item><title>从零搭建 Astro 个人博客</title><link>https://edwardchen.com/blog/astro-blog-setup/</link><guid isPermaLink="true">https://edwardchen.com/blog/astro-blog-setup/</guid><description>使用 Astro + TailwindCSS 构建现代化静态博客，实现内容集合、置顶排序、时间线导航等最佳实践。</description><pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate><content:encoded>
# 从零搭建 Astro 个人博客

本文记录使用 Astro 构建个人博客的完整流程，涵盖项目初始化、内容管理、交互优化、组件抽象等核心环节，并总结实际开发中的最佳实践。

---

## 一、技术选型

### 为什么选择 Astro？

Astro 是专为内容驱动型网站设计的现代 Web 框架，核心优势包括：

1. **零 JavaScript 输出** — 默认不发送 JS，按需 hydration，性能极致
2. **静态生成优先** — 预渲染 HTML，SEO 友好
3. **多框架兼容** — 可混用 React、Vue、Svelte 组件
4. **Markdown 原生支持** — Content Collections 提供类型安全的内容管理

### 技术栈

| 模块 | 技术选型 | 理由 |
|------|----------|------|
| 框架 | Astro 4.x | 静态生成，零 JS 输出 |
| 样式 | TailwindCSS | 原子化 CSS，开发效率高 |
| 内容 | Markdown + Content Collections | 类型安全，写作体验佳 |
| 部署 | Cloudflare Pages | 全球 CDN，自动 HTTPS，免费 |
| 版本控制 | GitHub | 免费托管，CI/CD 集成 |

---

## 二、项目初始化

### 创建项目

```bash
npm create astro@latest my-blog
# 或
npx create-astro@latest my-blog
```

推荐选项：
- 使用空模板（Empty）
- 启用 TypeScript
- 添加 TailwindCSS 集成

### 安装 TailwindCSS

```bash
npm install -D @astrojs/tailwind tailwindcss
```

`astro.config.mjs`:

```js
import { defineConfig } from &apos;astro/config&apos;;
import tailwind from &apos;@astrojs/tailwind&apos;;

export default defineConfig({
  integrations: [tailwind()],
});
```

---

## 三、内容集合（Content Collections）

### 1. 定义 Schema

`src/content/config.ts`:

```ts
import { defineCollection, z } from &apos;astro:content&apos;;

const blogCollection = defineCollection({
  type: &apos;content&apos;,
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.date(),
    tags: z.array(z.string()).default([]),
    pinned: z.boolean().default(false),
  }),
});

export const collections = {
  &apos;blog&apos;: blogCollection,
};
```

**Schema 设计要点：**
- `title` 和 `description` 为必填字段
- `pubDate` 使用 `z.date()` 自动解析 ISO 字符串
- `tags` 默认为空数组，避免未定义错误
- `pinned` 控制文章置顶，默认 `false`

### 2. 创建文章

`src/content/blog/your-post.md`:

```md
---
title: &quot;文章标题&quot;
description: &quot;文章摘要，用于列表页展示&quot;
pubDate: 2026-05-14
tags: [&quot;技术&quot;, &quot;笔记&quot;]
pinned: true
---

# 正文

这里是文章内容...
```

**Frontmatter 规范：**
- 日期格式：`YYYY-MM-DD`（ISO 8601）
- 标签数组：使用双引号，避免解析错误
- 置顶标识：`pinned: true` 即可

---

## 四、核心功能实现

### 4.1 Tag 展示与筛选

#### 问题描述

初始实现中，tag 标签挤在一起显示（如 &quot;AI 自动化生产力&quot;），缺乏间距和交互反馈。

#### 设计目标

1. 视觉：Stone 色系，适当间距，悬停微交互
2. 交互：点击筛选，URL 同步，浏览器导航支持
3. 无障碍：`aria-label`，键盘可访问

#### 实现方案

**单篇文章页** (`src/pages/blog/[slug].astro`):

```astro
&lt;div class=&quot;row gap-2 flex-wrap&quot;&gt;
  {post.data.tags.map((tag) =&gt; (
    &lt;a href={`/blog?tag=${encodeURIComponent(tag)}`} 
       class=&quot;pill text-xs text-stone-600 bg-stone-100 hover:bg-stone-200 hover:text-stone-900 transition-colors px-2.5 py-0.5&quot;
       aria-label={`查看 ${tag} 标签的文章`}&gt;
      #{tag}
    &lt;/a&gt;
  ))}
&lt;/div&gt;
```

**关键设计：**
- `gap-2 flex-wrap`：间距 + 换行
- Stone 色系：`text-stone-600 bg-stone-100`
- 微交互：`hover:bg-stone-200 transition-colors`
- 标签前缀：`#` 符号
- 可点击：`&lt;a&gt;` 标签跳转列表页

**博客列表页** (`src/pages/blog.astro`):

```astro
&lt;!-- Tag 筛选按钮 --&gt;
&lt;div class=&quot;tags&quot;&gt;
  &lt;button class=&quot;tag&quot; data-tag=&quot;all&quot;&gt;
    全部 &lt;span class=&quot;ct&quot;&gt;({posts.length})&lt;/span&gt;
  &lt;/button&gt;
  {allTags.map((tag) =&gt; (
    &lt;button class=&quot;tag&quot; data-tag={tag}&gt;
      {tag} &lt;span class=&quot;ct&quot;&gt;({tagCounts[tag]})&lt;/span&gt;
    &lt;/button&gt;
  ))}
&lt;/div&gt;

&lt;!-- 文章列表 --&gt;
&lt;ol class=&quot;posts&quot; id=&quot;posts-list&quot;&gt;
  {posts.map((post) =&gt; (
    &lt;li class=&quot;post&quot; data-tags={post.data.tags.join(&apos;,&apos;)}&gt;
      &lt;!-- 文章内容 --&gt;
    &lt;/li&gt;
  ))}
&lt;/ol&gt;
```

**客户端筛选逻辑** (`&lt;script is:inline&gt;`):

```javascript
(function() {
  const tagButtons = document.querySelectorAll(&apos;.tag&apos;);
  const posts = document.querySelectorAll(&apos;#posts-list .post&apos;);
  const urlParams = new URLSearchParams(window.location.search);
  const selectedTag = urlParams.get(&apos;tag&apos;);

  // 初始化状态
  if (selectedTag) {
    tagButtons.forEach(btn =&gt; {
      btn.classList.toggle(&apos;on&apos;, btn.dataset.tag === selectedTag);
    });
    filterPosts(selectedTag);
  } else {
    tagButtons[0]?.classList.add(&apos;on&apos;);
  }

  // 点击筛选
  tagButtons.forEach(btn =&gt; {
    btn.addEventListener(&apos;click&apos;, () =&gt; {
      const tag = btn.dataset.tag;
      
      // 更新按钮状态
      tagButtons.forEach(b =&gt; b.classList.remove(&apos;on&apos;));
      btn.classList.add(&apos;on&apos;);

      // 更新 URL（不刷新）
      const newUrl = tag === &apos;all&apos; 
        ? &apos;/blog&apos; 
        : &apos;/blog?tag=&apos; + encodeURIComponent(tag);
      history.pushState({ tag: tag }, &apos;&apos;, newUrl);

      // 筛选文章
      filterPosts(tag);
    });
  });

  // 筛选函数
  function filterPosts(tag) {
    posts.forEach(post =&gt; {
      const postTags = post.dataset.tags.split(&apos;,&apos;);
      post.style.display = (tag === &apos;all&apos; || postTags.includes(tag)) ? &apos;&apos; : &apos;none&apos;;
    });
  }

  // 浏览器前进后退
  window.addEventListener(&apos;popstate&apos;, function(e) {
    var tag = (e.state &amp;&amp; e.state.tag) || &apos;all&apos;;
    tagButtons.forEach(function(btn) {
      btn.classList.toggle(&apos;on&apos;, btn.dataset.tag === tag);
    });
    filterPosts(tag);
  });
})();
```

#### 踩坑记录

**问题 1：Script 组件压缩错误**

错误写法：
```astro
import Script from &apos;astro:script&apos;;
&lt;Script&gt;
  const x = 1;
&lt;/Script&gt;
```

正确写法：
```astro
&lt;script is:inline&gt;
  const x = 1;
&lt;/script&gt;
```

**问题 2：模板字符串兼容**

错误写法（压缩后出错）：
```javascript
const url = `/blog?tag=${encodeURIComponent(tag)}`;
```

正确写法：
```javascript
const url = &apos;/blog?tag=&apos; + encodeURIComponent(tag);
```

**问题 3：现代语法兼容**

错误写法：
```javascript
const tag = e.state?.tag || &apos;all&apos;;
tagButtons.forEach(btn =&gt; { ... });
```

正确写法：
```javascript
var tag = (e.state &amp;&amp; e.state.tag) || &apos;all&apos;;
tagButtons.forEach(function(btn) { ... });
```

**最佳实践：**
- 使用 `&lt;script is:inline&gt;` 而非 `&lt;Script&gt;` 组件
- 避免模板字符串，使用字符串拼接
- 避免可选链 `?.` 和箭头函数，使用传统语法
- 使用 `var` 而非 `const/let` 增强兼容性

---

### 4.2 置顶文章功能

#### 需求分析

支持文章置顶，置顶文章在列表和首页优先展示，并带有视觉标识。

#### 实现方案

**排序逻辑：**
1. 分离置顶和普通文章
2. 各自按日期倒序排序
3. 合并：置顶在前，普通在后

**代码实现：**

```javascript
const pinnedPosts = allPosts.filter(post =&gt; post.data.pinned);
const normalPosts = allPosts.filter(post =&gt; !post.data.pinned);

const sortedPinned = pinnedPosts.sort((a, b) =&gt; {
  const dateA = a.data.pubDate || new Date(0);
  const dateB = b.data.pubDate || new Date(0);
  return dateB.getTime() - dateA.getTime();
});

const sortedNormal = normalPosts.sort((a, b) =&gt; {
  const dateA = a.data.pubDate || new Date(0);
  const dateB = b.data.pubDate || new Date(0);
  return dateB.getTime() - dateA.getTime();
});

const posts = [...sortedPinned, ...sortedNormal];
```

**视觉标识：**

```astro
&lt;time class=&quot;date&quot;&gt;
  {post.data.pubDate?.toLocaleDateString(&apos;zh-CN&apos;)}
  {post.data.pinned &amp;&amp; (
    &lt;span class=&quot;pinned-badge&quot; title=&quot;置顶&quot;&gt;📌&lt;/span&gt;
  )}
&lt;/time&gt;
```

**CSS 样式：**

```css
.post.pinned .date {
  color: var(--accent);
  font-weight: 500;
}

.post.pinned .pinned-badge {
  display: inline-block;
  margin-left: 0.5rem;
  font-size: 14px;
}

.post.pinned .date::after {
  content: &apos;• 置顶&apos;;
  display: block;
  color: var(--accent);
  font-size: 10px;
  margin-top: 2px;
}
```

**效果：**
- 日期高亮（主题色）
- 图钉图标 📌
- &quot;• 置顶&quot; 文字说明

---

### 4.3 排序逻辑抽象化

#### 问题

排序代码在首页、博客列表页重复，维护成本高。

#### 解决方案

创建工具函数库，统一排序逻辑。

`src/utils/posts.ts`:

```typescript
export interface BlogPost {
  data: {
    pubDate?: Date;
    pinned?: boolean;
    [key: string]: unknown;
  };
  slug: string;
  [key: string]: unknown;
}

/**
 * 按置顶和日期排序文章
 */
export function sortPostsByPinnedAndDate(posts: BlogPost[]): BlogPost[] {
  const pinnedPosts = posts.filter(post =&gt; post.data.pinned);
  const normalPosts = posts.filter(post =&gt; !post.data.pinned);

  const sortedPinned = pinnedPosts.sort((a, b) =&gt; {
    const dateA = a.data.pubDate || new Date(0);
    const dateB = b.data.pubDate || new Date(0);
    return dateB.getTime() - dateA.getTime();
  });

  const sortedNormal = normalPosts.sort((a, b) =&gt; {
    const dateA = a.data.pubDate || new Date(0);
    const dateB = b.data.pubDate || new Date(0);
    return dateB.getTime() - dateA.getTime();
  });

  return [...sortedPinned, ...sortedNormal];
}

/**
 * 按年月分组文章
 */
export function groupPostsByYearMonth(posts: BlogPost[]): Record&lt;string, BlogPost[]&gt; {
  const groups: Record&lt;string, BlogPost[]&gt; = {};

  posts.forEach(post =&gt; {
    if (!post.data.pubDate) {
      if (!groups[&apos;未发布&apos;]) groups[&apos;未发布&apos;] = [];
      groups[&apos;未发布&apos;].push(post);
      return;
    }

    const year = post.data.pubDate.getFullYear();
    const month = String(post.data.pubDate.getMonth() + 1).padStart(2, &apos;0&apos;);
    const key = `${year}-${month}`;

    if (!groups[key]) groups[key] = [];
    groups[key].push(post);
  });

  return groups;
}

/**
 * 获取年月显示文本
 */
export function getYearMonthLabel(key: string): string {
  if (key === &apos;未发布&apos;) return &apos;未发布&apos;;
  const [year, month] = key.split(&apos;-&apos;);
  return `${year}年${parseInt(month)}月`;
}
```

**复用示例：**

`src/pages/index.astro`:
```astro
import { sortPostsByPinnedAndDate } from &apos;../utils/posts&apos;;

const allPostsRaw = await getCollection(&apos;blog&apos;);
const allPosts = sortPostsByPinnedAndDate(allPostsRaw);
const latestPosts = allPosts.slice(0, 3);
```

**优势：**
- 代码复用，减少重复
- 统一排序逻辑，避免不一致
- 类型安全（TypeScript）
- 易于测试和维护

---

### 4.4 时间线组件

#### 需求

在博客列表页右侧添加时间线，按年月分组展示文章，支持滚动跟随和折叠。

#### 设计要点

1. **布局**：右侧固定宽度（280px），不挤占列表空间
2. **定位**：`position: sticky`，滚动时跟随
3. **分组**：按 `2026 年 5 月` 格式分组，显示文章数量
4. **交互**：可折叠，悬停效果，置顶标识
5. **响应式**：移动端隐藏

#### 实现

`src/components/Timeline.astro`:

```astro
---
import { sortPostsByPinnedAndDate, groupPostsByYearMonth, getYearMonthLabel } from &apos;../utils/posts&apos;;

interface Props {
  posts: {
    data: {
      pubDate?: Date;
      title: string;
      pinned?: boolean;
    };
    slug: string;
  }[];
}

const { posts } = Astro.props;
const sortedPosts = sortPostsByPinnedAndDate(posts);
const groupedPosts = groupPostsByYearMonth(sortedPosts);

const yearMonths = Object.keys(groupedPosts).sort((a, b) =&gt; {
  if (a === &apos;未发布&apos; || b === &apos;未发布&apos;) return a === &apos;未发布&apos; ? 1 : -1;
  return b.localeCompare(a);
});
---

&lt;aside class=&quot;timeline&quot;&gt;
  &lt;div class=&quot;timeline-header&quot;&gt;
    &lt;h3&gt;时间线&lt;/h3&gt;
    &lt;div class=&quot;timeline-toggle&quot; title=&quot;收起/展开&quot;&gt;
      &lt;svg width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 16 16&quot; fill=&quot;none&quot;&gt;
        &lt;path d=&quot;M4 6L8 10L12 6&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot;/&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class=&quot;timeline-content&quot;&gt;
    {yearMonths.map((yearMonth) =&gt; (
      &lt;div class=&quot;timeline-group&quot;&gt;
        &lt;div class=&quot;timeline-year&quot;&gt;
          &lt;span class=&quot;year-label&quot;&gt;{getYearMonthLabel(yearMonth)}&lt;/span&gt;
          &lt;span class=&quot;year-count&quot;&gt;({groupedPosts[yearMonth].length})&lt;/span&gt;
        &lt;/div&gt;
        
        &lt;ul class=&quot;timeline-list&quot;&gt;
          {groupedPosts[yearMonth].map((post) =&gt; (
            &lt;li class={`timeline-item ${post.data.pinned ? &apos;pinned&apos; : &apos;&apos;}`}&gt;
              &lt;div class=&quot;timeline-dot&quot;&gt;&lt;/div&gt;
              &lt;div class=&quot;timeline-content-item&quot;&gt;
                &lt;time class=&quot;timeline-date&quot;&gt;
                  {post.data.pubDate?.toLocaleDateString(&apos;zh-CN&apos;, { month: &apos;numeric&apos;, day: &apos;numeric&apos; })}
                &lt;/time&gt;
                &lt;a href={`/blog/${post.slug}`} class=&quot;timeline-title&quot;&gt;
                  {post.data.title}
                  {post.data.pinned &amp;&amp; &lt;span class=&quot;pinned-icon&quot;&gt;📌&lt;/span&gt;}
                &lt;/a&gt;
              &lt;/div&gt;
            &lt;/li&gt;
          ))}
        &lt;/ul&gt;
      &lt;/div&gt;
    ))}
  &lt;/div&gt;
&lt;/aside&gt;

&lt;style&gt;
  .timeline {
    position: sticky;
    top: 5rem;
    max-height: calc(100vh - 6rem);
    overflow-y: auto;
    border-left: 1px solid var(--border);
    padding-left: 1.5rem;
    margin-left: 2rem;
  }

  .timeline-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 0;
    border-bottom: 1px solid var(--border);
  }

  .timeline-content.collapsed {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
  }

  .timeline-item {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.5rem 0;
    transition: transform 0.2s;
  }

  .timeline-item:hover {
    transform: translateX(4px);
  }

  .timeline-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--text-muted);
    margin-top: 0.5rem;
    transition: all 0.2s;
  }

  .timeline-item:hover .timeline-dot {
    background: var(--accent);
    transform: scale(1.3);
  }

  @media (max-width: 768px) {
    .timeline {
      display: none;
    }
  }
&lt;/style&gt;

&lt;script is:inline&gt;
  (function() {
    const toggle = document.querySelector(&apos;.timeline-toggle&apos;);
    const content = document.querySelector(&apos;.timeline-content&apos;);
    
    if (toggle &amp;&amp; content) {
      toggle.addEventListener(&apos;click&apos;, () =&gt; {
        content.classList.toggle(&apos;collapsed&apos;);
        const svg = toggle.querySelector(&apos;svg path&apos;);
        if (svg) {
          const isCollapsed = content.classList.contains(&apos;collapsed&apos;);
          svg.setAttribute(&apos;d&apos;, isCollapsed ? &apos;M4 10L8 6L12 10&apos; : &apos;M4 6L8 10L12 6&apos;);
        }
      });
    }
  })();
&lt;/script&gt;
```

**布局集成：**

`src/pages/blog.astro`:
```astro
&lt;section class=&quot;sec&quot;&gt;
  &lt;div class=&quot;posts-container&quot;&gt;
    &lt;ol class=&quot;posts&quot; id=&quot;posts-list&quot;&gt;
      &lt;!-- 文章列表 --&gt;
    &lt;/ol&gt;
    &lt;Timeline posts={posts} /&gt;
  &lt;/div&gt;
&lt;/section&gt;
```

`src/styles/global.css`:
```css
.posts-container {
  display: grid;
  grid-template-columns: 1fr 280px;
  gap: 2rem;
  align-items: start;
}
```

**交互效果：**
- 悬停时文章右移 4px
- 圆点放大并高亮
- 置顶文章圆点始终高亮
- 点击标题收起/展开

---

## 五、部署与优化

### 5.1 Cloudflare Pages 部署

1. **构建项目**
   ```bash
   npm run build
   ```

2. **配置 Cloudflare Pages**
   - 连接 GitHub 仓库
   - 构建命令：`npm run build`
   - 输出目录：`dist`
   - 框架预设：Astro

3. **自定义域名**
   - 在 Cloudflare Dashboard 添加自定义域名
   - 自动配置 HTTPS

### 5.2 性能优化

**构建优化：**
- 使用 SSG（静态生成），避免 SSR
- 图片懒加载
- 字体子集化

**运行时优化：**
- 零 JavaScript 输出（按需 hydration）
- 客户端筛选使用原生 DOM 操作，无框架依赖
- 避免重排重绘，使用 `transform` 动画

### 5.3 SEO 优化

- 语义化 HTML 标签（`&lt;article&gt;`, `&lt;time&gt;`, `&lt;aside&gt;`）
- Meta 标签完整（title, description, Open Graph）
- 结构化数据（JSON-LD）
- 站点地图（sitemap.xml）
- RSS 订阅

---

## 六、最佳实践总结

### 6.1 代码组织

- **工具函数抽象**：排序、分组、格式化等通用逻辑放入 `src/utils/`
- **组件复用**：时间线、Tag 等可复用 UI 放入 `src/components/`
- **类型安全**：使用 TypeScript，定义清晰的接口

### 6.2 内容管理

- **Frontmatter 规范**：统一字段命名和格式
- **标签命名**：使用有意义的关键词，避免过长
- **置顶策略**：仅对重要文章使用 `pinned: true`

### 6.3 交互设计

- **无刷新筛选**：使用 `history.pushState` 更新 URL
- **浏览器导航**：监听 `popstate` 事件支持前进后退
- **无障碍**：添加 `aria-label`，键盘可访问

### 6.4 性能优先

- **避免框架依赖**：客户端交互使用原生 JavaScript
- **减少重排**：使用 `transform` 而非 `top/left`
- **按需加载**：图片、字体懒加载

---

## 七、后续扩展

### 已实现功能
- [x] 评论系统（Giscus）
- [x] 全文搜索（FlexSearch）
- [x] RSS 订阅
- [x] Tag 云图
- [x] 相关文章推荐
- [x] 时间线导航
- [x] 暗黑模式（浅色/深色/跟随系统）
- [x] 标签筛选
- [x] 置顶文章
- [x] 自定义面板（主题/字体/密度）

### 待实现功能

#### 用户体验
- [x] **键盘导航** - `j/k` 切换文章，`/` 聚焦搜索，`Esc` 关闭弹窗 ✅ 2026-05-21
- [x] **图片懒加载 + 缩略图** - `loading=&quot;lazy&quot;` + 响应式 `srcset` ✅ 2026-05-21
- [x] **代码块复制按钮** - 一键复制代码，带 Toast 反馈 ✅ 2026-05-21
- [x] **阅读进度条** - 页面顶部显示阅读进度 ✅ 2026-05-21
- [x] **目录导航（TOC）** - 左侧章节目录，滚动高亮 ✅ 2026-05-21
- [x] **阅读时长估算** - 根据字数估算阅读时间 ✅ 2026-05-21
- [x] **回到顶部按钮** - 滚动 300px 后显示 ✅ 2026-05-21
- [x] **分享功能** - 微信/微博/Twitter/复制链接/二维码 ✅ 2026-05-21

#### 性能优化
- [x] **性能优化** - Lighthouse 评分目标（性能 95+，SEO 100） ✅ 2026-05-21
  - 图片优化：使用 `astro/assets`，WebP 转换，响应式图片
  - 预加载关键资源：`&lt;link rel=&quot;preconnect&quot;&gt;` + `&lt;link rel=&quot;preload&quot;&gt;`
  - 减少重排：使用 `transform` 而非 `top/left`
- [x] **字体优化** - `font-display: swap` 防止 FOIT，子集化，本地托管 ✅ 2026-05-21
- [x] **缓存策略** - Service Worker + HTTP 缓存头优化 ✅ 2026-05-21

#### SEO 与可访问性
- [ ] **SEO 优化** - 结构化数据（JSON-LD），Open Graph 元标签
  - BlogPosting 结构化数据
  - Twitter Cards
  - 站点地图（sitemap.xml）
- [ ] **无障碍增强** - `skip to main` 链接，焦点可见性，ARIA 标签

#### 高级功能
- [ ] **PWA 支持** - 离线访问，添加到主屏幕
- [x] **多语言切换** - 中英文切换（i18n） ✅ 2026-05-21
- [ ] **网站统计** - Umami / Plausible（隐私友好）
- [ ] **邮件订阅** - Newsletter 新文章推送

---

**更新时间**: 2026-05-14  
**阅读时间**: 12 分钟  
</content:encoded><category>Astro</category><category>TailwindCSS</category></item><item><title>如何成为一名 LLM 算法工程师</title><link>https://edwardchen.com/blog/llm-algorithm-engineer-roadmap/</link><guid isPermaLink="true">https://edwardchen.com/blog/llm-algorithm-engineer-roadmap/</guid><description>从基础、Transformer、RAG、微调、偏好优化到推理部署的 LLM 算法工程师学习路线</description><pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate><content:encoded>
# 如何成为一名 LLM 算法工程师

这篇文章记录如何规划 LLM 算法工程师的学习路径，也希望给同样在入门和进阶的人一个可复用的参考。

不停留在“会调用大模型 API”这个层面，也不想只读论文、看概念，却没有可以复现和交付的东西。目标是逐步建立一套能落地的 LLM 算法能力：理解模型机制，能处理和构造数据，能做训练与后训练实验，能搭建可评估的 RAG/Agent 系统，也能把模型用可观测、可控成本的方式部署到业务场景里。

这条路线会按“先跑通、再理解、再优化、最后交付”的节奏推进。每个阶段都会留下明确产出，避免学习变成碎片化收藏。

## 目标画像

LLM 算法工程师最终需要具备四层能力：

| 能力层 | 我要回答的问题 | 阶段产出 |
|------|------|------|
| 基础层 | 我能否稳定训练、调试、复现实验？ | PyTorch 训练循环、实验记录、误差分析 |
| 模型层 | 我是否理解 Transformer、生成、微调和对齐？ | mini Transformer、SFT/LoRA/DPO 实验报告 |
| 系统层 | 我能否把模型接入数据、工具、检索和评估？ | RAG 服务、评估集、可观测 trace |
| 交付层 | 我能否解释成本、延迟、效果和风险？ | benchmark、上线方案、模型卡、复盘文档 |

四层交替推进。只学理论会缺少工程判断，只堆框架会很难定位效果问题，只追大模型新闻会缺少可复用的基本功。

## 方向选择

LLM 算法岗位有很多分支，需要先确定主线，再补齐旁支。

| 方向 | 我为什么关注 | 重点能力 | 我可以做的项目 |
|------|------|------|------|
| LLM 应用算法 | 最容易和真实业务结合，也最适合快速形成作品 | Prompt、RAG、Agent、评估、业务闭环 | 企业知识库问答、智能客服、BI Agent |
| 模型微调与后训练 | 能建立更深的算法壁垒，避免只会调 API | 数据构造、SFT、LoRA、DPO/GRPO、训练诊断 | 领域指令模型、偏好优化实验 |
| 推理部署与性能优化 | 私有化部署和成本控制是企业场景的关键问题 | vLLM、量化、批处理、KV cache、吞吐延迟权衡 | 私有化模型服务、推理网关 |
| 研究复现与评测 | 训练我判断新方法是否真的有效 | paper reading、benchmark、ablation、误差归因 | 论文复现、模型能力评估 |

当前的主线会先放在“LLM 应用算法 + 评估”上，因为它最容易形成闭环；第二阶段再深入“微调与后训练”；如果后续有明确私有化部署需求，会把“推理部署”作为差异化能力继续加强。

## Milestone 进度看板

这是后续持续更新学习进度的主表。每完成一个 milestone，不只勾选状态，还要补上证据：代码仓库、实验截图、指标表、博客文章或复盘结论。

| Milestone | 建议周期 | 状态 | 完成度 | 验收产出 | 证据链接 / 笔记 |
|------|------|------|------|------|------|
| M0：定位与环境基线 | 第 1 周 | [ ] 未开始 [x] 进行中 [ ] 完成 | 10% | 能在本机或云 GPU 跑通一个开源 LLM 推理样例 | 当前已完成学习规划 |
| M1：Python、数学与 PyTorch 基础 | 第 2-4 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 独立写出训练循环，并解释 loss、梯度、优化器和过拟合 |  |
| M2：NLP 与 Transformer 基础 | 第 5-8 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 实现一个 mini decoder-only Transformer 或完成逐行阅读笔记 |  |
| M3：LLM 推理、Prompt 与工具调用 | 第 9-10 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 建立一套 prompt 测试集，能稳定输出结构化结果 |  |
| M4：RAG 与评估体系 | 第 11-14 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 做出一个带 retrieval/generation eval 的知识库问答系统 |  |
| M5：SFT、PEFT 与数据工程 | 第 15-19 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 使用 LoRA/QLoRA 完成一次领域微调并写实验报告 |  |
| M6：偏好优化与后训练 | 第 20-23 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 完成 DPO 或 GRPO toy experiment，说明偏好数据如何影响输出 |  |
| M7：推理服务与性能优化 | 第 24-27 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 部署 OpenAI-compatible LLM 服务并完成吞吐、延迟、成本 benchmark |  |
| M8：作品集与面试闭环 | 持续进行 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 形成 3-5 个可讲清楚 trade-off 的项目和文章 |  |

## M0：定位与环境基线

这个阶段的目标不是一开始就训练大模型，而是先建立可复现实验环境。只要环境和记录方式不稳定，后面的训练、评估和复盘都会变得很混乱。

### 需要掌握

- Python 环境管理：`uv`、`conda` 或 `venv` 任选其一，但要能固定依赖。
- 基础工具：Git、Linux shell、Jupyter、VS Code、CUDA/MPS 基本排障。
- LLM 基础库：`torch`、`transformers`、`datasets`、`accelerate`、`peft`、`trl`。
- 推理入口：本地小模型、云端 API、OpenAI-compatible server 至少跑通一种。

### 验收清单

- [ ] 创建一个专门的 `llm-learning` 仓库。
- [ ] 固定 Python 版本和依赖文件。
- [ ] 跑通一个 tokenizer 示例，解释 token、context window、special tokens。
- [ ] 跑通一个小模型生成示例，记录输入、输出、耗时、显存或内存占用。
- [ ] 写一篇环境搭建和踩坑记录。

### 目录规划

```text
llm-learning/
├── README.md
├── env/
├── notebooks/
├── experiments/
├── evals/
└── notes/
```

## M1：Python、数学与 PyTorch 基础

这一阶段不追求“知道很多模型名字”，而是把训练过程拆开看清楚。以后遇到 loss 不降、显存溢出、数据泄漏、过拟合这些问题时，需要能自己定位，而不是只换框架或换模型。

### 需要掌握

- Python 数据处理：迭代器、类型标注、日志、配置、单元测试。
- 数学基础：线性代数、概率统计、信息论、微积分中与梯度相关的部分。
- 机器学习基础：训练/验证/测试拆分，偏差方差，正则化，交叉熵，指标选择。
- PyTorch 基础：Tensor、autograd、`nn.Module`、optimizer、scheduler、DataLoader、混合精度。

### 验收清单

- [ ] 用 PyTorch 写一个完整训练循环，不依赖高级 trainer。
- [ ] 画出训练集和验证集 loss 曲线，并解释是否过拟合。
- [ ] 实现 early stopping、checkpoint、resume training。
- [ ] 对同一模型至少做 3 次学习率或 batch size 对比实验。
- [ ] 写一份“训练异常排查清单”：loss 不降、梯度爆炸、显存溢出、数据泄漏。

### 练习安排

1. 用 sklearn 或 PyTorch 训练一个文本分类 baseline。
2. 用纯 PyTorch 复现一个小型语言模型训练循环。
3. 把实验配置、随机种子、指标和模型文件统一记录下来。

## M2：NLP 与 Transformer 基础

Transformer 是 LLM 的核心抽象。这个阶段不希望只停留在“Self-Attention 很重要”，而是要能解释每个张量形状如何变化，以及训练和生成阶段的差异。

### 需要掌握

- NLP 基础：分词、词向量、语言模型、序列标注、文本分类、问答。
- Tokenization：BPE、WordPiece、Unigram、chat template、特殊 token。
- Transformer：embedding、positional encoding、multi-head attention、FFN、LayerNorm、residual。
- Decoder-only LLM：causal mask、next-token prediction、KV cache、generation decoding。
- 生成策略：greedy、beam search、temperature、top-k、top-p、repetition penalty。

### 验收清单

- [ ] 画出 decoder-only Transformer 的数据流。
- [ ] 手写 scaled dot-product attention，并验证输入输出 shape。
- [ ] 解释 causal mask 为什么训练时并行、生成时自回归。
- [ ] 实现一个 mini Transformer language model，哪怕只在小语料上训练。
- [ ] 阅读并整理 Transformer、RAG、LoRA、DPO 相关论文笔记。

### 阅读顺序

1. 先读课程讲义或教程，建立概念图。
2. 再读论文摘要、方法图和实验设置。
3. 最后复现最小可运行版本，不一开始追求完整训练规模。

## M3：LLM 推理、Prompt 与工具调用

这个阶段会把 Prompt 当成可测试的工程接口，而不是凭感觉写提示词。要学会把任务定义、上下文、约束、输出格式和评估标准说清楚。

### 需要掌握

- Chat message 结构：system、user、assistant、tool。
- 输出控制：JSON schema、结构化输出、函数调用、错误重试。
- Prompt 设计：任务说明、少样本示例、反例、边界条件、引用上下文。
- 推理参数：temperature、top-p、max tokens、stop、seed。
- 评估方式：黄金集、人工评分、LLM-as-judge、规则断言、回归测试。

### 验收清单

- [ ] 为一个真实任务建立 30-100 条测试输入。
- [ ] 为输出定义可机器检查的 schema。
- [ ] 比较至少 3 版 prompt，并记录准确率、稳定性和失败样例。
- [ ] 加入工具调用或函数调用，处理调用失败和空结果。
- [ ] 写出“什么时候该 prompt，什么时候该 RAG，什么时候该微调”的判断规则。

### 判断规则

| 问题类型 | 我会优先选择 | 原因 |
|------|------|------|
| 输出格式不稳定 | 结构化输出 / 函数调用 | 我先约束接口，不急着训练 |
| 缺少私有知识 | RAG | 知识可更新，成本低于微调 |
| 固定风格或固定流程 | Prompt + eval | 快速迭代，容易回归测试 |
| 领域术语和输出习惯长期稳定 | SFT / LoRA | 让模型学习模式，而不是每次塞长 prompt |
| 人类偏好难以用规则描述 | DPO / RLHF / GRPO | 用偏好数据优化排序或行为 |

## M4：RAG 与评估体系

RAG 是最想先做出闭环的方向。它的难点不是“把文档放进向量库”，而是让检索、重排、生成和评估能互相校验。没有评估的 RAG 很容易变成“看起来能答，实际上不可控”的系统。

### 需要掌握

- 文档处理：解析、清洗、切块、去重、metadata、版本管理。
- 向量检索：embedding、相似度、召回率、top-k、hybrid search。
- 重排：cross encoder reranker、规则 rerank、业务字段加权。
- 生成：上下文压缩、引用来源、拒答策略、答案格式控制。
- 评估：retrieval recall、faithfulness、answer correctness、latency、成本。

### 验收清单

- [ ] 选一个知识库，例如 Obsidian、项目文档或部署文档。
- [ ] 构造至少 50 条问题，标注答案来源文档。
- [ ] 分别评估 chunk size、top-k、embedding model、reranker 对结果的影响。
- [ ] 输出每次回答引用的来源片段。
- [ ] 建立失败样例分类：没召回、召回错、上下文冲突、模型幻觉、格式错误。

### 项目设想

先做一个“Obsidian 知识库问答”项目：

1. 读取 Markdown 文档，保留标题层级和路径 metadata。
2. 将内容切块并写入 FAISS、Milvus 或 pgvector。
3. 对每个问题返回答案、引用段落和置信度。
4. 使用固定评估集比较不同切块和检索策略。
5. 把失败样例写回笔记，形成下一轮优化任务。

## M5：SFT、PEFT 与数据工程

进入微调阶段后，要提醒自己：微调的核心不是把训练命令跑起来，而是理解数据为什么能改变模型行为。多数微调失败不是算法失败，而是数据定义、格式、分布和评估出了问题。

### 需要掌握

- 数据格式：instruction、input、output、conversation、chat template。
- 数据质量：去重、清洗、长度分布、标签一致性、泄漏检查。
- SFT：teacher forcing、loss mask、packing、learning rate、warmup、checkpoint。
- PEFT：LoRA、QLoRA、rank、alpha、target modules、merge。
- 实验诊断：训练 loss、验证 loss、样例输出、灾难性遗忘、过拟合。

### 验收清单

- [ ] 构造一个 500-5000 条的领域指令数据集。
- [ ] 写数据检查脚本，统计长度、空值、重复、异常标签。
- [ ] 使用 LoRA/QLoRA 完成一次 SFT。
- [ ] 设计 baseline：原始模型、prompt-only、SFT 模型对比。
- [ ] 写实验报告，包含配置、指标、样例、失败分析和下一步计划。

### 实验报告模板

| 项目 | 我要记录的内容 |
|------|------|
| 目标 | 这次微调希望改善什么行为 |
| 数据 | 数据来源、规模、格式、清洗规则 |
| 模型 | base model、上下文长度、tokenizer |
| 训练 | batch size、learning rate、epoch、LoRA 参数 |
| 评估 | 自动指标、人工评分、失败样例 |
| 结论 | 是否值得继续扩大数据或训练规模 |

## M6：偏好优化与后训练

这个阶段会学习如何让模型更符合偏好，而不是简单学会答案。它通常用于改善风格、安全性、帮助性、拒答边界、推理过程或多候选答案排序。

### 需要掌握

- 偏好数据：chosen/rejected、pairwise ranking、rubric、标注一致性。
- Reward modeling：奖励模型目标、过优化风险、reward hacking。
- DPO：直接用偏好对优化策略模型，工程上比完整 RLHF 更容易入门。
- GRPO/RLHF：理解 policy、reward、advantage、KL 约束、采样成本。
- 安全与边界：拒答、敏感问题、越权工具调用、幻觉控制。

### 验收清单

- [ ] 从一个任务中采集至少 200 组 chosen/rejected 样例。
- [ ] 定义偏好标注规则，并记录难判样例。
- [ ] 跑通 DPO 或 GRPO 的最小实验。
- [ ] 比较 SFT 模型与偏好优化模型的输出差异。
- [ ] 写出偏好优化带来的收益、代价和风险。

### 需要注意

偏好优化不适合拿来修复所有问题。如果模型缺少知识，优先做 RAG；如果输出格式不稳定，优先做结构化输出；如果领域任务样例不足，优先补 SFT 数据；只有当“多个可行答案之间的偏好”本身很重要时，再考虑 DPO、RLHF 或 GRPO。

## M7：推理服务与性能优化

模型效果只是上线的一半。真实业务还会关心吞吐、首 token 延迟、总延迟、显存、并发、成本、稳定性和降级策略。这个阶段要从“模型能跑”推进到“服务可用”。

### 需要掌握

- 推理引擎：vLLM、SGLang、TGI、llama.cpp 的定位和差异。
- 性能概念：prefill、decode、KV cache、continuous batching、tensor parallel。
- 压缩优化：量化、蒸馏、speculative decoding、上下文压缩。
- 服务接口：OpenAI-compatible API、鉴权、限流、超时、重试。
- 可观测性：请求日志、trace、token 用量、延迟分位数、错误分类。

### 验收清单

- [ ] 使用 vLLM 或同类引擎部署一个 OpenAI-compatible endpoint。
- [ ] 对不同并发、输入长度、输出长度做 benchmark。
- [ ] 记录 TTFT、TPOT、吞吐、显存占用和错误率。
- [ ] 设计限流、超时、fallback 和日志脱敏策略。
- [ ] 写一份“模型上线前检查清单”。

### Benchmark 记录表

| 模型 | 引擎 | 并发 | 输入 tokens | 输出 tokens | TTFT | 总延迟 P50/P95 | 吞吐 tokens/s | 显存 | 备注 |
|------|------|------|------|------|------|------|------|------|------|
|      |      |      |      |      |      |      |      |      |      |

## M8：作品集与面试闭环

作品集不需要堆很多 demo。希望每个项目都能讲清楚“问题、方案、指标、失败、取舍”。如果一个项目只能演示，不能解释取舍，它对长期成长的帮助就有限。

### 作品集规划

| 项目 | 我想展示的能力 | 最低验收标准 |
|------|------|------|
| mini Transformer 复现 | 模型基础 | 能解释 attention、mask、loss 和生成过程 |
| RAG 知识库问答 | 应用算法 | 有评估集、引用、失败分类和优化记录 |
| 领域 LoRA 微调 | 后训练入门 | 有数据报告、baseline 对比和误差分析 |
| 偏好优化实验 | 对齐理解 | 有 chosen/rejected 数据和 DPO/GRPO 对比 |
| vLLM 推理服务 | 工程交付 | 有 benchmark、限流、日志和部署说明 |

### 需要能回答的问题

- Transformer 中 Q/K/V 的作用是什么？为什么需要 multi-head？
- causal mask 解决什么问题？训练和生成阶段有什么不同？
- RAG 中 chunk size、top-k、reranker 如何影响召回和幻觉？
- 什么场景该用 prompt，什么场景该用 RAG，什么场景才需要微调？
- LoRA 的 rank、alpha、target modules 分别影响什么？
- SFT 后效果变差，会如何排查数据、训练和评估问题？
- DPO 和 RLHF 的核心区别是什么？
- vLLM 为什么能提升吞吐？KV cache 对显存有什么影响？
- 如何设计一个 LLM 应用的离线评估和线上监控？

## 每周学习节奏

把每周学习固定成四类动作，避免只输入不输出。

| 动作 | 时间占比 | 执行要求 |
|------|------|------|
| 学概念 | 25% | 课程、文档、论文，目标是建立概念图 |
| 写代码 | 35% | 每周至少一个可运行实验 |
| 做评估 | 25% | 用固定数据集比较不同方案 |
| 写复盘 | 15% | 记录失败样例、指标变化和下一步 |

## 进度日志模板

后续更新学习进度时，直接复制这个模板追加到本节下面。

```markdown
### YYYY-MM-DD

- Milestone：
- 本周完成：
- 关键证据：
- 指标变化：
- 主要卡点：
- 下周计划：
```

### 2026-05-20

- Milestone：M0
- 本周完成：创建 LLM 算法工程师学习路线和 milestone 进度看板。
- 关键证据：本文档。
- 指标变化：暂无。
- 主要卡点：还需要确认每周可投入时间，并尽快搭建第一个学习仓库。
- 下周计划：搭建 `llm-learning` 仓库，跑通 tokenizer 和小模型推理基线。

## 学习资料路径

下面这些资料按“先官方教程，再论文，再工程文档”的顺序使用。

| 类别 | 资料 | 使用方式 |
|------|------|------|
| 课程 | [Stanford CS224N](https://web.stanford.edu/class/cs224n/) | 用来补 NLP、Transformer、LLM 评估和 Agent/RAG 基础 |
| 官方教程 | [Hugging Face Transformers 文档](https://huggingface.co/docs/transformers) | 学 tokenizer、模型加载、Trainer、生成和微调 |
| 后训练 | [Hugging Face TRL 文档](https://huggingface.co/docs/trl) | 学 SFT、DPO、GRPO、Reward Modeling 等后训练方法 |
| 深度学习工程 | [PyTorch Tutorials](https://docs.pytorch.org/tutorials/) | 学训练循环、Transformer、分布式训练和性能优化 |
| Prompt/API | [OpenAI Prompt Engineering Guide](https://help.openai.com/en/articles/6654000-playground-and-prompt-engineering) | 学指令、上下文、输出格式和 prompt 评估 |
| 评估 | [OpenAI Evals Guide](https://platform.openai.com/docs/guides/evals) | 学如何系统化构建模型输出评估 |
| RAG 评估 | [Ragas Evaluation](https://docs.ragas.io/en/latest/references/evaluate/) | 学 RAG 自动评估的指标和接口 |
| 推理服务 | [vLLM Online Serving](https://docs.vllm.ai/en/latest/serving/online_serving/) | 学 OpenAI-compatible serving、并发和部署参数 |
| 论文 | [Attention Is All You Need](https://arxiv.org/abs/1706.03762) | Transformer 原始论文 |
| 论文 | [Retrieval-Augmented Generation](https://arxiv.org/abs/2005.11401) | RAG 经典论文 |
| 论文 | [LoRA](https://arxiv.org/abs/2106.09685) | PEFT 经典论文 |
| 论文 | [Direct Preference Optimization](https://arxiv.org/abs/2305.18290) | DPO 经典论文 |

---

## 总结

### 技术栈总结

| 组件 | 版本 | 我会用它做什么 |
|------|------|------|
| Python | 3.11 / 3.12 | 固定项目运行环境 |
| PyTorch | 当前稳定版 | 深度学习训练和推理基础 |
| Transformers | 当前稳定版 | 模型、tokenizer、Trainer 和生成接口 |
| Datasets / Accelerate | 当前稳定版 | 数据集处理和训练加速 |
| PEFT / TRL | 当前稳定版 | LoRA、SFT、DPO、GRPO 等微调与后训练 |
| FAISS / Milvus / pgvector | 任选 | RAG 向量检索 |
| Ragas / 自建 eval | 任选 | RAG 和 LLM 应用评估 |
| vLLM | 当前稳定版 | 高吞吐 LLM 推理服务 |
| LangSmith / OpenTelemetry / 自建日志 | 任选 | trace、评估和线上观测 |

### 核心步骤回顾

1. 建立可复现实验环境，不急着训练大模型。
2. 补齐 PyTorch、NLP 和 Transformer 基础。
3. 用 Prompt、RAG 和 eval 做出可交付应用。
4. 用 SFT、LoRA 和偏好优化提升模型行为。
5. 用 vLLM、benchmark 和可观测性完成工程闭环。

### 下一步优化

- [ ] 确认主攻方向：应用算法、微调后训练、推理部署或研究评测。
- [ ] 创建 `llm-learning` 仓库并补充环境说明。
- [ ] 建立第一版评估集。
- [ ] 用 Obsidian 文档做一个 RAG baseline。
- [ ] 每周更新一次 milestone 进度看板和进度日志。

---

**更新时间**: 2026-05-20  
**阅读时间**: 18 分钟  
**适用场景**: LLM 算法工程师学习规划、转岗准备、学习进度管理、作品集建设
</content:encoded><category>LLM</category><category>算法工程师</category><category>学习日志</category><category>学习规划</category><category>Transformer</category><category>RAG</category><category>Fine-tuning</category></item><item><title>Obsidian 完全指南：从入门到精通</title><link>https://edwardchen.com/blog/obsidian-complete-guide/</link><guid isPermaLink="true">https://edwardchen.com/blog/obsidian-complete-guide/</guid><description>深入探索 Obsidian 笔记应用，掌握高级用法、高效插件和工作流，打造你的第二大脑</description><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><content:encoded>
# Obsidian 完全指南：从入门到精通

&gt; **摘要**：Obsidian 是一款基于本地 Markdown 文件的强大笔记应用，通过双向链接、图谱视图和丰富的插件生态，帮助用户构建个人知识体系。本文将详细介绍 Obsidian 的核心功能、高级用法、高效插件以及如何将其融入实际工作流。

---

## 什么是 Obsidian

Obsidian 是一款**以本地 Markdown 文件为基础**的双向链接笔记应用，由 Shida Li 和 Erica Xu 于 2020 年推出。它的核心理念是：

- **数据本地化**：所有笔记都是本地 Markdown 文件，完全掌控你的数据
- **双向链接**：通过 `[[链接]]` 语法建立笔记间的关联，形成知识网络
- **可插拔架构**：丰富的社区插件生态系统，按需扩展功能
- **跨平台同步**：支持 Windows、macOS、Linux、iOS 和 Android

### 为什么选择 Obsidian

| 特性 | Obsidian | Notion | Roam Research |
|------|----------|--------|---------------|
| 数据存储 | 本地 Markdown | 云端 | 云端 |
| 离线可用 | ✅ | ❌ | ❌ |
| 双向链接 | ✅ | ✅ | ✅ |
| 插件生态 | 🌟🌟🌟🌟🌟 | 🌟🌟🌟 | 🌟🌟 |
| 启动速度 | ⚡ 极快 | 🐢 较慢 | 🐢 较慢 |
| 自定义程度 | 极高 | 中等 | 中等 |
| 价格 | 免费 | 收费 | 收费 |

---

## 核心功能详解

### 1. 双向链接 (Backlinks)

双向链接是 Obsidian 的核心功能，让你轻松建立笔记间的关联。

**基本语法**：
```markdown
这是普通文本，链接到 [[另一篇笔记]] 的内容。

自动创建链接：[[新笔记]]（如果不存在会自动创建）
```

**高级用法**：
```markdown
# 显示不同文本
[[目标笔记 | 自定义显示文本]]

# 链接到特定锚点
[[目标笔记#锚点]]

# 嵌入笔记内容
![[嵌入的笔记]]

# 嵌入特定块
![[笔记#^block-id]]
```

### 2. 图谱视图 (Graph View)

图谱视图可视化展示笔记间的连接关系，帮助你发现知识间的隐藏联系。

**使用技巧**：
- 拖拽节点重新布局
- 点击节点高亮相关连接
- 使用过滤器按标签、文件夹筛选
- 调整连接距离和节点大小

### 3. 反向链接面板

右侧面板显示所有链接到当前笔记的内容，分为：
- **反向链接**：直接链接到当前笔记
- **未链接的提及**：提到但未建立链接的内容
- **双向链接**：当前笔记链接到的内容

### 4. 模板系统

通过模板快速创建标准化笔记结构。

**基础模板示例**：
```markdown
---
title: &quot;{{title}}&quot;
date: {{date}}
tags: []
---

# {{title}}

## 概述

## 详细内容

## 参考
- 
```

### 5. 标签系统

标签用于跨文件夹组织内容：
```markdown
tags: [开发，教程，#工具/obsidian]

# 层级标签
#工作/项目/博客
#学习/编程/python
```

---

## 高级用法与技巧

### 1. 属性 (Frontmatter)

每篇笔记顶部可以添加 YAML frontmatter，用于元数据管理：

```yaml
---
title: &quot;文章标题&quot;
date: 2025-05-18
updated: 2025-05-20
status: draft
tags: [obsidian, 教程]
aliases: [别名 1, 别名 2]
icon: 📝
cover: &quot;path/to/image.jpg&quot;
---
```

### 2. 转义字符与特殊语法

```markdown
# 转义链接
\[\[这不是链接\]\]

# 数学公式
行内公式：$E = mc^2$
块级公式：
$$
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
$$

# 任务列表
- [x] 已完成任务
- [ ] 待办任务
- [ ] 子任务
  - [ ] 子子任务

# 折叠内容
&gt; [!collapse] 点击展开
&gt; 这里是折叠的内容
```

### 3. 代码块高亮

```markdown
```python hl_lines=&quot;3 5&quot;
def hello():
    name = &quot;Obsidian&quot;
    if name:
        print(f&quot;Hello, {name}!&quot;)
    return True
```

### 4. 调用笔记 (Transclusion)

将笔记内容嵌入到其他笔记中：

```markdown
# 嵌入整篇笔记
![[相关笔记]]

# 嵌入特定部分（需要 Dataview）
```dataview
TABLE WITHOUT ID
  field1 as &quot;字段 1&quot;,
  field2 as &quot;字段 2&quot;
FROM &quot;文件夹&quot;
WHERE status = &quot;active&quot;
```

### 5. 块引用与块 ID

为段落添加唯一 ID，实现精确引用：

```markdown
这是一个重要概念^#concept-001

在另一篇笔记中引用：![[当前笔记#^concept-001]]
```

---

## 必备高效插件

### 核心插件（内置）

#### 1. Templates（模板）
快速插入预设模板，标准化笔记结构。

**配置建议**：
```
模板文件夹：/Templates
日期格式：YYYY-MM-DD
时间格式：HH:mm
```

#### 2. Daily Notes（每日笔记）
自动创建每日日志，记录日常思考和任务。

**模板示例**：
```markdown
# 📅 {{date}}

## 🎯 今日目标
- 

## 📝 工作记录

## 💡 想法与灵感

## ✅ 完成事项
```

#### 3. Advanced Tables（高级表格）
提供类似 Excel 的表格编辑体验，支持快捷键操作。

#### 4. Command Palette（命令面板）
`Ctrl/Cmd + P` 快速访问所有功能。

### 社区插件推荐

#### 1. Dataview ⭐⭐⭐⭐⭐
**功能**：查询和展示笔记数据，将 Obsidian 变成数据库。

**使用示例**：
```dataview
LIST FROM &quot;博客&quot; 
WHERE status = &quot;published&quot; 
SORT file.ctime DESC
```

```dataview
TABLE date as &quot;日期&quot;, tags as &quot;标签&quot;
FROM &quot;日记&quot;
WHERE contains(tags, &quot;重要&quot;)
GROUP BY month(date)
```

#### 2. Kanban ⭐⭐⭐⭐⭐
**功能**：创建看板，管理项目和任务。

**示例**：
```kanban
列标题 1
- [ ] 任务 1
- [x] 任务 2

列标题 2
- [ ] 任务 3
```
#### 3. Calendar ⭐⭐⭐⭐
**功能**：可视化日历视图，快速访问每日笔记。

#### 4. Outline ⭐⭐⭐⭐
**功能**：显示当前文档的目录结构，快速导航。

#### 5. Word Count ⭐⭐⭐⭐
**功能**：统计字数、字符数、阅读时间。

#### 6. Slides ⭐⭐⭐⭐
**功能**：将笔记转换为幻灯片演示。

```markdown
---
slide: true
---

# 第一页

---

# 第二页
```

#### 7. Excalidraw ⭐⭐⭐⭐⭐
**功能**：在笔记中绘制手绘风格的图表和思维导图。

#### 8. Linter ⭐⭐⭐⭐
**功能**：自动格式化笔记，统一风格。

**配置示例**：
```json
{
  &quot;rule-1-heading-bullet&quot;: true,
  &quot;rule-2-space-after-list-markers&quot;: true,
  &quot;rule-11-footnote-after-punctuation&quot;: true
}
```

#### 9. Auto Note Mover ⭐⭐⭐⭐
**功能**：根据规则自动移动笔记到对应文件夹。

#### 10. Hot Reload ⭐⭐⭐⭐
**功能**：修改 CSS 和插件后自动刷新，无需重启。

#### 11. QuickAdd ⭐⭐⭐⭐⭐
**功能**：快速添加内容到指定位置，支持宏和脚本。

**示例宏**：
1. 创建新笔记
2. 插入模板
3. 添加标签
4. 打开笔记

#### 12. Tasks ⭐⭐⭐⭐⭐
**功能**：强大的任务管理，支持跨笔记查询任务。

```markdown
```tasks
NOT DONE
PATH 包含 &quot;工作&quot;
GROUP BY heading
```

#### 13. Admonition（已内置为 Callouts）
**功能**：创建可折叠的提示框。

```markdown
&gt; [!INFO] 信息
&gt; 这是信息提示框

&gt; [!TIP] 提示
&gt; 这是提示信息

&gt; [!WARNING] 警告
&gt; 这是警告信息

&gt; [!QUOTE] 引用
&gt; 这是引用内容
```

---

## 与工作流结合

### 1. 博客工作流

**Obsidian → Astro 博客**

```bash
# 目录结构
Vault/
├── 05-Blog/          # 博客文章
├── Templates/        # 模板
├── Assets/          # 资源文件
└── sync-and-commit.sh  # 同步脚本
```

**同步脚本示例**：
```bash
#!/bin/bash
# 同步 Obsidian 博客到 GitHub

OBSIDIAN_PATH=&quot;/path/to/05-Blog&quot;
REPO_PATH=&quot;/path/to/blog-repo/content&quot;

# 复制文件
cp -r &quot;$OBSIDIAN_PATH&quot;/* &quot;$REPO_PATH/&quot;

# 提交更改
cd &quot;$REPO_PATH&quot;
git add .
git commit -m &quot;更新博客文章 $(date +%Y-%m-%d)&quot;
git push
```

### 2. 项目管理工作流

**使用 Kanban + Dataview 管理项目**：

项目看板

```kanban
待办
- [[任务 1]] 📅 2025-05-20
- [[任务 2]]

进行中
- [[任务 3]] 👤 Edward

已完成
- [[任务 4]] ✅
```

项目统计

```dataview
TASK FROM &quot;项目&quot;
GROUP BY status
```

### 3. 学习工作流

**Zettelkasten（卡片盒）方法**：

1. **Fleeting Notes**：快速记录想法
2. **Literature Notes**：整理参考资料
3. **Permanent Notes**：形成永久知识卡片

**文件夹结构**：
```
00-Inbox/          # 临时笔记
10-Areas/          # 长期关注领域
20-Projects/       # 正在进行的项目
30-Resources/      # 参考资料
40-Archives/       # 归档内容
99-Templates/      # 模板
```

### 4. 会议记录工作流

**会议模板**：
```markdown
---
title: &quot;{{meeting_name}}&quot;
date: {{date}}
attendees: []
project: []
---

# 📋 {{meeting_name}}

&gt; 📅 {{date}} | 👥 {{attendees}}

## 🎯 目标

## 📝 讨论要点

## ✅ 行动项
- [ ] @责任人 任务描述 📅 截止日期

## 📎 参考资料
- 
```

---

## 最佳实践

### 1. 命名规范

- 使用有意义的文件名
- 避免特殊字符
- 保持一致的命名风格
- 示例：`YYYY-MM-DD-会议主题.md`

### 2. 标签策略

- 使用层级标签：`#类别/子类别`
- 控制标签数量（建议&lt;50 个）
- 定期清理无用标签
- 使用 Dataview 查询标签

### 3. 链接策略

- 链接概念而非关键词
- 使用描述性链接文本
- 定期整理孤立笔记
- 利用反向链接发现关联

### 4. 备份与同步

**推荐方案**：
- **官方同步**：Obsidian Sync（付费，最方便）
- **Git 同步**：适合开发者，版本控制
- **云盘同步**：iCloud、Dropbox、OneDrive
- **第三方工具**：Remotely Save、Syncthing

### 5. 性能优化

- 控制库大小（建议&lt;10,000 笔记）
- 定期归档旧笔记
- 使用资源文件夹管理媒体
- 禁用不用的插件

---

## 总结

Obsidian 不仅仅是一个笔记应用，它是一个**知识管理系统**，帮助你：

1. **建立知识网络**：通过双向链接连接想法
2. **提升工作效率**：模板、快捷键、插件自动化
3. **长期知识积累**：本地存储，永久可用
4. **个性化定制**：CSS、插件、主题无限可能

### 学习路线建议

1. **第一周**：掌握基本操作、双向链接、模板
2. **第一月**：学习 Dataview、配置常用插件
3. **第三月**：建立自己的工作流，探索高级功能
4. **持续**：关注社区插件，优化个人系统

---

## 参考资源

- [Obsidian 官方文档](https://help.obsidian.md/)
- [Obsidian 论坛](https://forum.obsidian.md/)
- [Obsidian 插件库](https://obsidian.md/plugins)
- [Obsidian YouTube 频道](https://www.youtube.com/@obsidianmd)

---

*最后更新：2025-05-18*
</content:encoded><category>obsidian</category></item><item><title>Ansible 自动化运维完整指南</title><link>https://edwardchen.com/blog/ansible-complete-guide/</link><guid isPermaLink="true">https://edwardchen.com/blog/ansible-complete-guide/</guid><description>从零掌握 Ansible 自动化运维工具，涵盖基础概念、Playbook 编写、角色管理、最佳实践和高级应用。</description><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>
# Ansible 自动化运维完整指南

本文将全面介绍 Ansible 自动化运维工具，从基础概念到高级应用，帮助你构建高效、可维护的基础设施自动化体系。

## 为什么选择 Ansible？

### 配置管理工具对比

| 工具 | 架构 | Agent | 学习曲线 | 适用场景 |
|------|------|-------|----------|----------|
| **Ansible** | Push | 无需 | 低 | 快速部署、简单运维 |
| Puppet | Pull | 需要 | 高 | 大型复杂环境 |
| Chef | Pull | 需要 | 高 | 企业级配置管理 |
| SaltStack | Push/Pull | 可选 | 中 | 高性能需求 |

### Ansible 的核心优势

- **无代理架构** — 通过 SSH 连接，无需安装 Agent
- **简单易学** — YAML 语法，直观易懂
- **幂等性** — 多次执行结果一致
- **丰富模块** — 3000+ 内置模块
- **社区活跃** — 大量社区角色和插件
- **双向通信** — 支持 ad-hoc 命令和 Playbook

## 核心概念

### 1. 控制节点 (Control Node)

运行 Ansible 的机器，可以是任何 Linux/Mac 系统。

```bash
# 安装 Ansible
sudo apt update
sudo apt install ansible

# 验证安装
ansible --version
```

### 2. 管理主机 (Managed Nodes)

被 Ansible 管理的目标服务器。

**要求：**
- 安装 OpenSSH Server
- 与控制节点网络可达
- 支持 SSH 密钥认证（推荐）

### 3. 库存 (Inventory)

定义被管理主机的配置文件。

**静态库存示例：**

```ini
# /etc/ansible/hosts 或 inventory.ini

[webservers]
web1 ansible_host=192.168.1.10 ansible_user=ubuntu
web2 ansible_host=192.168.1.11 ansible_user=ubuntu

[dbservers]
db1 ansible_host=192.168.1.20 ansible_user=root
db2 ansible_host=192.168.1.21 ansible_user=root

[loadbalancers]
lb1 ansible_host=192.168.1.5 ansible_user=admin

# 组合组
[web:children]
webservers
loadbalancers

# 变量定义
[webservers:vars]
http_port=80
max_clients=200

# 所有服务器
[all:vars]
ansible_ssh_common_args=&apos;-o StrictHostKeyChecking=no&apos;
```

**动态库存示例（JSON 格式）：**

```json
{
  &quot;webservers&quot;: {
    &quot;hosts&quot;: [&quot;web1.example.com&quot;, &quot;web2.example.com&quot;],
    &quot;vars&quot;: {
      &quot;http_port&quot;: 80
    }
  },
  &quot;dbservers&quot;: {
    &quot;hosts&quot;: [&quot;db1.example.com&quot;, &quot;db2.example.com&quot;],
    &quot;vars&quot;: {
      &quot;db_port&quot;: 3306
    }
  }
}
```

### 4. 模块 (Modules)

Ansible 的基本执行单元，完成特定任务。

**常用模块：**

| 模块 | 用途 | 示例 |
|------|------|------|
| `command` | 执行命令 | `command: /usr/bin/uptime` |
| `shell` | Shell 命令 | `shell: ls -la \| grep log` |
| `copy` | 复制文件 | `copy: src=/local/file dest=/remote/file` |
| `template` | 模板渲染 | `template: src=config.j2 dest=/etc/app.conf` |
| `yum`/`apt` | 包管理 | `yum: name=nginx state=present` |
| `service` | 服务管理 | `service: name=nginx state=started` |
| `user` | 用户管理 | `user: name=app useradd=yes` |
| `file` | 文件管理 | `file: path=/var/log/app state=directory` |
| `git` | Git 操作 | `git: repo=URL dest=/app version=main` |
| `debug` | 调试输出 | `debug: msg=&quot;Hello&quot;` |

### 5. Playbook

YAML 格式的任务清单，定义自动化流程。

```yaml
---
- name: 配置 Web 服务器
  hosts: webservers
  become: yes  # 使用 sudo
  vars:
    nginx_version: &quot;1.24&quot;
    
  tasks:
    - name: 安装 Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes
    
    - name: 启动 Nginx 服务
      service:
        name: nginx
        state: started
        enabled: yes
    
    - name: 复制配置文件
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: 重启 Nginx
      
  handlers:
    - name: 重启 Nginx
      service:
        name: nginx
        state: restarted
```

## 快速开始

### 1. 配置 SSH 免密登录

```bash
# 生成 SSH 密钥（如果没有）
ssh-keygen -t ed25519 -C &quot;ansible@control&quot;

# 复制公钥到目标主机
ssh-copy-id ubuntu@192.168.1.10
ssh-copy-id ubuntu@192.168.1.11

# 测试连接
ssh ubuntu@192.168.1.10
```

### 2. 创建库存文件

```bash
# 创建目录结构
mkdir -p ~/ansible/{inventory,playbooks,roles,templates}

# 创建 inventory.ini
cat &gt; ~/ansible/inventory.ini &lt;&lt; &apos;EOF&apos;
[webservers]
web1 ansible_host=192.168.1.10 ansible_user=ubuntu
web2 ansible_host=192.168.1.11 ansible_user=ubuntu

[webservers:vars]
ansible_python_interpreter=/usr/bin/python3
EOF
```

### 3. 测试连接

```bash
# Ping 所有主机
ansible all -i inventory.ini -m ping

# 输出示例：
# web1 | SUCCESS =&gt; { &quot;ping&quot;: &quot;pong&quot; }
# web2 | SUCCESS =&gt; { &quot;ping&quot;: &quot;pong&quot; }
```

### 4. 执行 Ad-hoc 命令

```bash
# 检查系统信息
ansible all -i inventory.ini -m setup -a &apos;filter=ansible_facts&apos;

# 执行命令
ansible webservers -i inventory.ini -m command -a &apos;uptime&apos;

# 安装软件包
ansible all -i inventory.ini -m apt -a &apos;name=htop state=present&apos;

# 复制文件
ansible webservers -i inventory.ini -m copy -a &apos;src=/local/file.txt dest=/tmp/file.txt&apos;

# 管理服务
ansible all -i inventory.ini -m service -a &apos;name=nginx state=restarted&apos;
```

## Playbook 深入

### 1. 基本结构

```yaml
---
- name: Play 名称
  hosts: 目标主机组
  become: true  # 是否提权
  become_method: sudo  # 提权方式
  vars_files:
    - vars/main.yml  # 外部变量文件
  vars:
    var1: value1  # 内联变量
  environment:  # 环境变量
    KEY1: value1
  connection: ssh  # 连接方式
  serial: 10%  # 分批执行
  max_fail_percentage: 10  # 最大失败百分比
  
  pre_tasks:  # 前置任务
    - name: 前置任务
      debug:
        msg: &quot;准备开始&quot;
  
  roles:  # 应用角色
    - common
    - nginx
    
  tasks:  # 任务列表
    - name: 任务名称
      module: 参数
      
  post_tasks:  # 后置任务
    - name: 后置任务
      debug:
        msg: &quot;完成&quot;
  
  handlers:  # 处理器
    - name: 处理器名称
      service:
        name: nginx
        state: restarted
```

### 2. 变量管理

**变量优先级（低到高）：**

1. 角色默认变量 (`defaults/main.yml`)
2. 角色变量 (`vars/main.yml`)
3. Playbook 变量 (`vars:`)
4. 库存变量 (`group_vars/`, `host_vars/`)
5. 额外变量 (`-e` 参数)

**变量文件示例：**

```yaml
# group_vars/webservers.yml
http_port: 80
max_clients: 200
nginx_worker_processes: auto
nginx_worker_connections: 1024

# host_vars/web1.yml
http_port: 8080
custom_domain: &quot;web1.example.com&quot;

# vars/app_config.yml
app_name: &quot;myapp&quot;
app_version: &quot;1.0.0&quot;
app_port: 3000
```

**在 Playbook 中使用：**

```yaml
- name: 使用变量
  debug:
    msg: &quot;应用 {{ app_name }} 版本 {{ app_version }}&quot;
```

### 3. 条件执行

```yaml
- name: 条件安装
  apt:
    name: &quot;{{ item }}&quot;
    state: present
  loop:
    - nginx
    - mysql-server
  when: install_nginx | bool

- name: 检查操作系统
  debug:
    msg: &quot;这是 Ubuntu 系统&quot;
  when: ansible_os_family == &quot;Debian&quot;

- name: 检查变量存在
  debug:
    msg: &quot;变量已定义&quot;
  when: my_var is defined

- name: 复杂条件
  debug:
    msg: &quot;生产环境且需要 SSL&quot;
  when: environment == &quot;production&quot; and ssl_enabled | bool
```

### 4. 循环处理

```yaml
- name: 安装多个软件包
  apt:
    name: &quot;{{ item }}&quot;
    state: present
  loop:
    - nginx
    - mysql-server
    - php

- name: 创建多个用户
  user:
    name: &quot;{{ item.name }}&quot;
    groups: &quot;{{ item.groups }}&quot;
    shell: /bin/bash
  loop:
    - { name: &apos;user1&apos;, groups: &apos;developers&apos; }
    - { name: &apos;user2&apos;, groups: &apos;admins&apos; }

- name: 从文件读取列表
  debug:
    msg: &quot;{{ item }}&quot;
  loop: &quot;{{ lookup(&apos;file&apos;, &apos;packages.txt&apos;).split(&apos;\n&apos;) }}&quot;

- name: 嵌套循环
  debug:
    msg: &quot;{{ item.key }} = {{ item.value }}&quot;
  loop: &quot;{{ my_dict | dict2items }}&quot;
```

### 5. 错误处理

```yaml
- name: 可能失败的任务
  command: /usr/bin/risky-command
  ignore_errors: yes  # 忽略错误继续执行

- name: 带重试的任务
  command: /usr/bin/flaky-command
  retries: 5
  delay: 10
  until: last_rc == 0

- name: 失败时执行
  block:
    - name: 任务 1
      command: /usr/bin/task1
    - name: 任务 2
      command: /usr/bin/task2
  rescue:
    - name: 错误处理
      debug:
        msg: &quot;任务失败，执行恢复操作&quot;
  always:
    - name: 始终执行
      debug:
        msg: &quot;无论成功失败都执行&quot;
```

### 6. 模板渲染 (Jinja2)

**模板文件示例 (nginx.conf.j2)：**

```jinja2
user www-data;
worker_processes {{ nginx_worker_processes | default(&apos;auto&apos;) }};
pid /run/nginx.pid;

events {
    worker_connections {{ nginx_worker_connections | default(1024) }};
}

http {
    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    {% for server in virtual_hosts %}
    server {
        listen {{ server.port | default(80) }};
        server_name {{ server.domain }};
        root {{ server.root | default(&apos;/var/www/html&apos;) }};
        
        {% if server.ssl_enabled | default(false) %}
        ssl on;
        ssl_certificate {{ server.ssl_cert }};
        ssl_key {{ server.ssl_key }};
        {% endif %}
        
        location / {
            try_files $uri $uri/ =404;
        }
    }
    {% endfor %}
}
```

**Playbook 中使用：**

```yaml
- name: 渲染配置文件
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: &apos;0644&apos;
    backup: yes  # 备份原文件
    validate: &apos;nginx -t -c %s&apos;  # 验证语法
  notify: 重启 Nginx
```

## 角色 (Roles)

### 1. 角色结构

```
roles/
└── nginx/
    ├── defaults/
    │   └── main.yml          # 默认变量（优先级最低）
    ├── vars/
    │   └── main.yml          # 角色变量
    ├── files/                # 静态文件
    │   └── custom.conf
    ├── templates/            # Jinja2 模板
    │   └── nginx.conf.j2
    ├── handlers/
    │   └── main.yml          # 处理器
    ├── tasks/
    │   ├── main.yml          # 主任务
    │   ├── install.yml       # 安装任务
    │   └── configure.yml     # 配置任务
    ├── meta/
    │   └── main.yml          # 元数据（依赖等）
    └── README.md
```

### 2. 创建角色

```bash
# 使用 ansible-galaxy 创建角色结构
ansible-galaxy init nginx

# 或手动创建
mkdir -p roles/nginx/{tasks,handlers,templates,files,vars,defaults}
```

### 3. 角色内容示例

**defaults/main.yml：**

```yaml
---
nginx_version: &quot;1.24&quot;
nginx_state: present
nginx_enabled: true
nginx_listen_port: 80
nginx_worker_processes: &quot;auto&quot;
nginx_worker_connections: 1024
```

**tasks/main.yml：**

```yaml
---
- name: 包含安装任务
  include_tasks: install.yml

- name: 包含配置任务
  include_tasks: configure.yml

- name: 确保 Nginx 运行
  service:
    name: nginx
    state: &quot;{{ &apos;started&apos; if nginx_enabled else &apos;stopped&apos; }}&quot;
    enabled: &quot;{{ nginx_enabled | bool }}&quot;
  notify: 重启 Nginx
```

**tasks/install.yml：**

```yaml
---
- name: 安装 Nginx (Debian)
  apt:
    name: &quot;nginx={{ nginx_version }}&quot;
    state: &quot;{{ nginx_state }}&quot;
    update_cache: yes
  when: ansible_os_family == &quot;Debian&quot;

- name: 安装 Nginx (RHEL)
  yum:
    name: &quot;nginx-{{ nginx_version }}&quot;
    state: &quot;{{ nginx_state }}&quot;
  when: ansible_os_family == &quot;RedHat&quot;
```

**handlers/main.yml：**

```yaml
---
- name: 重启 Nginx
  service:
    name: nginx
    state: restarted

- name: 重载 Nginx
  service:
    name: nginx
    state: reloaded
```

**meta/main.yml：**

```yaml
---
dependencies:
  - role: common
    tags: [&apos;common&apos;]
  
  - role:
    name: firewall
    when: enable_firewall | default(false)
```

### 4. 使用角色

```yaml
---
- name: 部署 Web 服务器
  hosts: webservers
  become: yes
  
  roles:
    - role: common
      tags: [&apos;common&apos;]
      
    - role: nginx
      nginx_listen_port: 8080  # 覆盖默认变量
      tags: [&apos;web&apos;]
      
    - role: monitoring
      when: enable_monitoring | default(false)
```

## 高级特性

### 1. 自定义模块

**Python 模块示例 (modules/custom_facts.py)：**

```python
#!/usr/bin/env python
ANSIBLE_METADATA = {
    &apos;metadata_version&apos;: &apos;1.1&apos;,
    &apos;status&apos;: [&apos;preview&apos;],
    &apos;supported_by&apos;: &apos;community&apos;
}

DOCUMENTATION = &apos;&apos;&apos;
---
module: custom_facts
short_description: 收集自定义系统信息
version_added: &quot;1.0&quot;
options:
    check_disk:
        description: 是否检查磁盘空间
        required: false
        type: bool
        default: true
&apos;&apos;&apos;

EXAMPLES = &apos;&apos;&apos;
- name: 收集自定义 facts
  custom_facts:
    check_disk: yes
&apos;&apos;&apos;

RETURN = &apos;&apos;&apos;
ansible_facts:
    description: 返回自定义 facts
    type: dict
&apos;&apos;&apos;

from ansible.module_utils.basic import AnsibleModule

def main():
    module = AnsibleModule(
        argument_spec=dict(
            check_disk=dict(type=&apos;bool&apos;, default=True)
        )
    )
    
    facts = {}
    
    if module.params[&apos;check_disk&apos;]:
        import os
        disk_usage = os.statvfs(&apos;/&apos;)
        total = disk_usage.f_frsize * disk_usage.f_blocks
        free = disk_usage.f_frsize * disk_usage.f_bfree
        facts[&apos;disk_total&apos;] = total
        facts[&apos;disk_free&apos;] = free
        facts[&apos;disk_percent_used&apos;] = ((total - free) / total) * 100
    
    module.exit_json(changed=False, ansible_facts=facts)

if __name__ == &apos;__main__&apos;:
    main()
```

**使用自定义模块：**

```yaml
- name: 收集自定义 facts
  custom_facts:
    check_disk: yes
    
- name: 显示磁盘使用率
  debug:
    msg: &quot;磁盘使用率：{{ disk_percent_used | round(2) }}%&quot;
  when: disk_percent_used &gt; 80
```

### 2. 自定义插件

**Filter 插件示例 (filter_plugins/custom_filters.py)：**

```python
def to_bytes(size_mb):
    &quot;&quot;&quot;将 MB 转换为字节&quot;&quot;&quot;
    return size_mb * 1024 * 1024

def truncate_middle(text, length=50):
    &quot;&quot;&quot;截断文本中间部分&quot;&quot;&quot;
    if len(text) &lt;= length:
        return text
    half = length // 2
    return text[:half] + &quot;...&quot; + text[-half:]

class FilterModule(object):
    def filters(self):
        return {
            &apos;to_bytes&apos;: to_bytes,
            &apos;truncate_middle&apos;: truncate_middle
        }
```

**使用自定义过滤器：**

```yaml
- name: 使用自定义过滤器
  debug:
    msg: &quot;{{ 100 | to_bytes }}&quot;
    
- name: 截断长文本
  debug:
    msg: &quot;{{ long_text | truncate_middle(30) }}&quot;
```

### 3. 动态库存

**AWS EC2 动态库存脚本示例：**

```python
#!/usr/bin/env python3
import boto3
import json
import sys

def get_inventory():
    ec2 = boto3.client(&apos;ec2&apos;, region_name=&apos;us-east-1&apos;)
    
    response = ec2.describe_instances(
        Filters=[
            {&apos;Name&apos;: &apos;tag:Environment&apos;, &apos;Values&apos;: [&apos;Production&apos;]}
        ]
    )
    
    inventory = {
        &apos;webservers&apos;: {&apos;hosts&apos;: [], &apos;vars&apos;: {}},
        &apos;dbservers&apos;: {&apos;hosts&apos;: [], &apos;vars&apos;: {}},
        &apos;_meta&apos;: {&apos;hostvars&apos;: {}}
    }
    
    for reservation in response[&apos;Reservations&apos;]:
        for instance in reservation[&apos;Instances&apos;]:
            if instance[&apos;State&apos;][&apos;Name&apos;] != &apos;running&apos;:
                continue
                
            hostname = instance[&apos;Tags&apos;][0][&apos;Value&apos;]
            ip = instance[&apos;PrivateIpAddress&apos;]
            instance_type = instance[&apos;InstanceType&apos;]
            
            # 根据标签分组
            for tag in instance.get(&apos;Tags&apos;, []):
                if tag[&apos;Key&apos;] == &apos;Role&apos;:
                    if tag[&apos;Value&apos;] in inventory:
                        inventory[tag[&apos;Value&apos;]][&apos;hosts&apos;].append(hostname)
                        inventory[&apos;_meta&apos;][&apos;hostvars&apos;][hostname] = {
                            &apos;ansible_host&apos;: ip,
                            &apos;ansible_user&apos;: &apos;ubuntu&apos;,
                            &apos;instance_type&apos;: instance_type
                        }
    
    return inventory

if __name__ == &apos;__main__&apos;:
    if sys.argv[1] == &apos;--list&apos;:
        print(json.dumps(get_inventory()))
    elif sys.argv[1] == &apos;--host&apos;:
        print(json.dumps({}))
```

### 4. 异步任务和轮询

```yaml
- name: 执行长时间任务
  async: 3600  # 最多运行 1 小时
  poll: 0      # 不等待，立即返回
  command: /usr/bin/long-running-script.sh
  register: async_result

- name: 检查任务状态
  async_status:
    jid: &quot;{{ async_result.ansible_job_id }}&quot;
  register: job_status
  until: job_status.finished
  retries: 30
  delay: 10

- name: 显示结果
  debug:
    msg: &quot;{{ job_status.results }}&quot;
  when: job_status.finished
```

### 5. 策略 (Strategies)

```yaml
- name: 使用不同策略
  hosts: all
  strategy: linear  # 默认，同步执行
  
  # 其他策略选项:
  # - free: 主机独立执行，不等待
  # - host_pinned: 类似 free，但保持主机顺序
  
  tasks:
    - name: 任务 1
      debug:
        msg: &quot;任务 1&quot;
    
    - name: 任务 2
      debug:
        msg: &quot;任务 2&quot;
```

### 6. 滚动更新

```yaml
- name: 滚动更新 Web 服务
  hosts: webservers
  serial: 1  # 每次只更新一台
  
  tasks:
    - name: 从负载均衡移除
      uri:
        url: &quot;http://lb-api/remove/{{ inventory_hostname }}&quot;
        method: POST
      delegate_to: localhost
      
    - name: 停止服务
      service:
        name: myapp
        state: stopped
        
    - name: 部署新版本
      git:
        repo: https://github.com/app/repo.git
        dest: /opt/myapp
        version: &quot;{{ app_version }}&quot;
        
    - name: 启动服务
      service:
        name: myapp
        state: started
        
    - name: 健康检查
      uri:
        url: &quot;http://localhost:8080/health&quot;
        status_code: 200
      register: health_result
      retries: 5
      delay: 10
      until: health_result.status == 200
      
    - name: 添加回负载均衡
      uri:
        url: &quot;http://lb-api/add/{{ inventory_hostname }}&quot;
        method: POST
      delegate_to: localhost
      when: health_result.status == 200
```

## 最佳实践

### 1. 项目结构

```
ansible-project/
├── inventory/
│   ├── production/
│   │   ├── hosts
│   │   ├── group_vars/
│   │   │   ├── all.yml
│   │   │   └── webservers.yml
│   │   └── host_vars/
│   └── staging/
├── group_vars/
│   ├── all.yml
│   └── webservers.yml
├── host_vars/
├── playbooks/
│   ├── site.yml          # 主 Playbook
│   ├── webserver.yml
│   ├── database.yml
│   └── backup.yml
├── roles/
│   ├── common/
│   ├── nginx/
│   ├── mysql/
│   └── monitoring/
├── templates/            # 全局模板
├── files/                # 全局文件
├── scripts/              # 辅助脚本
├── ansible.cfg           # 配置文件
├── requirements.yml      # 角色依赖
└── README.md
```

### 2. Ansible 配置

**ansible.cfg：**

```ini
[defaults]
inventory = ./inventory/production/hosts
remote_user = ubuntu
host_key_checking = False
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400
roles_path = ./roles
library = ./library
filter_plugins = ./filter_plugins
stdout_callback = yaml
callback_whitelist = profile_tasks, timer

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

[ssh_connection]
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
```

### 3. 变量管理最佳实践

```yaml
# group_vars/all.yml - 全局变量
ansible_python_interpreter: /usr/bin/python3
timezone: Asia/Shanghai
ntp_server: ntp.example.com

# group_vars/webservers.yml - 组变量
http_port: 80
max_clients: 200

# host_vars/web1.yml - 主机变量
custom_domain: web1.example.com

# vars/secrets.yml - 敏感信息（需加密）
db_password: &quot;{{ vault_db_password }}&quot;
api_key: &quot;{{ vault_api_key }}&quot;
```

### 4. 安全实践

**使用 Ansible Vault：**

```bash
# 创建加密文件
ansible-vault create group_vars/secrets.yml

# 编辑加密文件
ansible-vault edit group_vars/secrets.yml

# 查看加密文件
ansible-vault view group_vars/secrets.yml

# 加密普通文件
ansible-vault encrypt secrets.yml

# 解密文件
ansible-vault decrypt secrets.yml

# 运行 Playbook（输入密码）
ansible-playbook -i inventory site.yml --ask-vault-pass

# 使用密码文件
ansible-playbook -i inventory site.yml --vault-password-file=~/.vault_pass
```

**Playbook 中使用 Vault：**

```yaml
- name: 使用加密变量
  hosts: all
  vars_files:
    - group_vars/secrets.yml
    
  tasks:
    - name: 创建数据库用户
      mysql_user:
        name: app_user
        password: &quot;{{ vault_db_password }}&quot;  # 加密变量
        host: &apos;%&apos;
        priv: &apos;{{ db_name }}.*:ALL&apos;
```

### 5. 错误处理和日志

```yaml
- name: 带完整错误处理的任务
  block:
    - name: 执行关键任务
      command: /usr/bin/critical-task
      register: result
      
    - name: 验证结果
      assert:
        that:
          - result.rc == 0
          - &quot;success&quot; in result.stdout
        fail_msg: &quot;任务执行失败&quot;
        success_msg: &quot;任务成功完成&quot;
        
  rescue:
    - name: 记录错误
      debug:
        msg: &quot;任务失败：{{ result.stderr }}&quot;
        
    - name: 发送告警
      uri:
        url: &quot;{{ alert_webhook_url }}&quot;
        method: POST
        body: &quot;任务失败：{{ inventory_hostname }}&quot;
        status_code: 200
        
    - name: 回滚操作
      command: /usr/bin/rollback-script
      
  always:
    - name: 记录执行日志
      lineinfile:
        path: /var/log/ansible-execution.log
        line: &quot;{{ ansible_date_time.iso8601 }} - {{ inventory_hostname }} - {{ result.rc if result is defined else &apos;N/A&apos; }}&quot;
```

### 6. 性能优化

```yaml
# 启用 SSH 管道和连接复用
[ssh_connection]
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r

# 使用 serial 分批执行
- name: 分批部署
  hosts: all
  serial:
    - 10%
    - 25%
    - 100%
    
# 使用 max_fail_percentage 控制失败容忍度
- name: 容错部署
  hosts: all
  max_fail_percentage: 10
  
# 使用 tags 选择性执行
ansible-playbook site.yml --tags &quot;nginx,mysql&quot;

# 使用 limit 限制主机
ansible-playbook site.yml --limit &quot;web1,web2&quot;
```

## 实战案例

### 案例 1：部署 LAMP 栈

```yaml
---
- name: 部署 LAMP 栈
  hosts: webservers
  become: yes
  
  roles:
    - common
    - { role: mysql, tags: [&apos;database&apos;] }
    - { role: php, tags: [&apos;php&apos;] }
    - { role: nginx, tags: [&apos;web&apos;] }
    
  tasks:
    - name: 创建应用目录
      file:
        path: /var/www/myapp
        state: directory
        owner: www-data
        group: www-data
        mode: &apos;0755&apos;
        
    - name: 部署应用代码
      git:
        repo: https://github.com/myapp/repo.git
        dest: /var/www/myapp
        version: main
        
    - name: 配置虚拟主机
      template:
        src: nginx-vhost.conf.j2
        dest: &quot;/etc/nginx/sites-available/{{ app_name }}&quot;
      notify: 重载 Nginx
      
    - name: 启用虚拟主机
      file:
        src: &quot;/etc/nginx/sites-available/{{ app_name }}&quot;
        dest: &quot;/etc/nginx/sites-enabled/{{ app_name }}&quot;
        state: link
      notify: 重载 Nginx
      
  handlers:
    - name: 重载 Nginx
      service:
        name: nginx
        state: reloaded
```

### 案例 2：Docker 环境部署

```yaml
---
- name: 部署 Docker 环境
  hosts: docker_hosts
  become: yes
  
  tasks:
    - name: 安装依赖包
      apt:
        name:
          - apt-transport-https
          - ca-certificates
          - curl
          - software-properties-common
        state: present
        update_cache: yes
        
    - name: 添加 Docker GPG 密钥
      apt_key:
        url: https://download.docker.com/linux/ubuntu/gpg
        state: present
        
    - name: 添加 Docker 仓库
      apt_repository:
        repo: &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable&quot;
        state: present
        
    - name: 安装 Docker
      apt:
        name: docker-ce
        state: present
        update_cache: yes
        
    - name: 启动 Docker 服务
      service:
        name: docker
        state: started
        enabled: yes
        
    - name: 安装 Docker Compose
      get_url:
        url: &quot;https://github.com/docker/compose/releases/download/{{ docker_compose_version }}/docker-compose-Linux-x86_64&quot;
        dest: /usr/local/bin/docker-compose
        mode: &apos;0755&apos;
        
    - name: 添加用户到 docker 组
      user:
        name: &quot;{{ ansible_user_id }}&quot;
        groups: docker
        append: yes
```

## Ansible 与 Kubernetes 集成

### 1. 使用 Ansible 部署 K8s 集群

Ansible 是部署 Kubernetes 集群的经典工具，比 kubeadm 更灵活，适合复杂环境。

**使用 kubeadm 部署 K8s：**

```yaml
---
- name: 使用 kubeadm 部署 Kubernetes 集群
  hosts: all
  become: yes
  gather_facts: yes
  
  vars:
    k8s_version: &quot;1.28&quot;
    pod_network_cidr: &quot;10.244.0.0/16&quot;
    service_network_cidr: &quot;10.96.0.0/12&quot;
    
  pre_tasks:
    - name: 禁用 Swap
      command: swapoff -a
      changed_when: yes
      
    - name: 永久禁用 Swap
      lineinfile:
        path: /etc/fstab
        regexp: &apos;^.*swap.*&apos;
        state: absent
        
    - name: 加载内核模块
      modprobe:
        name: &quot;{{ item }}&quot;
        state: present
      loop:
        - overlay
        - br_netfilter
        
    - name: 配置 sysctl 参数
      sysctl:
        name: &quot;{{ item.key }}&quot;
        value: &quot;{{ item.value }}&quot;
        state: present
        reload: yes
      loop:
        - { key: &apos;net.bridge.bridge-nf-call-iptables&apos;, value: &apos;1&apos; }
        - { key: &apos;net.bridge.bridge-nf-call-ip6tables&apos;, value: &apos;1&apos; }
        - { key: &apos;net.ipv4.ip_forward&apos;, value: &apos;1&apos; }
        
  tasks:
    - name: 安装 containerd
      block:
        - name: 安装 containerd 依赖
          apt:
            name:
              - containerd.io
              - runc
            state: present
            update_cache: yes
            
        - name: 配置 containerd
          copy:
            content: |
              [plugins.&quot;io.containerd.grpc.v1.cri&quot;]
                [plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd]
                  default_runtime_name = &quot;runc&quot;
                  [plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd.runtimes.runc]
                    runtime_type = &quot;io.containerd.runc.v2&quot;
                    [plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd.runtimes.runc.options]
                      SystemdCgroup = true
            dest: /etc/containerd/config.toml
            
        - name: 重启 containerd
          service:
            name: containerd
            state: restarted
            enabled: yes
            
      rescue:
        - name: 记录 containerd 安装失败
          debug:
            msg: &quot;containerd 安装失败，请检查系统兼容性&quot;
            
    - name: 安装 Kubernetes 组件
      apt:
        name: 
          - &quot;kubeadm={{ k8s_version }}.*&quot;
          - &quot;kubelet={{ k8s_version }}.*&quot;
          - &quot;kubectl={{ k8s_version }}.*&quot;
        state: present
        update_cache: yes
        
    - name: 锁定 kubelet 版本
      command: apt-mark hold kubelet
      args:
        creates: /var/lib/kubelet-held
        
  post_tasks:
    - name: 验证 kubelet 版本
      command: kubelet version
      register: kubelet_version
      changed_when: no
      
    - name: 显示 kubelet 版本
      debug:
        msg: &quot;{{ kubelet_version.stdout_lines }}&quot;
```

**初始化 Control Plane：**

```yaml
---
- name: 初始化 Kubernetes Control Plane
  hosts: control_plane
  become: yes
  
  vars:
    k8s_version: &quot;1.28&quot;
    apiserver_address: &quot;{{ hostvars[&apos;k8s-master-01&apos;][&apos;ansible_default_ipv4&apos;][&apos;address&apos;] }}&quot;
    pod_network_cidr: &quot;10.244.0.0/16&quot;
    
  tasks:
    - name: 初始化集群
      command: &gt;
        kubeadm init
        --apiserver-advertise-address={{ apiserver_address }}
        --pod-network-cidr={{ pod_network_cidr }}
        --cri-socket=unix:///var/run/containerd/containerd.sock
        --ignore-preflight-errors=Swap
      register: kubeadm_init
      changed_when: &quot;&apos;initialized successfully&apos; in kubeadm_init.stdout&quot;
      
    - name: 创建 .kube 目录
      file:
        path: &quot;{{ ansible_user_dir }}/.kube&quot;
        state: directory
        mode: &apos;0700&apos;
        
    - name: 复制 kubeconfig
      copy:
        src: /etc/kubernetes/admin.conf
        dest: &quot;{{ ansible_user_dir }}/.kube/config&quot;
        remote_src: yes
        mode: &apos;0600&apos;
        
    - name: 保存 join 命令
      template:
        src: kubeadm-join-command.sh.j2
        dest: /tmp/kubeadm-join-worker.sh
        mode: &apos;0755&apos;
      when: kubeadm_init is changed
      
    - name: 显示 join 命令
      debug:
        msg: &quot;请运行以下命令将 worker 节点加入集群:\n{{ kubeadm_init.stdout | regex_search(&apos;kubeadm join.*&apos;) }}&quot;
```

**加入 Worker 节点：**

```yaml
---
- name: 将 Worker 节点加入集群
  hosts: workers
  become: yes
  
  tasks:
    - name: 从 master 获取 join 命令
      fetch:
        src: /tmp/kubeadm-join-worker.sh
        dest: /tmp/kubeadm-join-worker.sh
        flat: yes
      delegate_to: k8s-master-01
      run_once: yes
      
    - name: 复制 join 脚本到 worker
      copy:
        src: /tmp/kubeadm-join-worker.sh
        dest: /tmp/kubeadm-join-worker.sh
        mode: &apos;0755&apos;
        
    - name: 执行 join 命令
      command: /tmp/kubeadm-join-worker.sh
      async: 300
      poll: 10
      register: join_result
      
    - name: 验证节点加入
      command: kubectl get nodes -o json
      delegate_to: k8s-master-01
      run_once: yes
      register: nodes_status
      retries: 5
      delay: 30
      until: nodes_status.stdout | contains(inventory_hostname)
      
    - name: 显示节点状态
      debug:
        msg: &quot;{{ inventory_hostname }} 已成功加入集群&quot;
      when: nodes_status.stdout is defined and inventory_hostname in nodes_status.stdout
```

### 2. 安装 CNI 网络插件

```yaml
---
- name: 安装 Calico 网络插件
  hosts: control_plane
  become: yes
  vars:
    calico_version: &quot;v3.26.1&quot;
    
  tasks:
    - name: 下载 Calico 配置
      get_url:
        url: &quot;https://raw.githubusercontent.com/projectcalico/calico/{{ calico_version }}/manifests/tigera-operator.yaml&quot;
        dest: /tmp/tigera-operator.yaml
        
    - name: 应用 Calico 配置
      command: kubectl apply -f /tmp/tigera-operator.yaml
      register: calico_result
      changed_when: calico_result.rc == 0
      
    - name: 等待 Calico 就绪
      command: kubectl get pods -n kube-system -l k8s-app=calico-node -o jsonpath=&apos;{.items[*].status.phase}&apos;
      register: calico_status
      retries: 30
      delay: 10
      until: &apos;&quot;Running&quot;&apos; in calico_status.stdout
      
    - name: 显示 Calico 状态
      debug:
        msg: &quot;Calico 网络插件已就绪&quot;
```

### 3. 使用 k8s 模块管理资源

Ansible 提供了丰富的 Kubernetes 模块，可以直接管理 K8s 资源。

**安装 kubernetes-python：**

```yaml
- name: 安装 Python Kubernetes 库
  pip:
    name:
      - kubernetes
      - openshift
    state: present
  delegate_to: localhost
```

**管理 Deployment：**

```yaml
---
- name: 部署应用
  hosts: control_plane
  become: no
  
  tasks:
    - name: 创建命名空间
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: myapp
            
    - name: 部署应用
      k8s:
        state: present
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: myapp
            namespace: myapp
            labels:
              app: myapp
          spec:
            replicas: 3
            selector:
              matchLabels:
                app: myapp
            template:
              metadata:
                labels:
                  app: myapp
              spec:
                containers:
                  - name: myapp
                    image: nginx:{{ nginx_version | default(&apos;1.24&apos;) }}
                    ports:
                      - containerPort: 80
                    resources:
                      requests:
                        memory: &quot;64Mi&quot;
                        cpu: &quot;250m&quot;
                      limits:
                        memory: &quot;128Mi&quot;
                        cpu: &quot;500m&quot;
                    livenessProbe:
                      httpGet:
                        path: /
                        port: 80
                      initialDelaySeconds: 30
                      periodSeconds: 10
                    readinessProbe:
                      httpGet:
                        path: /
                        port: 80
                      initialDelaySeconds: 5
                      periodSeconds: 5
                        
    - name: 创建 Service
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Service
          metadata:
            name: myapp-service
            namespace: myapp
          spec:
            selector:
              app: myapp
            ports:
              - port: 80
                targetPort: 80
                protocol: TCP
            type: ClusterIP
            
    - name: 验证部署
      k8s_info:
        api_version: v1
        kind: Pod
        namespace: myapp
        label_selectors:
          - app=myapp
      register: pods
      
    - name: 显示 Pod 状态
      debug:
        msg: &quot;部署了 {{ pods.resources | length }} 个 Pod&quot;
```

**滚动更新：**

```yaml
---
- name: 滚动更新应用
  hosts: control_plane
  
  vars:
    app_name: myapp
    namespace: myapp
    new_version: &quot;1.25&quot;
    
  tasks:
    - name: 更新 Deployment 镜像
      k8s:
        state: patched
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: &quot;{{ app_name }}&quot;
            namespace: &quot;{{ namespace }}&quot;
          spec:
            template:
              spec:
                containers:
                  - name: &quot;{{ app_name }}&quot;
                    image: nginx:{{ new_version }}
        merge_type: strategic
        
    - name: 等待滚动更新完成
      k8s_info:
        api_version: apps/v1
        kind: Deployment
        namespace: &quot;{{ namespace }}&quot;
        name: &quot;{{ app_name }}&quot;
      register: deployment
      retries: 60
      delay: 10
      until: 
        - deployment.resources[0].status.readyReplicas == deployment.resources[0].spec.replicas
        
    - name: 显示更新状态
      debug:
        msg: |
          更新完成!
          期望副本数：{{ deployment.resources[0].spec.replicas }}
          就绪副本数：{{ deployment.resources[0].status.readyReplicas }}
          可用副本数：{{ deployment.resources[0].status.availableReplicas }}
```

**管理 ConfigMap 和 Secret：**

```yaml
---
- name: 管理配置和密钥
  hosts: control_plane
  
  tasks:
    - name: 创建 ConfigMap
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: app-config
            namespace: myapp
          data:
            APP_ENV: production
            LOG_LEVEL: info
            MAX_CONNECTIONS: &quot;100&quot;
            
    - name: 从文件创建 ConfigMap
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: app-config-files
            namespace: myapp
          data:
            app.conf: &quot;{{ lookup(&apos;file&apos;, &apos;files/app.conf&apos;) }}&quot;
            nginx.conf: &quot;{{ lookup(&apos;template&apos;, &apos;templates/nginx.conf.j2&apos;) }}&quot;
            
    - name: 创建 Secret
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Secret
          metadata:
            name: app-secret
            namespace: myapp
          type: Opaque
          stringData:
            DB_PASSWORD: &quot;{{ vault_db_password }}&quot;
            API_KEY: &quot;{{ vault_api_key }}&quot;
```

### 4. Helm 集成

使用 Ansible 管理 Helm Charts。

**安装 Helm：**

```yaml
---
- name: 安装 Helm
  hosts: control_plane
  
  tasks:
    - name: 下载 Helm
      get_url:
        url: &quot;https://get.helm.sh/helm-v{{ helm_version | default(&apos;3.12.0&apos;) }}-linux-amd64.tar.gz&quot;
        dest: /tmp/helm.tar.gz
        
    - name: 解压 Helm
      unarchive:
        src: /tmp/helm.tar.gz
        dest: /tmp
        remote_src: yes
        
    - name: 安装 Helm
      copy:
        src: /tmp/linux-amd64/helm
        dest: /usr/local/bin/helm
        mode: &apos;0755&apos;
        
    - name: 验证 Helm 安装
      command: helm version --short
      register: helm_version_output
      changed_when: no
```

**使用 Helm 部署应用：**

```yaml
---
- name: 使用 Helm 部署应用
  hosts: control_plane
  
  vars:
    releases:
      - name: prometheus
        chart: prometheus
        repo: https://prometheus-community.github.io/helm-charts
        namespace: monitoring
        version: &quot;25.18.0&quot;
        values:
          server:
            persistentVolume:
              enabled: false
          alertmanager:
            persistentVolume:
              enabled: false
              
      - name: grafana
        chart: grafana
        repo: https://grafana.github.io/helm-charts
        namespace: monitoring
        version: &quot;7.0.0&quot;
        values:
          adminPassword: &quot;{{ vault_grafana_password }}&quot;
          persistence:
            enabled: false
            size: 10Gi
            
  tasks:
    - name: 创建命名空间
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: &quot;{{ item.namespace }}&quot;
      loop: &quot;{{ releases }}&quot;
      loop_control:
        label: &quot;{{ item.namespace }}&quot;
        
    - name: 添加 Helm 仓库
      command: &gt;
        helm repo add {{ item.name }}
        {{ item.repo }}
        --force-update
      loop: &quot;{{ releases }}&quot;
      loop_control:
        label: &quot;添加仓库 {{ item.name }}&quot;
      register: helm_repo_add
      changed_when: &quot;&apos;has been added&apos; in helm_repo_add.stdout&quot;
      
    - name: 部署 Helm Chart
      helm:
        name: &quot;{{ item.name }}&quot;
        chart_name: &quot;{{ item.chart }}&quot;
        release_namespace: &quot;{{ item.namespace }}&quot;
        repo_url: &quot;{{ item.repo }}&quot;
        version: &quot;{{ item.version }}&quot;
        values: &quot;{{ item.values }}&quot;
        state: present
      loop: &quot;{{ releases }}&quot;
      loop_control:
        label: &quot;部署 {{ item.name }}&quot;
        
    - name: 验证部署
      command: helm list -n {{ item.namespace }}
      loop: &quot;{{ releases }}&quot;
      register: helm_list
      changed_when: no
      
    - name: 显示部署状态
      debug:
        msg: &quot;{{ item.name }} 部署成功&quot;
      loop: &quot;{{ releases }}&quot;
```

**Helm 升级和回滚：**

```yaml
---
- name: Helm 升级和回滚管理
  hosts: control_plane
  
  vars:
    release_name: prometheus
    namespace: monitoring
    new_version: &quot;25.19.0&quot;
    
  tasks:
    - name: 升级 Helm Release
      helm:
        name: &quot;{{ release_name }}&quot;
        chart_name: prometheus
        release_namespace: &quot;{{ namespace }}&quot;
        repo_url: https://prometheus-community.github.io/helm-charts
        version: &quot;{{ new_version }}&quot;
        state: present
        update_deps: yes
        wait: yes
        timeout: 300
      register: helm_upgrade
      
    - name: 显示升级历史
      command: helm history {{ release_name }} -n {{ namespace }}
      register: helm_history
      changed_when: no
      
    - name: 回滚到上一版本（如果升级失败）
      helm:
        name: &quot;{{ release_name }}&quot;
        release_namespace: &quot;{{ namespace }}&quot;
        state: present
        revision: &quot;{{ helm_history.stdout_lines[-2].split()[0] }}&quot;
      when: helm_upgrade.failed | default(false)
```

### 5. Ingress 管理

```yaml
---
- name: 配置 Ingress
  hosts: control_plane
  
  vars:
    ingress_host: &quot;myapp.example.com&quot;
    tls_enabled: true
    tls_secret: &quot;myapp-tls&quot;
    
  tasks:
    - name: 安装 Nginx Ingress Controller
      helm:
        name: nginx-ingress
        chart_name: ingress-nginx
        repo_url: https://kubernetes.github.io/ingress-nginx
        release_namespace: ingress-nginx
        create_namespace: yes
        values:
          controller:
            service:
              type: LoadBalancer
        state: present
        
    - name: 等待 Ingress Controller 就绪
      k8s_info:
        api_version: v1
        kind: Pod
        namespace: ingress-nginx
        label_selectors:
          - app.kubernetes.io/component=controller
      register: ingress_pods
      retries: 30
      delay: 10
      until: 
        - ingress_pods.resources | length &gt; 0
        - ingress_pods.resources[0].status.phase == &apos;Running&apos;
        
    - name: 创建 Ingress
      k8s:
        state: present
        definition:
          apiVersion: networking.k8s.io/v1
          kind: Ingress
          metadata:
            name: myapp-ingress
            namespace: myapp
            annotations:
              nginx.ingress.kubernetes.io/rewrite-target: /
              nginx.ingress.kubernetes.io/ssl-redirect: &quot;{{ &apos;true&apos; if tls_enabled else &apos;false&apos; }}&quot;
          spec:
            {% if tls_enabled %}
            tls:
              - hosts:
                  - &quot;{{ ingress_host }}&quot;
                secretName: &quot;{{ tls_secret }}&quot;
            {% endif %}
            rules:
              - host: &quot;{{ ingress_host }}&quot;
                http:
                  paths:
                    - path: /
                      pathType: Prefix
                      backend:
                        service:
                          name: myapp-service
                          port:
                            number: 80
```

### 6. 完整 K8s 运维 Playbook

```yaml
---
- name: 完整的 Kubernetes 应用部署和运维
  hosts: control_plane
  become: no
  
  vars_prompt:
    - name: environment
      prompt: &quot;选择环境 (dev/staging/production)&quot;
      private: no
      
  vars_files:
    - vars/k8s_config.yml
    - vars/secrets.yml
    
  tasks:
    # 1. 环境检查
    - name: 检查 kubectl 连接
      command: kubectl cluster-info
      register: cluster_info
      changed_when: no
      
    - name: 显示集群信息
      debug:
        msg: &quot;集群已就绪&quot;
      when: cluster_info.rc == 0
      
    # 2. 准备环境
    - name: 创建命名空间
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: &quot;{{ app_namespace }}&quot;
            labels:
              environment: &quot;{{ environment }}&quot;
              
    # 3. 部署基础设施
    - name: 部署 Prometheus 监控
      include_tasks: tasks/deploy-prometheus.yml
      when: enable_monitoring | default(false) | bool
      
    # 4. 部署应用
    - name: 部署主应用
      k8s:
        state: present
        src: &quot;files/{{ app_name }}-deployment.yaml&quot;
        
    - name: 部署服务
      k8s:
        state: present
        src: &quot;files/{{ app_name }}-service.yaml&quot;
        
    # 5. 配置 Ingress
    - name: 配置入口
      include_tasks: tasks/configure-ingress.yml
      when: environment == &apos;production&apos;
      
    # 6. 健康检查
    - name: 等待应用就绪
      k8s_info:
        api_version: v1
        kind: Pod
        namespace: &quot;{{ app_namespace }}&quot;
        label_selectors:
          - app={{ app_name }}
      register: app_pods
      retries: 60
      delay: 10
      until: 
        - app_pods.resources | length &gt;= app_replicas | default(1)
        - app_pods.resources[0].status.phase == &apos;Running&apos;
        
    # 7. 验证部署
    - name: 验证服务访问
      uri:
        url: &quot;http://{{ ingress_host }}/{{ app_name }}/health&quot;
        status_code: 200
      register: health_check
      retries: 10
      delay: 5
      until: health_check.status == 200
      when: environment != &apos;dev&apos;
      
    # 8. 清理旧版本
    - name: 清理旧版本资源
      k8s:
        state: absent
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: &quot;{{ app_name }}&quot;
            namespace: &quot;{{ app_namespace }}&quot;
            labels:
              version: &quot;{{ old_version }}&quot;
      when: cleanup_old_versions | default(false) | bool
      
  handlers:
    - name: 发送部署成功通知
      uri:
        url: &quot;{{ webhook_url }}&quot;
        method: POST
        body_format: json
        body:
          text: &quot;✅ 部署成功 - {{ app_name }} ({{ environment }})&quot;
          username: Ansible
          icon_emoji: &quot;:rocket:&quot;
      listen: &quot;部署完成&quot;
      
    - name: 发送部署失败通知
      uri:
        url: &quot;{{ webhook_url }}&quot;
        method: POST
        body_format: json
        body:
          text: &quot;❌ 部署失败 - {{ app_name }} ({{ environment }})&quot;
          username: Ansible
          icon_emoji: &quot;:warning:&quot;
      listen: &quot;部署失败&quot;
```

### 7. 使用 Ansible 管理多集群

```yaml
---
- name: 管理多个 Kubernetes 集群
  hosts: all
  
  vars:
    clusters:
      production:
        kubeconfig: &quot;~/.kube/config-production&quot;
        namespace: production
      staging:
        kubeconfig: &quot;~/.kube/config-staging&quot;
        namespace: staging
      development:
        kubeconfig: &quot;~/.kube/config-dev&quot;
        namespace: development
        
  tasks:
    - name: 遍历所有集群
      block:
        - name: 设置 kubeconfig
          set_fact:
            KUBECONFIG: &quot;{{ item.value.kubeconfig }}&quot;
          
        - name: 检查集群状态
          command: kubectl cluster-info
          environment:
            KUBECONFIG: &quot;{{ item.value.kubeconfig }}&quot;
          register: cluster_status
          changed_when: no
          
        - name: 部署应用到集群
          k8s:
            state: present
            definition:
              apiVersion: apps/v1
              kind: Deployment
              metadata:
                name: myapp
                namespace: &quot;{{ item.value.namespace }}&quot;
              spec:
                replicas: &quot;{{ item.value.replicas | default(1) }}&quot;
                selector:
                  matchLabels:
                    app: myapp
                template:
                  metadata:
                    labels:
                      app: myapp
                  spec:
                    containers:
                      - name: myapp
                        image: myapp:{{ app_version }}
          environment:
            KUBECONFIG: &quot;{{ item.value.kubeconfig }}&quot;
            
      loop: &quot;{{ clusters | dict2items }}&quot;
      loop_control:
        label: &quot;{{ item.key }}&quot;
        
      rescue:
        - name: 记录集群部署失败
          debug:
            msg: &quot;集群 {{ item.key }} 部署失败&quot;
```

### 8. K8s 资源监控和告警

```yaml
---
- name: K8s 资源监控和清理
  hosts: control_plane
  
  tasks:
    - name: 收集资源使用率
      command: kubectl top nodes
      register: node_resources
      changed_when: no
      
    - name: 检查资源使用率
      set_fact:
        high_usage_nodes: &gt;-
          {{ node_resources.stdout 
          | regex_findall(&apos;(\S+)\s+\d+\s+(\d+)/\d+&apos;) 
          | select(&apos;match&apos;, &apos;80|90|100&apos;) 
          | list }}
          
    - name: 告警：节点资源使用率高
      debug:
        msg: &quot;警告：节点资源使用率超过阈值&quot;
      when: high_usage_nodes | length &gt; 0
      changed_when: no
      
    - name: 清理失败 Pod
      command: &gt;
        kubectl delete pods --field-selector=status.phase=Failed
        -n {{ item }}
      loop:
        - default
        - kube-system
        - monitoring
      ignore_errors: yes
      register: cleanup_result
      
    - name: 清理完成 Pod
      command: &gt;
        kubectl delete pods --field-selector=status.phase=Succeeded
        -n {{ item }}
      loop:
        - batch
        - jobs
      ignore_errors: yes
      
    - name: 显示清理结果
      debug:
        msg: &quot;已清理 {{ cleanup_result | length }} 个命名空间中的异常 Pod&quot;
```

### 9. 备份和恢复

```yaml
---
- name: Kubernetes 资源备份和恢复
  hosts: control_plane
  
  vars:
    backup_dir: &quot;/backup/k8s&quot;
    backup_date: &quot;{{ ansible_date_time.iso8601_basic_short }}&quot;
    
  tasks:
    - name: 创建备份目录
      file:
        path: &quot;{{ backup_dir }}/{{ backup_date }}&quot;
        state: directory
        mode: &apos;0755&apos;
        
    - name: 备份所有命名空间
      command: &gt;
        kubectl get all --all-namespaces -o yaml
        &gt; {{ backup_dir }}/{{ backup_date }}/all-resources.yaml
      register: backup_all
      
    - name: 备份 ConfigMaps 和 Secrets
      command: &gt;
        kubectl get configmaps,secrets --all-namespaces -o yaml
        &gt; {{ backup_dir }}/{{ backup_date }}/configmaps-secrets.yaml
      register: backup_configs
      
    - name: 压缩备份文件
      command: &gt;
        tar -czf {{ backup_dir }}/backup-{{ backup_date }}.tar.gz
        -C {{ backup_dir }} {{ backup_date }}
        
    - name: 清理旧备份（保留 7 天）
      find:
        paths: &quot;{{ backup_dir }}&quot;
        patterns: &quot;*.tar.gz&quot;
        age: &quot;7d&quot;
      register: old_backups
      
    - name: 删除旧备份
      file:
        path: &quot;{{ item.path }}&quot;
        state: absent
      loop: &quot;{{ old_backups.files }}&quot;
      when: old_backups.files | length &gt; 0
      
    - name: 显示备份状态
      debug:
        msg: &quot;备份完成：{{ backup_dir }}/backup-{{ backup_date }}.tar.gz&quot;
```

## 调试和排错

### 1. 调试技巧

```bash
# 检查模式（不实际执行）
ansible-playbook site.yml --check

# 差异模式（显示文件变化）
ansible-playbook site.yml --diff

# 提高详细程度
ansible-playbook site.yml -v    # 一般信息
ansible-playbook site.yml -vv   # 更多细节
ansible-playbook site.yml -vvv  # 详细输出
ansible-playbook site.yml -vvvv # 调试级别

# 在特定任务后中断
ansible-playbook site.yml --step

# 执行到特定标签
ansible-playbook site.yml --start-at-task=&quot;任务名称&quot;
```

### 2. 常用调试任务

```yaml
- name: 调试变量
  debug:
    var: my_variable
    
- name: 显示主机 facts
  setup: {}
  register: facts
  
- name: 显示 facts
  debug:
    var: facts.ansible_facts.ansible_distribution

- name: 失败并显示消息
  fail:
    msg: &quot;调试中断点：{{ some_variable }}&quot;
    
- name: 等待用户输入
  pause:
    prompt: &quot;按回车继续...&quot;
```

### 3. 性能分析

```bash
# 启用性能回调插件
[defaults]
stdout_callback = profile_tasks
callback_whitelist = profile_tasks

# 运行 Playbook 查看每个任务耗时
ansible-playbook site.yml

# 输出示例：
# PLAY RECAP *****************************************************************
# ...
# 
# TASK PROFILE (time in seconds):
# - 安装 Nginx: 12.345
# - 配置 Nginx: 2.123
# - 启动服务：1.456
```

## 总结

### 核心要点

1. **无代理架构** — 通过 SSH 连接，简化部署
2. **幂等性** — 确保多次执行结果一致
3. **YAML 语法** — 直观易读的 Playbook
4. **角色管理** — 模块化、可复用的代码组织
5. **变量优先级** — 灵活配置管理
6. **错误处理** — block/rescue/always 机制
7. **安全实践** — Ansible Vault 加密敏感信息
8. **K8s 集成** — 原生支持 Kubernetes 资源管理
9. **Helm 管理** — 自动化 Helm Charts 部署和升级
10. **多集群管理** — 统一管理多个 K8s 集群

### Ansible + Kubernetes 最佳实践

- ✅ 使用 `k8s` 模块直接管理资源，避免 YAML 文件管理
- ✅ 使用 Helm 管理复杂应用（如 Prometheus、Grafana）
- ✅ 实现滚动更新和健康检查
- ✅ 配置 Ingress 和 TLS 自动化
- ✅ 定期备份 K8s 资源
- ✅ 监控资源使用率和异常 Pod
- ✅ 使用多集群管理实现环境隔离

### 学习路径

1. ✅ 掌握基础概念和 ad-hoc 命令
2. ✅ 编写简单 Playbook
3. ✅ 学习变量、模板、条件执行
4. ✅ 创建和使用角色
5. ✅ 掌握高级特性（异步、滚动更新等）
6. ✅ **学习 K8s 集成** — 部署集群、管理资源
7. ✅ **学习 Helm 集成** — 自动化应用部署
8. ✅ **实践多集群管理** — 生产环境运维

### 下一步

- [ ] 学习 Ansible Galaxy 使用
- [ ] 掌握动态库存编写
- [ ] 自定义模块和插件开发
- [ ] 集成 CI/CD 流水线（GitLab CI + Ansible）
- [ ] 学习 Ansible Tower/Automation Controller
- [ ] **深入学习 K8s Operator 开发**
- [ ] **实践 GitOps 工作流（ArgoCD + Ansible）**
- [ ] 参与开源角色贡献

---

**更新时间**: 2026-05-14  
**阅读时间**: 45 分钟  
**适用场景**: 自动化运维、配置管理、批量部署、Kubernetes 运维、基础设施即代码

### 参考资源

- [Ansible 官方文档](https://docs.ansible.com/)
- [Ansible Galaxy](https://galaxy.ansible.com/)
- [Ansible 最佳实践](https://docs.ansible.com/ansible/latest/user_guide/best_practices.html)
- [Ansible 中文社区](https://www.ansible.com.cn/)
- [Ansible Kubernetes 模块](https://docs.ansible.com/ansible/latest/collections/kubernetes/core/k8s_module.html)
- [Ansible Helm 模块](https://docs.ansible.com/ansible/latest/collections/community/general/helm_module.html)
- [Kubernetes 官方文档](https://kubernetes.io/docs/)
</content:encoded><category>Ansible</category><category>DevOps</category></item><item><title>从零搭建 Kubernetes 集群 (ARM64 Linux 离线版)</title><link>https://edwardchen.com/blog/k8s-cluster-setup-arm64/</link><guid isPermaLink="true">https://edwardchen.com/blog/k8s-cluster-setup-arm64/</guid><description>在 ARM64 架构的离线 Linux 服务器上从零搭建生产级 Kubernetes 集群，使用本地源完成 kubeadm 高可用部署。</description><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>
# 从零搭建 Kubernetes 集群 (ARM64 Linux 离线版)

本文将详细介绍如何在**无外网访问**的 ARM64 架构 Linux 服务器上从零搭建 Kubernetes 集群，包括离线资源准备、本地源配置、节点配置、集群初始化和高可用部署等完整流程。

## 为什么选择离线部署？

离线部署在内网环境、安全要求高的场景中非常重要：

- **安全性** — 避免外部网络攻击
- **合规性** — 满足等保、金融等行业要求
- **稳定性** — 不受外网波动影响
- **可控性** — 完全掌控软件版本和来源

## 环境准备

### 硬件要求

**最小配置：**
- **控制节点 (Control Plane)**：2 核 CPU, 4GB RAM, 50GB 磁盘
- **工作节点 (Worker Node)**：2 核 CPU, 4GB RAM, 50GB 磁盘
- **跳板机/资源机**（可选）：4 核 CPU, 8GB RAM, 200GB 磁盘（用于下载离线包）

**推荐配置（生产环境）：**
- **控制节点**：4 核 CPU, 8GB RAM, 100GB 磁盘
- **工作节点**：4 核 CPU, 16GB RAM, 200GB 磁盘

### 软件要求

- **操作系统**：Ubuntu 22.04 LTS / Debian 11+ / Rocky Linux 9+ (ARM64)
- **内核版本**：5.15+
- **Kubernetes 版本**：v1.28+
- **容器运行时**：containerd 1.6+

### 网络规划

```
节点角色        IP 地址           主机名
─────────────────────────────────────────
Control Plane   192.168.1.100     k8s-master-01
Worker Node 1   192.168.1.101     k8s-worker-01
Worker Node 2   192.168.1.102     k8s-worker-02
跳板机/资源机   192.168.1.200     k8s-jump-host  (可上网)
```

## 第一步：离线资源准备（在有网的机器上）

### 1.1 准备环境

```bash
# 在可以上网的机器上执行
mkdir -p /k8s-offline/{packages,images,certs}
cd /k8s-offline
```

### 1.2 下载 Kubernetes 组件

```bash
# 下载 kubeadm, kubelet, kubectl (ARM64)
VERSION=1.28.0
ARCH=arm64

# 方法 1：从 k8s 官方下载
wget https://pkgs.k8s.io/core:/stable:/v${VERSION}/deb/pool/main/k/kubeadm/kubeadm_${VERSION}-00_arm64.deb
wget https://pkgs.k8s.io/core:/stable:/v${VERSION}/deb/pool/main/k/kubelet/kubelet_${VERSION}-00_arm64.deb
wget https://pkgs.k8s.io/core:/stable:/v${VERSION}/deb/pool/main/k/kubectl/kubectl_${VERSION}-00_arm64.deb

# 方法 2：使用 apt download（推荐）
sudo apt update
sudo apt download kubeadm=${VERSION}-00 kubelet=${VERSION}-00 kubectl=${VERSION}-00

# 下载依赖包
sudo apt download -d --no-install kubeadm=${VERSION}-00 kubelet=${VERSION}-00 kubectl=${VERSION}-00

# 移动到离线目录
sudo cp /var/cache/apt/archives/*.deb ./packages/
```

### 1.3 下载容器运行时

```bash
# 下载 containerd 及其依赖
sudo apt download -d --no-install containerd.io

# 或者手动下载
wget https://github.com/containerd/containerd/releases/download/v1.6.22/containerd-1.6.22-linux-arm64.tar.gz

# 移动所有 deb 包
sudo cp /var/cache/apt/archives/*.deb ./packages/
```

### 1.4 下载 CNI 网络插件

```bash
# 下载 Calico YAML 文件（离线部署需要）
wget https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/tigera-operator.yaml -O calico-operator.yaml
wget https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/custom-resources.yaml -O calico-config.yaml

# 或者下载 Flannel
wget https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml -O flannel.yaml

# 下载所需镜像（需要预先 pull）
# 注意：这些镜像需要在有 Docker 的环境中 pull
docker pull calico/operator:v1.30.3
docker pull calico/node:v3.26.1
docker pull calico/cni:v3.26.1
docker pull calico/kube-controllers:v3.26.1
docker pull calico/pod2daemon-flexvol:v3.26.1

# 导出镜像
docker save calico/operator:v1.30.3 -o calico-operator.tar
docker save calico/node:v3.26.1 -o calico-node.tar
docker save calico/cni:v3.26.1 -o calico-cni.tar
docker save calico/kube-controllers:v3.26.1 -o calico-kube-controllers.tar
docker save calico/pod2daemon-flexvol:v3.26.1 -o calico-pod2daemon-flexvol.tar
```

### 1.5 打包离线资源

```bash
# 创建离线安装包
cd /k8s-offline
tar -czvf k8s-offline-packages.tar.gz \
    packages/ \
    calico-operator.yaml \
    calico-config.yaml \
    *.tar

# 传输到离线服务器
# 方法 1：使用 USB 盘
cp k8s-offline-packages.tar.gz /media/usb/

# 方法 2：使用 scp（如果跳板机能访问内网）
scp k8s-offline-packages.tar.gz root@192.168.1.100:/root/
```

## 第二步：离线环境配置（在所有节点上）

### 2.1 系统初始化

```bash
# 更新系统（如果有本地源）
sudo apt update &amp;&amp; sudo apt upgrade -y

# 安装必要工具（离线）
cd /root/k8s-offline/packages
sudo dpkg -i apt-transport-https*.deb ca-certificates*.deb curl*.deb gnupg*.deb lsb-release*.deb

# 禁用交换分区（Kubernetes 要求）
sudo swapoff -a
sudo sed -i &apos;/ swap / s/^\(.*\)$/#\1/g&apos; /etc/fstab

# 配置主机名和 hosts
sudo hostnamectl set-hostname k8s-master-01  # 根据实际节点修改
echo &quot;192.168.1.100 k8s-master-01&quot; | sudo tee -a /etc/hosts
echo &quot;192.168.1.101 k8s-worker-01&quot; | sudo tee -a /etc/hosts
echo &quot;192.168.1.102 k8s-worker-02&quot; | sudo tee -a /etc/hosts
```

### 2.2 配置内核参数

```bash
# 加载必要的内核模块
cat &lt;&lt;EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# 设置所需的 sysctl 参数
cat &lt;&lt;EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 应用配置
sudo sysctl --system
```

### 2.3 安装容器运行时（离线）

```bash
# 安装 containerd 依赖
cd /root/k8s-offline/packages
sudo dpkg -i libseccomp2*.deb runc*.deb containerd.io*.deb

# 或者使用 tar 包安装
tar -xzf containerd-1.6.22-linux-arm64.tar.gz -C /usr/local
sudo ln -s /usr/local/bin/containerd /usr/local/bin/
sudo ln -s /usr/local/bin/ctr /usr/local/bin/
sudo ln -s /usr/local/bin/containerd-shim-runc-v2 /usr/local/bin/

# 配置 containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

# 修改 sysctl 配置以支持 overlay
sudo sed -i &apos;s/SystemdCgroup = false/SystemdCgroup = true/g&apos; /etc/containerd/config.toml

# 创建 systemd 服务
cat &lt;&lt;EOF | sudo tee /etc/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Restart=always
RestartSec=5
Delegate=yes
KillMode=process
OOMScoreAdjust=-999
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity

[Install]
WantedBy=multi-user.target
EOF

# 启动 containerd
sudo systemctl daemon-reload
sudo systemctl enable containerd
sudo systemctl restart containerd
```

### 2.4 安装 Kubernetes 组件（离线）

```bash
# 安装 kubeadm, kubelet, kubectl
cd /root/k8s-offline/packages
sudo dpkg -i kubelet*.deb kubeadm*.deb kubectl*.deb

# 如果有依赖问题，安装所有包
sudo dpkg -i *.deb

# 验证安装
kubeadm version
kubelet version
kubectl version --client

# 锁定版本（防止误升级）
sudo apt-mark hold kubeadm kubelet kubectl
```

## 第三步：初始化 Control Plane（离线）

### 3.1 在控制节点上执行

```bash
# 初始化集群（使用本地 CRI socket）
sudo kubeadm init \
    --apiserver-advertise-address=192.168.1.100 \
    --pod-network-cidr=10.244.0.0/16 \
    --cri-socket=unix:///var/run/containerd/containerd.sock \
    --ignore-preflight-errors=Swap,CRI

# 保存 join 命令（后续需要在 worker 节点执行）
# 输出示例：
# kubeadm join 192.168.1.100:6443 --token &lt;token&gt; \
#     --discovery-token-ca-cert-hash sha256:&lt;hash&gt;
```

### 3.2 配置 kubectl

```bash
# 创建 .kube 目录
mkdir -p $HOME/.kube

# 复制配置文件
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 验证集群状态
kubectl get nodes
# 输出：NAME            STATUS     ROLES           AGE   VERSION
#       k8s-master-01   NotReady   control-plane   2m    v1.28.0
```

### 3.3 导入 CNI 镜像（离线）

```bash
# 导入 Calico 镜像
cd /root/k8s-offline
sudo ctr -n k8s.io images import calico-operator.tar
sudo ctr -n k8s.io images import calico-node.tar
sudo ctr -n k8s.io images import calico-cni.tar
sudo ctr -n k8s.io images import calico-kube-controllers.tar
sudo ctr -n k8s.io images import calico-pod2daemon-flexvol.tar

# 验证镜像
sudo ctr -n k8s.io images ls | grep calico
```

### 3.4 安装 CNI 网络插件（离线）

```bash
# 应用 Calico 配置（使用本地文件）
kubectl apply -f /root/k8s-offline/calico-operator.yaml
kubectl apply -f /root/k8s-offline/calico-config.yaml

# 或者使用 Flannel
# kubectl apply -f /root/k8s-offline/flannel.yaml
```

### 3.5 验证节点状态

```bash
# 等待节点就绪（约 2-3 分钟）
watch kubectl get nodes

# 输出：
# NAME            STATUS   ROLES           AGE   VERSION
# k8s-master-01   Ready    control-plane   5m    v1.28.0
```

## 第四步：加入 Worker 节点（离线）

### 4.1 在 Worker 节点上安装组件

```bash
# 在所有 worker 节点上执行相同的安装步骤
# 参考第二步：离线环境配置

# 安装 containerd
cd /root/k8s-offline/packages
sudo dpkg -i containerd.io*.deb
sudo systemctl enable containerd
sudo systemctl restart containerd

# 安装 kubernetes 组件
sudo dpkg -i kubelet*.deb kubeadm*.deb kubectl*.deb
```

### 4.2 导入必要镜像

```bash
# 在 worker 节点上导入 Calico 镜像
cd /root/k8s-offline
sudo ctr -n k8s.io images import calico-node.tar
sudo ctr -n k8s.io images import calico-cni.tar
sudo ctr -n k8s.io images import calico-pod2daemon-flexvol.tar
```

### 4.3 加入集群

```bash
# 在 k8s-worker-01 和 k8s-worker-02 上分别执行
sudo kubeadm join 192.168.1.100:6443 \
    --token &lt;token&gt; \
    --discovery-token-ca-cert-hash sha256:&lt;hash&gt;
```

### 4.4 验证集群

```bash
# 在控制节点上查看所有节点
kubectl get nodes -o wide

# 输出：
# NAME            STATUS   ROLES           AGE   VERSION   INTERNAL-IP    OS-IMAGE             KERNEL-VERSION    CONTAINER-RUNTIME
# k8s-master-01   Ready    control-plane   10m   v1.28.0   192.168.1.100 Ubuntu 22.04.3 LTS   5.15.0-91-generic   containerd://1.6.22
# k8s-worker-01   Ready    &lt;none&gt;          5m    v1.28.0   192.168.1.101 Ubuntu 22.04.3 LTS   5.15.0-91-generic   containerd://1.6.22
# k8s-worker-02   Ready    &lt;none&gt;          3m    v1.28.0   192.168.1.102 Ubuntu 22.04.3 LTS   5.15.0-91-generic   containerd://1.6.22
```

## 第五步：部署测试应用（离线）

### 5.1 准备应用镜像

```bash
# 在有网的机器上下载 nginx 镜像
docker pull nginx:alpine
docker save nginx:alpine -o nginx-alpine.tar

# 传输到 k8s 集群
scp nginx-alpine.tar root@192.168.1.100:/root/

# 在所有节点上导入镜像
sudo ctr -n k8s.io images import nginx-alpine.tar
```

### 5.2 部署 Nginx

```bash
# 创建 nginx 部署
kubectl create deployment nginx --image=nginx:alpine --replicas=3

# 创建服务
kubectl expose deployment nginx --type=NodePort --port=80 --target-port=80

# 查看部署状态
kubectl get pods -o wide
kubectl get svc
```

### 5.3 验证服务

```bash
# 测试服务访问
curl http://192.168.1.100:&lt;NodePort&gt;

# 查看 Pod 日志
kubectl logs -l app=nginx

# 进入 Pod 容器
kubectl exec -it &lt;pod-name&gt; -- /bin/sh
```

## 第六步：离线安装常用工具

### 6.1 安装 Helm（离线）

```bash
# 在有网的机器上下载 Helm
wget https://get.helm.sh/helm-v3.12.0-linux-arm64.tar.gz
tar -xzf helm-v3.12.0-linux-arm64.tar.gz
cp linux-arm64/helm /tmp/

# 传输到离线服务器
scp /tmp/helm root@192.168.1.100:/usr/local/bin/

# 在所有节点上安装
sudo chmod +x /usr/local/bin/helm
helm version
```

### 6.2 安装 k9s（离线）

```bash
# 在有网的机器上下载 k9s
wget https://github.com/derailed/k9s/releases/download/v0.30.9/k9s_Linux_arm64.tar.gz
tar -xzf k9s_Linux_arm64.tar.gz
cp k9s /tmp/

# 传输到离线服务器
scp /tmp/k9s root@192.168.1.100:/usr/local/bin/

# 在所有节点上安装
sudo chmod +x /usr/local/bin/k9s
k9s version
```

### 6.3 部署 Portainer（离线）

```bash
# 在有网的机器上下载 Portainer 镜像
helm repo add portainer https://portainer.github.io/k8s
helm pull portainer/portainer --version 1.8.0
tar -xzf portainer-1.8.0.tgz

# 导出所需镜像
docker pull portainer/agent:2.19.0
docker pull portainer/kube-state-metrics:2.8.0
docker save portainer/agent:2.19.0 -o portainer-agent.tar
docker save portainer/kube-state-metrics:2.8.0 -o portainer-kube-state-metrics.tar

# 传输到离线环境
scp portainer/ root@192.168.1.100:/root/
scp portainer-*.tar root@192.168.1.100:/root/

# 在离线环境导入镜像并部署
sudo ctr -n k8s.io images import portainer-agent.tar
sudo ctr -n k8s.io images import portainer-kube-state-metrics.tar

kubectl create namespace portainer
cd /root/portainer/portainer
kubectl apply -f templates/
```

## 常见问题排查

### 问题 1: dpkg 依赖错误

```bash
# 错误：dpkg: 处理包 xxx 时出错：依赖问题
# 解决：按顺序安装所有依赖包

cd /root/k8s-offline/packages
# 先安装基础依赖
sudo dpkg -i lib*.*.deb
# 再安装运行时
sudo dpkg -i runc*.deb
# 最后安装主包
sudo dpkg -i containerd.io*.deb

# 或者使用 --force-depends（不推荐）
sudo dpkg -i --force-depends package.deb
```

### 问题 2: 节点状态为 NotReady

```bash
# 查看 kubelet 日志
sudo journalctl -u kubelet -f

# 查看容器状态
sudo crictl ps -a

# 检查 CRI socket
ls -la /var/run/containerd/containerd.sock

# 检查镜像是否导入
sudo ctr -n k8s.io images ls
```

### 问题 3: Pod 镜像拉取失败

```bash
# 错误：ImagePullBackOff / ErrImagePull
# 原因：离线环境没有镜像

# 解决：确保镜像已导入所有节点
# 在控制节点检查
sudo ctr -n k8s.io images ls

# 在 worker 节点导入
sudo ctr -n k8s.io images import &lt;image&gt;.tar

# 重启 pod
kubectl delete pod &lt;pod-name&gt;
```

### 问题 4: CNI 插件失败

```bash
# 检查 Calico 状态
kubectl get pods -n kube-system | grep calico

# 查看 Calico 日志
kubectl logs -n kube-system &lt;calico-pod&gt;

# 检查镜像是否导入
sudo ctr -n k8s.io images ls | grep calico
```

## 离线镜像管理最佳实践

### 7.1 镜像清单管理

```bash
# 创建镜像清单文件
cat &lt;&lt;EOF &gt; image-list.txt
nginx:alpine
calico/operator:v1.30.3
calico/node:v3.26.1
calico/cni:v3.26.1
calico/kube-controllers:v3.26.1
calico/pod2daemon-flexvol:v3.26.1
EOF

# 批量导出镜像
while read image; do
    docker save &quot;$image&quot; -o &quot;${image//\//_}.tar&quot;
done &lt; image-list.txt

# 批量导入镜像
for img in *.tar; do
    sudo ctr -n k8s.io images import &quot;$img&quot;
done
```

### 7.2 本地镜像仓库（可选）

```bash
# 在有网的机器上搭建本地 Harbor 或 registry
docker run -d -p 5000:5000 --name registry registry:2

# 重新标记镜像
docker tag nginx:alpine 192.168.1.200:5000/nginx:alpine
docker push 192.168.1.200:5000/nginx:alpine

# 在 k8s 中配置 insecureRegistry
cat &lt;&lt;EOF | sudo tee /etc/containerd/config.toml | grep -A5 hosts
[plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry]
  [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.configs.&quot;192.168.1.200:5000&quot;.tls]
    insecure_skip_verify = true
EOF
```

## 高可用集群扩展

### 8.1 添加第二个 Control Plane（离线）

```bash
# 在第一个控制节点上获取控制平面 join 命令
sudo kubeadm token create --print-join-command --control-plane

# 在第二个控制节点上执行相同的安装步骤后加入
sudo &lt;join-command&gt;
```

### 8.2 部署 HA Proxy（离线）

```bash
# 在有网的机器上下载 HA Proxy
sudo apt download -d --no-install haproxy
scp /var/cache/apt/archives/haproxy*.deb root@192.168.1.200:/tmp/

# 在所有控制节点前部署 HA Proxy
cd /tmp
sudo dpkg -i haproxy*.deb

cat &lt;&lt;EOF &gt; /etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    maxconn 256

defaults
    log global
    mode tcp
    option dontlognull
    timeout connect 5000
    timeout client 50000
    timeout server 50000

frontend kubernetes-api
    bind *:6443
    default_backend kubernetes-api-servers

backend kubernetes-api-servers
    balance roundrobin
    server k8s-master-01 192.168.1.100:6443 check
    server k8s-master-02 192.168.1.103:6443 check
EOF

sudo systemctl enable haproxy
sudo systemctl restart haproxy
```

## 总结

通过以上步骤，我们成功在**完全离线**的 ARM64 架构 Linux 服务器上搭建了一个完整的 Kubernetes 集群。

### 技术栈总结

| 组件 | 版本 | 说明 | 离线方式 |
|------|------|------|----------|
| 操作系统 | Ubuntu 22.04 LTS | ARM64 架构 | - |
| Kubernetes | v1.28.0 | 容器编排平台 | deb 包离线安装 |
| 容器运行时 | containerd 1.6.22 | 容器运行时 | tar 包/deb 包 |
| 网络插件 | Calico v3.26.1 | CNI 网络 | YAML + 镜像导入 |
| 包管理器 | Helm v3.12 | K8s 包管理 | 二进制分发 |

### 核心步骤回顾

1. ✅ **离线资源准备** — 在有网机器下载所有包和镜像
2. ✅ **传输离线包** — 使用 USB 或跳板机传输
3. ✅ **系统初始化** — 配置内核参数，禁用 Swap
4. ✅ **离线安装运行时** — containerd 离线安装
5. ✅ **离线安装 K8s** — deb 包离线安装
6. ✅ **初始化 Control Plane** — kubeadm init
7. ✅ **导入 CNI 镜像** — 离线导入 Calico 镜像
8. ✅ **安装网络插件** — 本地 YAML 文件
9. ✅ **加入 Worker 节点** — kubeadm join
10. ✅ **部署测试应用** — 离线镜像导入

### 离线部署关键点

- **镜像管理** — 提前规划所需镜像清单
- **依赖处理** — 使用 `apt download -d` 获取完整依赖
- **版本锁定** — 确保所有节点版本一致
- **本地源** — 可选配置本地 apt 源加速部署
- **文档记录** — 记录所有版本号和配置参数

### 下一步优化

- [ ] 配置本地 apt 源（apt-ftparchive）
- [ ] 部署本地镜像仓库（Harbor/registry）
- [ ] 配置持久化存储（Local Path Provisioner）
- [ ] 部署 Ingress Controller（Nginx Ingress）
- [ ] 设置监控告警（Prometheus + Grafana 离线版）
- [ ] 实现备份恢复（Velero 离线配置）
- [ ] 配置 GitLab CI 离线流水线

### 离线资源清单模板

```bash
# 保存此清单用于未来部署
K8s 版本：v1.28.0
架构：arm64
日期：2026-05-14

必需包:
- kubeadm_1.28.0-00_arm64.deb
- kubelet_1.28.0-00_arm64.deb
- kubectl_1.28.0-00_arm64.deb
- containerd.io_1.6.22_arm64.deb

必需镜像:
- calico/operator:v1.30.3
- calico/node:v3.26.1
- calico/cni:v3.26.1
- calico/kube-controllers:v3.26.1
- calico/pod2daemon-flexvol:v3.26.1
- nginx:alpine (测试用)
```

---

**更新时间**: 2026-05-14  
**阅读时间**: 20 分钟  
**适用场景**: 内网环境、等保要求、金融系统、离线服务器、ARM64 架构
</content:encoded><category>Kubernetes</category><category>DevOps</category></item><item><title>LangChain 与 LangGraph 深度解析</title><link>https://edwardchen.com/blog/langchain-langgraph-tutorial/</link><guid isPermaLink="true">https://edwardchen.com/blog/langchain-langgraph-tutorial/</guid><description>从基础到进阶，全面掌握 LangChain 框架和 LangGraph 状态机，构建复杂的 AI Agent 工作流。</description><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>
# LangChain 与 LangGraph 深度解析

本文将深入探讨 LangChain 框架和 LangGraph 状态机，从基础概念到高级应用，帮助你构建复杂、可维护的 AI Agent 系统。

## 为什么需要 LangChain 和 LangGraph？

### 传统 LLM 应用的局限性

直接使用 LLM 面临以下挑战：

- **状态管理困难** — 多轮对话难以维护上下文
- **工作流复杂** — 多步骤任务编排混乱
- **可观测性差** — 难以调试和监控
- **缺乏循环** — 无法实现迭代优化
- **人类介入难** — 难以实现人机协作

### LangChain 的解决方案

**LangChain** 提供了：
- 📦 **模块化组件** — 链、提示词、记忆、工具
- 🔗 **编排能力** — 将多个组件串联
- 🧠 **记忆管理** — 维护对话历史
- 🛠️ **工具集成** — 连接外部 API 和数据源

### LangGraph 的进阶能力

**LangGraph** 在 LangChain 基础上增加了：
- 🔄 **循环工作流** — 支持迭代和重试
- 🎯 **状态机** — 明确的状态转换
- 👥 **人机协作** — 人类审核和干预
- 📊 **可视化** — 工作流图可视化
- 💾 **持久化** — 检查点和恢复

## LangChain 核心概念

### 1. 链 (Chains)

链是 LangChain 的基本构建块，将多个组件串联起来。

```python
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

# 创建提示模板
prompt = PromptTemplate(
    input_variables=[&quot;topic&quot;],
    template=&quot;请写一篇关于 {topic} 的短文，不超过 200 字。&quot;
)

# 创建 LLM
llm = OpenAI(temperature=0.7)

# 创建链
chain = LLMChain(llm=llm, prompt=prompt)

# 执行
result = chain.run(&quot;人工智能&quot;)
print(result)
```

### 2. 提示词管理 (Prompts)

```python
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

# 聊天提示模板
prompt = ChatPromptTemplate.from_messages([
    (&quot;system&quot;, &quot;你是一个有帮助的助手。&quot;),
    MessagesPlaceholder(&quot;chat_history&quot;),  # 对话历史
    (&quot;human&quot;, &quot;{input}&quot;)  # 用户输入
])

# 格式化提示
messages = prompt.format_messages(
    chat_history=[...],
    input=&quot;你好&quot;
)
```

### 3. 记忆 (Memory)

记忆组件维护对话历史，实现多轮对话。

```python
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

# 创建带记忆的链
memory = ConversationBufferMemory(
    memory_key=&quot;chat_history&quot;,
    return_messages=True
)

conversation = ConversationChain(
    llm=llm,
    memory=memory,
    verbose=True
)

# 多轮对话
conversation.predict(input=&quot;我叫 Edward&quot;)
conversation.predict(input=&quot;我做什么工作的？&quot;)
```

**记忆类型对比：**

| 类型                             | 说明        | 适用场景        |
| ------------------------------ | --------- | ----------- |
| ConversationBufferMemory       | 存储所有历史消息  | 简单对话        |
| ConversationBufferWindowMemory | 只保留最近 N 条 | 长对话节省 token |
| ConversationSummaryMemory      | 摘要历史对话    | 超长对话        |
| EntityMemory                   | 提取实体信息    | 需要记住事实的对话   |

### 4. 工具 (Tools)

工具让 LLM 能够调用外部函数和 API。

```python
from langchain.agents import tool

@tool
def get_current_weather(city: str) -&gt; str:
    &quot;&quot;&quot;获取当前天气信息&quot;&quot;&quot;
    # 模拟天气数据
    return f&quot;{city} 当前温度 25°C，晴天&quot;

@tool
def search_web(query: str) -&gt; str:
    &quot;&quot;&quot;搜索网络信息&quot;&quot;&quot;
    # 调用搜索引擎 API
    return f&quot;搜索结果：{query} 的相关信息&quot;

tools = [get_current_weather, search_web]
```

### 5. 代理 (Agents)

代理能够自主选择和使用工具。

```python
from langchain.agents import initialize_agent, AgentType

# 初始化代理
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    handle_parsing_errors=True,
    verbose=True
)

# 执行任务
response = agent.run(&quot;北京的天气怎么样？&quot;)
print(response)
```

## LangGraph 核心概念

### 什么是 LangGraph？

LangGraph 是基于 LangChain 的**有向图**框架，用于构建多 Agent、多步骤的 AI 应用。

**核心特性：**
- **状态机** — 明确定义状态和转换
- **循环** — 支持迭代和重试
- **人机协作** — 人类审核节点
- **持久化** — 检查点和恢复
- **可视化** — 自动生成流程图

### 1. 图的基本结构

```python
from langgraph.graph import StateGraph, END

# 定义状态
class State(TypedDict):
    input: str
    output: str
    iterations: int
    approval: bool

# 创建图
workflow = StateGraph(State)

# 添加节点
workflow.add_node(&quot;agent&quot;, agent_node)
workflow.add_node(&quot;human&quot;, human_approval_node)
workflow.add_node(&quot;refine&quot;, refinement_node)

# 添加边
workflow.add_edge(&quot;agent&quot;, &quot;human&quot;)
workflow.add_conditional_edges(
    &quot;human&quot;,
    should_refine,  # 条件函数
    {
        &quot;yes&quot;: &quot;refine&quot;,
        &quot;no&quot;: END
    }
)

# 设置入口点
workflow.set_entry_point(&quot;agent&quot;)

# 编译图
app = workflow.compile()
```

### 2. 状态管理

状态是 LangGraph 的核心，在节点间传递。

```python
from typing import TypedDict, Annotated, Sequence
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage

# 定义状态
class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]
    context: dict
    loop_count: int
    human_approved: bool

# 节点函数接收和返回状态
def agent_node(state: AgentState) -&gt; AgentState:
    # 访问状态
    messages = state[&quot;messages&quot;]
    
    # 处理逻辑
    response = llm.invoke(messages)
    
    # 返回更新的状态
    return {&quot;messages&quot;: [response]}

def human_approval_node(state: AgentState) -&gt; AgentState:
    # 等待人类审批
    approval = wait_for_human_approval(state[&quot;messages&quot;])
    
    return {&quot;human_approved&quot;: approval}
```

### 3. 条件边

条件边根据状态决定下一步走向。

```python
def should_continue(state: AgentState) -&gt; Literal[&quot;tools&quot;, &quot;end&quot;]:
    &quot;&quot;&quot;决定是否继续执行工具&quot;&quot;&quot;
    last_message = state[&quot;messages&quot;][-1]
    
    if last_message.tool_calls:
        return &quot;tools&quot;
    return &quot;end&quot;

def check_approval(state: AgentState) -&gt; Literal[&quot;approve&quot;, &quot;reject&quot;, &quot;revise&quot;]:
    &quot;&quot;&quot;检查人类审批结果&quot;&quot;&quot;
    if state[&quot;human_approved&quot;]:
        return &quot;approve&quot;
    elif state[&quot;loop_count&quot;] &gt; 3:
        return &quot;reject&quot;
    return &quot;revise&quot;

# 添加条件边
workflow.add_conditional_edges(
    &quot;agent&quot;,
    should_continue,
    {
        &quot;tools&quot;: &quot;tool_executor&quot;,
        &quot;end&quot;: END
    }
)

workflow.add_conditional_edges(
    &quot;human&quot;,
    check_approval,
    {
        &quot;approve&quot;: END,
        &quot;reject&quot;: &quot;end_with_error&quot;,
        &quot;revise&quot;: &quot;agent&quot;
    }
)
```

### 4. 循环和迭代

LangGraph 天然支持循环，通过条件边实现。

```python
# 定义最大迭代次数
MAX_ITERATIONS = 5

def iterative_refinement(state: AgentState) -&gt; AgentState:
    &quot;&quot;&quot;迭代优化答案&quot;&quot;&quot;
    if state[&quot;loop_count&quot;] &gt;= MAX_ITERATIONS:
        return {&quot;loop_count&quot;: state[&quot;loop_count&quot;]}
    
    # 生成改进版本
    improved = refine_answer(state[&quot;messages&quot;])
    
    return {
        &quot;messages&quot;: [improved],
        &quot;loop_count&quot;: state[&quot;loop_count&quot;] + 1
    }

# 添加循环边
workflow.add_edge(&quot;refine&quot;, &quot;agent&quot;)  # 回到 agent 节点
```

### 5. 持久化和检查点

```python
from langgraph.checkpoint.memory import MemorySaver

# 创建检查点存储器
checkpointer = MemorySaver()

# 编译时传入
app = workflow.compile(
    checkpointer=checkpointer,
    interrupt_before=[&quot;human&quot;]  # 在 human 节点前中断
)

# 创建线程
config = {&quot;configurable&quot;: {&quot;thread_id&quot;: &quot;1&quot;}}

# 执行并保存状态
result = app.invoke({&quot;messages&quot;: [&quot;你好&quot;]}, config)

# 恢复状态继续执行
result = app.invoke({&quot;messages&quot;: [&quot;继续&quot;]}, config)
```

## 实战案例

### 案例 1：带人类审核的内容生成

```python
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
from langchain_core.messages import HumanMessage, AIMessage

class ContentState(TypedDict):
    topic: str
    draft: str
    feedback: str
    approved: bool
    iteration: int

def generate_draft(state: ContentState) -&gt; ContentState:
    &quot;&quot;&quot;生成内容草稿&quot;&quot;&quot;
    prompt = f&quot;&quot;&quot;
    请写一篇关于 {state[&apos;topic&apos;]} 的文章。
    {f&apos;根据反馈改进：{state[&quot;feedback&quot;]}&apos; if state.get(&apos;feedback&apos;) else &apos;&apos;}
    &quot;&quot;&quot;
    
    response = llm.invoke(prompt)
    return {&quot;draft&quot;: response.content, &quot;iteration&quot;: state.get(&quot;iteration&quot;, 0) + 1}

def human_review(state: ContentState) -&gt; ContentState:
    &quot;&quot;&quot;人类审核&quot;&quot;&quot;
    print(f&quot;当前草稿：{state[&apos;draft&apos;]}&quot;)
    print(f&quot;迭代次数：{state[&apos;iteration&apos;]}&quot;)
    
    # 模拟人类输入
    approval = input(&quot;批准？(y/n/r 修订): &quot;)
    
    if approval == &apos;y&apos;:
        return {&quot;approved&quot;: True}
    elif approval == &apos;n&apos;:
        return {&quot;approved&quot;: False}
    else:
        feedback = input(&quot;提供反馈：&quot;)
        return {&quot;feedback&quot;: feedback, &quot;approved&quot;: False}

def decide_next(state: ContentState) -&gt; str:
    &quot;&quot;&quot;决定下一步&quot;&quot;&quot;
    if state[&quot;approved&quot;]:
        return &quot;end&quot;
    elif state[&quot;iteration&quot;] &gt;= 3:
        return &quot;end&quot;  # 达到最大迭代
    return &quot;revise&quot;

# 构建图
workflow = StateGraph(ContentState)

workflow.add_node(&quot;generate&quot;, generate_draft)
workflow.add_node(&quot;review&quot;, human_review)

workflow.set_entry_point(&quot;generate&quot;)
workflow.add_edge(&quot;generate&quot;, &quot;review&quot;)

workflow.add_conditional_edges(
    &quot;review&quot;,
    decide_next,
    {
        &quot;revise&quot;: &quot;generate&quot;,
        &quot;end&quot;: END
    }
)

app = workflow.compile()

# 执行
result = app.invoke({
    &quot;topic&quot;: &quot;人工智能的未来&quot;,
    &quot;iteration&quot;: 0
})

print(f&quot;最终内容：{result[&apos;draft&apos;]}&quot;)
```

### 案例 2：多 Agent 协作系统

```python
from typing import Literal

class MultiAgentState(TypedDict):
    query: str
    research: str
    analysis: str
    draft: str
    final_answer: str

# 研究 Agent
def researcher(state: MultiAgentState) -&gt; MultiAgentState:
    &quot;&quot;&quot;收集信息&quot;&quot;&quot;
    research_tools = [search_web, get_wikipedia, read_document]
    
    agent = create_agent(research_tools, &quot;研究专家&quot;)
    result = agent.run(f&quot;研究主题：{state[&apos;query&apos;]}&quot;)
    
    return {&quot;research&quot;: result}

# 分析 Agent
def analyst(state: MultiAgentState) -&gt; MultiAgentState:
    &quot;&quot;&quot;分析信息&quot;&quot;&quot;
    prompt = f&quot;&quot;&quot;
    基于以下研究结果进行分析：
    {state[&apos;research&apos;]}
    
    请提供：
    1. 关键发现
    2. 数据支持
    3. 潜在问题
    &quot;&quot;&quot;
    
    analysis = llm.invoke(prompt)
    return {&quot;analysis&quot;: analysis.content}

# 写作 Agent
def writer(state: MultiAgentState) -&gt; MultiAgentState:
    &quot;&quot;&quot;撰写答案&quot;&quot;&quot;
    prompt = f&quot;&quot;&quot;
    综合以下信息撰写最终答案：
    
    研究：{state[&apos;research&apos;]}
    分析：{state[&apos;analysis&apos;]}
    
    要求：
    - 结构清晰
    - 论据充分
    - 语言流畅
    &quot;&quot;&quot;
    
    draft = llm.invoke(prompt)
    return {&quot;draft&quot;: draft.content}

# 评审 Agent
def reviewer(state: MultiAgentState) -&gt; Literal[&quot;approve&quot;, &quot;revise&quot;]:
    &quot;&quot;&quot;评审答案&quot;&quot;&quot;
    prompt = f&quot;&quot;&quot;
    评审以下答案质量：
    {state[&apos;draft&apos;]}
    
    评分标准：
    - 准确性
    - 完整性
    - 逻辑性
    
    如果满意返回 &apos;approve&apos;，否则返回 &apos;revise&apos;
    &quot;&quot;&quot;
    
    review = llm.invoke(prompt)
    return &quot;approve&quot; if &quot;满意&quot; in review.content else &quot;revise&quot;

# 构建多 Agent 图
workflow = StateGraph(MultiAgentState)

workflow.add_node(&quot;researcher&quot;, researcher)
workflow.add_node(&quot;analyst&quot;, analyst)
workflow.add_node(&quot;writer&quot;, writer)
workflow.add_node(&quot;reviewer&quot;, reviewer)

# 定义流程
workflow.set_entry_point(&quot;researcher&quot;)
workflow.add_edge(&quot;researcher&quot;, &quot;analyst&quot;)
workflow.add_edge(&quot;analyst&quot;, &quot;writer&quot;)
workflow.add_edge(&quot;writer&quot;, &quot;reviewer&quot;)

# 添加条件边
workflow.add_conditional_edges(
    &quot;reviewer&quot;,
    (lambda state: &quot;revise&quot; if reviewer(state) == &quot;revise&quot; else &quot;end&quot;),
    {
        &quot;revise&quot;: &quot;writer&quot;,
        &quot;end&quot;: END
    }
)

app = workflow.compile()

# 执行
result = app.invoke({&quot;query&quot;: &quot;量子计算的发展现状&quot;})
print(result[&quot;draft&quot;])
```

### 案例 3：ReAct 模式实现

```python
from langgraph.prebuilt import ToolNode
from langchain.agents import AgentExecutor

class ReActState(TypedDict):
    messages: Annotated[list, add_messages]
    tool_output: str

def model_node(state: ReActState) -&gt; ReActState:
    &quot;&quot;&quot;LLM 决策节点&quot;&quot;&quot;
    messages = state[&quot;messages&quot;]
    
    # 使用能够进行工具调用的模型
    response = llm_with_tools.invoke(messages)
    
    return {&quot;messages&quot;: [response]}

def tool_node(state: ReActState) -&gt; ReActState:
    &quot;&quot;&quot;工具执行节点&quot;&quot;&quot;
    # 执行工具调用
    tool_calls = state[&quot;messages&quot;][-1].tool_calls
    
    outputs = []
    for tool_call in tool_calls:
        tool = tools_by_name[tool_call[&quot;name&quot;]]
        response = tool.invoke(tool_call[&quot;args&quot;])
        outputs.append(response)
    
    return {&quot;tool_output&quot;: &quot;\n&quot;.join(outputs)}

def should_continue(state: ReActState) -&gt; Literal[&quot;tools&quot;, &quot;end&quot;]:
    &quot;&quot;&quot;判断是否需要调用工具&quot;&quot;&quot;
    messages = state[&quot;messages&quot;]
    last_message = messages[-1]
    
    if last_message.tool_calls:
        return &quot;tools&quot;
    return &quot;end&quot;

# 构建 ReAct 图
workflow = StateGraph(ReActState)

workflow.add_node(&quot;agent&quot;, model_node)
workflow.add_node(&quot;tools&quot;, ToolNode(tools))

workflow.set_entry_point(&quot;agent&quot;)

workflow.add_conditional_edges(
    &quot;agent&quot;,
    should_continue,
    {
        &quot;tools&quot;: &quot;tools&quot;,
        &quot;end&quot;: END
    }
)

workflow.add_edge(&quot;tools&quot;, &quot;agent&quot;)

app = workflow.compile()

# 执行
result = app.invoke({
    &quot;messages&quot;: [HumanMessage(content=&quot;北京今天的天气如何？&quot;)]
})

print(result[&quot;messages&quot;][-1].content)
```

## 高级模式

### 1. 子图 (Subgraphs)

将复杂工作流分解为子图。

```python
# 创建子图
research_workflow = StateGraph(ResearchState)
research_workflow.add_node(&quot;search&quot;, search_node)
research_workflow.add_node(&quot;analyze&quot;, analyze_node)
research_workflow.compile()

# 在主图中使用子图
main_workflow = StateGraph(MainState)
main_workflow.add_node(&quot;research&quot;, research_workflow)
main_workflow.add_node(&quot;write&quot;, write_node)
```

### 2. 并发执行

```python
from langgraph.graph import START

# 并行启动多个节点
workflow.add_edge(START, &quot;researcher&quot;)
workflow.add_edge(START, &quot;data_collector&quot;)
workflow.add_edge(START, &quot;context_loader&quot;)

# 汇合点
workflow.add_edge(&quot;researcher&quot;, &quot;synthesizer&quot;)
workflow.add_edge(&quot;data_collector&quot;, &quot;synthesizer&quot;)
workflow.add_edge(&quot;context_loader&quot;, &quot;synthesizer&quot;)
```

### 3. 动态图

根据运行时状态动态修改图结构。

```python
def dynamic_router(state: State) -&gt; str:
    &quot;&quot;&quot;动态决定执行路径&quot;&quot;&quot;
    if state[&quot;complexity&quot;] &gt; 0.8:
        return &quot;complex_workflow&quot;
    elif state[&quot;complexity&quot;] &gt; 0.5:
        return &quot;medium_workflow&quot;
    return &quot;simple_workflow&quot;

workflow.add_conditional_edges(
    START,
    dynamic_router,
    {
        &quot;simple_workflow&quot;: &quot;simple_agent&quot;,
        &quot;medium_workflow&quot;: &quot;medium_agent&quot;,
        &quot;complex_workflow&quot;: &quot;complex_agent&quot;
    }
)
```

### 4. 超时和重试

```python
from langgraph.pregel import RetryPolicy

app = workflow.compile(
    checkpointer=checkpointer,
    retry_policy=RetryPolicy(
        max_attempts=3,
        interval=1.0,
        backoff=2.0
    ),
    timeout=300  # 5 分钟超时
)
```

## 调试和监控

### 1. 可视化

```python
# 生成流程图
app.get_graph().draw_mermaid_png(output_path=&quot;workflow.png&quot;)

# 或生成 Mermaid 代码
mermaid = app.get_graph().draw_mermaid()
print(mermaid)
```

### 2. 日志记录

```python
from langgraph.prebuilt import InjectedState

def logged_node(state: AgentState) -&gt; AgentState:
    import logging
    logging.info(f&quot;进入节点，状态：{state}&quot;)
    
    # 处理逻辑
    result = process(state)
    
    logging.info(f&quot;节点完成，返回：{result}&quot;)
    return result
```

### 3. 追踪

```python
from langchain.tracers import LangChainTracer

with LangChainTracer(project=&quot;langgraph-demo&quot;) as tracer:
    result = app.invoke(input_data)
```

## 最佳实践

### 1. 状态设计

- ✅ 使用 TypedDict 定义明确的状态结构
- ✅ 保持状态不可变，返回新状态而非修改
- ✅ 使用 Annotated 和 reducer 函数管理复杂状态

### 2. 节点设计

- ✅ 单一职责：每个节点只做一件事
- ✅ 无副作用：节点不应修改外部状态
- ✅ 可测试：节点函数应易于单元测试

### 3. 错误处理

```python
def safe_node(state: State) -&gt; State:
    try:
        result = risky_operation(state)
        return {&quot;result&quot;: result}
    except Exception as e:
        return {
            &quot;error&quot;: str(e),
            &quot;fallback&quot;: default_value
        }
```

### 4. 性能优化

- 使用异步节点处理 I/O 密集型任务
- 缓存重复计算结果
- 合理设置超时和重试策略
- 使用检查点避免重复计算

## LangChain vs LangGraph 对比

| 特性 | LangChain | LangGraph |
|------|-----------|-----------|
| 工作流类型 | 线性链 | 有向图 |
| 循环支持 | 有限 | 原生支持 |
| 状态管理 | 简单 | 复杂状态机 |
| 人机协作 | 困难 | 内置支持 |
| 持久化 | 基础 | 检查点机制 |
| 可视化 | 简单 | 完整流程图 |
| 适用场景 | 简单任务 | 复杂 Agent 系统 |

## 总结

### 核心要点

1. **LangChain** 提供了构建 LLM 应用的基础组件
2. **LangGraph** 在 LangChain 之上增加了图结构和状态机
3. **状态管理** 是 LangGraph 的核心，确保数据在节点间正确传递
4. **条件边** 实现动态流程控制
5. **检查点** 支持持久化和恢复
6. **人机协作** 实现人类审核和干预

### 学习路径

1. 掌握 LangChain 基础：链、提示词、记忆、工具
2. 理解 LangGraph 核心：状态、节点、边
3. 实践常见模式：ReAct、人类审核、多 Agent
4. 探索高级特性：子图、并发、动态图
5. 学习调试和监控技巧

### 下一步

- [ ] 阅读官方文档：https://langchain-ai.github.io/langgraph/
- [ ] 尝试构建自己的 Agent 系统
- [ ] 学习 LangSmith 追踪和调试
- [ ] 探索与其他框架的集成
- [ ] 参与开源社区贡献

---

**更新时间**: 2026-05-14  
**阅读时间**: 25 分钟  
**适用场景**: LLM 应用开发、Agent 系统构建、工作流编排

### 参考资源

- [LangChain 官方文档](https://python.langchain.com/)
- [LangGraph 官方文档](https://langchain-ai.github.io/langgraph/)
- [LangChain GitHub](https://github.com/langchain-ai/langchain)
- [LangGraph GitHub](https://github.com/langchain-ai/langgraph)
</content:encoded><category>LangChain</category><category>LangGraph</category><category>LLM</category><category>Agent</category><category>工作流</category><category>AI 工程</category></item></channel></rss>