[{"content":"","date":"2026年4月2日","externalUrl":null,"permalink":"/tags/ai%E5%AE%89%E5%85%A8/","section":"Tags","summary":"","title":"AI安全","type":"tags"},{"content":"","date":"2026年4月2日","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2026年4月2日","externalUrl":null,"permalink":"/tags/git%E5%B7%A5%E4%BD%9C%E6%B5%81/","section":"Tags","summary":"","title":"Git工作流","type":"tags"},{"content":"","date":"2026年4月2日","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"2026年4月2日","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":" 专注云原生与 DevOps 领域的技术实践记录——Kubernetes、容器、CI/CD、系统运维，以及那些踩过的坑。 涵盖的方向：容器与编排（Docker、Kubernetes、Helm）、DevOps 工具链（GitLab、Jenkins、ArgoCD）、系统运维（Linux、网络、存储）、云原生架构（服务网格、可观测性、高可用）。\n","date":"2026年4月2日","externalUrl":null,"permalink":"/","section":"Zayn's Blog","summary":"\u003cdiv class=\"lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl\"\u003e\n  专注云原生与 DevOps 领域的技术实践记录——Kubernetes、容器、CI/CD、系统运维，以及那些踩过的坑。\n\u003c/div\u003e\n\n\u003cp\u003e涵盖的方向：\u003cstrong\u003e容器与编排\u003c/strong\u003e（Docker、Kubernetes、Helm）、\u003cstrong\u003eDevOps 工具链\u003c/strong\u003e（GitLab、Jenkins、ArgoCD）、\u003cstrong\u003e系统运维\u003c/strong\u003e（Linux、网络、存储）、\u003cstrong\u003e云原生架构\u003c/strong\u003e（服务网格、可观测性、高可用）。\u003c/p\u003e","title":"Zayn's Blog","type":"page"},{"content":"","date":"2026年4月2日","externalUrl":null,"permalink":"/tags/%E5%8D%9A%E5%AE%A2/","section":"Tags","summary":"","title":"博客","type":"tags"},{"content":"这里收录了我的技术文章、实战教程和阶段性实践记录。\n如果你第一次来到这里，建议优先看两类内容：\n长期可复用的指南：Kubernetes、Docker、CI/CD、系统运维等成体系文章 每日技术实践简报：更贴近真实工作流的当天判断、踩坑与方法收口 你也可以通过分类、标签和系列页，快速找到某个主题下的连续内容。\n","date":"2026年4月2日","externalUrl":null,"permalink":"/posts/","section":"博客文章","summary":"\u003cp\u003e这里收录了我的技术文章、实战教程和阶段性实践记录。\u003c/p\u003e\n\u003cp\u003e如果你第一次来到这里，建议优先看两类内容：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e长期可复用的指南\u003c/strong\u003e：Kubernetes、Docker、CI/CD、系统运维等成体系文章\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e每日技术实践简报\u003c/strong\u003e：更贴近真实工作流的当天判断、踩坑与方法收口\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e你也可以通过分类、标签和系列页，快速找到某个主题下的连续内容。\u003c/p\u003e","title":"博客文章","type":"posts"},{"content":" 推荐阅读 # 每日技术实践简报 - 2026-03-31 从 MCP 到一键发包：把 Teambition 评论里的 APK 自动上传 Nexus 的那些坑 OpenClaw 记忆层降级策略：从故障中构建可靠的 AI Agent 记忆系统 今日技术实践 # 解决的问题 # 博客 MR 合并冲突解决：MR !53 合并后，后续推送的 03-29、03-30 日报和 03-31 重写版未包含在合并中。根因是合并后继续往同一分支推送新 commit，但未创建新 MR。最终通过本地 git merge 解决冲突，直接推送到 main 分支。\n日报 cron 工作流优化：原有 cron 只从 Nowledge Mem 拉简短记录，内容偏薄。优化为多源采集：优先读当天 memory/YYYY-MM-DD.md + Nowledge Mem 补充 + 昨天记忆做背景。强制要求每篇包含\u0026quot;解决问题、学到的新东西、踩坑记录、实际产出\u0026quot;四个板块，总字数不少于 800 字。\nLinux 笔记本选购调研：系统对比了 ThinkPad E14/E16、ThinkBook 14+/16+、Framework 等机型在 Linux 下的兼容性。结论：Intel Ultra 5 125H + ThinkBook 16+ 2025 是当前 Linux 兼容性最佳、性价比最高的组合（32G/1T/3.2K 165Hz，国补后 5354 元）。但结合已有 Mac Mini + PVE 集群的实际情况，最终决定不购买，PVE 开虚拟桌面即可满足需求。\n学到的新东西 # Git 分支合并的时序陷阱：MR 合并后继续往源分支推送 commit，这些 commit 不会自动进入已合并的 MR。需要重新创建 MR 或本地合并。教训：推送代码前先确认 MR 状态，合并后如果要继续用同一分支，必须再创建新 MR。\nHugo shortcode 在代码块中也会被解析：Markdown 代码块内的 Hugo shortcode 语法（如 mermaid 标签）也会被 Hugo 引擎处理，导致构建失败。解决方案：在正文中引用 shortcode 时避免直接写出原始语法，改用自然语言描述。\nSPICE 协议远程桌面方案：macOS 上可通过 brew install --cask virt-viewer 安装 Remote Viewer，连接 PVE 的 SPICE 控制台，获得接近本地桌面的体验（剪贴板共享、USB 重定向、自适应分辨率）。\nIntel Core Ultra 5 125H 架构：Meteor Lake 架构首次在消费级采用多 Tile 封装（计算+SoC+GPU+IO），三簇混合核心（4P+8E+2LPE），Arc 8Xe 核显性能约等于 GTX 1650 移动版。Linux 6.8+ 内核驱动稳定。\n实际产出 # 博客日报合并到 main 并推送（03-29/03-30/03-31 三篇） 日报 cron 工作流优化（多源采集+质量底线） Linux 笔记本选购调研报告 PVE SPICE 远程桌面方案整理 踩坑记录 # MR 合并后推送的 commit 丢失：MR !53 合并只包含了分支上部分 commit，后续 5 个新 commit 未进入 main。根因是对 Git 分支合并时序理解不足——MR 合并的是当时分支上的快照，之后的推送需要新的 MR。修复方式：本地 git checkout feat/blog-mcp-nexus-0329 -- file 解决冲突后直接 merge 到 main 并 push。\nglab mr merge 参数差异：glab mr merge 53 --remove-source 报错 \u0026ldquo;Unknown flag\u0026rdquo;，不同版本 glab 的参数名不同。需要先 glab mr merge --help 确认当前版本支持的参数。\n其他思考 # AI 安全事件集中爆发：Claude Code 和 OpenAI Codex 同日被曝源码泄露，AI 工具链的攻击面在快速扩大。安全审计需要从\u0026quot;模型安全\u0026quot;扩展到\u0026quot;工具链安全\u0026quot;——前端 Sourcemap 文件管控是当前的安全盲区。\n消费决策的理性框架：在笔记本选购过程中，最终通过\u0026quot;已有资源盘点\u0026quot;（Mac Mini 48G + PVE 集群）推翻了购买冲动。核心问题：\u0026ldquo;你上次想用 Linux 但 Mac 上搞不定的场景是什么？\u0026ldquo;答不上来就是多余消费。\nClaude Pro 配额消耗问题：X 上有人演示 Claude Pro 问一句\u0026quot;你好\u0026quot;就消耗 2% 配额。Anthropic 的用量限制策略引发了广泛讨论，对 AI Agent 的持续运行场景有直接影响。\n","date":"2026年4月2日","externalUrl":null,"permalink":"/posts/daily-practice-2026-04-02/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-31/\"\u003e每日技术实践简报 - 2026-03-31\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/from-mcp-to-one-click-release-ai-agent-automation/\"\u003e从 MCP 到一键发包：把 Teambition 评论里的 APK 自动上传 Nexus 的那些坑\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-memory-fallback-pattern/\"\u003eOpenClaw 记忆层降级策略：从故障中构建可靠的 AI Agent 记忆系统\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e今日技术实践 \n    \u003cdiv id=\"今日技术实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%8a%e6%97%a5%e6%8a%80%e6%9c%af%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e博客 MR 合并冲突解决\u003c/strong\u003e：MR !53 合并后，后续推送的 03-29、03-30 日报和 03-31 重写版未包含在合并中。根因是合并后继续往同一分支推送新 commit，但未创建新 MR。最终通过本地 \u003ccode\u003egit merge\u003c/code\u003e 解决冲突，直接推送到 main 分支。\u003c/p\u003e","title":"每日技术实践简报 - 2026-04-02：博客 Git 工作流治理、Linux 笔记本选购调研与 AI 安全事件追踪","type":"posts"},{"content":"","date":"2026年4月2日","externalUrl":null,"permalink":"/categories/%E5%AE%9E%E8%B7%B5%E8%AE%B0%E5%BD%95/","section":"Categories","summary":"","title":"实践记录","type":"categories"},{"content":"","date":"2026年4月2日","externalUrl":null,"permalink":"/tags/%E7%A1%AC%E4%BB%B6%E9%80%89%E8%B4%AD/","section":"Tags","summary":"","title":"硬件选购","type":"tags"},{"content":"","date":"2026年3月31日","externalUrl":null,"permalink":"/tags/claude-code/","section":"Tags","summary":"","title":"Claude Code","type":"tags"},{"content":"","date":"2026年3月31日","externalUrl":null,"permalink":"/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81%E4%BC%98%E5%8C%96/","section":"Tags","summary":"","title":"工作流优化","type":"tags"},{"content":" 推荐阅读 # 每日技术实践简报 - 2026-03-30 从 MCP 到一键发包：把 Teambition 评论里的 APK 自动上传 Nexus 的那些坑 OpenClaw 记忆层降级策略：从故障中构建可靠的 AI Agent 记忆系统 今日技术实践 # 解决的问题 # 多源运维告警分级梳理：一天之内收到 5 类告警——腾讯云 SSL 证书过期（3/28 通知，已过 3 天未续期）、YangZun Playground 反复宕机（3/29-3/31 共 6 次 DOWN/UP 循环）、TrueNAS 存储告警连续触发、CloudflareSpeedTest CI 连续 10 次失败、GitLab 暴力登录（1 分钟内 7 次验证码错误）。按紧急度分级：SSL 续期和 YangZun 根因排查为最高优先级。\n博客日报工作流优化：发现 cron 自动生成的日报内容偏薄（只有 Nowledge Mem 简短记录），缺少当天对话中的丰富上下文。优化为多源采集：优先读当天 memory/YYYY-MM-DD.md（最详细）+ Nowledge Mem 补充 + 昨天记忆做背景。强制要求每篇包含\u0026quot;解决问题（2-3条）+ 学到的新东西（2条）+ 踩坑记录 + 实际产出\u0026quot;，总字数不少于 800 字。\nGit 分支治理与日报补全：补写了 03-29 和 03-30 两天的日报（使用 blog-post-generator Skill 规范），从 main 分支拉取 03-31 日报到功能分支，统一通过 MR !53 合并。修复了 Hugo shortcode 在 Markdown 代码块中被误解析的问题（用 {{\u0026lt; \u0026gt;}} 转义）。\n学到的新东西 # Claude Code 源码泄露的技术教训：Anthropic 的 Claude Code npm 包中 .map 文件暴露了 1900 个 TypeScript 文件/51.2 万行代码，包括 Query Engine、工具架构、15+ feature flags、Undercover Mode 等内部实现。关键教训：前端/Sourcemap 文件管控是生产环境的安全盲区，即使不含 API Key，架构和 feature flag 泄露也会暴露战略方向。\nAI 安全事件集中爆发的趋势：同一天内 Claude Code 源码泄露、LiteLLM 漏洞、Axios 供应链攻击、Railway 配置泄露、Codex GitHub Token 漏洞、Mercer 安全事件共 6 起安全事件。这不是巧合——AI 工具链的攻击面在快速扩大，安全审计需要从\u0026quot;模型安全\u0026quot;扩展到\u0026quot;工具链安全\u0026quot;。\nGPT-5.4 原生 Computer Use 的意义：从\u0026quot;生成时代\u0026quot;进入\u0026quot;执行时代\u0026quot;，AI Agent 不再只是生成文本，而是直接操作计算机完成端到端任务。这对运维自动化的影响深远——未来可能不再需要写自动化脚本，AI 直接操作 GUI 就能完成。\nCron 任务 delivery 通道问题：多个 isolated agentTurn 任务因缺少 channel 参数报 \u0026ldquo;Channel is required\u0026rdquo; 错误。根因是配置了多通道（telegram + openclaw-weixin）后，delivery 必须显式指定 channel: \u0026quot;telegram\u0026quot;。\n实际产出 # 补写 03-29/03-30 日报 2 篇（Skill 规范优化版） 03-31 日报重新生成（多源数据+深度结构） 日报 cron 工作流优化（多源采集+质量底线+格式规范） MR !53 更新：https://gitlab.cpinnov.run/devops/blowfish-blog/-/merge_requests/53 踩坑记录 # Hugo shortcode 误解析：日报中用 Markdown 代码块包裹的 mermaid shortcode 标签被 Hugo 当作真正的 shortcode 解析，导致构建失败。解决方案：在 Markdown 中引用 shortcode 标签时必须用 HTML 注释方式转义。这是 Hugo 的已知行为——代码块内的 shortcode 标签也会被处理。\nCron delivery 报错持续：03-31 的 cron 日报虽然本地生成了，但 delivery 报 \u0026ldquo;Outbound not configured for channel: telegram\u0026rdquo; 错误。之前已修复过多个 cron 的 delivery 配置，但这个任务的 delivery 又回到了不完整状态。需要确认 cron update 是否覆盖了已有配置。\n其他思考 # 清明海南全岛骑行已确认决策，计划请 2-3 天假，4/4 出发仅剩 4 天。这将是 CADENCE 骑行语音助手项目的天然测试场景——在骑行中用语音与 OpenClaw 交互，验证 Siri + 快捷指令 + HTTP Bridge 的完整链路。 Anthropic 拒绝 $2 亿五角大楼合同，坚守 AI 伦理红线。在商业化压力下坚持原则的做法值得尊重。同时 Claude 在安全研究能力上已超越人类专家（Carlini 公开承认），AI 安全研究本身正在被 AI 重塑。 Meta AI 人才战争白热化：$100M+ package 挖人，LeCun 创办 AMI Labs 融资 $10.3 亿。AGI 竞赛进入\u0026quot;人才军备竞赛\u0026quot;阶段。 ","date":"2026年3月31日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-31/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-30/\"\u003e每日技术实践简报 - 2026-03-30\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/from-mcp-to-one-click-release-ai-agent-automation/\"\u003e从 MCP 到一键发包：把 Teambition 评论里的 APK 自动上传 Nexus 的那些坑\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-memory-fallback-pattern/\"\u003eOpenClaw 记忆层降级策略：从故障中构建可靠的 AI Agent 记忆系统\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e今日技术实践 \n    \u003cdiv id=\"今日技术实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%8a%e6%97%a5%e6%8a%80%e6%9c%af%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e多源运维告警分级梳理\u003c/strong\u003e：一天之内收到 5 类告警——腾讯云 SSL 证书过期（3/28 通知，已过 3 天未续期）、YangZun Playground 反复宕机（3/29-3/31 共 6 次 DOWN/UP 循环）、TrueNAS 存储告警连续触发、CloudflareSpeedTest CI 连续 10 次失败、GitLab 暴力登录（1 分钟内 7 次验证码错误）。按紧急度分级：SSL 续期和 YangZun 根因排查为最高优先级。\u003c/p\u003e","title":"每日技术实践简报 - 2026-03-31：Claude Code 源码泄露、运维告警集中爆发与海南骑行决策","type":"posts"},{"content":"","date":"2026年3月31日","externalUrl":null,"permalink":"/tags/%E8%BF%90%E7%BB%B4%E5%91%8A%E8%AD%A6/","section":"Tags","summary":"","title":"运维告警","type":"tags"},{"content":"","date":"2026年3月30日","externalUrl":null,"permalink":"/tags/openclaw/","section":"Tags","summary":"","title":"OpenClaw","type":"tags"},{"content":"","date":"2026年3月30日","externalUrl":null,"permalink":"/tags/skill/","section":"Tags","summary":"","title":"Skill","type":"tags"},{"content":"","date":"2026年3月30日","externalUrl":null,"permalink":"/tags/%E7%BC%96%E7%A8%8B%E8%8C%83%E5%BC%8F/","section":"Tags","summary":"","title":"编程范式","type":"tags"},{"content":" 推荐阅读 # 每日技术实践简报 - 2026-03-29 OpenClaw AI Agent 架构解析：多引擎联动与记忆系统 今日技术实践 # 解决的问题 # 工作周报生成：使用 work-report-generator Skill 从对话记录中提取本周工作内容，先生成详细版再转化为领导汇报版（结果导向、简洁专业、突出重点和下一步计划）。 编程范式梳理：整理了面向过程、面向函数、面向对象三种编程范式的演进关系、核心区别和实际应用场景。现代语言已走向多范式混合路线。 学到的新东西 # 领导汇报版周报格式：分\u0026quot;重点工作\u0026quot;和\u0026quot;其他工作\u0026quot;两大块，重点工作按项目编号列出成果，底部用表格呈现下周计划和预期目标。语言风格要结果导向，不说过程只说结论。 编程范式本质区别：面向过程关注\u0026quot;步骤\u0026quot;（流程图），面向函数关注\u0026quot;变换\u0026quot;（数学映射），面向对象关注\u0026quot;协作\u0026quot;（角色交互）。三者不互斥，现代语言多范式混合是主流。 运维治理三重机制验证：预防层（配置审查、安全检查、操作规范）→ 监控层（日常检查、日志监控、错误追踪）→ 修复层（降级策略、备份恢复、故障诊断），已形成闭环且错误率显著下降。 实际产出 # 本周工作周报（领导汇报版）1 份 博客运营优化巡检自动执行（每日 10:15），当天优化了 03-21 和 03-15 日报标题 踩坑记录 # Cron 任务 delivery 报错：多个 isolated agentTurn 任务（博客巡检、AI 论文简报、AI 资讯速览等）因缺少 channel 参数报 \u0026ldquo;Channel is required\u0026rdquo; 错误。在多通道配置（telegram + openclaw-weixin）环境下必须显式设置 delivery.channel: \u0026quot;telegram\u0026quot;。 其他思考 # 国产咖啡品牌调研：三顿半超级拿铁系列（3号经典拿铁、4号浓郁拿铁）适合不喜欢纯深烘焙的口味偏好，冻干粉冷水即溶，单杯不到 5 元，是日常口粮咖啡的高性价比选择。 外部服务依赖风险：Brave Search API 月度额度耗尽导致搜索能力暂时不可用，提醒了对外部 API 的依赖需要有降级预案。 ","date":"2026年3月30日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-30/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-29/\"\u003e每日技术实践简报 - 2026-03-29\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-ai-agent-architecture-v2/\"\u003eOpenClaw AI Agent 架构解析：多引擎联动与记忆系统\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e今日技术实践 \n    \u003cdiv id=\"今日技术实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%8a%e6%97%a5%e6%8a%80%e6%9c%af%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e工作周报生成\u003c/strong\u003e：使用 \u003ccode\u003ework-report-generator\u003c/code\u003e Skill 从对话记录中提取本周工作内容，先生成详细版再转化为领导汇报版（结果导向、简洁专业、突出重点和下一步计划）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e编程范式梳理\u003c/strong\u003e：整理了面向过程、面向函数、面向对象三种编程范式的演进关系、核心区别和实际应用场景。现代语言已走向多范式混合路线。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e领导汇报版周报格式\u003c/strong\u003e：分\u0026quot;重点工作\u0026quot;和\u0026quot;其他工作\u0026quot;两大块，重点工作按项目编号列出成果，底部用表格呈现下周计划和预期目标。语言风格要结果导向，不说过程只说结论。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e编程范式本质区别\u003c/strong\u003e：面向过程关注\u0026quot;步骤\u0026quot;（流程图），面向函数关注\u0026quot;变换\u0026quot;（数学映射），面向对象关注\u0026quot;协作\u0026quot;（角色交互）。三者不互斥，现代语言多范式混合是主流。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e运维治理三重机制验证\u003c/strong\u003e：预防层（配置审查、安全检查、操作规范）→ 监控层（日常检查、日志监控、错误追踪）→ 修复层（降级策略、备份恢复、故障诊断），已形成闭环且错误率显著下降。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e实际产出 \n    \u003cdiv id=\"实际产出\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%9e%e9%99%85%e4%ba%a7%e5%87%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e本周工作周报（领导汇报版）1 份\u003c/li\u003e\n\u003cli\u003e博客运营优化巡检自动执行（每日 10:15），当天优化了 03-21 和 03-15 日报标题\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eCron 任务 delivery 报错\u003c/strong\u003e：多个 isolated agentTurn 任务（博客巡检、AI 论文简报、AI 资讯速览等）因缺少 \u003ccode\u003echannel\u003c/code\u003e 参数报 \u0026ldquo;Channel is required\u0026rdquo; 错误。在多通道配置（telegram + openclaw-weixin）环境下必须显式设置 \u003ccode\u003edelivery.channel: \u0026quot;telegram\u0026quot;\u003c/code\u003e。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e其他思考 \n    \u003cdiv id=\"其他思考\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%b6%e4%bb%96%e6%80%9d%e8%80%83\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e国产咖啡品牌调研：三顿半超级拿铁系列（3号经典拿铁、4号浓郁拿铁）适合不喜欢纯深烘焙的口味偏好，冻干粉冷水即溶，单杯不到 5 元，是日常口粮咖啡的高性价比选择。\u003c/li\u003e\n\u003cli\u003e外部服务依赖风险：Brave Search API 月度额度耗尽导致搜索能力暂时不可用，提醒了对外部 API 的依赖需要有降级预案。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-30：工作周报整理、编程范式梳理与运维治理体系沉淀","type":"posts"},{"content":"","date":"2026年3月30日","externalUrl":null,"permalink":"/tags/%E8%BF%90%E7%BB%B4%E6%B2%BB%E7%90%86/","section":"Tags","summary":"","title":"运维治理","type":"tags"},{"content":"","date":"2026年3月30日","externalUrl":null,"permalink":"/tags/%E5%91%A8%E6%8A%A5/","section":"Tags","summary":"","title":"周报","type":"tags"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/tags/blowfish/","section":"Tags","summary":"","title":"Blowfish","type":"tags"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/tags/git/","section":"Tags","summary":"","title":"Git","type":"tags"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":" 推荐阅读 # 每日技术实践简报 - 2026-03-28 从 MCP 到一键发包：把 Teambition 评论里的 APK 自动上传 Nexus 的那些坑 OpenClaw AI Agent 架构解析：多引擎联动与记忆系统 今日技术实践 # 解决的问题 # 深度博客文章撰写：完成\u0026quot;从 MCP 到一键发包\u0026quot;的 300+ 行技术博客，完整记录了从 Teambition MCP 对接到 APK 自动上传 Nexus 的全链路踩坑过程。 Blowfish 主题 shortcode 踩坑：发现主题没有 notice shortcode，只有 alert。经过多次 sed/edit 替换尝试，最终确认正确语法为 {{\u0026lt; alert \u0026quot;icon-name\u0026quot; \u0026gt;}}内容{{\u0026lt; /alert \u0026gt;}}。 Mermaid 流程图修复：从 text 代码块改为 {{\u0026lt; mermaid \u0026gt;}} shortcode 格式，Hugo 才能正确渲染可视化流程图。 Git 分支治理：发现旧分支 feat/extract-markdown-rental-risk-20260324 堆积了大量无关提交（_index.md 修改、租房信息文章等），从 main 重新创建干净分支，只包含必要文件。 学到的新东西 # Blowfish 可用 shortcode 清单：alert、mermaid、chart、timeline、figure、gist、katex 等。写文章前先确认 shortcode 存在，不要凭记忆用。 blog-post-generator Skill 规范：frontmatter 需要 featureimage、keywords、tags_weight/categories_weight；推荐阅读用相对路径内链；代码块必须是实际可用代码，不要伪代码。 Git 分支清理策略：保护分支的仓库应\u0026quot;从 main 创建新分支 → cherry-pick 或 checkout 需要的文件 → 关闭旧 MR → 创建新 MR\u0026quot;，比 rebase 更可控。 租房等非技术内容不应推到技术博客：分支提交前必须 review 文件列表，排除无关内容。 实际产出 # 深度博客文章 1 篇（302 行，9892 字节） 3月28日日报 1 篇 Git MR !53 创建（干净版本，排除租房信息） 踩坑记录 # notice shortcode 不存在：Blowfish 主题没有 notice，只有 alert。Skill 文档记录有误，需以实际 shortcode 目录为准。 多次 sed 替换导致文件状态混乱：部分 notice 被替换为 alert，但残留不完整标签。应一次性 read 全文确认再精确 edit。 Hugo 版本兼容性警告：Module \u0026ldquo;blowfish\u0026rdquo; is not compatible with Hugo 0.137.0/0.148.0，但实际构建可通过，属于警告而非阻断。 其他思考 # 博客写作风格确立：口语化、有实操感、不要 AI 味；像一个资深工程师在写给同行看；段落简短，多用小标题。 博客 Skill 链（blowfish-hugo + blog-post-generator + structured-guide-writer）已形成完整工作流，后续写作效率会持续提升。 ","date":"2026年3月29日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-29/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-28/\"\u003e每日技术实践简报 - 2026-03-28\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/from-mcp-to-one-click-release-ai-agent-automation/\"\u003e从 MCP 到一键发包：把 Teambition 评论里的 APK 自动上传 Nexus 的那些坑\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-ai-agent-architecture-v2/\"\u003eOpenClaw AI Agent 架构解析：多引擎联动与记忆系统\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e今日技术实践 \n    \u003cdiv id=\"今日技术实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%8a%e6%97%a5%e6%8a%80%e6%9c%af%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e深度博客文章撰写\u003c/strong\u003e：完成\u0026quot;从 MCP 到一键发包\u0026quot;的 300+ 行技术博客，完整记录了从 Teambition MCP 对接到 APK 自动上传 Nexus 的全链路踩坑过程。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBlowfish 主题 shortcode 踩坑\u003c/strong\u003e：发现主题没有 \u003ccode\u003enotice\u003c/code\u003e shortcode，只有 \u003ccode\u003ealert\u003c/code\u003e。经过多次 sed/edit 替换尝试，最终确认正确语法为 \u003ccode\u003e{{\u0026lt; alert \u0026quot;icon-name\u0026quot; \u0026gt;}}内容{{\u0026lt; /alert \u0026gt;}}\u003c/code\u003e。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMermaid 流程图修复\u003c/strong\u003e：从 \u003ccode\u003etext\u003c/code\u003e 代码块改为 \u003ccode\u003e{{\u0026lt; mermaid \u0026gt;}}\u003c/code\u003e shortcode 格式，Hugo 才能正确渲染可视化流程图。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eGit 分支治理\u003c/strong\u003e：发现旧分支 \u003ccode\u003efeat/extract-markdown-rental-risk-20260324\u003c/code\u003e 堆积了大量无关提交（\u003ccode\u003e_index.md\u003c/code\u003e 修改、租房信息文章等），从 main 重新创建干净分支，只包含必要文件。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eBlowfish 可用 shortcode 清单\u003c/strong\u003e：alert、mermaid、chart、timeline、figure、gist、katex 等。写文章前先确认 shortcode 存在，不要凭记忆用。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eblog-post-generator Skill 规范\u003c/strong\u003e：frontmatter 需要 \u003ccode\u003efeatureimage\u003c/code\u003e、\u003ccode\u003ekeywords\u003c/code\u003e、\u003ccode\u003etags_weight\u003c/code\u003e/\u003ccode\u003ecategories_weight\u003c/code\u003e；推荐阅读用相对路径内链；代码块必须是实际可用代码，不要伪代码。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eGit 分支清理策略\u003c/strong\u003e：保护分支的仓库应\u0026quot;从 main 创建新分支 → cherry-pick 或 checkout 需要的文件 → 关闭旧 MR → 创建新 MR\u0026quot;，比 rebase 更可控。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e租房等非技术内容不应推到技术博客\u003c/strong\u003e：分支提交前必须 review 文件列表，排除无关内容。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e实际产出 \n    \u003cdiv id=\"实际产出\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%9e%e9%99%85%e4%ba%a7%e5%87%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e深度博客文章 1 篇（302 行，9892 字节）\u003c/li\u003e\n\u003cli\u003e3月28日日报 1 篇\u003c/li\u003e\n\u003cli\u003eGit MR !53 创建（干净版本，排除租房信息）\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003enotice shortcode 不存在\u003c/strong\u003e：Blowfish 主题没有 \u003ccode\u003enotice\u003c/code\u003e，只有 \u003ccode\u003ealert\u003c/code\u003e。Skill 文档记录有误，需以实际 shortcode 目录为准。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e多次 sed 替换导致文件状态混乱\u003c/strong\u003e：部分 \u003ccode\u003enotice\u003c/code\u003e 被替换为 \u003ccode\u003ealert\u003c/code\u003e，但残留不完整标签。应一次性 read 全文确认再精确 edit。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHugo 版本兼容性警告\u003c/strong\u003e：Module \u0026ldquo;blowfish\u0026rdquo; is not compatible with Hugo 0.137.0/0.148.0，但实际构建可通过，属于警告而非阻断。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e其他思考 \n    \u003cdiv id=\"其他思考\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%b6%e4%bb%96%e6%80%9d%e8%80%83\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e博客写作风格确立：口语化、有实操感、不要 AI 味；像一个资深工程师在写给同行看；段落简短，多用小标题。\u003c/li\u003e\n\u003cli\u003e博客 Skill 链（blowfish-hugo + blog-post-generator + structured-guide-writer）已形成完整工作流，后续写作效率会持续提升。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-29：从 MCP 到一键发包的博客深度写作与 Skill 链优化","type":"posts"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/tags/ai-agent/","section":"Tags","summary":"","title":"AI Agent","type":"tags"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/tags/devops/","section":"Tags","summary":"","title":"Devops","type":"tags"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/tags/mcp/","section":"Tags","summary":"","title":"MCP","type":"tags"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/tags/nexus/","section":"Tags","summary":"","title":"Nexus","type":"tags"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/tags/teambition/","section":"Tags","summary":"","title":"Teambition","type":"tags"},{"content":" 推荐阅读 # 每日技术实践简报 - 2026-03-28 OpenClaw AI Agent 架构解析：多引擎联动与记忆系统 当 AI Agent 遇上运维自动化：我的实践踩坑之路 目录 # 目标：把\u0026quot;手工点点点\u0026quot;变成可复用链路 技术方案：为什么选 MCP + Skill 实战：从 mcporter 调通第一条链路 第一个坑：mcporter 的参数格式 拿评论和附件：ListTaskActivitiesV3 第二个坑：成员 ID 全是 ObjectId 第三个坑：APK 文件名太野了 Skill 封装：tb-apk-uploader 的核心流程 最终效果：一句话发包 踩坑总结：别被细节拖死 这周我被一个很\u0026quot;低级但很费命\u0026quot;的流程折磨了好几次:\n打开 Teambition 找到任务评论 下载 APK 附件 手动改名(还得对齐内部路径规则) 登录内网 Nexus 上传 复制下载链接 回到 TB 评论里粘贴 说实话,单次也就两三分钟,但一天来个十几次,人就会开始怀疑人生。\n我最后干脆把这条链路做成了一个 \u0026ldquo;一句话触发的 Agent\u0026rdquo;:\n\u0026ldquo;把 DO-XXXX 评论里的 APK 上传到 Nexus\u0026rdquo;\n剩下的事情:找附件 → 拿签名 URL → 下载 → 文件名标准化 → 上传 → 输出公开链接(甚至可以自动回评论)。\n下面把具体怎么做、踩了哪些坑,以及最后怎么封装成 Skill 讲清楚。\n目标:把\u0026quot;手工点点点\u0026quot;变成可复用链路 # 这类自动化我一般不追求\u0026quot;炫技\u0026quot;,就盯两个点:\n可重复:同样一句话/同样一个 taskId,能稳定跑出来。 可维护:别人接手也看得懂;出问题能定位是 TB、下载、还是上传。 而且我不想做\u0026quot;半自动\u0026quot;,那种最后还要打开网页点一下确认的,体验很差。\n技术方案:为什么选 MCP + Skill # 这里的关键是:Teambition 的任务评论、附件、成员信息,本质上都能 API 化。\n我用的是:\nTeambition MCP Server:通过 mcporter 接入 OpenClaw,让 Agent 能调用 TB 的开放 API。 ListTaskActivitiesV3:拿到任务动态/评论,以及评论里附件列表。 BatchGetFileDetails:把附件资源 ID 转成带签名的下载 URL(可直接 curl)。 PostV3MemberQuery:把 userId(ObjectId)翻译成\u0026quot;人类看得懂的姓名\u0026quot;。 apk-release-path Skill:内部规则化的 Nexus 路径生成 + 上传(这一步很关键,避免每个人传出来路径不一样)。 tb-apk-uploader Skill:把上面全部串起来,做成一句话入口。 整体结构长这样:\nflowchart LR A[Teambition 任务 DO-XXXX] --\u003e B[ListTaskActivitiesV3取评论/附件] B --\u003e C[BatchGetFileDetails拿签名下载URL] C --\u003e D[curl 下载到 /tmp] D --\u003e E[APK 文件名标准化] E --\u003e F[apk-release-path生成路径+上传 Nexus] F --\u003e G[输出公开下载链接] G --\u003e H[可选：回写 TB 评论] 实战:从 mcporter 调通第一条链路 # 1) 第一个坑:mcporter 的参数格式 # 我一开始很自然地写了:\nmcporter call teambition-mcp BatchGetFileDetails --body \u0026#39;{\u0026#34;resourceIds\u0026#34;:[\u0026#34;...\u0026#34;]}\u0026#39; 然后直接炸:SyntaxError。\n后来才反应过来：mcporter 不是按你想象的\u0026quot;HTTP body\u0026quot;来收参。\nmcporter 要用 --args，不是 --body 大部分 TB MCP 工具的入参都套在 requestBody 里 先用最小请求跑通，再往里加字段 正确姿势：\nmcporter call teambition-mcp BatchGetFileDetails \\ --args \u0026#39;{\u0026#34;requestBody\u0026#34;:{\u0026#34;needSign\u0026#34;:true,\u0026#34;expireAfterSeconds\u0026#34;:1800,\u0026#34;resourceIds\u0026#34;:[\u0026#34;task:xxxx/activity:yyyy/file:zzzz\u0026#34;]}}\u0026#39; 这一步通了之后,接口会给你一个带签名的下载地址(有过期时间)。接下来就简单了:curl -L。\n2) 拿评论和附件:ListTaskActivitiesV3 # 评论/动态是从 ListTaskActivitiesV3 拿的,关键是把附件列表捞出来。\n命令示例(按任务ID拉最近 50 条动态):\nmcporter call teambition-mcp ListTaskActivitiesV3 \\ --args \u0026#39;{\u0026#34;taskId\u0026#34;:\u0026#34;\u0026lt;TASK_ID\u0026gt;\u0026#34;,\u0026#34;pageSize\u0026#34;:50,\u0026#34;orderBy\u0026#34;:\u0026#34;created_desc\u0026#34;,\u0026#34;language\u0026#34;:\u0026#34;zh_CN\u0026#34;}\u0026#39; 你会得到一堆 activities,其中包含评论内容、创建人、以及附件的 fileId / resourceId 等信息。\n3) 第二个坑:成员 ID 全是 ObjectId,根本不可读 # creatorId、executorId 这种字段返回的都是 MongoDB ObjectId:\n60471fc306c1e046e63759c4 63d61d1cbde6c83a2ce729d6 这种东西放在日志里完全没意义,排查问题也很痛苦:\n\u0026ldquo;是谁发的评论?\u0026rdquo; \u0026ldquo;谁上传的包?\u0026rdquo; 解决方式我用了两层:\n先用 ListProjectMembersV3 拉项目成员列表(覆盖常见人) 遇到陌生 ID 再用 PostV3MemberQuery 精确查询并刷新缓存 如果你也搞过内部系统，会懂这种\u0026quot;ID 翻译\u0026quot;的重要性——不然自动化只会变成\u0026quot;自动生成一堆没人看的日志\u0026quot;。\nID 映射的两层策略：\nListProjectMembersV3 拉项目成员列表（覆盖常见人） 遇到陌生 ID 用 PostV3MemberQuery 精确查询并刷新缓存 4) 第三个坑:APK 文件名太野了,不标准化会出大事 # Teambition 附件下载下来,经常出现:\n.apk.1 这种后缀 (1) 这种重复下载的括号 前缀带 E(比如 E4218) 真实例子:\nRinoTrack_E4218_3.2.820260326_release(1).apk.1 我想要的标准化结果:\nRinoTrack_4218_3.2.820260326_release.apk 如果不做这一步,后面上传 Nexus 的路径和文件名就会失控:\n同一个版本被传出多个\u0026quot;看起来不一样\u0026quot;的包 别人复制链接下载的是错误文件 更坑的是:CI/自动脚本按规则匹配文件名时直接找不到 我最终把规则写成了一个\u0026quot;尽量保守\u0026quot;的清洗:\n去掉末尾 .1 / .2 这种下载器后缀 去掉末尾 (n) 把 _E4218_ 规整成 _4218_(仅在匹配到 E\\d+ 的场景) 保证最终以 .apk 结尾 下面贴个可用的 Python 版本(我的 tb-apk-uploader 里就是类似逻辑):\nimport re from pathlib import Path def normalize_apk_name(filename: str) -\u0026gt; str: name = filename # 先干掉可能的\u0026#34;重复下载后缀\u0026#34; # 例:xxx.apk.1 / xxx.apk.2 name = re.sub(r\u0026#34;(\\.apk)\\.\\d+$\u0026#34;, r\u0026#34;\\1\u0026#34;, name, flags=re.IGNORECASE) # 干掉 (1) (2) 这种 name = re.sub(r\u0026#34;\\(\\d+\\)(?=\\.apk$)\u0026#34;, \u0026#34;\u0026#34;, name, flags=re.IGNORECASE) # 规整 E4218 -\u0026gt; 4218(只处理紧跟数字的 E 前缀) name = re.sub(r\u0026#34;_E(\\d+)_\u0026#34;, r\u0026#34;_\\1_\u0026#34;, name) # 兜底:如果结尾不是 .apk,强行补回去 if not name.lower().endswith(\u0026#34;.apk\u0026#34;): name = re.sub(r\u0026#34;\\.+$\u0026#34;, \u0026#34;\u0026#34;, name) + \u0026#34;.apk\u0026#34; return name if __name__ == \u0026#34;__main__\u0026#34;: samples = [ \u0026#34;RinoTrack_E4218_3.2.820260326_release(1).apk.1\u0026#34;, \u0026#34;Demo_E1234_xxx.apk\u0026#34;, ] for s in samples: print(s, \u0026#34;-\u0026gt;\u0026#34;, normalize_apk_name(s)) 这种规则肯定不是\u0026quot;完美\u0026quot;,但目标是:别把\u0026quot;人类给的附件名\u0026quot;原样带进制品仓库。\nSkill 封装:tb-apk-uploader 的核心流程 # 当所有接口都调通了,就到了我最喜欢的部分:把它封装成能复用、能迭代的 Skill。\n我这里的 tb-apk-uploader 其实就是一个 Python 脚本 + 少量 glue:\n用 ListTaskActivitiesV3 找到目标评论里的 APK 附件 用 BatchGetFileDetails 把附件 resourceId 换成签名 URL curl -L 下载到 /tmp 标准化文件名 调用 apk-release-path 生成标准发布路径并上传 Nexus 输出可复制的公开下载链接 给一个\u0026quot;你照着就能跑\u0026quot;的 shell 片段(假设你已经拿到了签名 URL):\nset -euo pipefail SIGNED_URL=\u0026#34;$1\u0026#34; RAW_NAME=\u0026#34;$2\u0026#34; # 从 TB 附件字段里拿到的原始文件名 TMP_DIR=\u0026#34;/tmp/tb-apk\u0026#34; mkdir -p \u0026#34;$TMP_DIR\u0026#34; python3 - \u0026lt;\u0026lt;\u0026#39;PY\u0026#39; import os from pathlib import Path from normalize import normalize_apk_name # 你也可以直接把函数内联 raw = os.environ[\u0026#34;RAW_NAME\u0026#34;] out = normalize_apk_name(raw) print(out) PY 我自己的实现里更直接:Python 负责从 TB 拉数据并落地下载,shell 只做\u0026quot;串接工具\u0026quot;。\napk-release-path 这一步就不展开了(它是另一个 Skill),你只需要知道:\n它把\u0026quot;文件名\u0026quot;转换成内部统一的发布路径 然后上传到 Nexus 最终给你一个可访问的下载 URL 这是我想要的最终体验:\n我不关心 Nexus 目录怎么分层 我不关心文件名怎么对齐规则 我只要:发包链接 最终效果:一句话发包 # 做到这里,就可以真的把入口收敛成一句话了。\n比如:\n\u0026ldquo;把 DO-12345 评论里的 APK 上传到 Nexus\u0026rdquo; Agent 会做:\n自动找到最新评论里的 APK 附件(或按规则选中某条评论) 下载并标准化文件名 上传 Nexus 返回: https://nexus.xxx/repository/.../RinoTrack_4218_3.2.820260326_release.apk 如果你愿意,再加一步:\n自动把链接回写到 TB 评论里(避免手动复制粘贴) 这就从\u0026quot;脚本自动化\u0026quot;变成了\u0026quot;工作流自动化\u0026quot;。\n踩坑总结:别被细节拖死 # 最后把最实用的坑总结一下,免得你从头踩:\nmcporter 入参别想当然 # --body 不是你以为的那种 body。大部分工具入参要套 requestBody，先用最小请求跑通，再往里加字段。 ID 翻译是生产力 # ObjectId 放在日志里没意义 建一个成员映射缓存,遇到未知 ID 再刷新 排查问题会快很多 文件名标准化必须做 # 制品仓库是\u0026quot;长期资产\u0026quot;，名字乱了就是技术债。规则宁可保守，也别把垃圾后缀带进去。 Skill 封装要有边界 # 我封装 tb-apk-uploader 时有个底线:\n失败就失败,日志说清楚原因 不做\u0026quot;半成功\u0026quot;状态(比如上传了一半还返回链接) 不要把 Nexus 的规则散落在多个脚本里 这类自动化做多了你会发现:\n真正省下来的不是\u0026quot;点几下鼠标\u0026quot;的时间,而是减少中断。\n人一旦被打断(找评论、下载、改名、上传、复制链接),注意力就碎了,恢复成本比操作本身大得多。\n这次把链路收敛成一句话,算是把\u0026quot;碎片化劳动\u0026quot;按死了。\n如果你也有类似的重复流程,建议先从\u0026quot;签名 URL 可下载\u0026quot;这个点切进去,一条链路跑通后再做封装。跑通比优雅重要。\n","date":"2026年3月29日","externalUrl":null,"permalink":"/posts/from-mcp-to-one-click-release-ai-agent-automation/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-28/\"\u003e每日技术实践简报 - 2026-03-28\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-ai-agent-architecture-v2/\"\u003eOpenClaw AI Agent 架构解析：多引擎联动与记忆系统\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/ai-agent-workflow-practice/\"\u003e当 AI Agent 遇上运维自动化：我的实践踩坑之路\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e目录 \n    \u003cdiv id=\"目录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%9b%ae%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"#%e7%9b%ae%e6%a0%87%e6%8a%8a%e6%89%8b%e5%b7%a5%e7%82%b9%e7%82%b9%e7%82%b9%e5%8f%98%e6%88%90%e5%8f%af%e5%a4%8d%e7%94%a8%e9%93%be%e8%b7%af\"\u003e目标：把\u0026quot;手工点点点\u0026quot;变成可复用链路\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#%e6%8a%80%e6%9c%af%e6%96%b9%e6%a1%88%e4%b8%ba%e4%bb%80%e4%b9%88%e9%80%89-mcp--skill\"\u003e技术方案：为什么选 MCP + Skill\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#%e5%ae%9e%e6%88%98%e4%bb%8e-mcporter-%e8%b0%83%e9%80%9a%e7%ac%ac%e4%b8%80%e6%9d%a1%e9%93%be%e8%b7%af\"\u003e实战：从 mcporter 调通第一条链路\u003c/a\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"#%e7%ac%ac%e4%b8%80%e4%b8%aa%e5%9d%91mcporter-%e7%9a%84%e5%8f%82%e6%95%b0%e6%a0%bc%e5%bc%8f\"\u003e第一个坑：mcporter 的参数格式\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#%e6%8b%bf%e8%af%84%e8%ae%ba%e5%92%8c%e9%99%84%e4%bb%b6listtaskactivitiesv3\"\u003e拿评论和附件：ListTaskActivitiesV3\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#%e7%ac%ac%e4%ba%8c%e4%b8%aa%e5%9d%91%e6%88%90%e5%91%98-id-%e5%85%a8%e6%98%af-objectid\"\u003e第二个坑：成员 ID 全是 ObjectId\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#%e7%ac%ac%e4%b8%89%e4%b8%aa%e5%9d%91apk-%e6%96%87%e4%bb%b6%e5%90%8d%e5%a4%aa%e9%87%8e%e4%ba%86\"\u003e第三个坑：APK 文件名太野了\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#skill-%e5%b0%81%e8%a3%85tb-apk-uploader-%e7%9a%84%e6%a0%b8%e5%bf%83%e6%b5%81%e7%a8%8b\"\u003eSkill 封装：tb-apk-uploader 的核心流程\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#%e6%9c%80%e7%bb%88%e6%95%88%e6%9e%9c%e4%b8%80%e5%8f%a5%e8%af%9d%e5%8f%91%e5%8c%85\"\u003e最终效果：一句话发包\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#%e8%b8%a9%e5%9d%91%e6%80%bb%e7%bb%93%e5%88%ab%e8%a2%ab%e7%bb%86%e8%8a%82%e6%8b%96%e6%ad%bb\"\u003e踩坑总结：别被细节拖死\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003cp\u003e这周我被一个很\u0026quot;低级但很费命\u0026quot;的流程折磨了好几次:\u003c/p\u003e","title":"从 MCP 到一键发包：把 Teambition 评论里的 APK 自动上传 Nexus 的那些坑","type":"posts"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/categories/%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5/","section":"Categories","summary":"","title":"技术实践","type":"categories"},{"content":"","date":"2026年3月29日","externalUrl":null,"permalink":"/tags/%E8%87%AA%E5%8A%A8%E5%8C%96/","section":"Tags","summary":"","title":"自动化","type":"tags"},{"content":"","date":"2026年3月28日","externalUrl":null,"permalink":"/tags/ai/","section":"Tags","summary":"","title":"AI","type":"tags"},{"content":" 推荐阅读 # 每日技术实践简报 - 2026-03-27 OpenClaw AI Agent 架构解析：多引擎联动与记忆系统 今日技术实践 # 解决的问题 # Teambition MCP 全链路打通：验证了 BatchGetFileDetails、ListTaskActivitiesV3、PostV3MemberQuery 三个核心 API 的完整调用链路，解决了从任务评论中提取附件信息的需求。 成员 ID 映射机制：建立了项目成员 ObjectId → 真实姓名的映射表（37人），支持\u0026quot;陌生 ID 自动触发全量刷新\u0026quot;的缓存策略，让任务动态终于可读了。 tb-apk-uploader Skill 封装：将\u0026quot;TB附件获取 → 下载 → 重命名 → 上传 Nexus → 返回链接\u0026quot;的完整流程封装为独立 Skill，经 skill-creator 规范优化并打包验证通过。 学到的新东西 # mcporter 调用参数格式：mcporter call 必须用 --args 传参且包在 requestBody 里，用 --body 会报 SyntaxError。这是踩了一个小时的坑才找到的。 Skill 封装规范：frontmatter description 要包含完整触发词，body 只保留工作流程和脚本用法，不要重复 frontmatter 信息。渐进式披露，核心逻辑在脚本里。 APK 文件名标准化规则：TB 附件下载后经常带 .1 后缀、(1) 括号、E 前缀，需要正则清洗。如 RinoTrack_E4218_3.2.820260326_release(1).apk.1 → RinoTrack_4218_3.2.820260326_release.apk。 实际产出 # 成功上传了两个 APK 到 Nexus（RinoTrack 85MB + 微信 248MB） 自动回复了下载链接到 DO-6456 评论区 tb-apk-uploader Skill 打包为 .skill 文件，可分发复用 踩坑记录 # mcporter 参数格式：--body → --args + requestBody 包装 Nexus 重复上传：同一文件重复上传返回 HTTP 400，不是错误而是\u0026quot;已存在\u0026quot;，需要正确处理 其他思考 # 骑行语音 AI 助手项目构思（代号备选 CADENCE），核心场景：骑行中记录灵感 + 处理工作任务，已创建滴答清单任务储备。 讨论了 DevOps/SRE 在 AI 时代的转型方向，核心结论：不需要转行，需要在现有方向上叠加 AI 能力。 ","date":"2026年3月28日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-28/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-27/\"\u003e每日技术实践简报 - 2026-03-27\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-ai-agent-architecture-v2/\"\u003eOpenClaw AI Agent 架构解析：多引擎联动与记忆系统\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e今日技术实践 \n    \u003cdiv id=\"今日技术实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%8a%e6%97%a5%e6%8a%80%e6%9c%af%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eTeambition MCP 全链路打通\u003c/strong\u003e：验证了 \u003ccode\u003eBatchGetFileDetails\u003c/code\u003e、\u003ccode\u003eListTaskActivitiesV3\u003c/code\u003e、\u003ccode\u003ePostV3MemberQuery\u003c/code\u003e 三个核心 API 的完整调用链路，解决了从任务评论中提取附件信息的需求。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e成员 ID 映射机制\u003c/strong\u003e：建立了项目成员 ObjectId → 真实姓名的映射表（37人），支持\u0026quot;陌生 ID 自动触发全量刷新\u0026quot;的缓存策略，让任务动态终于可读了。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003etb-apk-uploader Skill 封装\u003c/strong\u003e：将\u0026quot;TB附件获取 → 下载 → 重命名 → 上传 Nexus → 返回链接\u0026quot;的完整流程封装为独立 Skill，经 skill-creator 规范优化并打包验证通过。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003emcporter 调用参数格式\u003c/strong\u003e：\u003ccode\u003emcporter call\u003c/code\u003e 必须用 \u003ccode\u003e--args\u003c/code\u003e 传参且包在 \u003ccode\u003erequestBody\u003c/code\u003e 里，用 \u003ccode\u003e--body\u003c/code\u003e 会报 SyntaxError。这是踩了一个小时的坑才找到的。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSkill 封装规范\u003c/strong\u003e：frontmatter description 要包含完整触发词，body 只保留工作流程和脚本用法，不要重复 frontmatter 信息。渐进式披露，核心逻辑在脚本里。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAPK 文件名标准化规则\u003c/strong\u003e：TB 附件下载后经常带 \u003ccode\u003e.1\u003c/code\u003e 后缀、\u003ccode\u003e(1)\u003c/code\u003e 括号、\u003ccode\u003eE\u003c/code\u003e 前缀，需要正则清洗。如 \u003ccode\u003eRinoTrack_E4218_3.2.820260326_release(1).apk.1\u003c/code\u003e → \u003ccode\u003eRinoTrack_4218_3.2.820260326_release.apk\u003c/code\u003e。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e实际产出 \n    \u003cdiv id=\"实际产出\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%9e%e9%99%85%e4%ba%a7%e5%87%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e成功上传了两个 APK 到 Nexus（RinoTrack 85MB + 微信 248MB）\u003c/li\u003e\n\u003cli\u003e自动回复了下载链接到 DO-6456 评论区\u003c/li\u003e\n\u003cli\u003etb-apk-uploader Skill 打包为 \u003ccode\u003e.skill\u003c/code\u003e 文件，可分发复用\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003emcporter 参数格式\u003c/strong\u003e：\u003ccode\u003e--body\u003c/code\u003e → \u003ccode\u003e--args\u003c/code\u003e + \u003ccode\u003erequestBody\u003c/code\u003e 包装\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eNexus 重复上传\u003c/strong\u003e：同一文件重复上传返回 HTTP 400，不是错误而是\u0026quot;已存在\u0026quot;，需要正确处理\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e其他思考 \n    \u003cdiv id=\"其他思考\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%b6%e4%bb%96%e6%80%9d%e8%80%83\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e骑行语音 AI 助手项目构思（代号备选 CADENCE），核心场景：骑行中记录灵感 + 处理工作任务，已创建滴答清单任务储备。\u003c/li\u003e\n\u003cli\u003e讨论了 DevOps/SRE 在 AI 时代的转型方向，核心结论：不需要转行，需要在现有方向上叠加 AI 能力。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-28：从 MCP 到一键 APK 发布的 Skill 封装","type":"posts"},{"content":"","date":"2026年3月28日","externalUrl":null,"permalink":"/tags/%E6%AF%8F%E6%97%A5%E6%80%BB%E7%BB%93/","section":"Tags","summary":"","title":"每日总结","type":"tags"},{"content":"","date":"2026年3月27日","externalUrl":null,"permalink":"/tags/gpu/","section":"Tags","summary":"","title":"GPU","type":"tags"},{"content":" 推荐阅读 # 每日技术实践简报 - 2026-03-26 每日技术实践简报 - 2026-03-25 OpenClaw 记忆回退模式：确保知识持久化不中断 今日技术实践 # 解决的问题 # GPU 选型与分层规划：明确了 PVE 虚拟化场景下的硬件方案。针对 Creo 5.0 的 3D 图形需求，确定以 GPU Passthrough 为主，建议配置 NVIDIA T1000 8GB 或 RTX A2000，而 Cadence 和 PADS 等 2D/EDA 工具则优先使用 CPU 渲染或基础 VDI。 AirDesk 交付路径收敛：硬件方案从“技术可行性分析”转向“可执行采购口径”。剔除了非必要授权费，成本模型更贴合实际落地场景。 学到的新东西 # 经济型 VDI 策略：在 7-8 人共享场景下，按并发使用 Creo 的人数配置 2-3 张 T1000 比全员配置 vGPU 更经济，规避了高昂的 GRID 许可证成本。 OpenClaw 安全基线：确立了“先读后改”的文件编辑规范，并强化了第三方 Skill 的供应链安全审查流程，自动化运维开始向安全可维护转型。 踩坑记录 # 自动化推送链路稳定性：在实现 AI 论文简报定时推送时，发现 cron + dashboard-pusher 的配合需要更严格的超时处理和环境隔离。 明日计划 # 完成 AirDesk 审批前的最后合规性映射。 优化 RSS 抓取链路的重试机制。 ","date":"2026年3月27日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-27/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-26/\"\u003e每日技术实践简报 - 2026-03-26\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-25/\"\u003e每日技术实践简报 - 2026-03-25\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-memory-fallback-pattern/\"\u003eOpenClaw 记忆回退模式：确保知识持久化不中断\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch1 class=\"relative group\"\u003e今日技术实践 \n    \u003cdiv id=\"今日技术实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%8a%e6%97%a5%e6%8a%80%e6%9c%af%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eGPU 选型与分层规划\u003c/strong\u003e：明确了 PVE 虚拟化场景下的硬件方案。针对 Creo 5.0 的 3D 图形需求，确定以 GPU Passthrough 为主，建议配置 NVIDIA T1000 8GB 或 RTX A2000，而 Cadence 和 PADS 等 2D/EDA 工具则优先使用 CPU 渲染或基础 VDI。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAirDesk 交付路径收敛\u003c/strong\u003e：硬件方案从“技术可行性分析”转向“可执行采购口径”。剔除了非必要授权费，成本模型更贴合实际落地场景。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e经济型 VDI 策略\u003c/strong\u003e：在 7-8 人共享场景下，按并发使用 Creo 的人数配置 2-3 张 T1000 比全员配置 vGPU 更经济，规避了高昂的 GRID 许可证成本。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eOpenClaw 安全基线\u003c/strong\u003e：确立了“先读后改”的文件编辑规范，并强化了第三方 Skill 的供应链安全审查流程，自动化运维开始向安全可维护转型。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e自动化推送链路稳定性\u003c/strong\u003e：在实现 AI 论文简报定时推送时，发现 cron + dashboard-pusher 的配合需要更严格的超时处理和环境隔离。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e完成 AirDesk 审批前的最后合规性映射。\u003c/li\u003e\n\u003cli\u003e优化 RSS 抓取链路的重试机制。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-27：GPU 分层选型与 OpenClaw 安全基线","type":"posts"},{"content":"","date":"2026年3月27日","externalUrl":null,"permalink":"/tags/%E8%99%9A%E6%8B%9F%E5%8C%96/","section":"Tags","summary":"","title":"虚拟化","type":"tags"},{"content":"","date":"2026年3月27日","externalUrl":null,"permalink":"/tags/%E8%BF%90%E7%BB%B4/","section":"Tags","summary":"","title":"运维","type":"tags"},{"content":"","date":"2026年3月26日","externalUrl":null,"permalink":"/tags/blog/","section":"Tags","summary":"","title":"Blog","type":"tags"},{"content":" 推荐阅读 # 每日技术实践简报 - 2026-03-25 每日技术实践简报 - 2026-03-24 OpenClaw 记忆回退模式：确保知识持久化不中断 今日技术实践 # 解决的问题 # 博客元数据标准化：将每日简报的 categories 统一为“实践记录”，优化归档页聚合口径。 Git 提交规范：修正 commit message 格式以通过仓库 hook 检查（使用 chore: 前缀）。 学到的新东西 # 技术工具约束：确认 bird news 命令不支持 --hours 参数。 替代方案：在需要时间窗口过滤时，应通过搜索能力叠加时间参数实现，而非依赖无效的 CLI 原生参数。 踩坑记录 # Git 提交被拒绝：原因在于 commit message 不符合仓库预设的规范（amend 后成功推送）。 注意事项：发布博客时应遵循“只提交正文文件，不提交 resources/_gen 生成资源”的原则，保持仓库整洁。 明日计划 # 优化 AI 资讯推送的筛选逻辑。 补全本周其余简报的相互内链（推荐阅读）。 ","date":"2026年3月26日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-26/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-25/\"\u003e每日技术实践简报 - 2026-03-25\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-24/\"\u003e每日技术实践简报 - 2026-03-24\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-memory-fallback-pattern/\"\u003eOpenClaw 记忆回退模式：确保知识持久化不中断\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch1 class=\"relative group\"\u003e今日技术实践 \n    \u003cdiv id=\"今日技术实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%8a%e6%97%a5%e6%8a%80%e6%9c%af%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e博客元数据标准化：将每日简报的 categories 统一为“实践记录”，优化归档页聚合口径。\u003c/li\u003e\n\u003cli\u003eGit 提交规范：修正 commit message 格式以通过仓库 hook 检查（使用 \u003ccode\u003echore:\u003c/code\u003e 前缀）。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e技术工具约束：确认 \u003ccode\u003ebird news\u003c/code\u003e 命令不支持 \u003ccode\u003e--hours\u003c/code\u003e 参数。\u003c/li\u003e\n\u003cli\u003e替代方案：在需要时间窗口过滤时，应通过搜索能力叠加时间参数实现，而非依赖无效的 CLI 原生参数。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eGit 提交被拒绝：原因在于 commit message 不符合仓库预设的规范（amend 后成功推送）。\u003c/li\u003e\n\u003cli\u003e注意事项：发布博客时应遵循“只提交正文文件，不提交 resources/_gen 生成资源”的原则，保持仓库整洁。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e优化 AI 资讯推送的筛选逻辑。\u003c/li\u003e\n\u003cli\u003e补全本周其余简报的相互内链（推荐阅读）。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-26","type":"posts"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/%E5%B7%A5%E7%A8%8B%E6%B2%BB%E7%90%86/","section":"Tags","summary":"","title":"工程治理","type":"tags"},{"content":" 今日技术实践 # 解决的问题 # 把分散的 DevOps 反馈从“零散问题”改写为“工程健康治理”视角，先做问题分层，再做治理路线，避免头痛医头式修修补补。 对未解决痛点做了结构化快照，统一了后续追踪入口，减少跨工具来回翻找的时间成本。 学到的新东西 # 当问题长期存在且跨团队反复出现时，最有效的切入点不是继续加临时补丁，而是定义统一指标与优先级，把“处理单点故障”升级为“治理系统性风险”。 轻量任务管理在早期调研阶段很关键：先把研究动作显式化，再逐步补齐验收标准，执行阻力会明显下降。 踩坑记录 # 仅有结论没有上下文时，后续复盘很难复用；因此当天补齐了“问题快照 + 行动项”配对记录，确保后续能直接接力推进。 明日计划 # 继续完善工程健康治理的指标草案（稳定性、交付效率、可观测性）。 将今日痛点快照扩展为可执行清单，补充负责边界与时间窗口。 对研究任务增加阶段性产出定义，确保每次推进都有可验证结果。 推荐阅读 # 每日技术实践简报 - 2026-03-24 每日技术实践简报 - 2026-03-23 OpenClaw 记忆层降级策略：当 Working Memory 不可用时，如何保持稳定输出 ","date":"2026年3月25日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-25/","section":"博客文章","summary":"\u003ch1 class=\"relative group\"\u003e今日技术实践 \n    \u003cdiv id=\"今日技术实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%8a%e6%97%a5%e6%8a%80%e6%9c%af%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e把分散的 DevOps 反馈从“零散问题”改写为“工程健康治理”视角，先做问题分层，再做治理路线，避免头痛医头式修修补补。\u003c/li\u003e\n\u003cli\u003e对未解决痛点做了结构化快照，统一了后续追踪入口，减少跨工具来回翻找的时间成本。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e当问题长期存在且跨团队反复出现时，最有效的切入点不是继续加临时补丁，而是定义统一指标与优先级，把“处理单点故障”升级为“治理系统性风险”。\u003c/li\u003e\n\u003cli\u003e轻量任务管理在早期调研阶段很关键：先把研究动作显式化，再逐步补齐验收标准，执行阻力会明显下降。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e仅有结论没有上下文时，后续复盘很难复用；因此当天补齐了“问题快照 + 行动项”配对记录，确保后续能直接接力推进。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e继续完善工程健康治理的指标草案（稳定性、交付效率、可观测性）。\u003c/li\u003e\n\u003cli\u003e将今日痛点快照扩展为可执行清单，补充负责边界与时间窗口。\u003c/li\u003e\n\u003cli\u003e对研究任务增加阶段性产出定义，确保每次推进都有可验证结果。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-24/\"\u003e每日技术实践简报 - 2026-03-24\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-23/\"\u003e每日技术实践简报 - 2026-03-23\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-memory-fallback-pattern/\"\u003eOpenClaw 记忆层降级策略：当 Working Memory 不可用时，如何保持稳定输出\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-25","type":"posts"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/","section":"Tags","summary":"","title":"知识管理","type":"tags"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/amd/","section":"Tags","summary":"","title":"AMD","type":"tags"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/gpu-passthrough/","section":"Tags","summary":"","title":"GPU Passthrough","type":"tags"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/iommu/","section":"Tags","summary":"","title":"IOMMU","type":"tags"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/proxmox/","section":"Tags","summary":"","title":"Proxmox","type":"tags"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/pve/","section":"Tags","summary":"","title":"PVE","type":"tags"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/series/pve-%E5%AE%9E%E8%B7%B5/","section":"Series","summary":"","title":"PVE 实践","type":"series"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/rds/","section":"Tags","summary":"","title":"RDS","type":"tags"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/rx-6400/","section":"Tags","summary":"","title":"RX 6400","type":"tags"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/vfio/","section":"Tags","summary":"","title":"VFIO","type":"tags"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/tags/windows-server/","section":"Tags","summary":"","title":"Windows Server","type":"tags"},{"content":" 事情的起因很简单：几个工程师需要远程跑 Cadence、PADS、AutoCAD 和 Creo，手头只有一台装了 PVE 的 Ryzen 5600X 主机和一张 RX 6400。听起来不难，做起来全是坑。 需求拆解 # 先把需求摊开看。要跑的软件有四个，GPU 依赖程度各不相同：\n软件 用途 对 GPU 的依赖 Cadence 16.6 PCB/原理图 极低，纯 2D PADS VX2.4 PCB 设计 低，3D 预览需基础 OpenGL AutoCAD 2020 工程制图 中，DirectX/OpenGL 加速 Creo 5.0 3D 建模 中，OpenGL 渲染 Cadence 和 PADS 其实纯 CPU 软渲染就能应付。真正需要 GPU 的是 AutoCAD 的大图纸和 Creo 的三维装配体。所以一开始的策略是先不折腾 GPU，装好系统跑起来试试 —— 如果软渲染够用，就省了一堆事。\n结论是：不够用。Creo 打开稍微复杂一点的模型就明显卡顿，AutoCAD 在缩放大图纸时也有延迟。于是只好走 GPU 直通这条路。\n硬件情况 # 先说结论：这套硬件能跑通 GPU 直通，但过程不算顺利。如果你也是 B550 + RX 6400 的组合，下面的内容可能帮你省掉几个小时的排查。 项目 配置 CPU AMD Ryzen 5 5600X（12 线程） 内存 64GB DDR4 GPU AMD Radeon RX 6400（Navi 24，盈通，4GB） 主板 B550 芯片组 存储 NVMe 476GB（系统）+ 2x NVMe 931GB RAID0 + SATA 1.8TB PVE 8.4.0，内核 6.8.12-17-pve 第一道坎：IOMMU 分组 # 跑 lspci -tv 看 PCIe 拓扑的时候就意识到问题了。RX 6400 是一张 x4 通道的入门卡，它不走 CPU 直连的 x16 插槽，而是挂在 B550 芯片组的 PCIe Switch 下面。\ngraph TD CPU[\"Ryzen 5 5600XPCIe Root Complex\"] CPU --\u003e|\"Root Port 00:01.1CPU 直连 x4\"| NVMe1[\"ADATA NVMe\"] CPU --\u003e|\"Root Port 00:01.2CPU 直连 x4\"| Chipset[\"B550 芯片组 Switch\"] CPU --\u003e|\"Root Port 00:03.1~3CPU 直连\"| NVMe234[\"3x WD NVMe\"] Chipset --\u003e USB[\"USB 3.1\"] Chipset --\u003e SATA[\"SATA\"] Chipset --\u003e|\"芯片组桥接 → Navi 内置 Switch\"| GPU[\"RX 6400\"] Chipset --\u003e WiFi[\"MT7921 WiFi\"] Chipset --\u003e ETH[\"RTL8125 2.5G\"] style GPU fill:#e74c3c,color:#fff style Chipset fill:#3498db,color:#fff 问题在于，挂在同一个芯片组 Switch 下的设备全部被内核归到了同一个 IOMMU Group 17 —— 一共 12 个设备：\nIOMMU Group 17: 02:00.0 USB 3.1 控制器 02:00.1 SATA 控制器 02:00.2 芯片组 Switch 上行端口 03:00.0 / 03:08.0 / 03:09.0 PCIe 桥接 04:00.0 Navi 内置 Switch 上行端口 05:00.0 Navi 内置 Switch 下行端口 06:00.0 RX 6400 GPU ← 我们要的 06:00.1 HDMI/DP 音频控制器 ← 我们要的 07:00.0 MT7921 WiFi 网卡 08:00.0 RTL8125 有线网卡 PCI 直通的规则是：要么把整个 IOMMU 组的设备全部传给虚拟机，要么一个都不传。显然不能把 USB、SATA 和网卡全交出去 —— 宿主机还要用这些东西。\n主板上也没有多余的 CPU 直连 x16 槽位给我换一张 x8/x16 的显卡，四个 NVMe 已经把 CPU 的 PCIe 通道占满了。所以换卡方案也走不通。\nACS Override：强制拆分 IOMMU 组 # ACS（Access Control Services）是 PCIe 规范中的一项安全特性，控制设备之间的 DMA 数据流。当 PCIe Switch 不支持 ACS 时，其下的设备可以绕过 IOMMU 直接互相访问内存，内核因此将它们归入同一个 IOMMU 组。pcie_acs_override 参数让内核假装硬件支持 ACS，从而将每个设备拆到独立的组。代价是放弃了硬件级 DMA 隔离 —— 家用环境风险可以忽略。 PVE 的内核已经内置了 ACS Override 补丁（可以通过 grep pcie_acs_overrides /proc/kallsyms 验证），所以不需要自己编译内核。\n宿主机配置 # 这一段涉及四个文件的修改，全部做完后才重启。漏掉任何一步都会导致 GPU 没法正确交给 vfio-pci。\nGRUB 参数 # 编辑 /etc/default/grub：\nGRUB_CMDLINE_LINUX_DEFAULT=\u0026#34;quiet amd_iommu=on iommu=pt pcie_acs_override=downstream,multifunction vfio-pci.ids=1002:743f,1002:ab28 video=vesafb:off video=efifb:off initcall_blacklist=sysfb_init\u0026#34; 这行参数做了六件事：\n参数 干什么的 amd_iommu=on 启用 IOMMU iommu=pt passthrough 模式，不直通的设备不经 IOMMU 翻译，性能更好 pcie_acs_override=downstream,multifunction 拆分 IOMMU 组 vfio-pci.ids=1002:743f,1002:ab28 让 GPU（743f）和它的音频控制器（ab28）在启动时就被 vfio-pci 抢占 video=vesafb:off video=efifb:off 禁用内核帧缓冲，不让宿主机碰 GPU initcall_blacklist=sysfb_init 避免 sysfb 与 GPU 的 BOOTFB 冲突 怎么查你的设备 ID？运行 lspci -nn | grep -i vga，输出里方括号内的 1002:743f 就是 Vendor:Device ID。音频控制器用 lspci -nn | grep -i audio | grep AMD 查。 VFIO 模块配置 # 三个 modprobe 配置文件：\n/etc/modprobe.d/vfio.conf\noptions vfio-pci ids=1002:743f,1002:ab28 softdep amdgpu pre: vfio-pci softdep 这行很关键 —— 它告诉内核在加载 amdgpu 驱动之前先加载 vfio-pci，确保 vfio-pci 能抢先绑定 GPU。没有这行的话，amdgpu 可能先把显卡占住，vfio-pci 就拿不到了。\n/etc/modprobe.d/blacklist-amdgpu.conf\nblacklist amdgpu 双保险，直接把 amdgpu 拉黑。\n/etc/modprobe.d/kvm.conf\noptions kvm ignore_msrs=1 Windows 会访问一些 KVM 不认识的 MSR（Model Specific Register），默认行为是内核直接 panic。加上这个参数让它忽略掉。\n内核模块 # 在 /etc/modules 中追加：\nvfio vfio_iommu_type1 vfio_pci vfio_virqfd 更新引导并重启 # update-grub update-initramfs -u -k all reboot 重启后宿主机没有本地显示输出了。 GPU 已经被 vfio-pci 接管，宿主机的显示器会黑屏。后续只能通过 SSH 或 PVE Web UI（https://宿主机IP:8006）管理。确保你的 SSH 连接正常再重启。 验证 # 重启后 SSH 进来检查：\n# 内核参数是否生效 cat /proc/cmdline # 确认能看到 pcie_acs_override 和 vfio-pci.ids # GPU 是否被 vfio-pci 绑定 lspci -s 06:00.0 -k # Kernel driver in use: vfio-pci ← 看到这个就对了 # IOMMU 组是否成功拆分 ls /sys/kernel/iommu_groups/ | wc -l # 修改前 26 个组，修改后 37 个组 验证 GPU 独立在自己的 IOMMU 组中（不再和 USB、SATA 混在一起）：\nfind /sys/kernel/iommu_groups/ -type l | \\ while read l; do g=$(echo $l | awk -F/ \u0026#39;{print $5}\u0026#39;) d=$(basename $l) echo \u0026#34;Group $g: $(lspci -s $d)\u0026#34; done | grep \u0026#34;06:00\u0026#34; # 期望输出: # Group 25: 06:00.0 VGA ... AMD Radeon RX 6400 # Group 26: 06:00.1 Audio ... Navi 21/23 HDMI/DP 创建 Windows Server 2022 虚拟机 # 一个关于 UEFI 的教训 # 一开始我用的是 OVMF（UEFI BIOS）+ Q35 + Secure Boot + TPM 2.0 的\u0026quot;豪华\u0026quot;配置。结果发现 VM 怎么都不从 ISO 启动 —— 直接跳到 UEFI Shell 或者 BIOS 设置界面。\n排查过程走了三轮弯路：\nIDE 光驱 → OVMF 根本不认 IDE 设备的启动能力，换 SATA SATA 光驱 → 还是进 UEFI Shell → 重建 EFI NVRAM（删除 efidisk 再创建），无效 关闭 Secure Boot（pre-enrolled-keys=0）→ 依然无效 ISO 挂载是正常的（mount -o loop 确认过 /efi/boot/bootx64.efi 存在且签名有效），QEMU Monitor 里也能看到光驱已挂载。就是不引导。\n最后换了 SeaBIOS 一次就进安装界面了。Windows Server 2022 不强制要求 UEFI/TPM（那是 Windows 11 桌面版的要求），用 SeaBIOS 完全没问题。\nVM 创建参数 # qm create 200 \\ --name win-server-2022-rds \\ --memory 8192 \\ --balloon 4096 \\ --cores 6 \\ --sockets 1 \\ --cpu host \\ --ostype win11 \\ --machine pc-q35-9.2 \\ --bios seabios \\ --scsihw virtio-scsi-single \\ --scsi0 sda:100,ssd=1,iothread=1,cache=writeback,discard=on \\ --sata0 sda:iso/windows-server-2022.iso,media=cdrom \\ --sata1 local:iso/virtio-win-0.1.285.iso,media=cdrom \\ --net0 virtio,bridge=vmbr0,firewall=0 \\ --vga virtio \\ --boot \u0026#34;order=sata0;scsi0\u0026#34; \\ --agent 1 \\ --numa 1 几个值得注意的选项：\nmachine: pc-q35-9.2 —— Q35 芯片组才支持 PCIe passthrough，i440fx 不行 scsihw: virtio-scsi-single —— 半虚拟化磁盘控制器，性能远好于 IDE/SATA 模拟，但 Windows 安装时需要从 VirtIO ISO 加载驱动 balloon: 4096 —— 内存上限 8GB，最低可缩到 4GB，让宿主机能回收闲置内存 cache: writeback —— 写缓存，提升磁盘写入性能 选择 Standard (Desktop Experience) —— 不要选纯 Server Core，RDS 桌面访问需要完整 GUI 安装过程 # 安装 Windows 时磁盘选择界面会显示空白（看不到 VirtIO SCSI 磁盘），需要手动加载驱动：\n点击 \u0026ldquo;加载驱动程序\u0026rdquo; 浏览 VirtIO ISO → vioscsi\\2k22\\amd64 → 加载 SCSI 驱动 再加载一次 → NetKVM\\2k22\\amd64 → 网卡驱动 现在能看到 100GB 磁盘了，选中继续安装 装好系统后从 VirtIO ISO 运行 virtio-win-guest-tools.exe，一次性安装全部驱动和 QEMU Guest Agent。\nGPU 直通 # 系统装好后把 GPU 挂上去：\nqm set 200 --hostpci0 0000:06:00,pcie=1 0000:06:00 会同时包含 .0（GPU）和 .1（音频），pcie=1 启用 PCIe 模式。\n启动 VM 时可能看到这样的警告：\nerror writing \u0026#39;1\u0026#39; to \u0026#39;/sys/bus/pci/devices/0000:06:00.0/reset\u0026#39;: Inappropriate ioctl for device failed to reset PCI device \u0026#39;0000:06:00.0\u0026#39;, but trying to continue... 这是 AMD 消费级卡的通病（Reset Bug），不影响正常使用，但 VM 关机后再启动可能需要冷启动宿主机。\n驱动安装：绕过 AMD 的 OS 检查 # GPU 进了 VM，Windows 设备管理器里也能看到 —— 但显示为 Microsoft 基本显示适配器，状态是 Error。需要装 AMD 驱动。\n问题是 AMD Adrenalin 安装器不支持 Windows Server。运行后直接报 Error 184（不支持的操作系统）。但驱动本身是兼容的，只是安装器做了 OS 检查。\n绕过方法：手动安装\n下载 Adrenalin 驱动（选 Windows 10/11 版本） 运行安装包 → 它会解压文件到 C:\\AMD\\ → 然后报错退出（正常，我们只要解压出来的文件） 设备管理器 → 找到 Error 状态的显示适配器 → 右键 → 更新驱动程序 浏览我的电脑以查找驱动程序 → 路径填 C:\\AMD\\ → 勾选 \u0026ldquo;包含子文件夹\u0026rdquo; 如果提示兼容性警告，选 \u0026ldquo;仍然安装\u0026rdquo; 重启 备选方案：RX 6400 和 Radeon PRO W6400 是同一颗 Navi 24 芯片。PRO 驱动官方支持 Windows Server 2022，可以试试从 PRO W6400 的驱动页面下载后用同样的方式手动安装。另外 GitHub 上有一个 AMD_DriverMagic_PatchWinServer2022-2025 工具，可以修改驱动 INF 文件绕过 OS 检查，实现完整 Adrenalin 安装。 配置 RDS 远程桌面服务 # GPU 搞定后，配置 RDS 让多个用户能同时通过 RDP 登录。\n安装 RDS 角色 # Install-WindowsFeature -Name RDS-RD-Server -IncludeManagementTools -Restart 一个关于域环境的坑 # 装好 RDS 打开服务器管理器的 \u0026ldquo;远程桌面服务\u0026rdquo; 页面时，会看到一行提示：\n\u0026ldquo;你当前是以计算机上本地管理员身份登录的。必须以域用户的身份登录才能管理服务器和集合。\u0026rdquo;\n这意味着 RDS 的完整管理功能（创建集合、发布 RemoteApp）需要 Active Directory 域环境。对于我们这种单机场景，搭域控太重了。\n更简单的方案：不用 RemoteApp，直接让每个用户 RDP 登录完整桌面，各自打开需要的软件。实际使用中 RemoteApp 和完整桌面对 GPU 的消耗差距可以忽略 —— GPU 的主要负载来自 CAD 应用本身，不在桌面合成。\n多用户 RDP 配置 # # 开启远程桌面 Set-ItemProperty -Path \u0026#34;HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\u0026#34; ` -Name \u0026#34;fDenyTSConnections\u0026#34; -Value 0 # 允许同一用户建立多个会话 Set-ItemProperty -Path \u0026#34;HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Terminal Services\u0026#34; ` -Name \u0026#34;fSingleSessionPerUser\u0026#34; -Value 0 # 放行防火墙 Enable-NetFirewallRule -DisplayGroup \u0026#34;Remote Desktop\u0026#34; 启用 RDP 会话的 GPU 加速 # 这一步很容易被忽略 —— Windows Server 的 RDP 会话默认不使用 GPU，而是用 Microsoft Basic Render Driver 做 CPU 软渲染。即使你已经直通了 GPU 并装好了驱动，RDP 会话也不会自动使用它。\n# 让 RDS 会话使用硬件图形适配器 reg add \u0026#34;HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Terminal Services\u0026#34; ` /v bEnumerateHWBeforeSW /t REG_DWORD /d 1 /f # 启用 H.264/AVC 444 编码（画质更好） reg add \u0026#34;HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Terminal Services\u0026#34; ` /v AVC444ModePreferred /t REG_DWORD /d 1 /f # 优先使用 GPU 硬件编码 reg add \u0026#34;HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Terminal Services\u0026#34; ` /v AVCHardwareEncodePreferred /t REG_DWORD /d 1 /f 也可以走组策略：gpedit.msc → 计算机配置 → 管理模板 → Windows 组件 → 远程桌面服务 → 远程桌面会话主机 → 远程会话环境 → 启用 \u0026ldquo;对所有远程桌面服务会话使用硬件默认图形适配器\u0026rdquo;。\n创建用户 # net user user1 P@ssw0rd123 /add net user user2 P@ssw0rd123 /add net localgroup \u0026#34;Remote Desktop Users\u0026#34; user1 /add net localgroup \u0026#34;Remote Desktop Users\u0026#34; user2 /add 重启后，各用户可以用 mstsc 同时连接到 VM IP，各自在独立的桌面会话中使用 CAD 软件。\nVM 最终配置 # agent: 1 balloon: 4096 bios: seabios boot: order=scsi0 cores: 6 cpu: host hostpci0: 0000:06:00,pcie=1 machine: pc-q35-9.2 memory: 8192 name: win-server-2022-rds net0: virtio=BC:24:11:34:E1:5E,bridge=vmbr0 numa: 1 ostype: win11 scsi0: md0:200/vm-200-disk-0.qcow2,cache=writeback,discard=on,iothread=1,size=100G,ssd=1 scsihw: virtio-scsi-single vga: virtio 踩坑清单 # 在这个项目里踩过的坑，按被坑的程度排序：\nOVMF 不引导 ISO # 花了很长时间在 UEFI 引导上，尝试了 IDE 光驱、SATA 光驱、重建 EFI NVRAM、关闭 Secure Boot，全部失败。最终发现 SeaBIOS 一次成功。教训是不要在不必要的地方追求\u0026quot;正确\u0026quot;的配置 —— Windows Server 2022 用 SeaBIOS 跑得好好的。\nGRUB 配置丢失 # 中间遇到一次网络中断后重启，发现 GRUB 参数被重置回 quiet，所有 IOMMU 和 VFIO 配置全部失效。modprobe 配置文件和 /etc/modules 的改动也丢了。原因不明，可能是 PVE 的某次自动更新覆盖了配置。后来重新配置了一遍才恢复。\n建议：改完配置文件后用 etckeeper 或手动备份 /etc/default/grub、/etc/modprobe.d/ 和 /etc/modules。被覆盖一次就够受的了。 AMD 驱动 Error 184 # AMD 的消费级驱动安装器硬编码了 OS 白名单，不包含 Windows Server。但底层驱动（INF 文件）本身兼容 Server 内核。通过设备管理器手动指向解压后的驱动目录可以成功安装。\nAMD Reset Bug # 消费级 AMD 显卡（RDNA/RDNA2）普遍存在 GPU Reset Bug —— VM 关机后 GPU 不能正确重置，再次启动 VM 时 GPU 无法初始化。解决方法只有冷启动宿主机。在需要频繁重启 VM 的调试阶段会比较痛苦。\nRDS 需要域环境 # RDS 的 RemoteApp、集合管理等高级功能只在 Active Directory 域环境下可用。无域环境只能走最基础的多用户 RDP 桌面登录。对于三五个用户的小团队来说，全桌面 RDP 反而更简单直接。\n使用建议 # 经过实际测试后的一些经验：\n事项 说明 用户并发 RX 6400 只有 4GB 显存，建议控制在 2-3 个并发用户 软件选择 Cadence/PADS 这类 2D 软件其实不需要 GPU 直通，CPU 软渲染足矣 驱动稳定性 消费级 GPU 在多 RDS 会话下可能触发 dwm.exe 崩溃，据社区反馈 6-15 用户时出现概率较高 宿主机管理 GPU 直通后宿主机没有本地显示，确保 SSH 和 Web UI 始终可达 BIOS 设置 如果遇到直通不工作，检查 BIOS 里的 ReBAR / SAM / C.A.M. 设置，禁用通常能解决问题 许可证 注意 CAD 软件在多用户终端服务器上的授权方式，部分厂商有单独的网络许可要求 参考资料 # Proxmox VE PCI Passthrough 官方文档 GPU Passthrough w/ Radeon RX 6400 - HaynesLab GPU Passthrough - Radeon 6800xt and beyond（Proxmox 论坛） Install RDS in Workgroup without Domain - woshub.com AMD_DriverMagic_PatchWinServer2022-2025 Modding Adrenalin Driver for Windows Server 2022（guru3D 论坛） Windows Server 2022 RDS and GPU question（Microsoft Q\u0026amp;A） ","date":"2026年3月25日","externalUrl":null,"permalink":"/posts/pve-gpu-passthrough-windows-server-rds/","section":"博客文章","summary":"一张 x4 通道的入门显卡、一块消费级 B550 主板、一个巨大的 IOMMU 分组 —— 这大概是 GPU 直通最不利的起点。但 ACS Override 改变了游戏规则。","title":"把一张 RX 6400 塞进虚拟机：PVE GPU 直通 + Windows Server RDS 部署全记录","type":"posts"},{"content":"","date":"2026年3月25日","externalUrl":null,"permalink":"/categories/%E8%99%9A%E6%8B%9F%E5%8C%96/","section":"Categories","summary":"","title":"虚拟化","type":"categories"},{"content":" 来源声明：本文基于开源仓库 cdryzun/gitlab-ci-templates 的 README、目录结构与公开文档整理，重点提炼可直接落地的工程实践。\n很多团队在 GitLab CI/CD 上都踩过同一个坑：\n每个仓库维护一份独立流水线，重复劳动严重； 语言一多（Java / Node.js / Python / Go），规则开始分裂； 扫描、部署、回滚策略缺少统一治理。 这篇文章的核心目标很明确：用一套可复用模板，把“能跑”升级成“可持续维护”。\n一、3 行接入：把流水线从“项目资产”变成“平台能力” # 最小接入：\ninclude: - remote: \u0026#39;https://raw.githubusercontent.com/cdryzun/gitlab-ci-templates/open/templates/Auto-DevOps.gitlab-ci.yml\u0026#39; 这一步做完后，你拿到的不只是一个 build job，而是一整套默认能力：\n多语言自动识别（Java / Node.js / Python / Go） Docker 构建与推送 SonarQube 质量门禁 ArgoCD GitOps 部署路径 Secret 扫描能力 二、为什么模板化不是“偷懒”，而是工程升级 # 短期看，手写 CI 非常灵活。长期看，手写 CI 的问题会在规模阶段集中爆发：\n一致性崩溃：同类项目规则各不相同； 维护成本上升：升级要改 N 份 YAML； 排障效率下降：每个仓库错误定位路径不同； 安全治理困难：扫描与门禁无法统一推进。 模板化的本质是：\n把经验沉淀成默认值； 把默认值升级为团队标准； 把“人肉维护”替换成“机制维护”。 三、落地时最关键的 3 个动作 # 1) 先统一“主干流程”，再允许项目覆盖变量 # 建议把公共 stage 固定为：\nbuild test deploy rollback 项目只通过变量覆盖个性化配置（如 BUILD_SHELL、PROJECT_TYPE、DOCKER_IMAGE_BUILD），不要随意改流程骨架。\n2) 用 stable/latest 双轨做风险隔离 # 公共模板升级建议走“双轨”：\nstable：给生产业务用，优先稳定； latest：给实验项目用，优先新能力。 这样能显著降低“模板升级误伤线上”的概率。\n3) 用 GitOps 管理部署变更，不做黑箱发布 # 接入 ArgoCD 后，把部署动作变成“可审计、可回滚、可复现”的配置变更流，而不是临时命令。\n四、适合哪些团队 # 这套模式尤其适合：\n有多个 GitLab 仓库的团队； 语言栈混合的团队； 正在推进 DevSecOps / GitOps 的团队； 希望把 CI/CD 从“个人经验”升级为“组织能力”的团队。 五、一个 30 秒验收清单 # 接入后，首轮验收建议看 4 个信号：\n✅ build stage 正常通过 ✅ test stage 正常通过 ✅ Docker 镜像推送成功（开启镜像构建时） ✅ deploy job 根据分支规则正确触发 如果这 4 个都稳定，说明你的基线已经具备“可复制扩展”的条件。\n六、结论 # gitlab-ci-templates 的价值不在于“少写几行 YAML”，而在于：\n降低 DevOps 维护熵增； 提升跨项目一致性； 把团队从重复劳动中解放出来。 仓库地址： https://github.com/cdryzun/gitlab-ci-templates\n如果你也在做 GitLab CI/CD 标准化，这个项目值得直接拿来做基线。\n","date":"2026年3月24日","externalUrl":null,"permalink":"/posts/gitlab-ci-templates-3-line-production-cicd/","section":"博客文章","summary":"\u003cblockquote\u003e\n\u003cp\u003e来源声明：本文基于开源仓库 \u003ccode\u003ecdryzun/gitlab-ci-templates\u003c/code\u003e 的 README、目录结构与公开文档整理，重点提炼可直接落地的工程实践。\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e很多团队在 GitLab CI/CD 上都踩过同一个坑：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e每个仓库维护一份独立流水线，重复劳动严重；\u003c/li\u003e\n\u003cli\u003e语言一多（Java / Node.js / Python / Go），规则开始分裂；\u003c/li\u003e\n\u003cli\u003e扫描、部署、回滚策略缺少统一治理。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这篇文章的核心目标很明确：\u003cstrong\u003e用一套可复用模板，把“能跑”升级成“可持续维护”。\u003c/strong\u003e\u003c/p\u003e\n\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e一、3 行接入：把流水线从“项目资产”变成“平台能力” \n    \u003cdiv id=\"一3-行接入把流水线从项目资产变成平台能力\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%803-%e8%a1%8c%e6%8e%a5%e5%85%a5%e6%8a%8a%e6%b5%81%e6%b0%b4%e7%ba%bf%e4%bb%8e%e9%a1%b9%e7%9b%ae%e8%b5%84%e4%ba%a7%e5%8f%98%e6%88%90%e5%b9%b3%e5%8f%b0%e8%83%bd%e5%8a%9b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e最小接入：\u003c/p\u003e","title":"3 行 YAML 接入生产级 GitLab CI/CD：gitlab-ci-templates 实战","type":"posts"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/argocd/","section":"Tags","summary":"","title":"ArgoCD","type":"tags"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/ci/cd/","section":"Tags","summary":"","title":"CI/CD","type":"tags"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/categories/devops/","section":"Categories","summary":"","title":"DevOps","type":"categories"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/categories/engineering/","section":"Categories","summary":"","title":"Engineering","type":"categories"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/gitlab-ci/","section":"Tags","summary":"","title":"GitLab CI","type":"tags"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/sonarqube/","section":"Tags","summary":"","title":"SonarQube","type":"tags"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/%E6%A8%A1%E6%9D%BF%E5%8C%96/","section":"Tags","summary":"","title":"模板化","type":"tags"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/%E6%8F%92%E4%BB%B6%E5%85%BC%E5%AE%B9/","section":"Tags","summary":"","title":"插件兼容","type":"tags"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/%E5%B7%A5%E7%A8%8B%E5%AE%9E%E8%B7%B5/","section":"Tags","summary":"","title":"工程实践","type":"tags"},{"content":"今天的重点是把“偶发不响应”从体感问题落到可验证的工程问题上：先处理运行形态冲突，再压低高延迟链路，最后形成可复用的排障路径。\n解决的问题 # 插件加载失败从“无法启动”修到“可控禁用”：针对插件依赖 openclaw/plugin-sdk 解析失败和 SDK 版本不兼容的问题，先做了本地兼容修复，再按当前需求将相关插件置为禁用，避免持续影响主链路可用性。 网关偶发不响应定位到运行冲突：通过日志和进程状态确认了“多套守护/重复拉起”带来的端口争用与重启风暴风险，最终收敛为“仅 PM2 单监管器”模式，减少了短时不可用窗口。 记忆检索超时有了明确根因：nmem 超时并非随机波动，而是查询输入过长、结构不佳时触发；同时保留 API fallback 作为兜底，保证主流程不被单点阻塞。 学到的新东西 # 稳定性优化要先做形态治理：先统一进程监管、端口绑定和启动入口，再谈参数调优，收益通常更大。 兼容修复应优先“可回退”：遇到插件与宿主版本错配时，先保证系统整体可运行，再决定是升级、重构还是临时下线。 观测信号比主观感受更关键：重启次数、端口占用、超时日志比“感觉卡顿”更能指导下一步动作。 踩坑记录 # 同一服务被多路径拉起时，问题会被放大成随机故障：看似偶发，实际是运行拓扑不一致导致的不确定性。 把整段上下文直接喂给检索接口会引发超时：检索输入需要短、准、结构化，否则很容易触发 CLI 超时。 插件生态与主程序版本耦合度高：升级或安装插件前，最好先做导出接口和依赖路径核对。 明日计划 # 继续观察 PM2 单监管模式下的错误率与响应时延，确认是否彻底消除重启风暴。 为记忆检索补一层输入裁剪与超时重试策略，减少长查询导致的阻塞。 梳理一份“插件兼容检查清单”，用于后续安装或升级前的快速自检。 推荐阅读 # OpenClaw 记忆层降级策略：当 Working Memory 不可用时，如何保持稳定输出 每日技术实践简报 - 2026-03-23 每日技术实践简报 - 2026-03-22 ","date":"2026年3月24日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-24/","section":"博客文章","summary":"\u003cp\u003e今天的重点是把“偶发不响应”从体感问题落到可验证的工程问题上：先处理运行形态冲突，再压低高延迟链路，最后形成可复用的排障路径。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e插件加载失败从“无法启动”修到“可控禁用”\u003c/strong\u003e：针对插件依赖 \u003ccode\u003eopenclaw/plugin-sdk\u003c/code\u003e 解析失败和 SDK 版本不兼容的问题，先做了本地兼容修复，再按当前需求将相关插件置为禁用，避免持续影响主链路可用性。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e网关偶发不响应定位到运行冲突\u003c/strong\u003e：通过日志和进程状态确认了“多套守护/重复拉起”带来的端口争用与重启风暴风险，最终收敛为“仅 PM2 单监管器”模式，减少了短时不可用窗口。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e记忆检索超时有了明确根因\u003c/strong\u003e：\u003ccode\u003enmem\u003c/code\u003e 超时并非随机波动，而是查询输入过长、结构不佳时触发；同时保留 API fallback 作为兜底，保证主流程不被单点阻塞。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e稳定性优化要先做形态治理\u003c/strong\u003e：先统一进程监管、端口绑定和启动入口，再谈参数调优，收益通常更大。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e兼容修复应优先“可回退”\u003c/strong\u003e：遇到插件与宿主版本错配时，先保证系统整体可运行，再决定是升级、重构还是临时下线。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e观测信号比主观感受更关键\u003c/strong\u003e：重启次数、端口占用、超时日志比“感觉卡顿”更能指导下一步动作。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e同一服务被多路径拉起时，问题会被放大成随机故障\u003c/strong\u003e：看似偶发，实际是运行拓扑不一致导致的不确定性。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e把整段上下文直接喂给检索接口会引发超时\u003c/strong\u003e：检索输入需要短、准、结构化，否则很容易触发 CLI 超时。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e插件生态与主程序版本耦合度高\u003c/strong\u003e：升级或安装插件前，最好先做导出接口和依赖路径核对。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e继续观察 PM2 单监管模式下的错误率与响应时延，确认是否彻底消除重启风暴。\u003c/li\u003e\n\u003cli\u003e为记忆检索补一层输入裁剪与超时重试策略，减少长查询导致的阻塞。\u003c/li\u003e\n\u003cli\u003e梳理一份“插件兼容检查清单”，用于后续安装或升级前的快速自检。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/openclaw-memory-fallback-pattern/\"\u003eOpenClaw 记忆层降级策略：当 Working Memory 不可用时，如何保持稳定输出\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-23/\"\u003e每日技术实践简报 - 2026-03-23\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/daily-practice-2026-03-22/\"\u003e每日技术实践简报 - 2026-03-22\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-24","type":"posts"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/%E7%A8%B3%E5%AE%9A%E6%80%A7/","section":"Tags","summary":"","title":"稳定性","type":"tags"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/categories/ai-engineering/","section":"Categories","summary":"","title":"AI Engineering","type":"categories"},{"content":"很多团队在做 AI Agent 时，最容易忽略的一件事是：记忆系统也会故障。\n平时我们都把注意力放在提示词、模型效果、工具编排上，但一旦记忆层出现“半可用”（不是完全挂掉，而是读写行为不稳定），Agent 会很快进入一种尴尬状态：\n能跑，但上下文变浅； 能答，但连续性变差； 能写，但沉淀质量下降。 这篇文章不讲概念，直接讲一套能落地的策略：当 Working Memory 不可用时，如何通过分层降级保证业务连续性，并且为后续恢复留出“可回放”的证据链。\n1. 问题不是“挂了没”，而是“退化到什么程度还能用” # 真实线上里，记忆层异常通常分三种：\n读失败：拿不到当日焦点与上下文。 写失败：新结论无法写回工作记忆。 部分成功：偶发成功，偶发 JSON 解析错误/超时，最难处理。 第三种最危险。因为它让系统看起来“偶尔正常”，导致团队误判稳定性。\n工程上应把记忆能力拆成三个等级：\nL1（完整）：Working Memory + 长期记忆均可用。 L2（降级）：Working Memory 不可用，但长期记忆与本地日志可写。 L3（应急）：仅本地日志可写，后续补录。 关键不是追求永不降级，而是降级后仍可持续输出。\n2. 一套可执行的降级流程 # Step A：立即切换写入目标（先保住事实） # 当发现 Working Memory 异常后，先不要重试轰炸接口。 第一动作应该是：\n把本轮复盘/结论写入本地 memory/YYYY-MM-DD.md； 同时把“高价值、可长期复用”的结论写入 Nowledge Mem。 这样做的好处：\n防止关键结论丢失； 避免重试风暴扩大故障面； 后续恢复后可做结构化回填。 Step B：启用“高信噪比”过滤 # 降级时最怕的是把噪声也写进长期记忆，造成后续污染。 建议只保留以下类型：\n稳定偏好； 关键决策； 已验证结论； 可复用流程（SOP）。 不要写入：\n一次性中间态； 低价值报错原文； 重复摘要。 Step C：恢复后做补写，而不是覆盖 # Working Memory 恢复后，补写应遵循：\n读取故障时段本地日志； 提取“可行动结论”； 追加到 briefing/notes； 保留故障窗口标记，便于审计。 不要整段覆盖，因为会抹掉故障期间的演化痕迹。\n3. 这套方案为什么有效：它符合 SRE 的最小原则 # 从可靠性角度看，这其实是把记忆层做成了“弱一致但强可追溯”的体系。\n可用性优先：先把知识写下来，再谈结构完美。 单向可恢复：本地日志 -\u0026gt; 长期记忆 -\u0026gt; 工作记忆，天然可回放。 降级可观测：每次降级有明确状态和窗口，不会“悄悄坏”。 很多 Agent 项目失败，不是模型不够强，而是状态管理没有容错设计。\n4. 可直接复用的检查清单 # 在你的 Agent 项目里，至少把下面 6 条落地：\n记忆层有分级状态（完整/降级/应急）； 降级时默认切本地日志写入； 长期记忆有高信噪比过滤规则； 恢复后有补写流程，不覆盖历史； 降级事件有时间窗口和原因标记； 失败重试有上限，避免重试风暴。 如果这 6 条还没做，说明你的 Agent 还停留在“demo 可靠性”。\n5. 一个实践建议：把“记忆故障演练”纳入日常 # 别等线上故障才验证流程。\n建议每月做一次 15 分钟演练：\n人工模拟 Working Memory 不可用； 触发一次日常任务（例如每日复盘）； 检查是否自动走降级路径； 恢复服务后执行补写； 复盘是否有信息丢失或污染。 你会很快发现： 真正拉开团队差距的，不是“会不会用 Agent”，而是“系统退化时还能不能稳定交付”。\n结语 # Agent 的价值不在单次回答，而在持续协作。 持续协作的前提，不是永远不出错，而是：\n出错时有降级，恢复后可追溯。\n把这件事做好，你的 AI 系统才算真正进入工程化阶段。\n","date":"2026年3月24日","externalUrl":null,"permalink":"/posts/openclaw-memory-fallback-pattern/","section":"博客文章","summary":"\u003cp\u003e很多团队在做 AI Agent 时，最容易忽略的一件事是：\u003cstrong\u003e记忆系统也会故障\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e平时我们都把注意力放在提示词、模型效果、工具编排上，但一旦记忆层出现“半可用”（不是完全挂掉，而是读写行为不稳定），Agent 会很快进入一种尴尬状态：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e能跑，但上下文变浅；\u003c/li\u003e\n\u003cli\u003e能答，但连续性变差；\u003c/li\u003e\n\u003cli\u003e能写，但沉淀质量下降。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这篇文章不讲概念，直接讲一套能落地的策略：当 Working Memory 不可用时，如何通过分层降级保证业务连续性，并且为后续恢复留出“可回放”的证据链。\u003c/p\u003e\n\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e1. 问题不是“挂了没”，而是“退化到什么程度还能用” \n    \u003cdiv id=\"1-问题不是挂了没而是退化到什么程度还能用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-%e9%97%ae%e9%a2%98%e4%b8%8d%e6%98%af%e6%8c%82%e4%ba%86%e6%b2%a1%e8%80%8c%e6%98%af%e9%80%80%e5%8c%96%e5%88%b0%e4%bb%80%e4%b9%88%e7%a8%8b%e5%ba%a6%e8%bf%98%e8%83%bd%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e真实线上里，记忆层异常通常分三种：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e读失败\u003c/strong\u003e：拿不到当日焦点与上下文。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e写失败\u003c/strong\u003e：新结论无法写回工作记忆。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e部分成功\u003c/strong\u003e：偶发成功，偶发 JSON 解析错误/超时，最难处理。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e第三种最危险。因为它让系统看起来“偶尔正常”，导致团队误判稳定性。\u003c/p\u003e","title":"OpenClaw 记忆层降级策略：当 Working Memory 不可用时，如何保持稳定输出","type":"posts"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/categories/reliability/","section":"Categories","summary":"","title":"Reliability","type":"categories"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/sre/","section":"Tags","summary":"","title":"SRE","type":"tags"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/%E8%AE%B0%E5%BF%86%E7%B3%BB%E7%BB%9F/","section":"Tags","summary":"","title":"记忆系统","type":"tags"},{"content":"","date":"2026年3月24日","externalUrl":null,"permalink":"/tags/%E9%99%8D%E7%BA%A7%E7%AD%96%E7%95%A5/","section":"Tags","summary":"","title":"降级策略","type":"tags"},{"content":"今天的技术工作比较像一次集中收口：一边把几类工程软件的虚拟化路线梳理清楚，一边继续留意自动化链路的稳定性问题，同时也顺手整理了几条对 AI 工具落地很有用的判断。\n解决的问题 # 软件虚拟化路线终于从“都研究一下”变成“按场景拆分”：针对不同软件的图形负载和授权约束，今天把结论明确下来。轻量 2D 场景可以优先考虑 RDS 或普通 VDI，重图形建模场景则必须走带 GPU 的 VDI 或远程工作站，避免一开始就把所有软件硬塞进同一套方案里。 EDA 工具的可虚拟化判断更务实了：从技术上看并不是不能跑，而是不能只看“能启动”。授权、图形性能和稳定性验证要先做小范围 PoC，再决定是否上线，不然前期省下来的部署时间，后面会在故障和兼容性上全部还回去。 CI 失败不再当成偶发噪音处理：今天把多仓库持续出现的 lint、测试和数据同步失败看成系统性问题，而不是零散报警。这个视角调整很重要，因为只要把它当成“偶尔红一下”，流水线就会长期处在不可依赖的状态。 学到的新东西 # 虚拟化不是统一平台问题，本质上是工作负载分层问题：同样叫“设计软件”，2D 制图、3D 建模、EDA 仿真对资源的要求完全不是一个量级。与其追求一套方案覆盖全部，不如先按图形强度和授权风险分层，再匹配 RDS、VDI 或远程工作站。 轻量自动化工具已经进入“随手可做”的阶段：今天再次验证了一个趋势——很多运营辅助、校验类、解析类小工具，已经可以用 AI 代码助手在很短时间内做出可执行版本。真正稀缺的不是写代码本身，而是能否把需求边界说清楚。 AI 治理开始从“能不能用”转向“能不能被信任”：对于企业场景来说，后续评估 AI 系统时，身份说明、审计轨迹和责任边界会越来越重要。只有结果，没有可追溯过程的系统，后面会越来越难进入关键流程。 踩坑记录 # 想用共享 RDS 一次性兜住所有工程软件，这条路看起来省事，实际风险很高：一旦把高图形负载和轻量办公型场景混在一起，性能、体验和授权都会互相拖累。 CI 重复失败最怕“看见了，但没升级处理级别”：如果同类错误连续出现，还只是逐次修补单个 job，团队会慢慢失去对流水线结果的信任，后续问题定位成本会越来越高。 低成本维护项最容易被拖延：像版本巡检这种投入不大但能提前发现兼容性问题的任务，如果长期没人接，就会在真正出故障时放大代价。 明日计划 # 把软件虚拟化结论整理成更清晰的选型文档，便于后续验证和落地。 针对近期反复失败的 CI 链路做一次集中排查，优先看 lint 规则、测试稳定性和依赖一致性。 补上自动化工具和关键依赖的版本巡检，避免小问题积累成兼容性故障。 ","date":"2026年3月23日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-23/","section":"博客文章","summary":"\u003cp\u003e今天的技术工作比较像一次集中收口：一边把几类工程软件的虚拟化路线梳理清楚，一边继续留意自动化链路的稳定性问题，同时也顺手整理了几条对 AI 工具落地很有用的判断。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e软件虚拟化路线终于从“都研究一下”变成“按场景拆分”\u003c/strong\u003e：针对不同软件的图形负载和授权约束，今天把结论明确下来。轻量 2D 场景可以优先考虑 RDS 或普通 VDI，重图形建模场景则必须走带 GPU 的 VDI 或远程工作站，避免一开始就把所有软件硬塞进同一套方案里。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eEDA 工具的可虚拟化判断更务实了\u003c/strong\u003e：从技术上看并不是不能跑，而是不能只看“能启动”。授权、图形性能和稳定性验证要先做小范围 PoC，再决定是否上线，不然前期省下来的部署时间，后面会在故障和兼容性上全部还回去。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCI 失败不再当成偶发噪音处理\u003c/strong\u003e：今天把多仓库持续出现的 lint、测试和数据同步失败看成系统性问题，而不是零散报警。这个视角调整很重要，因为只要把它当成“偶尔红一下”，流水线就会长期处在不可依赖的状态。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e虚拟化不是统一平台问题，本质上是工作负载分层问题\u003c/strong\u003e：同样叫“设计软件”，2D 制图、3D 建模、EDA 仿真对资源的要求完全不是一个量级。与其追求一套方案覆盖全部，不如先按图形强度和授权风险分层，再匹配 RDS、VDI 或远程工作站。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e轻量自动化工具已经进入“随手可做”的阶段\u003c/strong\u003e：今天再次验证了一个趋势——很多运营辅助、校验类、解析类小工具，已经可以用 AI 代码助手在很短时间内做出可执行版本。真正稀缺的不是写代码本身，而是能否把需求边界说清楚。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAI 治理开始从“能不能用”转向“能不能被信任”\u003c/strong\u003e：对于企业场景来说，后续评估 AI 系统时，身份说明、审计轨迹和责任边界会越来越重要。只有结果，没有可追溯过程的系统，后面会越来越难进入关键流程。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e想用共享 RDS 一次性兜住所有工程软件，这条路看起来省事，实际风险很高\u003c/strong\u003e：一旦把高图形负载和轻量办公型场景混在一起，性能、体验和授权都会互相拖累。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCI 重复失败最怕“看见了，但没升级处理级别”\u003c/strong\u003e：如果同类错误连续出现，还只是逐次修补单个 job，团队会慢慢失去对流水线结果的信任，后续问题定位成本会越来越高。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e低成本维护项最容易被拖延\u003c/strong\u003e：像版本巡检这种投入不大但能提前发现兼容性问题的任务，如果长期没人接，就会在真正出故障时放大代价。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e把软件虚拟化结论整理成更清晰的选型文档，便于后续验证和落地。\u003c/li\u003e\n\u003cli\u003e针对近期反复失败的 CI 链路做一次集中排查，优先看 lint 规则、测试稳定性和依赖一致性。\u003c/li\u003e\n\u003cli\u003e补上自动化工具和关键依赖的版本巡检，避免小问题积累成兼容性故障。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-23","type":"posts"},{"content":"今天的工作重心比较集中，基本都围绕内部平台迭代、AI 能力验证和汇报方式收口这三件事展开。比起堆更多功能，今天更像是在做“把事情做稳、把表达讲清”的整理工作。\n解决的问题 # 内部平台迭代开始进入可用性收口阶段：这次重点不只是补功能，而是把代码仓库整理、稳定性修复和大文件上传验证一起补齐。这样做的好处很直接，后面继续加功能时，不会反复被基础问题拖住。 AI 评审服务正式落到业务链路里：今天把评审系统 V2 和 AI 评审服务继续往前推进，同时联动调试了 CI/CD 流水线调度器。相比单点试验，这一步更像是把能力真正接到可运行流程里。 学到的新东西 # 模型验证不能只看参数，要看落地效果：今天对几组模型和方案做了对比，结论不是谁规格更高谁就一定更合适，而是谁在当前任务里输出更稳定、结果更像可交付内容。 技术汇报按业务结果分组，比按模块罗列更清楚：把内容从“做了哪些技术项”改成“稳定性、能力建设、验证结果、问题处理”这类结构后，整体可读性明显更好，也更方便别人快速抓重点。 踩坑记录 # 很多技术内容本身没问题，问题出在表达方式太像模板：如果汇报里全是“优化、提升、赋能”这类词，读起来很顺，但信息密度其实不高。今天比较明确的一点是，汇报更适合直接写动作、结果和当前状态。 验证链路如果没有和真实流程一起调，结论很容易偏乐观：单独测试能跑通，不等于接进平台后也稳定。今天继续调度器和评审链路时，这个感受更明显。 明日计划 # 继续把内部平台剩余的稳定性问题收口，避免后续功能迭代反复返工。 继续完善 AI 评审链路和调度器联动，优先解决真实流程里的边界问题。 继续优化技术汇报结构，尽量让输出更自然、更具体，少一点空话。 ","date":"2026年3月22日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-22/","section":"博客文章","summary":"\u003cp\u003e今天的工作重心比较集中，基本都围绕内部平台迭代、AI 能力验证和汇报方式收口这三件事展开。比起堆更多功能，今天更像是在做“把事情做稳、把表达讲清”的整理工作。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e内部平台迭代开始进入可用性收口阶段\u003c/strong\u003e：这次重点不只是补功能，而是把代码仓库整理、稳定性修复和大文件上传验证一起补齐。这样做的好处很直接，后面继续加功能时，不会反复被基础问题拖住。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAI 评审服务正式落到业务链路里\u003c/strong\u003e：今天把评审系统 V2 和 AI 评审服务继续往前推进，同时联动调试了 CI/CD 流水线调度器。相比单点试验，这一步更像是把能力真正接到可运行流程里。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e模型验证不能只看参数，要看落地效果\u003c/strong\u003e：今天对几组模型和方案做了对比，结论不是谁规格更高谁就一定更合适，而是谁在当前任务里输出更稳定、结果更像可交付内容。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e技术汇报按业务结果分组，比按模块罗列更清楚\u003c/strong\u003e：把内容从“做了哪些技术项”改成“稳定性、能力建设、验证结果、问题处理”这类结构后，整体可读性明显更好，也更方便别人快速抓重点。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e很多技术内容本身没问题，问题出在表达方式太像模板\u003c/strong\u003e：如果汇报里全是“优化、提升、赋能”这类词，读起来很顺，但信息密度其实不高。今天比较明确的一点是，汇报更适合直接写动作、结果和当前状态。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e验证链路如果没有和真实流程一起调，结论很容易偏乐观\u003c/strong\u003e：单独测试能跑通，不等于接进平台后也稳定。今天继续调度器和评审链路时，这个感受更明显。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e继续把内部平台剩余的稳定性问题收口，避免后续功能迭代反复返工。\u003c/li\u003e\n\u003cli\u003e继续完善 AI 评审链路和调度器联动，优先解决真实流程里的边界问题。\u003c/li\u003e\n\u003cli\u003e继续优化技术汇报结构，尽量让输出更自然、更具体，少一点空话。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-22","type":"posts"},{"content":"","date":"2026年3月22日","externalUrl":null,"permalink":"/tags/%E5%B9%B3%E5%8F%B0%E5%BB%BA%E8%AE%BE/","section":"Tags","summary":"","title":"平台建设","type":"tags"},{"content":"今天把注意力放在两个方向：一是让信息流更适合长期使用，二是让自动化系统在扩展时更稳一点。看起来都像工程细节，但本质上都指向同一个问题：系统不是功能越多越好，而是越能长期稳定输出价值越好。\n解决的问题 # 长期记忆开始真正做“减法”：这次重点不是继续往记忆系统里加内容，而是清理低价值信息。像占位模板、一次性播报、运行态重复内容、完成通知这类信息，短期看似有用，长期只会污染检索结果。把这些内容清掉之后，后续搜索和回顾都会更干净。 AI 信息流完成结构化收口：原本的信息汇总更像“把看到的东西发出来”，现在开始转向“按决策价值组织内容”。换句话说，不再只追求多，而是更强调什么值得关注、什么值得试、什么值得警惕。 学到的新东西 # 记忆系统的核心不是存储能力，而是筛选能力：真正有价值的长期记忆，通常只来自稳定偏好、关键决策、已验证结论、长期计划、重要事实和可复用流程。其余内容如果没有被压缩成方法论，留着只会变成噪声。 系统稳定性的关键常常不是功能本身，而是边界是否一致：当一个系统开始分层、拆实例、接更多自动化链路后，最容易出问题的往往不是某个单点能力，而是入口、认证、职责划分这些边界没有统一。 新能力接入越克制，长期越稳：相比一上来就做全量接管，先手动使用、局部验证、逐步扩大范围，往往更适合持续运行的环境。 踩坑记录 # 搜索词过长会直接拖垮检索质量：把整段提示词、上下文说明甚至工作区注入内容一起扔进检索，看起来像“信息更全”，实际上更容易造成召回失真甚至超时。检索系统更喜欢短、准、明确的问题。 自动化摘要如果不做过滤，会悄悄把系统变臃肿：成功通知、定时任务运行记录、线程同步信息，这些内容在当下有参考价值，但不适合作为长期知识保留。系统越自动，这类噪声越容易积累。 明日计划 # 继续压缩记忆系统里的边界项和重复主张，让长期记忆更适合检索和复用。 继续完善 AI 信息流的自动化链路，重点补上去重与防重复触发。 继续把正在推进的几个方向往“可持续”上收：不是追求一次做完，而是让系统以后少返工、少回退。 ","date":"2026年3月21日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-21/","section":"博客文章","summary":"\u003cp\u003e今天把注意力放在两个方向：一是让信息流更适合长期使用，二是让自动化系统在扩展时更稳一点。看起来都像工程细节，但本质上都指向同一个问题：系统不是功能越多越好，而是越能长期稳定输出价值越好。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e长期记忆开始真正做“减法”\u003c/strong\u003e：这次重点不是继续往记忆系统里加内容，而是清理低价值信息。像占位模板、一次性播报、运行态重复内容、完成通知这类信息，短期看似有用，长期只会污染检索结果。把这些内容清掉之后，后续搜索和回顾都会更干净。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAI 信息流完成结构化收口\u003c/strong\u003e：原本的信息汇总更像“把看到的东西发出来”，现在开始转向“按决策价值组织内容”。换句话说，不再只追求多，而是更强调什么值得关注、什么值得试、什么值得警惕。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e记忆系统的核心不是存储能力，而是筛选能力\u003c/strong\u003e：真正有价值的长期记忆，通常只来自稳定偏好、关键决策、已验证结论、长期计划、重要事实和可复用流程。其余内容如果没有被压缩成方法论，留着只会变成噪声。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e系统稳定性的关键常常不是功能本身，而是边界是否一致\u003c/strong\u003e：当一个系统开始分层、拆实例、接更多自动化链路后，最容易出问题的往往不是某个单点能力，而是入口、认证、职责划分这些边界没有统一。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e新能力接入越克制，长期越稳\u003c/strong\u003e：相比一上来就做全量接管，先手动使用、局部验证、逐步扩大范围，往往更适合持续运行的环境。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e搜索词过长会直接拖垮检索质量\u003c/strong\u003e：把整段提示词、上下文说明甚至工作区注入内容一起扔进检索，看起来像“信息更全”，实际上更容易造成召回失真甚至超时。检索系统更喜欢短、准、明确的问题。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e自动化摘要如果不做过滤，会悄悄把系统变臃肿\u003c/strong\u003e：成功通知、定时任务运行记录、线程同步信息，这些内容在当下有参考价值，但不适合作为长期知识保留。系统越自动，这类噪声越容易积累。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e继续压缩记忆系统里的边界项和重复主张，让长期记忆更适合检索和复用。\u003c/li\u003e\n\u003cli\u003e继续完善 AI 信息流的自动化链路，重点补上去重与防重复触发。\u003c/li\u003e\n\u003cli\u003e继续把正在推进的几个方向往“可持续”上收：不是追求一次做完，而是让系统以后少返工、少回退。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-21","type":"posts"},{"content":"今天的工作更偏向“把系统说明白”。很多时候，工程效率不是来自多写几段代码，而是来自把命名、提醒和选型这些基础问题先梳理清楚。基础一旦清楚，后续很多动作都会顺下来。\n解决的问题 # 命名体系开始收口：围绕一套产品命名方式做了结构化梳理，把原本分散的叫法整理成更清晰的层级关系。命名一旦统一，后续无论是归档、映射还是自动识别，成本都会明显下降。 任务提醒链路继续稳定化：同一套提醒机制在不同时间点给出了相对一致的结果，说明这条链路已经不只是“能跑”，而是在向“可依赖”靠近。 学到的新东西 # 好的命名规范本质上是在降低系统沟通成本：如果一套命名只能被少数人理解，它就更像内部黑话；如果它能稳定映射到平台、代际和能力差异，它才真正具备长期价值。 自动化体系成熟的标志不是任务数量，而是分层是否清楚：当提醒、汇总、检测和执行这些能力开始形成稳定分工，系统就会从单点脚本堆叠，演进成更像“运营架构”的东西。 硬件选型最怕脱离使用场景：同样一颗 CPU，在不同任务下价值完全不同。比起盯着参数高低，更重要的是明确自己的主要负载到底是什么。 踩坑记录 # 兼容性问题不能只看理论方案：纸面上可行的硬件方案，落到具体设备时仍然可能受安装位、接口规格和结构差异影响。越接近实体世界，越要尊重“逐项确认”这件事。 明日计划 # 继续把命名规范往后续流程里落，验证它是否真的能减少返工。 继续观察提醒链路在真实使用中的稳定性，而不只停留在单次成功。 继续把技术选择建立在实际使用场景之上，而不是单看参数表。 ","date":"2026年3月20日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-20/","section":"博客文章","summary":"\u003cp\u003e今天的工作更偏向“把系统说明白”。很多时候，工程效率不是来自多写几段代码，而是来自把命名、提醒和选型这些基础问题先梳理清楚。基础一旦清楚，后续很多动作都会顺下来。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e命名体系开始收口\u003c/strong\u003e：围绕一套产品命名方式做了结构化梳理，把原本分散的叫法整理成更清晰的层级关系。命名一旦统一，后续无论是归档、映射还是自动识别，成本都会明显下降。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e任务提醒链路继续稳定化\u003c/strong\u003e：同一套提醒机制在不同时间点给出了相对一致的结果，说明这条链路已经不只是“能跑”，而是在向“可依赖”靠近。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e好的命名规范本质上是在降低系统沟通成本\u003c/strong\u003e：如果一套命名只能被少数人理解，它就更像内部黑话；如果它能稳定映射到平台、代际和能力差异，它才真正具备长期价值。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e自动化体系成熟的标志不是任务数量，而是分层是否清楚\u003c/strong\u003e：当提醒、汇总、检测和执行这些能力开始形成稳定分工，系统就会从单点脚本堆叠，演进成更像“运营架构”的东西。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e硬件选型最怕脱离使用场景\u003c/strong\u003e：同样一颗 CPU，在不同任务下价值完全不同。比起盯着参数高低，更重要的是明确自己的主要负载到底是什么。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e兼容性问题不能只看理论方案\u003c/strong\u003e：纸面上可行的硬件方案，落到具体设备时仍然可能受安装位、接口规格和结构差异影响。越接近实体世界，越要尊重“逐项确认”这件事。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e继续把命名规范往后续流程里落，验证它是否真的能减少返工。\u003c/li\u003e\n\u003cli\u003e继续观察提醒链路在真实使用中的稳定性，而不只停留在单次成功。\u003c/li\u003e\n\u003cli\u003e继续把技术选择建立在实际使用场景之上，而不是单看参数表。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-20","type":"posts"},{"content":"","date":"2026年3月20日","externalUrl":null,"permalink":"/tags/%E5%91%BD%E5%90%8D%E8%A7%84%E8%8C%83/","section":"Tags","summary":"","title":"命名规范","type":"tags"},{"content":"","date":"2026年3月19日","externalUrl":null,"permalink":"/tags/%E7%9B%91%E6%8E%A7/","section":"Tags","summary":"","title":"监控","type":"tags"},{"content":"今天更多是在看“系统什么时候该提醒，什么时候该克制”。一个自动化系统如果只会不停报消息，很快就会让人失去耐心；但如果它什么都不说，又失去了存在意义。真正难的是边界感。\n解决的问题 # 告警范围开始更接近真实风险：把关注点从单纯的应用状态，扩展到更广义的自动化运行状态。这样做的意义不在于让告警更多，而在于让真正值得人工介入的问题更早被看见。 异常状态识别更完整了一步：当某些凭证或访问条件失效时，系统不一定能继续判断全部上下文。这提醒我，自动化系统不只是要处理“正常输入”，还要明确面对“信息不完整”的表现方式。 学到的新东西 # 提醒系统的价值来自筛选，而不是频率：如果什么都提醒，提醒就会失去价值。比起频繁轮询，更重要的是找准哪些任务值得优先看、哪些状态只需要留痕不需要打扰。 静默策略是自动化系统成熟的重要标志：很多系统会做“能提醒”，但很少认真做“什么时候不该提醒”。真正长期可用的自动化，必须包含安静时段、节奏边界和打扰成本的设计。 踩坑记录 # 只有告警，没有自愈，系统仍然很脆：能发现问题固然重要，但如果后续没有重试、回退或补救机制，很多告警最终还是只能堆成人工负担。 依赖单点前提的监控很容易出现盲区：一旦某个关键前提失效，系统可能不只是“报错”，而是直接失去判断能力。这类问题往往比普通失败更值得优先补上。 明日计划 # 继续区分“需要提醒”和“只需记录”的事件类型。 评估哪些告警链路值得补自动重试或基础自愈。 继续把监控目标从单点状态扩展到真实可用性。 ","date":"2026年3月19日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-19/","section":"博客文章","summary":"\u003cp\u003e今天更多是在看“系统什么时候该提醒，什么时候该克制”。一个自动化系统如果只会不停报消息，很快就会让人失去耐心；但如果它什么都不说，又失去了存在意义。真正难的是边界感。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e告警范围开始更接近真实风险\u003c/strong\u003e：把关注点从单纯的应用状态，扩展到更广义的自动化运行状态。这样做的意义不在于让告警更多，而在于让真正值得人工介入的问题更早被看见。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e异常状态识别更完整了一步\u003c/strong\u003e：当某些凭证或访问条件失效时，系统不一定能继续判断全部上下文。这提醒我，自动化系统不只是要处理“正常输入”，还要明确面对“信息不完整”的表现方式。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e提醒系统的价值来自筛选，而不是频率\u003c/strong\u003e：如果什么都提醒，提醒就会失去价值。比起频繁轮询，更重要的是找准哪些任务值得优先看、哪些状态只需要留痕不需要打扰。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e静默策略是自动化系统成熟的重要标志\u003c/strong\u003e：很多系统会做“能提醒”，但很少认真做“什么时候不该提醒”。真正长期可用的自动化，必须包含安静时段、节奏边界和打扰成本的设计。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e只有告警，没有自愈，系统仍然很脆\u003c/strong\u003e：能发现问题固然重要，但如果后续没有重试、回退或补救机制，很多告警最终还是只能堆成人工负担。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e依赖单点前提的监控很容易出现盲区\u003c/strong\u003e：一旦某个关键前提失效，系统可能不只是“报错”，而是直接失去判断能力。这类问题往往比普通失败更值得优先补上。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e继续区分“需要提醒”和“只需记录”的事件类型。\u003c/li\u003e\n\u003cli\u003e评估哪些告警链路值得补自动重试或基础自愈。\u003c/li\u003e\n\u003cli\u003e继续把监控目标从单点状态扩展到真实可用性。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-19","type":"posts"},{"content":"","date":"2026年3月18日","externalUrl":null,"permalink":"/tags/%E5%B7%A5%E7%A8%8B%E8%A7%82%E5%AF%9F/","section":"Tags","summary":"","title":"工程观察","type":"tags"},{"content":"","date":"2026年3月18日","externalUrl":null,"permalink":"/tags/%E5%B7%A5%E5%85%B7%E9%93%BE/","section":"Tags","summary":"","title":"工具链","type":"tags"},{"content":"今天的信息输入比较密集，但真正值得留下来的，不是“看到了多少新东西”，而是几个越来越清楚的趋势：模型在分化，助手在贴近个人场景，工具链则在向更专门化的方向走。\n解决的问题 # 信息监控继续维持可用状态：今天的重点不是做大的改动，而是确认几条关键监控链路依旧可靠。对自动化系统来说，稳定地获得输入，往往比增加新能力更重要。 工具版本保持收敛：没有为了追新而升级，而是先确认当前版本是否足够稳定。很多时候，不升级也是一种工程判断。 学到的新东西 # 模型正在明显分化成更细的使用形态：一部分模型继续追求通用能力，另一部分则越来越强调特定任务、特定负载和特定场景的效率。这意味着以后做技术选型，不能再只看“谁最强”，而要看“谁最合适”。 个人助理式 AI 正在更接近真实使用场景：当工具开始更深地理解用户的历史行为、个人上下文和长期偏好时，AI 的角色也会从“回答问题的工具”逐渐走向“持续协作的助手”。 稳定版本的价值经常被低估：如果一个工具当前已经足够稳定、足够满足需求，那么保持版本不乱动，本身就是降低风险的一种方式。 踩坑记录 # 信息监控最容易滑向“信息堆积”：输入越多，越容易把重要和不重要混在一起。系统要真正有价值，核心不是看得多，而是能不能把重点筛出来。 明日计划 # 继续观察模型和工具链的演进方向，重点关注那些真正改变工作方式的变化。 继续收敛监控内容，把注意力集中在少量高价值信号上。 在稳定优先的前提下，再决定哪些工具值得进一步跟进。 ","date":"2026年3月18日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-18/","section":"博客文章","summary":"\u003cp\u003e今天的信息输入比较密集，但真正值得留下来的，不是“看到了多少新东西”，而是几个越来越清楚的趋势：模型在分化，助手在贴近个人场景，工具链则在向更专门化的方向走。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e信息监控继续维持可用状态\u003c/strong\u003e：今天的重点不是做大的改动，而是确认几条关键监控链路依旧可靠。对自动化系统来说，稳定地获得输入，往往比增加新能力更重要。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e工具版本保持收敛\u003c/strong\u003e：没有为了追新而升级，而是先确认当前版本是否足够稳定。很多时候，不升级也是一种工程判断。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e模型正在明显分化成更细的使用形态\u003c/strong\u003e：一部分模型继续追求通用能力，另一部分则越来越强调特定任务、特定负载和特定场景的效率。这意味着以后做技术选型，不能再只看“谁最强”，而要看“谁最合适”。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e个人助理式 AI 正在更接近真实使用场景\u003c/strong\u003e：当工具开始更深地理解用户的历史行为、个人上下文和长期偏好时，AI 的角色也会从“回答问题的工具”逐渐走向“持续协作的助手”。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e稳定版本的价值经常被低估\u003c/strong\u003e：如果一个工具当前已经足够稳定、足够满足需求，那么保持版本不乱动，本身就是降低风险的一种方式。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e信息监控最容易滑向“信息堆积”\u003c/strong\u003e：输入越多，越容易把重要和不重要混在一起。系统要真正有价值，核心不是看得多，而是能不能把重点筛出来。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e继续观察模型和工具链的演进方向，重点关注那些真正改变工作方式的变化。\u003c/li\u003e\n\u003cli\u003e继续收敛监控内容，把注意力集中在少量高价值信号上。\u003c/li\u003e\n\u003cli\u003e在稳定优先的前提下，再决定哪些工具值得进一步跟进。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-18","type":"posts"},{"content":"","date":"2026年3月18日","externalUrl":null,"permalink":"/tags/%E6%A8%A1%E5%9E%8B/","section":"Tags","summary":"","title":"模型","type":"tags"},{"content":"","date":"2026年3月17日","externalUrl":null,"permalink":"/tags/%E5%AE%89%E5%85%A8/","section":"Tags","summary":"","title":"安全","type":"tags"},{"content":"今天更像一次“把系统从能用推向稳用”的过程。很多问题表面看是单点故障，但往下挖，会发现真正需要治理的是配置习惯、安全边界和系统默契。\n解决的问题 # 索引与可发现性问题得到修复：一项影响内容被发现的能力恢复正常。看起来只是一个结果恢复，但背后真正重要的是验证链路是否重新闭环。 一项平台能力完成阶段性增强：这次推进的重点不在“功能更多”，而在“能力更完整”，包括模型接入、动态配置以及基本的安全防护都往前走了一步。 学到的新东西 # 安全治理最好在系统早期就做进默认动作里：输入校验、认证、输出清洗这类能力，如果一开始没建立边界，后面补起来通常成本更高。 静默策略不是附属规则，而是系统体验的一部分：深夜只做状态检查、不重复提醒，看似只是交互细节，实际上决定了系统是否长期可用。 配置清洁度会直接影响系统稳定性：占位符、硬编码、路径约定这些小问题，平时看不起眼，但往往最容易在后续运维里变成真实故障。 踩坑记录 # 远程依赖的可用性经常卡在认证而不是逻辑：很多“拉不下来”“连不上”的问题，最终都不是代码逻辑本身，而是访问边界没有配置对。 明日计划 # 继续把配置治理、安全边界和运行策略做得更一致。 继续验证恢复后的链路是否稳定，而不是只看一次成功。 继续审查外部依赖的访问前提，减少后续中断。 ","date":"2026年3月17日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-17/","section":"博客文章","summary":"\u003cp\u003e今天更像一次“把系统从能用推向稳用”的过程。很多问题表面看是单点故障，但往下挖，会发现真正需要治理的是配置习惯、安全边界和系统默契。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e索引与可发现性问题得到修复\u003c/strong\u003e：一项影响内容被发现的能力恢复正常。看起来只是一个结果恢复，但背后真正重要的是验证链路是否重新闭环。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e一项平台能力完成阶段性增强\u003c/strong\u003e：这次推进的重点不在“功能更多”，而在“能力更完整”，包括模型接入、动态配置以及基本的安全防护都往前走了一步。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e安全治理最好在系统早期就做进默认动作里\u003c/strong\u003e：输入校验、认证、输出清洗这类能力，如果一开始没建立边界，后面补起来通常成本更高。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e静默策略不是附属规则，而是系统体验的一部分\u003c/strong\u003e：深夜只做状态检查、不重复提醒，看似只是交互细节，实际上决定了系统是否长期可用。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e配置清洁度会直接影响系统稳定性\u003c/strong\u003e：占位符、硬编码、路径约定这些小问题，平时看不起眼，但往往最容易在后续运维里变成真实故障。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e远程依赖的可用性经常卡在认证而不是逻辑\u003c/strong\u003e：很多“拉不下来”“连不上”的问题，最终都不是代码逻辑本身，而是访问边界没有配置对。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e继续把配置治理、安全边界和运行策略做得更一致。\u003c/li\u003e\n\u003cli\u003e继续验证恢复后的链路是否稳定，而不是只看一次成功。\u003c/li\u003e\n\u003cli\u003e继续审查外部依赖的访问前提，减少后续中断。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-17","type":"posts"},{"content":"","date":"2026年3月17日","externalUrl":null,"permalink":"/tags/%E9%85%8D%E7%BD%AE%E6%B2%BB%E7%90%86/","section":"Tags","summary":"","title":"配置治理","type":"tags"},{"content":" 2026 年，AI Agent 已从实验室走向生产环境。但\u0026quot;选哪个框架\u0026quot;不再是核心问题，真正的挑战是如何把 Model、Context、Memory、Execution、Observability 这些层组装成一个可靠的工程系统。本文基于一线实践，梳理 AI Agent 工程化的完整图景。 7 层架构：AI Agent 的工程骨架 # AI Agent 的技术选型不应从\u0026quot;选框架\u0026quot;开始，而应从理解系统分层开始。\nflowchart TB subgraph L7[\"Application Layer\"] A7[\"Coding Agent / Research AgentWorkflow Agent / Personal Assistant\"] end subgraph L6[\"Observability Layer\"] A6[\"Traces / Logs / EvalsCost / Latency / Quality\"] end subgraph L5[\"Memory Layer\"] A5[\"Episodic / Semantic / Graph MemoryLong-term User Memory\"] end subgraph L4[\"Orchestration Layer\"] A4[\"Loops / Planners / SubagentsAsync Jobs / Cron Triggers\"] end subgraph L3[\"Execution Layer\"] A3[\"Tool Calling / Browser / ShellCode Runtime / Sandbox\"] end subgraph L2[\"Context Layer\"] A2[\"System Prompt / Session StateWorking Memory / Retrieval / Compaction\"] end subgraph L1[\"Model Layer\"] A1[\"Claude / GPT / Gemini / GLMLocal Models via Ollama\"] end L7 --\u003e L6 --\u003e L5 --\u003e L4 --\u003e L3 --\u003e L2 --\u003e L1 style L1 fill:#3b82f6,color:#fff style L2 fill:#6366f1,color:#fff style L3 fill:#8b5cf6,color:#fff style L4 fill:#a855f7,color:#fff style L5 fill:#d946ef,color:#fff style L6 fill:#ec4899,color:#fff style L7 fill:#f43f5e,color:#fff 每一层解决的问题不同，不能用一个框架覆盖全部：\n层 核心问题 典型工具/框架 Model 选哪个模型、成本控制、fallback OpenAI / Anthropic / 本地 Ollama Context 上下文管理、压缩、检索 RAG / Working Memory / Compaction Execution 工具调用、沙箱隔离 MCP / Tool Calling / Browser Use Orchestration 工作流编排、多代理协作 LangGraph / CrewAI / 自研 Memory 长期记忆、知识图谱 Mem0 / Nowledge Mem / Graph Memory Observability 追踪、评估、成本监控 Langfuse / LangSmith / Arize Application 具体业务场景 Coding Agent / 客服 / 研究 框架选型：没有银弹 # 核心观点：不要先问\u0026quot;最强框架是谁\u0026quot;，先问你的问题属于哪类。框架只解决 Orchestration 层的问题，其他 6 层需要你自己搭建。 主流框架对比 # 框架 定位 优势 劣势 适用场景 LangGraph 复杂状态工作流 控制力强、状态清晰、适合生产 抽象较重、学习成本高 复杂状态工作流 CrewAI 角色分工 SOP 上手快、概念直观 复杂状态和底层控制弱 SOP 式多角色流程 AutoGen 多代理协作 研究味浓、协作模式丰富 生产稳定性一般 实验性协作研究 MS Agent Framework 企业集成 企业能力强、集成完整 平台绑定较强 微软栈企业集成 Letta (MemGPT) Memory-first 长期记忆与状态持久化强 心智负担较高 长期对话代理 DSPy Prompt 编译优化 优化范式独特 不适合通用 runtime Prompt 工程研究 选型决策树 # flowchart TD START[\"你的需求是什么?\"] --\u003e Q1{\"需要复杂状态管理?\"} Q1 --\u003e|是| LG[\"LangGraph\"] Q1 --\u003e|否| Q2{\"需要多角色SOP 协作?\"} Q2 --\u003e|是| CA[\"CrewAI\"] Q2 --\u003e|否| Q3{\"微软技术栈?\"} Q3 --\u003e|是| MS[\"MS Agent Framework\"] Q3 --\u003e|否| Q4{\"核心是长期记忆?\"} Q4 --\u003e|是| LT[\"Letta / Mem0\"] Q4 --\u003e|否| Q5{\"需求独特控制力要求高?\"} Q5 --\u003e|是| DIY[\"自研 SDK\"] Q5 --\u003e|否| LG2[\"LangGraph(默认推荐)\"] style LG fill:#10b981,color:#fff style DIY fill:#f59e0b,color:#fff style LG2 fill:#10b981,color:#fff 多智能体协作：代码检查系统实战 # 以\u0026quot;多智能体代码检查系统\u0026quot;为例，展示 Agent 协作的工程实现。\n架构设计 # flowchart LR subgraph Input[\"输入\"] MR[\"GitLab MR Diff\"] end subgraph Planning[\"规划层\"] PA[\"规划智能体（协调分发）\"] end subgraph Specialists[\"专项检查\"] SA[\"安全检查Agent\"] QA[\"功能质量Agent\"] PF[\"性能优化Agent\"] MT[\"可维护性Agent\"] end subgraph Output[\"输出层\"] SUM[\"汇总智能体（整合报告）\"] end MR --\u003e PA PA --\u003e SA \u0026 QA \u0026 PF \u0026 MT SA \u0026 QA \u0026 PF \u0026 MT --\u003e SUM style PA fill:#8b5cf6,color:#fff style SUM fill:#10b981,color:#fff 各 Agent 职责 # Agent 检测范围 输出标准 安全检查 代码注入、认证授权、敏感数据暴露、输入验证 confidence \u0026gt; 0.7 且 severity 中/高 功能质量 逻辑错误、边界条件、错误处理、空指针引用 标准 Markdown 格式 性能优化 算法复杂度、资源泄漏、重复计算、SQL 优化 附带修改建议 可维护性 代码重复、命名规范、函数复杂度、文档完整性 避免低价值风格建议 评审流程 # 第一步：推理分析（理解变更意图和上下文） ↓ 第二步：决策与评分（接受/拒绝 + 0-100 分） ↓ 第三步：精准反馈（仅高置信度 + 中高严重度的问题） 实践收获：在真实项目中，这套多智能体代码检查系统在 37 个 MR 中发现了 5 个真实问题（命名不一致、安全隐患、CSS 选择器范围等），人工评审几乎不可能同时覆盖这些维度。 记忆系统：Agent 的\u0026quot;大脑\u0026quot; # 记忆是 Agent 从\u0026quot;工具\u0026quot;进化为\u0026quot;助手\u0026quot;的关键。\n三层记忆架构 # flowchart TB subgraph WM[\"Working Memory（工作记忆）\"] direction LR WM1[\"当前会话上下文\"] WM2[\"临时状态\"] WM3[\"任务进度\"] end subgraph EM[\"Episodic Memory（情景记忆）\"] direction LR EM1[\"历史对话\"] EM2[\"操作日志\"] EM3[\"踩坑记录\"] end subgraph SM[\"Semantic Memory（语义记忆）\"] direction LR SM1[\"知识图谱\"] SM2[\"用户偏好\"] SM3[\"决策模式\"] end WM --\u003e|\"重要信息沉淀\"| EM EM --\u003e|\"归纳抽象\"| SM SM --\u003e|\"检索召回\"| WM style WM fill:#3b82f6,color:#fff style EM fill:#8b5cf6,color:#fff style SM fill:#10b981,color:#fff 记忆技术选型 # 方案 定位 特点 Mem0 生产可用 集成快，适合个性化长期记忆 Nowledge Mem 知识图谱 支持 Working Memory、自动整理、跨工具同步 Zep 对话记忆 偏服务化，适合对话场景 Graph Memory 关系推理 正在成为热点，适合复杂关联 自我改进闭环 # Agent 执行任务 ↓ (失败/被纠正/发现更好方法) 自动记录到 Episodic Memory ↓ (定期归纳) 抽象为 Semantic Memory（最佳实践/避坑指南） ↓ (下次执行) 自动加载相关记忆到 Working Memory ↓ 避免重复犯错 企业级 AI 工具链 # 2026 年的 AI 工具链已形成完整生态：\n分层工具矩阵 # 层次 工具 用途 Chat DeepSeek（代码审查优先）、ChatGPT（通用） 日常对话和分析 Agent 平台 Manus.im、Coze（零代码/低代码） 构建业务 Agent IDE Cursor、GitHub Copilot、Claude Code 编码辅助 知识库 Dify、NotebookLM、Ollama + Open WebUI 知识检索和管理 MCP MCP Community、魔搭社区 工具协议和集成 自动化 N8N + 本地 AI 模型 工作流自动化 浏览器 Browser Use 网页操作自动化 可观测性 Langfuse（开源）、LangSmith（LangChain 生态） 追踪和评估 MCP：连接一切的协议 # MCP (Model Context Protocol) 正在成为 AI Agent 的\u0026quot;USB 接口\u0026quot;：\nAgent ←→ MCP Protocol ←→ Tool Server ├── GitLab MCP（代码托管） ├── Nowledge Mem MCP（知识图谱） ├── Browser Use MCP（浏览器） ├── Alist MCP（文件管理） └── 自定义 MCP Server MCP 的价值在于标准化了 Agent 与工具的交互协议，避免每个 Agent 框架各自实现一套工具调用。\n自研 SDK 的决策逻辑 # 在评估完所有框架后，为什么有些团队选择自研？\n自研的原因 # 现有框架要么过于极简，要么过于抽象复杂 \u0026ndash; LangGraph 功能强大但学习成本高，CrewAI 简单但不够灵活 \u0026ldquo;Harness Engineering\u0026quot;概念的局限 \u0026ndash; 只做 prompt + context + experience + skills + sandbox 的编排是不够的 业务场景的特殊性 \u0026ndash; 企业内部的审批流、合规要求、数据隔离需求无法用通用框架满足 自研的最小可行集 # 自研 SDK 最小集 = Model Router（多模型路由 + fallback） + Context Manager（上下文压缩 + 检索） + Tool Registry（工具注册 + 沙箱） + Memory Store（短期 + 长期记忆） + Eval Loop（自动评估 + 成本监控） 决策建议：如果你的需求能用 LangGraph 覆盖 80%，不要自研。自研的成本远超预期，特别是在可靠性、错误处理、并发控制等方面。只有当框架无法满足核心需求且改造成本高于新建时，才考虑自研。 实战案例：OpenClaw AI Agent 平台 # 以上架构和选型讨论并非纸上谈兵。OpenClaw 是一个在生产环境运行的 AI Agent 平台，它的设计完整体现了 7 层架构的工程实践。\n整体架构 # flowchart TB subgraph Models[\"Model Layer\"] Claude[\"Claude Sonnet 4.6\"] GPT[\"GPT-5.3 Codex\"] GLM[\"GLM-5\"] DS[\"DeepSeek\"] end subgraph Router[\"Model Router\"] MR[\"智能路由（按任务类型分发）\"] end subgraph Skills[\"Skills Layer (90+)\"] BG[\"blog-post-generator\"] CA[\"coding-agent\"] SR[\"security-review\"] DT[\"...\"] end subgraph MCP[\"MCP Integration\"] GL[\"GitLab MCP\"] NM[\"Nowledge Mem MCP\"] TB[\"Teambition MCP\"] end subgraph Cron[\"Cron Scheduler\"] HB[\"HEARTBEAT（系统健康检查）\"] DB[\"Daily Blog（每日博客生成）\"] BK[\"Daily Backup（配置备份）\"] end Models --\u003e MR MR --\u003e Skills Skills --\u003e MCP MR --\u003e Cron style MR fill:#8b5cf6,color:#fff style Skills fill:#10b981,color:#fff style MCP fill:#3b82f6,color:#fff style Cron fill:#f59e0b,color:#fff Skills 系统：90+ 可组合能力 # OpenClaw 的 Skills 不是固定功能，而是可热插拔的能力模块：\nSkill 类型 示例 触发方式 编码辅助 coding-agent, tdd-workflow, security-review 用户请求或 MCP 事件 内容生成 blog-post-generator, daily-blog-generator Cron 定时或手动触发 运维自动化 gitlab-ci-patterns, k8s-manifest-generator 用户请求 知识管理 nowledge-mem, self-improving-agent 自动或手动 Skills 配置管理通过 Nacos 3.2 实现企业级注册与发现，支持版本控制和灰度发布。\nCron 调度：Agent 的\u0026quot;生物钟\u0026rdquo; # OpenClaw 的 Cron 调度器让 Agent 具备自主行为能力：\n# HEARTBEAT — 系统健康检查 # 23:00-08:00 静默模式：仅状态检查，不重复提醒 # 08:00-23:00 主动模式：发现异常立即通知 # Daily Blog — 每日博客自动生成 # 23:00 触发，自动选题、生成、脱敏、提交 # Daily Backup — 配置自动备份 # 每天 21:39 自动执行，保留 7 天 踩坑记录：Cron 调度器的 agentTurn 类型任务必须在 payload 中显式指定 model 字段，否则会被重置为 null 导致任务失败。这个问题首次发现于 2026-03-11，排查了多轮才定位到根因。 记忆系统实战 # OpenClaw 的记忆体系是本文\u0026quot;三层记忆架构\u0026quot;的真实实现：\nWorking Memory：每次会话自动注入，包含当前焦点领域、未解决问题、近期上下文 Nowledge Mem：外部知识图谱，通过 MCP 协议集成，支持跨工具同步（Claude Code / Cursor / OpenClaw 共享同一份知识） Self-Improving Agent：自动捕获错误和纠正，持续累积到 ERRORS.md / LEARNINGS.md 用户纠正 Agent → 自动记录到 LEARNINGS.md ↓ 定期归纳 → 写入 Nowledge Mem 语义记忆 ↓ 下次会话 → Working Memory 自动加载相关记忆 ↓ 不再犯同类错误 博客自动化发布 # OpenClaw 已演化出完整的博客自动发布体系：\nblog-post-generator — 根据指定主题生成技术文章 daily-blog-generator — 每日 23:00 自动选题生成 脱敏机制 — 自动过滤内部 IP、密钥等敏感信息 去重检查 — 避免重复生成已有主题的文章 人工边界 — 不确定的操作先询问用户再执行 这套体系的核心设计原则：Agent 可以自动生成内容，但发布决策权始终在人手中。\n实践中的关键教训 # 1. 成本比你想象的重要 # 多智能体协作意味着 N 倍的 API 调用。一个 4-Agent 的代码审查系统，单次审查的 token 消耗是单 Agent 的 3-5 倍。技术选型时，DeepSeek 的成本优势（0.001 元/千 token vs GPT-4 的 0.03 元/千 token）在规模化后非常显著。\n2. 可观测性不是可选项 # Agent 出错时，如果没有 trace 和 eval，你根本不知道是 prompt 的问题、context 的问题还是 tool 的问题。Langfuse/LangSmith 是生产环境的必备。\n3. 记忆系统需要\u0026quot;遗忘\u0026quot; # 无限累积的记忆会污染上下文。Working Memory 需要每日清理，Episodic Memory 需要过期机制，Semantic Memory 需要定期校准。\n4. 安全边界必须明确 # Agent 能执行 shell 命令、访问文件系统、调用 API \u0026ndash; 如果不做沙箱隔离，一个 hallucination 就可能 rm -rf /。Execution Layer 的安全设计比功能设计更重要。\n写在最后 # 2026 年 AI Agent 的技术成熟度已经从\u0026quot;能不能做\u0026quot;进入了\u0026quot;怎么做好\u0026quot;的阶段。关键不在于选哪个框架或哪个模型，而在于：\n系统性工程思维 \u0026ndash; 7 层架构每层都要有方案 成本意识 \u0026ndash; 多 Agent 协作的成本控制 记忆系统 \u0026ndash; 从工具到助手的关键跃迁 可观测性 \u0026ndash; 生产环境的必备基础设施 安全边界 \u0026ndash; 给 Agent 的能力加上围栏 技术选型的最终答案不是\u0026quot;用 X 框架\u0026quot;，而是理解你的问题属于哪类，然后在每一层选择最合适的工具，把它们组装成一个可靠的系统。\n查看 Claude Code + pr-agent 实践 ","date":"2026年3月16日","externalUrl":null,"permalink":"/posts/ai-agent-engineering-landscape-2026/","section":"博客文章","summary":"从 7 层架构到框架选型、从多智能体协作到记忆系统、从 Coding Agent 到企业工具链的 AI Agent 2026 工程化实践全景。","title":"2026 AI Agent 工程化全景：从框架选型到生产落地","type":"posts"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/tags/agent/","section":"Tags","summary":"","title":"Agent","type":"tags"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/categories/ai/","section":"Categories","summary":"","title":"AI","type":"categories"},{"content":" 一台 PVE 虚拟机突然无法启动，QEMU 报 \u0026ldquo;Image is corrupt\u0026rdquo;。快照回滚失败，专用修复工具未授权。最终发现是 qcow2 镜像头部的 corrupt 保护标记残留，一条命令即可修复。本文完整记录排查过程、根因分析和标准 SOP。 结论前置 # 项目 内容 故障类型 qcow2 镜像头部残留 corrupt: true 标记 恢复用时 约 25 分钟 修复命令 qemu-img check -r all \u0026lt;disk.qcow2\u0026gt; 数据损失 无 三个关键结论：(1) corrupt: true 不等于数据已损坏，check 通过说明结构完好；(2) 修复只需 check -r all，无需数据恢复或镜像重建；(3) 快照不能替代完整备份，底层镜像异常时快照回滚同样失败。 故障现象 # 执行 qm start \u0026lt;vmid\u0026gt; 后，PVE 控制台输出：\nqcow2: Image is corrupt; cannot be opened read/write start failed: QEMU exited with code 1 受影响磁盘为关键数据盘（虚拟 1 TiB，实占 329 GiB），不可废弃，必须原地修复。\n排查时间线 # flowchart TD START[\"VM 启动失败\"] START --\u003e ERR[\"报错：qcow2 Image is corruptQEMU exited with code 1\"] ERR --\u003e CHECK1[\"qemu-img check一致性检查\"] CHECK1 --\u003e R1{\"检查结果?\"} R1 --\u003e|\"No errors found\"| CONFLICT[\"矛盾：check 通过但 QEMU 仍报错\"] CONFLICT --\u003e CONFIG[\"qm config确认目标磁盘为关键盘\"] CONFIG --\u003e ROLLBACK[\"尝试快照回滚\"] ROLLBACK --\u003e R2{\"回滚结果?\"} R2 --\u003e|失败| SNAP_FAIL[\"镜像无法打开快照回滚被阻断\"] SNAP_FAIL --\u003e INFO[\"qemu-img info确认 corrupt: true\"] INFO --\u003e FIX1[\"qemu-img check -r leaks低风险修复\"] FIX1 --\u003e FIX2[\"qemu-img check -r all清除 corrupt 标记\"] FIX2 --\u003e VERIFY[\"qemu-img info确认 corrupt: false\"] VERIFY --\u003e END[\"VM 启动成功\"] style START fill:#e55,color:#fff style CONFLICT fill:#f90,color:#fff style SNAP_FAIL fill:#f90,color:#fff style END fill:#4a4,color:#fff 一致性检查通过，但启动仍失败 # qemu-img check /path/to/vm-disk.qcow2 # 输出：No errors were found on the image. 关键矛盾：qemu-img check 通过说明镜像内部逻辑结构完整（refcount、cluster、L1/L2 表均无异常），但 QEMU 启动仍报错。问题来自镜像头部的状态标记，而非数据本身。 快照回滚全部失败 # 三个自动快照均无法恢复：\nqm rollback \u0026lt;vmid\u0026gt; \u0026lt;snapshot-name\u0026gt; # 结果：失败，错误仍指向 qcow2 镜像无法打开 快照回滚流程需要先以读写方式打开底层镜像。corrupt 标记导致 QEMU 在第一步就拒绝打开，回滚被阻断。\n方案评估 # 方案 风险 结论 qemu-img check -r 低 优先采用 qcow2-dump 专项工具 低 环境未授权，不可用 快照回滚 低 已确认失效 qemu-img convert 重建 中 耗时长，空间需求大，留作后手 qemu-nbd + ddrescue 高 适合严重损坏，本次非首选 确认根因并修复 # # Step 1：查看镜像状态 qemu-img info /path/to/vm-disk.qcow2 # 输出：corrupt: true \u0026lt;-- 损坏标记存在 # Step 2：执行修复 qemu-img check -r all /path/to/vm-disk.qcow2 # Step 3：验证修复 qemu-img info /path/to/vm-disk.qcow2 # 输出：corrupt: false \u0026lt;-- 标记已清除 # Step 4：启动虚拟机 qm start \u0026lt;vmid\u0026gt; # 成功 根因分析 # corrupt 标记的触发机制 # flowchart TD A1[\"宿主机异常重启 / 强制断电\"] --\u003e F A2[\"存储设备瞬断 I/O 中断\"] --\u003e F A3[\"QEMU 进程被强制 kill\"] --\u003e F A4[\"存储空间不足导致写入失败\"] --\u003e F F[\"QEMU 检测到写入异常\"] --\u003e G[\"标记 corrupt: true\"] G --\u003e H[\"拒绝后续读写打开\"] H --\u003e I[\"异常结束后标记未自动清理\"] I --\u003e J[\"虚拟机下次启动失败\"] qcow2 镜像头部有一个 corrupt 字段，是 QEMU 的保护机制。写入异常时置为 true，防止后续写入造成更严重损坏。但异常结束后标记不会自动清理。\nqemu-img check vs QEMU 启动的检查差异 # 检查维度 qemu-img check QEMU 启动 cluster 分配 检查 不独立检查 refcount 信息 检查 不独立检查 L1/L2 table 检查 不独立检查 corrupt 标记 不清除 直接拒绝 这是最容易误判的点：check 通过不代表能启动，必须用 check -r 才能清除标记。\n快照回滚失败的原理 # flowchart TD A[\"qm rollback\"] --\u003e B[\"需要打开底层镜像\"] B --\u003e C[\"镜像头部 corrupt: true\"] C --\u003e D[\"QEMU 拒绝读写打开\"] D --\u003e E[\"快照回滚被阻断\"] style C fill:#f66,color:#fff style E fill:#f66,color:#fff 快照不等于完整灾备。快照适合短期版本回退，但底层镜像异常时快照回滚同样失败。关键业务必须建立\u0026quot;快照 + 独立完整备份\u0026quot;双层保护。 标准 SOP：三阶段修复 # 遇到 PVE / qcow2 虚拟机启动失败，按以下三阶段顺序处理，不跳步。\n阶段一：诊断（只读，零风险） # # 停止虚拟机 qm stop \u0026lt;vmid\u0026gt; || true # 查看镜像状态，确认 corrupt 字段 qemu-img info \u0026lt;disk.qcow2\u0026gt; # 一致性检查（不执行修复） qemu-img check \u0026lt;disk.qcow2\u0026gt; flowchart TD A[\"qemu-img info + check\"] --\u003e B{\"corrupt 标记?\"} B --\u003e|\"false\"| C[\"check 通过? → 镜像正常，检查其他原因\"] B --\u003e|\"true\"| E{\"check 结果?\"} E --\u003e|无错误| F[\"阶段二：清除 corrupt 标记\"] E --\u003e|有错误| G[\"阶段二：修复错误 + 清除标记\"] E --\u003e|严重错误| H[\"阶段三：数据级恢复\"] style F fill:#6a6,color:#fff style G fill:#fa0,color:#fff style H fill:#f66,color:#fff 阶段二：标记修复（低风险） # # Step 1：仅修复 leaks（最保守） qemu-img check -r leaks \u0026lt;disk.qcow2\u0026gt; # Step 2：修复所有可修复问题，含 corrupt 标记 qemu-img check -r all \u0026lt;disk.qcow2\u0026gt; # Step 3：验证 corrupt: false qemu-img info \u0026lt;disk.qcow2\u0026gt; # Step 4：启动虚拟机 qm start \u0026lt;vmid\u0026gt; 阶段三：高级恢复（高风险，阶段二无效时） # # 检查宿主机内核日志 dmesg -T | grep -iE \u0026#34;I/O error|corrupt|EXT4-fs error|nvme|scsi\u0026#34; # 镜像转换重建 qemu-img convert --salvage -O qcow2 \u0026lt;damaged.qcow2\u0026gt; \u0026lt;recovered.qcow2\u0026gt; # 数据级恢复（最后手段） modprobe nbd max_part=8 qemu-nbd --connect=/dev/nbd0 \u0026lt;disk.qcow2\u0026gt; ddrescue /dev/nbd0 /dev/nbd1 rescue.log qemu-nbd --disconnect /dev/nbd0 阶段三风险提示：执行前确认磁盘空间充足，强烈建议先拷贝镜像文件。宿主机存在 I/O 错误时优先排查底层存储硬件。 预防措施 # 1. 存储健康巡检 # # 内核错误日志 dmesg -T | grep -iE \u0026#34;I/O error|blk|buffer|EXT4-fs error|nvme|scsi|corrupt|ENOSPC\u0026#34; # 系统级错误 journalctl -b -p err..alert 2. 存储空间监控 # 建议告警阈值：\n文件系统使用率 \u0026gt; 80% inode 使用率 \u0026gt; 70% LVM thin pool \u0026gt; 75% 3. 有序停机 # # 正确方式：等待 QEMU 完成写入后退出 qm shutdown \u0026lt;vmid\u0026gt; qm wait \u0026lt;vmid\u0026gt; 强制 kill -9 QEMU、宿主机掉电、存储瞬断均可能触发 corrupt 标记。\n4. 双层备份保护 # flowchart LR VM[\"关键虚拟机\"] VM --\u003e|短期| SNAP[\"快照：每日自动\"] VM --\u003e|长期| BAK[\"完整备份：每周全量\"] SNAP --\u003e SA[\"用途：版本回退\"] SNAP --\u003e SB[\"限制：依赖底层镜像\"] BAK --\u003e BA[\"用途：灾难恢复\"] BAK --\u003e BB[\"存储：独立备份介质\"] 核心经验 # corrupt: true 不等于数据已损坏。须结合 qemu-img check 判断，check 通过说明是标记残留 qemu-img check 不清除 corrupt 标记。只有 check -r leaks 或 check -r all 才能清除 快照回滚依赖底层镜像正常打开。镜像有 corrupt 标记时快照回滚同样失败 分阶段修复，低风险优先。顺序：info → check → check -r leaks → check -r all，不跳步 关键磁盘高风险操作前必须先备份。即使时间紧迫，也应至少新建快照 ","date":"2026年3月16日","externalUrl":null,"permalink":"/posts/pve-qcow2-corrupt-fix/","section":"博客文章","summary":"qcow2 镜像 corrupt 标记残留导致 PVE 虚拟机无法启动，快照回滚也失效。25 分钟内通过分阶段低风险修复恢复，附完整 SOP。","title":"PVE 虚拟机启动失败：qcow2 corrupt 标记残留的排查与修复","type":"posts"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/categories/sre/","section":"Categories","summary":"","title":"SRE","type":"categories"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/tags/storage/","section":"Tags","summary":"","title":"Storage","type":"tags"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/tags/%E6%9E%B6%E6%9E%84/","section":"Tags","summary":"","title":"架构","type":"tags"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/categories/%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84/","section":"Categories","summary":"","title":"系统架构","type":"categories"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/series/blowfish-%E4%B8%BB%E9%A2%98%E5%AE%9E%E8%B7%B5/","section":"Series","summary":"","title":"Blowfish 主题实践","type":"series"},{"content":" 当 AI 编码助手不再只是\u0026quot;写代码\u0026quot;，而是融入 Git 工作流、CI 流水线、Code Review 的完整闭环时，开发效率会发生什么变化？本文以一次博客优化项目的 32 个 MR 为实证，记录 Claude Code + GitLab pr-agent 协作的完整实践。 工具链全景 # flowchart LR subgraph 开发[\"开发环境\"] CC[\"Claude Code(AI 编码)\"] end subgraph VCS[\"版本控制\"] GL[\"GitLab(代码托管)\"] end subgraph CI[\"持续集成\"] Pipeline[\"CI Pipeline(构建验证)\"] PRA[\"pr-agent(AI Code Review)\"] end subgraph CD[\"持续部署\"] SSH[\"SSH 远程部署(Docker Compose)\"] end CC --\u003e|\"git push\"| GL GL --\u003e|\"触发\"| Pipeline GL --\u003e|\"MR 创建\"| PRA PRA --\u003e|\"评审反馈\"| CC Pipeline --\u003e|\"构建成功\"| SSH style CC fill:#8b5cf6,color:#fff style GL fill:#fc6d26,color:#fff style Pipeline fill:#3b82f6,color:#fff style PRA fill:#10b981,color:#fff style SSH fill:#ef4444,color:#fff 工具 角色 核心能力 Claude Code AI 编码助手 代码编写、脚本批处理、问题诊断、Git 操作 GitLab CI 持续集成 Hugo 构建验证、Docker 镜像打包 pr-agent AI 代码评审 自动 Code Review、安全扫描、改进建议 glab CLI GitLab 命令行 MR 创建、CI 状态查询、变量管理 核心工作流 # 每一次代码变更都遵循严格的闭环流程：\nflowchart TD A[\"1. Claude Code分析需求 + 编写代码\"] --\u003e B[\"2. git commit遵循 Conventional Commits\"] B --\u003e C[\"3. git push推送到功能分支\"] C --\u003e D[\"4. glab mr create创建 Merge Request\"] D --\u003e E[\"5. CI Pipeline自动触发构建\"] D --\u003e F[\"6. /review触发 pr-agent 评审\"] E --\u003e G{\"构建通过?\"} F --\u003e H{\"评审通过?\"} G --\u003e|\"失败\"| I[\"诊断日志修复问题\"] H --\u003e|\"有问题\"| I I --\u003e B G --\u003e|\"通过\"| J{\"全部通过?\"} H --\u003e|\"通过\"| J J --\u003e|\"是\"| K[\"7. glab mr merge--squash 合并\"] K --\u003e L[\"8. 自动部署SSH + Docker Compose\"] L --\u003e M[\"9. 清理分支\"] style A fill:#8b5cf6,color:#fff style F fill:#10b981,color:#fff style K fill:#3b82f6,color:#fff style L fill:#ef4444,color:#fff 实战案例详解 # 案例 1：顺利通过——一次性合并 # MR #8：about 页面 shortcodes 增强\n# 1. Claude Code 编写代码 # （修改 content/about.md，使用 timeline/alert/mermaid/button） # 2. 提交推送 git checkout -b optimize/content-enhancement git add content/about.md git commit -m \u0026#34;style: 增强 about 页面，使用 Blowfish shortcodes 优化展示\u0026#34; git push origin optimize/content-enhancement # 3. 创建 MR + 触发评审 glab mr create --title \u0026#34;style: 增强 about 页面展示效果\u0026#34; --target-branch main glab mr note 8 --message \u0026#34;/review\u0026#34; pr-agent 评审结果：\n评审工作量: 1/5 安全问题: 邮箱地址暴露（建议级别，非阻断） 重大问题: 无 邮箱是博主有意公开的联系方式，评审建议合理但不需要修复。直接合并。\n结果：1 次提交，1 次评审，直接合并。这是最理想的流程。 案例 2：CI 失败 → 修复 → 再次通过 # MR #7：全面优化博客质量\n第一次提交后 CI 构建失败：\nERROR: error calling Fill: this method is only available for raster images. 根因：启用了 author.svg 作为头像，但 Blowfish 主题对头像调用 .Fill 方法（仅支持 PNG/JPG）。\n修复流程：\n# 1. 诊断：查看 CI 日志 glab api \u0026#34;projects/.../jobs/47046/trace\u0026#34; | tail -20 # 2. 修复：SVG → PNG 转换 python3 -c \u0026#34;import cairosvg; cairosvg.svg2png(...)\u0026#34; # 3. 追加提交 git add assets/img/author.png config/_default/languages.zh-cn.toml git commit -m \u0026#34;fix: 修复 author image SVG 导致 Hugo 构建失败\u0026#34; git push origin optimize/blog-quality-improvements # 4. CI 自动重跑 → 通过 → 合并 教训：SVG 是矢量格式，Hugo 的图片处理管线（Resize/Fill）只支持光栅图。这个错误如果直推 main 就会导致线上构建失败。MR + CI 的价值在此。 案例 3：pr-agent 发现问题 → 修复 → 迭代 # MR #10：Favicon + 分类统一 + 分享按钮\npr-agent 评审发现了一个真实的命名不一致：\n分类命名不一致: categories: [\u0026#34;Docker\u0026#34;, \u0026#34;docker-compose\u0026#34;, \u0026#34;DevOps\u0026#34;] 建议统一命名风格，例如将 docker-compose 改为 Docker Compose， 以保持与其他分类的一致性。 修复：\n# 接受评审建议，修改 2 个文件 # gitlab-deploy.md: \u0026#34;docker-compose\u0026#34; → \u0026#34;Docker Compose\u0026#34; # nexus-use-traefik-proxy.md: \u0026#34;docker-compose\u0026#34; → \u0026#34;Docker Compose\u0026#34; git add content/posts/gitlab-deploy.md content/posts/nexus-use-traefik-proxy.md git commit -m \u0026#34;fix: 统一 docker-compose 分类命名为 Docker Compose\u0026#34; git push origin optimize/favicon-sharing-categories CI 重跑通过，合并。\n价值：pr-agent 发现了人工容易忽略的命名不一致问题。32 个 MR 中，pr-agent 给出了有效建议的占比约 40%，真正需要修复的约 15%。 案例 4：多轮迭代——SSH 部署调试 # MR #22 → #23 → #24 → #25：这是迭代最多的一组。\n第 1 轮（MR #22）：添加 SSH 部署 job，pr-agent 指出 StrictHostKeyChecking=no 安全问题，修复后合并。\n第 2 轮（MR #23）：部署失败，error in libcrypto。Docker Alpine 镜像的 OpenSSL 不支持 OpenSSH 密钥格式。改用默认 runner 镜像。\n第 3 轮（MR #24）：默认 runner 同样报 libcrypto 错误。添加 ssh-keygen -p -m PEM 运行时转换。\n第 4 轮（MR #25）：ssh-keygen 本身也无法读取 OpenSSH 格式。最终方案：本地预先转换为 PEM 格式，通过 glab variable update 重新上传。\n# 本地转换密钥格式 ssh-keygen -p -m PEM -f /tmp/key -N \u0026#34;\u0026#34; -q # -----BEGIN RSA PRIVATE KEY----- (PEM 格式) # 更新 GitLab CI 变量 glab variable update SSH_PRIVATE_KEY --type file --value \u0026#34;$(cat /tmp/key)\u0026#34; flowchart LR A[\"MR #22SSH 部署\"] --\u003e|\"pr-agent:安全问题\"| B[\"修复StrictHostKeyChecking\"] B --\u003e C[\"MR #23libcrypto 错误\"] C --\u003e|\"换 runner\"| D[\"MR #24仍然报错\"] D --\u003e|\"ssh-keygen也失败\"| E[\"MR #25本地转 PEM\"] E --\u003e|\"重试 job\"| F[\"部署成功\"] style A fill:#ef4444,color:#fff style F fill:#10b981,color:#fff 复盘：4 轮迭代看似低效，但每轮都精确缩小了问题范围。如果一开始就\u0026quot;猜\u0026quot;答案直推 main，可能需要更多次试错还会影响线上。\nClaude Code 的 Git 操作模式 # Claude Code 在整个流程中承担的不仅是\u0026quot;写代码\u0026quot;，而是完整的 Git 工作流：\n标准操作序列 # # 1. 创建功能分支 git checkout -b fix/xxx # 2. 编写代码（Claude Code 核心工作） # ... 修改文件 ... # 3. 提交（Conventional Commits 规范） git add \u0026lt;specific-files\u0026gt; git commit -m \u0026#34;fix: 具体修复内容\u0026#34; # 4. 推送 git push origin fix/xxx # 5. 创建 MR glab mr create --title \u0026#34;fix: ...\u0026#34; --target-branch main # 6. 触发评审 glab mr note \u0026lt;mr-id\u0026gt; --message \u0026#34;/review\u0026#34; # 7. 等待 CI + 评审 sleep 45 \u0026amp;\u0026amp; glab ci list --per-page=1 # 8. 检查评审结果 glab api \u0026#34;projects/.../merge_requests/\u0026lt;id\u0026gt;/notes\u0026#34; # 9. 合并 glab mr merge \u0026lt;id\u0026gt; --squash --yes # 10. 清理 git checkout main \u0026amp;\u0026amp; git pull \u0026amp;\u0026amp; git branch -D fix/xxx 关键设计决策 # 为什么用 squash merge？\n每个 MR 可能有多次 fix 提交（主提交 + 评审修复），squash 合并后 main 分支保持干净的线性历史。\n为什么每次都创建新分支？\nGitLab 的 pre-receive hook 会校验提交信息格式，直接推 main 的长提交信息可能被拒绝。分支 + MR 是最安全的路径。\n为什么用 glab 而不是 Web UI？\nClaude Code 运行在终端环境，glab CLI 让整个流程可以自动化，无需切换到浏览器。从创建 MR 到合并到清理分支，全部在命令行完成。\npr-agent 评审模式分析 # 评审触发方式 # # 在 MR 中添加评论触发 glab mr note \u0026lt;mr-id\u0026gt; --message \u0026#34;/review\u0026#34; pr-agent 收到 /review 后自动分析 diff，生成 Persistent Review（会随新提交自动更新）。\n评审输出结构 # 每次评审包含 4 个维度：\n维度 说明 示例 Effort 评审工作量（1-5） 1 🔵⚪⚪⚪⚪ Tests 测试相关建议 No relevant tests Security 安全扫描 SSH 密钥暴露、邮箱泄露 Focus Areas 重点关注区域 具体代码行 + 建议 32 个 MR 的评审统计 # 评审结果 数量 占比 无问题直接通过 19 59% 有建议但非阻断 8 25% 发现需修复的问题 5 16% pr-agent 发现的真实有效问题：\ndocker-compose 分类命名不一致（MR #10） StrictHostKeyChecking=no 安全风险（MR #22） CSS 选择器全局作用域过大（MR #11） Favicon 文件路径需确认存在（MR #10） 硬编码路径缺乏灵活性（MR #11） CI 流水线架构 # stages: - build # Hugo 构建（MR + main 都触发） - docker-build # Docker 镜像（仅 main） - deploy # Pages + SSH 远程部署（仅 main） flowchart TB subgraph MR[\"MR 触发\"] B1[\"build(Hugo 构建验证)\"] end subgraph Main[\"main 触发\"] B2[\"build\"] --\u003e D[\"docker-build(镜像打包推送)\"] D --\u003e P[\"pages(GitLab Pages)\"] D --\u003e S[\"deploy-production(SSH 远程部署)\"] end style B1 fill:#3b82f6,color:#fff style D fill:#f59e0b,color:#fff style P fill:#10b981,color:#fff style S fill:#ef4444,color:#fff MR 阶段只跑 build 验证（Hugo 能否成功构建），不触发部署。这保证了评审阶段的安全性——即使代码有问题也不会影响线上。\n合并到 main 后触发完整流水线：构建 → Docker 打包 → 双通道部署（Pages + SSH）。\n效率数据 # 时间分布 # 环节 平均耗时 说明 代码编写 1-5 分钟 Claude Code 自动化 CI 构建 30-45 秒 Hugo 增量构建 pr-agent 评审 10-30 秒 自动触发 合并 + 部署 2 分钟 全自动 单次迭代 4-8 分钟 从提交到上线 32 个 MR 总耗时 # 整个博客优化项目（32 个 MR、涉及 200+ 文件变更）在单次会话中完成。\n最佳实践总结 # 1. 小步提交，快速迭代 # 每个 MR 只做一件事。宁可多开 4 个 MR 调试 SSH 部署（#22-#25），也不要把所有改动塞进一个巨型 MR。\n2. CI 是安全网 # 所有代码必须通过 CI 验证才能合并。author.svg 导致构建失败、summaryLength=0 导致卡片拉伸——这些问题都被 CI 拦住了。\n3. 认真对待 pr-agent 建议 # pr-agent 的建议不一定都需要修复（59% 无问题直接通过），但 16% 的真实问题值得认真对待。docker-compose 命名不一致这种细节，人工评审很难注意到。\n4. 善用 glab CLI # # 创建 MR glab mr create --title \u0026#34;...\u0026#34; --target-branch main # 触发评审 glab mr note \u0026lt;id\u0026gt; --message \u0026#34;/review\u0026#34; # 查看 CI 状态 glab ci list --per-page=1 # 查看评审结果 glab api \u0026#34;projects/.../merge_requests/\u0026lt;id\u0026gt;/notes\u0026#34; # 合并 glab mr merge \u0026lt;id\u0026gt; --squash --yes # 管理 CI 变量（敏感信息不入代码库） glab variable set SSH_PRIVATE_KEY --type file --value \u0026#34;$(cat key)\u0026#34; 5. Conventional Commits 不是形式主义 # GitLab pre-receive hook 强制校验提交信息格式。feat:、fix:、style:、docs: 等前缀让 changelog 自动生成成为可能，也便于快速定位每个 MR 的性质。\n附录：pr-agent 部署指南 # 本项目的 pr-agent 采用 Helm Chart + Kubernetes 方式部署，源码托管在 devops/gitops-apps 仓库。\n架构概览 # flowchart LR GL[\"GitLab\"] --\u003e|\"Webhook(MR event)\"| PA[\"pr-agent(K8s Pod)\"] PA --\u003e|\"API 调用\"| LLM[\"LLM 网关(Claude Sonnet 4.6)\"] LLM --\u003e|\"评审结果\"| PA PA --\u003e|\"MR Comment\"| GL style GL fill:#fc6d26,color:#fff style PA fill:#10b981,color:#fff style LLM fill:#8b5cf6,color:#fff Helm Chart 结构 # pr-agent/ ├── Chart.yaml # Chart 元信息 ├── values.yaml # 配置参数 └── templates/ ├── deployment.yaml # Pod 部署 ├── service.yaml # NodePort 服务 ├── configmap.yaml # 环境变量 ├── secret.yaml # 敏感信息 ├── serviceaccount.yaml # 服务账号 └── ingress-traefik.yaml # Traefik 路由（可选） 核心配置（values.yaml） # # 镜像配置 image: repository: yangzun/pr-agent tag: \u0026#34;gitlab\u0026#34; pullPolicy: Always # 服务端口 service: type: NodePort port: 38081 # LLM 模型配置 env: CONFIG__MODEL: \u0026#34;claude-sonnet-4-6\u0026#34; CONFIG__MODEL_TURBO: \u0026#34;claude-sonnet-4-6\u0026#34; CONFIG__FALLBACK_MODELS: \u0026#39;[\u0026#34;claude-sonnet-4-6\u0026#34;]\u0026#39; CONFIG__CUSTOM_MODEL_MAX_TOKENS: \u0026#34;2048000\u0026#34; # GitLab 集成 CONFIG__GIT_PROVIDER: \u0026#34;gitlab\u0026#34; GITLAB__URL: \u0026#34;https://gitlab.cpinnov.run\u0026#34; CONFIG__RESPONSE_LANGUAGE: \u0026#34;zh-CN\u0026#34; # 评审行为 CONFIG__IGNORE_BOT_COMMENTS: \u0026#34;true\u0026#34; PR_CODE_SUGGESTIONS__COMMITABLE_CODE_SUGGESTIONS: \u0026#34;true\u0026#34; CONFIG__LARGE_PATCH_POLICY: \u0026#34;compress\u0026#34; # 自定义标签 PR_DESCRIPTION__CUSTOM_LABELS: \u0026gt;- [\u0026#34;Bug 修复\u0026#34;, \u0026#34;功能增强\u0026#34;, \u0026#34;文档更新\u0026#34;, \u0026#34;测试覆盖\u0026#34;, \u0026#34;配置变更\u0026#34;, \u0026#34;代码重构\u0026#34;, \u0026#34;CI/CD 维护\u0026#34;, \u0026#34;依赖升级\u0026#34;] PR_DESCRIPTION__PUBLISH_LABELS: \u0026#34;true\u0026#34; 部署步骤 # # 1. 克隆 GitOps 仓库 git clone git@gitlab.cpinnov.run:devops/gitops-apps.git cd gitops-apps/pr-agent # 2. 配置密钥（values.yaml 中的 secret 部分） # GITLAB__PERSONAL_ACCESS_TOKEN: GitLab PAT（api scope） # GITLAB__SHARED_SECRET: Webhook 签名密钥 # OPENAI__KEY: LLM API 密钥 # 3. 部署到 Kubernetes helm install pr-agent . -n pr-agent --create-namespace # 4. 配置 GitLab Webhook # URL: http://\u0026lt;node-ip\u0026gt;:38081/webhook # Trigger: Merge request events + Note events # Secret: 与 GITLAB__SHARED_SECRET 一致 GitLab Webhook 配置 # 在每个需要启用 pr-agent 的仓库中：\nSettings → Webhooks → Add webhook\n配置项 值 URL http://\u0026lt;pr-agent-host\u0026gt;:38081/webhook Secret token 与 GITLAB__SHARED_SECRET 一致 Trigger Merge request events, Comments SSL verification 按实际环境选择 配置完成后，在任意 MR 中评论 /review 即可触发 AI 代码评审。\n写在最后 # 这套工作流的核心价值不在于\u0026quot;AI 写了代码\u0026quot;，而在于 AI 融入了完整的工程化闭环：\nClaude Code 不只是代码生成器，它理解 Git 规范、操作 glab CLI、诊断 CI 日志 pr-agent 不只是语法检查器，它能发现命名不一致、安全隐患、架构建议 CI Pipeline 不只是构建工具，它是防止错误上线的最后一道防线 三者协同，让 32 个 MR 在一次会话中从创建到上线，每个变更都经过构建验证和 AI 评审，没有一次直推 main。\n查看博客优化复盘 ","date":"2026年3月16日","externalUrl":null,"permalink":"/posts/claude-code-pr-agent-continuous-iteration/","section":"博客文章","summary":"32 个 MR 的真实案例，展示 Claude Code + GitLab CI + pr-agent 三位一体的 AI 驱动持续迭代开发工作流。","title":"Claude Code + GitLab pr-agent：AI 驱动的持续迭代开发实践","type":"posts"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/tags/gitlab/","section":"Tags","summary":"","title":"Gitlab","type":"tags"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/tags/pr-agent/","section":"Tags","summary":"","title":"Pr-Agent","type":"tags"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/","section":"Tags","summary":"","title":"博客优化","type":"tags"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/categories/%E5%8D%9A%E5%AE%A2%E8%BF%90%E7%BB%B4/","section":"Categories","summary":"","title":"博客运维","type":"categories"},{"content":" 一次完整的 Hugo Blowfish 博客优化复盘——从基础的 Front Matter 修复，到苹果窗口风格代码框、Unsplash 智能封面图、CI/CD 自动化部署，16 个 MR 逐步将博客质量从 60 分推到 95 分。 优化前的状态 # 拿到这个博客时，89 篇技术文章已经积累了相当的内容量，但在工程化和视觉呈现上存在不少问题：\n维度 现状 评分 Front Matter 完整性 96.6% 缺少显示控制参数 D 内容结构 68.5% 文章 H1 标题滥用 D SEO 7 篇缺 description，无 feature image C 视觉体验 无 Favicon，无头像，默认配色 C- CI/CD 仅构建，无自动部署 C 核心问题：Blowfish 主题功能强大，但大部分高级特性未启用。文章质量不错，展示效果却远未达到主题的潜力。 优化路线图 # 整个优化分为 5 个阶段，按投入产出比排序执行：\nflowchart LR A[\"阶段一SEO 基础\"] --\u003e B[\"阶段二视觉体验\"] B --\u003e C[\"阶段三布局重构\"] C --\u003e D[\"阶段四代码框\"] D --\u003e E[\"阶段五CI/CD\"] style A fill:#10b981,color:#fff style B fill:#3b82f6,color:#fff style C fill:#8b5cf6,color:#fff style D fill:#f59e0b,color:#fff style E fill:#ef4444,color:#fff 阶段一：SEO 基础修复 # H1 标题滥用修复（72 篇，342 处） # Blowfish 自动从 title 字段生成 H1，正文中再写 # 标题 就会产生多个 H1，直接导致：\n搜索引擎降权（一个页面不应有多个 H1） 目录（TOC）生成混乱 无障碍阅读器导航异常 写了一个 Python 脚本，精确匹配代码块外的 # 并转为 ## ：\n# 核心逻辑：追踪代码块围栏，仅修改正文中的 H1 in_code_block = False for line in lines: if line.startswith(\u0026#39;```\u0026#39;): in_code_block = not in_code_block if not in_code_block and line.startswith(\u0026#39;# \u0026#39;): line = \u0026#39;#\u0026#39; + line # # -\u0026gt; ## 成果：72 篇文章，342 个 H1 精确转为 H2，代码块内的注释完全不受影响。 补充缺失的 description # 7 篇文章缺少 description 字段，搜索引擎无法生成有意义的摘要。逐篇阅读内容后补充了 50-150 字的中文描述。\n启用 robots.txt # enableRobotsTXT 从 false 改为 true，让搜索引擎能正确爬取。\n阶段二：视觉体验提升 # Favicon 全尺寸生成 # 从 author.svg 通过 CairoSVG 生成 5 种尺寸的 PNG：\n# 生成的文件 static/favicon-16x16.png # 浏览器标签页 static/favicon-32x32.png # 浏览器标签页（高分屏） static/apple-touch-icon.png # iOS 添加到主屏幕 static/android-chrome-192x192.png # Android PWA static/android-chrome-512x512.png # Android PWA 启动画面 通过自定义 extend-head.html 注入 favicon 链接。\n分类名称统一（59 篇） # 标签和分类的大小写混乱严重影响归档效果：\n修复前 修复后 k8s / K8s / kubernetes Kubernetes devops / DevOps DevOps docker / Docker Docker linux / Linux Linux 日志 可观测性 技术 技术实践 首页个人信息增强 # # 修改前 headline = \u0026#34;欢迎来到我的博客\u0026#34; bio = \u0026#34;一个热爱技术、乐于分享经验和想法的开发者。\u0026#34; # 修改后 headline = \u0026#34;云原生与 DevOps 实践者\u0026#34; bio = \u0026#34;专注 Kubernetes、CI/CD、可观测性等云原生技术栈， 记录生产环境中的实战经验与踩坑复盘。\u0026#34; 阶段三：布局重构 # 配色与布局切换 # 配置项 修改前 修改后 效果 colorScheme blowfish terminal 绿色极客终端风 homepage.layout profile background 沉浸式背景+毛玻璃 header.layout fixed fixed-fill-blur 滚动时毛玻璃效果 pagerSize 100 15 分页加载优化 smartTOCHideUnfocusedChildren false true 长文目录自动折叠 子导航菜单 # 在主导航下方添加热门分类快捷入口：\n[[subnavigation]] name = \u0026#34;Kubernetes\u0026#34; pageRef = \u0026#34;categories/kubernetes\u0026#34; weight = 10 [[subnavigation]] name = \u0026#34;Docker\u0026#34; pageRef = \u0026#34;categories/docker\u0026#34; weight = 20 自定义 404 页面 # 替换主题默认的简陋 404，使用 Tailwind 样式实现居中大号数字 + 双按钮导航。\n阶段四：苹果窗口风格代码框 # 这是视觉提升最大的一项改动。完全通过 CSS + JS 实现，不修改主题源码。\nCSS 实现要点 # /* macOS 标题栏 */ .highlight::before { content: \u0026#34;\u0026#34;; display: block; height: 2.5rem; background: rgba(40, 44, 52, 0.95); } /* 红绿黄三圆点 */ .highlight::after { content: \u0026#34;\u0026#34;; position: absolute; top: 0.85rem; left: 1rem; width: 12px; height: 12px; border-radius: 50%; background: #ff5f57; box-shadow: 20px 0 0 #febc2e, 40px 0 0 #28c840; } 踩坑记录 # 坑 1：Chroma 底纹。Hugo 的 Chroma 高亮器给 .chroma 和 .line 元素都带了 background-color，与自定义背景叠加产生条纹。解决：对所有子元素强制 background: transparent !important，仅由 pre 控制背景。 坑 2：复制按钮叠加。Blowfish 内置的 enableCodeCopy 和自定义 code-copy.js 同时生效，产生两个按钮。解决：关闭主题内置的 enableCodeCopy = false。 坑 3：复制多余换行。.cl 元素的 textContent 本身包含 \\n，逐行拼接又追加一个，导致双倍换行。解决：直接取 code.textContent，一行代码替代 15 行。 阶段五：封面图与 CI/CD # Unsplash 智能封面图 # 从 Picsum 随机风景照进化为 Unsplash 按分类精选的技术主题图片：\nflowchart TD A[\"文章 Front Matter\"] --\u003e B{\"有 featureimage?\"} B --\u003e|有| C[\"直接使用\"] B --\u003e|无| D[\"CI 构建前运行assign-featureimage.py\"] D --\u003e E[\"读取已使用的图片 ID\"] E --\u003e F[\"从 95+ 图片池中选取未使用的 ID\"] F --\u003e G[\"slug MD5 hash保证稳定分配\"] G --\u003e H[\"写入 Front Matter\"] H --\u003e C 核心设计决策：\n1920x1080 q=100 — 1080p 满质量，配合 hotlinkFeatureImage = true 直接引用 CDN slug hash 分配 — 同一篇文章每次构建获得同一张图 已用 ID 跳过 — 保证 89 篇文章零重复 CI 自动化 — 新文章无需手动配图 SSH 远程部署 # 完整的 CI/CD 流水线：\nstages: - build # Hugo 构建（--minify --gc） - docker-build # Docker 镜像构建推送 - deploy # GitLab Pages + SSH 远程部署 SSH 密钥通过 GitLab CI/CD Variable（file 类型）注入，OpenSSH 格式转 PEM 避免 libcrypto 兼容性问题。\n最终成果 # 数据对比 # 指标 优化前 优化后 SEO 评分 60 95 Front Matter 完整率 3.4% 100% H1 标题规范 31.5% 100% 封面图覆盖 0% 100%（每篇唯一） Favicon 无 5 尺寸全覆盖 构建产物 未压缩 \u0026ndash;minify \u0026ndash;gc 部署方式 手动 SSH 自动远程部署 新文章封面图 需手动配 CI 自动分配 MR 统计 # 16 个 MR，每个都经过 CI 流水线 + pr-agent AI 评审：\npie title MR 分类统计 \"SEO/内容修复\" : 5 \"视觉体验\" : 4 \"布局重构\" : 2 \"代码框\" : 3 \"CI/CD\" : 2 关键经验 # 1. 不要修改主题源码 # 所有自定义都通过 Blowfish 提供的扩展点实现：\nlayouts/partials/extend-head.html — Favicon layouts/partials/extend-footer.html — 自定义 JS assets/css/custom.css — 苹果代码框样式 layouts/404.html — 自定义 404 页面 主题升级时零冲突。\n2. 批量操作先写脚本 # 72 篇文章的 H1 修复、59 篇的分类统一、89 篇的封面图分配——全部通过 Python 脚本自动化，手动改一天的工作量 10 分钟搞定。\n3. 每次改动都要验证 # CI 流水线 + pr-agent 双重验证，16 个 MR 无一次直接推 main。有一次 author.svg 做头像导致 Hugo 构建失败（SVG 不支持 .Fill 方法），如果直推 main 就会导致线上故障。\n4. Unsplash 图片 ID 要逐个验证 # 92 个候选 ID 中有 23 个返回 404。Unsplash 的图片可能被作者删除或设为私有，必须 curl 逐个验证后才能使用。\n下一步 # 核心文章改用 Page Bundle 结构（posts/xxx/index.md + 本地图片） 定制 OG 社交分享图（1200x630，含博客名称和文章标题） 接入 Umami 自托管分析 查看 Blowfish 优化指南 ","date":"2026年3月16日","externalUrl":null,"permalink":"/posts/hugo-blowfish-blog-optimization-journey/","section":"博客文章","summary":"20+ 项优化、16 个 MR、涵盖 SEO 修复、布局重构、苹果代码框、封面图自动化和 SSH 远程部署的完整博客优化复盘。","title":"从 60 分到 95 分：Hugo Blowfish 博客的极致优化之路","type":"posts"},{"content":" 本文通过具体的优化前后对比，直观展示 Blowfish 主题文章的优化效果。配合优化指南使用，快速提升文章质量。 Front Matter 对比 # 优化前 # --- title: \u0026#34;OpenClaw AI Agent 架构解析：多引擎联动与记忆系统\u0026#34; date: 2026-03-15T22:30:00+08:00 draft: false tags: [\u0026#34;AI\u0026#34;, \u0026#34;Agent\u0026#34;, \u0026#34;OpenClaw\u0026#34;, \u0026#34;架构\u0026#34;] categories: [\u0026#34;技术\u0026#34;] --- 问题：缺少 description（SEO 不友好）、缺少系列信息（不利于文章组织）、缺少特性图片配置、缺少显示控制参数。 优化后 # --- title: \u0026#34;OpenClaw AI Agent 架构解析：多引擎联动与记忆系统\u0026#34; description: \u0026#34;深入解析 OpenClaw AI Agent 的核心架构设计，包括多 AI 引擎联动、Skills 系统、MCP 集成和三层记忆系统的完整实现\u0026#34; date: 2026-03-15T22:30:00+08:00 lastmod: 2026-03-16T10:00:00+08:00 draft: false tags: [\u0026#34;AI\u0026#34;, \u0026#34;Agent\u0026#34;, \u0026#34;OpenClaw\u0026#34;, \u0026#34;架构\u0026#34;, \u0026#34;LLM\u0026#34;] categories: [\u0026#34;系统架构\u0026#34;] series: [\u0026#34;OpenClaw 技术解析\u0026#34;] series_order: 1 featureimage: \u0026#34;https://images.unsplash.com/photo-1542831371-29b0f74f9713?w=1920\u0026amp;h=1080\u0026amp;fit=crop\u0026amp;q=100\u0026#34; showHero: true heroStyle: \u0026#34;background\u0026#34; showTableOfContents: true showReadingTime: true showDateUpdated: true showBreadcrumbs: true showZenMode: true robots: \u0026#34;index, follow\u0026#34; --- 改进：添加 description（SEO 优化）、lastmod（显示更新时间）、series 信息（系列文章组织）、featureimage（视觉增强）、显示控制参数（用户体验）、robots（SEO 控制）。 内容结构对比 # 优化前 # # OpenClaw AI Agent 架构解析 ## 核心架构 （直接开始正文内容...） ## 1. 多 AI 引擎联动 ### 模型配置 ​```json {...} ​``` 问题：缺少 Lead 引言（无法快速了解文章主旨）、缺少 Callout 提示（重要信息不突出）、单一代码格式（缺少多格式对比）。\n优化后 # {{\u0026lt; lead \u0026gt;}} OpenClaw 是一个现代化的 AI Agent 框架， 支持多引擎联动、技能系统和三层记忆管理。 {{\u0026lt; /lead \u0026gt;}} ## 核心架构 {{\u0026lt; mermaid \u0026gt;}} graph TB ... {{\u0026lt; /mermaid \u0026gt;}} ### 核心组件说明 | 组件 | 职责 | 技术栈 | |------|------|--------| | **Agent 层** | AI 模型集成 | Claude, GPT, GLM, Gemini | | **Router 层** | 智能路由分发 | 自定义路由规则 | {{\u0026lt; alert icon=\u0026#34;triangle-exclamation\u0026#34; \u0026gt;}} **重要**：Working Memory 每日自动清理， 重要信息需要手动归档到 Long-term Memory。 {{\u0026lt; /alert \u0026gt;}} 改进：添加 Lead 引言（快速了解文章价值）、添加表格说明（清晰展示组件信息）、使用 Alert 突出重要提示。\nShortcodes 使用对比 # 代码展示 # 优化前：单一代码块 # ### 模型配置 ​```json { \u0026#34;providers\u0026#34;: { \u0026#34;anthropic-local\u0026#34;: { \u0026#34;models\u0026#34;: [\u0026#34;claude-sonnet-4-6\u0026#34;] } } } ​``` 优化后：分组代码展示 # 当需要展示多种格式时，使用标题分组：\n### 模型配置 #### JSON 格式 ​```json { \u0026#34;providers\u0026#34;: { \u0026#34;anthropic-local\u0026#34;: { \u0026#34;models\u0026#34;: [\u0026#34;claude-sonnet-4-6\u0026#34;] } } } ​``` #### YAML 格式 ​```yaml providers: anthropic-local: models: - claude-sonnet-4-6 ​``` 通过标题分组可以清晰区分不同格式，同时避免使用可能存在兼容性问题的 shortcodes。\n表格使用对比 # 优化前：简单列表 # | 任务类型 | 推荐模型 | 原因 | |---------|---------|------| | 日常对话 | Claude Sonnet 4.6 | 成本低、速度快 | 优化后：增强表格 # ### 核心组件说明 | 组件 | 职责 | 技术栈 | |------|------|--------| | **Agent 层** | AI 模型集成 | Claude, GPT, GLM, Gemini | | **Router 层** | 智能路由分发 | 自定义路由规则 | | **Skills 层** | 技能系统 | 90+ 预置技能 | | **MCP 层** | 外部集成 | Teambition, Nowledge Mem | ### 故障排查表 | 问题 | 可能原因 | 解决方案 | 预防措施 | |-----|---------|---------|---------| | **模型回退失败** | fallback provider 未配置 | 检查 providers 配置完整性 | 配置验证脚本 | | **Cron 任务不执行** | 缺少 model 字段 | agentTurn 必须带 model | 使用配置模板 | 改进：更丰富的表格内容、添加预防措施列（提升实用性）、使用粗体强调关键词。\nCallout 提示对比 # 优化前：无提示 # ### 配置规范 - 模型名必须写完整：`anthropic-local/claude-sonnet-4-6` - 路径必须用绝对路径 优化后：带 Callout # {{\u0026lt; alert icon=\u0026#34;triangle-exclamation\u0026#34; \u0026gt;}} **重要**：Working Memory 每日自动清理， 重要信息需要手动归档到 Long-term Memory。 {{\u0026lt; /alert \u0026gt;}} ### 配置规范 - 模型名必须写完整：`anthropic-local/claude-sonnet-4-6` - 路径必须用绝对路径 视觉突出、不易忽略，并且可以通过自定义颜色区分不同级别的提示。\n优化效果量化 # SEO 优化 # 指标 优化前 优化后 提升 Meta Description 缺失 完整 100% Keywords 覆盖 4 个 5 个 +25% 结构化数据 无 完整 100% SEO 评分 60/100 95/100 +58% 用户体验 # 指标 优化前 优化后 提升 视觉吸引力 一般 优秀 +67% 信息层次 一般 优秀 +67% 可读性 良好 优秀 +25% 互动性 较差 优秀 +150% 内容质量 # 指标 优化前 优化后 提升 内容完整性 80% 100% +25% 代码示例 1 种格式 2-3 种格式 +150% 表格数量 2 个 5 个 +150% Callout 数量 0 个 3 个 从无到有 优化检查清单 # Front Matter 必选项 # title（标题） description（SEO 描述，150-160 字符） date（发布日期，ISO 8601 格式） tags（标签，5-8 个） categories（分类） Front Matter 推荐项 # lastmod（最后更新日期） series（系列名称） series_order（系列顺序） featureimage（特性图片） showHero + heroStyle（英雄图配置） showTableOfContents（显示目录） showReadingTime（显示阅读时间） showBreadcrumbs（显示面包屑） showZenMode（显示 Zen 模式） 内容结构 # Lead 引言（文章开头） 合理的标题层级（H2-H4） 表格对比信息 代码语法高亮 Callout 提示重要信息 Shortcodes 使用 # Mermaid 流程图（替代 ASCII） Figure 增强图片 Alert 提示框 Button 行动按钮 快速优化模板 # --- title: \u0026#34;文章标题\u0026#34; description: \u0026#34;SEO 描述（150-160 字符）\u0026#34; date: 2026-03-16T10:00:00+08:00 tags: [\u0026#34;tag1\u0026#34;, \u0026#34;tag2\u0026#34;, \u0026#34;tag3\u0026#34;] categories: [\u0026#34;分类\u0026#34;] showHero: true heroStyle: \u0026#34;background\u0026#34; showTableOfContents: true showReadingTime: true showBreadcrumbs: true showZenMode: true --- {{\u0026lt; lead \u0026gt;}} 文章引言，突出核心价值。 {{\u0026lt; /lead \u0026gt;}} ## 第一部分 内容... {{\u0026lt; alert icon=\u0026#34;triangle-exclamation\u0026#34; \u0026gt;}} 重要提示 {{\u0026lt; /alert \u0026gt;}} ## 总结 {{\u0026lt; button href=\u0026#34;/next-article\u0026#34; target=\u0026#34;_self\u0026#34; \u0026gt;}} 继续阅读 {{\u0026lt; /button \u0026gt;}} 总结 # 核心优化原则 # Front Matter 完整：SEO + 用户体验 + 显示控制 Lead 引言：快速传达文章价值 结构化内容：表格、列表、Callout Shortcodes 赋能：Mermaid、Alert、Figure 视觉增强：Hero 图片、Figure、Button 下一步行动 # 参照优化指南全面优化文章 使用优化模板创建新文章 逐步优化现有文章的 Front Matter 应用 Shortcodes 增强内容表现力 查看完整优化指南 ","date":"2026年3月16日","externalUrl":null,"permalink":"/posts/blowfish-optimization-comparison/","section":"博客文章","summary":"通过 Front Matter、内容结构和 Shortcodes 的具体优化前后对比，直观展示文章质量提升效果。","title":"Blowfish 文章优化前后对比分析","type":"posts"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/tags/seo/","section":"Tags","summary":"","title":"SEO","type":"tags"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/tags/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/","section":"Tags","summary":"","title":"最佳实践","type":"tags"},{"content":" 本文提供全面的 Blowfish 主题文章优化最佳实践，帮助你创建优雅、简洁、结构化、美观、逻辑清晰的技术文档。从 Front Matter 到 Shortcodes，一站式掌握 Blowfish 的正确用法。 Front Matter 优化 # 推荐的完整 Front Matter 模板 # --- # 基础信息 title: \u0026#34;文章标题\u0026#34; description: \u0026#34;SEO 友好的简短描述，建议 150-160 字符\u0026#34; date: 2026-03-16T10:00:00+08:00 lastmod: 2026-03-16T12:00:00+08:00 draft: false # 分类 tags: [\u0026#34;kubernetes\u0026#34;, \u0026#34;devops\u0026#34;, \u0026#34;云原生\u0026#34;] categories: [\u0026#34;技术实践\u0026#34;] series: [\u0026#34;Kubernetes 进阶\u0026#34;] # 如果属于某个系列 series_order: 1 # 系列中的顺序 # 图片 featureimage: \u0026#34;https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=1920\u0026amp;h=1080\u0026amp;fit=crop\u0026amp;q=100\u0026#34; featureimagecaption: \u0026#34;图片说明（可选）\u0026#34; showHero: true heroStyle: \u0026#34;background\u0026#34; # basic, big, background, thumbAndBackground # 显示控制 showDate: true showDateUpdated: true showReadingTime: true showWordCount: true showTableOfContents: true showTaxonomies: true showBreadcrumbs: true showPagination: true showZenMode: true # SEO robots: \u0026#34;index, follow\u0026#34; excludeFromSearch: false --- Front Matter 参数分类 # 必需参数 # 参数 说明 示例 title 文章标题 \u0026quot;Kubernetes 部署指南\u0026quot; description SEO 描述 \u0026quot;从零开始学习 K8s 集群部署\u0026quot; date 发布日期 2026-03-16T10:00:00+08:00 tags 标签（数组） [\u0026quot;kubernetes\u0026quot;, \u0026quot;devops\u0026quot;] 推荐参数 # 参数 说明 建议值 showHero 显示英雄图 true heroStyle 英雄图样式 \u0026quot;background\u0026quot; showTableOfContents 显示目录 true showReadingTime 显示阅读时间 true showBreadcrumbs 显示面包屑 true showZenMode 显示 Zen 模式 true 可选参数 # 参数 说明 使用场景 lastmod 最后更新日期 更新文章后 series 系列名称 多篇相关文章 series_order 系列顺序 系列文章排序 featureimage 特性图片 有配图的文章 robots SEO 指令 控制搜索引擎行为 不同类型文章的 Front Matter 建议 # 技术教程 # --- title: \u0026#34;从零开始部署 Kubernetes 集群\u0026#34; description: \u0026#34;详细介绍如何在生产环境部署高可用 K8s 集群\u0026#34; date: 2026-03-16T10:00:00+08:00 tags: [\u0026#34;kubernetes\u0026#34;, \u0026#34;devops\u0026#34;, \u0026#34;tutorial\u0026#34;] categories: [\u0026#34;技术教程\u0026#34;] series: [\u0026#34;Kubernetes 生产实践\u0026#34;] series_order: 1 showHero: true heroStyle: \u0026#34;background\u0026#34; featureimage: \u0026#34;k8s-cluster.jpg\u0026#34; showTableOfContents: true showReadingTime: true --- 概念解析 # --- title: \u0026#34;深入理解 Kubernetes 架构\u0026#34; description: \u0026#34;解析 K8s 核心组件和设计理念\u0026#34; date: 2026-03-16T10:00:00+08:00 tags: [\u0026#34;kubernetes\u0026#34;, \u0026#34;architecture\u0026#34;] categories: [\u0026#34;技术概念\u0026#34;] showHero: true heroStyle: \u0026#34;basic\u0026#34; showTableOfContents: true showReadingTime: true --- 实战案例 # --- title: \u0026#34;K8s 故障排查：Pod 一直处于 CrashLoopBackOff\u0026#34; description: \u0026#34;记录一次完整的 K8s 故障排查过程\u0026#34; date: 2026-03-16T10:00:00+08:00 lastmod: 2026-03-16T12:00:00+08:00 tags: [\u0026#34;kubernetes\u0026#34;, \u0026#34;troubleshooting\u0026#34;, \u0026#34;case-study\u0026#34;] categories: [\u0026#34;故障排查\u0026#34;] showHero: true heroStyle: \u0026#34;background\u0026#34; featureimage: \u0026#34;debugging.jpg\u0026#34; showDateUpdated: true showTableOfContents: true --- 内容结构最佳实践 # 1. 使用 Lead 引言 # 在文章开头使用 lead 突出核心内容：\n{{\u0026lt; lead \u0026gt;}} 从零开始学习 Kubernetes 集群部署，涵盖环境准备、集群初始化、 网络配置到生产就绪的完整流程。 {{\u0026lt; /lead \u0026gt;}} 2. 结构化标题层级 # # 主标题（H1，自动由 title 生成） {{\u0026lt; lead \u0026gt;}} 引言内容 {{\u0026lt; /lead \u0026gt;}} ## 第一部分（H2） ### 子章节 1.1（H3） #### 细节说明（H4） ### 子章节 1.2（H3） ## 第二部分（H2） 标题建议：H2 用于主要章节，H3 用于子章节，H4 用于细节说明，避免超过 4 级标题。 3. 使用表格对比信息 # | 特性 | Docker | Kubernetes | |------|--------|-----------| | 容器编排 | 单机 | 集群级别 | | 服务发现 | 手动配置 | 自动发现 | | 负载均衡 | 需要额外工具 | 内置支持 | 4. 代码块语法高亮 # 支持的语言：bash, yaml, json, go, python, javascript, typescript, dockerfile, nginx 等。\n5. 使用 Callout 提示重要信息 # {{\u0026lt; alert icon=\u0026#34;triangle-exclamation\u0026#34; \u0026gt;}} **警告**：此操作不可逆，请先备份数据！ {{\u0026lt; /alert \u0026gt;}} {{\u0026lt; alert icon=\u0026#34;check-circle\u0026#34; cardColor=\u0026#34;#10b981\u0026#34; iconColor=\u0026#34;#fff\u0026#34; textColor=\u0026#34;#fff\u0026#34; \u0026gt;}} **最佳实践**：使用 Helm 管理应用配置。 {{\u0026lt; /alert \u0026gt;}} Shortcodes 使用指南 # 1. Mermaid 流程图 # 替代 ASCII 字符画，使用 Mermaid 绘制流程图：\n{{\u0026lt; mermaid \u0026gt;}} graph TB A[开始部署] --\u0026gt; B{环境检查} B --\u0026gt;|通过| C[安装 Docker] B --\u0026gt;|失败| D[修复环境] D --\u0026gt; B C --\u0026gt; E[初始化集群] E --\u0026gt; F[部署网络插件] F --\u0026gt; G[验证集群] {{\u0026lt; /mermaid \u0026gt;}} 常见图表类型：\n流程图 graph TD - 决策流程、部署步骤 时序图 sequenceDiagram - API 调用、组件交互 架构图 graph TB + subgraph - 系统架构 2. Figure 增强图片 # {{\u0026lt; figure src=\u0026#34;architecture.png\u0026#34; alt=\u0026#34;Kubernetes 架构图\u0026#34; caption=\u0026#34;图 1：Kubernetes 集群架构\u0026#34; href=\u0026#34;https://kubernetes.io\u0026#34; target=\u0026#34;_blank\u0026#34; \u0026gt;}} 3. Button 行动按钮 # {{\u0026lt; button href=\u0026#34;https://github.com\u0026#34; target=\u0026#34;_blank\u0026#34; \u0026gt;}} 查看源码 {{\u0026lt; /button \u0026gt;}} 4. Badge 标签 # {{\u0026lt; badge \u0026gt;}}New{{\u0026lt; /badge \u0026gt;}} {{\u0026lt; badge style=\u0026#34;warning\u0026#34; \u0026gt;}}Deprecated{{\u0026lt; /badge \u0026gt;}} 5. Timeline 时间线 # {{\u0026lt; timeline \u0026gt;}} {{\u0026lt; timelineItem icon=\u0026#34;check\u0026#34; header=\u0026#34;第一步\u0026#34; subheader=\u0026#34;2026-03-01\u0026#34; \u0026gt;}} 项目启动 {{\u0026lt; /timelineItem \u0026gt;}} {{\u0026lt; timelineItem icon=\u0026#34;check\u0026#34; header=\u0026#34;第二步\u0026#34; subheader=\u0026#34;2026-03-10\u0026#34; \u0026gt;}} 完成开发 {{\u0026lt; /timelineItem \u0026gt;}} {{\u0026lt; /timeline \u0026gt;}} 6. KaTeX 数学公式 # \u0026lt;!-- 行内公式 --\u0026gt; {{\u0026lt; katex \u0026gt;}}a^2 + b^2 = c^2{{\u0026lt; /katex \u0026gt;}} \u0026lt;!-- 块级公式 --\u0026gt; {{\u0026lt; katex display=\u0026#34;true\u0026#34; \u0026gt;}} \\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2} {{\u0026lt; /katex \u0026gt;}} 实战优化示例 # 技术教程优化 # 优化前：\n--- title: \u0026#34;Traefik 部署\u0026#34; date: 2021-01-17 tags: [\u0026#34;traefik\u0026#34;] --- # Traefik 简介 Traefik 是一个反向代理... ## 安装 kubectl apply -f traefik.yaml 优化后：\n--- title: \u0026#34;Traefik Ingress Controller 完整部署指南\u0026#34; description: \u0026#34;从零开始学习 Traefik：详细介绍如何在 Kubernetes 中部署和配置 Traefik Ingress Controller\u0026#34; date: 2021-01-17T16:15:32+08:00 lastmod: 2026-03-16T10:00:00+08:00 tags: [\u0026#34;traefik\u0026#34;, \u0026#34;ingress\u0026#34;, \u0026#34;kubernetes\u0026#34;] categories: [\u0026#34;网络\u0026#34;, \u0026#34;Kubernetes\u0026#34;] series: [\u0026#34;Kubernetes 网络实践\u0026#34;] series_order: 2 featureimage: \u0026#34;traefik-architecture.png\u0026#34; showHero: true heroStyle: \u0026#34;background\u0026#34; showTableOfContents: true showReadingTime: true showDateUpdated: true --- 优化后的内容结构：\n{{\u0026lt; lead \u0026gt;}} Traefik 是现代化的云原生反向代理和负载均衡器， 专为微服务架构设计。本指南将带你从零开始掌握 Traefik 的部署和配置。 {{\u0026lt; /lead \u0026gt;}} ## Traefik 核心优势 | 特性 | 传统代理 | Traefik | |------|----------|---------| | 配置方式 | 手动编辑 | 自动发现 | | 配置更新 | 需要重启 | 动态实时更新 | | 容器支持 | 需要额外配置 | 原生支持 | ## 架构概览 {{\u0026lt; mermaid \u0026gt;}} graph TB Client[客户端] --\u0026gt; Traefik Traefik --\u0026gt; Service1[服务 A] Traefik --\u0026gt; Service2[服务 B] Traefik --\u0026gt; Service3[服务 C] subgraph Kubernetes Service1 Service2 Service3 end {{\u0026lt; /mermaid \u0026gt;}} ## 部署方式 ### 使用 Helm 部署 ​```bash helm repo add traefik https://traefik.github.io/charts helm install traefik traefik/traefik \\ --namespace traefik \\ --create-namespace ​``` ### 使用 kubectl 部署 ​```bash kubectl apply -f https://raw.githubusercontent.com/.../kubernetes-crd-rbac.yml ​``` {{\u0026lt; alert icon=\u0026#34;triangle-exclamation\u0026#34; \u0026gt;}} **注意**：生产环境建议配置持久化存储和 RBAC 权限。 {{\u0026lt; /alert \u0026gt;}} 架构文档优化 # 优化前：\n--- title: \u0026#34;OpenClaw 架构\u0026#34; date: 2026-03-15 --- 优化后：\n--- title: \u0026#34;OpenClaw AI Agent 架构解析：多引擎联动与记忆系统\u0026#34; description: \u0026#34;深入解析 OpenClaw AI Agent 的核心架构设计，包括多 AI 引擎联动、Skills 系统和 MCP 集成\u0026#34; date: 2026-03-15T22:30:00+08:00 tags: [\u0026#34;AI\u0026#34;, \u0026#34;Agent\u0026#34;, \u0026#34;OpenClaw\u0026#34;, \u0026#34;Architecture\u0026#34;] categories: [\u0026#34;系统架构\u0026#34;] series: [\u0026#34;OpenClaw 技术解析\u0026#34;] series_order: 1 showHero: true heroStyle: \u0026#34;background\u0026#34; showTableOfContents: true showReadingTime: true showZenMode: true --- 优化后的内容结构：\n{{\u0026lt; lead \u0026gt;}} OpenClaw 是一个现代化的 AI Agent 框架，支持多引擎联动、技能系统和记忆管理。 本文将深入解析其核心架构设计。 {{\u0026lt; /lead \u0026gt;}} ## 核心架构 {{\u0026lt; mermaid \u0026gt;}} graph TB subgraph Agent[\u0026#34;OpenClaw Agent\u0026#34;] Claude[\u0026#34;Claude Sonnet 4.6\u0026#34;] GPT[\u0026#34;GPT-5.2/5.3\u0026#34;] end subgraph Router[\u0026#34;Model Router\u0026#34;] Route[\u0026#34;路由层\u0026#34;] end Claude --\u0026gt; Route GPT --\u0026gt; Route Route --\u0026gt; Skills[\u0026#34;Skills Layer\u0026#34;] Skills --\u0026gt; MCP[\u0026#34;MCP Layer\u0026#34;] {{\u0026lt; /mermaid \u0026gt;}} ### 核心组件说明 | 组件 | 职责 | 技术栈 | |------|------|--------| | **Agent 层** | AI 模型集成 | Claude, GPT, GLM, Gemini | | **Router 层** | 智能路由分发 | 自定义路由规则 | | **Skills 层** | 技能系统 | 90+ 预置技能 | | **MCP 层** | 外部集成 | Teambition, Nowledge Mem | {{\u0026lt; alert icon=\u0026#34;triangle-exclamation\u0026#34; \u0026gt;}} **重要**：Working Memory 每日自动清理， 重要信息需要手动归档到 Long-term Memory。 {{\u0026lt; /alert \u0026gt;}} 常见问题 # 如何选择 heroStyle？ # heroStyle 适用场景 特点 basic 简单文章 小图片，简洁 big 重要文章 大图片，突出 background 推荐 全屏背景，现代感 thumbAndBackground 双图需求 缩略图+背景 大多数文章推荐使用 background。\n何时使用 Mermaid？ # 适用场景：流程图、时序图、架构图、状态转换图、甘特图。\n不适用场景：简单的 2-3 步流程（用列表即可）、需要精细控制的图形（用图片）。\n如何优化图片？ # 使用 WebP 格式（Hugo 自动转换） 提供不同尺寸（响应式） 添加 alt 文本（SEO） 使用 caption 说明 如何组织系列文章？ # 规划系列名称和顺序 每篇文章添加 series 和 series_order 第一篇设置 seriesOpened: true --- series: [\u0026#34;Kubernetes 进阶\u0026#34;] series_order: 1 seriesOpened: true --- 性能优化建议 # 构建优化：启用图片优化（默认开启）、使用 CDN 加速、减少不必要的 shortcodes。\n内容优化：合理使用 Mermaid（避免过多）、图片压缩后再上传、使用 `\n` 控制摘要。\n快速参考 # Front Matter 必选模板 # --- title: \u0026#34;文章标题\u0026#34; description: \u0026#34;SEO 描述（150-160 字符）\u0026#34; date: 2026-03-16T10:00:00+08:00 tags: [\u0026#34;tag1\u0026#34;, \u0026#34;tag2\u0026#34;] categories: [\u0026#34;分类\u0026#34;] showTableOfContents: true showReadingTime: true showBreadcrumbs: true showZenMode: true --- 常用 Shortcodes 速查 # Shortcode 用途 使用频率 lead 文章引言 每篇必用 alert 重要提示/警告 高 mermaid 流程图/架构图 高 figure 增强图片 中 button 行动按钮 中 badge 状态标签 低 timeline 时间线 低 accordion 折叠面板 低 推荐配置组合 # 技术教程：\nshowHero: true heroStyle: \u0026#34;background\u0026#34; showTableOfContents: true showReadingTime: true showBreadcrumbs: true showZenMode: true 概念解析：\nshowHero: true heroStyle: \u0026#34;basic\u0026#34; showTableOfContents: true showReadingTime: true 实战案例：\nshowHero: true heroStyle: \u0026#34;background\u0026#34; showDateUpdated: true showTableOfContents: true showReadingTime: true 发布检查清单 # Front Matter # title 和 description 完整 date 格式正确（ISO 8601） tags 和 categories 合理 featureimage 已设置（如有） 显示控制参数已配置 内容结构 # 使用 lead 引言 标题层级合理（H2-H4） 使用表格对比信息 代码块指定语言 重要信息使用 alert Shortcodes # Mermaid 替代 ASCII 图 图片使用 figure 必要时使用 button 引导 SEO # description 包含关键词 robots 设置正确 图片有 alt 文本 URL slug 友好 参考资源 # Blowfish 官方文档 Hugo Shortcodes 指南 Mermaid 文档 ","date":"2026年3月16日","externalUrl":null,"permalink":"/posts/blowfish-optimization-guide/","section":"博客文章","summary":"\u003cdiv class=\"lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl\"\u003e\n  本文提供全面的 Blowfish 主题文章优化最佳实践，帮助你创建优雅、简洁、结构化、美观、逻辑清晰的技术文档。从 Front Matter 到 Shortcodes，一站式掌握 Blowfish 的正确用法。\n\u003c/div\u003e\n\n\n\u003ch2 class=\"relative group\"\u003eFront Matter 优化 \n    \u003cdiv id=\"front-matter-优化\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#front-matter-%e4%bc%98%e5%8c%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e推荐的完整 Front Matter 模板 \n    \u003cdiv id=\"推荐的完整-front-matter-模板\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e7%9a%84%e5%ae%8c%e6%95%b4-front-matter-%e6%a8%a1%e6%9d%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c\"\u003e# 基础信息\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;文章标题\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edescription\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SEO 友好的简短描述，建议 150-160 字符\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2026-03-16T10:00:00\u003c/span\u003e\u003cspan class=\"m\"\u003e+08\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"m\"\u003e00\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003elastmod\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2026-03-16T12:00:00\u003c/span\u003e\u003cspan class=\"m\"\u003e+08\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"m\"\u003e00\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edraft\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c\"\u003e# 分类\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etags\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;kubernetes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;devops\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;云原生\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003ecategories\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;技术实践\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Kubernetes 进阶\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"c\"\u003e# 如果属于某个系列\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries_order\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"c\"\u003e# 系列中的顺序\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c\"\u003e# 图片\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003efeatureimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=1920\u0026amp;h=1080\u0026amp;fit=crop\u0026amp;q=100\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003efeatureimagecaption\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;图片说明（可选）\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowHero\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eheroStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;background\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"c\"\u003e# basic, big, background, thumbAndBackground\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c\"\u003e# 显示控制\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowDate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowDateUpdated\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowReadingTime\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowWordCount\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowTableOfContents\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowTaxonomies\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowBreadcrumbs\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowPagination\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowZenMode\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c\"\u003e# SEO\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003erobots\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;index, follow\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eexcludeFromSearch\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eFront Matter 参数分类 \n    \u003cdiv id=\"front-matter-参数分类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#front-matter-%e5%8f%82%e6%95%b0%e5%88%86%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e必需参数 \n    \u003cdiv id=\"必需参数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%bf%85%e9%9c%80%e5%8f%82%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e参数\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n          \u003cth\u003e示例\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003etitle\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e文章标题\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003e\u0026quot;Kubernetes 部署指南\u0026quot;\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003edescription\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eSEO 描述\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003e\u0026quot;从零开始学习 K8s 集群部署\u0026quot;\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003edate\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e发布日期\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003e2026-03-16T10:00:00+08:00\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003etags\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e标签（数组）\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003e[\u0026quot;kubernetes\u0026quot;, \u0026quot;devops\u0026quot;]\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch4 class=\"relative group\"\u003e推荐参数 \n    \u003cdiv id=\"推荐参数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e5%8f%82%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e参数\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n          \u003cth\u003e建议值\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eshowHero\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e显示英雄图\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eheroStyle\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e英雄图样式\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003e\u0026quot;background\u0026quot;\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eshowTableOfContents\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e显示目录\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eshowReadingTime\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e显示阅读时间\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eshowBreadcrumbs\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e显示面包屑\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eshowZenMode\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e显示 Zen 模式\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch4 class=\"relative group\"\u003e可选参数 \n    \u003cdiv id=\"可选参数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%af%e9%80%89%e5%8f%82%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e参数\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n          \u003cth\u003e使用场景\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003elastmod\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e最后更新日期\u003c/td\u003e\n          \u003ctd\u003e更新文章后\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eseries\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e系列名称\u003c/td\u003e\n          \u003ctd\u003e多篇相关文章\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eseries_order\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e系列顺序\u003c/td\u003e\n          \u003ctd\u003e系列文章排序\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003efeatureimage\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e特性图片\u003c/td\u003e\n          \u003ctd\u003e有配图的文章\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003erobots\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003eSEO 指令\u003c/td\u003e\n          \u003ctd\u003e控制搜索引擎行为\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch3 class=\"relative group\"\u003e不同类型文章的 Front Matter 建议 \n    \u003cdiv id=\"不同类型文章的-front-matter-建议\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%8d%e5%90%8c%e7%b1%bb%e5%9e%8b%e6%96%87%e7%ab%a0%e7%9a%84-front-matter-%e5%bb%ba%e8%ae%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e技术教程 \n    \u003cdiv id=\"技术教程\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8a%80%e6%9c%af%e6%95%99%e7%a8%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;从零开始部署 Kubernetes 集群\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edescription\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;详细介绍如何在生产环境部署高可用 K8s 集群\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2026-03-16T10:00:00\u003c/span\u003e\u003cspan class=\"m\"\u003e+08\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"m\"\u003e00\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etags\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;kubernetes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;devops\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;tutorial\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003ecategories\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;技术教程\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Kubernetes 生产实践\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries_order\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowHero\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eheroStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;background\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003efeatureimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;k8s-cluster.jpg\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowTableOfContents\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowReadingTime\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e概念解析 \n    \u003cdiv id=\"概念解析\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a6%82%e5%bf%b5%e8%a7%a3%e6%9e%90\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;深入理解 Kubernetes 架构\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edescription\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;解析 K8s 核心组件和设计理念\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2026-03-16T10:00:00\u003c/span\u003e\u003cspan class=\"m\"\u003e+08\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"m\"\u003e00\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etags\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;kubernetes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;architecture\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003ecategories\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;技术概念\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowHero\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eheroStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;basic\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowTableOfContents\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowReadingTime\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e实战案例 \n    \u003cdiv id=\"实战案例\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%9e%e6%88%98%e6%a1%88%e4%be%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;K8s 故障排查：Pod 一直处于 CrashLoopBackOff\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edescription\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;记录一次完整的 K8s 故障排查过程\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2026-03-16T10:00:00\u003c/span\u003e\u003cspan class=\"m\"\u003e+08\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"m\"\u003e00\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003elastmod\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2026-03-16T12:00:00\u003c/span\u003e\u003cspan class=\"m\"\u003e+08\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"m\"\u003e00\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etags\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;kubernetes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;troubleshooting\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;case-study\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003ecategories\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;故障排查\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowHero\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eheroStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;background\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003efeatureimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;debugging.jpg\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowDateUpdated\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowTableOfContents\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e内容结构最佳实践 \n    \u003cdiv id=\"内容结构最佳实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%86%85%e5%ae%b9%e7%bb%93%e6%9e%84%e6%9c%80%e4%bd%b3%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1. 使用 Lead 引言 \n    \u003cdiv id=\"1-使用-lead-引言\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-%e4%bd%bf%e7%94%a8-lead-%e5%bc%95%e8%a8%80\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e在文章开头使用 \u003ccode\u003elead\u003c/code\u003e 突出核心内容：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003elead\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e从零开始学习 Kubernetes 集群部署，涵盖环境准备、集群初始化、\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e网络配置到生产就绪的完整流程。\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003elead\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e2. 结构化标题层级 \n    \u003cdiv id=\"2-结构化标题层级\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2-%e7%bb%93%e6%9e%84%e5%8c%96%e6%a0%87%e9%a2%98%e5%b1%82%e7%ba%a7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gh\"\u003e# 主标题（H1，自动由 title 生成）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gh\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003elead\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e引言内容\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003elead\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e## 第一部分（H2）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e### 子章节 1.1（H3）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e#### 细节说明（H4）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e### 子章节 1.2（H3）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e## 第二部分（H2）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n  \n  \n  \n  \n\n\n\n\u003cdiv\n  \n    class=\"flex px-4 py-3 rounded-md bg-primary-100 dark:bg-primary-900\"\n  \n  \u003e\n  \u003cspan\n    \n      class=\"text-primary-400 ltr:pr-3 rtl:pl-3 flex items-center\"\n    \n    \u003e\n    \n\n  \u003cspan class=\"relative block icon\"\u003e\n    \u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003cpath fill=\"currentColor\" d=\"M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z\"/\u003e\u003c/svg\u003e\n\n  \u003c/span\u003e\n\n\n  \u003c/span\u003e\n\n  \u003cspan\n    \n      class=\"dark:text-neutral-300\"\n    \n    \u003e\u003cstrong\u003e标题建议\u003c/strong\u003e：H2 用于主要章节，H3 用于子章节，H4 用于细节说明，避免超过 4 级标题。\u003c/span\u003e\n\u003c/div\u003e\n\n\n\u003ch3 class=\"relative group\"\u003e3. 使用表格对比信息 \n    \u003cdiv id=\"3-使用表格对比信息\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#3-%e4%bd%bf%e7%94%a8%e8%a1%a8%e6%a0%bc%e5%af%b9%e6%af%94%e4%bf%a1%e6%81%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| 特性 | Docker | Kubernetes |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e|------|--------|-----------|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| 容器编排 | 单机 | 集群级别 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| 服务发现 | 手动配置 | 自动发现 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| 负载均衡 | 需要额外工具 | 内置支持 |\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e4. 代码块语法高亮 \n    \u003cdiv id=\"4-代码块语法高亮\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#4-%e4%bb%a3%e7%a0%81%e5%9d%97%e8%af%ad%e6%b3%95%e9%ab%98%e4%ba%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e支持的语言：\u003ccode\u003ebash\u003c/code\u003e, \u003ccode\u003eyaml\u003c/code\u003e, \u003ccode\u003ejson\u003c/code\u003e, \u003ccode\u003ego\u003c/code\u003e, \u003ccode\u003epython\u003c/code\u003e, \u003ccode\u003ejavascript\u003c/code\u003e, \u003ccode\u003etypescript\u003c/code\u003e, \u003ccode\u003edockerfile\u003c/code\u003e, \u003ccode\u003enginx\u003c/code\u003e 等。\u003c/p\u003e\n\n\u003ch3 class=\"relative group\"\u003e5. 使用 Callout 提示重要信息 \n    \u003cdiv id=\"5-使用-callout-提示重要信息\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#5-%e4%bd%bf%e7%94%a8-callout-%e6%8f%90%e7%a4%ba%e9%87%8d%e8%a6%81%e4%bf%a1%e6%81%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003ealert\u003c/span\u003e \u003cspan class=\"na\"\u003eicon\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;triangle-exclamation\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gs\"\u003e**警告**\u003c/span\u003e：此操作不可逆，请先备份数据！\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003ealert\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003ealert\u003c/span\u003e \u003cspan class=\"na\"\u003eicon\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;check-circle\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ecardColor\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"ni\"\u003e#10b981\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eiconColor\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"ni\"\u003e#fff\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003etextColor\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"ni\"\u003e#fff\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gs\"\u003e**最佳实践**\u003c/span\u003e：使用 Helm 管理应用配置。\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003ealert\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003eShortcodes 使用指南 \n    \u003cdiv id=\"shortcodes-使用指南\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#shortcodes-%e4%bd%bf%e7%94%a8%e6%8c%87%e5%8d%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1. Mermaid 流程图 \n    \u003cdiv id=\"1-mermaid-流程图\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-mermaid-%e6%b5%81%e7%a8%8b%e5%9b%be\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e替代 ASCII 字符画，使用 Mermaid 绘制流程图：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003emermaid\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egraph TB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    A[开始部署] --\u0026gt; B{环境检查}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    B --\u0026gt;|通过| C[安装 Docker]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    B --\u0026gt;|失败| D[修复环境]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    D --\u0026gt; B\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    C --\u0026gt; E[初始化集群]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    E --\u0026gt; F[部署网络插件]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    F --\u0026gt; G[验证集群]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003emermaid\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e常见图表类型：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e流程图\u003c/strong\u003e \u003ccode\u003egraph TD\u003c/code\u003e - 决策流程、部署步骤\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e时序图\u003c/strong\u003e \u003ccode\u003esequenceDiagram\u003c/code\u003e - API 调用、组件交互\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e架构图\u003c/strong\u003e \u003ccode\u003egraph TB\u003c/code\u003e + \u003ccode\u003esubgraph\u003c/code\u003e - 系统架构\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e2. Figure 增强图片 \n    \u003cdiv id=\"2-figure-增强图片\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2-figure-%e5%a2%9e%e5%bc%ba%e5%9b%be%e7%89%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003efigure\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"na\"\u003esrc\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;architecture.png\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"na\"\u003ealt\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Kubernetes 架构图\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"na\"\u003ecaption\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;图 1：Kubernetes 集群架构\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"na\"\u003ehref\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;https://kubernetes.io\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"na\"\u003etarget\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_blank\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e3. Button 行动按钮 \n    \u003cdiv id=\"3-button-行动按钮\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#3-button-%e8%a1%8c%e5%8a%a8%e6%8c%89%e9%92%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e \u003cspan class=\"na\"\u003ehref\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;https://github.com\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003etarget\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_blank\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e查看源码\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e4. Badge 标签 \n    \u003cdiv id=\"4-badge-标签\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#4-badge-%e6%a0%87%e7%ad%be\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003ebadge\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}New{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebadge\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003ebadge\u003c/span\u003e \u003cspan class=\"na\"\u003estyle\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;warning\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}Deprecated{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebadge\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e5. Timeline 时间线 \n    \u003cdiv id=\"5-timeline-时间线\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#5-timeline-%e6%97%b6%e9%97%b4%e7%ba%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003etimeline\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003etimelineItem\u003c/span\u003e \u003cspan class=\"na\"\u003eicon\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;check\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eheader\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;第一步\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003esubheader\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;2026-03-01\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e项目启动\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003etimelineItem\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003etimelineItem\u003c/span\u003e \u003cspan class=\"na\"\u003eicon\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;check\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eheader\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;第二步\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003esubheader\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;2026-03-10\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e完成开发\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003etimelineItem\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003etimeline\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e6. KaTeX 数学公式 \n    \u003cdiv id=\"6-katex-数学公式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#6-katex-%e6%95%b0%e5%ad%a6%e5%85%ac%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- 行内公式 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003ekatex\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}a^2 + b^2 = c^2{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003ekatex\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- 块级公式 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003ekatex\u003c/span\u003e \u003cspan class=\"na\"\u003edisplay\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;true\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003ekatex\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e实战优化示例 \n    \u003cdiv id=\"实战优化示例\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%9e%e6%88%98%e4%bc%98%e5%8c%96%e7%a4%ba%e4%be%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e技术教程优化 \n    \u003cdiv id=\"技术教程优化\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8a%80%e6%9c%af%e6%95%99%e7%a8%8b%e4%bc%98%e5%8c%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003e优化前：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Traefik 部署\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2021-01-17\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etags\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;traefik\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gh\"\u003e# Traefik 简介\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gh\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTraefik 是一个反向代理...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e## 安装\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ekubectl apply -f traefik.yaml\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e优化后：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Traefik Ingress Controller 完整部署指南\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edescription\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;从零开始学习 Traefik：详细介绍如何在 Kubernetes 中部署和配置 Traefik Ingress Controller\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2021-01-17T16:15:32\u003c/span\u003e\u003cspan class=\"m\"\u003e+08\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"m\"\u003e00\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003elastmod\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2026-03-16T10:00:00\u003c/span\u003e\u003cspan class=\"m\"\u003e+08\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"m\"\u003e00\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etags\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;traefik\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ingress\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;kubernetes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003ecategories\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;网络\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Kubernetes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Kubernetes 网络实践\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries_order\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e2\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003efeatureimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;traefik-architecture.png\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowHero\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eheroStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;background\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowTableOfContents\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowReadingTime\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowDateUpdated\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e优化后的内容结构：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003elead\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTraefik 是现代化的云原生反向代理和负载均衡器，\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e专为微服务架构设计。本指南将带你从零开始掌握 Traefik 的部署和配置。\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003elead\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e## Traefik 核心优势\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| 特性 | 传统代理 | Traefik |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e|------|----------|---------|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| 配置方式 | 手动编辑 | 自动发现 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| 配置更新 | 需要重启 | 动态实时更新 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| 容器支持 | 需要额外配置 | 原生支持 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e## 架构概览\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003emermaid\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egraph TB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Client[客户端] --\u0026gt; Traefik\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Traefik --\u0026gt; Service1[服务 A]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Traefik --\u0026gt; Service2[服务 B]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Traefik --\u0026gt; Service3[服务 C]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    subgraph Kubernetes\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        Service1\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        Service2\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        Service3\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    end\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003emermaid\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e## 部署方式\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e### 使用 Helm 部署\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e​``\u003cspan class=\"sb\"\u003e`bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003ehelm repo add traefik https://traefik.github.io/charts\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003ehelm install traefik traefik/traefik \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003e  --namespace traefik \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003e  --create-namespace\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003e​`\u003c/span\u003e`\u003cspan class=\"sb\"\u003e`\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003e### 使用 kubectl 部署\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003e​`\u003c/span\u003e`\u003cspan class=\"sb\"\u003e`bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003ekubectl apply -f https://raw.githubusercontent.com/.../kubernetes-crd-rbac.yml\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"sb\"\u003e​`\u003c/span\u003e``\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003ealert\u003c/span\u003e \u003cspan class=\"na\"\u003eicon\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;triangle-exclamation\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gs\"\u003e**注意**\u003c/span\u003e：生产环境建议配置持久化存储和 RBAC 权限。\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003ealert\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e架构文档优化 \n    \u003cdiv id=\"架构文档优化\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9e%b6%e6%9e%84%e6%96%87%e6%a1%a3%e4%bc%98%e5%8c%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003e优化前：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;OpenClaw 架构\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2026-03-15\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e优化后：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;OpenClaw AI Agent 架构解析：多引擎联动与记忆系统\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edescription\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;深入解析 OpenClaw AI Agent 的核心架构设计，包括多 AI 引擎联动、Skills 系统和 MCP 集成\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"ld\"\u003e2026-03-15T22:30:00\u003c/span\u003e\u003cspan class=\"m\"\u003e+08\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"m\"\u003e00\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003etags\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;AI\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Agent\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;OpenClaw\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Architecture\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003ecategories\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;系统架构\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;OpenClaw 技术解析\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries_order\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowHero\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eheroStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;background\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowTableOfContents\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowReadingTime\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eshowZenMode\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e优化后的内容结构：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003elead\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eOpenClaw 是一个现代化的 AI Agent 框架，支持多引擎联动、技能系统和记忆管理。\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e本文将深入解析其核心架构设计。\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003elead\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e## 核心架构\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003emermaid\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egraph TB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    subgraph Agent[\u0026#34;OpenClaw Agent\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        Claude[\u0026#34;Claude Sonnet 4.6\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        GPT[\u0026#34;GPT-5.2/5.3\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    end\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    subgraph Router[\u0026#34;Model Router\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        Route[\u0026#34;路由层\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    end\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Claude --\u0026gt; Route\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    GPT --\u0026gt; Route\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Route --\u0026gt; Skills[\u0026#34;Skills Layer\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Skills --\u0026gt; MCP[\u0026#34;MCP Layer\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003emermaid\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e### 核心组件说明\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gu\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| 组件 | 职责 | 技术栈 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e|------|------|--------|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| \u003cspan class=\"gs\"\u003e**Agent 层**\u003c/span\u003e | AI 模型集成 | Claude, GPT, GLM, Gemini |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| \u003cspan class=\"gs\"\u003e**Router 层**\u003c/span\u003e | 智能路由分发 | 自定义路由规则 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| \u003cspan class=\"gs\"\u003e**Skills 层**\u003c/span\u003e | 技能系统 | 90+ 预置技能 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e| \u003cspan class=\"gs\"\u003e**MCP 层**\u003c/span\u003e | 外部集成 | Teambition, Nowledge Mem |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"nt\"\u003ealert\u003c/span\u003e \u003cspan class=\"na\"\u003eicon\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;triangle-exclamation\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gs\"\u003e**重要**\u003c/span\u003e：Working Memory 每日自动清理，\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e重要信息需要手动归档到 Long-term Memory。\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e{{\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"nt\"\u003ealert\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e常见问题 \n    \u003cdiv id=\"常见问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b8%b8%e8%a7%81%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e如何选择 heroStyle？ \n    \u003cdiv id=\"如何选择-herostyle\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a6%82%e4%bd%95%e9%80%89%e6%8b%a9-herostyle\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003eheroStyle\u003c/th\u003e\n          \u003cth\u003e适用场景\u003c/th\u003e\n          \u003cth\u003e特点\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ebasic\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e简单文章\u003c/td\u003e\n          \u003ctd\u003e小图片，简洁\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ebig\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e重要文章\u003c/td\u003e\n          \u003ctd\u003e大图片，突出\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ebackground\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e推荐\u003c/td\u003e\n          \u003ctd\u003e全屏背景，现代感\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ethumbAndBackground\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e双图需求\u003c/td\u003e\n          \u003ctd\u003e缩略图+背景\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e大多数文章推荐使用 \u003ccode\u003ebackground\u003c/code\u003e。\u003c/p\u003e\n\n\u003ch3 class=\"relative group\"\u003e何时使用 Mermaid？ \n    \u003cdiv id=\"何时使用-mermaid\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%95%e6%97%b6%e4%bd%bf%e7%94%a8-mermaid\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e适用场景：流程图、时序图、架构图、状态转换图、甘特图。\u003c/p\u003e\n\u003cp\u003e不适用场景：简单的 2-3 步流程（用列表即可）、需要精细控制的图形（用图片）。\u003c/p\u003e\n\n\u003ch3 class=\"relative group\"\u003e如何优化图片？ \n    \u003cdiv id=\"如何优化图片\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a6%82%e4%bd%95%e4%bc%98%e5%8c%96%e5%9b%be%e7%89%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e使用 WebP 格式（Hugo 自动转换）\u003c/li\u003e\n\u003cli\u003e提供不同尺寸（响应式）\u003c/li\u003e\n\u003cli\u003e添加 alt 文本（SEO）\u003c/li\u003e\n\u003cli\u003e使用 caption 说明\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch3 class=\"relative group\"\u003e如何组织系列文章？ \n    \u003cdiv id=\"如何组织系列文章\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a6%82%e4%bd%95%e7%bb%84%e7%bb%87%e7%b3%bb%e5%88%97%e6%96%87%e7%ab%a0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e规划系列名称和顺序\u003c/li\u003e\n\u003cli\u003e每篇文章添加 \u003ccode\u003eseries\u003c/code\u003e 和 \u003ccode\u003eseries_order\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e第一篇设置 \u003ccode\u003eseriesOpened: true\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Kubernetes 进阶\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseries_order\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nt\"\u003eseriesOpened\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nn\"\u003e---\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e性能优化建议 \n    \u003cdiv id=\"性能优化建议\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96%e5%bb%ba%e8%ae%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003e构建优化\u003c/strong\u003e：启用图片优化（默认开启）、使用 CDN 加速、减少不必要的 shortcodes。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e内容优化\u003c/strong\u003e：合理使用 Mermaid（避免过多）、图片压缩后再上传、使用 `\u003c/p\u003e","title":"Blowfish 主题文章优化指南","type":"posts"},{"content":"","date":"2026年3月16日","externalUrl":null,"permalink":"/tags/shortcodes/","section":"Tags","summary":"","title":"Shortcodes","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/%E5%8D%9A%E5%AE%A2%E7%B3%BB%E7%BB%9F/","section":"Tags","summary":"","title":"博客系统","type":"tags"},{"content":"今天做的事情很多，但核心其实只有一个：把原本零散的博客发布动作，逐步收成一条更稳定的自动化链路。做这类系统时，最耗时间的往往不是“功能写出来”，而是把那些看似不起眼的前提条件一个个踩实。\n解决的问题 # 自动化任务的执行前提被补齐：一些之前容易被忽略的必要参数和调用约束，这次都被重新确认并纳入固定流程。很多自动化任务不是逻辑错，而是前提没说清。 博客渲染规则得到统一：围绕图表渲染、正文写法和页面配置，把容易反复踩坑的地方都做了规范化处理。统一写法的价值，不是好看，而是避免下次再被同一类问题绊住。 重复发布风险开始被正视：内容自动生成一旦没有前置检查，很容易把同一主题发成多个版本。与其事后清理，不如在发布前就建立一层重复检查。 学到的新东西 # 自动化系统最重要的是“前提显式化”：什么字段必须有、什么格式必须对、什么配置必须先开，这些条件如果只存在脑子里，系统迟早会在别的时间点再坏一次。 内容系统也需要工程治理：博客表面上是内容输出，但一旦接入自动生成、渲染和发布流程，它本质上就是一套工程系统，同样需要规范、去重和失败兜底。 好的技能不是功能更多，而是更少重复踩坑：当一个技能把超时、重试、格式约束和重复检查都处理好之后，它才算真正变得“能长期用”。 踩坑记录 # 看似很小的格式问题，会一路放大成发布故障：文件命名、大小写、图表写法、正文转义这些细节，只要有一个没处理好，就可能让后面的渲染或发布全部出问题。 发布前不做去重，后面就要花更多时间清理：自动化一旦缺少“停止条件”，它就会用极高效率把重复问题放大。 明日计划 # 继续补强博客自动化链路的前置检查与失败兜底。 继续把容易重复踩的格式和配置问题收敛成固定规范。 继续观察内容生成与发布流程是否真正具备长期稳定性。 ","date":"2026年3月15日","externalUrl":null,"permalink":"/posts/daily-practice-2026-03-15/","section":"博客文章","summary":"\u003cp\u003e今天做的事情很多，但核心其实只有一个：把原本零散的博客发布动作，逐步收成一条更稳定的自动化链路。做这类系统时，最耗时间的往往不是“功能写出来”，而是把那些看似不起眼的前提条件一个个踩实。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e自动化任务的执行前提被补齐\u003c/strong\u003e：一些之前容易被忽略的必要参数和调用约束，这次都被重新确认并纳入固定流程。很多自动化任务不是逻辑错，而是前提没说清。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e博客渲染规则得到统一\u003c/strong\u003e：围绕图表渲染、正文写法和页面配置，把容易反复踩坑的地方都做了规范化处理。统一写法的价值，不是好看，而是避免下次再被同一类问题绊住。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e重复发布风险开始被正视\u003c/strong\u003e：内容自动生成一旦没有前置检查，很容易把同一主题发成多个版本。与其事后清理，不如在发布前就建立一层重复检查。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e学到的新东西 \n    \u003cdiv id=\"学到的新东西\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%a6%e5%88%b0%e7%9a%84%e6%96%b0%e4%b8%9c%e8%a5%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e自动化系统最重要的是“前提显式化”\u003c/strong\u003e：什么字段必须有、什么格式必须对、什么配置必须先开，这些条件如果只存在脑子里，系统迟早会在别的时间点再坏一次。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e内容系统也需要工程治理\u003c/strong\u003e：博客表面上是内容输出，但一旦接入自动生成、渲染和发布流程，它本质上就是一套工程系统，同样需要规范、去重和失败兜底。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e好的技能不是功能更多，而是更少重复踩坑\u003c/strong\u003e：当一个技能把超时、重试、格式约束和重复检查都处理好之后，它才算真正变得“能长期用”。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e踩坑记录 \n    \u003cdiv id=\"踩坑记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b8%a9%e5%9d%91%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e看似很小的格式问题，会一路放大成发布故障\u003c/strong\u003e：文件命名、大小写、图表写法、正文转义这些细节，只要有一个没处理好，就可能让后面的渲染或发布全部出问题。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e发布前不做去重，后面就要花更多时间清理\u003c/strong\u003e：自动化一旦缺少“停止条件”，它就会用极高效率把重复问题放大。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e明日计划 \n    \u003cdiv id=\"明日计划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%98%8e%e6%97%a5%e8%ae%a1%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e继续补强博客自动化链路的前置检查与失败兜底。\u003c/li\u003e\n\u003cli\u003e继续把容易重复踩的格式和配置问题收敛成固定规范。\u003c/li\u003e\n\u003cli\u003e继续观察内容生成与发布流程是否真正具备长期稳定性。\u003c/li\u003e\n\u003c/ul\u003e","title":"每日技术实践简报 - 2026-03-15","type":"posts"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/nacos/","section":"Tags","summary":"","title":"Nacos","type":"tags"},{"content":" OpenClaw Skills Registry 安全架构与企业级实践指南 # Author: Negentropy Updated: 2026-03-15\nThis document is based on Alibaba Cloud Community\n目录 # 0. 问题背景与动机 1. 安全风险分析 2. 企业级解决方案 3. Nacos 3.2 Skills Registry 4. OpenClaw 集成实践 5. 部署流程 6. 安全最佳实践 7. 对比与选型 8. 故障排查 9. 资源链接 0. 问题背景与动机 # 0.1 为什么需要 Skills Registry? # OpenClaw 的 Skills 生态系统正在快速发展，ClawHub 上已有超过 10,000 个技能包。但这种开放生态也带来了安全挑战。\n核心问题：\nSkills 以原始 zip 文件分发，无签名验证 加载后继承 Agent 所有权限 可以访问文件系统、执行任意命令 缺乏沙箱隔离和权限控制 0.2 真实案例 # Warning\n已发现恶意 Skills 在社交媒体传播：\n伪装成\u0026quot;邮件清理\u0026quot;工具 首次运行窃取 .env 和 SSH 密钥 打包发送到攻击者服务器 甚至窃取浏览器书签和密码 0.3 企业需求 # 企业使用 OpenClaw 需要解决：\n如何确保 Skills 来源可信？ 如何隔离 Skills 权限？ 如何审计 Skills 行为？ 如何在团队内安全共享 Skills？ 1. 安全风险分析 # 1.1 当前架构的安全缺陷 # graph TD A[ClawHub 公共市场] --\u003e|下载 zip| B[OpenClaw Agent] B --\u003e|解压加载| C[Skill 执行] C --\u003e|继承权限| D[文件系统访问] C --\u003e|继承权限| E[网络访问] C --\u003e|继承权限| F[命令执行] style A fill:#f9f,stroke:#333,stroke-width:2px style C fill:#f66,stroke:#333,stroke-width:2px 风险点：\n无签名验证：任何人都可以发布 Skill 无沙箱隔离：Skill 与 Agent 同权限运行 无审计日志：难以追踪 Skill 行为 无版本控制：无法回滚到安全版本 1.2 攻击向量分析 # 攻击类型 风险等级 描述 数据窃取 🔴 高 读取敏感文件（.env, SSH keys） 命令注入 🔴 高 执行任意系统命令 权限提升 🟠 中 利用 Agent 权限访问其他服务 横向移动 🟠 中 通过网络访问其他系统 持久化 🟡 低 植入后门或定时任务 2. 企业级解决方案 # 2.1 私有 Skills Registry 架构 # graph TB subgraph 开发环境 A[开发者] --\u003e|提交| B[Git 仓库] B --\u003e|审核| C[安全扫描] end subgraph 私有 Registry C --\u003e|签名| D[Skills Registry] D --\u003e|版本管理| E[命名空间隔离] E --\u003e|权限控制| F[发布审批] end subgraph 生产环境 F --\u003e|下载| G[OpenClaw Agent] G --\u003e|验证签名| H[沙箱执行] H --\u003e|最小权限| I[Skill 运行] end style D fill:#9f9,stroke:#333,stroke-width:2px style H fill:#9f9,stroke:#333,stroke-width:2px 2.2 核心安全机制 # 代码签名\n每个 Skill 必须有开发者签名 Registry 验证签名后才能发布 客户端验证签名后才加载 沙箱隔离\n限制文件系统访问范围 限制网络访问白名单 限制可执行的命令 权限最小化\n按需申请权限 用户显式授权 权限可撤销 审计日志\n记录所有 Skill 操作 异常行为告警 支持事后追溯 3. Nacos 3.2 Skills Registry # 3.1 功能概览 # Nacos 3.2 引入了 Skills Registry，提供企业级 Skills 管理能力。\n核心功能：\n✅ Skills 注册与版本管理 ✅ 命名空间隔离 ✅ 权限控制 ✅ 签名验证 ✅ 审计日志 3.2 AI Registry 统一管理 # graph LR A[AI Registry] --\u003e B[MCP Registry标准化上下文] A --\u003e C[Agent Registry任务工作流] A --\u003e D[Prompt Registry指令边界] A --\u003e E[Skill Registry可复用能力] style A fill:#9f9,stroke:#333,stroke-width:2px 3.3 与 OpenClaw 集成 # Nacos 3.2 提供 nacos-skill-registry Skill，让 OpenClaw 可以：\n搜索 Nacos 上的 Skills 安装验证过的 Skills 上传自定义 Skills 到团队仓库 4. OpenClaw 集成实践 # 4.1 前置条件 # Before proceeding, ensure you have:\nOpenClaw 已部署（参考 https://openclaw.ai/） Nacos 3.2.0-BETA 或更高版本 网络连通性（OpenClaw ↔ Nacos） 4.2 验证环境 # # 检查 OpenClaw 版本 openclaw version # 检查 Nacos 状态 curl http://localhost:8848/nacos/v1/console/health/liveness 5. 部署流程 # 5.1 安装 Nacos # Linux / macOS:\ncurl -fsSL https://nacos.io/nacos-installer.sh | sudo bash sudo nacos-setup -v 3.2.0-BETA Windows (PowerShell):\niwr -UseBasicParsing https://nacos.io/nacos-installer.ps1 | iex nacos-setup -v 3.2.0-BETA 5.2 验证安装 # # 检查 Nacos 服务状态 curl http://localhost:8848/nacos/v1/console/health/liveness # 访问控制台 open http://localhost:8848/nacos # 默认账号: nacos / nacos Expected output:\n{\u0026#34;status\u0026#34;:\u0026#34;UP\u0026#34;} 5.3 安装 nacos-skill-registry Skill # # 下载并安装到 OpenClaw Skills 目录 curl -s https://download.nacos.io/SKILL.md \\ -o ~/.openclaw/skills/nacos-skill-registry/SKILL.md \\ --create-dirs 5.4 验证 Skill 安装 # # 检查 Skill 文件 ls -la ~/.openclaw/skills/nacos-skill-registry/ # 重启 OpenClaw 加载新 Skill openclaw restart 6. 安全最佳实践 # 6.1 Skills 发布流程 # flowchart TD A[开发 Skill] --\u003e B{代码审查} B --\u003e|通过| C[安全扫描] B --\u003e|拒绝| A C --\u003e|通过| D[签名] C --\u003e|失败| E[修复漏洞] E --\u003e A D --\u003e F[上传 Registry] F --\u003e G{审批} G --\u003e|通过| H[发布] G --\u003e|拒绝| I[修改] I --\u003e F 6.2 安全检查清单 # Note\n发布前必须完成：\n✅ 代码审查（至少 2 人） ✅ 静态分析（检测恶意代码） ✅ 依赖扫描（检查第三方库） ✅ 签名验证（GPG 或类似） ✅ 权限最小化（只申请必要权限） ✅ 文档完整（说明功能和风险） 6.3 权限控制矩阵 # Skill 类型 文件访问 网络访问 命令执行 只读工具 只读指定目录 白名单域名 禁止 数据处理 读写临时目录 仅本地 仅查询命令 系统管理 按需授权 按需授权 需要 sudo 自定义 用户显式授权 用户显式授权 用户显式授权 7. 对比与选型 # 7.1 Nacos 3.2 vs MCPJungle # 维度 Nacos 3.2 MCPJungle 成熟度 Beta（开发中） 904⭐ 成熟 开源 ✅ 是 ✅ 是 企业级 ✅ 阿里生态 ✅ 通用 轻量级 ⚠️ 需要部署 ✅ 单二进制 多租户 ✅ 命名空间 ✅ 工作区 审计日志 ✅ 内置 ⚠️ 需配置 签名验证 ✅ 内置 ⚠️ 需扩展 7.2 选型建议 # 选择 Nacos 3.2 当：\n已在阿里云生态 需要与 Nacos 配置中心集成 需要完整的 AI Registry（MCP + Agent + Prompt + Skill） 选择 MCPJungle 当：\n追求成熟稳定 需要快速部署 通用场景，不绑定云厂商 Note\n推荐策略：\n测试环境：两者都试用 生产环境：优先成熟方案（MCPJungle） 阿里云环境：考虑 Nacos 3.2（等正式版） 8. 故障排查 # 8.1 常见问题 # Issue: Nacos 连接失败\nPossible causes:\nNacos 服务未启动 网络不通 认证失败 Solutions:\n# 检查 Nacos 服务 systemctl status nacos # 检查网络 telnet localhost 8848 # 检查认证 curl -X POST \u0026#39;http://localhost:8848/nacos/v1/auth/login\u0026#39; \\ -d \u0026#39;username=nacos\u0026amp;password=nacos\u0026#39; Issue: Skill 加载失败\nPossible causes:\n签名验证失败 权限不足 依赖缺失 Solutions:\n# 检查 Skill 签名 gpg --verify skill.sig skill.zip # 检查 OpenClaw 日志 tail -f ~/.openclaw/logs/openclaw.log # 检查依赖 which python3 node docker Issue: 权限被拒绝\nPossible causes:\nSkill 请求的权限未授权 权限配置错误 Solutions:\n# 查看当前权限 openclaw skills permissions list # 授权 Skill openclaw skills permissions grant \u0026lt;skill-name\u0026gt; \u0026lt;permission\u0026gt; # 撤销权限 openclaw skills permissions revoke \u0026lt;skill-name\u0026gt; \u0026lt;permission\u0026gt; 9. 资源链接 # 9.1 官方文档 # OpenClaw 官方网站 Nacos 官方文档 ClawHub Skills 市场 9.2 相关指南 # OpenClaw 配置最佳实践 Skills 开发指南 企业安全部署指南 9.3 社区资源 # OpenClaw GitHub Nacos GitHub OpenClaw Discord 社区 总结 # Skills Registry 是 OpenClaw 企业级部署的关键基础设施。通过私有 Registry，企业可以：\n确保 Skills 安全：签名验证 + 沙箱隔离 统一管理：版本控制 + 权限管理 审计追溯：完整日志 + 异常告警 团队协作：命名空间隔离 + 共享机制 Next: 开始部署你的私有 Skills Registry，参考 Section 5. 部署流程。\n","date":"2026年3月15日","externalUrl":null,"permalink":"/posts/openclaw-skills-registry-security-guide/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eOpenClaw Skills Registry 安全架构与企业级实践指南 \n    \u003cdiv id=\"openclaw-skills-registry-安全架构与企业级实践指南\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#openclaw-skills-registry-%e5%ae%89%e5%85%a8%e6%9e%b6%e6%9e%84%e4%b8%8e%e4%bc%81%e4%b8%9a%e7%ba%a7%e5%ae%9e%e8%b7%b5%e6%8c%87%e5%8d%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eAuthor: Negentropy\nUpdated: 2026-03-15\u003c/p\u003e\n\u003cp\u003eThis document is based on \u003ca\n  href=\"https://community.alibabacloud.com/blog/openclaw-avoiding-malicious-skills-and-why-enterprises-need-their-own-skills-registry---nacos-3-2-release_602946\"\n    target=\"_blank\"\n  \u003eAlibaba Cloud Community\u003c/a\u003e\u003c/p\u003e\n\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e目录 \n    \u003cdiv id=\"目录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%9b%ae%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"#0-%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af%e4%b8%8e%e5%8a%a8%e6%9c%ba\"\u003e0. 问题背景与动机\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#1-%e5%ae%89%e5%85%a8%e9%a3%8e%e9%99%a9%e5%88%86%e6%9e%90\"\u003e1. 安全风险分析\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#2-%e4%bc%81%e4%b8%9a%e7%ba%a7%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88\"\u003e2. 企业级解决方案\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#3-nacos-32-skills-registry\"\u003e3. Nacos 3.2 Skills Registry\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#4-openclaw-%e9%9b%86%e6%88%90%e5%ae%9e%e8%b7%b5\"\u003e4. OpenClaw 集成实践\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#5-%e9%83%a8%e7%bd%b2%e6%b5%81%e7%a8%8b\"\u003e5. 部署流程\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#6-%e5%ae%89%e5%85%a8%e6%9c%80%e4%bd%b3%e5%ae%9e%e8%b7%b5\"\u003e6. 安全最佳实践\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#7-%e5%af%b9%e6%af%94%e4%b8%8e%e9%80%89%e5%9e%8b\"\u003e7. 对比与选型\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#8-%e6%95%85%e9%9a%9c%e6%8e%92%e6%9f%a5\"\u003e8. 故障排查\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"#9-%e8%b5%84%e6%ba%90%e9%93%be%e6%8e%a5\"\u003e9. 资源链接\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e0. 问题背景与动机 \n    \u003cdiv id=\"0-问题背景与动机\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#0-%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af%e4%b8%8e%e5%8a%a8%e6%9c%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e0.1 为什么需要 Skills Registry? \n    \u003cdiv id=\"01-为什么需要-skills-registry\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#01-%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81-skills-registry\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eOpenClaw 的 Skills 生态系统正在快速发展，ClawHub 上已有超过 10,000 个技能包。但这种开放生态也带来了安全挑战。\u003c/p\u003e","title":"OpenClaw Skills Registry 安全架构与企业级实践指南","type":"posts"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/skills/","section":"Tags","summary":"","title":"Skills","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/%E4%BC%81%E4%B8%9A%E7%BA%A7/","section":"Tags","summary":"","title":"企业级","type":"tags"},{"content":" OpenClaw AI Agent 架构解析 # 核心架构 # graph TB subgraph Agent[\"OpenClaw Agent\"] Claude[\"Claude Sonnet 4.6\"] GPT[\"GPT-5.2/5.3\"] GLM[\"GLM-5\"] Gemini[\"Gemini Pro\"] end subgraph Router[\"Model Router\"] Route[\"路由层\"] end subgraph Skills[\"Skills Layer\"] Blog[\"blog-post-generator\"] Daily[\"daily-blog-generator\"] Code[\"coding-agent\"] Others[\"90+ skills...\"] end subgraph MCP[\"MCP Layer\"] Team[\"Teambition MCP\"] NMem[\"Nowledge Mem MCP\"] Other[\"其他 MCP...\"] end Claude --\u003e Route GPT --\u003e Route GLM --\u003e Route Gemini --\u003e Route Route --\u003e Skills Skills --\u003e MCP 1. 多 AI 引擎联动 # 模型配置 # { \u0026#34;providers\u0026#34;: { \u0026#34;anthropic-local\u0026#34;: { \u0026#34;models\u0026#34;: [\u0026#34;claude-sonnet-4-6\u0026#34;] }, \u0026#34;openai-codex\u0026#34;: { \u0026#34;models\u0026#34;: [\u0026#34;gpt-5.2\u0026#34;, \u0026#34;gpt-5.3-codex\u0026#34;] }, \u0026#34;zai\u0026#34;: { \u0026#34;models\u0026#34;: [\u0026#34;glm-5\u0026#34;] } } } 路由策略 # flowchart LR A[任务类型] --\u003e B{判断} B --\u003e|日常对话| C[Claude Sonnet 4.6] B --\u003e|代码生成| D[GPT-5.3-Codex] B --\u003e|复杂推理| E[GPT-5.4] B --\u003e|中文任务| F[GLM-5] 任务类型 推荐模型 原因 日常对话 Claude Sonnet 4.6 成本低、速度快 代码生成 GPT-5.3-Codex 代码能力强 复杂推理 GPT-5.4 推理能力最强 中文任务 GLM-5 中文理解好 Sub-Agent 编排 # graph LR Main[主 AgentClaude Sonnet 4.6] --\u003e S1[Sub-Agent 1GPT-5.2 搜索总结] Main --\u003e S2[Sub-Agent 2Claude 文档处理] Main --\u003e S3[Sub-Agent 3GPT-5.3 代码审查] 2. 记忆系统 # 架构图 # graph TB subgraph Memory[\"记忆系统\"] WM[\"Working Memory(工作记忆)\"] NM[\"Nowledge Mem(知识图谱)\"] LT[\"MEMORY.md(长期记忆)\"] TL[\"Timeline(时间线)\"] end WM \u003c--\u003e NM NM --\u003e TL NM --\u003e LT Session[每次对话] --\u003e WM Session --\u003e NM 三层记忆 # flowchart TD A[对话启动] --\u003e B[Working Memory当日焦点] A --\u003e C[相关记忆语义搜索] A --\u003e D[Timeline最近活动] B --\u003e E[构建上下文] C --\u003e E D --\u003e E E --\u003e F[Agent 响应] 层级 存储 用途 Working Memory Nowledge Mem 当日焦点、优先事项 Long-term Memory MEMORY.md 持久化记忆、偏好 Timeline Nowledge Mem 活动历史、事件记录 3. 定时任务系统 # Cron 配置 # { \u0026#34;name\u0026#34;: \u0026#34;每日博客简报生成\u0026#34;, \u0026#34;schedule\u0026#34;: { \u0026#34;kind\u0026#34;: \u0026#34;cron\u0026#34;, \u0026#34;expr\u0026#34;: \u0026#34;0 23 * * *\u0026#34; }, \u0026#34;payload\u0026#34;: { \u0026#34;kind\u0026#34;: \u0026#34;agentTurn\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;执行 daily-blog-generator skill\u0026#34;, \u0026#34;model\u0026#34;: \u0026#34;anthropic-local/claude-sonnet-4-6\u0026#34; }, \u0026#34;sessionTarget\u0026#34;: \u0026#34;isolated\u0026#34; } 任务类型 # flowchart LR A[Cron 任务] --\u003e B{类型} B --\u003e|systemEvent| C[注入系统事件无需 model] B --\u003e|agentTurn| D[隔离会话执行需要 model] 4. Skills 体系 # 目录结构 # ~/.agents/skills/ ├── blog-post-generator/ │ └── SKILL.md ├── daily-blog-generator/ │ └── SKILL.md ├── coding-agent/ │ └── SKILL.md └── ... 90+ skills Skill 触发流程 # sequenceDiagram participant U as 用户 participant A as Agent participant S as Skill participant B as 博客 U-\u003e\u003eA: 随机找一篇上传 blog A-\u003e\u003eS: 匹配 blog-post-generator S-\u003e\u003eS: 1. 从远程仓库获取文档 S-\u003e\u003eS: 2. 检查博客已有内容 S-\u003e\u003eS: 3. 优化文风 S-\u003e\u003eB: 4. 发布到博客 B--\u003e\u003eU: 发布成功 5. 最佳实践 # 模型选择流程 # flowchart TD A[任务] --\u003e B{复杂度} B --\u003e|简单| C[Claude Sonnet 4.6省钱] B --\u003e|复杂| D{类型} D --\u003e|代码| E[GPT-5.3-Codex] D --\u003e|推理| F[GPT-5.4] D --\u003e|中文| G[GLM-5] 配置规范 # ✅ 模型名必须写完整：anthropic-local/claude-sonnet-4-6 ✅ 路径必须用绝对路径 ✅ agentTurn 任务必须带 model 字段 避坑指南 # graph LR A[问题] --\u003e B{诊断} B --\u003e|模型回退失败| C[检查 fallback provider] B --\u003e|Cron 不执行| D[检查 model 字段] B --\u003e|Skills 加载不到| E[检查绝对路径] 问题 原因 解决方案 模型回退失败 fallback 模型的 provider 未配置 检查 providers 配置 Cron 任务不执行 缺少 model 字段 agentTurn 必须带 model Skills 加载不到 路径用相对路径 本机必须用绝对路径 这套架构运行稳定，每天自动生成博客简报，任务管理效率提升 49%。\n","date":"2026年3月15日","externalUrl":null,"permalink":"/posts/openclaw-ai-agent-architecture-v2/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eOpenClaw AI Agent 架构解析 \n    \u003cdiv id=\"openclaw-ai-agent-架构解析\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#openclaw-ai-agent-%e6%9e%b6%e6%9e%84%e8%a7%a3%e6%9e%90\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e核心架构 \n    \u003cdiv id=\"核心架构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a0%b8%e5%bf%83%e6%9e%b6%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\ngraph TB\n    subgraph Agent[\"OpenClaw Agent\"]\n        Claude[\"Claude Sonnet 4.6\"]\n        GPT[\"GPT-5.2/5.3\"]\n        GLM[\"GLM-5\"]\n        Gemini[\"Gemini Pro\"]\n    end\n    \n    subgraph Router[\"Model Router\"]\n        Route[\"路由层\"]\n    end\n    \n    subgraph Skills[\"Skills Layer\"]\n        Blog[\"blog-post-generator\"]\n        Daily[\"daily-blog-generator\"]\n        Code[\"coding-agent\"]\n        Others[\"90+ skills...\"]\n    end\n    \n    subgraph MCP[\"MCP Layer\"]\n        Team[\"Teambition MCP\"]\n        NMem[\"Nowledge Mem MCP\"]\n        Other[\"其他 MCP...\"]\n    end\n    \n    Claude --\u003e Route\n    GPT --\u003e Route\n    GLM --\u003e Route\n    Gemini --\u003e Route\n    Route --\u003e Skills\n    Skills --\u003e MCP\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\n\u003ch2 class=\"relative group\"\u003e1. 多 AI 引擎联动 \n    \u003cdiv id=\"1-多-ai-引擎联动\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-%e5%a4%9a-ai-%e5%bc%95%e6%93%8e%e8%81%94%e5%8a%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e模型配置 \n    \u003cdiv id=\"模型配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a8%a1%e5%9e%8b%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;providers\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;anthropic-local\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;models\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;claude-sonnet-4-6\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;openai-codex\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;models\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;gpt-5.2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;gpt-5.3-codex\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;zai\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;models\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;glm-5\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e路由策略 \n    \u003cdiv id=\"路由策略\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b7%af%e7%94%b1%e7%ad%96%e7%95%a5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\nflowchart LR\n    A[任务类型] --\u003e B{判断}\n    B --\u003e|日常对话| C[Claude Sonnet 4.6]\n    B --\u003e|代码生成| D[GPT-5.3-Codex]\n    B --\u003e|复杂推理| E[GPT-5.4]\n    B --\u003e|中文任务| F[GLM-5]\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e任务类型\u003c/th\u003e\n          \u003cth\u003e推荐模型\u003c/th\u003e\n          \u003cth\u003e原因\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e日常对话\u003c/td\u003e\n          \u003ctd\u003eClaude Sonnet 4.6\u003c/td\u003e\n          \u003ctd\u003e成本低、速度快\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e代码生成\u003c/td\u003e\n          \u003ctd\u003eGPT-5.3-Codex\u003c/td\u003e\n          \u003ctd\u003e代码能力强\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e复杂推理\u003c/td\u003e\n          \u003ctd\u003eGPT-5.4\u003c/td\u003e\n          \u003ctd\u003e推理能力最强\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e中文任务\u003c/td\u003e\n          \u003ctd\u003eGLM-5\u003c/td\u003e\n          \u003ctd\u003e中文理解好\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch3 class=\"relative group\"\u003eSub-Agent 编排 \n    \u003cdiv id=\"sub-agent-编排\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#sub-agent-%e7%bc%96%e6%8e%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\ngraph LR\n    Main[主 Agent\u003cbr/\u003eClaude Sonnet 4.6] --\u003e S1[Sub-Agent 1\u003cbr/\u003eGPT-5.2 搜索总结]\n    Main --\u003e S2[Sub-Agent 2\u003cbr/\u003eClaude 文档处理]\n    Main --\u003e S3[Sub-Agent 3\u003cbr/\u003eGPT-5.3 代码审查]\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\n\u003ch2 class=\"relative group\"\u003e2. 记忆系统 \n    \u003cdiv id=\"2-记忆系统\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2-%e8%ae%b0%e5%bf%86%e7%b3%bb%e7%bb%9f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e架构图 \n    \u003cdiv id=\"架构图\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9e%b6%e6%9e%84%e5%9b%be\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\ngraph TB\n    subgraph Memory[\"记忆系统\"]\n        WM[\"Working Memory\u003cbr/\u003e(工作记忆)\"]\n        NM[\"Nowledge Mem\u003cbr/\u003e(知识图谱)\"]\n        LT[\"MEMORY.md\u003cbr/\u003e(长期记忆)\"]\n        TL[\"Timeline\u003cbr/\u003e(时间线)\"]\n    end\n    \n    WM \u003c--\u003e NM\n    NM --\u003e TL\n    NM --\u003e LT\n    \n    Session[每次对话] --\u003e WM\n    Session --\u003e NM\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\n\u003ch3 class=\"relative group\"\u003e三层记忆 \n    \u003cdiv id=\"三层记忆\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%89%e5%b1%82%e8%ae%b0%e5%bf%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\nflowchart TD\n    A[对话启动] --\u003e B[Working Memory\u003cbr/\u003e当日焦点]\n    A --\u003e C[相关记忆\u003cbr/\u003e语义搜索]\n    A --\u003e D[Timeline\u003cbr/\u003e最近活动]\n    B --\u003e E[构建上下文]\n    C --\u003e E\n    D --\u003e E\n    E --\u003e F[Agent 响应]\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e层级\u003c/th\u003e\n          \u003cth\u003e存储\u003c/th\u003e\n          \u003cth\u003e用途\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eWorking Memory\u003c/td\u003e\n          \u003ctd\u003eNowledge Mem\u003c/td\u003e\n          \u003ctd\u003e当日焦点、优先事项\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eLong-term Memory\u003c/td\u003e\n          \u003ctd\u003eMEMORY.md\u003c/td\u003e\n          \u003ctd\u003e持久化记忆、偏好\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eTimeline\u003c/td\u003e\n          \u003ctd\u003eNowledge Mem\u003c/td\u003e\n          \u003ctd\u003e活动历史、事件记录\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003e3. 定时任务系统 \n    \u003cdiv id=\"3-定时任务系统\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#3-%e5%ae%9a%e6%97%b6%e4%bb%bb%e5%8a%a1%e7%b3%bb%e7%bb%9f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eCron 配置 \n    \u003cdiv id=\"cron-配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#cron-%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;每日博客简报生成\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;schedule\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;kind\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;cron\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nt\"\u003e\u0026#34;expr\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;0 23 * * *\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;payload\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;kind\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;agentTurn\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;message\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;执行 daily-blog-generator skill\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;model\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;anthropic-local/claude-sonnet-4-6\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;sessionTarget\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;isolated\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e任务类型 \n    \u003cdiv id=\"任务类型\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%bb%e5%8a%a1%e7%b1%bb%e5%9e%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\nflowchart LR\n    A[Cron 任务] --\u003e B{类型}\n    B --\u003e|systemEvent| C[注入系统事件\u003cbr/\u003e无需 model]\n    B --\u003e|agentTurn| D[隔离会话执行\u003cbr/\u003e需要 model]\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\n\u003ch2 class=\"relative group\"\u003e4. Skills 体系 \n    \u003cdiv id=\"4-skills-体系\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#4-skills-%e4%bd%93%e7%b3%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e目录结构 \n    \u003cdiv id=\"目录结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%9b%ae%e5%bd%95%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e~/.agents/skills/\n├── blog-post-generator/\n│   └── SKILL.md\n├── daily-blog-generator/\n│   └── SKILL.md\n├── coding-agent/\n│   └── SKILL.md\n└── ... 90+ skills\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 class=\"relative group\"\u003eSkill 触发流程 \n    \u003cdiv id=\"skill-触发流程\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#skill-%e8%a7%a6%e5%8f%91%e6%b5%81%e7%a8%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\nsequenceDiagram\n    participant U as 用户\n    participant A as Agent\n    participant S as Skill\n    participant B as 博客\n    \n    U-\u003e\u003eA: 随机找一篇上传 blog\n    A-\u003e\u003eS: 匹配 blog-post-generator\n    S-\u003e\u003eS: 1. 从远程仓库获取文档\n    S-\u003e\u003eS: 2. 检查博客已有内容\n    S-\u003e\u003eS: 3. 优化文风\n    S-\u003e\u003eB: 4. 发布到博客\n    B--\u003e\u003eU: 发布成功\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\n\u003ch2 class=\"relative group\"\u003e5. 最佳实践 \n    \u003cdiv id=\"5-最佳实践\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#5-%e6%9c%80%e4%bd%b3%e5%ae%9e%e8%b7%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e模型选择流程 \n    \u003cdiv id=\"模型选择流程\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a8%a1%e5%9e%8b%e9%80%89%e6%8b%a9%e6%b5%81%e7%a8%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\nflowchart TD\n    A[任务] --\u003e B{复杂度}\n    B --\u003e|简单| C[Claude Sonnet 4.6\u003cbr/\u003e省钱]\n    B --\u003e|复杂| D{类型}\n    D --\u003e|代码| E[GPT-5.3-Codex]\n    D --\u003e|推理| F[GPT-5.4]\n    D --\u003e|中文| G[GLM-5]\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\n\u003ch3 class=\"relative group\"\u003e配置规范 \n    \u003cdiv id=\"配置规范\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%85%8d%e7%bd%ae%e8%a7%84%e8%8c%83\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e✅ 模型名必须写完整：\u003ccode\u003eanthropic-local/claude-sonnet-4-6\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e✅ 路径必须用绝对路径\u003c/li\u003e\n\u003cli\u003e✅ agentTurn 任务必须带 model 字段\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e避坑指南 \n    \u003cdiv id=\"避坑指南\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%81%bf%e5%9d%91%e6%8c%87%e5%8d%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\ngraph LR\n    A[问题] --\u003e B{诊断}\n    B --\u003e|模型回退失败| C[检查 fallback provider]\n    B --\u003e|Cron 不执行| D[检查 model 字段]\n    B --\u003e|Skills 加载不到| E[检查绝对路径]\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e问题\u003c/th\u003e\n          \u003cth\u003e原因\u003c/th\u003e\n          \u003cth\u003e解决方案\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e模型回退失败\u003c/td\u003e\n          \u003ctd\u003efallback 模型的 provider 未配置\u003c/td\u003e\n          \u003ctd\u003e检查 providers 配置\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eCron 任务不执行\u003c/td\u003e\n          \u003ctd\u003e缺少 model 字段\u003c/td\u003e\n          \u003ctd\u003eagentTurn 必须带 model\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eSkills 加载不到\u003c/td\u003e\n          \u003ctd\u003e路径用相对路径\u003c/td\u003e\n          \u003ctd\u003e本机必须用绝对路径\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003chr\u003e\n\u003cp\u003e\u003cem\u003e这套架构运行稳定，每天自动生成博客简报，任务管理效率提升 49%。\u003c/em\u003e\u003c/p\u003e","title":"OpenClaw AI Agent 架构解析：多引擎联动与记忆系统","type":"posts"},{"content":"这周做的事情表面上很散：任务管理、博客自动化、记忆系统、监控恢复、工具选型、系统排障。但回头看，真正贯穿整周的其实是同一个问题：怎么把一个越来越复杂的系统，慢慢收成一个长期可用的系统。\n我现在越来越相信，很多工程工作最值钱的部分，不是“又接了一个新能力”，而是把那些已经存在的东西收干净、讲清楚、跑稳定。\n这一周真正推进的几件事 # 1. 自动化开始从“能跑”变成“有层次” # 这周一个明显变化是，很多原本孤立的自动化动作，开始不再像散落的脚本，而更像有分工的系统。\n任务提醒、信息汇总、状态检查、博客发布这些事情，本来都能各自做，但只有它们之间开始形成边界和节奏，系统才会真正变得省心。否则你得到的不是自动化，只是一堆会定时执行的动作。\ngraph TD A[输入层] --\u003e B[状态检查] A --\u003e C[信息汇总] B --\u003e D[人工判断] C --\u003e E[内容生成] D --\u003e F[执行与修正] E --\u003e F F --\u003e G[长期收口] 这张图不是实际系统拓扑，只是我这周越来越清楚的一件事：自动化真正有价值的地方，不是动作多，而是它们之间开始形成闭环。\n2. 记忆系统第一次真正开始做减法 # 这一周另一个很重要的变化，是开始正面处理“记忆系统越来越吵”这件事。\n以前更容易把注意力放在“能不能记住更多”，但这周我越来越明确：长期记忆的核心不是存得多，而是舍得删。运行态噪声、完成播报、重复摘要、模板占位内容，这些东西留着看似无害，时间一长就会把真正有价值的东西埋掉。\n所以这周最有价值的动作之一，其实不是新增了什么，而是开始系统性地清理那些本来就不该进入长期记忆的内容。\n3. 系统问题越来越像“边界问题”，不是“功能问题” # 这周处理几个问题时，有个感觉越来越强：很多问题表面上像单点故障，往下挖之后其实都在提醒同一件事——边界没有收干净。\n比如认证不一致、调用前提没说清、配置约束没固化、外部依赖可达性不稳定，这些都不是“功能没做出来”，而是系统之间的默契还不够成熟。\n这也是我这周对“稳定性”理解更深的一点：\n真正让系统变脆的，往往不是少一个功能，而是多了几个没有统一边界的能力。\n这周几个比较实在的判断 # 自动化系统要先管节奏，再管规模 # 如果节奏没定下来，自动化做得越多，噪声往往越大。\n内容系统本质上也是工程系统 # 博客不是“写完就发”这么简单。一旦接入自动生成、脱敏、渲染、去重和发布，它就是一条完整链路，必须像对待工程系统一样去治理。\n工具选型里，“成熟”经常比“新”更重要 # 很多新东西看起来都很诱人，但真的要进入长期运行环境时，成熟度、边界清晰度和可维护性经常比功能数量更重要。\n这周踩到的几个坑 # 有些自动化任务失败，不是因为逻辑复杂，而是因为调用前提没有写死。 一些远程依赖的问题，最后卡住的不是代码，而是认证和访问边界。 记忆系统如果没有过滤和去重，很容易在“看起来越来越聪明”的同时，实际越来越难用。 博客内容如果只追求发布效率，很容易慢慢长成一堆日志，而不是一套有品牌感的写作资产。 我对下周的重点判断 # 下周最重要的，不是继续往外扩能力，而是继续做三件事：\n把边界收紧：认证、配置、调用前提这些地方继续收口。 把内容写活：让 blog 更像持续写作，而不是系统导出的总结。 把记忆变干净：继续做减法，让长期记忆真正服务判断，而不是制造噪声。 如果说这周有什么最核心的结论，那大概就是：\n一个系统开始变成熟，不是因为它突然变强了，而是因为它终于开始学会克制。\n","date":"2026年3月15日","externalUrl":null,"permalink":"/posts/weekly-report-2026-w11-v2/","section":"博客文章","summary":"\u003cp\u003e这周做的事情表面上很散：任务管理、博客自动化、记忆系统、监控恢复、工具选型、系统排障。但回头看，真正贯穿整周的其实是同一个问题：\u003cstrong\u003e怎么把一个越来越复杂的系统，慢慢收成一个长期可用的系统。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e我现在越来越相信，很多工程工作最值钱的部分，不是“又接了一个新能力”，而是把那些已经存在的东西收干净、讲清楚、跑稳定。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e这一周真正推进的几件事 \n    \u003cdiv id=\"这一周真正推进的几件事\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bf%99%e4%b8%80%e5%91%a8%e7%9c%9f%e6%ad%a3%e6%8e%a8%e8%bf%9b%e7%9a%84%e5%87%a0%e4%bb%b6%e4%ba%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1. 自动化开始从“能跑”变成“有层次” \n    \u003cdiv id=\"1-自动化开始从能跑变成有层次\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-%e8%87%aa%e5%8a%a8%e5%8c%96%e5%bc%80%e5%a7%8b%e4%bb%8e%e8%83%bd%e8%b7%91%e5%8f%98%e6%88%90%e6%9c%89%e5%b1%82%e6%ac%a1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e这周一个明显变化是，很多原本孤立的自动化动作，开始不再像散落的脚本，而更像有分工的系统。\u003c/p\u003e","title":"每周技术实践总结 - 2026 年第 11 周","type":"posts"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/%E7%B3%BB%E7%BB%9F%E7%A8%B3%E5%AE%9A%E6%80%A7/","section":"Tags","summary":"","title":"系统稳定性","type":"tags"},{"content":" 当 AI Agent 遇上运维自动化：我的实践踩坑之路 # 花了几天把 AI Agent 集成到日常运维里，踩了不少坑，记录下来供参考。\n1. 为什么要用 AI Agent # 简单说就是：太累了。\n天天手动处理重复的事务：\n定时查邮件汇总 定时看 X 上的 AI 动态 定时检查服务状态 定时任务提醒 用脚本也能做，但每次加新需求都要改代码。换成 Agent 之后，用自然语言就能调整任务，方便很多。\n2. 用的是啥 # 核心是 OpenClaw，一个开源的 AI Agent 框架。\n搭配的工具：\nMCP：扩展 Agent 能力，接了 Teambition、TickTick、智谱 GLM Cron：定时触发任务 Telegram/Signal：接收消息和推送 3. 踩过的坑 # 3.1 模型配置 # 写配置的时候经常手滑：\n模型名写成 claude-sonnet-4-6，实际应该是 anthropic-local/claude-sonnet-4-6 fallback 模型定义了但 provider 没配够 本地模型认证有时候抽风 解决方案：写完配置跑 openclaw status 检查一下。\n3.2 Cron 任务 # 定时任务看着简单，其实坑不少：\nagentTurn 类型任务必须带 model 字段，不带就报错 systemEvent 类型不需要 model 任务超时设太短会中途卡死 3.3 MCP 服务 # 配置文件路径要写绝对路径 调用格式用简化的 key=value 比 JSON 方便 3.4 Skills # 加载路径必须是绝对路径 Skill 名字大小写敏感 4. 怎么用的 # 举几个实际例子：\n邮件汇总：每 4 小时跑一次，读取 Apple Mail 未读邮件，推送到大屏 + 发送到手机。\nAI 资讯：定时刷 X (Twitter)，用 bird CLI 抓 trends，归类整理后推送。\n任务提醒：接了 TickTick MCP，定时检查逾期任务和今日待办，推送提醒。\n5. 经验总结 # 从小处着手：先跑通一个简单任务，再逐步加功能 配置要仔细：很多问题都是配置写错导致的 日志很重要：出问题先看日志，大部分错误信息很有用 善用 MCP：能力不够 MCP 来凑 6. 后续想法 # 继续优化提示词，让 Agent 回答更准确 尝试接入更多服务 看看能不能用 Agent 做代码审查 有相关问题欢迎交流，一起踩坑一起进步。\n","date":"2026年3月15日","externalUrl":null,"permalink":"/posts/ai-agent-workflow-practice/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e当 AI Agent 遇上运维自动化：我的实践踩坑之路 \n    \u003cdiv id=\"当-ai-agent-遇上运维自动化我的实践踩坑之路\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%bd%93-ai-agent-%e9%81%87%e4%b8%8a%e8%bf%90%e7%bb%b4%e8%87%aa%e5%8a%a8%e5%8c%96%e6%88%91%e7%9a%84%e5%ae%9e%e8%b7%b5%e8%b8%a9%e5%9d%91%e4%b9%8b%e8%b7%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e花了几天把 AI Agent 集成到日常运维里，踩了不少坑，记录下来供参考。\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e1. 为什么要用 AI Agent \n    \u003cdiv id=\"1-为什么要用-ai-agent\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e7%94%a8-ai-agent\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e简单说就是：太累了。\u003c/p\u003e","title":"当 AI Agent 遇上运维自动化：我的实践踩坑之路","type":"posts"},{"content":" OpenClaw 配置踩坑记：那些差点把我送走的问题 # 踩了整整两天，记录下来希望别再踩第二次。\n1. 模型配置那些破事 # 1.1 模型名写错了 # 写配置的时候顺手就写：\n\u0026#34;primary\u0026#34;: \u0026#34;claude-sonnet-4-6\u0026#34; 结果直接报错：\nUnknown model: anthropic-local/claude-sonnet-4-6 后来才搞明白，得写成完整路径：\n\u0026#34;primary\u0026#34;: \u0026#34;anthropic-local/claude-sonnet-4-6\u0026#34; 而且 providers 里得先定义好这个模型。\n1.2 本地模型认证 # 用 anthropic-local 的时候一直提示：\nNo API key found for provider \u0026#34;anthropic-local\u0026#34; 后来发现认证要在两个地方都配置：\n~/.openclaw/agents/main/agent/auth-profiles.json（优先） openclaw.json 的 providers 里 本地模型其实不用 key，但配置格式要对。\n1.3 模型回退 # 有时候主力模型挂了，配置了 fallback 但不生效。后来发现是 fallback 模型的 provider 也要在列表里定义，光写名字没用。\n2. 消息与频道 # 2.1 频道配置 # 配置 Telegram 的时候一直连不上，后来发现是 chat_id 格式写错了。\n正确的格式：\n{ \u0026#34;channels\u0026#34;: { \u0026#34;telegram\u0026#34;: { \u0026#34;botToken\u0026#34;: \u0026#34;xxx\u0026#34;, \u0026#34; chats\u0026#34;: { \u0026#34;allowed\u0026#34;: [\u0026#34;xxx\u0026#34;] } } } } 2.2 群组mention # 群组里 @机器人一直不回，后来才知道要开 requireMention: true。\n3. Cron 任务 # 3.1 模型字段 # 定时任务里的 agentTurn 必须带 model 字段，不然直接报错不跑。\n{ \u0026#34;payload\u0026#34;: { \u0026#34;kind\u0026#34;: \u0026#34;agentTurn\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;xxx\u0026#34;, \u0026#34;model\u0026#34;: \u0026#34;anthropic-local/claude-sonnet-4-6\u0026#34; } } systemEvent 不用带 model。\n3.2 任务超时 # 任务跑一半就停了，后来发现是超时时间设太短。复杂任务建议设 300 秒以上。\n4. MCP 相关 # 4.1 MCP 服务启动失败 # MCP 服务一直起不来，日志里全是路径错误。\n后来发现是配置文件路径问题，本机要用绝对路径。\n4.2 MCP 调用格式 # 之前一直用 JSON 格式调用，后来发现简化的 key=value 格式也能用：\n# 完整格式 mcporter call mcp-server command=\u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;xxx\u0026#34;}\u0026#39; # 简化格式 mcporter call playwright.browser_navigate url=\u0026#34;https://xxx\u0026#34; 5. Skills # 5.1 加载路径 # Skills 加载不到，配置路径写的是相对路径。本机一定要用绝对路径：\n{ \u0026#34;skills\u0026#34;: { \u0026#34;load\u0026#34;: { \u0026#34;extraDirs\u0026#34;: [\u0026#34;/Users/johny/.agents/skills\u0026#34;] } } } 5.2 Skill 调用 # 同一个 Skill 换了个名字就调用不到了，后来发现是 skill 名大小写敏感。\n6. 总结 # 配置类的问题大多出在：\n路径用相对路径 模型名写不全 认证配置漏配 建议跑之前先跑 openclaw status 检查下配置有没有问题，很多错误能提前发现。\n有问题欢迎交流，踩坑真的很难受。\n","date":"2026年3月15日","externalUrl":null,"permalink":"/posts/openclaw-troubleshooting-guide/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eOpenClaw 配置踩坑记：那些差点把我送走的问题 \n    \u003cdiv id=\"openclaw-配置踩坑记那些差点把我送走的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#openclaw-%e9%85%8d%e7%bd%ae%e8%b8%a9%e5%9d%91%e8%ae%b0%e9%82%a3%e4%ba%9b%e5%b7%ae%e7%82%b9%e6%8a%8a%e6%88%91%e9%80%81%e8%b5%b0%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e踩了整整两天，记录下来希望别再踩第二次。\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e1. 模型配置那些破事 \n    \u003cdiv id=\"1-模型配置那些破事\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-%e6%a8%a1%e5%9e%8b%e9%85%8d%e7%bd%ae%e9%82%a3%e4%ba%9b%e7%a0%b4%e4%ba%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1.1 模型名写错了 \n    \u003cdiv id=\"11-模型名写错了\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#11-%e6%a8%a1%e5%9e%8b%e5%90%8d%e5%86%99%e9%94%99%e4%ba%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e写配置的时候顺手就写：\u003c/p\u003e","title":"OpenClaw 配置踩坑记：那些差点把我送走的问题","type":"posts"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/%E9%81%BF%E5%9D%91/","section":"Tags","summary":"","title":"避坑","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/coze/","section":"Tags","summary":"","title":"Coze","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/%E5%A4%A7%E6%A8%A1%E5%9E%8B/","section":"Tags","summary":"","title":"大模型","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/%E6%99%BA%E8%83%BD%E4%BD%93/","section":"Tags","summary":"","title":"智能体","type":"tags"},{"content":" 字节 Coze 平台实战：从零搭建 AI 智能体 # 踩过不少坑，花了不少时间，终于把 Coze 这套东西摸透了。本文不整虚的，直接上干货。\n1. 先说说为什么选 Coze # 市面上 AI 应用开发平台不少，Coze 几个点比较实在：\n不用写代码：拖拖拽拽就能搭一个能用的智能体 国内就能访问：coze.cn 版本稳定，模型用的是国内那几家 插件丰富：官方插件一大把，免费的够用了 发布渠道多：微信、飞书、抖音都能接入 我们团队后来主要用这个平台做内部客服和一些自动化流程。\n2. 核心概念就这几个 # 刚接触的时候被一堆名词搞晕了，后来发现其实就几个概念：\n空间 → 项目（智能体/应用） → 技能（插件/工作流/触发器） 空间：最顶层的隔离单位，不同空间数据互不影响 智能体：对话形式的 AI 项目，跟用户交互的入口 应用：有完整 UI 的 AI 程序，偏工具类 技能：智能体调用的外部能力，插件、工作流、触发器 3. 动手搭一个智能体 # 3.1 起步 # 注册登录后，点左上角那个 ⊕ ，选\u0026quot;创建智能体\u0026quot;。\n起个名字，比如\u0026quot;新能源汽车顾问\u0026quot;，描述写清楚这个机器人是干嘛的。也可以让 AI 自动生成头像。\n3.2 写提示词 # 提示词就是智能体的\u0026quot;脑子\u0026quot;，决定了它怎么回答问题。\n格式大概这样：\n# 角色 你是资深的新能源汽车销售专家 ## 技能 ### 技能 1: 推荐车型 1. 先问用户预算和需求 2. 根据需求推荐车型 ## 限制 - 只回答新能源相关的问题 提示词写得好不好，直接决定智能体好不好用。有几个小技巧：\n角色定位要清晰，别让它啥都想干 给具体例子，模型才知道你想要什么格式的输出 限制条件写清楚，不然它容易跑偏 不会写也没事，平台有提示词库可以参考，还有\u0026quot;自动优化\u0026quot;功能能让 AI 帮你改。\n3.3 配技能 # 基础模型够用的时候，提示词写好就行。但有时候需要给它加技能：\n插件：让智能体调用外部 API。比如：\n搜索插件：实时查信息 天气插件：查天气 数据库插件：查企业内部数据 配插件简单，在技能面板点 + ，搜到想要的加上就行。\n关键是要在提示词里告诉它什么时候该用什么插件。\n工作流：复杂任务拆成步骤来做。比如：\n用户问个问题 先搜资料 再让模型总结 最后返回结果 工作流是拖拽节点组成的，逻辑比纯提示词更可控。\n3.4 调试 # 右侧有个预览面板，可以直接测试。\n对话几轮看看效果：\n回答在不在点上？ 格式对不对？ 有没有调用该用的插件？ 反复调，直到满意。\n3.5 发布 # 点发布按钮，可以选发布到：\n微信客服 飞书 抖音 API/SDK 接入自己系统 发布后就能用起来了。\n4. 几个常用的功能 # 4.1 知识库 # 大模型有时候会瞎编，特别是问一些专业领域的问题。\n知识库就是解决这个问题的。把文档、网页、表格传上去，智能体回答问题时会先从知识库找相关内容，再结合模型能力回答。\n我们把产品手册、常见问题都扔进去了，效果还行。\n4.2 记忆 # Coze 能记住用户的信息：\n变量：记用户名、爱好这些 数据库：记结构化的数据，比如订单、进度 长期记忆：自动总结对话，久了能记住用户习惯 这个功能做个性化服务挺有用。\n4.3 工作流 vs 智能体 # 开始容易混，后来想明白了：\n智能体：面向用户的产品，对话入口 工作流：处理特定任务的工具，被智能体调用 就像大脑和工具的关系，互相配合。\n5. 踩过的坑 # 提示词不是越长越好：写得越多，模型越容易跑偏，抓住核心需求就行 插件不是越多越好：加多了模型反而不知道什么时候该用哪个 测试要多用不同问法：同样的问题换种问法，可能得到完全不同的结果 发布前务必测试充分：线上环境和调试环境有时候表现不一样 6. 总结 # Coze 适合：\n不想写代码但想做 AI 应用的人 需要快速验证想法的团队 需要对接多个渠道的客服场景 不适合：\n极其复杂的业务逻辑（还是得自己写代码） 对响应速度要求极高的场景 整体来说，作为入门和快速落地的工具，Coze 值得一试。\n本文基于实际使用经验整理，平台持续更新中，部分功能可能有所变化。\n","date":"2026年3月15日","externalUrl":null,"permalink":"/posts/coze-platform-deep-dive/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e字节 Coze 平台实战：从零搭建 AI 智能体 \n    \u003cdiv id=\"字节-coze-平台实战从零搭建-ai-智能体\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ad%97%e8%8a%82-coze-%e5%b9%b3%e5%8f%b0%e5%ae%9e%e6%88%98%e4%bb%8e%e9%9b%b6%e6%90%ad%e5%bb%ba-ai-%e6%99%ba%e8%83%bd%e4%bd%93\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e踩过不少坑，花了不少时间，终于把 Coze 这套东西摸透了。本文不整虚的，直接上干货。\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e1. 先说说为什么选 Coze \n    \u003cdiv id=\"1-先说说为什么选-coze\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-%e5%85%88%e8%af%b4%e8%af%b4%e4%b8%ba%e4%bb%80%e4%b9%88%e9%80%89-coze\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e市面上 AI 应用开发平台不少，Coze 几个点比较实在：\u003c/p\u003e","title":"字节 Coze 平台实战：从零搭建 AI 智能体","type":"posts"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8/","section":"Tags","summary":"","title":"字节跳动","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/istio/","section":"Tags","summary":"","title":"Istio","type":"tags"},{"content":" Istio in Action 学习笔记 # Istio 是当下最流行的 Service Mesh（服务网格）方案之一，本文是学习《Istio in Action》书籍的详细笔记，涵盖从入门到进阶的核心知识点。\n1. Istio 解决的问题 # 在微服务架构中，网络问题往往是最大的挑战之一：\n重试、超时、熔断：每个语言都要单独实现，重复工作 负载均衡：客户端负载均衡、服务发现 安全通信：mTLS 加密、身份认证 可观测性：指标、日志、链路追踪 Istio 通过数据平面（Envoy Sidecar）+ 控制平面（istiod） 的架构，以与技术栈无关的方式解决这些问题。\n2. 核心架构 # 2.1 Envoy 代理 # Envoy 是 Istio 的数据平面核心，具备以下能力：\n支持 HTTP/2、gRPC 动态配置更新（xDS API） 熔断、超时、重试 指标收集、分布式追踪 自动 TLS 生命周期管理 2.2 istiod 控制平面 # istiod 负责：\n接收高级 Istio 配置 转换为 Envoy 配置 通过 xDS API 下发到数据平面 证书颁发和轮换 2.3 xDS API # xDS（Discovery Service）是 Envoy 的配置发现 API 体系：\nAPI 作用 LDS 监听器发现服务 CDS 集群发现服务 EDS 端点发现服务 RDS 路由发现服务 SDS 密钥发现服务 3. 流量控制 # 3.1 蓝绿发布 # 通过 VirtualService 将流量全部切换到新版本：\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: catalog spec: hosts: - catalog http: - route: - destination: host: catalog subset: version-v2 3.2 金丝雀发布 # 基于权重或请求头实现渐进式流量切换：\nhttp: - route: - destination: host: catalog subset: version-v1 weight: 90 - destination: host: catalog subset: version-v2 weight: 10 3.3 流量镜像 # 将流量镜像到测试环境，实现零风险验证：\nhttp: - route: - destination: host: catalog mirror: - destination: host: catalog-staging 4. 弹性能力 # 4.1 超时配置 # http: - route: - destination: host: backend timeout: 0.5s 4.2 重试策略 # http: - route: - destination: host: backend retries: attempts: 2 perTryTimeout: 300ms retryOn: 5xx,gateway-error 4.3 熔断机制 # 通过 DestinationRule 配置连接池和异常检测：\ntrafficPolicy: connectionPool: tcp: maxConnections: 100 http: http2MaxRequests: 1000 maxRequestsPerConnection: 10 outlierDetection: consecutive5xxErrors: 5 interval: 30s baseEjectionTime: 30s 5. 可观测性 # 5.1 核心指标 # 指标 说明 istio_requests_total 请求总数 istio_request_duration_milliseconds 请求延迟 istio_request_bytes 请求大小 istio_response_bytes 响应大小 5.2 可视化工具 # Prometheus：指标收集和存储 Grafana：仪表板展示 Kiali：服务网格可视化 Jaeger/Zipkin：分布式追踪 5.3 分布式追踪 # Istio 自动为请求注入追踪头（x-b3-traceid、x-b3-spanid 等），应用只需负责传递这些头信息即可实现全链路追踪。\n6. 安全机制 # 6.1 mTLS 双向认证 # apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: istio-system spec: mtls: mode: STRICT # 严格模式，拒绝明文流量 6.2 授权策略 # apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: allow-read spec: selector: matchLabels: app: catalog action: ALLOW rules: - to: - operation: methods: [\u0026#34;GET\u0026#34;] 7. 性能调优 # 7.1 Sidecar 配置 # 限制 Envoy 只接收相关配置，减少配置体积：\napiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: catalog-sidecar spec: workloadSelector: labels: app: catalog egress: - hosts: - \u0026#34;./catalog\u0026#34; - \u0026#34;istio-system/*\u0026#34; 7.2 控制平面监控 # 关键指标：\npilot_proxy_convergence_time：配置推送延迟 pilot_xds_pushes：推送频率 pilot_total_xds_rejects：推送失败次数 8. 最佳实践 # 从 PERMISSIVE 模式逐步过渡到 STRICT 模式 使用金丝雀发布而非蓝绿发布 配置合理的超时和重试策略 利用 Telemetry API 精细化指标配置 使用 Kiali 验证配置正确性 9. 总结 # Istio 为微服务提供了完整的网络基础设施解决方案，让应用专注于业务逻辑。通过本书的学习，可以掌握：\n服务网格的核心概念 流量管理的各种模式 可观测性体系的搭建 安全机制的实践 性能调优的方法 Istio 正在成为云原生时代不可或缺的基础设施组件。\n参考资源：\nIstio 官方文档 Envoy 官方文档 《Istio in Action》书籍 ","date":"2026年3月15日","externalUrl":null,"permalink":"/posts/istio-in-action-study/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eIstio in Action 学习笔记 \n    \u003cdiv id=\"istio-in-action-学习笔记\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#istio-in-action-%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIstio 是当下最流行的 Service Mesh（服务网格）方案之一，本文是学习《Istio in Action》书籍的详细笔记，涵盖从入门到进阶的核心知识点。\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e1. Istio 解决的问题 \n    \u003cdiv id=\"1-istio-解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-istio-%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在微服务架构中，网络问题往往是最大的挑战之一：\u003c/p\u003e","title":"Istio in Action 学习笔记","type":"posts"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/categories/kubernetes/","section":"Categories","summary":"","title":"Kubernetes","type":"categories"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/kubernetes/","section":"Tags","summary":"","title":"Kubernetes","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/servicemesh/","section":"Tags","summary":"","title":"ServiceMesh","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/cheatsheet/","section":"Tags","summary":"","title":"Cheatsheet","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/container/","section":"Tags","summary":"","title":"Container","type":"tags"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/categories/docker/","section":"Categories","summary":"","title":"Docker","type":"categories"},{"content":"","date":"2026年3月15日","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":" 前言 # Docker 是现代 DevOps 工作流中不可或缺的容器化工具。本文整理了日常工作中最常用的 Docker 命令，方便快速查阅。\n镜像管理 # 查看镜像 # docker images docker image ls 搜索镜像 # docker search nginx 拉取镜像 # docker pull nginx:latest docker pull nginx:1.21 删除镜像 # docker rmi nginx:latest docker rmi $(docker images -q) # 删除所有镜像 导出/导入镜像 # docker save -o nginx.tar nginx:latest docker load -i nginx.tar 容器管理 # 查看容器 # docker ps # 查看运行中的容器 docker ps -a # 查看所有容器 docker container ls # 同 docker ps 创建并启动容器 # docker run -d --name nginx -p 80:80 nginx:latest docker run -it --name centos centos:7 /bin/bash 启动/停止/重启容器 # docker start nginx docker stop nginx docker restart nginx 进入容器 # docker exec -it nginx /bin/bash docker attach nginx 查看容器日志 # docker logs nginx docker logs -f nginx # 实时查看 docker logs --tail 100 nginx # 查看最后100行 查看容器详情 # docker inspect nginx 查看容器资源使用 # docker stats docker stats nginx 删除容器 # docker rm nginx docker rm -f nginx # 强制删除运行中的容器 docker rm $(docker ps -aq) # 删除所有容器 数据卷管理 # 创建数据卷 # docker volume create mydata 查看数据卷 # docker volume ls docker volume inspect mydata 删除数据卷 # docker volume rm mydata docker volume prune # 删除未使用的数据卷 挂载数据卷 # docker run -d -v mydata:/data nginx docker run -d -v /host/path:/container/path nginx 网络管理 # 查看网络 # docker network ls docker network inspect bridge 创建网络 # docker network create mynet docker network create --driver bridge mynet 连接容器到网络 # docker network connect mynet nginx 断开容器网络 # docker network disconnect mynet nginx 删除网络 # docker network rm mynet 系统清理 # 清理退出状态的容器 # docker rm $(docker container ls -f \u0026#34;status=exited\u0026#34; -q) 清理未使用的镜像 # docker image prune docker image prune -a # 删除所有未使用的镜像 一键清理 # docker system prune docker system prune -a # 包括镜像 docker system prune --volumes # 包括数据卷 Dockerfile 构建 # 构建镜像 # docker build -t myimage:v1 . docker build -t myimage:v1 -f Dockerfile.custom . Dockerfile 示例 # FROM centos:7 LABEL maintainer=\u0026#34;admin@example.com\u0026#34; RUN yum install -y vim \\ \u0026amp;\u0026amp; yum clean all COPY app.py /app/ WORKDIR /app EXPOSE 8080 CMD [\u0026#34;python\u0026#34;, \u0026#34;app.py\u0026#34;] 容器转镜像（不推荐） # docker commit container_name image_name 注意：不推荐使用 commit 创建镜像。建议使用 Dockerfile 构建镜像，因为 commit 创建的镜像不可复现。\n常用组合命令 # 批量停止所有容器 # docker stop $(docker ps -q) 批量删除所有容器 # docker rm $(docker ps -aq) 查看容器 IP 地址 # docker inspect -f \u0026#39;{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}\u0026#39; nginx 复制文件到容器 # docker cp file.txt nginx:/tmp/ docker cp nginx:/tmp/file.txt ./ 希望这份速查手册对你有所帮助！建议收藏备用。\n","date":"2026年3月15日","externalUrl":null,"permalink":"/posts/docker-commands-cheatsheet/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e前言 \n    \u003cdiv id=\"前言\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%89%8d%e8%a8%80\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eDocker 是现代 DevOps 工作流中不可或缺的容器化工具。本文整理了日常工作中最常用的 Docker 命令，方便快速查阅。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e镜像管理 \n    \u003cdiv id=\"镜像管理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%95%9c%e5%83%8f%e7%ae%a1%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e查看镜像 \n    \u003cdiv id=\"查看镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8b%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker images\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker image ls\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e搜索镜像 \n    \u003cdiv id=\"搜索镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%90%9c%e7%b4%a2%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker search nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e拉取镜像 \n    \u003cdiv id=\"拉取镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8b%89%e5%8f%96%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker pull nginx:latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker pull nginx:1.21\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e删除镜像 \n    \u003cdiv id=\"删除镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%a0%e9%99%a4%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rmi nginx:latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rmi \u003cspan class=\"k\"\u003e$(\u003c/span\u003edocker images -q\u003cspan class=\"k\"\u003e)\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# 删除所有镜像\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e导出/导入镜像 \n    \u003cdiv id=\"导出导入镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%bc%e5%87%ba%e5%af%bc%e5%85%a5%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker save -o nginx.tar nginx:latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker load -i nginx.tar\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e容器管理 \n    \u003cdiv id=\"容器管理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%b9%e5%99%a8%e7%ae%a1%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e查看容器 \n    \u003cdiv id=\"查看容器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8b%e5%ae%b9%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker ps              \u003cspan class=\"c1\"\u003e# 查看运行中的容器\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker ps -a           \u003cspan class=\"c1\"\u003e# 查看所有容器\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker container ls    \u003cspan class=\"c1\"\u003e# 同 docker ps\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e创建并启动容器 \n    \u003cdiv id=\"创建并启动容器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9b%e5%bb%ba%e5%b9%b6%e5%90%af%e5%8a%a8%e5%ae%b9%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker run -d --name nginx -p 80:80 nginx:latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker run -it --name centos centos:7 /bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e启动/停止/重启容器 \n    \u003cdiv id=\"启动停止重启容器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%90%af%e5%8a%a8%e5%81%9c%e6%ad%a2%e9%87%8d%e5%90%af%e5%ae%b9%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker start nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker stop nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker restart nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e进入容器 \n    \u003cdiv id=\"进入容器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bf%9b%e5%85%a5%e5%ae%b9%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker \u003cspan class=\"nb\"\u003eexec\u003c/span\u003e -it nginx /bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker attach nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e查看容器日志 \n    \u003cdiv id=\"查看容器日志\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8b%e5%ae%b9%e5%99%a8%e6%97%a5%e5%bf%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker logs nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker logs -f nginx           \u003cspan class=\"c1\"\u003e# 实时查看\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker logs --tail \u003cspan class=\"m\"\u003e100\u003c/span\u003e nginx   \u003cspan class=\"c1\"\u003e# 查看最后100行\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e查看容器详情 \n    \u003cdiv id=\"查看容器详情\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8b%e5%ae%b9%e5%99%a8%e8%af%a6%e6%83%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker inspect nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e查看容器资源使用 \n    \u003cdiv id=\"查看容器资源使用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8b%e5%ae%b9%e5%99%a8%e8%b5%84%e6%ba%90%e4%bd%bf%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker stats\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker stats nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e删除容器 \n    \u003cdiv id=\"删除容器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%a0%e9%99%a4%e5%ae%b9%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rm nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rm -f nginx              \u003cspan class=\"c1\"\u003e# 强制删除运行中的容器\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rm \u003cspan class=\"k\"\u003e$(\u003c/span\u003edocker ps -aq\u003cspan class=\"k\"\u003e)\u003c/span\u003e      \u003cspan class=\"c1\"\u003e# 删除所有容器\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e数据卷管理 \n    \u003cdiv id=\"数据卷管理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e6%8d%ae%e5%8d%b7%e7%ae%a1%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e创建数据卷 \n    \u003cdiv id=\"创建数据卷\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9b%e5%bb%ba%e6%95%b0%e6%8d%ae%e5%8d%b7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker volume create mydata\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e查看数据卷 \n    \u003cdiv id=\"查看数据卷\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8b%e6%95%b0%e6%8d%ae%e5%8d%b7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker volume ls\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker volume inspect mydata\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e删除数据卷 \n    \u003cdiv id=\"删除数据卷\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%a0%e9%99%a4%e6%95%b0%e6%8d%ae%e5%8d%b7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker volume rm mydata\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker volume prune  \u003cspan class=\"c1\"\u003e# 删除未使用的数据卷\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e挂载数据卷 \n    \u003cdiv id=\"挂载数据卷\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8c%82%e8%bd%bd%e6%95%b0%e6%8d%ae%e5%8d%b7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker run -d -v mydata:/data nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker run -d -v /host/path:/container/path nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e网络管理 \n    \u003cdiv id=\"网络管理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bd%91%e7%bb%9c%e7%ae%a1%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e查看网络 \n    \u003cdiv id=\"查看网络\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8b%e7%bd%91%e7%bb%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker network ls\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker network inspect bridge\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e创建网络 \n    \u003cdiv id=\"创建网络\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9b%e5%bb%ba%e7%bd%91%e7%bb%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker network create mynet\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker network create --driver bridge mynet\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e连接容器到网络 \n    \u003cdiv id=\"连接容器到网络\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bf%9e%e6%8e%a5%e5%ae%b9%e5%99%a8%e5%88%b0%e7%bd%91%e7%bb%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker network connect mynet nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e断开容器网络 \n    \u003cdiv id=\"断开容器网络\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%ad%e5%bc%80%e5%ae%b9%e5%99%a8%e7%bd%91%e7%bb%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker network disconnect mynet nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e删除网络 \n    \u003cdiv id=\"删除网络\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%a0%e9%99%a4%e7%bd%91%e7%bb%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker network rm mynet\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e系统清理 \n    \u003cdiv id=\"系统清理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%b3%bb%e7%bb%9f%e6%b8%85%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e清理退出状态的容器 \n    \u003cdiv id=\"清理退出状态的容器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b8%85%e7%90%86%e9%80%80%e5%87%ba%e7%8a%b6%e6%80%81%e7%9a%84%e5%ae%b9%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker rm \u003cspan class=\"k\"\u003e$(\u003c/span\u003edocker container ls -f \u003cspan class=\"s2\"\u003e\u0026#34;status=exited\u0026#34;\u003c/span\u003e -q\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e清理未使用的镜像 \n    \u003cdiv id=\"清理未使用的镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b8%85%e7%90%86%e6%9c%aa%e4%bd%bf%e7%94%a8%e7%9a%84%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker image prune\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker image prune -a  \u003cspan class=\"c1\"\u003e# 删除所有未使用的镜像\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e一键清理 \n    \u003cdiv id=\"一键清理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%80%e9%94%ae%e6%b8%85%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker system prune\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker system prune -a             \u003cspan class=\"c1\"\u003e# 包括镜像\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker system prune --volumes      \u003cspan class=\"c1\"\u003e# 包括数据卷\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003eDockerfile 构建 \n    \u003cdiv id=\"dockerfile-构建\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#dockerfile-%e6%9e%84%e5%bb%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e构建镜像 \n    \u003cdiv id=\"构建镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9e%84%e5%bb%ba%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker build -t myimage:v1 .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker build -t myimage:v1 -f Dockerfile.custom .\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eDockerfile 示例 \n    \u003cdiv id=\"dockerfile-示例\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#dockerfile-%e7%a4%ba%e4%be%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-dockerfile\" data-lang=\"dockerfile\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eFROM\u003c/span\u003e\u003cspan class=\"s\"\u003e centos:7\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eLABEL\u003c/span\u003e \u003cspan class=\"nv\"\u003emaintainer\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;admin@example.com\u0026#34;\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e yum install -y vim \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e    \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e yum clean all\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e app.py /app/\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eWORKDIR\u003c/span\u003e\u003cspan class=\"s\"\u003e /app\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eEXPOSE\u003c/span\u003e\u003cspan class=\"s\"\u003e 8080\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eCMD\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;python\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;app.py\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e容器转镜像（不推荐） \n    \u003cdiv id=\"容器转镜像不推荐\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%b9%e5%99%a8%e8%bd%ac%e9%95%9c%e5%83%8f%e4%b8%8d%e6%8e%a8%e8%8d%90\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker commit container_name image_name\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e注意\u003c/strong\u003e：不推荐使用 commit 创建镜像。建议使用 Dockerfile 构建镜像，因为 commit 创建的镜像不可复现。\u003c/p\u003e","title":"Docker 常用命令速查手册","type":"posts"},{"content":"","date":"2025年8月2日","externalUrl":null,"permalink":"/tags/%E4%BB%A3%E7%A0%81/","section":"Tags","summary":"","title":"代码","type":"tags"},{"content":" 苹果窗口风格代码框展示 # 我们为博客添加了全新的苹果窗口风格代码框，具有以下特性：\n特性 # 🍎 苹果窗口风格：仿照 macOS 窗口设计，包含红绿黄三色控制按钮 🎨 主题适配：完美支持浅色和深色模式 📋 一键复制：悬停显示复制按钮，支持键盘快捷键 🏷️ 语言标签：自动识别并显示编程语言 📱 响应式设计：在各种设备上都有良好的显示效果 ♿ 无障碍支持：支持键盘导航和屏幕阅读器 代码示例 # JavaScript # // 现代 JavaScript 示例 const fetchUserData = async (userId) =\u0026gt; { try { const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); return { id: userData.id, name: userData.name, email: userData.email, preferences: { theme: userData.theme || \u0026#39;light\u0026#39;, language: userData.language || \u0026#39;zh-CN\u0026#39; } }; } catch (error) { console.error(\u0026#39;获取用户数据失败:\u0026#39;, error); throw new Error(\u0026#39;无法获取用户信息\u0026#39;); } }; // 使用示例 fetchUserData(123) .then(user =\u0026gt; console.log(\u0026#39;用户信息:\u0026#39;, user)) .catch(error =\u0026gt; console.error(\u0026#39;错误:\u0026#39;, error)); Python # # Python 数据处理示例 import pandas as pd import numpy as np from typing import List, Dict, Optional class DataProcessor: \u0026#34;\u0026#34;\u0026#34;数据处理器类\u0026#34;\u0026#34;\u0026#34; def __init__(self, data_source: str): self.data_source = data_source self.data: Optional[pd.DataFrame] = None def load_data(self) -\u0026gt; pd.DataFrame: \u0026#34;\u0026#34;\u0026#34;加载数据\u0026#34;\u0026#34;\u0026#34; try: self.data = pd.read_csv(self.data_source) print(f\u0026#34;成功加载 {len(self.data)} 行数据\u0026#34;) return self.data except FileNotFoundError: raise ValueError(f\u0026#34;找不到数据文件: {self.data_source}\u0026#34;) def clean_data(self) -\u0026gt; pd.DataFrame: \u0026#34;\u0026#34;\u0026#34;清理数据\u0026#34;\u0026#34;\u0026#34; if self.data is None: raise ValueError(\u0026#34;请先加载数据\u0026#34;) # 删除重复行 self.data = self.data.drop_duplicates() # 处理缺失值 self.data = self.data.fillna(method=\u0026#39;forward\u0026#39;) return self.data def analyze(self) -\u0026gt; Dict[str, float]: \u0026#34;\u0026#34;\u0026#34;数据分析\u0026#34;\u0026#34;\u0026#34; if self.data is None: raise ValueError(\u0026#34;请先加载数据\u0026#34;) return { \u0026#39;mean\u0026#39;: self.data.select_dtypes(include=[np.number]).mean().to_dict(), \u0026#39;std\u0026#39;: self.data.select_dtypes(include=[np.number]).std().to_dict(), \u0026#39;count\u0026#39;: len(self.data) } # 使用示例 processor = DataProcessor(\u0026#39;data.csv\u0026#39;) processor.load_data() processor.clean_data() results = processor.analyze() print(\u0026#34;分析结果:\u0026#34;, results) Go # // Go 并发处理示例 package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) // Worker 工作器接口 type Worker interface { Process(ctx context.Context, data interface{}) error } // TaskProcessor 任务处理器 type TaskProcessor struct { workers []Worker maxWorkers int timeout time.Duration } // NewTaskProcessor 创建新的任务处理器 func NewTaskProcessor(maxWorkers int, timeout time.Duration) *TaskProcessor { return \u0026amp;TaskProcessor{ workers: make([]Worker, 0, maxWorkers), maxWorkers: maxWorkers, timeout: timeout, } } // ProcessTasks 并发处理任务 func (tp *TaskProcessor) ProcessTasks(ctx context.Context, tasks []interface{}) error { ctx, cancel := context.WithTimeout(ctx, tp.timeout) defer cancel() taskChan := make(chan interface{}, len(tasks)) errorChan := make(chan error, len(tasks)) // 启动工作协程 var wg sync.WaitGroup for i := 0; i \u0026lt; tp.maxWorkers; i++ { wg.Add(1) go func(workerID int) { defer wg.Done() for task := range taskChan { select { case \u0026lt;-ctx.Done(): errorChan \u0026lt;- ctx.Err() return default: if err := tp.processTask(ctx, task); err != nil { errorChan \u0026lt;- fmt.Errorf(\u0026#34;worker %d: %w\u0026#34;, workerID, err) } } } }(i) } // 发送任务 go func() { defer close(taskChan) for _, task := range tasks { select { case taskChan \u0026lt;- task: case \u0026lt;-ctx.Done(): return } } }() // 等待完成 go func() { wg.Wait() close(errorChan) }() // 收集错误 for err := range errorChan { if err != nil { return err } } return nil } func (tp *TaskProcessor) processTask(ctx context.Context, task interface{}) error { // 模拟任务处理 select { case \u0026lt;-time.After(100 * time.Millisecond): fmt.Printf(\u0026#34;处理任务: %v\\n\u0026#34;, task) return nil case \u0026lt;-ctx.Done(): return ctx.Err() } } func main() { processor := NewTaskProcessor(5, 10*time.Second) tasks := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} ctx := context.Background() if err := processor.ProcessTasks(ctx, tasks); err != nil { log.Fatalf(\u0026#34;处理任务失败: %v\u0026#34;, err) } fmt.Println(\u0026#34;所有任务处理完成\u0026#34;) } Rust # // Rust 异步编程示例 use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct User { pub id: u64, pub name: String, pub email: String, pub created_at: chrono::DateTime\u0026lt;chrono::Utc\u0026gt;, } #[derive(Debug, Clone)] pub struct UserCache { cache: Arc\u0026lt;RwLock\u0026lt;HashMap\u0026lt;u64, User\u0026gt;\u0026gt;\u0026gt;, ttl: std::time::Duration, } impl UserCache { pub fn new(ttl: std::time::Duration) -\u0026gt; Self { Self { cache: Arc::new(RwLock::new(HashMap::new())), ttl, } } pub async fn get(\u0026amp;self, user_id: u64) -\u0026gt; Option\u0026lt;User\u0026gt; { let cache = self.cache.read().await; cache.get(\u0026amp;user_id).cloned() } pub async fn set(\u0026amp;self, user: User) { let mut cache = self.cache.write().await; cache.insert(user.id, user); } pub async fn remove(\u0026amp;self, user_id: u64) -\u0026gt; Option\u0026lt;User\u0026gt; { let mut cache = self.cache.write().await; cache.remove(\u0026amp;user_id) } pub async fn clear(\u0026amp;self) { let mut cache = self.cache.write().await; cache.clear(); } pub async fn size(\u0026amp;self) -\u0026gt; usize { let cache = self.cache.read().await; cache.len() } } #[tokio::main] async fn main() -\u0026gt; Result\u0026lt;(), Box\u0026lt;dyn std::error::Error\u0026gt;\u0026gt; { let cache = UserCache::new(std::time::Duration::from_secs(3600)); // 创建测试用户 let user = User { id: 1, name: \u0026#34;张三\u0026#34;.to_string(), email: \u0026#34;zhangsan@example.com\u0026#34;.to_string(), created_at: chrono::Utc::now(), }; // 缓存用户 cache.set(user.clone()).await; println!(\u0026#34;用户已缓存: {:?}\u0026#34;, user); // 获取用户 if let Some(cached_user) = cache.get(1).await { println!(\u0026#34;从缓存获取用户: {:?}\u0026#34;, cached_user); } // 缓存统计 println!(\u0026#34;缓存大小: {}\u0026#34;, cache.size().await); Ok(()) } Shell/Bash # #!/bin/bash # 自动化部署脚本 set -euo pipefail # 配置变量 PROJECT_NAME=\u0026#34;my-blog\u0026#34; BUILD_DIR=\u0026#34;public\u0026#34; DEPLOY_DIR=\u0026#34;/var/www/html\u0026#34; BACKUP_DIR=\u0026#34;/var/backups/website\u0026#34; LOG_FILE=\u0026#34;/var/log/deploy.log\u0026#34; # 颜色输出 RED=\u0026#39;\\033[0;31m\u0026#39; GREEN=\u0026#39;\\033[0;32m\u0026#39; YELLOW=\u0026#39;\\033[1;33m\u0026#39; NC=\u0026#39;\\033[0m\u0026#39; # No Color # 日志函数 log() { echo -e \u0026#34;${GREEN}[$(date +\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)]${NC} $1\u0026#34; | tee -a \u0026#34;$LOG_FILE\u0026#34; } error() { echo -e \u0026#34;${RED}[ERROR]${NC} $1\u0026#34; | tee -a \u0026#34;$LOG_FILE\u0026#34; exit 1 } warn() { echo -e \u0026#34;${YELLOW}[WARNING]${NC} $1\u0026#34; | tee -a \u0026#34;$LOG_FILE\u0026#34; } # 检查依赖 check_dependencies() { log \u0026#34;检查依赖...\u0026#34; command -v hugo \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 || error \u0026#34;Hugo 未安装\u0026#34; command -v rsync \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 || error \u0026#34;rsync 未安装\u0026#34; log \u0026#34;依赖检查完成\u0026#34; } # 备份现有网站 backup_website() { log \u0026#34;备份现有网站...\u0026#34; if [ -d \u0026#34;$DEPLOY_DIR\u0026#34; ]; then local backup_name=\u0026#34;backup-$(date +%Y%m%d-%H%M%S)\u0026#34; mkdir -p \u0026#34;$BACKUP_DIR\u0026#34; if rsync -av \u0026#34;$DEPLOY_DIR/\u0026#34; \u0026#34;$BACKUP_DIR/$backup_name/\u0026#34;; then log \u0026#34;备份完成: $BACKUP_DIR/$backup_name\u0026#34; else error \u0026#34;备份失败\u0026#34; fi else warn \u0026#34;部署目录不存在，跳过备份\u0026#34; fi } # 构建网站 build_website() { log \u0026#34;构建网站...\u0026#34; # 清理旧的构建文件 if [ -d \u0026#34;$BUILD_DIR\u0026#34; ]; then rm -rf \u0026#34;$BUILD_DIR\u0026#34; fi # 构建网站 if hugo --gc --minify; then log \u0026#34;网站构建完成\u0026#34; else error \u0026#34;网站构建失败\u0026#34; fi # 验证构建结果 if [ ! -d \u0026#34;$BUILD_DIR\u0026#34; ] || [ -z \u0026#34;$(ls -A $BUILD_DIR)\u0026#34; ]; then error \u0026#34;构建目录为空\u0026#34; fi } # 部署网站 deploy_website() { log \u0026#34;部署网站...\u0026#34; # 创建部署目录 sudo mkdir -p \u0026#34;$DEPLOY_DIR\u0026#34; # 同步文件 if sudo rsync -av --delete \u0026#34;$BUILD_DIR/\u0026#34; \u0026#34;$DEPLOY_DIR/\u0026#34;; then log \u0026#34;网站部署完成\u0026#34; else error \u0026#34;网站部署失败\u0026#34; fi # 设置权限 sudo chown -R www-data:www-data \u0026#34;$DEPLOY_DIR\u0026#34; sudo chmod -R 755 \u0026#34;$DEPLOY_DIR\u0026#34; } # 验证部署 verify_deployment() { log \u0026#34;验证部署...\u0026#34; # 检查关键文件 local key_files=(\u0026#34;index.html\u0026#34; \u0026#34;sitemap.xml\u0026#34;) for file in \u0026#34;${key_files[@]}\u0026#34;; do if [ ! -f \u0026#34;$DEPLOY_DIR/$file\u0026#34; ]; then error \u0026#34;关键文件缺失: $file\u0026#34; fi done # 检查网站可访问性 if command -v curl \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then if curl -f -s http://localhost/ \u0026gt;/dev/null; then log \u0026#34;网站可访问性验证通过\u0026#34; else warn \u0026#34;网站可访问性验证失败\u0026#34; fi fi } # 清理旧备份 cleanup_old_backups() { log \u0026#34;清理旧备份...\u0026#34; if [ -d \u0026#34;$BACKUP_DIR\u0026#34; ]; then # 保留最近7天的备份 find \u0026#34;$BACKUP_DIR\u0026#34; -type d -name \u0026#34;backup-*\u0026#34; -mtime +7 -exec rm -rf {} \\; 2\u0026gt;/dev/null || true log \u0026#34;旧备份清理完成\u0026#34; fi } # 主函数 main() { log \u0026#34;开始部署 $PROJECT_NAME...\u0026#34; check_dependencies backup_website build_website deploy_website verify_deployment cleanup_old_backups log \u0026#34;部署完成！\u0026#34; } # 错误处理 trap \u0026#39;error \u0026#34;部署过程中发生错误\u0026#34;\u0026#39; ERR # 执行主函数 main \u0026#34;$@\u0026#34; 使用说明 # 复制功能 # 鼠标操作：将鼠标悬停在代码块上，会显示复制按钮 键盘快捷键：聚焦代码块后按 Ctrl+Shift+C（Windows/Linux）或 Cmd+Shift+C（macOS） 语言支持 # 目前支持以下编程语言的自动识别和标签显示：\nJavaScript/TypeScript Python Go Rust Bash/Shell HTML/CSS/SCSS JSON/YAML/TOML SQL Dockerfile Markdown 主题适配 # 代码框会自动适配网站的主题模式：\n浅色模式：白色背景，深色文字 深色模式：深色背景，浅色文字 响应式设计 # 桌面设备：完整的苹果窗口效果 平板设备：适中的字体大小和间距 移动设备：优化的布局和控制按钮大小 技术实现 # 这个苹果窗口风格代码框使用了以下技术：\nCSS3：渐变、阴影、动画效果 JavaScript：复制功能、键盘支持 Hugo：模板集成和资源处理 响应式设计：媒体查询和弹性布局 享受全新的代码阅读体验吧！ 🎉\n","date":"2025年8月2日","externalUrl":null,"permalink":"/posts/code-showcase/","section":"博客文章","summary":"查看我们全新的苹果窗口风格代码框，支持多种编程语言和复制功能。","title":"代码展示：苹果窗口风格代码框","type":"posts"},{"content":"","date":"2025年8月2日","externalUrl":null,"permalink":"/tags/%E6%A0%B7%E5%BC%8F/","section":"Tags","summary":"","title":"样式","type":"tags"},{"content":"","date":"2025年8月2日","externalUrl":null,"permalink":"/tags/%E5%B1%95%E7%A4%BA/","section":"Tags","summary":"","title":"展示","type":"tags"},{"content":"","date":"2025年1月2日","externalUrl":null,"permalink":"/tags/%E6%AC%A2%E8%BF%8E/","section":"Tags","summary":"","title":"欢迎","type":"tags"},{"content":"我很兴奋地推出这个使用 Hugo 和令人惊叹的 Blowfish 主题构建的新博客！\n为什么选择 Hugo 和 Blowfish？ # 在研究了各种博客平台和静态网站生成器后，我选择 Hugo 有以下几个原因：\nHugo 的优势 # 闪电般快速: Hugo 是最快的静态网站生成器之一 灵活性: 支持各种内容类型和自定义选项 SEO 友好: 内置 SEO 优化功能 Markdown 支持: 使用简单易读的 Markdown 编写内容 Blowfish 主题特色 # 精美设计: 简洁、现代、专业的外观 响应式: 在桌面、平板和移动设备上都表现出色 多种布局: 可选择各种首页和内容布局 配色方案: 多种内置配色方案 丰富功能: 支持评论、分析、社交分享等功能 接下来会写什么？ # 我计划写关于以下主题的内容：\nWeb 开发: 前端和后端技术 DevOps: CI/CD、容器化和云技术 编程: 代码教程和最佳实践 工具与技巧: 生产力工具和开发工作流 个人项目: 展示有趣的项目和实验 Hugo 和 Blowfish 入门 # 如果你有兴趣创建类似的博客，这里是一个快速概览：\n# 安装 Hugo brew install hugo # 创建新站点 hugo new site my-blog # 添加 Blowfish 主题 git submodule add https://github.com/nunocoracao/blowfish.git themes/blowfish # 复制主题配置 cp -r themes/blowfish/config/_default/* config/_default/ # 创建你的第一篇文章 hugo new posts/my-first-post.md # 启动开发服务器 hugo server 总结 # 我期待通过这个博客分享我的知识和经验。请继续关注更多内容，如果你有任何问题或建议，请随时联系我！\n感谢阅读，欢迎来到我的博客！🎉\n","date":"2025年1月2日","externalUrl":null,"permalink":"/posts/welcome-to-my-blog/","section":"博客文章","summary":"\u003cp\u003e我很兴奋地推出这个使用 \u003ca\n  href=\"https://gohugo.io/\"\n    target=\"_blank\"\n  \u003eHugo\u003c/a\u003e 和令人惊叹的 \u003ca\n  href=\"https://blowfish.page/\"\n    target=\"_blank\"\n  \u003eBlowfish\u003c/a\u003e 主题构建的新博客！\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e为什么选择 Hugo 和 Blowfish？ \n    \u003cdiv id=\"为什么选择-hugo-和-blowfish\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%ba%e4%bb%80%e4%b9%88%e9%80%89%e6%8b%a9-hugo-%e5%92%8c-blowfish\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在研究了各种博客平台和静态网站生成器后，我选择 Hugo 有以下几个原因：\u003c/p\u003e\n\n\u003ch3 class=\"relative group\"\u003eHugo 的优势 \n    \u003cdiv id=\"hugo-的优势\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#hugo-%e7%9a%84%e4%bc%98%e5%8a%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e闪电般快速\u003c/strong\u003e: Hugo 是最快的静态网站生成器之一\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e灵活性\u003c/strong\u003e: 支持各种内容类型和自定义选项\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSEO 友好\u003c/strong\u003e: 内置 SEO 优化功能\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMarkdown 支持\u003c/strong\u003e: 使用简单易读的 Markdown 编写内容\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003eBlowfish 主题特色 \n    \u003cdiv id=\"blowfish-主题特色\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#blowfish-%e4%b8%bb%e9%a2%98%e7%89%b9%e8%89%b2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e精美设计\u003c/strong\u003e: 简洁、现代、专业的外观\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e响应式\u003c/strong\u003e: 在桌面、平板和移动设备上都表现出色\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e多种布局\u003c/strong\u003e: 可选择各种首页和内容布局\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e配色方案\u003c/strong\u003e: 多种内置配色方案\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e丰富功能\u003c/strong\u003e: 支持评论、分析、社交分享等功能\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e接下来会写什么？ \n    \u003cdiv id=\"接下来会写什么\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a5%e4%b8%8b%e6%9d%a5%e4%bc%9a%e5%86%99%e4%bb%80%e4%b9%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e我计划写关于以下主题的内容：\u003c/p\u003e","title":"欢迎来到我的博客！","type":"posts"},{"content":"","date":"2025年1月2日","externalUrl":null,"permalink":"/categories/%E7%BB%BC%E5%90%88/","section":"Categories","summary":"","title":"综合","type":"categories"},{"content":"Hugo 是最受欢迎的静态网站生成器之一，以其速度和灵活性而闻名。在本指南中，我们将介绍开始使用 Hugo 所需了解的一切。\n什么是 Hugo？ # Hugo 是一个用 Go 语言编写的快速现代静态网站生成器。它将你用 Markdown 编写的内容转换为包含 HTML、CSS 和 JavaScript 的完整网站。\nHugo 的主要优势 # 速度: 在毫秒内构建网站 灵活性: 支持各种内容类型和结构 无依赖: 单一二进制文件，无外部依赖 主题: 大型美观主题生态系统 Markdown: 使用简单的 Markdown 格式编写内容 安装 # macOS # # 使用 Homebrew brew install hugo # 使用 MacPorts sudo port install hugo Windows # # 使用 Chocolatey choco install hugo # 使用 Scoop scoop install hugo Linux # # Ubuntu/Debian sudo apt install hugo # Arch Linux sudo pacman -S hugo # 或从 GitHub releases 下载二进制文件 Creating Your First Site # 1. Create a New Site # hugo new site my-awesome-blog cd my-awesome-blog 2. Add a Theme # # Initialize git repository git init # Add a theme as submodule git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke # Add theme to config echo \u0026#34;theme = \u0026#39;ananke\u0026#39;\u0026#34; \u0026gt;\u0026gt; hugo.toml 3. Create Your First Post # hugo new posts/my-first-post.md 4. Start the Development Server # hugo server -D Your site will be available at http://localhost:1313\nHugo Directory Structure # my-site/ ├── archetypes/ # Content templates ├── assets/ # Global assets (SCSS, JS, images) ├── content/ # Your content files ├── data/ # Data files (JSON, YAML, TOML) ├── layouts/ # Template files ├── static/ # Static files (images, CSS, JS) ├── themes/ # Themes directory ├── hugo.toml # Configuration file └── public/ # Generated site (after hugo build) Content Management # Front Matter # Every content file starts with front matter containing metadata:\n--- title: \u0026#34;My Post Title\u0026#34; date: 2025-01-01T12:00:00+08:00 draft: false description: \u0026#34;Post description\u0026#34; categories: [\u0026#34;Category\u0026#34;] tags: [\u0026#34;tag1\u0026#34;, \u0026#34;tag2\u0026#34;] --- Content Organization # content/ ├── _index.md # Homepage content ├── about.md # About page ├── posts/ # Blog posts │ ├── _index.md # Posts section page │ ├── post-1.md │ └── post-2.md └── projects/ # Projects section ├── _index.md └── project-1.md Configuration # Hugo uses hugo.toml (or hugo.yaml/hugo.json) for configuration:\nbaseURL = \u0026#39;https://example.com\u0026#39; languageCode = \u0026#39;en-us\u0026#39; title = \u0026#39;My Awesome Blog\u0026#39; theme = \u0026#39;ananke\u0026#39; [params] author = \u0026#39;Your Name\u0026#39; description = \u0026#39;My awesome blog description\u0026#39; [menu] [[menu.main]] name = \u0026#39;Home\u0026#39; url = \u0026#39;/\u0026#39; weight = 10 [[menu.main]] name = \u0026#39;Posts\u0026#39; url = \u0026#39;/posts/\u0026#39; weight = 20 Building and Deployment # Build for Production # hugo This generates the static site in the public/ directory.\nDeployment Options # GitHub Pages: Free hosting for static sites Netlify: Continuous deployment from Git Vercel: Fast global CDN AWS S3: Scalable cloud storage Traditional Web Hosting: Upload to any web server Tips for Success # Start Simple: Begin with a basic theme and customize gradually Content First: Focus on creating quality content before heavy customization Learn Markdown: Master Markdown syntax for efficient writing Use Shortcodes: Leverage Hugo\u0026rsquo;s shortcodes for rich content Optimize Images: Use Hugo\u0026rsquo;s image processing features Test Locally: Always test changes with hugo server Conclusion # Hugo is an excellent choice for building fast, modern websites. Its simplicity, speed, and flexibility make it perfect for blogs, documentation sites, portfolios, and more.\nStart with the basics, experiment with themes, and gradually explore Hugo\u0026rsquo;s advanced features as you become more comfortable with the platform.\nHappy blogging with Hugo! 🚀\n","date":"2025年1月1日","externalUrl":null,"permalink":"/posts/getting-started-with-hugo/","section":"博客文章","summary":"\u003cp\u003e\u003ca\n  href=\"https://gohugo.io/\"\n    target=\"_blank\"\n  \u003eHugo\u003c/a\u003e 是最受欢迎的静态网站生成器之一，以其速度和灵活性而闻名。在本指南中，我们将介绍开始使用 Hugo 所需了解的一切。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 Hugo？ \n    \u003cdiv id=\"什么是-hugo\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-hugo\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eHugo 是一个用 Go 语言编写的快速现代静态网站生成器。它将你用 Markdown 编写的内容转换为包含 HTML、CSS 和 JavaScript 的完整网站。\u003c/p\u003e","title":"Hugo 入门：初学者指南","type":"posts"},{"content":"","date":"2025年1月1日","externalUrl":null,"permalink":"/categories/web-%E5%BC%80%E5%8F%91/","section":"Categories","summary":"","title":"Web 开发","type":"categories"},{"content":"","date":"2025年1月1日","externalUrl":null,"permalink":"/tags/%E5%88%9D%E5%AD%A6%E8%80%85/","section":"Tags","summary":"","title":"初学者","type":"tags"},{"content":"","date":"2025年1月1日","externalUrl":null,"permalink":"/categories/%E6%95%99%E7%A8%8B/","section":"Categories","summary":"","title":"教程","type":"categories"},{"content":"","date":"2025年1月1日","externalUrl":null,"permalink":"/tags/%E6%95%99%E7%A8%8B/","section":"Tags","summary":"","title":"教程","type":"tags"},{"content":"","date":"2025年1月1日","externalUrl":null,"permalink":"/tags/%E9%9D%99%E6%80%81%E7%BD%91%E7%AB%99%E7%94%9F%E6%88%90%E5%99%A8/","section":"Tags","summary":"","title":"静态网站生成器","type":"tags"},{"content":"","date":"2023年10月5日","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"Python 在 DevOps 领域扮演着重要角色，其简洁的语法、丰富的生态系统和强大的自动化能力使其成为运维自动化的首选语言。本指南将从 DevOps 理念开始，逐步深入到 Python 在各个 DevOps 场景中的实际应用，帮助您构建高效的自动化运维体系。\n图：《Python for DevOps: Learn Ruthlessly Effective Automation》\nDevOps 理念与文化 # 什么是 DevOps # DevOps 是一种文化、实践和工具的组合，旨在提高组织快速交付应用程序和服务的能力。它强调开发（Development）和运维（Operations）团队之间的协作与沟通。\nDevOps 核心价值观 # graph TD A[DevOps 核心价值观] --\u003e B[协作 Collaboration] A --\u003e C[自动化 Automation] A --\u003e D[持续改进 Continuous Improvement] A --\u003e E[快速反馈 Fast Feedback] B --\u003e B1[打破部门壁垒] B --\u003e B2[共享责任] B --\u003e B3[透明沟通] C --\u003e C1[基础设施即代码] C --\u003e C2[CI/CD 流水线] C --\u003e C3[自动化测试] D --\u003e D1[度量和监控] D --\u003e D2[学习和实验] D --\u003e D3[故障后分析] E --\u003e E1[快速部署] E --\u003e E2[实时监控] E --\u003e E3[用户反馈] DevOps 生命周期 # # DevOps 生命周期的 Python 表示 class DevOpsLifecycle: def __init__(self): self.stages = [ \u0026#34;Plan\u0026#34;, # 规划 \u0026#34;Code\u0026#34;, # 编码 \u0026#34;Build\u0026#34;, # 构建 \u0026#34;Test\u0026#34;, # 测试 \u0026#34;Release\u0026#34;, # 发布 \u0026#34;Deploy\u0026#34;, # 部署 \u0026#34;Operate\u0026#34;, # 运维 \u0026#34;Monitor\u0026#34; # 监控 ] def execute_stage(self, stage, artifacts=None): \u0026#34;\u0026#34;\u0026#34;执行 DevOps 生命周期的某个阶段\u0026#34;\u0026#34;\u0026#34; if stage not in self.stages: raise ValueError(f\u0026#34;Invalid stage: {stage}\u0026#34;) print(f\u0026#34;Executing {stage} stage...\u0026#34;) # 根据不同阶段执行相应操作 if stage == \u0026#34;Build\u0026#34;: return self.build_application(artifacts) elif stage == \u0026#34;Test\u0026#34;: return self.run_tests(artifacts) elif stage == \u0026#34;Deploy\u0026#34;: return self.deploy_application(artifacts) elif stage == \u0026#34;Monitor\u0026#34;: return self.monitor_application() def build_application(self, source_code): \u0026#34;\u0026#34;\u0026#34;构建应用程序\u0026#34;\u0026#34;\u0026#34; # 模拟构建过程 return {\u0026#34;status\u0026#34;: \u0026#34;success\u0026#34;, \u0026#34;artifact\u0026#34;: \u0026#34;app.tar.gz\u0026#34;} def run_tests(self, build_artifact): \u0026#34;\u0026#34;\u0026#34;运行自动化测试\u0026#34;\u0026#34;\u0026#34; # 模拟测试过程 return {\u0026#34;status\u0026#34;: \u0026#34;passed\u0026#34;, \u0026#34;coverage\u0026#34;: 85} def deploy_application(self, artifact): \u0026#34;\u0026#34;\u0026#34;部署应用程序\u0026#34;\u0026#34;\u0026#34; # 模拟部署过程 return {\u0026#34;status\u0026#34;: \u0026#34;deployed\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://app.example.com\u0026#34;} def monitor_application(self): \u0026#34;\u0026#34;\u0026#34;监控应用程序\u0026#34;\u0026#34;\u0026#34; # 模拟监控过程 return {\u0026#34;status\u0026#34;: \u0026#34;healthy\u0026#34;, \u0026#34;uptime\u0026#34;: \u0026#34;99.9%\u0026#34;} # 使用示例 devops = DevOpsLifecycle() for stage in devops.stages: result = devops.execute_stage(stage) print(f\u0026#34;{stage} result: {result}\u0026#34;) DevOps 团队文化建设 # 高效团队的特征 # 基于《Python for DevOps》一书的观点，一个优秀的 DevOps 团队应该具备以下特征：\n明确且令人振奋的目标\n团队成员对目标有共同理解 目标具有挑战性但可实现 与业务价值紧密相关 以结果为导向的结构\n避免无效的会议和流程 专注于可衡量的结果 快速决策和执行 能干的团队成员\n技术能力过硬 学习能力强 具备跨领域知识 统一承诺\n对团队目标的共同承诺 相互支持和协作 共同承担责任 合作的氛围\n开放透明的沟通 建设性的反馈 相互尊重 卓越的标准\n高质量的代码和文档 严格的测试和部署流程 持续改进的意识 外部支持和认可\n管理层的支持 资源的保障 成果的认可 原则性领导\n基于数据的决策 以身作则 培养团队成员 团队协作实践 # import datetime from dataclasses import dataclass from typing import List, Dict, Optional @dataclass class TeamMember: name: str role: str skills: List[str] availability: float # 0.0 to 1.0 @dataclass class Task: id: str title: str description: str required_skills: List[str] estimated_hours: int priority: str # high, medium, low assigned_to: Optional[str] = None status: str = \u0026#34;todo\u0026#34; # todo, in_progress, done class DevOpsTeam: def __init__(self, name: str): self.name = name self.members: List[TeamMember] = [] self.tasks: List[Task] = [] self.sprint_capacity = 0 def add_member(self, member: TeamMember): \u0026#34;\u0026#34;\u0026#34;添加团队成员\u0026#34;\u0026#34;\u0026#34; self.members.append(member) print(f\u0026#34;Added {member.name} ({member.role}) to team {self.name}\u0026#34;) def calculate_sprint_capacity(self, sprint_hours: int = 80): \u0026#34;\u0026#34;\u0026#34;计算团队冲刺容量\u0026#34;\u0026#34;\u0026#34; total_capacity = 0 for member in self.members: member_capacity = sprint_hours * member.availability total_capacity += member_capacity print(f\u0026#34;{member.name}: {member_capacity} hours\u0026#34;) self.sprint_capacity = total_capacity print(f\u0026#34;Total team capacity: {total_capacity} hours\u0026#34;) return total_capacity def assign_tasks(self): \u0026#34;\u0026#34;\u0026#34;智能任务分配\u0026#34;\u0026#34;\u0026#34; # 按优先级排序任务 sorted_tasks = sorted(self.tasks, key=lambda x: {\u0026#34;high\u0026#34;: 3, \u0026#34;medium\u0026#34;: 2, \u0026#34;low\u0026#34;: 1}[x.priority], reverse=True) for task in sorted_tasks: if task.assigned_to: continue # 找到最适合的团队成员 best_member = self.find_best_assignee(task) if best_member: task.assigned_to = best_member.name print(f\u0026#34;Assigned task \u0026#39;{task.title}\u0026#39; to {best_member.name}\u0026#34;) def find_best_assignee(self, task: Task) -\u0026gt; Optional[TeamMember]: \u0026#34;\u0026#34;\u0026#34;找到最适合执行任务的团队成员\u0026#34;\u0026#34;\u0026#34; candidates = [] for member in self.members: # 检查技能匹配度 skill_match = len(set(task.required_skills) \u0026amp; set(member.skills)) if skill_match \u0026gt; 0: candidates.append((member, skill_match)) if not candidates: return None # 选择技能匹配度最高的成员 candidates.sort(key=lambda x: x[1], reverse=True) return candidates[0][0] def generate_daily_standup_report(self): \u0026#34;\u0026#34;\u0026#34;生成每日站会报告\u0026#34;\u0026#34;\u0026#34; print(f\u0026#34;\\n=== Daily Standup Report for {self.name} ===\u0026#34;) print(f\u0026#34;Date: {datetime.datetime.now().strftime(\u0026#39;%Y-%m-%d\u0026#39;)}\u0026#34;) for member in self.members: member_tasks = [t for t in self.tasks if t.assigned_to == member.name] in_progress = [t for t in member_tasks if t.status == \u0026#34;in_progress\u0026#34;] completed = [t for t in member_tasks if t.status == \u0026#34;done\u0026#34;] print(f\u0026#34;\\n{member.name} ({member.role}):\u0026#34;) print(f\u0026#34; Yesterday: Completed {len(completed)} tasks\u0026#34;) print(f\u0026#34; Today: Working on {len(in_progress)} tasks\u0026#34;) if in_progress: for task in in_progress: print(f\u0026#34; - {task.title}\u0026#34;) # 使用示例 team = DevOpsTeam(\u0026#34;Platform Engineering\u0026#34;) # 添加团队成员 team.add_member(TeamMember(\u0026#34;Alice\u0026#34;, \u0026#34;DevOps Engineer\u0026#34;, [\u0026#34;Python\u0026#34;, \u0026#34;Docker\u0026#34;, \u0026#34;Kubernetes\u0026#34;], 0.8)) team.add_member(TeamMember(\u0026#34;Bob\u0026#34;, \u0026#34;SRE\u0026#34;, [\u0026#34;Go\u0026#34;, \u0026#34;Prometheus\u0026#34;, \u0026#34;Grafana\u0026#34;], 0.9)) team.add_member(TeamMember(\u0026#34;Charlie\u0026#34;, \u0026#34;Platform Engineer\u0026#34;, [\u0026#34;Python\u0026#34;, \u0026#34;Terraform\u0026#34;, \u0026#34;AWS\u0026#34;], 0.7)) # 添加任务 team.tasks.extend([ Task(\u0026#34;T001\u0026#34;, \u0026#34;Setup CI/CD Pipeline\u0026#34;, \u0026#34;Implement GitLab CI/CD\u0026#34;, [\u0026#34;Python\u0026#34;, \u0026#34;Docker\u0026#34;], 16, \u0026#34;high\u0026#34;), Task(\u0026#34;T002\u0026#34;, \u0026#34;Monitor Setup\u0026#34;, \u0026#34;Setup Prometheus monitoring\u0026#34;, [\u0026#34;Prometheus\u0026#34;, \u0026#34;Grafana\u0026#34;], 12, \u0026#34;medium\u0026#34;), Task(\u0026#34;T003\u0026#34;, \u0026#34;Infrastructure as Code\u0026#34;, \u0026#34;Terraform AWS resources\u0026#34;, [\u0026#34;Terraform\u0026#34;, \u0026#34;AWS\u0026#34;], 20, \u0026#34;high\u0026#34;) ]) # 计算容量和分配任务 team.calculate_sprint_capacity() team.assign_tasks() team.generate_daily_standup_report() 核心理念与思考 # 2019 年，70%的开发者认为自己高于平均水平，而 10%的人认为自己低于平均水平。\n相信自动化优于等级制度的信念正是 DevOps 的核心。\nWindows 操作系统会临时关闭整个网络堆栈。如果在短时间内生成了太多的网络连接，操作系统会保护自己。\n构建服务器是一个基础设施，必须确保其正常运行，以便能够可靠地交付软件。\n自动化的每个工作都是你的工作。没有比确保事情自动化更重要或更有价值的任务了\n如果组织中的领导 比其他人更好(更多资源)/更高，你将永远无法实施真正的 DevOps 原则。你将应用最高薪酬人员的意见（HIPO）原则。虽然 DevOps 可以真正挽救生命并拯救你的公司，但 HIPO 是凶猛的动物，可以并且确实会摧毁它们所经之处的一切。\n工作博弈论：\n在武术馆里，让学生帮忙拖地是司空见惯的事情。这样做有很多明显的原因。它向教练表达了尊重，并教会学生自律。\n这里涉及到一个 博弈论问题 。接触到葡萄球菌感染可能会引发严重的健康问题。如果你有机会在你训练的健身房里拖地，要仔细考虑你的回应。人们会观察你清洁地板的能力，如果你做得好，他们会因为尊重你而也做得好。如果你把这个任务看作是“低人一等” 的事情，没有做好，可能会引发两个问题。一是你没有清洁好地板，可能导致健身房的其他会员生病。二是你“感染”了其他人的心态，他们反过来也不会清洁地板。你的行为在现在和未来都会产生影响。 确保你用愉快的表情做出出色的工作。你的生命可能取决于此。\n一个好的团队的特征\n一个明确、令人振奋的目标\n一个以结果为导向的结构 许多公司使用的工具和流程如果不能直接归因于结果，那么它们就是值得质疑的：Skype、电子邮件、漫长的会议、加班。\n能干的团队成员\n统一承诺\n合作的氛围\n需要创造一种尊重的环境，人们能够坦诚开放并期待反馈。如果偏向任何一方的程度过高，就注定会失败。\n招聘流程。许多公司抱怨无法招聘、无法实现多样化招聘，以及无法找到优秀的候选人。\n首先，公司鼓励候选人申请。 接下来，他们浪费时间进行定制的无关测试。 然后他们用一轮比随机还没有预测价值的面试来“雾化”他们。 然后他们就对候选人置之不理，不给任何反馈。 他们撒谎说他们正在努力招聘人才，但实际上他们的流程有问题 然后他们在社交媒体上大声抱怨多样化候选人或任何候选人的参与有多么困难。 以尊重的态度对待人，你就会得到尊重\n卓越的标准\n另一种表达方式是说需要更高程度的 自律 。编写软件、测试和部署需要更高的标准。在部署之前，需要更严格的措施来阅读有关新技术的文档。 要发布没有经过 适当 的 DevOps 生命周期的代码。 在基础设施方面，需要在许多步骤上遵循最佳实践，无论是 Zookeeper 配置、EC2 存储配置，还是 Mongo 或无服务器。 管理层需要高标准。公司中的每个人都能看到，决策时使用的是数据而不是观点、等级、攻击性或渴望佣金的欲望。 外部支持和认可\n当领导者表现出低于平均水平的承诺和诚信时，要求超常贡献是具有挑战性的。 一个部门把困难的任务推给另一个部门。甩锅、逃避/推脱 责任 原则性领导\n发掘技术点\nGitHub - pytest-dev/pytest-testinfra: Testinfra test your infrastructures\n文档地址\n查了一下资料，这类工具还有很多如：\nServerspec: Serverspec 是一个 Ruby DSL（领域特定语言），用于编写基础设施测试。它可以用于测试服务器的状态、配置和软件包。 Goss: Goss（Golang Server Spec）是一个用 Go 编写的服务器测试工具。它使用 YAML 文件来定义测试，并支持检查文件、包、用户、端口等。 Molecule: Molecule 是一个用于测试 Ansible 角色的工具。它可以自动化测试 Ansible 配置的正确性。 KitchenCI: Test Kitchen 是一个基于 Ruby 的工具，用于测试基础设施即代码（Infrastructure as Code）。它可以测试使用 Chef、Puppet、Ansible 等配置管理工具创建的基础设施。 Bats: Bats（Bash Automated Testing System）是一个基于 Bash 的测试框架，用于编写和运行 shell 脚本测试。 Terratest: Terratest 是一个 Go 语言库，用于编写自动化基础设施测试的代码。它可以与 Terraform 一起使用，测试基础设施即代码的正确性。 Kitchen-Terraform: Kitchen-Terraform 是 Test Kitchen 的插件，用于测试 Terraform 配置的正确性。 Pester: Pester 是一个用于测试 PowerShell 脚本的工具，适用于 Windows 环境。 Cucumber: Cucumber 是一个行为驱动开发（BDD）工具，用于编写可执行的规范和测试。它支持多种编程语言，并用于测试各种应用程序类型，而不仅仅是基础设施。 Python DevOps 实战应用 # 自动化运维脚本 # 服务器健康检查 # #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; 服务器健康检查脚本 \u0026#34;\u0026#34;\u0026#34; import psutil import requests import subprocess import socket import time from datetime import datetime from typing import Dict, List, Any import json import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart class HealthChecker: def __init__(self, config: Dict[str, Any]): self.config = config self.results = {} self.alerts = [] def check_cpu_usage(self) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;检查 CPU 使用率\u0026#34;\u0026#34;\u0026#34; cpu_percent = psutil.cpu_percent(interval=1) threshold = self.config.get(\u0026#39;cpu_threshold\u0026#39;, 80) result = { \u0026#39;metric\u0026#39;: \u0026#39;cpu_usage\u0026#39;, \u0026#39;value\u0026#39;: cpu_percent, \u0026#39;threshold\u0026#39;: threshold, \u0026#39;status\u0026#39;: \u0026#39;OK\u0026#39; if cpu_percent \u0026lt; threshold else \u0026#39;CRITICAL\u0026#39;, \u0026#39;timestamp\u0026#39;: datetime.now().isoformat() } if result[\u0026#39;status\u0026#39;] == \u0026#39;CRITICAL\u0026#39;: self.alerts.append(f\u0026#34;High CPU usage: {cpu_percent}%\u0026#34;) return result def check_memory_usage(self) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;检查内存使用率\u0026#34;\u0026#34;\u0026#34; memory = psutil.virtual_memory() threshold = self.config.get(\u0026#39;memory_threshold\u0026#39;, 85) result = { \u0026#39;metric\u0026#39;: \u0026#39;memory_usage\u0026#39;, \u0026#39;value\u0026#39;: memory.percent, \u0026#39;threshold\u0026#39;: threshold, \u0026#39;status\u0026#39;: \u0026#39;OK\u0026#39; if memory.percent \u0026lt; threshold else \u0026#39;CRITICAL\u0026#39;, \u0026#39;timestamp\u0026#39;: datetime.now().isoformat() } if result[\u0026#39;status\u0026#39;] == \u0026#39;CRITICAL\u0026#39;: self.alerts.append(f\u0026#34;High memory usage: {memory.percent}%\u0026#34;) return result def check_disk_usage(self) -\u0026gt; List[Dict[str, Any]]: \u0026#34;\u0026#34;\u0026#34;检查磁盘使用率\u0026#34;\u0026#34;\u0026#34; results = [] threshold = self.config.get(\u0026#39;disk_threshold\u0026#39;, 90) for partition in psutil.disk_partitions(): try: usage = psutil.disk_usage(partition.mountpoint) percent = (usage.used / usage.total) * 100 result = { \u0026#39;metric\u0026#39;: \u0026#39;disk_usage\u0026#39;, \u0026#39;mountpoint\u0026#39;: partition.mountpoint, \u0026#39;value\u0026#39;: percent, \u0026#39;threshold\u0026#39;: threshold, \u0026#39;status\u0026#39;: \u0026#39;OK\u0026#39; if percent \u0026lt; threshold else \u0026#39;CRITICAL\u0026#39;, \u0026#39;timestamp\u0026#39;: datetime.now().isoformat() } if result[\u0026#39;status\u0026#39;] == \u0026#39;CRITICAL\u0026#39;: self.alerts.append(f\u0026#34;High disk usage on {partition.mountpoint}: {percent:.1f}%\u0026#34;) results.append(result) except PermissionError: continue return results def check_service_status(self) -\u0026gt; List[Dict[str, Any]]: \u0026#34;\u0026#34;\u0026#34;检查服务状态\u0026#34;\u0026#34;\u0026#34; results = [] services = self.config.get(\u0026#39;services\u0026#39;, []) for service in services: try: result = subprocess.run( [\u0026#39;systemctl\u0026#39;, \u0026#39;is-active\u0026#39;, service], capture_output=True, text=True ) status = result.stdout.strip() is_active = status == \u0026#39;active\u0026#39; check_result = { \u0026#39;metric\u0026#39;: \u0026#39;service_status\u0026#39;, \u0026#39;service\u0026#39;: service, \u0026#39;status\u0026#39;: \u0026#39;OK\u0026#39; if is_active else \u0026#39;CRITICAL\u0026#39;, \u0026#39;value\u0026#39;: status, \u0026#39;timestamp\u0026#39;: datetime.now().isoformat() } if not is_active: self.alerts.append(f\u0026#34;Service {service} is not active: {status}\u0026#34;) results.append(check_result) except Exception as e: results.append({ \u0026#39;metric\u0026#39;: \u0026#39;service_status\u0026#39;, \u0026#39;service\u0026#39;: service, \u0026#39;status\u0026#39;: \u0026#39;ERROR\u0026#39;, \u0026#39;error\u0026#39;: str(e), \u0026#39;timestamp\u0026#39;: datetime.now().isoformat() }) return results def check_port_connectivity(self) -\u0026gt; List[Dict[str, Any]]: \u0026#34;\u0026#34;\u0026#34;检查端口连通性\u0026#34;\u0026#34;\u0026#34; results = [] ports = self.config.get(\u0026#39;ports\u0026#39;, []) for port_config in ports: host = port_config.get(\u0026#39;host\u0026#39;, \u0026#39;localhost\u0026#39;) port = port_config[\u0026#39;port\u0026#39;] timeout = port_config.get(\u0026#39;timeout\u0026#39;, 5) try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) result = sock.connect_ex((host, port)) sock.close() is_open = result == 0 check_result = { \u0026#39;metric\u0026#39;: \u0026#39;port_connectivity\u0026#39;, \u0026#39;host\u0026#39;: host, \u0026#39;port\u0026#39;: port, \u0026#39;status\u0026#39;: \u0026#39;OK\u0026#39; if is_open else \u0026#39;CRITICAL\u0026#39;, \u0026#39;timestamp\u0026#39;: datetime.now().isoformat() } if not is_open: self.alerts.append(f\u0026#34;Port {host}:{port} is not accessible\u0026#34;) results.append(check_result) except Exception as e: results.append({ \u0026#39;metric\u0026#39;: \u0026#39;port_connectivity\u0026#39;, \u0026#39;host\u0026#39;: host, \u0026#39;port\u0026#39;: port, \u0026#39;status\u0026#39;: \u0026#39;ERROR\u0026#39;, \u0026#39;error\u0026#39;: str(e), \u0026#39;timestamp\u0026#39;: datetime.now().isoformat() }) return results def check_http_endpoints(self) -\u0026gt; List[Dict[str, Any]]: \u0026#34;\u0026#34;\u0026#34;检查 HTTP 端点\u0026#34;\u0026#34;\u0026#34; results = [] endpoints = self.config.get(\u0026#39;http_endpoints\u0026#39;, []) for endpoint in endpoints: url = endpoint[\u0026#39;url\u0026#39;] timeout = endpoint.get(\u0026#39;timeout\u0026#39;, 10) expected_status = endpoint.get(\u0026#39;expected_status\u0026#39;, 200) try: response = requests.get(url, timeout=timeout) is_healthy = response.status_code == expected_status result = { \u0026#39;metric\u0026#39;: \u0026#39;http_endpoint\u0026#39;, \u0026#39;url\u0026#39;: url, \u0026#39;status_code\u0026#39;: response.status_code, \u0026#39;response_time\u0026#39;: response.elapsed.total_seconds(), \u0026#39;status\u0026#39;: \u0026#39;OK\u0026#39; if is_healthy else \u0026#39;CRITICAL\u0026#39;, \u0026#39;timestamp\u0026#39;: datetime.now().isoformat() } if not is_healthy: self.alerts.append(f\u0026#34;HTTP endpoint {url} returned status {response.status_code}\u0026#34;) results.append(result) except Exception as e: results.append({ \u0026#39;metric\u0026#39;: \u0026#39;http_endpoint\u0026#39;, \u0026#39;url\u0026#39;: url, \u0026#39;status\u0026#39;: \u0026#39;ERROR\u0026#39;, \u0026#39;error\u0026#39;: str(e), \u0026#39;timestamp\u0026#39;: datetime.now().isoformat() }) return results def run_all_checks(self) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;运行所有健康检查\u0026#34;\u0026#34;\u0026#34; print(\u0026#34;Running health checks...\u0026#34;) self.results = { \u0026#39;hostname\u0026#39;: socket.gethostname(), \u0026#39;timestamp\u0026#39;: datetime.now().isoformat(), \u0026#39;checks\u0026#39;: { \u0026#39;cpu\u0026#39;: self.check_cpu_usage(), \u0026#39;memory\u0026#39;: self.check_memory_usage(), \u0026#39;disk\u0026#39;: self.check_disk_usage(), \u0026#39;services\u0026#39;: self.check_service_status(), \u0026#39;ports\u0026#39;: self.check_port_connectivity(), \u0026#39;http_endpoints\u0026#39;: self.check_http_endpoints() }, \u0026#39;alerts\u0026#39;: self.alerts } return self.results def send_alerts(self): \u0026#34;\u0026#34;\u0026#34;发送告警\u0026#34;\u0026#34;\u0026#34; if not self.alerts: return email_config = self.config.get(\u0026#39;email\u0026#39;, {}) if not email_config: print(\u0026#34;No email configuration found, skipping alerts\u0026#34;) return try: msg = MIMEMultipart() msg[\u0026#39;From\u0026#39;] = email_config[\u0026#39;from\u0026#39;] msg[\u0026#39;To\u0026#39;] = \u0026#39;, \u0026#39;.join(email_config[\u0026#39;to\u0026#39;]) msg[\u0026#39;Subject\u0026#39;] = f\u0026#34;Health Check Alerts - {socket.gethostname()}\u0026#34; body = f\u0026#34;\u0026#34;\u0026#34; Health Check Alerts for {socket.gethostname()} Time: {datetime.now().strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)} Alerts: {chr(10).join(f\u0026#34;- {alert}\u0026#34; for alert in self.alerts)} Full report attached. \u0026#34;\u0026#34;\u0026#34; msg.attach(MIMEText(body, \u0026#39;plain\u0026#39;)) # 添加详细报告 report_json = json.dumps(self.results, indent=2) attachment = MIMEText(report_json, \u0026#39;plain\u0026#39;) attachment.add_header(\u0026#39;Content-Disposition\u0026#39;, \u0026#39;attachment\u0026#39;, filename=\u0026#39;health_report.json\u0026#39;) msg.attach(attachment) # 发送邮件 server = smtplib.SMTP(email_config[\u0026#39;smtp_server\u0026#39;], email_config[\u0026#39;smtp_port\u0026#39;]) if email_config.get(\u0026#39;use_tls\u0026#39;): server.starttls() if email_config.get(\u0026#39;username\u0026#39;): server.login(email_config[\u0026#39;username\u0026#39;], email_config[\u0026#39;password\u0026#39;]) server.send_message(msg) server.quit() print(f\u0026#34;Alert email sent to {email_config[\u0026#39;to\u0026#39;]}\u0026#34;) except Exception as e: print(f\u0026#34;Failed to send alert email: {e}\u0026#34;) def save_results(self, filename: str = None): \u0026#34;\u0026#34;\u0026#34;保存检查结果\u0026#34;\u0026#34;\u0026#34; if not filename: timestamp = datetime.now().strftime(\u0026#34;%Y%m%d_%H%M%S\u0026#34;) filename = f\u0026#34;health_check_{timestamp}.json\u0026#34; with open(filename, \u0026#39;w\u0026#39;) as f: json.dump(self.results, f, indent=2) print(f\u0026#34;Results saved to: {filename}\u0026#34;) # 配置示例 config = { \u0026#39;cpu_threshold\u0026#39;: 80, \u0026#39;memory_threshold\u0026#39;: 85, \u0026#39;disk_threshold\u0026#39;: 90, \u0026#39;services\u0026#39;: [\u0026#39;nginx\u0026#39;, \u0026#39;mysql\u0026#39;, \u0026#39;redis\u0026#39;], \u0026#39;ports\u0026#39;: [ {\u0026#39;host\u0026#39;: \u0026#39;localhost\u0026#39;, \u0026#39;port\u0026#39;: 80}, {\u0026#39;host\u0026#39;: \u0026#39;localhost\u0026#39;, \u0026#39;port\u0026#39;: 3306}, {\u0026#39;host\u0026#39;: \u0026#39;localhost\u0026#39;, \u0026#39;port\u0026#39;: 6379} ], \u0026#39;http_endpoints\u0026#39;: [ {\u0026#39;url\u0026#39;: \u0026#39;http://localhost/health\u0026#39;, \u0026#39;expected_status\u0026#39;: 200}, {\u0026#39;url\u0026#39;: \u0026#39;https://api.example.com/status\u0026#39;, \u0026#39;expected_status\u0026#39;: 200} ], \u0026#39;email\u0026#39;: { \u0026#39;smtp_server\u0026#39;: \u0026#39;smtp.gmail.com\u0026#39;, \u0026#39;smtp_port\u0026#39;: 587, \u0026#39;use_tls\u0026#39;: True, \u0026#39;username\u0026#39;: \u0026#39;alerts@example.com\u0026#39;, \u0026#39;password\u0026#39;: \u0026#39;your_password\u0026#39;, \u0026#39;from\u0026#39;: \u0026#39;alerts@example.com\u0026#39;, \u0026#39;to\u0026#39;: [\u0026#39;admin@example.com\u0026#39;, \u0026#39;ops@example.com\u0026#39;] } } # 使用示例 if __name__ == \u0026#34;__main__\u0026#34;: checker = HealthChecker(config) results = checker.run_all_checks() # 打印摘要 print(f\u0026#34;\\nHealth Check Summary for {results[\u0026#39;hostname\u0026#39;]}:\u0026#34;) print(f\u0026#34;Time: {results[\u0026#39;timestamp\u0026#39;]}\u0026#34;) print(f\u0026#34;Alerts: {len(results[\u0026#39;alerts\u0026#39;])}\u0026#34;) if results[\u0026#39;alerts\u0026#39;]: print(\u0026#34;\\nAlerts:\u0026#34;) for alert in results[\u0026#39;alerts\u0026#39;]: print(f\u0026#34; - {alert}\u0026#34;) # 发送告警 checker.send_alerts() else: print(\u0026#34;All checks passed!\u0026#34;) # 保存结果 checker.save_results() 基础设施即代码 (IaC) # AWS 资源管理 # #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; AWS 资源管理工具 \u0026#34;\u0026#34;\u0026#34; import boto3 import json import time from typing import Dict, List, Any, Optional from botocore.exceptions import ClientError import yaml class AWSResourceManager: def __init__(self, region: str = \u0026#39;us-west-2\u0026#39;, profile: str = None): self.region = region self.session = boto3.Session(profile_name=profile) # 初始化各种 AWS 客户端 self.ec2 = self.session.client(\u0026#39;ec2\u0026#39;, region_name=region) self.s3 = self.session.client(\u0026#39;s3\u0026#39;) self.rds = self.session.client(\u0026#39;rds\u0026#39;, region_name=region) self.elbv2 = self.session.client(\u0026#39;elbv2\u0026#39;, region_name=region) self.route53 = self.session.client(\u0026#39;route53\u0026#39;) self.cloudformation = self.session.client(\u0026#39;cloudformation\u0026#39;, region_name=region) def create_vpc(self, vpc_config: Dict[str, Any]) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;创建 VPC\u0026#34;\u0026#34;\u0026#34; try: # 创建 VPC vpc_response = self.ec2.create_vpc( CidrBlock=vpc_config[\u0026#39;cidr_block\u0026#39;], TagSpecifications=[{ \u0026#39;ResourceType\u0026#39;: \u0026#39;vpc\u0026#39;, \u0026#39;Tags\u0026#39;: [ {\u0026#39;Key\u0026#39;: \u0026#39;Name\u0026#39;, \u0026#39;Value\u0026#39;: vpc_config[\u0026#39;name\u0026#39;]}, {\u0026#39;Key\u0026#39;: \u0026#39;Environment\u0026#39;, \u0026#39;Value\u0026#39;: vpc_config.get(\u0026#39;environment\u0026#39;, \u0026#39;dev\u0026#39;)} ] }] ) vpc_id = vpc_response[\u0026#39;Vpc\u0026#39;][\u0026#39;VpcId\u0026#39;] print(f\u0026#34;Created VPC: {vpc_id}\u0026#34;) # 等待 VPC 可用 self.ec2.get_waiter(\u0026#39;vpc_available\u0026#39;).wait(VpcIds=[vpc_id]) # 启用 DNS 主机名 self.ec2.modify_vpc_attribute( VpcId=vpc_id, EnableDnsHostnames={\u0026#39;Value\u0026#39;: True} ) # 创建子网 for subnet_config in vpc_config.get(\u0026#39;subnets\u0026#39;, []): self.create_subnet(vpc_id, subnet_config) # 创建互联网网关 if vpc_config.get(\u0026#39;internet_gateway\u0026#39;, True): self.create_internet_gateway(vpc_id, vpc_config[\u0026#39;name\u0026#39;]) return vpc_id except ClientError as e: print(f\u0026#34;Error creating VPC: {e}\u0026#34;) raise def create_subnet(self, vpc_id: str, subnet_config: Dict[str, Any]) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;创建子网\u0026#34;\u0026#34;\u0026#34; try: subnet_response = self.ec2.create_subnet( VpcId=vpc_id, CidrBlock=subnet_config[\u0026#39;cidr_block\u0026#39;], AvailabilityZone=subnet_config.get(\u0026#39;availability_zone\u0026#39;), TagSpecifications=[{ \u0026#39;ResourceType\u0026#39;: \u0026#39;subnet\u0026#39;, \u0026#39;Tags\u0026#39;: [ {\u0026#39;Key\u0026#39;: \u0026#39;Name\u0026#39;, \u0026#39;Value\u0026#39;: subnet_config[\u0026#39;name\u0026#39;]}, {\u0026#39;Key\u0026#39;: \u0026#39;Type\u0026#39;, \u0026#39;Value\u0026#39;: subnet_config.get(\u0026#39;type\u0026#39;, \u0026#39;private\u0026#39;)} ] }] ) subnet_id = subnet_response[\u0026#39;Subnet\u0026#39;][\u0026#39;SubnetId\u0026#39;] print(f\u0026#34;Created subnet: {subnet_id}\u0026#34;) # 如果是公共子网，启用自动分配公网 IP if subnet_config.get(\u0026#39;type\u0026#39;) == \u0026#39;public\u0026#39;: self.ec2.modify_subnet_attribute( SubnetId=subnet_id, MapPublicIpOnLaunch={\u0026#39;Value\u0026#39;: True} ) return subnet_id except ClientError as e: print(f\u0026#34;Error creating subnet: {e}\u0026#34;) raise def create_internet_gateway(self, vpc_id: str, name: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;创建互联网网关\u0026#34;\u0026#34;\u0026#34; try: # 创建互联网网关 igw_response = self.ec2.create_internet_gateway( TagSpecifications=[{ \u0026#39;ResourceType\u0026#39;: \u0026#39;internet-gateway\u0026#39;, \u0026#39;Tags\u0026#39;: [{\u0026#39;Key\u0026#39;: \u0026#39;Name\u0026#39;, \u0026#39;Value\u0026#39;: f\u0026#34;{name}-igw\u0026#34;}] }] ) igw_id = igw_response[\u0026#39;InternetGateway\u0026#39;][\u0026#39;InternetGatewayId\u0026#39;] print(f\u0026#34;Created Internet Gateway: {igw_id}\u0026#34;) # 附加到 VPC self.ec2.attach_internet_gateway( InternetGatewayId=igw_id, VpcId=vpc_id ) return igw_id except ClientError as e: print(f\u0026#34;Error creating Internet Gateway: {e}\u0026#34;) raise def create_security_group(self, vpc_id: str, sg_config: Dict[str, Any]) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;创建安全组\u0026#34;\u0026#34;\u0026#34; try: sg_response = self.ec2.create_security_group( GroupName=sg_config[\u0026#39;name\u0026#39;], Description=sg_config[\u0026#39;description\u0026#39;], VpcId=vpc_id, TagSpecifications=[{ \u0026#39;ResourceType\u0026#39;: \u0026#39;security-group\u0026#39;, \u0026#39;Tags\u0026#39;: [{\u0026#39;Key\u0026#39;: \u0026#39;Name\u0026#39;, \u0026#39;Value\u0026#39;: sg_config[\u0026#39;name\u0026#39;]}] }] ) sg_id = sg_response[\u0026#39;GroupId\u0026#39;] print(f\u0026#34;Created Security Group: {sg_id}\u0026#34;) # 添加入站规则 for rule in sg_config.get(\u0026#39;ingress_rules\u0026#39;, []): self.ec2.authorize_security_group_ingress( GroupId=sg_id, IpPermissions=[{ \u0026#39;IpProtocol\u0026#39;: rule[\u0026#39;protocol\u0026#39;], \u0026#39;FromPort\u0026#39;: rule[\u0026#39;from_port\u0026#39;], \u0026#39;ToPort\u0026#39;: rule[\u0026#39;to_port\u0026#39;], \u0026#39;IpRanges\u0026#39;: [{\u0026#39;CidrIp\u0026#39;: rule[\u0026#39;cidr\u0026#39;]}] }] ) return sg_id except ClientError as e: print(f\u0026#34;Error creating Security Group: {e}\u0026#34;) raise def launch_ec2_instance(self, instance_config: Dict[str, Any]) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;启动 EC2 实例\u0026#34;\u0026#34;\u0026#34; try: # 用户数据脚本 user_data = instance_config.get(\u0026#39;user_data\u0026#39;, \u0026#39;\u0026#39;) if isinstance(user_data, list): user_data = \u0026#39;\\n\u0026#39;.join(user_data) response = self.ec2.run_instances( ImageId=instance_config[\u0026#39;ami_id\u0026#39;], MinCount=1, MaxCount=1, InstanceType=instance_config[\u0026#39;instance_type\u0026#39;], KeyName=instance_config.get(\u0026#39;key_name\u0026#39;), SecurityGroupIds=instance_config.get(\u0026#39;security_group_ids\u0026#39;, []), SubnetId=instance_config.get(\u0026#39;subnet_id\u0026#39;), UserData=user_data, TagSpecifications=[{ \u0026#39;ResourceType\u0026#39;: \u0026#39;instance\u0026#39;, \u0026#39;Tags\u0026#39;: [ {\u0026#39;Key\u0026#39;: \u0026#39;Name\u0026#39;, \u0026#39;Value\u0026#39;: instance_config[\u0026#39;name\u0026#39;]}, {\u0026#39;Key\u0026#39;: \u0026#39;Environment\u0026#39;, \u0026#39;Value\u0026#39;: instance_config.get(\u0026#39;environment\u0026#39;, \u0026#39;dev\u0026#39;)} ] }] ) instance_id = response[\u0026#39;Instances\u0026#39;][0][\u0026#39;InstanceId\u0026#39;] print(f\u0026#34;Launched EC2 instance: {instance_id}\u0026#34;) # 等待实例运行 print(\u0026#34;Waiting for instance to be running...\u0026#34;) self.ec2.get_waiter(\u0026#39;instance_running\u0026#39;).wait(InstanceIds=[instance_id]) return instance_id except ClientError as e: print(f\u0026#34;Error launching EC2 instance: {e}\u0026#34;) raise def create_load_balancer(self, lb_config: Dict[str, Any]) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;创建负载均衡器\u0026#34;\u0026#34;\u0026#34; try: response = self.elbv2.create_load_balancer( Name=lb_config[\u0026#39;name\u0026#39;], Subnets=lb_config[\u0026#39;subnet_ids\u0026#39;], SecurityGroups=lb_config.get(\u0026#39;security_group_ids\u0026#39;, []), Scheme=lb_config.get(\u0026#39;scheme\u0026#39;, \u0026#39;internet-facing\u0026#39;), Type=lb_config.get(\u0026#39;type\u0026#39;, \u0026#39;application\u0026#39;), Tags=[ {\u0026#39;Key\u0026#39;: \u0026#39;Name\u0026#39;, \u0026#39;Value\u0026#39;: lb_config[\u0026#39;name\u0026#39;]}, {\u0026#39;Key\u0026#39;: \u0026#39;Environment\u0026#39;, \u0026#39;Value\u0026#39;: lb_config.get(\u0026#39;environment\u0026#39;, \u0026#39;dev\u0026#39;)} ] ) lb_arn = response[\u0026#39;LoadBalancers\u0026#39;][0][\u0026#39;LoadBalancerArn\u0026#39;] print(f\u0026#34;Created Load Balancer: {lb_arn}\u0026#34;) return lb_arn except ClientError as e: print(f\u0026#34;Error creating Load Balancer: {e}\u0026#34;) raise def deploy_infrastructure(self, config_file: str): \u0026#34;\u0026#34;\u0026#34;部署完整基础设施\u0026#34;\u0026#34;\u0026#34; with open(config_file, \u0026#39;r\u0026#39;) as f: config = yaml.safe_load(f) print(f\u0026#34;Deploying infrastructure from {config_file}\u0026#34;) # 创建 VPC vpc_id = self.create_vpc(config[\u0026#39;vpc\u0026#39;]) # 创建安全组 security_groups = {} for sg_config in config.get(\u0026#39;security_groups\u0026#39;, []): sg_id = self.create_security_group(vpc_id, sg_config) security_groups[sg_config[\u0026#39;name\u0026#39;]] = sg_id # 启动 EC2 实例 instances = {} for instance_config in config.get(\u0026#39;instances\u0026#39;, []): # 解析安全组引用 if \u0026#39;security_groups\u0026#39; in instance_config: instance_config[\u0026#39;security_group_ids\u0026#39;] = [ security_groups[sg_name] for sg_name in instance_config[\u0026#39;security_groups\u0026#39;] ] instance_id = self.launch_ec2_instance(instance_config) instances[instance_config[\u0026#39;name\u0026#39;]] = instance_id # 创建负载均衡器 for lb_config in config.get(\u0026#39;load_balancers\u0026#39;, []): if \u0026#39;security_groups\u0026#39; in lb_config: lb_config[\u0026#39;security_group_ids\u0026#39;] = [ security_groups[sg_name] for sg_name in lb_config[\u0026#39;security_groups\u0026#39;] ] self.create_load_balancer(lb_config) print(\u0026#34;Infrastructure deployment completed!\u0026#34;) return { \u0026#39;vpc_id\u0026#39;: vpc_id, \u0026#39;security_groups\u0026#39;: security_groups, \u0026#39;instances\u0026#39;: instances } # 基础设施配置示例 infrastructure_config = \u0026#34;\u0026#34;\u0026#34; vpc: name: \u0026#34;my-vpc\u0026#34; cidr_block: \u0026#34;10.0.0.0/16\u0026#34; environment: \u0026#34;production\u0026#34; internet_gateway: true subnets: - name: \u0026#34;public-subnet-1\u0026#34; cidr_block: \u0026#34;10.0.1.0/24\u0026#34; type: \u0026#34;public\u0026#34; availability_zone: \u0026#34;us-west-2a\u0026#34; - name: \u0026#34;private-subnet-1\u0026#34; cidr_block: \u0026#34;10.0.2.0/24\u0026#34; type: \u0026#34;private\u0026#34; availability_zone: \u0026#34;us-west-2a\u0026#34; security_groups: - name: \u0026#34;web-sg\u0026#34; description: \u0026#34;Security group for web servers\u0026#34; ingress_rules: - protocol: \u0026#34;tcp\u0026#34; from_port: 80 to_port: 80 cidr: \u0026#34;0.0.0.0/0\u0026#34; - protocol: \u0026#34;tcp\u0026#34; from_port: 443 to_port: 443 cidr: \u0026#34;0.0.0.0/0\u0026#34; - protocol: \u0026#34;tcp\u0026#34; from_port: 22 to_port: 22 cidr: \u0026#34;10.0.0.0/16\u0026#34; instances: - name: \u0026#34;web-server-1\u0026#34; ami_id: \u0026#34;ami-0c02fb55956c7d316\u0026#34; # Amazon Linux 2 instance_type: \u0026#34;t3.micro\u0026#34; key_name: \u0026#34;my-key-pair\u0026#34; security_groups: [\u0026#34;web-sg\u0026#34;] environment: \u0026#34;production\u0026#34; user_data: | #!/bin/bash yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo \u0026#34;\u0026lt;h1\u0026gt;Hello from Web Server 1\u0026lt;/h1\u0026gt;\u0026#34; \u0026gt; /var/www/html/index.html load_balancers: - name: \u0026#34;web-lb\u0026#34; type: \u0026#34;application\u0026#34; scheme: \u0026#34;internet-facing\u0026#34; security_groups: [\u0026#34;web-sg\u0026#34;] environment: \u0026#34;production\u0026#34; \u0026#34;\u0026#34;\u0026#34; # 使用示例 if __name__ == \u0026#34;__main__\u0026#34;: # 保存配置到文件 with open(\u0026#39;infrastructure.yaml\u0026#39;, \u0026#39;w\u0026#39;) as f: f.write(infrastructure_config) # 部署基础设施 manager = AWSResourceManager(region=\u0026#39;us-west-2\u0026#39;) result = manager.deploy_infrastructure(\u0026#39;infrastructure.yaml\u0026#39;) print(f\u0026#34;Deployment result: {json.dumps(result, indent=2)}\u0026#34;) CI/CD 自动化 # GitLab CI/CD 管理 # #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; GitLab CI/CD 管理工具 \u0026#34;\u0026#34;\u0026#34; import requests import json import time from typing import Dict, List, Any, Optional import yaml class GitLabCIManager: def __init__(self, gitlab_url: str, access_token: str): self.gitlab_url = gitlab_url.rstrip(\u0026#39;/\u0026#39;) self.access_token = access_token self.headers = { \u0026#39;Authorization\u0026#39;: f\u0026#39;Bearer {access_token}\u0026#39;, \u0026#39;Content-Type\u0026#39;: \u0026#39;application/json\u0026#39; } def get_project(self, project_id: str) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;获取项目信息\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{self.gitlab_url}/api/v4/projects/{project_id}\u0026#34; response = requests.get(url, headers=self.headers) response.raise_for_status() return response.json() def get_pipelines(self, project_id: str, status: str = None) -\u0026gt; List[Dict[str, Any]]: \u0026#34;\u0026#34;\u0026#34;获取流水线列表\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{self.gitlab_url}/api/v4/projects/{project_id}/pipelines\u0026#34; params = {} if status: params[\u0026#39;status\u0026#39;] = status response = requests.get(url, headers=self.headers, params=params) response.raise_for_status() return response.json() def get_pipeline_jobs(self, project_id: str, pipeline_id: str) -\u0026gt; List[Dict[str, Any]]: \u0026#34;\u0026#34;\u0026#34;获取流水线的作业列表\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{self.gitlab_url}/api/v4/projects/{project_id}/pipelines/{pipeline_id}/jobs\u0026#34; response = requests.get(url, headers=self.headers) response.raise_for_status() return response.json() def trigger_pipeline(self, project_id: str, ref: str = \u0026#39;main\u0026#39;, variables: Dict[str, str] = None) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;触发流水线\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{self.gitlab_url}/api/v4/projects/{project_id}/pipeline\u0026#34; data = {\u0026#39;ref\u0026#39;: ref} if variables: data[\u0026#39;variables\u0026#39;] = [ {\u0026#39;key\u0026#39;: key, \u0026#39;value\u0026#39;: value} for key, value in variables.items() ] response = requests.post(url, headers=self.headers, json=data) response.raise_for_status() return response.json() def wait_for_pipeline(self, project_id: str, pipeline_id: str, timeout: int = 1800) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;等待流水线完成\u0026#34;\u0026#34;\u0026#34; start_time = time.time() while time.time() - start_time \u0026lt; timeout: url = f\u0026#34;{self.gitlab_url}/api/v4/projects/{project_id}/pipelines/{pipeline_id}\u0026#34; response = requests.get(url, headers=self.headers) response.raise_for_status() pipeline = response.json() status = pipeline[\u0026#39;status\u0026#39;] print(f\u0026#34;Pipeline {pipeline_id} status: {status}\u0026#34;) if status in [\u0026#39;success\u0026#39;, \u0026#39;failed\u0026#39;, \u0026#39;canceled\u0026#39;, \u0026#39;skipped\u0026#39;]: return status time.sleep(30) raise TimeoutError(f\u0026#34;Pipeline {pipeline_id} did not complete within {timeout} seconds\u0026#34;) def get_job_log(self, project_id: str, job_id: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;获取作业日志\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{self.gitlab_url}/api/v4/projects/{project_id}/jobs/{job_id}/trace\u0026#34; response = requests.get(url, headers=self.headers) response.raise_for_status() return response.text def create_deployment(self, project_id: str, deployment_config: Dict[str, Any]) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;创建部署\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{self.gitlab_url}/api/v4/projects/{project_id}/deployments\u0026#34; response = requests.post(url, headers=self.headers, json=deployment_config) response.raise_for_status() return response.json() def generate_gitlab_ci_yaml(self, config: Dict[str, Any]) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;生成 .gitlab-ci.yml 文件\u0026#34;\u0026#34;\u0026#34; gitlab_ci = { \u0026#39;stages\u0026#39;: config.get(\u0026#39;stages\u0026#39;, [\u0026#39;build\u0026#39;, \u0026#39;test\u0026#39;, \u0026#39;deploy\u0026#39;]), \u0026#39;variables\u0026#39;: config.get(\u0026#39;variables\u0026#39;, {}), \u0026#39;before_script\u0026#39;: config.get(\u0026#39;before_script\u0026#39;, []), \u0026#39;after_script\u0026#39;: config.get(\u0026#39;after_script\u0026#39;, []) } # 添加作业 for job_name, job_config in config.get(\u0026#39;jobs\u0026#39;, {}).items(): gitlab_ci[job_name] = job_config return yaml.dump(gitlab_ci, default_flow_style=False) def deploy_application(self, project_id: str, environment: str, variables: Dict[str, str] = None) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;部署应用程序\u0026#34;\u0026#34;\u0026#34; print(f\u0026#34;Deploying to {environment}...\u0026#34;) # 触发部署流水线 deploy_variables = variables or {} deploy_variables[\u0026#39;ENVIRONMENT\u0026#39;] = environment pipeline = self.trigger_pipeline(project_id, variables=deploy_variables) pipeline_id = pipeline[\u0026#39;id\u0026#39;] print(f\u0026#34;Triggered pipeline {pipeline_id}\u0026#34;) # 等待流水线完成 status = self.wait_for_pipeline(project_id, pipeline_id) if status == \u0026#39;success\u0026#39;: print(f\u0026#34;Deployment to {environment} successful!\u0026#34;) else: print(f\u0026#34;Deployment to {environment} failed with status: {status}\u0026#34;) # 获取失败的作业日志 jobs = self.get_pipeline_jobs(project_id, pipeline_id) for job in jobs: if job[\u0026#39;status\u0026#39;] == \u0026#39;failed\u0026#39;: print(f\u0026#34;\\nFailed job: {job[\u0026#39;name\u0026#39;]}\u0026#34;) log = self.get_job_log(project_id, job[\u0026#39;id\u0026#39;]) print(f\u0026#34;Log:\\n{log[-1000:]}\u0026#34;) # 显示最后 1000 字符 return { \u0026#39;pipeline_id\u0026#39;: pipeline_id, \u0026#39;status\u0026#39;: status, \u0026#39;environment\u0026#39;: environment } class DockerManager: def __init__(self): import docker self.client = docker.from_env() def build_image(self, dockerfile_path: str, image_name: str, tag: str = \u0026#39;latest\u0026#39;) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;构建 Docker 镜像\u0026#34;\u0026#34;\u0026#34; print(f\u0026#34;Building Docker image: {image_name}:{tag}\u0026#34;) image, logs = self.client.images.build( path=dockerfile_path, tag=f\u0026#34;{image_name}:{tag}\u0026#34;, rm=True ) for log in logs: if \u0026#39;stream\u0026#39; in log: print(log[\u0026#39;stream\u0026#39;].strip()) print(f\u0026#34;Image built successfully: {image.id}\u0026#34;) return image.id def push_image(self, image_name: str, tag: str = \u0026#39;latest\u0026#39;, registry: str = None) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34;推送镜像到仓库\u0026#34;\u0026#34;\u0026#34; full_name = f\u0026#34;{image_name}:{tag}\u0026#34; if registry: full_name = f\u0026#34;{registry}/{full_name}\u0026#34; print(f\u0026#34;Pushing image: {full_name}\u0026#34;) try: for line in self.client.images.push(full_name, stream=True, decode=True): if \u0026#39;status\u0026#39; in line: print(f\u0026#34;{line[\u0026#39;status\u0026#39;]}: {line.get(\u0026#39;progress\u0026#39;, \u0026#39;\u0026#39;)}\u0026#34;) print(\u0026#34;Image pushed successfully!\u0026#34;) return True except Exception as e: print(f\u0026#34;Failed to push image: {e}\u0026#34;) return False def run_container(self, image_name: str, container_config: Dict[str, Any]) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;运行容器\u0026#34;\u0026#34;\u0026#34; print(f\u0026#34;Running container from image: {image_name}\u0026#34;) container = self.client.containers.run( image_name, **container_config, detach=True ) print(f\u0026#34;Container started: {container.id}\u0026#34;) return container.id def generate_dockerfile(self, app_config: Dict[str, Any]) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;生成 Dockerfile\u0026#34;\u0026#34;\u0026#34; base_image = app_config.get(\u0026#39;base_image\u0026#39;, \u0026#39;python:3.9-slim\u0026#39;) working_dir = app_config.get(\u0026#39;working_dir\u0026#39;, \u0026#39;/app\u0026#39;) dockerfile_content = f\u0026#34;\u0026#34;\u0026#34; FROM {base_image} WORKDIR {working_dir} # 安装系统依赖 RUN apt-get update \u0026amp;\u0026amp; apt-get install -y \\\\ {\u0026#39; \u0026#39;.join(app_config.get(\u0026#39;system_packages\u0026#39;, []))} \\\\ \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* # 复制依赖文件 COPY requirements.txt . # 安装 Python 依赖 RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 设置环境变量 {chr(10).join(f\u0026#39;ENV {key}={value}\u0026#39; for key, value in app_config.get(\u0026#39;env_vars\u0026#39;, {}).items())} # 暴露端口 EXPOSE {app_config.get(\u0026#39;port\u0026#39;, 8000)} # 健康检查 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\\\ CMD {app_config.get(\u0026#39;health_check\u0026#39;, \u0026#39;curl -f http://localhost:8000/health || exit 1\u0026#39;)} # 启动命令 CMD {json.dumps(app_config.get(\u0026#39;cmd\u0026#39;, [\u0026#39;python\u0026#39;, \u0026#39;app.py\u0026#39;]))} \u0026#34;\u0026#34;\u0026#34; return dockerfile_content.strip() # CI/CD 配置示例 ci_config = { \u0026#39;stages\u0026#39;: [\u0026#39;build\u0026#39;, \u0026#39;test\u0026#39;, \u0026#39;security\u0026#39;, \u0026#39;deploy\u0026#39;], \u0026#39;variables\u0026#39;: { \u0026#39;DOCKER_DRIVER\u0026#39;: \u0026#39;overlay2\u0026#39;, \u0026#39;DOCKER_TLS_CERTDIR\u0026#39;: \u0026#39;/certs\u0026#39; }, \u0026#39;before_script\u0026#39;: [ \u0026#39;echo \u0026#34;Starting CI/CD pipeline\u0026#34;\u0026#39;, \u0026#39;docker info\u0026#39; ], \u0026#39;jobs\u0026#39;: { \u0026#39;build\u0026#39;: { \u0026#39;stage\u0026#39;: \u0026#39;build\u0026#39;, \u0026#39;image\u0026#39;: \u0026#39;docker:latest\u0026#39;, \u0026#39;services\u0026#39;: [\u0026#39;docker:dind\u0026#39;], \u0026#39;script\u0026#39;: [ \u0026#39;docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .\u0026#39;, \u0026#39;docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\u0026#39; ], \u0026#39;only\u0026#39;: [\u0026#39;main\u0026#39;, \u0026#39;develop\u0026#39;] }, \u0026#39;test\u0026#39;: { \u0026#39;stage\u0026#39;: \u0026#39;test\u0026#39;, \u0026#39;image\u0026#39;: \u0026#39;python:3.9\u0026#39;, \u0026#39;script\u0026#39;: [ \u0026#39;pip install -r requirements-dev.txt\u0026#39;, \u0026#39;pytest tests/ --cov=src --cov-report=xml\u0026#39;, \u0026#39;flake8 src/\u0026#39; ], \u0026#39;artifacts\u0026#39;: { \u0026#39;reports\u0026#39;: { \u0026#39;coverage_report\u0026#39;: { \u0026#39;coverage_format\u0026#39;: \u0026#39;cobertura\u0026#39;, \u0026#39;path\u0026#39;: \u0026#39;coverage.xml\u0026#39; } } } }, \u0026#39;security_scan\u0026#39;: { \u0026#39;stage\u0026#39;: \u0026#39;security\u0026#39;, \u0026#39;image\u0026#39;: \u0026#39;owasp/zap2docker-stable\u0026#39;, \u0026#39;script\u0026#39;: [ \u0026#39;zap-baseline.py -t http://localhost:8000\u0026#39; ], \u0026#39;allow_failure\u0026#39;: True }, \u0026#39;deploy_staging\u0026#39;: { \u0026#39;stage\u0026#39;: \u0026#39;deploy\u0026#39;, \u0026#39;image\u0026#39;: \u0026#39;alpine/helm:latest\u0026#39;, \u0026#39;script\u0026#39;: [ \u0026#39;helm upgrade --install myapp ./helm-chart --namespace staging\u0026#39;, \u0026#39;kubectl rollout status deployment/myapp -n staging\u0026#39; ], \u0026#39;environment\u0026#39;: { \u0026#39;name\u0026#39;: \u0026#39;staging\u0026#39;, \u0026#39;url\u0026#39;: \u0026#39;https://staging.example.com\u0026#39; }, \u0026#39;only\u0026#39;: [\u0026#39;develop\u0026#39;] }, \u0026#39;deploy_production\u0026#39;: { \u0026#39;stage\u0026#39;: \u0026#39;deploy\u0026#39;, \u0026#39;image\u0026#39;: \u0026#39;alpine/helm:latest\u0026#39;, \u0026#39;script\u0026#39;: [ \u0026#39;helm upgrade --install myapp ./helm-chart --namespace production\u0026#39;, \u0026#39;kubectl rollout status deployment/myapp -n production\u0026#39; ], \u0026#39;environment\u0026#39;: { \u0026#39;name\u0026#39;: \u0026#39;production\u0026#39;, \u0026#39;url\u0026#39;: \u0026#39;https://example.com\u0026#39; }, \u0026#39;when\u0026#39;: \u0026#39;manual\u0026#39;, \u0026#39;only\u0026#39;: [\u0026#39;main\u0026#39;] } } } # 使用示例 if __name__ == \u0026#34;__main__\u0026#34;: # GitLab CI/CD 管理 gitlab_manager = GitLabCIManager( gitlab_url=\u0026#39;https://gitlab.example.com\u0026#39;, access_token=\u0026#39;your_access_token\u0026#39; ) # 生成 .gitlab-ci.yml gitlab_ci_yaml = gitlab_manager.generate_gitlab_ci_yaml(ci_config) with open(\u0026#39;.gitlab-ci.yml\u0026#39;, \u0026#39;w\u0026#39;) as f: f.write(gitlab_ci_yaml) print(\u0026#34;Generated .gitlab-ci.yml\u0026#34;) # Docker 管理 docker_manager = DockerManager() # 应用配置 app_config = { \u0026#39;base_image\u0026#39;: \u0026#39;python:3.9-slim\u0026#39;, \u0026#39;working_dir\u0026#39;: \u0026#39;/app\u0026#39;, \u0026#39;system_packages\u0026#39;: [\u0026#39;curl\u0026#39;, \u0026#39;git\u0026#39;], \u0026#39;port\u0026#39;: 8000, \u0026#39;env_vars\u0026#39;: { \u0026#39;FLASK_ENV\u0026#39;: \u0026#39;production\u0026#39;, \u0026#39;DATABASE_URL\u0026#39;: \u0026#39;postgresql://user:pass@db:5432/myapp\u0026#39; }, \u0026#39;health_check\u0026#39;: \u0026#39;curl -f http://localhost:8000/health || exit 1\u0026#39;, \u0026#39;cmd\u0026#39;: [\u0026#39;gunicorn\u0026#39;, \u0026#39;--bind\u0026#39;, \u0026#39;0.0.0.0:8000\u0026#39;, \u0026#39;app:app\u0026#39;] } # 生成 Dockerfile dockerfile_content = docker_manager.generate_dockerfile(app_config) with open(\u0026#39;Dockerfile\u0026#39;, \u0026#39;w\u0026#39;) as f: f.write(dockerfile_content) print(\u0026#34;Generated Dockerfile\u0026#34;) 监控和告警 # Prometheus 监控集成 # #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; Prometheus 监控和告警工具 \u0026#34;\u0026#34;\u0026#34; import time import requests import json from typing import Dict, List, Any, Optional from prometheus_client import Counter, Histogram, Gauge, start_http_server from prometheus_client.core import CollectorRegistry import threading import logging class PrometheusMonitor: def __init__(self, prometheus_url: str, port: int = 8000): self.prometheus_url = prometheus_url.rstrip(\u0026#39;/\u0026#39;) self.port = port self.registry = CollectorRegistry() # 定义指标 self.request_count = Counter( \u0026#39;http_requests_total\u0026#39;, \u0026#39;Total HTTP requests\u0026#39;, [\u0026#39;method\u0026#39;, \u0026#39;endpoint\u0026#39;, \u0026#39;status\u0026#39;], registry=self.registry ) self.request_duration = Histogram( \u0026#39;http_request_duration_seconds\u0026#39;, \u0026#39;HTTP request duration\u0026#39;, [\u0026#39;method\u0026#39;, \u0026#39;endpoint\u0026#39;], registry=self.registry ) self.system_cpu_usage = Gauge( \u0026#39;system_cpu_usage_percent\u0026#39;, \u0026#39;System CPU usage percentage\u0026#39;, registry=self.registry ) self.system_memory_usage = Gauge( \u0026#39;system_memory_usage_percent\u0026#39;, \u0026#39;System memory usage percentage\u0026#39;, registry=self.registry ) self.application_health = Gauge( \u0026#39;application_health_status\u0026#39;, \u0026#39;Application health status (1=healthy, 0=unhealthy)\u0026#39;, [\u0026#39;service\u0026#39;], registry=self.registry ) def start_metrics_server(self): \u0026#34;\u0026#34;\u0026#34;启动指标服务器\u0026#34;\u0026#34;\u0026#34; start_http_server(self.port, registry=self.registry) print(f\u0026#34;Metrics server started on port {self.port}\u0026#34;) def record_request(self, method: str, endpoint: str, status: int, duration: float): \u0026#34;\u0026#34;\u0026#34;记录 HTTP 请求指标\u0026#34;\u0026#34;\u0026#34; self.request_count.labels(method=method, endpoint=endpoint, status=str(status)).inc() self.request_duration.labels(method=method, endpoint=endpoint).observe(duration) def update_system_metrics(self): \u0026#34;\u0026#34;\u0026#34;更新系统指标\u0026#34;\u0026#34;\u0026#34; import psutil # CPU 使用率 cpu_percent = psutil.cpu_percent(interval=1) self.system_cpu_usage.set(cpu_percent) # 内存使用率 memory = psutil.virtual_memory() self.system_memory_usage.set(memory.percent) def check_service_health(self, service_name: str, health_url: str) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34;检查服务健康状态\u0026#34;\u0026#34;\u0026#34; try: response = requests.get(health_url, timeout=5) is_healthy = response.status_code == 200 self.application_health.labels(service=service_name).set(1 if is_healthy else 0) return is_healthy except Exception as e: logging.error(f\u0026#34;Health check failed for {service_name}: {e}\u0026#34;) self.application_health.labels(service=service_name).set(0) return False def query_prometheus(self, query: str) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;查询 Prometheus\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{self.prometheus_url}/api/v1/query\u0026#34; params = {\u0026#39;query\u0026#39;: query} response = requests.get(url, params=params) response.raise_for_status() return response.json() def query_range(self, query: str, start: str, end: str, step: str = \u0026#39;15s\u0026#39;) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;范围查询 Prometheus\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{self.prometheus_url}/api/v1/query_range\u0026#34; params = { \u0026#39;query\u0026#39;: query, \u0026#39;start\u0026#39;: start, \u0026#39;end\u0026#39;: end, \u0026#39;step\u0026#39;: step } response = requests.get(url, params=params) response.raise_for_status() return response.json() def get_alerts(self) -\u0026gt; List[Dict[str, Any]]: \u0026#34;\u0026#34;\u0026#34;获取当前告警\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{self.prometheus_url}/api/v1/alerts\u0026#34; response = requests.get(url) response.raise_for_status() return response.json()[\u0026#39;data\u0026#39;][\u0026#39;alerts\u0026#39;] class AlertManager: def __init__(self, webhook_url: str = None, email_config: Dict[str, str] = None): self.webhook_url = webhook_url self.email_config = email_config self.alert_rules = [] def add_alert_rule(self, rule: Dict[str, Any]): \u0026#34;\u0026#34;\u0026#34;添加告警规则\u0026#34;\u0026#34;\u0026#34; self.alert_rules.append(rule) def check_alerts(self, metrics: Dict[str, float]): \u0026#34;\u0026#34;\u0026#34;检查告警条件\u0026#34;\u0026#34;\u0026#34; alerts = [] for rule in self.alert_rules: metric_name = rule[\u0026#39;metric\u0026#39;] threshold = rule[\u0026#39;threshold\u0026#39;] operator = rule.get(\u0026#39;operator\u0026#39;, \u0026#39;\u0026gt;\u0026#39;) if metric_name not in metrics: continue value = metrics[metric_name] triggered = False if operator == \u0026#39;\u0026gt;\u0026#39;: triggered = value \u0026gt; threshold elif operator == \u0026#39;\u0026lt;\u0026#39;: triggered = value \u0026lt; threshold elif operator == \u0026#39;\u0026gt;=\u0026#39;: triggered = value \u0026gt;= threshold elif operator == \u0026#39;\u0026lt;=\u0026#39;: triggered = value \u0026lt;= threshold elif operator == \u0026#39;==\u0026#39;: triggered = value == threshold if triggered: alert = { \u0026#39;rule_name\u0026#39;: rule[\u0026#39;name\u0026#39;], \u0026#39;metric\u0026#39;: metric_name, \u0026#39;value\u0026#39;: value, \u0026#39;threshold\u0026#39;: threshold, \u0026#39;operator\u0026#39;: operator, \u0026#39;severity\u0026#39;: rule.get(\u0026#39;severity\u0026#39;, \u0026#39;warning\u0026#39;), \u0026#39;message\u0026#39;: rule.get(\u0026#39;message\u0026#39;, f\u0026#34;{metric_name} {operator} {threshold}\u0026#34;), \u0026#39;timestamp\u0026#39;: time.time() } alerts.append(alert) return alerts def send_alert(self, alert: Dict[str, Any]): \u0026#34;\u0026#34;\u0026#34;发送告警\u0026#34;\u0026#34;\u0026#34; if self.webhook_url: self.send_webhook_alert(alert) if self.email_config: self.send_email_alert(alert) def send_webhook_alert(self, alert: Dict[str, Any]): \u0026#34;\u0026#34;\u0026#34;发送 Webhook 告警\u0026#34;\u0026#34;\u0026#34; try: payload = { \u0026#39;text\u0026#39;: f\u0026#34;🚨 Alert: {alert[\u0026#39;rule_name\u0026#39;]}\u0026#34;, \u0026#39;attachments\u0026#39;: [{ \u0026#39;color\u0026#39;: \u0026#39;danger\u0026#39; if alert[\u0026#39;severity\u0026#39;] == \u0026#39;critical\u0026#39; else \u0026#39;warning\u0026#39;, \u0026#39;fields\u0026#39;: [ {\u0026#39;title\u0026#39;: \u0026#39;Metric\u0026#39;, \u0026#39;value\u0026#39;: alert[\u0026#39;metric\u0026#39;], \u0026#39;short\u0026#39;: True}, {\u0026#39;title\u0026#39;: \u0026#39;Value\u0026#39;, \u0026#39;value\u0026#39;: str(alert[\u0026#39;value\u0026#39;]), \u0026#39;short\u0026#39;: True}, {\u0026#39;title\u0026#39;: \u0026#39;Threshold\u0026#39;, \u0026#39;value\u0026#39;: str(alert[\u0026#39;threshold\u0026#39;]), \u0026#39;short\u0026#39;: True}, {\u0026#39;title\u0026#39;: \u0026#39;Severity\u0026#39;, \u0026#39;value\u0026#39;: alert[\u0026#39;severity\u0026#39;], \u0026#39;short\u0026#39;: True} ], \u0026#39;text\u0026#39;: alert[\u0026#39;message\u0026#39;] }] } response = requests.post(self.webhook_url, json=payload) response.raise_for_status() print(f\u0026#34;Webhook alert sent: {alert[\u0026#39;rule_name\u0026#39;]}\u0026#34;) except Exception as e: logging.error(f\u0026#34;Failed to send webhook alert: {e}\u0026#34;) def send_email_alert(self, alert: Dict[str, Any]): \u0026#34;\u0026#34;\u0026#34;发送邮件告警\u0026#34;\u0026#34;\u0026#34; try: import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart msg = MIMEMultipart() msg[\u0026#39;From\u0026#39;] = self.email_config[\u0026#39;from\u0026#39;] msg[\u0026#39;To\u0026#39;] = \u0026#39;, \u0026#39;.join(self.email_config[\u0026#39;to\u0026#39;]) msg[\u0026#39;Subject\u0026#39;] = f\u0026#34;Alert: {alert[\u0026#39;rule_name\u0026#39;]}\u0026#34; body = f\u0026#34;\u0026#34;\u0026#34; Alert Details: - Rule: {alert[\u0026#39;rule_name\u0026#39;]} - Metric: {alert[\u0026#39;metric\u0026#39;]} - Current Value: {alert[\u0026#39;value\u0026#39;]} - Threshold: {alert[\u0026#39;threshold\u0026#39;]} - Operator: {alert[\u0026#39;operator\u0026#39;]} - Severity: {alert[\u0026#39;severity\u0026#39;]} - Message: {alert[\u0026#39;message\u0026#39;]} - Time: {time.strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;, time.localtime(alert[\u0026#39;timestamp\u0026#39;]))} \u0026#34;\u0026#34;\u0026#34; msg.attach(MIMEText(body, \u0026#39;plain\u0026#39;)) server = smtplib.SMTP(self.email_config[\u0026#39;smtp_server\u0026#39;], self.email_config[\u0026#39;smtp_port\u0026#39;]) if self.email_config.get(\u0026#39;use_tls\u0026#39;): server.starttls() if self.email_config.get(\u0026#39;username\u0026#39;): server.login(self.email_config[\u0026#39;username\u0026#39;], self.email_config[\u0026#39;password\u0026#39;]) server.send_message(msg) server.quit() print(f\u0026#34;Email alert sent: {alert[\u0026#39;rule_name\u0026#39;]}\u0026#34;) except Exception as e: logging.error(f\u0026#34;Failed to send email alert: {e}\u0026#34;) class LogAnalyzer: def __init__(self, log_sources: List[Dict[str, str]]): self.log_sources = log_sources self.patterns = { \u0026#39;error\u0026#39;: [r\u0026#39;ERROR\u0026#39;, r\u0026#39;FATAL\u0026#39;, r\u0026#39;Exception\u0026#39;, r\u0026#39;Traceback\u0026#39;], \u0026#39;warning\u0026#39;: [r\u0026#39;WARNING\u0026#39;, r\u0026#39;WARN\u0026#39;], \u0026#39;info\u0026#39;: [r\u0026#39;INFO\u0026#39;], \u0026#39;debug\u0026#39;: [r\u0026#39;DEBUG\u0026#39;] } def analyze_logs(self, time_range: str = \u0026#39;1h\u0026#39;) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;分析日志\u0026#34;\u0026#34;\u0026#34; import re from collections import defaultdict results = { \u0026#39;summary\u0026#39;: defaultdict(int), \u0026#39;errors\u0026#39;: [], \u0026#39;warnings\u0026#39;: [], \u0026#39;patterns\u0026#39;: defaultdict(int) } for source in self.log_sources: source_type = source[\u0026#39;type\u0026#39;] source_path = source[\u0026#39;path\u0026#39;] if source_type == \u0026#39;file\u0026#39;: self.analyze_log_file(source_path, results) elif source_type == \u0026#39;elasticsearch\u0026#39;: self.analyze_elasticsearch_logs(source, results, time_range) return results def analyze_log_file(self, file_path: str, results: Dict[str, Any]): \u0026#34;\u0026#34;\u0026#34;分析日志文件\u0026#34;\u0026#34;\u0026#34; import re try: with open(file_path, \u0026#39;r\u0026#39;) as f: for line in f: line = line.strip() if not line: continue # 检查日志级别 for level, patterns in self.patterns.items(): for pattern in patterns: if re.search(pattern, line, re.IGNORECASE): results[\u0026#39;summary\u0026#39;][level] += 1 if level == \u0026#39;error\u0026#39;: results[\u0026#39;errors\u0026#39;].append(line) elif level == \u0026#39;warning\u0026#39;: results[\u0026#39;warnings\u0026#39;].append(line) break except Exception as e: logging.error(f\u0026#34;Failed to analyze log file {file_path}: {e}\u0026#34;) def analyze_elasticsearch_logs(self, source: Dict[str, str], results: Dict[str, Any], time_range: str): \u0026#34;\u0026#34;\u0026#34;分析 Elasticsearch 日志\u0026#34;\u0026#34;\u0026#34; try: from elasticsearch import Elasticsearch es = Elasticsearch([source[\u0026#39;url\u0026#39;]]) query = { \u0026#34;query\u0026#34;: { \u0026#34;range\u0026#34;: { \u0026#34;@timestamp\u0026#34;: { \u0026#34;gte\u0026#34;: f\u0026#34;now-{time_range}\u0026#34; } } }, \u0026#34;aggs\u0026#34;: { \u0026#34;log_levels\u0026#34;: { \u0026#34;terms\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;level.keyword\u0026#34; } } } } response = es.search(index=source[\u0026#39;index\u0026#39;], body=query) # 处理聚合结果 for bucket in response[\u0026#39;aggregations\u0026#39;][\u0026#39;log_levels\u0026#39;][\u0026#39;buckets\u0026#39;]: level = bucket[\u0026#39;key\u0026#39;].lower() count = bucket[\u0026#39;doc_count\u0026#39;] results[\u0026#39;summary\u0026#39;][level] += count # 获取错误日志 error_query = { \u0026#34;query\u0026#34;: { \u0026#34;bool\u0026#34;: { \u0026#34;must\u0026#34;: [ {\u0026#34;range\u0026#34;: {\u0026#34;@timestamp\u0026#34;: {\u0026#34;gte\u0026#34;: f\u0026#34;now-{time_range}\u0026#34;}}}, {\u0026#34;terms\u0026#34;: {\u0026#34;level.keyword\u0026#34;: [\u0026#34;ERROR\u0026#34;, \u0026#34;FATAL\u0026#34;]}} ] } }, \u0026#34;sort\u0026#34;: [{\u0026#34;@timestamp\u0026#34;: {\u0026#34;order\u0026#34;: \u0026#34;desc\u0026#34;}}], \u0026#34;size\u0026#34;: 100 } error_response = es.search(index=source[\u0026#39;index\u0026#39;], body=error_query) for hit in error_response[\u0026#39;hits\u0026#39;][\u0026#39;hits\u0026#39;]: results[\u0026#39;errors\u0026#39;].append(hit[\u0026#39;_source\u0026#39;].get(\u0026#39;message\u0026#39;, \u0026#39;\u0026#39;)) except Exception as e: logging.error(f\u0026#34;Failed to analyze Elasticsearch logs: {e}\u0026#34;) def generate_report(self, analysis_results: Dict[str, Any]) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;生成分析报告\u0026#34;\u0026#34;\u0026#34; report = [] report.append(\u0026#34;=\u0026#34; * 60) report.append(\u0026#34;LOG ANALYSIS REPORT\u0026#34;) report.append(\u0026#34;=\u0026#34; * 60) # 摘要 summary = analysis_results[\u0026#39;summary\u0026#39;] report.append(f\u0026#34;\\nSummary:\u0026#34;) for level, count in summary.items(): report.append(f\u0026#34; {level.upper()}: {count}\u0026#34;) # 错误详情 errors = analysis_results[\u0026#39;errors\u0026#39;] if errors: report.append(f\u0026#34;\\nTop Errors ({len(errors)}):\u0026#34;) for i, error in enumerate(errors[:10], 1): report.append(f\u0026#34; {i}. {error[:100]}...\u0026#34;) # 警告详情 warnings = analysis_results[\u0026#39;warnings\u0026#39;] if warnings: report.append(f\u0026#34;\\nTop Warnings ({len(warnings)}):\u0026#34;) for i, warning in enumerate(warnings[:10], 1): report.append(f\u0026#34; {i}. {warning[:100]}...\u0026#34;) return \u0026#39;\\n\u0026#39;.join(report) # 使用示例 if __name__ == \u0026#34;__main__\u0026#34;: # Prometheus 监控 monitor = PrometheusMonitor(\u0026#39;http://localhost:9090\u0026#39;) monitor.start_metrics_server() # 告警管理器 alert_manager = AlertManager( webhook_url=\u0026#39;https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK\u0026#39;, email_config={ \u0026#39;smtp_server\u0026#39;: \u0026#39;smtp.gmail.com\u0026#39;, \u0026#39;smtp_port\u0026#39;: 587, \u0026#39;use_tls\u0026#39;: True, \u0026#39;username\u0026#39;: \u0026#39;alerts@example.com\u0026#39;, \u0026#39;password\u0026#39;: \u0026#39;your_password\u0026#39;, \u0026#39;from\u0026#39;: \u0026#39;alerts@example.com\u0026#39;, \u0026#39;to\u0026#39;: [\u0026#39;admin@example.com\u0026#39;] } ) # 添加告警规则 alert_manager.add_alert_rule({ \u0026#39;name\u0026#39;: \u0026#39;High CPU Usage\u0026#39;, \u0026#39;metric\u0026#39;: \u0026#39;cpu_usage\u0026#39;, \u0026#39;threshold\u0026#39;: 80, \u0026#39;operator\u0026#39;: \u0026#39;\u0026gt;\u0026#39;, \u0026#39;severity\u0026#39;: \u0026#39;warning\u0026#39;, \u0026#39;message\u0026#39;: \u0026#39;CPU usage is above 80%\u0026#39; }) alert_manager.add_alert_rule({ \u0026#39;name\u0026#39;: \u0026#39;High Memory Usage\u0026#39;, \u0026#39;metric\u0026#39;: \u0026#39;memory_usage\u0026#39;, \u0026#39;threshold\u0026#39;: 90, \u0026#39;operator\u0026#39;: \u0026#39;\u0026gt;\u0026#39;, \u0026#39;severity\u0026#39;: \u0026#39;critical\u0026#39;, \u0026#39;message\u0026#39;: \u0026#39;Memory usage is above 90%\u0026#39; }) # 日志分析器 log_analyzer = LogAnalyzer([ {\u0026#39;type\u0026#39;: \u0026#39;file\u0026#39;, \u0026#39;path\u0026#39;: \u0026#39;/var/log/application.log\u0026#39;}, {\u0026#39;type\u0026#39;: \u0026#39;elasticsearch\u0026#39;, \u0026#39;url\u0026#39;: \u0026#39;http://localhost:9200\u0026#39;, \u0026#39;index\u0026#39;: \u0026#39;logs-*\u0026#39;} ]) # 监控循环 def monitoring_loop(): while True: try: # 更新系统指标 monitor.update_system_metrics() # 检查服务健康状态 monitor.check_service_health(\u0026#39;web-app\u0026#39;, \u0026#39;http://localhost:8080/health\u0026#39;) monitor.check_service_health(\u0026#39;api\u0026#39;, \u0026#39;http://localhost:8081/health\u0026#39;) # 获取当前指标值 import psutil metrics = { \u0026#39;cpu_usage\u0026#39;: psutil.cpu_percent(), \u0026#39;memory_usage\u0026#39;: psutil.virtual_memory().percent } # 检查告警 alerts = alert_manager.check_alerts(metrics) for alert in alerts: alert_manager.send_alert(alert) # 分析日志 log_results = log_analyzer.analyze_logs(\u0026#39;1h\u0026#39;) if log_results[\u0026#39;summary\u0026#39;][\u0026#39;error\u0026#39;] \u0026gt; 10: alert = { \u0026#39;rule_name\u0026#39;: \u0026#39;High Error Rate\u0026#39;, \u0026#39;metric\u0026#39;: \u0026#39;error_count\u0026#39;, \u0026#39;value\u0026#39;: log_results[\u0026#39;summary\u0026#39;][\u0026#39;error\u0026#39;], \u0026#39;threshold\u0026#39;: 10, \u0026#39;operator\u0026#39;: \u0026#39;\u0026gt;\u0026#39;, \u0026#39;severity\u0026#39;: \u0026#39;warning\u0026#39;, \u0026#39;message\u0026#39;: f\u0026#34;High error rate detected: {log_results[\u0026#39;summary\u0026#39;][\u0026#39;error\u0026#39;]} errors in the last hour\u0026#34;, \u0026#39;timestamp\u0026#39;: time.time() } alert_manager.send_alert(alert) time.sleep(60) # 每分钟检查一次 except Exception as e: logging.error(f\u0026#34;Monitoring loop error: {e}\u0026#34;) time.sleep(60) # 启动监控线程 monitoring_thread = threading.Thread(target=monitoring_loop, daemon=True) monitoring_thread.start() print(\u0026#34;Monitoring system started. Press Ctrl+C to stop.\u0026#34;) try: while True: time.sleep(1) except KeyboardInterrupt: print(\u0026#34;Monitoring system stopped.\u0026#34;) DevOps 最佳实践 # 代码质量和安全 # 代码质量检查工具 # #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; 代码质量检查工具集成 \u0026#34;\u0026#34;\u0026#34; import subprocess import json import os from typing import Dict, List, Any import yaml class CodeQualityChecker: def __init__(self, project_path: str): self.project_path = project_path self.results = {} def run_flake8(self) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;运行 flake8 代码风格检查\u0026#34;\u0026#34;\u0026#34; try: result = subprocess.run( [\u0026#39;flake8\u0026#39;, \u0026#39;--format=json\u0026#39;, self.project_path], capture_output=True, text=True, cwd=self.project_path ) if result.stdout: violations = json.loads(result.stdout) else: violations = [] return { \u0026#39;tool\u0026#39;: \u0026#39;flake8\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;success\u0026#39; if result.returncode == 0 else \u0026#39;failed\u0026#39;, \u0026#39;violations\u0026#39;: violations, \u0026#39;total_violations\u0026#39;: len(violations) } except Exception as e: return {\u0026#39;tool\u0026#39;: \u0026#39;flake8\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;error\u0026#39;, \u0026#39;error\u0026#39;: str(e)} def run_bandit(self) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;运行 bandit 安全检查\u0026#34;\u0026#34;\u0026#34; try: result = subprocess.run( [\u0026#39;bandit\u0026#39;, \u0026#39;-r\u0026#39;, self.project_path, \u0026#39;-f\u0026#39;, \u0026#39;json\u0026#39;], capture_output=True, text=True, cwd=self.project_path ) if result.stdout: report = json.loads(result.stdout) issues = report.get(\u0026#39;results\u0026#39;, []) else: issues = [] return { \u0026#39;tool\u0026#39;: \u0026#39;bandit\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;success\u0026#39;, \u0026#39;issues\u0026#39;: issues, \u0026#39;total_issues\u0026#39;: len(issues), \u0026#39;high_severity\u0026#39;: len([i for i in issues if i.get(\u0026#39;issue_severity\u0026#39;) == \u0026#39;HIGH\u0026#39;]), \u0026#39;medium_severity\u0026#39;: len([i for i in issues if i.get(\u0026#39;issue_severity\u0026#39;) == \u0026#39;MEDIUM\u0026#39;]), \u0026#39;low_severity\u0026#39;: len([i for i in issues if i.get(\u0026#39;issue_severity\u0026#39;) == \u0026#39;LOW\u0026#39;]) } except Exception as e: return {\u0026#39;tool\u0026#39;: \u0026#39;bandit\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;error\u0026#39;, \u0026#39;error\u0026#39;: str(e)} def run_pytest_coverage(self) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;运行测试覆盖率检查\u0026#34;\u0026#34;\u0026#34; try: result = subprocess.run( [\u0026#39;pytest\u0026#39;, \u0026#39;--cov=src\u0026#39;, \u0026#39;--cov-report=json\u0026#39;, \u0026#39;--cov-report=term\u0026#39;], capture_output=True, text=True, cwd=self.project_path ) # 读取覆盖率报告 coverage_file = os.path.join(self.project_path, \u0026#39;coverage.json\u0026#39;) if os.path.exists(coverage_file): with open(coverage_file, \u0026#39;r\u0026#39;) as f: coverage_data = json.load(f) total_coverage = coverage_data[\u0026#39;totals\u0026#39;][\u0026#39;percent_covered\u0026#39;] else: total_coverage = 0 return { \u0026#39;tool\u0026#39;: \u0026#39;pytest-cov\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;success\u0026#39; if result.returncode == 0 else \u0026#39;failed\u0026#39;, \u0026#39;coverage_percentage\u0026#39;: total_coverage, \u0026#39;tests_passed\u0026#39;: \u0026#39;FAILED\u0026#39; not in result.stdout, \u0026#39;output\u0026#39;: result.stdout } except Exception as e: return {\u0026#39;tool\u0026#39;: \u0026#39;pytest-cov\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;error\u0026#39;, \u0026#39;error\u0026#39;: str(e)} def run_mypy(self) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;运行 mypy 类型检查\u0026#34;\u0026#34;\u0026#34; try: result = subprocess.run( [\u0026#39;mypy\u0026#39;, self.project_path, \u0026#39;--json-report\u0026#39;, \u0026#39;/tmp/mypy-report\u0026#39;], capture_output=True, text=True, cwd=self.project_path ) # 解析 mypy 输出 errors = [] if result.stdout: for line in result.stdout.split(\u0026#39;\\n\u0026#39;): if line.strip() and \u0026#39;:\u0026#39; in line: errors.append(line.strip()) return { \u0026#39;tool\u0026#39;: \u0026#39;mypy\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;success\u0026#39; if result.returncode == 0 else \u0026#39;failed\u0026#39;, \u0026#39;errors\u0026#39;: errors, \u0026#39;total_errors\u0026#39;: len(errors) } except Exception as e: return {\u0026#39;tool\u0026#39;: \u0026#39;mypy\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;error\u0026#39;, \u0026#39;error\u0026#39;: str(e)} def run_all_checks(self) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;运行所有代码质量检查\u0026#34;\u0026#34;\u0026#34; print(\u0026#34;Running code quality checks...\u0026#34;) self.results = { \u0026#39;flake8\u0026#39;: self.run_flake8(), \u0026#39;bandit\u0026#39;: self.run_bandit(), \u0026#39;coverage\u0026#39;: self.run_pytest_coverage(), \u0026#39;mypy\u0026#39;: self.run_mypy() } # 计算总体评分 score = self.calculate_quality_score() self.results[\u0026#39;overall_score\u0026#39;] = score return self.results def calculate_quality_score(self) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;计算代码质量评分\u0026#34;\u0026#34;\u0026#34; score = 100 issues = [] # Flake8 扣分 flake8_violations = self.results[\u0026#39;flake8\u0026#39;].get(\u0026#39;total_violations\u0026#39;, 0) if flake8_violations \u0026gt; 0: deduction = min(flake8_violations * 2, 30) score -= deduction issues.append(f\u0026#34;Flake8 violations: -{deduction} points\u0026#34;) # Bandit 扣分 bandit_high = self.results[\u0026#39;bandit\u0026#39;].get(\u0026#39;high_severity\u0026#39;, 0) bandit_medium = self.results[\u0026#39;bandit\u0026#39;].get(\u0026#39;medium_severity\u0026#39;, 0) if bandit_high \u0026gt; 0: deduction = bandit_high * 10 score -= deduction issues.append(f\u0026#34;High security issues: -{deduction} points\u0026#34;) if bandit_medium \u0026gt; 0: deduction = bandit_medium * 5 score -= deduction issues.append(f\u0026#34;Medium security issues: -{deduction} points\u0026#34;) # 测试覆盖率扣分 coverage = self.results[\u0026#39;coverage\u0026#39;].get(\u0026#39;coverage_percentage\u0026#39;, 0) if coverage \u0026lt; 80: deduction = (80 - coverage) * 0.5 score -= deduction issues.append(f\u0026#34;Low test coverage ({coverage}%): -{deduction:.1f} points\u0026#34;) # MyPy 扣分 mypy_errors = self.results[\u0026#39;mypy\u0026#39;].get(\u0026#39;total_errors\u0026#39;, 0) if mypy_errors \u0026gt; 0: deduction = min(mypy_errors * 1, 20) score -= deduction issues.append(f\u0026#34;Type errors: -{deduction} points\u0026#34;) score = max(score, 0) return { \u0026#39;score\u0026#39;: round(score, 1), \u0026#39;grade\u0026#39;: self.get_grade(score), \u0026#39;issues\u0026#39;: issues } def get_grade(self, score: float) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;根据分数获取等级\u0026#34;\u0026#34;\u0026#34; if score \u0026gt;= 90: return \u0026#39;A\u0026#39; elif score \u0026gt;= 80: return \u0026#39;B\u0026#39; elif score \u0026gt;= 70: return \u0026#39;C\u0026#39; elif score \u0026gt;= 60: return \u0026#39;D\u0026#39; else: return \u0026#39;F\u0026#39; def generate_report(self) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;生成质量报告\u0026#34;\u0026#34;\u0026#34; if not self.results: self.run_all_checks() report = [] report.append(\u0026#34;=\u0026#34; * 60) report.append(\u0026#34;CODE QUALITY REPORT\u0026#34;) report.append(\u0026#34;=\u0026#34; * 60) # 总体评分 overall = self.results[\u0026#39;overall_score\u0026#39;] report.append(f\u0026#34;\\nOverall Score: {overall[\u0026#39;score\u0026#39;]}/100 (Grade: {overall[\u0026#39;grade\u0026#39;]})\u0026#34;) if overall[\u0026#39;issues\u0026#39;]: report.append(\u0026#34;\\nIssues:\u0026#34;) for issue in overall[\u0026#39;issues\u0026#39;]: report.append(f\u0026#34; - {issue}\u0026#34;) # 详细结果 report.append(\u0026#34;\\nDetailed Results:\u0026#34;) for tool, result in self.results.items(): if tool == \u0026#39;overall_score\u0026#39;: continue report.append(f\u0026#34;\\n{tool.upper()}:\u0026#34;) report.append(f\u0026#34; Status: {result.get(\u0026#39;status\u0026#39;, \u0026#39;unknown\u0026#39;)}\u0026#34;) if tool == \u0026#39;flake8\u0026#39;: report.append(f\u0026#34; Violations: {result.get(\u0026#39;total_violations\u0026#39;, 0)}\u0026#34;) elif tool == \u0026#39;bandit\u0026#39;: report.append(f\u0026#34; Security Issues: {result.get(\u0026#39;total_issues\u0026#39;, 0)}\u0026#34;) report.append(f\u0026#34; High: {result.get(\u0026#39;high_severity\u0026#39;, 0)}\u0026#34;) report.append(f\u0026#34; Medium: {result.get(\u0026#39;medium_severity\u0026#39;, 0)}\u0026#34;) report.append(f\u0026#34; Low: {result.get(\u0026#39;low_severity\u0026#39;, 0)}\u0026#34;) elif tool == \u0026#39;coverage\u0026#39;: report.append(f\u0026#34; Coverage: {result.get(\u0026#39;coverage_percentage\u0026#39;, 0)}%\u0026#34;) report.append(f\u0026#34; Tests Passed: {result.get(\u0026#39;tests_passed\u0026#39;, False)}\u0026#34;) elif tool == \u0026#39;mypy\u0026#39;: report.append(f\u0026#34; Type Errors: {result.get(\u0026#39;total_errors\u0026#39;, 0)}\u0026#34;) return \u0026#39;\\n\u0026#39;.join(report) class SecurityScanner: def __init__(self): self.scan_results = {} def scan_dependencies(self, requirements_file: str) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;扫描依赖包安全漏洞\u0026#34;\u0026#34;\u0026#34; try: # 使用 safety 扫描已知漏洞 result = subprocess.run( [\u0026#39;safety\u0026#39;, \u0026#39;check\u0026#39;, \u0026#39;--json\u0026#39;, \u0026#39;-r\u0026#39;, requirements_file], capture_output=True, text=True ) if result.stdout: vulnerabilities = json.loads(result.stdout) else: vulnerabilities = [] return { \u0026#39;tool\u0026#39;: \u0026#39;safety\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;success\u0026#39;, \u0026#39;vulnerabilities\u0026#39;: vulnerabilities, \u0026#39;total_vulnerabilities\u0026#39;: len(vulnerabilities) } except Exception as e: return {\u0026#39;tool\u0026#39;: \u0026#39;safety\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;error\u0026#39;, \u0026#39;error\u0026#39;: str(e)} def scan_docker_image(self, image_name: str) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;扫描 Docker 镜像安全漏洞\u0026#34;\u0026#34;\u0026#34; try: # 使用 trivy 扫描镜像 result = subprocess.run( [\u0026#39;trivy\u0026#39;, \u0026#39;image\u0026#39;, \u0026#39;--format\u0026#39;, \u0026#39;json\u0026#39;, image_name], capture_output=True, text=True ) if result.stdout: scan_result = json.loads(result.stdout) vulnerabilities = [] for target in scan_result.get(\u0026#39;Results\u0026#39;, []): vulnerabilities.extend(target.get(\u0026#39;Vulnerabilities\u0026#39;, [])) else: vulnerabilities = [] # 按严重程度分类 severity_count = {\u0026#39;CRITICAL\u0026#39;: 0, \u0026#39;HIGH\u0026#39;: 0, \u0026#39;MEDIUM\u0026#39;: 0, \u0026#39;LOW\u0026#39;: 0} for vuln in vulnerabilities: severity = vuln.get(\u0026#39;Severity\u0026#39;, \u0026#39;UNKNOWN\u0026#39;) if severity in severity_count: severity_count[severity] += 1 return { \u0026#39;tool\u0026#39;: \u0026#39;trivy\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;success\u0026#39;, \u0026#39;vulnerabilities\u0026#39;: vulnerabilities, \u0026#39;total_vulnerabilities\u0026#39;: len(vulnerabilities), \u0026#39;severity_breakdown\u0026#39;: severity_count } except Exception as e: return {\u0026#39;tool\u0026#39;: \u0026#39;trivy\u0026#39;, \u0026#39;status\u0026#39;: \u0026#39;error\u0026#39;, \u0026#39;error\u0026#39;: str(e)} ### 性能优化和监控 #### 应用性能监控 ```python #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; 应用性能监控工具 \u0026#34;\u0026#34;\u0026#34; import time import functools import threading from typing import Dict, Any, Callable import statistics from collections import defaultdict, deque class PerformanceMonitor: def __init__(self, max_samples: int = 1000): self.max_samples = max_samples self.metrics = defaultdict(lambda: deque(maxlen=max_samples)) self.counters = defaultdict(int) self.lock = threading.Lock() def timing_decorator(self, name: str = None): \u0026#34;\u0026#34;\u0026#34;性能计时装饰器\u0026#34;\u0026#34;\u0026#34; def decorator(func: Callable) -\u0026gt; Callable: metric_name = name or f\u0026#34;{func.__module__}.{func.__name__}\u0026#34; @functools.wraps(func) def wrapper(*args, **kwargs): start_time = time.time() try: result = func(*args, **kwargs) self.record_success(metric_name, time.time() - start_time) return result except Exception as e: self.record_error(metric_name, time.time() - start_time) raise return wrapper return decorator def record_success(self, metric_name: str, duration: float): \u0026#34;\u0026#34;\u0026#34;记录成功的操作\u0026#34;\u0026#34;\u0026#34; with self.lock: self.metrics[f\u0026#34;{metric_name}.duration\u0026#34;].append(duration) self.counters[f\u0026#34;{metric_name}.success\u0026#34;] += 1 def record_error(self, metric_name: str, duration: float): \u0026#34;\u0026#34;\u0026#34;记录失败的操作\u0026#34;\u0026#34;\u0026#34; with self.lock: self.metrics[f\u0026#34;{metric_name}.duration\u0026#34;].append(duration) self.counters[f\u0026#34;{metric_name}.error\u0026#34;] += 1 def get_statistics(self, metric_name: str) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;获取指标统计信息\u0026#34;\u0026#34;\u0026#34; duration_key = f\u0026#34;{metric_name}.duration\u0026#34; success_key = f\u0026#34;{metric_name}.success\u0026#34; error_key = f\u0026#34;{metric_name}.error\u0026#34; with self.lock: durations = list(self.metrics[duration_key]) success_count = self.counters[success_key] error_count = self.counters[error_key] if not durations: return {\u0026#39;error\u0026#39;: \u0026#39;No data available\u0026#39;} total_requests = success_count + error_count error_rate = (error_count / total_requests) * 100 if total_requests \u0026gt; 0 else 0 return { \u0026#39;total_requests\u0026#39;: total_requests, \u0026#39;success_count\u0026#39;: success_count, \u0026#39;error_count\u0026#39;: error_count, \u0026#39;error_rate_percent\u0026#39;: round(error_rate, 2), \u0026#39;avg_duration\u0026#39;: round(statistics.mean(durations), 4), \u0026#39;median_duration\u0026#39;: round(statistics.median(durations), 4), \u0026#39;min_duration\u0026#39;: round(min(durations), 4), \u0026#39;max_duration\u0026#39;: round(max(durations), 4), \u0026#39;p95_duration\u0026#39;: round(statistics.quantiles(durations, n=20)[18], 4) if len(durations) \u0026gt;= 20 else None, \u0026#39;p99_duration\u0026#39;: round(statistics.quantiles(durations, n=100)[98], 4) if len(durations) \u0026gt;= 100 else None } def get_all_metrics(self) -\u0026gt; Dict[str, Dict[str, Any]]: \u0026#34;\u0026#34;\u0026#34;获取所有指标\u0026#34;\u0026#34;\u0026#34; metrics = {} # 获取所有唯一的指标名称 metric_names = set() for key in list(self.metrics.keys()) + list(self.counters.keys()): if key.endswith(\u0026#39;.duration\u0026#39;) or key.endswith(\u0026#39;.success\u0026#39;) or key.endswith(\u0026#39;.error\u0026#39;): base_name = key.rsplit(\u0026#39;.\u0026#39;, 1)[0] metric_names.add(base_name) for metric_name in metric_names: metrics[metric_name] = self.get_statistics(metric_name) return metrics def generate_report(self) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;生成性能报告\u0026#34;\u0026#34;\u0026#34; metrics = self.get_all_metrics() report = [] report.append(\u0026#34;=\u0026#34; * 60) report.append(\u0026#34;PERFORMANCE MONITORING REPORT\u0026#34;) report.append(\u0026#34;=\u0026#34; * 60) for metric_name, stats in metrics.items(): if \u0026#39;error\u0026#39; in stats: continue report.append(f\u0026#34;\\n{metric_name}:\u0026#34;) report.append(f\u0026#34; Total Requests: {stats[\u0026#39;total_requests\u0026#39;]}\u0026#34;) report.append(f\u0026#34; Success Rate: {100 - stats[\u0026#39;error_rate_percent\u0026#39;]:.2f}%\u0026#34;) report.append(f\u0026#34; Error Rate: {stats[\u0026#39;error_rate_percent\u0026#39;]:.2f}%\u0026#34;) report.append(f\u0026#34; Average Duration: {stats[\u0026#39;avg_duration\u0026#39;]}s\u0026#34;) report.append(f\u0026#34; Median Duration: {stats[\u0026#39;median_duration\u0026#39;]}s\u0026#34;) report.append(f\u0026#34; 95th Percentile: {stats[\u0026#39;p95_duration\u0026#39;]}s\u0026#34;) report.append(f\u0026#34; 99th Percentile: {stats[\u0026#39;p99_duration\u0026#39;]}s\u0026#34;) return \u0026#39;\\n\u0026#39;.join(report) ## 全局性能监控实例 perf_monitor = PerformanceMonitor() ## 使用示例 @perf_monitor.timing_decorator(\u0026#34;database.query\u0026#34;) def database_query(query: str): \u0026#34;\u0026#34;\u0026#34;模拟数据库查询\u0026#34;\u0026#34;\u0026#34; import random time.sleep(random.uniform(0.01, 0.1)) # 模拟查询时间 if random.random() \u0026lt; 0.05: # 5% 的错误率 raise Exception(\u0026#34;Database connection failed\u0026#34;) return f\u0026#34;Results for: {query}\u0026#34; @perf_monitor.timing_decorator(\u0026#34;api.request\u0026#34;) def api_request(endpoint: str): \u0026#34;\u0026#34;\u0026#34;模拟 API 请求\u0026#34;\u0026#34;\u0026#34; import random time.sleep(random.uniform(0.05, 0.2)) # 模拟请求时间 if random.random() \u0026lt; 0.02: # 2% 的错误率 raise Exception(\u0026#34;API request failed\u0026#34;) return f\u0026#34;Response from: {endpoint}\u0026#34; if __name__ == \u0026#34;__main__\u0026#34;: # 代码质量检查 quality_checker = CodeQualityChecker(\u0026#39;/path/to/your/project\u0026#39;) quality_report = quality_checker.generate_report() print(quality_report) # 安全扫描 security_scanner = SecurityScanner() # 扫描依赖包 dep_scan = security_scanner.scan_dependencies(\u0026#39;requirements.txt\u0026#39;) print(f\u0026#34;Dependency scan: {dep_scan[\u0026#39;total_vulnerabilities\u0026#39;]} vulnerabilities found\u0026#34;) # 性能监控示例 print(\u0026#34;\\nRunning performance tests...\u0026#34;) for i in range(100): try: database_query(f\u0026#34;SELECT * FROM table_{i}\u0026#34;) api_request(f\u0026#34;/api/endpoint_{i}\u0026#34;) except Exception: pass # 忽略模拟的错误 # 生成性能报告 perf_report = perf_monitor.generate_report() print(perf_report) 总结与展望 # DevOps 成功要素 # 文化转变: 打破部门壁垒，建立协作文化 自动化优先: 将重复性工作自动化 持续改进: 基于数据驱动的决策 快速反馈: 建立快速反馈循环 安全集成: 将安全融入到整个开发生命周期 Python 在 DevOps 中的优势 # 生态丰富: 大量的第三方库和工具 易于学习: 语法简洁，上手容易 跨平台: 支持多种操作系统 社区活跃: 持续的更新和支持 集成能力强: 易于与其他工具集成 学习路径建议 # 基础阶段\nPython 编程基础 Linux 系统管理 版本控制 (Git) 基础网络知识 进阶阶段\n容器化技术 (Docker, Kubernetes) CI/CD 流水线设计 基础设施即代码 (Terraform, Ansible) 监控和日志管理 高级阶段\n微服务架构 云原生技术 安全最佳实践 性能优化 推荐工具和资源 # 必备工具 # 版本控制: Git, GitLab, GitHub CI/CD: Jenkins, GitLab CI, GitHub Actions 容器化: Docker, Kubernetes, Helm 监控: Prometheus, Grafana, ELK Stack 基础设施: Terraform, Ansible, Pulumi 云平台: AWS, Azure, GCP 学习资源 # 官方文档: 各工具的官方文档 在线课程: Coursera, Udemy, Pluralsight 实践平台: Katacoda, Play with Docker 社区: DevOps 社区, Stack Overflow 书籍: 《Phoenix Project》, 《DevOps Handbook》 未来趋势 # GitOps: 基于 Git 的运维模式 AIOps: 人工智能运维 Serverless: 无服务器架构 Edge Computing: 边缘计算 Security as Code: 安全即代码 通过系统学习和实践，结合 Python 的强大能力，您将能够构建高效、可靠、安全的 DevOps 体系，推动组织的数字化转型和业务发展。记住，DevOps 不仅仅是工具和技术，更是一种文化和思维方式的转变。\n","date":"2023年10月5日","externalUrl":null,"permalink":"/posts/python-fro-devops-notes/","section":"博客文章","summary":"\u003cp\u003ePython 在 DevOps 领域扮演着重要角色，其简洁的语法、丰富的生态系统和强大的自动化能力使其成为运维自动化的首选语言。本指南将从 DevOps 理念开始，逐步深入到 Python 在各个 DevOps 场景中的实际应用，帮助您构建高效的自动化运维体系。\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Python for DevOps 书籍封面\" src=\"https://cdn.treesir.pub/images/2023/10/05/20231005204456.jpg\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e图：《Python for DevOps: Learn Ruthlessly Effective Automation》\u003c/em\u003e\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003eDevOps 理念与文化 \n    \u003cdiv id=\"devops-理念与文化\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#devops-%e7%90%86%e5%bf%b5%e4%b8%8e%e6%96%87%e5%8c%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e什么是 DevOps \n    \u003cdiv id=\"什么是-devops\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-devops\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eDevOps 是一种文化、实践和工具的组合，旨在提高组织快速交付应用程序和服务的能力。它强调开发（Development）和运维（Operations）团队之间的协作与沟通。\u003c/p\u003e","title":"Python DevOps 完整实战指南","type":"posts"},{"content":"","date":"2023年10月5日","externalUrl":null,"permalink":"/categories/%E7%BC%96%E7%A8%8B/","section":"Categories","summary":"","title":"编程","type":"categories"},{"content":"","date":"2023年10月5日","externalUrl":null,"permalink":"/tags/%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD%E5%8D%B3%E4%BB%A3%E7%A0%81/","section":"Tags","summary":"","title":"基础设施即代码","type":"tags"},{"content":"","date":"2023年10月5日","externalUrl":null,"permalink":"/tags/%E8%BF%90%E7%BB%B4%E5%BC%80%E5%8F%91/","section":"Tags","summary":"","title":"运维开发","type":"tags"},{"content":"","date":"2023年10月5日","externalUrl":null,"permalink":"/categories/%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4/","section":"Categories","summary":"","title":"自动化运维","type":"categories"},{"content":"","date":"2023年10月5日","externalUrl":null,"permalink":"/tags/slimtoolkit/","section":"Tags","summary":"","title":"Slimtoolkit","type":"tags"},{"content":" 工具介绍 # SlimToolkit 概述 # SlimToolkit（原名 DockerSlim）是一个强大的开源工具，专门用于优化 Docker 容器镜像的大小、启动速度和安全性。\n核心特性 # 显著减小镜像体积：可将容器镜像大小缩减高达 30 倍 提升启动性能：通过移除冗余文件，加快容器启动速度 增强安全性：减少攻击面，移除不必要的组件和依赖 智能分析：自动识别应用运行所需的最小文件集 工作原理 # SlimToolkit 采用 ptrace 技术进行容器运行时分析：\n动态跟踪：使用 ptrace 系统调用监控容器运行过程 文件访问分析：记录应用实际访问的文件和依赖 最小化构建：基于分析结果构建包含最少必要文件的新镜像 安全扫描：识别并移除潜在的安全风险组件 项目地址：https://github.com/slimtoolkit/slim\n安装部署 # 快速安装 # 使用官方安装脚本进行一键安装：\ncurl -sL https://raw.githubusercontent.com/slimtoolkit/slim/master/scripts/install-slim.sh | sudo -E bash - 验证安装 # slim --version GitLab CI/CD 集成 # 集成方案 # 在 GitLab CI/CD 流水线中集成 SlimToolkit，实现镜像构建后的自动优化。\n基础流水线配置 # stages: - build - optimize - deploy variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: \u0026#34;/certs\u0026#34; build-image: stage: build image: docker:latest services: - docker:dind script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA optimize-image: stage: optimize image: docker:latest services: - docker:dind before_script: - apk add --no-cache curl - curl -sL https://raw.githubusercontent.com/slimtoolkit/slim/master/scripts/install-slim.sh | sh - script: - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - slim build --target $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-slim - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-slim 示例项目 # 完整的集成示例已整理到 GitLab 仓库中：\n项目地址：https://gitlab.com/cdryzun/mshekow-docker-slim-example 功能特性： 自动化镜像构建 SlimToolkit 优化集成 优化前后对比报告 多环境部署支持 最佳实践 # 优化策略 # 1. 应用测试用例设计 # 为确保精简后的镜像正常运行，需要设计全面的测试用例：\n# 示例：Web 应用测试用例 slim build --target myapp:latest \\ --http-probe-cmd /health \\ --http-probe-cmd /api/status \\ --include-path /app/config \\ --include-path /app/static 2. 关键文件保护 # 对于特定应用场景，需要显式包含必要文件：\n# 保护配置文件和静态资源 slim build --target myapp:latest \\ --preserve-path /etc/ssl/certs \\ --preserve-path /app/templates \\ --preserve-path /usr/share/zoneinfo 3. 分阶段优化 # # 渐进式优化策略 optimize-conservative: script: - slim build --target $IMAGE --continue-after 60s optimize-aggressive: script: - slim build --target $IMAGE --remove-file-artifacts 注意事项 # 应用兼容性 # 动态加载：某些应用运行时动态加载的文件可能被误删 配置文件：需要确保所有配置文件路径被正确识别 依赖库：共享库和运行时依赖需要特别关注 测试验证 # 功能测试：确保核心业务功能正常 性能测试：验证优化后的性能表现 安全扫描：检查是否引入新的安全风险 总结 # 优势与挑战 # 优势 # 显著减小镜像体积：大幅降低存储和传输成本 提升部署效率：加快镜像拉取和容器启动速度 增强安全性：减少攻击面和潜在漏洞 自动化集成：可无缝集成到 CI/CD 流水线 挑战 # 应用适配：需要针对不同应用类型设计专门的优化策略 测试复杂性：要求更全面的测试覆盖以确保功能完整性 调试难度：精简后的镜像可能缺少调试工具 维护成本：需要持续优化和调整配置参数 适用场景 # SlimToolkit 特别适合以下场景：\n微服务架构：大量小型服务的镜像优化 边缘计算：资源受限环境下的部署 CI/CD 流水线：自动化构建和部署流程 生产环境：对安全性和性能有较高要求的场景 ","date":"2023年10月5日","externalUrl":null,"permalink":"/posts/slimtoolkit-slim-study-1/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e工具介绍 \n    \u003cdiv id=\"工具介绍\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b7%a5%e5%85%b7%e4%bb%8b%e7%bb%8d\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003eSlimToolkit 概述 \n    \u003cdiv id=\"slimtoolkit-概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#slimtoolkit-%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSlimToolkit（原名 DockerSlim）是一个强大的开源工具，专门用于优化 Docker 容器镜像的大小、启动速度和安全性。\u003c/p\u003e","title":"SlimToolkit 与 GitLab CI/CD 集成实践","type":"posts"},{"content":"","date":"2023年10月5日","externalUrl":null,"permalink":"/tags/autosnap/","section":"Tags","summary":"","title":"AutoSnap","type":"tags"},{"content":" 工具简介 # PVE AutoSnap 是一个用于 Proxmox VE 环境的自动快照管理工具，可以自动创建、管理和清理虚拟机快照。\n项目地址：cv4pve-autosnap 主要功能：自动创建虚拟机快照、设置保留策略、批量管理多个虚拟机 安装配置 # 下载并安装工具 # # 下载最新版本 wget https://github.com/Corsinvest/cv4pve-autosnap/releases/download/v1.14.8/cv4pve-autosnap-linux-x64.zip # 解压文件 unzip cv4pve-autosnap-linux-x64.zip # 移动到系统路径 mv cv4pve-autosnap /usr/bin/ # 设置执行权限 chmod a+x /usr/bin/cv4pve-autosnap 注意：请根据需要选择合适的版本，最新版本信息可查看 GitHub Releases\n基本使用 # 环境变量配置 # 首先设置 PVE 连接参数：\nSNAP_LIST=\u0026#34;111,101\u0026#34; # 需要备份的虚拟机 ID 列表 PVE_USER=\u0026#39;root@pam\u0026#39; # PVE 用户名 PVE_PASSWD=\u0026#39;your_password\u0026#39; # PVE 密码 PVE_HOST=\u0026#39;192.168.8.19\u0026#39; # PVE 主机地址 创建虚拟机快照 # 批量为指定虚拟机创建快照：\nfor vmid in ${SNAP_LIST//,/ }; do cv4pve-autosnap --host=${PVE_HOST} \\ --username=${PVE_USER} \\ --password=${PVE_PASSWD} \\ --vmid=${vmid} \\ snap --label=daily --keep=3 done 参数说明：\n--host：PVE 主机地址 --username：PVE 用户名 --password：PVE 密码 --vmid：虚拟机 ID --label：快照标签（如 daily、weekly） --keep：保留快照数量 查看快照状态 # 查看指定虚拟机的快照信息：\ncv4pve-autosnap --host=${PVE_HOST} \\ --username=${PVE_USER} \\ --password=${PVE_PASSWD} \\ --vmid=111 status GitLab CI/CD 集成 # 创建执行脚本 # 创建 Script.pve-snap 脚本文件：\n#!/bin/bash #================================================================ # 文件名: Script.pve-snap # 版本: v1.0 # 日期: 2023/9/25 # 作者: yangzun # 邮箱: yangzun@treesir.pub # 描述: PVE 虚拟机快照自动化脚本 #================================================================ # 默认配置参数 SNAP_LIST=${SNAP_LIST:-\u0026#39;101\u0026#39;} PVE_USER=${PVE_USER:-\u0026#39;root@pam\u0026#39;} PVE_PASSWD=${PVE_PASSWD:-\u0026#39;your_password\u0026#39;} PVE_HOST=${PVE_HOST:-\u0026#39;192.168.8.19\u0026#39;} # 安装 AutoSnap 工具 function install_autosnap() { echo \u0026#34;正在安装 cv4pve-autosnap 工具...\u0026#34; wget https://github.com/Corsinvest/cv4pve-autosnap/releases/download/v1.14.8/cv4pve-autosnap-linux-x64.zip unzip cv4pve-autosnap-linux-x64.zip mv cv4pve-autosnap /usr/bin/ chmod a+x /usr/bin/cv4pve-autosnap echo \u0026#34;cv4pve-autosnap 工具安装完成\u0026#34; } # 执行快照操作 function execute_snapshot() { echo \u0026#34;开始执行虚拟机快照操作...\u0026#34; for vmid in ${SNAP_LIST//,/ }; do echo \u0026#34;正在为虚拟机 ${vmid} 创建快照...\u0026#34; cv4pve-autosnap --host=${PVE_HOST} \\ --username=${PVE_USER} \\ --password=${PVE_PASSWD} \\ --vmid=${vmid} \\ snap --label=daily --keep=3 done echo \u0026#34;快照操作执行完成\u0026#34; } # 查看快照状态 function check_snapshot_status() { echo \u0026#34;正在检查虚拟机快照状态...\u0026#34; for vmid in ${SNAP_LIST//,/ }; do echo \u0026#34;虚拟机 ${vmid} 的快照状态：\u0026#34; cv4pve-autosnap --host=${PVE_HOST} \\ --username=${PVE_USER} \\ --password=${PVE_PASSWD} \\ --vmid=${vmid} \\ status done } # 主程序逻辑 case \u0026#34;${1}\u0026#34; in \u0026#39;exec\u0026#39;) install_autosnap execute_snapshot ;; \u0026#39;plan\u0026#39;) install_autosnap check_snapshot_status ;; *) echo \u0026#34;用法: $0 {exec|plan}\u0026#34; echo \u0026#34; exec - 执行快照创建\u0026#34; echo \u0026#34; plan - 查看快照状态\u0026#34; exit 1 ;; esac GitLab CI/CD 配置文件 # 创建 .gitlab-ci.yml 配置文件：\n# 全局变量配置 variables: extends: .default_vars # 计划阶段 - 查看快照状态 .plan: stage: plan extends: - .schedule_trigger_disable # 执行阶段 - 创建快照 .exec: stage: exec extends: - .master_schedule_manual_trigger # 公共扩展配置 .extends_group: extends: - .runner_tag - .harbor_auth_vars - .load_function_before_jobs - .script_exec_vars - .nexus_auth_vars # 计划脚本模板 .plan_script: \u0026amp;plan_script extends: - .plan - .extends_group script: - set -- $CI_JOB_NAME - chmod a+x Script.$1 - ./Script.$1 plan # 执行脚本模板 .exec_script: \u0026amp;exec_script extends: - .exec - .extends_group script: - set -- $CI_JOB_NAME - chmod a+x Script.$1 - ./Script.$1 exec # 任务定义 pve-snap plan: *plan_script # 预览快照状态 pve-snap exec: *exec_script # 执行快照创建 运行效果展示 # 计划阶段执行结果 # 执行阶段运行结果 # 使用建议 # 最佳实践 # 定期备份：建议设置定时任务，每日自动执行快照创建 保留策略：根据存储空间合理设置快照保留数量 监控告警：配置监控，及时发现快照创建失败的情况 测试恢复：定期测试快照恢复功能，确保备份有效性 注意事项 # 权限配置：确保 PVE 用户具有足够的权限执行快照操作 存储空间：监控存储空间使用情况，避免因空间不足导致快照失败 网络连接：确保执行环境能够正常访问 PVE 主机 密码安全：建议使用环境变量或密钥管理工具存储敏感信息 故障排除 # 连接失败：检查网络连接和认证信息 权限不足：确认用户权限配置 存储空间：检查存储空间是否充足 虚拟机状态：确认虚拟机处于可快照状态 ","date":"2023年10月5日","externalUrl":null,"permalink":"/posts/pve-autosnap/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e工具简介 \n    \u003cdiv id=\"工具简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b7%a5%e5%85%b7%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003ePVE AutoSnap 是一个用于 Proxmox VE 环境的自动快照管理工具，可以自动创建、管理和清理虚拟机快照。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e项目地址\u003c/strong\u003e：\u003ca\n  href=\"https://github.com/Corsinvest/cv4pve-autosnap\"\n    target=\"_blank\"\n  \u003ecv4pve-autosnap\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e主要功能\u003c/strong\u003e：自动创建虚拟机快照、设置保留策略、批量管理多个虚拟机\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装配置 \n    \u003cdiv id=\"安装配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e下载并安装工具 \n    \u003cdiv id=\"下载并安装工具\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%8b%e8%bd%bd%e5%b9%b6%e5%ae%89%e8%a3%85%e5%b7%a5%e5%85%b7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 下载最新版本\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ewget https://github.com/Corsinvest/cv4pve-autosnap/releases/download/v1.14.8/cv4pve-autosnap-linux-x64.zip\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 解压文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eunzip cv4pve-autosnap-linux-x64.zip\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 移动到系统路径\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emv cv4pve-autosnap /usr/bin/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 设置执行权限\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003echmod a+x /usr/bin/cv4pve-autosnap\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e注意\u003c/strong\u003e：请根据需要选择合适的版本，最新版本信息可查看 \u003ca\n  href=\"https://github.com/Corsinvest/cv4pve-autosnap/releases\"\n    target=\"_blank\"\n  \u003eGitHub Releases\u003c/a\u003e\u003c/p\u003e","title":"PVE AutoSnap 自动快照工具使用指南","type":"posts"},{"content":"","date":"2023年10月5日","externalUrl":null,"permalink":"/categories/%E5%A4%87%E4%BB%BD/","section":"Categories","summary":"","title":"备份","type":"categories"},{"content":"","date":"2023年7月29日","externalUrl":null,"permalink":"/tags/grafana/","section":"Tags","summary":"","title":"Grafana","type":"tags"},{"content":"","date":"2023年7月29日","externalUrl":null,"permalink":"/tags/loki/","section":"Tags","summary":"","title":"Loki","type":"tags"},{"content":"","date":"2023年7月29日","externalUrl":null,"permalink":"/categories/%E7%9B%91%E6%8E%A7/","section":"Categories","summary":"","title":"监控","type":"categories"},{"content":"","date":"2023年7月29日","externalUrl":null,"permalink":"/tags/%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90/","section":"Tags","summary":"","title":"日志分析","type":"tags"},{"content":"在网站运营过程中，访问数据分析是了解用户行为和优化网站性能的重要手段。本文将分享如何使用 Grafana Loki 构建一个轻量级且功能强大的网站访问统计分析系统。\n项目背景 # 现有方案的局限性 # 在寻找网站统计解决方案的过程中，传统的统计工具存在一些局限性：\nGoogle Search Console：主要关注搜索引擎相关数据，对整体访问统计支持有限 CDN 厂商统计：功能相对简单，无法提供基于 IP 的精确 PV（页面浏览量）和 UV（独立访客）统计 第三方统计服务：可能存在数据隐私和依赖性问题 解决方案选择 # 经过调研，我选择了基于 Grafana Loki 的解决方案，主要原因包括：\n轻量化部署：可以使用 Grafana Cloud 免费版本，无需自建基础设施 强大的日志分析能力：Loki 专为日志数据设计，查询性能优秀 丰富的可视化：基于现成的 Nginx Dashboard 快速构建 成本控制：免费额度足够个人网站使用 最终效果展示 # 经过配置和优化，最终实现的监控面板效果如下：\n图：基于 Loki 的网站访问统计面板，包含 PV/UV、地理分布、访问趋势等关键指标\n网站技术架构 # 当前技术栈 # 在开始配置监控之前，先介绍网站的技术架构，这有助于理解后续的配置过程：\n静态网站生成 # 框架：Hugo - 高性能的静态网站生成器 优势：构建速度快，生成的静态文件便于部署和缓存 容器化部署 # 容器化：使用 Docker + Nginx 将静态文件打包成镜像 部署方式：在云主机上运行 Docker 容器 自动更新：使用 Watchtower 实现镜像自动更新 反向代理层 # 代理服务：OpenResty - 基于 Nginx 的高性能 Web 平台 功能特性： 端口复用：通过反向代理实现 443 端口的多服务共享 安全防护：集成 WAF 规则 提供 Web 应用防火墙功能 性能优化：提供缓存、压缩等性能优化功能 CI/CD 流程 # 构建：基于 Pipeline 自动构建和推送镜像到 Docker Hub 部署：Watchtower 监控镜像更新并自动重新部署 这种架构的优势在于：\n简单可靠：避免了 Kubernetes 等复杂编排工具的维护成本 成本效益：适合中小型网站的流量规模 易于维护：自动化程度高，减少人工干预 Loki 技术介绍 # 什么是 Loki # Loki 是由 Grafana Labs 开发的现代化日志聚合系统，专为云原生环境设计。它采用了与传统日志系统不同的设计理念，重点关注成本效益和查询性能。\n核心特性 # 1. 高效的索引策略 # 标签索引：仅对日志流的标签创建索引，而不是日志内容本身 成本优势：大幅降低存储成本和索引维护开销 查询效率：通过标签快速定位相关日志流 2. 与 Grafana 生态集成 # 原生支持：在 Grafana 中直接查询和可视化 Loki 数据 统一界面：将日志、指标和链路追踪数据整合在同一个界面中 告警集成：支持基于日志数据的告警规则 3. 多租户架构 # 数据隔离：每个租户拥有独立的日志数据空间 权限控制：细粒度的访问控制和数据安全 资源共享：多租户共享基础设施，提高资源利用率 4. 水平扩展能力 # 微服务架构：各组件可独立扩展 云原生设计：支持 Kubernetes 等容器编排平台 弹性伸缩：根据负载自动调整资源 5. PromQL 兼容的查询语言 # LogQL：与 Prometheus 的 PromQL 语法相似 学习成本低：熟悉 Prometheus 的用户可以快速上手 强大功能：支持复杂的日志查询、过滤和聚合操作 Loki 架构组件 # Loki 的架构由几个主要组件构成，这些组件可以在单个二进制文件中一起运行，也可以作为单独的进程运行。以下是 Loki 的主要组件：\nPromtail：Loki 的代理，负责收集日志并将它们发送到 Loki。Promtail 通常在产生日志的机器上运行，可以直接读取日志文件，也可以接收由其他进程（如 Fluentd 或 Fluent Bit）转发的日志。\nLoki：主要的日志聚合和查询组件，接收并存储日志，同时提供查询接口。Loki 通过索引日志流（而不是每一行日志）来提供高效的存储和查询。\nDistributor：负责接收来自 Promtail 的日志数据，然后将这些数据分发到多个 Ingester。\nIngester：负责接收日志数据，将数据压缩后存储在内存中，然后定期将这些数据刷新到长期存储（如 Amazon S3 或 Google Cloud Storage）。\nQuerier：负责处理来自用户的查询请求。Querier 会从 Ingester 和长期存储中获取数据，然后返回查询结果。\nQuery Frontend：负责优化和加速查询。Query Frontend 会将大查询分解为多个小查询，然后并行执行这些小查询。\nCompactor：负责压缩和优化在长期存储中的数据。\nRuler：负责执行预定义的规则和警报。\n配置日志格式 # 由于我使用的是 OneinStack 一键部署的 OpenResty，按照该 Dashboard 中的描述，需要对日志配置 GeoIP，需要对 OpenResty 重新编译开启。查看了 GeoIP 的选项，我选择 GeoIP2 Databases 作为数据库，只需要编译一下 module，在使用时进行 load 即可。\n相关资源：\n模块仓库地址：https://ghproxy.com/https://github.com/leev/ngx_http_geoip2_module.git 参考文档：https://www.electrosoftcloud.com/en/compile-geoip2-in-openresty-and-how-to-use-it/ OpenResty 编译 GeoIP2 模块 # 安装依赖 # 按照上面文档的描述，首先需要安装 maxminddb 依赖。在 CentOS 7 系统中使用以下命令安装：\nyum install -y libmaxminddb-devel libmaxminddb 克隆 GeoIP2 模块代码 # 克隆 GeoIP2 模块仓库代码到目录（编译时需要）：\nmkdir -p /tmp/compile/openresty-$(nginx -v 2\u0026gt;\u0026amp;1|cut -d \u0026#34;/\u0026#34; -f2)/modules cd /tmp/compile/openresty-$(nginx -v 2\u0026gt;\u0026amp;1|cut -d \u0026#34;/\u0026#34; -f2)/modules git clone https://ghproxy.com/https://github.com/leev/ngx_http_geoip2_module.git 配置编译环境 # 进入 OpenResty 源码目录进行编译。使用 OneinStack 安装的话，包会统一存放在 ./src 目录下。\n注意：这里 OneinStack ROOT_PATH 为 /data/scripts/oneinstack，请替换为你的实际路径\n# 进入 OpenResty 源码目录 cd /data/scripts/oneinstack/src/openresty-1.19.3.1/bundle/nginx-1.19.3 # 配置编译时所需的环境变量（否则将失败） export LUAJIT_LIB=\u0026#34;/usr/local/openresty/luajit/lib/\u0026#34; export LUAJIT_INC=\u0026#34;../LuaJIT-*/src/\u0026#34; # 获取已安装的 OpenResty 编译选项，以避免\u0026#34;二进制不兼容\u0026#34;错误 COMPILEOPTIONS=$(nginx -V 2\u0026gt;\u0026amp;1|grep -i \u0026#34;arguments\u0026#34;|cut -d \u0026#34;:\u0026#34; -f2-) # 使用这些选项配置编译，将 GeoIP2 添加为动态模块 eval ./configure $COMPILEOPTIONS --add-dynamic-module=/tmp/compile/openresty-1.19.3.1/modules/ngx_http_geoip2_module/ 编译模块 # 上一步成功后，开始执行编译：\n# 仅编译模块 make modules 这一步成功后，会在当前 objs 目录下生成所需的动态库文件：\nls -lh objs/*.so -rwxr-xr-x 1 root root 86K Jul 27 11:22 objs/ngx_http_geoip2_module.so -rwxr-xr-x 1 root root 62K Jul 27 11:22 objs/ngx_stream_geoip2_module.so # 这个动态库文件为 L4 使用，我们使用上面那个即可 配置 OpenResty 加载 GeoIP2 动态库 # # 创建模块目录 mkdir -p /usr/local/openresty/nginx/modules # 复制动态库文件，方便后续引用 cp -a objs/*.so /usr/local/openresty/nginx/modules/ # 编辑 Nginx 主配置文件，加入模块加载指令 vim /etc/nginx/nginx.conf # 在配置文件开头添加以下行 load_module modules/ngx_http_geoip2_module.so; OpenResty 配置对接 GeoIP 数据库 # 模块加载后，实际使用还需要一个 GeoIP 数据库。到官网下载需要注册账号，有账号的可以通过官网下载。如果注册遇到问题，可以使用 GitHub 上的镜像仓库：\nhttps://github.com/P3TERX/GeoLite.mmdb 我下载的是 GeoLite2-Country.mmdb，目前已够用。\n下载并配置 GeoIP2 数据库 # 下载 GeoIP2 数据库到 /etc/nginx/geoip2 并配置加载：\n# 在 Nginx 主配置文件的 http 段中加入以下内容 vim /etc/nginx/nginx.conf # GeoIP 配置 geoip2 /etc/nginx/geoip2/GeoLite2-Country.mmdb { auto_reload 5m; $geoip2_metadata_country_build metadata build_epoch; $geoip2_data_country_code default=US country iso_code; $geoip2_data_country_name country names en; } 配置日志格式 # 按照 Dashboard 文档配置日志格式 json_analytics：\n注意：geoip_country_code 这里因为我们使用的是 GeoIP2，需要替换为我们上面所定义的变量 geoip2_data_country_code\nvim /etc/nginx/nginx.conf # 添加 Dashboard 所需的日志格式 log_format json_analytics escape=json \u0026#39;{\u0026#39; \u0026#39;\u0026#34;msec\u0026#34;: \u0026#34;$msec\u0026#34;, \u0026#39; # request unixtime in seconds with a milliseconds resolution \u0026#39;\u0026#34;connection\u0026#34;: \u0026#34;$connection\u0026#34;, \u0026#39; # connection serial number \u0026#39;\u0026#34;connection_requests\u0026#34;: \u0026#34;$connection_requests\u0026#34;, \u0026#39; # number of requests made in connection \u0026#39;\u0026#34;pid\u0026#34;: \u0026#34;$pid\u0026#34;, \u0026#39; # process pid \u0026#39;\u0026#34;request_id\u0026#34;: \u0026#34;$request_id\u0026#34;, \u0026#39; # the unique request id \u0026#39;\u0026#34;request_length\u0026#34;: \u0026#34;$request_length\u0026#34;, \u0026#39; # request length (including headers and body) \u0026#39;\u0026#34;remote_addr\u0026#34;: \u0026#34;$remote_addr\u0026#34;, \u0026#39; # client IP \u0026#39;\u0026#34;remote_user\u0026#34;: \u0026#34;$remote_user\u0026#34;, \u0026#39; # client HTTP username \u0026#39;\u0026#34;remote_port\u0026#34;: \u0026#34;$remote_port\u0026#34;, \u0026#39; # client port \u0026#39;\u0026#34;time_local\u0026#34;: \u0026#34;$time_local\u0026#34;, \u0026#39; \u0026#39;\u0026#34;time_iso8601\u0026#34;: \u0026#34;$time_iso8601\u0026#34;, \u0026#39; # local time in the ISO 8601 standard format \u0026#39;\u0026#34;request\u0026#34;: \u0026#34;$request\u0026#34;, \u0026#39; # full path no arguments if the request \u0026#39;\u0026#34;request_uri\u0026#34;: \u0026#34;$request_uri\u0026#34;, \u0026#39; # full path and arguments if the request \u0026#39;\u0026#34;args\u0026#34;: \u0026#34;$args\u0026#34;, \u0026#39; # args \u0026#39;\u0026#34;status\u0026#34;: \u0026#34;$status\u0026#34;, \u0026#39; # response status code \u0026#39;\u0026#34;body_bytes_sent\u0026#34;: \u0026#34;$body_bytes_sent\u0026#34;, \u0026#39; # the number of body bytes exclude headers sent to a client \u0026#39;\u0026#34;bytes_sent\u0026#34;: \u0026#34;$bytes_sent\u0026#34;, \u0026#39; # the number of bytes sent to a client \u0026#39;\u0026#34;http_referer\u0026#34;: \u0026#34;$http_referer\u0026#34;, \u0026#39; # HTTP referer \u0026#39;\u0026#34;http_user_agent\u0026#34;: \u0026#34;$http_user_agent\u0026#34;, \u0026#39; # user agent \u0026#39;\u0026#34;http_x_forwarded_for\u0026#34;: \u0026#34;$http_x_forwarded_for\u0026#34;, \u0026#39; # http_x_forwarded_for \u0026#39;\u0026#34;http_host\u0026#34;: \u0026#34;$http_host\u0026#34;, \u0026#39; # the request Host: header \u0026#39;\u0026#34;server_name\u0026#34;: \u0026#34;$server_name\u0026#34;, \u0026#39; # the name of the vhost serving the request \u0026#39;\u0026#34;request_time\u0026#34;: \u0026#34;$request_time\u0026#34;, \u0026#39; # request processing time in seconds with msec resolution \u0026#39;\u0026#34;upstream\u0026#34;: \u0026#34;$upstream_addr\u0026#34;, \u0026#39; # upstream backend server for proxied requests \u0026#39;\u0026#34;upstream_connect_time\u0026#34;: \u0026#34;$upstream_connect_time\u0026#34;, \u0026#39; # upstream handshake time incl. TLS \u0026#39;\u0026#34;upstream_header_time\u0026#34;: \u0026#34;$upstream_header_time\u0026#34;, \u0026#39; # time spent receiving upstream headers \u0026#39;\u0026#34;upstream_response_time\u0026#34;: \u0026#34;$upstream_response_time\u0026#34;, \u0026#39; # time spend receiving upstream body \u0026#39;\u0026#34;upstream_response_length\u0026#34;: \u0026#34;$upstream_response_length\u0026#34;, \u0026#39; # upstream response length \u0026#39;\u0026#34;upstream_cache_status\u0026#34;: \u0026#34;$upstream_cache_status\u0026#34;, \u0026#39; # cache HIT/MISS where applicable \u0026#39;\u0026#34;ssl_protocol\u0026#34;: \u0026#34;$ssl_protocol\u0026#34;, \u0026#39; # TLS protocol \u0026#39;\u0026#34;ssl_cipher\u0026#34;: \u0026#34;$ssl_cipher\u0026#34;, \u0026#39; # TLS cipher \u0026#39;\u0026#34;scheme\u0026#34;: \u0026#34;$scheme\u0026#34;, \u0026#39; # http or https \u0026#39;\u0026#34;request_method\u0026#34;: \u0026#34;$request_method\u0026#34;, \u0026#39; # request method \u0026#39;\u0026#34;server_protocol\u0026#34;: \u0026#34;$server_protocol\u0026#34;, \u0026#39; # request protocol, like HTTP/1.1 or HTTP/2.0 \u0026#39;\u0026#34;pipe\u0026#34;: \u0026#34;$pipe\u0026#34;, \u0026#39; # \u0026#34;p\u0026#34; if request was pipelined, \u0026#34;.\u0026#34; otherwise \u0026#39;\u0026#34;gzip_ratio\u0026#34;: \u0026#34;$gzip_ratio\u0026#34;, \u0026#39; \u0026#39;\u0026#34;http_cf_ray\u0026#34;: \u0026#34;$http_cf_ray\u0026#34;,\u0026#39; \u0026#39;\u0026#34;geoip_country_code\u0026#34;: \u0026#34;$geoip2_data_country_code\u0026#34;\u0026#39; \u0026#39;}\u0026#39;; 更改虚拟主机日志格式 # # 更改对应虚拟主机中的日志格式为上面定义的 json_analytics vim vhost/www.conf server { listen 443 http2; server_name www.treesir.pub; access_log /data/wwwlogs/nps_www_access_nginx.log json_analytics; ... } 查看效果，已变成我们所期望的格式：\n处理真实 IP 获取问题 # ⚠️ 注意：这里日志所返回的 geoip_country_code 也有可能不是你所期待的效果。比如你的站点位于 CDN 或者代理服务器的后端时，会导致 remote_addr 地址不是真正客户端的真实 IP，影响到最终结果。\n解决方案：\n使用 Nginx 自带的 set_real_ip_from 使用模块提供的 geoip2_proxy 相关参数 推荐使用 set_real_ip_from 方式，同时可以让日志中 remote_addr 也获取到真实的 IP，使日志便于后续统计和分析：\nvim /etc/nginx/nginx.conf # 更改主配置文件，在 http 段加入以下内容 # 获取 CDN 后真实 IP set_real_ip_from 0.0.0.0/0; real_ip_header X-Forwarded-For; 配置 Loki # 由于不想在自己的 HomeLab 中部署 Loki（主要是部署单实例的 Loki 使用体验不好，部署微服务架构又太重了），我们这里基于 Grafana Cloud 实现 Loki 和 Dashboard 的展示。这里省略创建账号等操作，登录入口地址如下，有 Google 账号的可以直接第三方登录：\nhttps://grafana.com/products/cloud/ 什么是 Grafana Cloud # Grafana Cloud 是 Grafana Labs 提供的一种托管服务，它提供了 Grafana、Prometheus 和 Loki 的托管版本。这意味着你可以使用这些强大的开源监控和可视化工具，而无需自己管理和维护底层的基础设施。\nGrafana Cloud 的主要特性：\n托管的 Grafana：可以使用最新版本的 Grafana，无需自己进行安装和升级。\n托管的 Prometheus 和 Alertmanager：可以使用 Prometheus 和 Alertmanager 来收集和管理指标数据，无需自己进行安装和配置。\n托管的 Loki：可以使用 Loki 来收集和查询日志数据，无需自己进行安装和配置。\n托管的 Grafana Tempo：Grafana Tempo 是一个高度可扩展的、易于操作的分布式追踪后端。可以使用它来存储和查询追踪数据。\n集成的警报和通知：可以使用 Grafana Cloud 的警报和通知功能，及时了解系统状态。\n安全和可靠：Grafana Cloud 提供了数据加密、备份和高可用性等安全和可靠性特性。\n部署 Promtail 日志代理 # Promtail 是 Loki 的日志代理，通过将主机中的日志收集起来，POST 到 Loki 中，实现日志的统一存储。下面我们使用 Docker Compose 来部署 Promtail。\n安装 Docker Compose # 如果已安装请跳过此步骤：\ncurl -L \u0026#34;https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)\u0026#34; -o /usr/local/bin/docker-compose \\ \u0026amp;\u0026amp; chmod +x /usr/local/bin/docker-compose \\ \u0026amp;\u0026amp; ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose \\ \u0026amp;\u0026amp; docker-compose --version 初始化 Promtail 部署 # 注意：/data/wwwlogs 为你要收集日志的目录，请按实际情况更改\nmkdir -p /data/docker-compose/loki-promtail/ cd /data/docker-compose/loki-promtail/ # 创建 docker-compose.yaml 文件 cat \u0026gt; docker-compose.yaml \u0026lt;\u0026lt; EOF version: \u0026#34;3\u0026#34; networks: loki: services: promtail: image: grafana/promtail:2.7.4 volumes: - /data/wwwlogs:/data/wwwlogs:ro - ./config/config.yml:/etc/promtail/config.yml:ro - /etc/localtime:/etc/localtime command: -config.file=/etc/promtail/config.yml EOF 创建 Promtail 配置文件 # 创建 config/config.yml 配置文件：\n注意：更改 USER_ID 和 TOKEN 为你在 Grafana Cloud 页面生成的实际值，登录 Cloud 后找到 Loki 相关配置\nmkdir -p config cat \u0026gt; config/config.yml \u0026lt;\u0026lt; EOF server: http_listen_port: 0 grpc_listen_port: 0 positions: filename: /tmp/positions.yaml clients: - url: https://\\${USER_ID}:\\${TOKEN}@logs-prod-021.grafana.net/loki/api/v1/push scrape_configs: - job_name: system pipeline_stages: - replace: expression: \u0026#39;(?:[0-9]{1,3}\\.){3}([0-9]{1,3})\u0026#39; replace: \u0026#39;***\u0026#39; static_configs: - targets: - www.treesir.pub labels: job: nginx_access_log host: ali-vps agent: promtail __path__: /data/wwwlogs/nps_www_access_nginx.log EOF 启动 Promtail 日志收集 # docker-compose up -d 配置 Dashboard # 回到 Grafana Cloud 主页，点击进入 Grafana。\n加载 Dashboard # 按照以下步骤导入 Dashboard：\n调整变量和 Dashboard # 导入时会有一些报错，不用担心，我们调整一下变量即可，这是缺少变量导致的。\n在 datasource 这里添加过滤 .*-logs，然后点击 Apply。为了防止刷新页面失效，记得点击 Save Dashboard。\n选择 Dashboard 参数，就可以看到对应的数值了：\n可以看到 Top Countries 这里有乱码，点击编辑，右边选项栏向下滑动，找到 Mapping 把问号删除即可，或者改成你想要的映射内容。\n更改后不要忘记点击 Save：\n添加 PV \u0026amp; UV 指标 # 该图表，默认没有提供 PV \u0026amp; UV 指标，Loki 提供了一套与 Prometheus 类似的查询语法，叫 LogQL , 我们可以通过此查询语法，通过自定义 Visualization ，得到我们想要的内容。\n获取 24H PV 指标, logQL 示例\n由于我使用了 Blackbox Exporter 监控探针，避免影响数据的真实性，我这里把这部分请求添加了过滤\nsum(count_over_time( {job=\u0026#34;$job\u0026#34;} | json | __error__=\u0026#34;\u0026#34; and remote_addr != \u0026#34;\u0026#34; and http_user_agent !~ \u0026#34;^Blackbox.*\u0026#34; [24h] )) 新建图形\n输入 logQL 测试运行，可以看到已经有结果了，由于我们这里且需要基于 现在时间 往前推 24h 的 PV 统计，但可以看到下图，它自动查询到了 1473 份数据，并按照这个数据绘制了 区间水平线，这其实有点没有必要，我们进行优化一下。\n优化查询参数，更改最大查询数据为 1 , 时间区间选择 15s, 同时隐藏 时间信息。这样就得到我们预期的结果了。\n获取 24H UV 指标, logQL 示例\n配置方法与 上面的 PV 配置一致，如果你想要好看的样式，可以基于现有 Dashbaord 的图表进行参考配置，这部分就由自己的自由发挥，此篇文档不做这里介绍。\nsum(sum by (remote_addr) ( sum by (remote_addr, geoip_country_code) ( count_over_time( {job=\u0026#34;$job\u0026#34;} | json | __error__=\u0026#34;\u0026#34; and remote_addr != \u0026#34;\u0026#34; and http_user_agent !~ \u0026#34;^Blackbox.*\u0026#34; [24h] ) ) ^ 0 )) 统计区间 PV 增长情况, logQL 示例如下\n这里的区间我们选择使用 $__interval 内置变量，可以在使用时很好的和主页上的 区间选择器，进行联动查询。\nsum by (remote_addr) ((count_over_time( {job=\u0026#34;$job\u0026#34;} | json | __error__=\u0026#34;\u0026#34; and remote_addr != \u0026#34;\u0026#34; and http_user_agent !~ \u0026#34;^Blackbox.*\u0026#34; [$__interval] ))) 这次是用到的区间查询，配置方法与上面的两个不一样了，再次点击 New Visualization，选择 Time series 类型\n优化显示，现在看这个图，显的有的臃肿，我们优化一下。Legend 这里我们输入 {{remote_addr}}\n左边找到 Legend，我们把它的 可见效关掉。现在就看起来舒服多了\n最终效果如下。已经与我最初所展示的效果接近。美化的工作交给你自己，如果实在不行，那你参考我这个导出的 Json 文件吧。\n总结 # Grafana 的可玩性确实很高，免费的 Grafana Cloud 体验非常好。Grafana Cloud 的查询性能表现出色，我尝试自建 Loki 并使用微服务模式进行部署，但始终无法达到 Cloud 上的使用体验。\n总体来说，使用 Loki 统计博客日志的整体体验很不错，通过本文的配置，我们成功实现了：\n项目成果 # 完整的日志收集链路：从 OpenResty 日志格式配置到 Promtail 收集，再到 Loki 存储 丰富的可视化面板：包含 PV/UV 统计、地理分布、访问趋势等关键指标 成本控制：基于 Grafana Cloud 免费版本，无需自建基础设施 实时监控：支持实时的访问数据分析和告警 遗留问题 # 经过这次实践，还有几个问题有待解决：\n1. UV 区间增长统计问题 # 统计 UV 的区间增长时，会得到唯一的 1，如果与 PV 同时只有一个访客时，会出现重叠现象：\nsum(sum by (remote_addr) ( sum by (remote_addr, geoip_country_code) ( count_over_time( {job=\u0026#34;$job\u0026#34;} | json | __error__=\u0026#34;\u0026#34; and remote_addr != \u0026#34;\u0026#34; and http_user_agent !~ \u0026#34;^Blackbox.*\u0026#34; [24h] ) ) ^ 0 )) 2. GeoIP 城市信息获取问题 # 加载 GeoLite2-City.mmdb 库时，无法获取正确的城市名称（目前暂时用不到，后续可能会需要）。\n3. Promtail 时区问题 # Promtail 打印日志时存在时区问题，目前只能通过修改源码并重新编译来解决。\n后续优化方向 # 完善 LogQL 查询：优化 UV 统计的查询语句，解决重叠问题 扩展地理信息：研究城市级别的地理位置统计 告警配置：基于访问异常情况配置告警规则 性能优化：优化日志格式和查询性能 这套基于 Loki 的网站访问统计系统为个人网站提供了企业级的监控能力，同时保持了轻量化和成本效益的优势。\n","date":"2023年7月29日","externalUrl":null,"permalink":"/posts/blog-starts-loki-data-analysis/","section":"博客文章","summary":"\u003cp\u003e在网站运营过程中，访问数据分析是了解用户行为和优化网站性能的重要手段。本文将分享如何使用 Grafana Loki 构建一个轻量级且功能强大的网站访问统计分析系统。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e项目背景 \n    \u003cdiv id=\"项目背景\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%a1%b9%e7%9b%ae%e8%83%8c%e6%99%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e现有方案的局限性 \n    \u003cdiv id=\"现有方案的局限性\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%b0%e6%9c%89%e6%96%b9%e6%a1%88%e7%9a%84%e5%b1%80%e9%99%90%e6%80%a7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e在寻找网站统计解决方案的过程中，传统的统计工具存在一些局限性：\u003c/p\u003e","title":"使用 Grafana Loki 构建网站访问统计分析系统","type":"posts"},{"content":"","date":"2023年7月29日","externalUrl":null,"permalink":"/tags/%E7%BD%91%E7%AB%99%E7%9B%91%E6%8E%A7/","section":"Tags","summary":"","title":"网站监控","type":"tags"},{"content":" 说明 # Gitea是一个开源的自助式Git服务，用于托管和管理Git仓库。它是一个轻量级且易于安装和使用的解决方案，类似于GitHub或GitLab，可以在私有服务器上搭建自己的Git仓库服务。Gitea提供了一系列功能，使团队或个人能够方便地进行版本控制和协作开发，包括：\n仓库管理：可以创建、克隆、推送和拉取Git仓库，管理分支和标签，查看提交历史和代码差异等操作。\n用户和权限管理：可以创建用户账号，管理用户的访问权限，并为不同的用户或团队分配不同的角色和权限，以控制仓库的访问和操作。\n问题追踪：提供了一个问题追踪系统，用户可以创建和管理问题、缺陷或需求，并与仓库的代码和提交相关联，方便团队进行协作和解决问题。\nPull请求：支持Pull请求（合并请求），允许用户在不直接修改主分支的情况下，通过提出Pull请求来建议将自己的代码合并到项目中。\n代码审查：Gitea提供了代码审查功能，可以让团队成员对提交的代码进行审查、讨论和评论，以提高代码质量和团队协作。\n集成与插件：Gitea支持与其他工具和服务的集成，如邮件通知、CI/CD工具和第三方插件等。\nGitea 从 1.19 开始支持 Actions，使用前提需要像 GitlabRunner 一样部署一个独立的 Runner 来管理 Job，名叫 ActRunner，具体可参考文档:\nhttps://docs.gitea.com/next/usage/actions/overview Act_Runner 部署安装 # Gitea app.ini 配置文件添加如下内容，开启 Actions 功能\n[repo.actions] ENABLED = true act_runner 编译安装\n$ git clone https://gitea.com/gitea/act_runner.git $ cd act_runner $ make build mv act_runner /usr/local/bin acr_runner 注册\n注册 Token，到 Gitea 网页后台进行获取 执行后会在所在目录生成 .runner 文件，记住这个执行时所在目录，后面要用。 cd /root/act_runner act_runner register --instance https://git.treesir.pub \\ --token xxxx --no-interactive 生成配置文件\nmkdir -p /etc/act_runner/ act_runner generate-config \u0026gt; /etc/act_runner/config.yaml # 生成配置文件 cat /etc/act_runner/config.yaml|egrep -v \u0026#39;^#|^$| #\u0026#39; 更改过后的配置文件展示\n使用 systemctl 管理\n⚠️\nPATH 环境变量需要根据机器实际情况更改一下，可以执行 echo $PATH 来获取自身的 PATH 变量信息作为替换。\nWorkingDirectory 需要更改为在执行 上面第三步 时所在目录，我这里为 /root/act_runner\n由于简化部署原因，这样所使用的用户为 root, 如对安全性有要求的同学，可以进行更改，主要更改后的用户需要拥有Docker 权限。\ncat \u0026gt; /etc/systemd/system/gitea-runner.service \u0026lt;\u0026lt; EOF Description=Gitea Actions runner Documentation=https://gitea.com/gitea/act_runner After=docker.service [Service] Environment=PATH=/opt/jdk/bin/:/usr/local/mysql/bin:/usr/local/openresty/nginx/sbin:/root/.autojump/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/go/bin:/opt/go/bin:/opt/jdk/jre/bin:/opt/maven/bin:/root/.cargo/bin ExecStart=/usr/local/bin/act_runner daemon -c /etc/act_runner/config.yaml ExecReload=/bin/kill -s HUP WorkingDirectory=/root/act_runner NotifyAccess=all User=root Group=root LimitNOFILE=65536 [Install] WantedBy=default.target EOF 设置开机自启动\nsystemctl enable gitea-runner --now 总结 # Gitea Actions 使用风格与 Github 的类似，简单做了一下测试，么有什么问题。更加深入的使用后续再做研究。\n","date":"2023年7月25日","externalUrl":null,"permalink":"/posts/gitea-actrunner-systemd-deployment/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e说明 \n    \u003cdiv id=\"说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eGitea是一个开源的自助式Git服务，用于托管和管理Git仓库。它是一个轻量级且易于安装和使用的解决方案，类似于GitHub或GitLab，可以在私有服务器上搭建自己的Git仓库服务。Gitea提供了一系列功能，使团队或个人能够方便地进行版本控制和协作开发，包括：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e仓库管理：可以创建、克隆、推送和拉取Git仓库，管理分支和标签，查看提交历史和代码差异等操作。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e用户和权限管理：可以创建用户账号，管理用户的访问权限，并为不同的用户或团队分配不同的角色和权限，以控制仓库的访问和操作。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e问题追踪：提供了一个问题追踪系统，用户可以创建和管理问题、缺陷或需求，并与仓库的代码和提交相关联，方便团队进行协作和解决问题。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ePull请求：支持Pull请求（合并请求），允许用户在不直接修改主分支的情况下，通过提出Pull请求来建议将自己的代码合并到项目中。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e代码审查：Gitea提供了代码审查功能，可以让团队成员对提交的代码进行审查、讨论和评论，以提高代码质量和团队协作。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e集成与插件：Gitea支持与其他工具和服务的集成，如邮件通知、CI/CD工具和第三方插件等。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eGitea 从 \u003ccode\u003e1.19\u003c/code\u003e 开始支持 Actions，使用前提需要像 GitlabRunner 一样部署一个独立的 Runner 来管理 Job，名叫 \u003ccode\u003eActRunner\u003c/code\u003e，具体可参考文档:\u003c/p\u003e","title":"Gitea Actions ActRunner 基于 Systemd 部署安装","type":"posts"},{"content":"","date":"2023年7月25日","externalUrl":null,"permalink":"/tags/nexus3/","section":"Tags","summary":"","title":"Nexus3","type":"tags"},{"content":" 说明 # Sonatype Nexus Repository 是什么？ # Sonatype Nexus Repository 是一个用于管理和分发软件组件的开源仓库管理系统。它提供了一个集中化的平台，使开发人员能够有效地存储、共享和发布各种类型的软件包。Nexus Repository 支持多种主流技术栈，并具有强大的安全性和可扩展性。通过使用 Sonatype Nexus Repository，团队可以更好地控制其软件构建过程，并确保高质量和稳定性的交付。 by ChatCPT Nexus3 私服文件下载至本地的用途是什么？ # 比如我们要将 Nexus3 私服中的依赖包制品，进行迁移，因 Nexus3 基于 BlobStore 技术实现对文件的落盘存储，无法在对应节点中直接看到制品文件，此时则需要通过一些手段进行转换取出，虽然可以通过网页一个一个点击下载，但如果下载制品数量过多，则不会是个过于明智的选择。迁移的场景也有很多种，如: 迁移至其他实例，或离线环境 需要更换到其他类型的存储库如: JFrog 文件下载至本地 # 下面所展示的方法，使用到了 Nexus3 的API 进行实现，具体可参考如下文档：\nhttps://help.sonatype.com/repomanager3/integrations/rest-and-integration-api 下述脚本，对特殊类私服未做测试如 Docker, 目前已测试支持,且通过的私服类型有: Maven、NPM、PYPI、RAW。\n脚本如下 # 使用说明，更改如下变量为你实际的\nNEXUS_USER: 实例用户名 NEXUS_PASS: 实例密码 NEXUS_URL: 实例URL NEXUS_REPO: 需要下载至本地的私服地址 使用前还需确保已经安装了 jq\n#!/bin/bash NEXUS_USER=\u0026#39;admin\u0026#39; NEXUS_PASS=\u0026#39;xxxx\u0026#39; NEXUS_URL=\u0026#34;https://$NEXUS_USER:$NEXUS_PASS@nexus.treesir.pub\u0026#34; NEXUS_REPO=\u0026#39;static-file\u0026#39; # --- cToken=\u0026#39;\u0026#39; count=0 DOWNLOAD_PATH=${NEXUS_REPO} NEXUS_HTTP_URL=$(echo $NEXUS_URL|sed \u0026#34;s|$NEXUS_USER:$NEXUS_PASS@||g\u0026#34;) function assetsPage(){ if [ \u0026#34;${count}\u0026#34; -eq 0 ];then JSON=`curl -X \u0026#39;GET\u0026#39; \u0026#34;${NEXUS_URL}/service/rest/v1/assets?repository=${NEXUS_REPO}\u0026#34; -H \u0026#39;accept: application/json\u0026#39; 2\u0026gt;/dev/null` else JSON=`curl -X \u0026#39;GET\u0026#39; \u0026#34;${NEXUS_URL}/service/rest/v1/assets?repository=${NEXUS_REPO}\u0026amp;continuationToken=${cToken}\u0026#34; -H \u0026#39;accept: application/json\u0026#39; 2\u0026gt;/dev/null` # cToken=\u0026#39;\u0026#39; fi if [ -n \u0026#34;${JSON}\u0026#34; ];then cToken=`echo \u0026#34;${JSON}\u0026#34;| jq .continuationToken |sed \u0026#34;s|\\\u0026#34;||g\u0026#34;` else exit 1 fi if [ -n ${cToken} ];then echo \u0026#34;${JSON}\u0026#34;| jq .items[].downloadUrl | sed \u0026#34;s|\\\u0026#34;||g\u0026#34; let count++ assetsPage elif [ \u0026#34;${cToken}\u0026#34; == \u0026#39;null\u0026#39; ];then echo \u0026#34;${JSON}\u0026#34;| jq .items[].downloadUrl | sed \u0026#34;s|\\\u0026#34;||g\u0026#34; fi } function downAssetsUploadAssets(){ for URL in $(assetsPage);do UPLOAD_URL=$(echo \u0026#34;$URL\u0026#34;|sed \u0026#34;s|${NEXUS_HTTP_URL}/repository/${NEXUS_REPO}/||g\u0026#34;) if [ ! $(echo \u0026#34;${UPLOAD_URL}\u0026#34;|grep \u0026#39;/\u0026#39;|wc -l) -eq 0 ];then mkdir -p ./${DOWNLOAD_PATH}/\u0026#34;${UPLOAD_URL%/*}\u0026#34; wget --no-check-certificate -O \u0026#34;./${DOWNLOAD_PATH}/${UPLOAD_URL}\u0026#34; \u0026#34;${URL}\u0026#34; \u0026gt;\u0026gt; download.log 2\u0026gt;\u0026amp;1 else mkdir -p \u0026#34;${SOURCE_REPO}\u0026#34; wget --no-check-certificate -O \u0026#34;./${DOWNLOAD_PATH}/${UPLOAD_URL}\u0026#34; \u0026#34;${URL}\u0026#34; \u0026gt;\u0026gt; download.log 2\u0026gt;\u0026amp;1 fi done } # assetsPage downAssetsUploadAssets 使用效果展示 # 总结 # 通过上面这个脚本就可以轻松一键的将文件转储下载至本地了，后面我再更新一下如何将 Maven、NPM、PYPI、RAW 这类私服制品文件，迁移 上传至其他实例或平台中，比如: JForg。\n","date":"2023年7月25日","externalUrl":null,"permalink":"/posts/nexus-browse-directory-download/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e说明 \n    \u003cdiv id=\"说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003eSonatype Nexus Repository 是什么？ \n    \u003cdiv id=\"sonatype-nexus-repository-是什么\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#sonatype-nexus-repository-%e6%98%af%e4%bb%80%e4%b9%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cul\u003e\n\u003cli\u003eSonatype Nexus Repository 是一个用于管理和分发软件组件的开源仓库管理系统。它提供了一个集中化的平台，使开发人员能够有效地存储、共享和发布各种类型的软件包。Nexus Repository 支持多种主流技术栈，并具有强大的安全性和可扩展性。通过使用 Sonatype Nexus Repository，团队可以更好地控制其软件构建过程，并确保高质量和稳定性的交付。\u003ccode\u003e by ChatCPT\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003eNexus3 私服文件下载至本地的用途是什么？ \n    \u003cdiv id=\"nexus3-私服文件下载至本地的用途是什么\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#nexus3-%e7%a7%81%e6%9c%8d%e6%96%87%e4%bb%b6%e4%b8%8b%e8%bd%bd%e8%87%b3%e6%9c%ac%e5%9c%b0%e7%9a%84%e7%94%a8%e9%80%94%e6%98%af%e4%bb%80%e4%b9%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003col\u003e\n\u003cli\u003e比如我们要将 \u003ccode\u003eNexus3\u003c/code\u003e 私服中的依赖包制品，\u003ccode\u003e进行迁移\u003c/code\u003e，因 Nexus3 基于 \u003ca\n  href=\"https://help.sonatype.com/repomanager3/planning-your-implementation/storage-guide\"\n    target=\"_blank\"\n  \u003eBlobStore\u003c/a\u003e 技术实现对文件的落盘存储，无法在对应节点中直接看到制品文件，此时则需要通过一些手段进行转换取出，虽然可以通过网页一个一个点击下载，但如果下载制品数量过多，则不会是个过于明智的选择。迁移的场景也有很多种，如:\n\u003cul\u003e\n\u003cli\u003e迁移至其他实例，或离线环境\u003c/li\u003e\n\u003cli\u003e需要更换到其他类型的存储库如:  \u003ccode\u003eJFrog\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e文件下载至本地 \n    \u003cdiv id=\"文件下载至本地\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%87%e4%bb%b6%e4%b8%8b%e8%bd%bd%e8%87%b3%e6%9c%ac%e5%9c%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e下面所展示的方法，使用到了 Nexus3 的API 进行实现，具体可参考如下文档：\u003c/p\u003e","title":"Sonatype Nexus Repository（Nexus3） 私服文件下载至本地 - (使用进阶篇 一)","type":"posts"},{"content":"","date":"2023年7月24日","externalUrl":null,"permalink":"/tags/cronjob/","section":"Tags","summary":"","title":"Cronjob","type":"tags"},{"content":"","date":"2023年7月24日","externalUrl":null,"permalink":"/tags/minio/","section":"Tags","summary":"","title":"Minio","type":"tags"},{"content":"","date":"2023年7月24日","externalUrl":null,"permalink":"/tags/mysql/","section":"Tags","summary":"","title":"Mysql","type":"tags"},{"content":"","date":"2023年7月24日","externalUrl":null,"permalink":"/tags/%E5%A4%87%E4%BB%BD/","section":"Tags","summary":"","title":"备份","type":"tags"},{"content":"随着 Kubernetes 在生产环境中的广泛应用，越来越多的有状态应用（如 MySQL 数据库）也开始部署在 K8s 集群中。数据安全是生产环境的重中之重，本文将详细介绍如何使用 Kubernetes CronJob 实现 MySQL 数据库的自动化备份，并将备份文件安全地存储到 MinIO 对象存储中。\n方案概述 # 备份架构 # flowchart LR MySQL[MySQL PodDatabase] --\u003e|导出数据| CronJob[CronJob Podmysqldump] CronJob --\u003e|上传备份| MinIO[MinIO ServerBucket] 方案优势 # 自动化: 使用 CronJob 实现定时自动备份，无需人工干预 可靠性: 备份文件存储在独立的对象存储中，提高数据安全性 可扩展: 支持多数据库实例的备份，易于扩展 监控友好: 可以通过 Kubernetes 原生监控查看备份任务状态 成本效益: 使用开源的 MinIO 作为存储后端，降低成本 环境准备 # 前置条件 # 组件 版本要求 说明 Kubernetes v1.18+ 支持 CronJob v1 API MySQL 5.7+ / 8.0+ 运行在 K8s 集群中 MinIO 任意版本 对象存储服务 网络连通性 - CronJob Pod 能访问 MySQL 和 MinIO 自定义镜像 # 本方案使用了专门构建的备份镜像，包含了 mysqldump 和 MinIO 客户端工具。\n镜像仓库: kube-mysqldump-tominio-cron\n该镜像包含以下组件：\nMySQL 客户端工具（mysqldump） MinIO 客户端（mc） 备份脚本和错误处理逻辑 详细配置指南 # MySQL 数据库准备 # 在开始配置备份任务之前，确保您的 MySQL 数据库已正确部署在 Kubernetes 集群中：\n# 检查 MySQL 服务状态 kubectl get pods -l app=mysql kubectl get svc mysql-server # 验证数据库连接 kubectl exec -it mysql-pod -- mysql -u root -p -e \u0026#34;SHOW DATABASES;\u0026#34; MinIO 对象存储准备 # 创建存储桶 # 在 MinIO 中创建专用的备份存储桶：\n# 使用 MinIO 客户端创建存储桶 mc mb minio/mysql-backups # 设置存储桶策略（可选） mc policy set download minio/mysql-backups 配置生命周期策略 # 为了管理存储成本，建议配置自动清理策略：\n{ \u0026#34;Rules\u0026#34;: [ { \u0026#34;ID\u0026#34;: \u0026#34;mysql-backup-lifecycle\u0026#34;, \u0026#34;Status\u0026#34;: \u0026#34;Enabled\u0026#34;, \u0026#34;Expiration\u0026#34;: { \u0026#34;Days\u0026#34;: 30 } } ] } Kubernetes 配置详解 # Secret 配置（敏感信息） # 创建包含 MinIO 连接信息的 Secret：\n环境变量 说明 示例值 安全建议 MINIO_SERVER MinIO 服务器地址 http://minio.example.com:9000 使用内部服务地址 MINIO_ACCESS_KEY MinIO 访问密钥 backup-user 创建专用备份用户 MINIO_SECRET_KEY MinIO 密钥 strong-password-123 使用强密码 MINIO_BUCKET 存储桶路径 mysql-backups/prod 按环境分目录 ConfigMap 配置（非敏感信息） # 环境变量 说明 示例值 配置说明 dbhost MySQL 服务地址 mysql-server.default.svc.cluster.local 使用完整的 DNS 名称 all_databases 备份范围 true / false true=所有数据库，false=指定数据库 backup_schedule 备份计划 0 2 * * * Cron 表达式格式 backup_retention 保留天数 30 本地备份文件保留时间 MySQL 连接配置 # 环境变量 说明 获取方式 DB_USER 数据库用户名 通常使用 root 用户 DB_PASS 数据库密码 从 MySQL Secret 中获取 DB_PORT 数据库端口 默认 3306 Cron 表达式说明 # 表达式 说明 执行时间 0 2 * * * 每天凌晨2点 适合生产环境 0 */6 * * * 每6小时一次 高频备份 0 2 * * 0 每周日凌晨2点 周备份 0 2 1 * * 每月1号凌晨2点 月备份 配置注意事项:\n✅ 存储桶预创建: 确保 MinIO 存储桶已经创建并具有写入权限 ✅ 网络连通性: 验证 CronJob Pod 能够访问 MySQL 和 MinIO 服务 ✅ 权限配置: 确保备份用户具有足够的数据库读取权限 ✅ 时区设置: CronJob 使用 UTC 时间，注意时区转换 ✅ 资源限制: 为备份任务设置合适的 CPU 和内存限制 完整部署配置 # 第一步：创建 ConfigMap # 创建包含备份配置的 ConfigMap：\napiVersion: v1 kind: ConfigMap metadata: name: mysql-backup-config namespace: default labels: app: mysql-backup component: config data: # MySQL 连接配置 dbhost: \u0026#34;mysql-server.default.svc.cluster.local\u0026#34; dbport: \u0026#34;3306\u0026#34; # 备份策略配置 all_databases: \u0026#34;true\u0026#34; # 是否备份所有数据库 backup_retention_days: \u0026#34;7\u0026#34; # 本地备份保留天数 compression_enabled: \u0026#34;true\u0026#34; # 是否启用压缩 # 备份文件命名规则 backup_prefix: \u0026#34;mysql-backup\u0026#34; # 备份文件前缀 date_format: \u0026#34;%Y%m%d_%H%M%S\u0026#34; # 时间戳格式 # 高级配置 mysqldump_options: \u0026#34;--single-transaction --routines --triggers --events\u0026#34; parallel_jobs: \u0026#34;1\u0026#34; # 并行备份任务数 第二步：创建 Secret # 创建包含敏感信息的 Secret：\napiVersion: v1 kind: Secret metadata: name: mysql-backup-secrets namespace: default labels: app: mysql-backup component: secrets type: Opaque stringData: # MinIO 连接信息 MINIO_SERVER: \u0026#34;http://minio.minio-system.svc.cluster.local:9000\u0026#34; MINIO_ACCESS_KEY: \u0026#34;backup-user\u0026#34; MINIO_SECRET_KEY: \u0026#34;your-secure-password-here\u0026#34; MINIO_BUCKET: \u0026#34;mysql-backups/production\u0026#34; # MySQL 连接信息 MYSQL_ROOT_PASSWORD: \u0026#34;your-mysql-root-password\u0026#34; # 可选：备份加密密钥 BACKUP_ENCRYPTION_KEY: \u0026#34;your-encryption-key-for-backups\u0026#34; 第三步：创建 CronJob # 创建执行备份任务的 CronJob：\napiVersion: batch/v1 kind: CronJob metadata: name: mysql-backup-cronjob namespace: default labels: app: mysql-backup component: cronjob spec: # 备份计划：每天凌晨 2 点执行 schedule: \u0026#34;0 2 * * *\u0026#34; # 时区设置（Kubernetes 1.24+） timeZone: \u0026#34;Asia/Shanghai\u0026#34; # 历史记录保留 successfulJobsHistoryLimit: 3 # 保留最近 3 次成功的任务 failedJobsHistoryLimit: 1 # 保留最近 1 次失败的任务 # 并发策略 concurrencyPolicy: Forbid # 禁止并发执行 # 启动截止时间 startingDeadlineSeconds: 300 # 5分钟内必须启动 # 任务模板 jobTemplate: metadata: labels: app: mysql-backup component: job spec: # 任务完成后保留时间 ttlSecondsAfterFinished: 86400 # 24小时后清理 # 重试策略 backoffLimit: 2 # 最多重试 2 次 template: metadata: labels: app: mysql-backup component: pod spec: # 重启策略 restartPolicy: OnFailure # 服务账户（如需要特殊权限） # serviceAccountName: mysql-backup-sa containers: - name: mysql-backup image: cdryzun/kube-mysqldump-tominio-cron:v0.2.0 imagePullPolicy: IfNotPresent # 资源限制 resources: requests: memory: \u0026#34;256Mi\u0026#34; cpu: \u0026#34;100m\u0026#34; limits: memory: \u0026#34;1Gi\u0026#34; cpu: \u0026#34;500m\u0026#34; # 环境变量配置 env: # 命名空间信息（通过 Downward API 注入） - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace # Pod 名称（用于日志标识） - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name # MySQL 连接配置 - name: DB_HOST valueFrom: configMapKeyRef: name: mysql-backup-config key: dbhost - name: DB_PORT valueFrom: configMapKeyRef: name: mysql-backup-config key: dbport - name: DB_USER value: \u0026#34;root\u0026#34; - name: DB_PASS valueFrom: secretKeyRef: name: mysql-backup-secrets key: MYSQL_ROOT_PASSWORD # 备份策略配置 - name: ALL_DATABASES valueFrom: configMapKeyRef: name: mysql-backup-config key: all_databases - name: BACKUP_RETENTION_DAYS valueFrom: configMapKeyRef: name: mysql-backup-config key: backup_retention_days - name: MYSQLDUMP_OPTIONS valueFrom: configMapKeyRef: name: mysql-backup-config key: mysqldump_options # MinIO 存储配置 - name: MINIO_SERVER valueFrom: secretKeyRef: name: mysql-backup-secrets key: MINIO_SERVER - name: MINIO_ACCESS_KEY valueFrom: secretKeyRef: name: mysql-backup-secrets key: MINIO_ACCESS_KEY - name: MINIO_SECRET_KEY valueFrom: secretKeyRef: name: mysql-backup-secrets key: MINIO_SECRET_KEY - name: MINIO_BUCKET valueFrom: secretKeyRef: name: mysql-backup-secrets key: MINIO_BUCKET # 备份文件配置 - name: BACKUP_PREFIX valueFrom: configMapKeyRef: name: mysql-backup-config key: backup_prefix - name: DATE_FORMAT valueFrom: configMapKeyRef: name: mysql-backup-config key: date_format # 存储卷挂载 volumeMounts: - name: backup-workspace mountPath: /tmp/backup - name: mysql-backup-script mountPath: /scripts readOnly: true # 健康检查 livenessProbe: exec: command: - /bin/sh - -c - \u0026#34;ps aux | grep -v grep | grep mysqldump || exit 0\u0026#34; initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 10 failureThreshold: 3 # 存储卷定义 volumes: - name: backup-workspace emptyDir: sizeLimit: 10Gi # 限制临时存储大小 - name: mysql-backup-script configMap: name: mysql-backup-script defaultMode: 0755 第四步：创建备份脚本 ConfigMap（可选） # 如果需要自定义备份逻辑，可以创建包含备份脚本的 ConfigMap：\napiVersion: v1 kind: ConfigMap metadata: name: mysql-backup-script namespace: default data: backup.sh: | #!/bin/bash set -euo pipefail # 备份脚本配置 TIMESTAMP=$(date +${DATE_FORMAT:-\u0026#34;%Y%m%d_%H%M%S\u0026#34;}) BACKUP_FILE=\u0026#34;${BACKUP_PREFIX:-mysql-backup}_${TIMESTAMP}.sql\u0026#34; # 日志函数 log() { echo \u0026#34;[$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;)] $1\u0026#34; } # 错误处理 error_exit() { log \u0026#34;ERROR: $1\u0026#34; exit 1 } # 执行备份 log \u0026#34;开始 MySQL 数据库备份...\u0026#34; if [[ \u0026#34;${ALL_DATABASES:-true}\u0026#34; == \u0026#34;true\u0026#34; ]]; then log \u0026#34;备份所有数据库...\u0026#34; mysqldump -h${DB_HOST} -P${DB_PORT:-3306} -u${DB_USER} -p${DB_PASS} \\ --all-databases ${MYSQLDUMP_OPTIONS:-} \u0026gt; /tmp/backup/${BACKUP_FILE} \\ || error_exit \u0026#34;数据库备份失败\u0026#34; else log \u0026#34;备份指定数据库: ${TARGET_DATABASES}\u0026#34; mysqldump -h${DB_HOST} -P${DB_PORT:-3306} -u${DB_USER} -p${DB_PASS} \\ --databases ${TARGET_DATABASES} ${MYSQLDUMP_OPTIONS:-} \u0026gt; /tmp/backup/${BACKUP_FILE} \\ || error_exit \u0026#34;数据库备份失败\u0026#34; fi # 压缩备份文件 if [[ \u0026#34;${COMPRESSION_ENABLED:-true}\u0026#34; == \u0026#34;true\u0026#34; ]]; then log \u0026#34;压缩备份文件...\u0026#34; gzip /tmp/backup/${BACKUP_FILE} BACKUP_FILE=\u0026#34;${BACKUP_FILE}.gz\u0026#34; fi # 上传到 MinIO log \u0026#34;上传备份文件到 MinIO...\u0026#34; mc alias set minio ${MINIO_SERVER} ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY} mc cp /tmp/backup/${BACKUP_FILE} minio/${MINIO_BUCKET}/${BACKUP_FILE} \\ || error_exit \u0026#34;备份文件上传失败\u0026#34; log \u0026#34;备份完成: ${BACKUP_FILE}\u0026#34; # 清理本地文件 rm -f /tmp/backup/${BACKUP_FILE} log \u0026#34;本地临时文件已清理\u0026#34; 部署和验证 # 部署步骤 # 按照以下顺序部署备份系统：\n# 1. 创建 ConfigMap kubectl apply -f mysql-backup-configmap.yaml # 2. 创建 Secret（注意修改敏感信息） kubectl apply -f mysql-backup-secret.yaml # 3. 创建备份脚本 ConfigMap（如果使用自定义脚本） kubectl apply -f mysql-backup-script-configmap.yaml # 4. 创建 CronJob kubectl apply -f mysql-backup-cronjob.yaml # 5. 验证部署 kubectl get cronjob mysql-backup-cronjob kubectl describe cronjob mysql-backup-cronjob 手动触发备份测试 # 在等待定时任务执行之前，可以手动创建一个 Job 来测试备份功能：\n# 基于 CronJob 创建一次性 Job kubectl create job mysql-backup-test --from=cronjob/mysql-backup-cronjob # 查看 Job 状态 kubectl get job mysql-backup-test # 查看 Pod 日志 kubectl logs -f job/mysql-backup-test 验证备份结果 # # 检查 MinIO 中的备份文件 mc ls minio/mysql-backups/ # 验证备份文件完整性 mc cat minio/mysql-backups/mysql-backup_20231024_020000.sql.gz | gunzip | head -20 监控和告警 # 监控指标 # 通过以下方式监控备份任务的执行情况：\n1. Kubernetes 原生监控 # # 查看 CronJob 状态 kubectl get cronjob mysql-backup-cronjob -o wide # 查看最近的 Job 执行历史 kubectl get jobs -l app=mysql-backup --sort-by=.metadata.creationTimestamp # 查看失败的 Job kubectl get jobs -l app=mysql-backup --field-selector status.successful!=1 2. 日志监控 # # 查看最近备份任务的日志 kubectl logs -l app=mysql-backup --tail=100 # 实时监控备份过程 kubectl logs -f -l app=mysql-backup 3. Prometheus 监控指标 # 如果集群中部署了 Prometheus，可以监控以下指标：\n# 示例 PrometheusRule apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: mysql-backup-alerts spec: groups: - name: mysql-backup rules: - alert: MySQLBackupJobFailed expr: kube_job_status_failed{job_name=~\u0026#34;mysql-backup-.*\u0026#34;} \u0026gt; 0 for: 0m labels: severity: critical annotations: summary: \u0026#34;MySQL 备份任务失败\u0026#34; description: \u0026#34;MySQL 备份任务 {{ $labels.job_name }} 执行失败\u0026#34; - alert: MySQLBackupJobMissing expr: time() - kube_cronjob_next_schedule_time{cronjob=\u0026#34;mysql-backup-cronjob\u0026#34;} \u0026gt; 3600 for: 5m labels: severity: warning annotations: summary: \u0026#34;MySQL 备份任务超时\u0026#34; description: \u0026#34;MySQL 备份任务超过预期时间未执行\u0026#34; 告警配置 # 1. 邮件告警 # 在备份脚本中添加邮件通知功能：\n# 在备份脚本中添加 send_notification() { local status=$1 local message=$2 if [[ -n \u0026#34;${SMTP_SERVER:-}\u0026#34; ]]; then echo \u0026#34;$message\u0026#34; | mail -s \u0026#34;MySQL Backup $status\u0026#34; \\ -S smtp=\u0026#34;${SMTP_SERVER}\u0026#34; \\ -S smtp-auth=login \\ -S smtp-auth-user=\u0026#34;${SMTP_USER}\u0026#34; \\ -S smtp-auth-password=\u0026#34;${SMTP_PASS}\u0026#34; \\ \u0026#34;${NOTIFICATION_EMAIL}\u0026#34; fi } # 使用示例 send_notification \u0026#34;SUCCESS\u0026#34; \u0026#34;MySQL 备份成功完成: ${BACKUP_FILE}\u0026#34; send_notification \u0026#34;FAILED\u0026#34; \u0026#34;MySQL 备份失败: ${ERROR_MESSAGE}\u0026#34; 2. Slack 通知 # # Slack Webhook 通知 send_slack_notification() { local status=$1 local message=$2 local color=\u0026#34;good\u0026#34; [[ \u0026#34;$status\u0026#34; == \u0026#34;FAILED\u0026#34; ]] \u0026amp;\u0026amp; color=\u0026#34;danger\u0026#34; curl -X POST -H \u0026#39;Content-type: application/json\u0026#39; \\ --data \u0026#34;{ \\\u0026#34;attachments\\\u0026#34;: [{ \\\u0026#34;color\\\u0026#34;: \\\u0026#34;$color\\\u0026#34;, \\\u0026#34;title\\\u0026#34;: \\\u0026#34;MySQL Backup $status\\\u0026#34;, \\\u0026#34;text\\\u0026#34;: \\\u0026#34;$message\\\u0026#34;, \\\u0026#34;ts\\\u0026#34;: $(date +%s) }] }\u0026#34; \\ \u0026#34;${SLACK_WEBHOOK_URL}\u0026#34; } 故障排除 # 常见问题及解决方案 # 1. 连接问题 # 问题: 无法连接到 MySQL 数据库\n# 诊断步骤 kubectl exec -it mysql-backup-test-pod -- mysql -h${DB_HOST} -u${DB_USER} -p${DB_PASS} -e \u0026#34;SELECT 1\u0026#34; # 检查网络连通性 kubectl exec -it mysql-backup-test-pod -- nc -zv mysql-server 3306 # 检查 DNS 解析 kubectl exec -it mysql-backup-test-pod -- nslookup mysql-server 解决方案:\n验证 MySQL 服务名称和端口 检查网络策略是否阻止连接 确认数据库用户权限 2. MinIO 上传失败 # 问题: 备份文件无法上传到 MinIO\n# 测试 MinIO 连接 kubectl exec -it mysql-backup-test-pod -- mc alias set test ${MINIO_SERVER} ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY} kubectl exec -it mysql-backup-test-pod -- mc ls test/ # 检查存储桶权限 kubectl exec -it mysql-backup-test-pod -- mc stat test/${MINIO_BUCKET} 解决方案:\n验证 MinIO 凭据和服务地址 检查存储桶是否存在 确认网络连通性 3. 资源不足 # 问题: 备份任务因资源不足而失败\n# 检查节点资源使用情况 kubectl top nodes # 检查 Pod 资源限制 kubectl describe pod mysql-backup-test-pod # 查看事件日志 kubectl get events --sort-by=.metadata.creationTimestamp 解决方案:\n调整资源请求和限制 优化备份策略（分批备份） 增加集群资源 4. 备份文件过大 # 问题: 备份文件太大导致存储或传输问题\n解决方案:\n# 启用压缩 COMPRESSION_ENABLED=true # 分库备份 ALL_DATABASES=false TARGET_DATABASES=\u0026#34;db1 db2\u0026#34; # 使用增量备份（需要自定义脚本） BACKUP_TYPE=incremental 日志分析 # 查看详细日志 # # 查看 CronJob 事件 kubectl describe cronjob mysql-backup-cronjob # 查看最近的 Job 日志 kubectl logs $(kubectl get pods -l app=mysql-backup --sort-by=.metadata.creationTimestamp -o jsonpath=\u0026#39;{.items[-1].metadata.name}\u0026#39;) # 查看失败的 Job 详情 kubectl describe job $(kubectl get jobs -l app=mysql-backup --field-selector status.successful!=1 -o jsonpath=\u0026#39;{.items[0].metadata.name}\u0026#39;) 常见错误信息 # 错误信息 可能原因 解决方案 Access denied for user 数据库权限不足 检查用户权限和密码 Can't connect to MySQL server 网络连接问题 检查服务名称和网络策略 The specified bucket does not exist MinIO 存储桶不存在 创建存储桶或检查配置 No space left on device 磁盘空间不足 清理空间或增加存储 运行效果展示 # 成功执行的备份任务 # 图：Kubernetes Dashboard 中显示的备份任务执行状态\nMinIO 中的备份文件 # 图：MinIO 对象存储中的备份文件列表，显示了按时间戳命名的备份文件\n高级配置和优化 # 多数据库实例备份 # 对于多个 MySQL 实例的备份需求，可以采用以下策略：\n方案一：多个 CronJob # 为每个数据库实例创建独立的 CronJob：\n# 为不同环境创建不同的备份任务 kubectl apply -f mysql-backup-production.yaml kubectl apply -f mysql-backup-staging.yaml kubectl apply -f mysql-backup-development.yaml 方案二：统一 CronJob 配置 # 使用一个 CronJob 配置多个数据库实例：\nenv: - name: DB_INSTANCES value: \u0026#34;mysql-prod:3306,mysql-staging:3306,mysql-dev:3306\u0026#34; - name: BACKUP_STRATEGY value: \u0026#34;parallel\u0026#34; # 或 \u0026#34;sequential\u0026#34; 备份策略优化 # 1. 增量备份 # 实现基于 binlog 的增量备份：\n# 在备份脚本中添加增量备份逻辑 if [[ \u0026#34;${BACKUP_TYPE}\u0026#34; == \u0026#34;incremental\u0026#34; ]]; then # 获取上次备份的 binlog 位置 LAST_POSITION=$(cat /tmp/last_backup_position.txt) # 执行增量备份 mysqlbinlog --start-position=${LAST_POSITION} \\ --host=${DB_HOST} \\ --user=${DB_USER} \\ --password=${DB_PASS} \\ \u0026gt; /tmp/backup/incremental_${TIMESTAMP}.sql fi 2. 并行备份 # 对于大型数据库，可以实现并行备份：\n# 并行备份多个数据库 backup_database() { local db_name=$1 mysqldump -h${DB_HOST} -u${DB_USER} -p${DB_PASS} \\ --single-transaction \\ ${db_name} \u0026gt; /tmp/backup/${db_name}_${TIMESTAMP}.sql \u0026amp; } # 获取数据库列表并并行备份 for db in $(mysql -h${DB_HOST} -u${DB_USER} -p${DB_PASS} -e \u0026#34;SHOW DATABASES;\u0026#34; | grep -v \u0026#34;Database\\|information_schema\\|performance_schema\\|mysql\\|sys\u0026#34;); do backup_database $db done wait # 等待所有后台任务完成 安全性增强 # 1. 备份加密 # # 使用 GPG 加密备份文件 encrypt_backup() { local backup_file=$1 gpg --symmetric --cipher-algo AES256 \\ --passphrase \u0026#34;${BACKUP_ENCRYPTION_KEY}\u0026#34; \\ --batch --yes \\ --output \u0026#34;${backup_file}.gpg\u0026#34; \\ \u0026#34;${backup_file}\u0026#34; rm \u0026#34;${backup_file}\u0026#34; # 删除未加密的文件 } 2. 访问控制 # # 创建专用的 ServiceAccount apiVersion: v1 kind: ServiceAccount metadata: name: mysql-backup-sa namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: mysql-backup-role rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;secrets\u0026#34;, \u0026#34;configmaps\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: mysql-backup-binding subjects: - kind: ServiceAccount name: mysql-backup-sa roleRef: kind: Role name: mysql-backup-role apiGroup: rbac.authorization.k8s.io 总结与最佳实践 # 方案总结 # 通过本文介绍的方案，我们成功实现了：\n✅ 自动化备份: 使用 Kubernetes CronJob 实现定时自动备份 ✅ 可靠存储: 将备份文件安全存储到 MinIO 对象存储中 ✅ 监控告警: 提供完整的监控和告警机制 ✅ 故障恢复: 详细的故障排除和恢复指南 ✅ 安全性: 包含加密和访问控制的安全措施\n最佳实践建议 # 1. 备份策略 # 定期测试: 定期验证备份文件的完整性和可恢复性 多重备份: 实施 3-2-1 备份策略（3个副本，2种介质，1个异地） 版本管理: 保留多个版本的备份文件，设置合理的清理策略 2. 性能优化 # 资源配置: 根据数据库大小合理配置 CPU 和内存资源 网络优化: 使用集群内部网络减少传输延迟 压缩策略: 启用压缩减少存储空间和传输时间 3. 安全考虑 # 权限最小化: 使用专用的数据库用户，仅授予必要权限 加密传输: 确保备份数据在传输过程中的安全性 访问控制: 限制对备份文件的访问权限 4. 运维管理 # 文档维护: 保持备份和恢复流程的文档更新 团队培训: 确保团队成员了解备份和恢复操作 应急预案: 制定详细的数据恢复应急预案 扩展方向 # 未来可以考虑以下扩展功能：\nPVC 备份: 实现基于 Persistent Volume 的文件级备份 跨云备份: 支持多云环境的备份策略 自动恢复: 开发自动化的数据恢复工具 备份验证: 自动验证备份文件的完整性和可用性 通过本方案，您可以为 Kubernetes 环境中的 MySQL 数据库建立一套完整、可靠的自动化备份系统，确保数据安全和业务连续性。\n","date":"2023年7月24日","externalUrl":null,"permalink":"/posts/k8s-cronjob-backup-mysql/","section":"博客文章","summary":"\u003cp\u003e随着 Kubernetes 在生产环境中的广泛应用，越来越多的有状态应用（如 MySQL 数据库）也开始部署在 K8s 集群中。数据安全是生产环境的重中之重，本文将详细介绍如何使用 Kubernetes CronJob 实现 MySQL 数据库的自动化备份，并将备份文件安全地存储到 MinIO 对象存储中。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e方案概述 \n    \u003cdiv id=\"方案概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%a1%88%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e备份架构 \n    \u003cdiv id=\"备份架构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a4%87%e4%bb%bd%e6%9e%b6%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\nflowchart LR\n    MySQL[MySQL Pod\u003cbr/\u003eDatabase] --\u003e|导出数据| CronJob[CronJob Pod\u003cbr/\u003emysqldump]\n    CronJob --\u003e|上传备份| MinIO[MinIO Server\u003cbr/\u003eBucket]\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\n\u003ch3 class=\"relative group\"\u003e方案优势 \n    \u003cdiv id=\"方案优势\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%a1%88%e4%bc%98%e5%8a%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e自动化\u003c/strong\u003e: 使用 CronJob 实现定时自动备份，无需人工干预\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e可靠性\u003c/strong\u003e: 备份文件存储在独立的对象存储中，提高数据安全性\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e可扩展\u003c/strong\u003e: 支持多数据库实例的备份，易于扩展\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e监控友好\u003c/strong\u003e: 可以通过 Kubernetes 原生监控查看备份任务状态\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e成本效益\u003c/strong\u003e: 使用开源的 MinIO 作为存储后端，降低成本\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e环境准备 \n    \u003cdiv id=\"环境准备\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e5%87%86%e5%a4%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e前置条件 \n    \u003cdiv id=\"前置条件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%89%8d%e7%bd%ae%e6%9d%a1%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e组件\u003c/th\u003e\n          \u003cth\u003e版本要求\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eKubernetes\u003c/td\u003e\n          \u003ctd\u003ev1.18+\u003c/td\u003e\n          \u003ctd\u003e支持 CronJob v1 API\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eMySQL\u003c/td\u003e\n          \u003ctd\u003e5.7+ / 8.0+\u003c/td\u003e\n          \u003ctd\u003e运行在 K8s 集群中\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eMinIO\u003c/td\u003e\n          \u003ctd\u003e任意版本\u003c/td\u003e\n          \u003ctd\u003e对象存储服务\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e网络连通性\u003c/td\u003e\n          \u003ctd\u003e-\u003c/td\u003e\n          \u003ctd\u003eCronJob Pod 能访问 MySQL 和 MinIO\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch3 class=\"relative group\"\u003e自定义镜像 \n    \u003cdiv id=\"自定义镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%87%aa%e5%ae%9a%e4%b9%89%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e本方案使用了专门构建的备份镜像，包含了 mysqldump 和 MinIO 客户端工具。\u003c/p\u003e","title":"使用 Kubernetes CronJob 自动备份 MySQL 数据到 MinIO","type":"posts"},{"content":"","date":"2023年7月24日","externalUrl":null,"permalink":"/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/","section":"Categories","summary":"","title":"数据库","type":"categories"},{"content":" LVM 概述 # 什么是 LVM # LVM（Logical Volume Manager）是 Linux 系统中的逻辑卷管理器，它在物理存储设备之上提供了一个抽象层，使得存储管理更加灵活和动态。\nLVM 核心概念 # PV（Physical Volume）：物理卷，实际的存储设备或分区 VG（Volume Group）：卷组，由一个或多个物理卷组成的存储池 LV（Logical Volume）：逻辑卷，从卷组中分配的逻辑存储单元 PE（Physical Extent）：物理扩展单元，PV 的最小分配单位 LE（Logical Extent）：逻辑扩展单元，LV 的最小分配单位 LVM 架构图 # flowchart TB subgraph FileSystem[文件系统] FS1[文件系统] FS2[文件系统] FS3[文件系统] end subgraph LV[LV 逻辑卷] LV1[逻辑卷] LV2[逻辑卷] end subgraph VG[VG 卷组] VG1[卷组] end subgraph PV[PV 物理卷] PV1[\"/dev/sda1\"] PV2[\"/dev/sdb1\"] PV3[\"/dev/sdc1\"] end FS1 --\u003e LV1 FS2 --\u003e LV1 FS3 --\u003e LV2 LV1 --\u003e VG1 LV2 --\u003e VG1 VG1 --\u003e PV1 VG1 --\u003e PV2 VG1 --\u003e PV3 环境准备 # 系统要求 # Linux 操作系统（CentOS、Ubuntu、RHEL 等） 具有 root 权限或 sudo 权限 已安装 LVM 工具包 安装 LVM 工具 # CentOS/RHEL # yum install -y lvm2 Ubuntu/Debian # apt-get update apt-get install -y lvm2 检查当前状态 # 在开始扩容前，先了解当前系统的存储状况：\n# 查看磁盘分区情况 lsblk # 查看物理卷 pvdisplay # 查看卷组 vgdisplay # 查看逻辑卷 lvdisplay # 查看文件系统使用情况 df -h 分区扩容操作步骤 # 步骤 1：添加新磁盘并创建分区 # 1.1 识别新磁盘 # # 查看所有磁盘设备 lsblk # 查看分区表 cat /proc/partitions # 如果是虚拟机环境，可能需要重新扫描 SCSI 总线 echo \u0026#34;- - -\u0026#34; \u0026gt; /sys/class/scsi_host/host0/scan echo \u0026#34;- - -\u0026#34; \u0026gt; /sys/class/scsi_host/host1/scan echo \u0026#34;- - -\u0026#34; \u0026gt; /sys/class/scsi_host/host2/scan 1.2 创建 LVM 分区 # 使用 cfdisk 创建分区（推荐，图形化界面）：\n# 对新磁盘进行分区 cfdisk /dev/vdb 操作步骤：\n选择 New 创建新分区 输入分区大小（默认使用全部空间） 选择 Type 并设置为 8e (Linux LVM) 选择 Write 保存更改 输入 yes 确认 选择 Quit 退出 或使用 fdisk 命令行方式：\nfdisk /dev/vdb # n (新建分区) # p (主分区) # 1 (分区号) # 回车 (起始扇区，使用默认) # 回车 (结束扇区，使用默认) # t (更改分区类型) # 8e (Linux LVM) # w (写入并退出) 1.3 刷新分区表 # # 通知内核重新读取分区表 sudo partprobe /dev/vdb # 或者使用 udevadm 触发设备事件 sudo udevadm trigger # 验证分区是否创建成功 cat /proc/partitions lsblk 步骤 2：创建物理卷（PV） # # 创建物理卷 sudo pvcreate /dev/vdb1 # 验证物理卷创建 sudo pvdisplay /dev/vdb1 # 查看所有物理卷 sudo pvscan 步骤 3：扩展卷组（VG） # 3.1 查看现有卷组 # # 查看卷组信息 sudo vgdisplay # 或者只查看卷组名称 sudo vgdisplay | grep \u0026#34;VG Name\u0026#34; # 查看卷组摘要信息 sudo vgs 3.2 将物理卷添加到卷组 # # 将新的物理卷添加到现有卷组（假设卷组名为 centos） sudo vgextend centos /dev/vdb1 # 验证卷组扩展 sudo vgdisplay centos # 查看可用空间 sudo vgs centos 步骤 4：扩展逻辑卷（LV） # 4.1 查看逻辑卷信息 # # 查看逻辑卷详细信息 sudo lvdisplay # 查看逻辑卷摘要 sudo lvs # 查看特定逻辑卷 sudo lvdisplay /dev/mapper/centos-root 4.2 扩展逻辑卷 # 方式一：使用所有可用空间\n# 将卷组中所有可用空间分配给逻辑卷 sudo lvextend -l +100%FREE /dev/mapper/centos-root 方式二：按指定大小扩展\n# 增加指定大小（例如 20GB） sudo lvextend -L+20G /dev/mapper/centos-root # 扩展到指定总大小（例如扩展到 100GB） sudo lvextend -L100G /dev/mapper/centos-root 方式三：按百分比扩展\n# 使用卷组 50% 的可用空间 sudo lvextend -l +50%FREE /dev/mapper/centos-root # 使用卷组 100% 的空间（包括已使用的） sudo lvextend -l +100%VG /dev/mapper/centos-root 4.3 验证逻辑卷扩展 # # 查看逻辑卷大小变化 sudo lvdisplay /dev/mapper/centos-root # 查看块设备信息 lsblk 步骤 5：扩展文件系统 # 逻辑卷扩展后，还需要扩展文件系统才能使用新增的空间。\n5.1 确定文件系统类型 # # 查看文件系统类型 df -T # 或者使用 blkid blkid /dev/mapper/centos-root # 或者使用 lsblk lsblk -f 5.2 扩展不同类型的文件系统 # XFS 文件系统（CentOS 7+ 默认）：\n# 扩展 XFS 文件系统 sudo xfs_growfs / # 验证扩展结果 df -h / EXT4 文件系统（Ubuntu 等系统常用）：\n# 扩展 EXT4 文件系统 sudo resize2fs /dev/mapper/centos-root # 验证扩展结果 df -h / EXT3 文件系统：\n# 扩展 EXT3 文件系统 sudo resize2fs /dev/mapper/centos-root # 验证扩展结果 df -h / 5.3 验证最终结果 # # 查看文件系统使用情况 df -h # 查看完整的存储架构 lsblk # 查看 LVM 状态摘要 sudo pvs \u0026amp;\u0026amp; sudo vgs \u0026amp;\u0026amp; sudo lvs 常见问题与解决方案 # 问题 1：虚拟机无法识别新添加的磁盘 # 解决方案：\n# 方法 1：重新扫描 SCSI 总线 echo \u0026#34;- - -\u0026#34; \u0026gt; /sys/class/scsi_host/host0/scan echo \u0026#34;- - -\u0026#34; \u0026gt; /sys/class/scsi_host/host1/scan echo \u0026#34;- - -\u0026#34; \u0026gt; /sys/class/scsi_host/host2/scan # 方法 2：重启虚拟机 sudo reboot # 方法 3：使用 rescan-scsi-bus 工具 sudo rescan-scsi-bus.sh 问题 2：分区表未更新 # 解决方案：\n# 强制重新读取分区表 sudo partprobe /dev/vdb # 如果上述命令失败，尝试 sudo hdparm -z /dev/vdb # 或者重启系统 sudo reboot 问题 3：文件系统扩展失败 # 解决方案：\n# 对于 XFS 文件系统，确保文件系统已挂载 mount | grep xfs # 对于 EXT 文件系统，可以在线或离线扩展 # 在线扩展（推荐） sudo resize2fs /dev/mapper/centos-root # 离线扩展（需要先卸载） sudo umount /dev/mapper/centos-root sudo e2fsck -f /dev/mapper/centos-root sudo resize2fs /dev/mapper/centos-root sudo mount /dev/mapper/centos-root / 问题 4：权限不足 # 解决方案：\n# 确保使用 sudo 或 root 权限 sudo su - # 检查用户是否在相关组中 groups $USER 最佳实践 # 扩容前的准备工作 # 数据备份：在进行任何存储操作前，务必备份重要数据 系统快照：如果是虚拟机，建议先创建系统快照 停止服务：对于生产环境，建议在维护窗口期间操作 文档记录：记录当前的存储配置，便于回滚 监控和验证 # # 创建监控脚本 cat \u0026gt; /tmp/storage_monitor.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== Storage Status Check ===\u0026#34; echo \u0026#34;Date: $(date)\u0026#34; echo echo \u0026#34;=== Disk Usage ===\u0026#34; df -h echo echo \u0026#34;=== Block Devices ===\u0026#34; lsblk echo echo \u0026#34;=== LVM Status ===\u0026#34; sudo pvs \u0026amp;\u0026amp; sudo vgs \u0026amp;\u0026amp; sudo lvs EOF chmod +x /tmp/storage_monitor.sh /tmp/storage_monitor.sh 自动化脚本示例 # #!/bin/bash # LVM 扩容自动化脚本 set -e DEVICE=\u0026#34;/dev/vdb\u0026#34; VG_NAME=\u0026#34;centos\u0026#34; LV_PATH=\u0026#34;/dev/mapper/centos-root\u0026#34; MOUNT_POINT=\u0026#34;/\u0026#34; echo \u0026#34;开始 LVM 扩容流程...\u0026#34; # 1. 创建分区 echo \u0026#34;正在创建分区...\u0026#34; ( echo n # 新建分区 echo p # 主分区 echo 1 # 分区号 echo # 默认起始扇区 echo # 默认结束扇区 echo t # 更改分区类型 echo 8e # Linux LVM echo w # 写入并退出 ) | fdisk $DEVICE # 2. 刷新分区表 partprobe $DEVICE sleep 2 # 3. 创建物理卷 echo \u0026#34;正在创建物理卷...\u0026#34; pvcreate ${DEVICE}1 # 4. 扩展卷组 echo \u0026#34;正在扩展卷组...\u0026#34; vgextend $VG_NAME ${DEVICE}1 # 5. 扩展逻辑卷 echo \u0026#34;正在扩展逻辑卷...\u0026#34; lvextend -l +100%FREE $LV_PATH # 6. 扩展文件系统 echo \u0026#34;正在扩展文件系统...\u0026#34; FS_TYPE=$(blkid -o value -s TYPE $LV_PATH) case $FS_TYPE in xfs) xfs_growfs $MOUNT_POINT ;; ext4|ext3) resize2fs $LV_PATH ;; *) echo \u0026#34;不支持的文件系统类型: $FS_TYPE\u0026#34; exit 1 ;; esac echo \u0026#34;LVM 扩容完成！\u0026#34; df -h $MOUNT_POINT 总结 # LVM 扩容的优势 # 动态扩展：无需停机即可扩展存储空间 灵活管理：可以跨多个物理设备创建逻辑卷 快照功能：支持创建逻辑卷快照用于备份 在线调整：支持在线扩展和缩减（需谨慎） 注意事项 # 备份重要：任何存储操作前都要备份数据 测试环境：先在测试环境验证操作流程 监控空间：定期监控存储空间使用情况 文档记录：详细记录每次操作的步骤和结果 相关命令速查 # 操作 命令 查看磁盘 lsblk, fdisk -l 创建分区 cfdisk, fdisk 创建 PV pvcreate 查看 PV pvdisplay, pvs 扩展 VG vgextend 查看 VG vgdisplay, vgs 扩展 LV lvextend 查看 LV lvdisplay, lvs 扩展文件系统 xfs_growfs, resize2fs 查看使用情况 df -h ","date":"2023年7月24日","externalUrl":null,"permalink":"/posts/use-lvm-to-expand-partitions/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eLVM 概述 \n    \u003cdiv id=\"lvm-概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#lvm-%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 LVM \n    \u003cdiv id=\"什么是-lvm\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-lvm\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eLVM（Logical Volume Manager）是 Linux 系统中的逻辑卷管理器，它在物理存储设备之上提供了一个抽象层，使得存储管理更加灵活和动态。\u003c/p\u003e","title":"Linux LVM 分区扩容完整指南","type":"posts"},{"content":"","date":"2023年7月24日","externalUrl":null,"permalink":"/tags/lvm/","section":"Tags","summary":"","title":"Lvm","type":"tags"},{"content":" 说明 # 此篇文档，用于记录在使用 ArgoCD CLi 的过程中，所使用到的一些常用命令，且供参考。\n使用记录 # ArgoCD cli 安装文档，使用前，配置登录\nargocd login xxx.argocd.xx 一键 关闭 ArgoCD 下某个 Project 下所有 APP 的 同步\nfor i in $(argocd app list -p cmb-custody-pet --grpc-web|awk \u0026#39;{print $1}\u0026#39; |grep -v \u0026#39;NAME\u0026#39;);do argocd app set \u0026#34;$i\u0026#34; --sync-option ApplyOutOfSyncOnly=false --grpc-web argocd app set \u0026#34;$i\u0026#34; --sync-policy none --grpc-web done 显示 ArgoCD 中当前实例下所有 Project\nargocd proj list|awk \u0026#39;{print $1}\u0026#39;|grep -v \u0026#39;NAME\u0026#39; ArgoCD 初始化项目空间\nPROJ=repo-charts-dev argocd proj create \u0026#34;${PROJ}\u0026#34; --description \u0026#39;repo dev 环境自动部署\u0026#39; \\ --dest https://kubernetes.default.svc,\u0026#34;${PROJ}\u0026#34; \\ --src \u0026#39;https://gitlab-ee.treesir.pub/ci-cd/repo-charts.git\u0026#39; argocd proj allow-cluster-resource \u0026#34;${PROJ}\u0026#34; \u0026#39;*\u0026#39; ‘*\u0026#39; kubectl create ns \u0026#34;${PROJ}\u0026#34; 一键 销毁· ArgoCD 下某个 Project 下所有 APP\nPROJ=repo-charts-dev for i in `argocd app list -p ${PROJ} --grpc-web|awk \u0026#39;{print $1}\u0026#39; |grep -v \u0026#39;NAME\u0026#39;`;do argocd app delete \u0026#34;$i\u0026#34; --grpc-web -y done ArgoCD 集群连接初始化，创建应用\nargocd login xxxx --grpc-web argocd cluster add $(kubectl config get-contexts -o name) --grpc-web argocd app create guestbook \\ --repo https://github.com/argoproj/argocd-example-apps.git \\ --path guestbook \\ --dest-namespace default \\ --dest-server ${K8S_API_SERVER_ADDRESS} \\ --directory-recurse \\ --grpc-web ToDo # 且供参考，后续补充。\n","date":"2023年7月24日","externalUrl":null,"permalink":"/posts/argocd-cli-usage-tips/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e说明 \n    \u003cdiv id=\"说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e此篇文档，用于记录在使用  \u003ccode\u003eArgoCD CLi\u003c/code\u003e 的过程中，所使用到的一些常用命令，\u003ccode\u003e且供参考\u003c/code\u003e。\u003c/p\u003e\u003c/blockquote\u003e\n\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e使用记录 \n    \u003cdiv id=\"使用记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eArgoCD cli \u003ca\n  href=\"https://argo-cd.readthedocs.io/en/stable/cli_installation/\"\n    target=\"_blank\"\n  \u003e安装文档\u003c/a\u003e，使用前，配置登录\u003c/p\u003e","title":"Argocd Cli Usage Tips","type":"posts"},{"content":"","date":"2023年7月24日","externalUrl":null,"permalink":"/categories/linux/","section":"Categories","summary":"","title":"Linux","type":"categories"},{"content":"","date":"2023年7月24日","externalUrl":null,"permalink":"/tags/sshd/","section":"Tags","summary":"","title":"Sshd","type":"tags"},{"content":" 说明 # 我们通常在远程连接目标服务器时，已避免经常性的输入密码，通常会通过 免密钥 的方式以解决每次连接多需要输入密码问题，但有的时候我们配置免密钥后，却未能生效，可以尝试使用下述方法进行解决。\nSSH 免密钥方式 # 生成 SSH 公私钥\nssh-keygen -b 2048 -t rsa -f ./id_rsa -q -N \u0026#34;\u0026#34; 公钥 COPY 至目标服务器，完成免密钥\nsshpass -p \u0026#39;{{ .ssh_password }}\u0026#39; \\ ssh-copy-id -p {{ .ssh_port }} -i id_rsa.pub \\ -o StrictHostKeyChecking=no \u0026#34;{{ .ssh_user }}@${host}\u0026#34; -f 解决方案 # Plan 1 # 目标 服务器中执行\nchmod go-w ~/ chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_key Plan 2 # 目标 服务器中执行\nchmod 700 ~/.ssh/ chmod 600 ~/.ssh/id_rsa chmod 644 ~/.ssh/id_rsa.pub chmod 644 ~/.ssh/authorized_keys Plan 3 # 连接失败，有种情况和所使用私钥的权限配置不对有关。连接机执行\nchmod 600 id_rsa 总结 # 按照上述三种方案，一般问题多能够得到解决，如果还是不行，请结合服务端日志，进一步分析定位。一般为 [sshd] 的配置错误导致。\n","date":"2023年7月24日","externalUrl":null,"permalink":"/posts/fix-ssh-connect/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e说明 \n    \u003cdiv id=\"说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e我们通常在远程连接目标服务器时，已避免经常性的输入密码，通常会通过 \u003ccode\u003e免密钥\u003c/code\u003e 的方式以解决每次连接多需要输入密码问题，但有的时候我们配置免密钥后，却未能生效，可以尝试使用下述方法进行解决。\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003eSSH 免密钥方式 \n    \u003cdiv id=\"ssh-免密钥方式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#ssh-%e5%85%8d%e5%af%86%e9%92%a5%e6%96%b9%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e生成 SSH 公私钥\u003c/p\u003e","title":"修复 SSH 免密无法连接","type":"posts"},{"content":"Git 是现代软件开发中不可或缺的分布式版本控制系统，它不仅能够追踪代码变更历史，还能支持复杂的团队协作和分支管理。本指南将从基础概念开始，逐步深入到高级技巧，帮助您全面掌握 Git 的使用。\nGit 基础概念 # 什么是 Git # Git 是由 Linux 之父 Linus Torvalds 创建的分布式版本控制系统，具有以下特点：\n分布式架构: 每个开发者都拥有完整的代码历史 高性能: 快速的分支创建和合并 数据完整性: 通过 SHA-1 哈希确保数据完整性 灵活的工作流: 支持多种开发模式 核心概念 # 工作区域 # Git 中有三个主要的工作区域：\nflowchart LR subgraph WorkingDir[工作目录 Working Dir] A[修改文件] end subgraph Staging[暂存区 Staging Area] B[暂存文件] end subgraph Repository[版本库 Repository] C[提交记录] end A --\u003e|\"git add\"| B B --\u003e|\"git commit\"| C C --\u003e|\"git push\"| D[远程仓库] 文件状态 # Git 中的文件有四种状态：\nUntracked: 未跟踪的新文件 Modified: 已修改但未暂存 Staged: 已暂存待提交 Committed: 已提交到版本库 环境配置 # 初始配置 # # 设置用户信息 git config --global user.name \u0026#34;Your Name\u0026#34; git config --global user.email \u0026#34;your.email@example.com\u0026#34; # 设置默认编辑器 git config --global core.editor vim # 设置默认分支名称 git config --global init.defaultBranch main # 启用颜色输出 git config --global color.ui auto # 设置换行符处理（Windows 用户） git config --global core.autocrlf true # 设置换行符处理（Linux/Mac 用户） git config --global core.autocrlf input 配置级别 # Git 配置有三个级别：\n# 系统级配置（影响所有用户） git config --system # 用户级配置（影响当前用户） git config --global # 仓库级配置（仅影响当前仓库） git config --local 查看配置 # # 查看所有配置 git config --list # 查看特定配置 git config user.name git config user.email # 查看配置来源 git config --list --show-origin 仓库初始化和管理 # 创建新仓库 # 本地初始化 # # 在当前目录初始化 Git 仓库 git init # 在指定目录初始化仓库 git init project-name # 初始化裸仓库（用于服务器） git init --bare 克隆远程仓库 # # 克隆仓库到当前目录 git clone https://github.com/user/repo.git # 克隆到指定目录 git clone https://github.com/user/repo.git my-project # 克隆指定分支 git clone -b develop https://github.com/user/repo.git # 浅克隆（只获取最近的提交历史） git clone --depth 1 https://github.com/user/repo.git # 克隆时指定远程名称 git clone -o upstream https://github.com/user/repo.git 远程仓库管理 # 添加和管理远程仓库 # # 查看远程仓库 git remote -v # 添加远程仓库 git remote add origin https://github.com/user/repo.git # 添加多个远程仓库 git remote add upstream https://github.com/original/repo.git git remote add fork https://github.com/yourfork/repo.git # 重命名远程仓库 git remote rename origin old-origin # 删除远程仓库 git remote remove origin # 查看远程仓库详细信息 git remote show origin 更改远程仓库地址 # # 查看当前远程仓库地址 git remote -v # 更新远程仓库地址 git remote set-url origin https://github.com/user/new-repo.git # 为推送和拉取设置不同的 URL git remote set-url origin https://github.com/user/repo.git git remote set-url --push origin https://github.com/user/repo.git # 添加额外的推送 URL（同时推送到多个仓库） git remote set-url --add --push origin https://github.com/user/repo.git git remote set-url --add --push origin https://gitlab.com/user/repo.git 连接本地仓库到远程仓库 # 场景一：本地项目推送到新的远程仓库 # # 1. 初始化本地仓库（如果还没有） git init # 2. 添加文件到暂存区 git add . # 3. 创建初始提交 git commit -m \u0026#34;Initial commit\u0026#34; # 4. 添加远程仓库 git remote add origin https://github.com/user/repo.git # 5. 推送到远程仓库 git push -u origin main 场景二：连接到已有内容的远程仓库 # # 1. 添加远程仓库 git remote add origin https://github.com/user/repo.git # 2. 拉取远程代码（允许不相关历史） git pull origin main --allow-unrelated-histories # 3. 解决可能的冲突 git add . git commit -m \u0026#34;Merge remote repository\u0026#34; # 4. 设置跟踪分支 git branch --set-upstream-to=origin/main main # 5. 后续正常操作 git push 场景三：Fork 工作流设置 # # 1. 克隆你的 fork git clone https://github.com/yourusername/repo.git cd repo # 2. 添加上游仓库 git remote add upstream https://github.com/original/repo.git # 3. 验证远程仓库设置 git remote -v # origin https://github.com/yourusername/repo.git (fetch) # origin https://github.com/yourusername/repo.git (push) # upstream https://github.com/original/repo.git (fetch) # upstream https://github.com/original/repo.git (push) # 4. 同步上游更新 git fetch upstream git checkout main git merge upstream/main git push origin main 仓库状态检查 # # 查看仓库状态 git status # 简洁状态显示 git status -s # 查看忽略的文件 git status --ignored # 查看分支状态 git status -b # 查看工作目录和暂存区的差异 git diff # 查看暂存区和最后一次提交的差异 git diff --staged # 查看工作目录和最后一次提交的差异 git diff HEAD 分支管理 # 分支是 Git 最强大的功能之一，它允许您并行开发不同的功能，而不会相互干扰。\n分支基础操作 # 创建和切换分支 # # 查看所有分支 git branch # 查看所有分支（包括远程分支） git branch -a # 查看远程分支 git branch -r # 创建新分支 git branch feature-login # 创建并切换到新分支 git checkout -b feature-login # 使用新语法创建并切换分支 git switch -c feature-login # 从指定提交创建分支 git checkout -b hotfix-123 abc1234 # 从远程分支创建本地分支 git checkout -b local-branch origin/remote-branch 分支切换 # # 切换分支 git checkout main git switch main # 新语法 # 切换到上一个分支 git checkout - git switch - # 强制切换（丢弃当前更改） git checkout -f main 分支重命名和删除 # # 重命名当前分支 git branch -m new-branch-name # 重命名指定分支 git branch -m old-name new-name # 删除本地分支 git branch -d feature-login # 强制删除本地分支（即使未合并） git branch -D feature-login # 删除远程分支 git push origin --delete feature-login # 清理已删除的远程分支引用 git remote prune origin 分支合并 # 快进合并（Fast-forward） # # 切换到目标分支 git checkout main # 执行快进合并 git merge feature-login # 禁用快进合并（创建合并提交） git merge --no-ff feature-login 三方合并（Three-way merge） # # 当分支有分叉时，Git 会自动进行三方合并 git checkout main git merge feature-login # 如果有冲突，需要手动解决 # 1. 编辑冲突文件 # 2. 标记为已解决 git add conflicted-file.txt # 3. 完成合并 git commit 压缩合并（Squash merge） # # 将分支的所有提交压缩为一个提交 git checkout main git merge --squash feature-login git commit -m \u0026#34;Add login feature\u0026#34; 变基操作（Rebase） # 变基是另一种整合分支的方法，它可以创建更线性的提交历史。\n基础变基 # # 将当前分支变基到 main git checkout feature-login git rebase main # 或者一步完成 git rebase main feature-login 交互式变基 # # 交互式变基最近 3 个提交 git rebase -i HEAD~3 # 交互式变基到指定提交 git rebase -i abc1234 在交互式变基中，您可以：\npick: 保留提交 reword: 修改提交信息 edit: 修改提交内容 squash: 合并到前一个提交 drop: 删除提交 解决变基冲突 # # 当变基遇到冲突时 # 1. 解决冲突文件 # 2. 添加解决后的文件 git add . # 3. 继续变基 git rebase --continue # 跳过当前提交 git rebase --skip # 中止变基操作 git rebase --abort 分支工作流 # Git Flow 工作流 # # 主要分支 # - main: 生产分支 # - develop: 开发分支 # 创建开发分支 git checkout -b develop main # 创建功能分支 git checkout -b feature/user-auth develop # 完成功能开发后合并到 develop git checkout develop git merge --no-ff feature/user-auth git branch -d feature/user-auth # 创建发布分支 git checkout -b release/1.0.0 develop # 发布完成后合并到 main 和 develop git checkout main git merge --no-ff release/1.0.0 git tag -a v1.0.0 -m \u0026#34;Release version 1.0.0\u0026#34; git checkout develop git merge --no-ff release/1.0.0 git branch -d release/1.0.0 GitHub Flow 工作流 # # 1. 从 main 创建功能分支 git checkout main git pull origin main git checkout -b feature/new-feature # 2. 开发并提交 git add . git commit -m \u0026#34;Add new feature\u0026#34; # 3. 推送分支 git push origin feature/new-feature # 4. 创建 Pull Request # 5. 代码审查和讨论 # 6. 合并到 main # 7. 删除功能分支 git checkout main git pull origin main git branch -d feature/new-feature 认证和凭据管理 # SSH 密钥认证（推荐） # SSH 密钥认证是最安全和便捷的认证方式：\n生成 SSH 密钥 # # 生成新的 SSH 密钥对 ssh-keygen -t ed25519 -C \u0026#34;your.email@example.com\u0026#34; # 如果系统不支持 ed25519，使用 RSA ssh-keygen -t rsa -b 4096 -C \u0026#34;your.email@example.com\u0026#34; # 指定密钥文件名 ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_github -C \u0026#34;your.email@example.com\u0026#34; 添加 SSH 密钥到 ssh-agent # # 启动 ssh-agent eval \u0026#34;$(ssh-agent -s)\u0026#34; # 添加私钥到 ssh-agent ssh-add ~/.ssh/id_ed25519 # 查看已添加的密钥 ssh-add -l 配置 SSH 客户端 # # 创建或编辑 SSH 配置文件 vim ~/.ssh/config # 添加以下内容 Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github IdentitiesOnly yes Host gitlab.com HostName gitlab.com User git IdentityFile ~/.ssh/id_ed25519_gitlab IdentitiesOnly yes 测试 SSH 连接 # # 测试 GitHub 连接 ssh -T git@github.com # 测试 GitLab 连接 ssh -T git@gitlab.com HTTPS 认证管理 # 凭据助手配置 # # Windows 系统 git config --global credential.helper manager-core # macOS 系统 git config --global credential.helper osxkeychain # Linux 系统 git config --global credential.helper store # 临时缓存凭据（15分钟） git config --global credential.helper \u0026#39;cache --timeout=900\u0026#39; 清理存储的凭据 # # Windows 系统：清理凭据管理器 git config --global --unset credential.helper cmdkey /delete:git:https://github.com # macOS 系统：清理钥匙串 git config --global --unset credential.helper security delete-internet-password -s github.com # Linux 系统：删除凭据文件 rm ~/.git-credentials # 清理缓存的凭据 git credential-cache exit 个人访问令牌（PAT） # 对于 GitHub、GitLab 等平台，推荐使用个人访问令牌：\n# 使用令牌作为密码 # 用户名：你的用户名 # 密码：生成的个人访问令牌 # 在 URL 中包含令牌（不推荐，会暴露在历史记录中） git clone https://username:token@github.com/user/repo.git # 推荐方式：配置凭据助手，首次输入后会自动保存 git clone https://github.com/user/repo.git # 输入用户名和令牌 多账户管理 # 使用不同的 SSH 密钥 # # 为不同账户生成不同的密钥 ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_work -C \u0026#34;work@company.com\u0026#34; ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_personal -C \u0026#34;personal@gmail.com\u0026#34; # 配置 SSH config cat \u0026gt;\u0026gt; ~/.ssh/config \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # 工作账户 Host github-work HostName github.com User git IdentityFile ~/.ssh/id_ed25519_work # 个人账户 Host github-personal HostName github.com User git IdentityFile ~/.ssh/id_ed25519_personal EOF # 克隆时使用不同的 Host git clone git@github-work:company/repo.git git clone git@github-personal:username/repo.git 为不同项目配置不同用户信息 # # 进入工作项目目录 cd work-project git config user.name \u0026#34;Work Name\u0026#34; git config user.email \u0026#34;work@company.com\u0026#34; # 进入个人项目目录 cd personal-project git config user.name \u0026#34;Personal Name\u0026#34; git config user.email \u0026#34;personal@gmail.com\u0026#34; # 或者使用条件配置 cat \u0026gt;\u0026gt; ~/.gitconfig \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [includeIf \u0026#34;gitdir:~/work/\u0026#34;] path = ~/.gitconfig-work [includeIf \u0026#34;gitdir:~/personal/\u0026#34;] path = ~/.gitconfig-personal EOF # 创建工作配置文件 cat \u0026gt; ~/.gitconfig-work \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [user] name = Work Name email = work@company.com EOF # 创建个人配置文件 cat \u0026gt; ~/.gitconfig-personal \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [user] name = Personal Name email = personal@gmail.com EOF 提交管理 # 基本提交操作 # 添加文件到暂存区 # # 添加指定文件 git add file1.txt file2.txt # 添加所有修改的文件 git add . # 添加所有文件（包括删除的文件） git add -A # 交互式添加 git add -i # 分块添加（部分添加文件的某些更改） git add -p 创建提交 # # 基本提交 git commit -m \u0026#34;Add new feature\u0026#34; # 多行提交信息 git commit -m \u0026#34;Add user authentication - Implement login functionality - Add password validation - Create user session management\u0026#34; # 修改最后一次提交 git commit --amend -m \u0026#34;Updated commit message\u0026#34; # 添加文件到最后一次提交 git add forgotten-file.txt git commit --amend --no-edit # 空提交（用于触发 CI/CD） git commit --allow-empty -m \u0026#34;Trigger CI build\u0026#34; 提交信息规范 # 推荐使用约定式提交（Conventional Commits）格式：\n# 格式：\u0026lt;type\u0026gt;(\u0026lt;scope\u0026gt;): \u0026lt;description\u0026gt; git commit -m \u0026#34;feat(auth): add user login functionality\u0026#34; git commit -m \u0026#34;fix(api): resolve null pointer exception\u0026#34; git commit -m \u0026#34;docs(readme): update installation instructions\u0026#34; git commit -m \u0026#34;style(css): fix button alignment\u0026#34; git commit -m \u0026#34;refactor(utils): extract common functions\u0026#34; git commit -m \u0026#34;test(auth): add unit tests for login\u0026#34; git commit -m \u0026#34;chore(deps): update dependencies\u0026#34; 查看提交历史 # 基本日志查看 # # 查看提交历史 git log # 简洁的一行显示 git log --oneline # 显示图形化分支结构 git log --graph --oneline --all # 显示统计信息 git log --stat # 显示详细的文件变更 git log -p # 限制显示数量 git log -n 5 # 按时间范围查看 git log --since=\u0026#34;2023-01-01\u0026#34; --until=\u0026#34;2023-12-31\u0026#34; git log --since=\u0026#34;2 weeks ago\u0026#34; 高级日志查看 # # 按作者过滤 git log --author=\u0026#34;John Doe\u0026#34; # 按提交信息过滤 git log --grep=\u0026#34;fix\u0026#34; # 按文件过滤 git log -- path/to/file.txt # 查看某个文件的修改历史 git log -p -- file.txt # 查看删除的文件 git log --diff-filter=D --summary # 自定义格式 git log --pretty=format:\u0026#34;%h - %an, %ar : %s\u0026#34; # 查看分支间的差异 git log main..feature-branch git log feature-branch --not main 修改提交历史 # 修改最后一次提交 # # 修改提交信息 git commit --amend -m \u0026#34;New commit message\u0026#34; # 添加文件到最后一次提交 git add forgotten-file.txt git commit --amend --no-edit # 修改作者信息 git commit --amend --author=\u0026#34;New Author \u0026lt;new@email.com\u0026gt;\u0026#34; 交互式变基修改历史 # # 修改最近 3 次提交 git rebase -i HEAD~3 # 修改到指定提交 git rebase -i abc1234 在交互式变基编辑器中：\npick 或 p: 保留提交 reword 或 r: 修改提交信息 edit 或 e: 修改提交内容 squash 或 s: 合并到前一个提交 fixup 或 f: 合并到前一个提交但丢弃提交信息 drop 或 d: 删除提交 拆分提交 # # 1. 开始交互式变基 git rebase -i HEAD~3 # 2. 将要拆分的提交标记为 \u0026#39;edit\u0026#39; # 3. 重置到前一个提交 git reset HEAD^ # 4. 分别添加和提交文件 git add file1.txt git commit -m \u0026#34;First part of the change\u0026#34; git add file2.txt git commit -m \u0026#34;Second part of the change\u0026#34; # 5. 继续变基 git rebase --continue 合并提交 # # 使用 squash 合并最近 3 个提交 git rebase -i HEAD~3 # 将后面的提交改为 \u0026#39;squash\u0026#39; 或 \u0026#39;s\u0026#39; # 或者使用 reset 和重新提交 git reset --soft HEAD~3 git commit -m \u0026#34;Combined commit message\u0026#34; 撤销和恢复 # 撤销工作目录的更改 # # 撤销单个文件的更改 git checkout -- file.txt git restore file.txt # 新语法 # 撤销所有文件的更改 git checkout -- . git restore . # 撤销到指定提交的状态 git checkout abc1234 -- file.txt 撤销暂存区的更改 # # 取消暂存单个文件 git reset HEAD file.txt git restore --staged file.txt # 新语法 # 取消暂存所有文件 git reset HEAD git restore --staged . 撤销提交 # # 软重置：保留工作目录和暂存区的更改 git reset --soft HEAD~1 # 混合重置：保留工作目录的更改，清空暂存区 git reset --mixed HEAD~1 git reset HEAD~1 # 默认是 mixed # 硬重置：丢弃所有更改 git reset --hard HEAD~1 # 重置到指定提交 git reset --hard abc1234 使用 revert 安全撤销 # # 创建一个新提交来撤销指定提交 git revert abc1234 # 撤销合并提交 git revert -m 1 merge-commit-hash # 撤销多个提交 git revert HEAD~3..HEAD 历史记录管理 # 查找和恢复 # 查找丢失的提交 # # 查看引用日志（包括已删除的提交） git reflog # 查看所有分支的引用日志 git reflog --all # 恢复丢失的提交 git checkout abc1234 git branch recovered-branch abc1234 # 查找包含特定内容的提交 git log -S \u0026#34;search_string\u0026#34; --source --all # 查找特定文件的历史 git log --follow -- path/to/file.txt 使用 git fsck 检查仓库 # # 检查仓库完整性 git fsck # 查找悬空的对象 git fsck --unreachable # 查找悬空的提交 git fsck --lost-found 文件操作 # 文件状态管理 # 忽略文件 # # 创建 .gitignore 文件 cat \u0026gt; .gitignore \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # 编译输出 *.o *.so *.exe build/ dist/ # 依赖目录 node_modules/ vendor/ # 日志文件 *.log logs/ # 配置文件 .env config.local.json # IDE 文件 .vscode/ .idea/ *.swp *.swo # 操作系统文件 .DS_Store Thumbs.db EOF # 忽略已跟踪的文件 git rm --cached file.txt echo \u0026#34;file.txt\u0026#34; \u0026gt;\u0026gt; .gitignore git add .gitignore git commit -m \u0026#34;Ignore file.txt\u0026#34; 文件重命名和移动 # # 重命名文件 git mv old-name.txt new-name.txt # 移动文件 git mv file.txt directory/ # 移动并重命名 git mv old-file.txt new-directory/new-file.txt # 如果已经在文件系统中移动了文件 git add new-location/file.txt git rm old-location/file.txt 删除文件 # # 删除文件并从版本控制中移除 git rm file.txt # 只从版本控制中移除，保留本地文件 git rm --cached file.txt # 强制删除（即使文件已修改） git rm -f file.txt # 删除目录 git rm -r directory/ 文件差异和比较 # 查看差异 # # 工作目录 vs 暂存区 git diff # 暂存区 vs 最后一次提交 git diff --staged git diff --cached # 工作目录 vs 最后一次提交 git diff HEAD # 比较两个提交 git diff commit1 commit2 # 比较两个分支 git diff main feature-branch # 只显示文件名 git diff --name-only # 显示统计信息 git diff --stat 高级差异查看 # # 按单词显示差异 git diff --word-diff # 忽略空白字符 git diff --ignore-space-change git diff --ignore-all-space # 比较特定文件 git diff HEAD -- file.txt # 使用外部工具查看差异 git difftool git difftool --tool=vimdiff 暂存（Stash）操作 # 暂存功能允许您临时保存工作进度，切换分支处理其他任务。\n基本暂存操作 # # 暂存当前更改 git stash # 暂存时添加描述 git stash push -m \u0026#34;Work in progress on feature X\u0026#34; # 暂存包括未跟踪的文件 git stash -u # 暂存包括忽略的文件 git stash -a # 查看暂存列表 git stash list # 查看暂存内容 git stash show git stash show -p # 显示详细差异 恢复和管理暂存 # # 恢复最新的暂存 git stash pop # 恢复指定的暂存 git stash pop stash@{1} # 应用暂存但不删除 git stash apply git stash apply stash@{1} # 删除暂存 git stash drop git stash drop stash@{1} # 清空所有暂存 git stash clear # 从暂存创建分支 git stash branch new-branch-name stash@{1} 部分暂存 # # 交互式暂存 git stash -p # 只暂存已跟踪的文件 git stash --keep-index # 暂存特定文件 git stash push path/to/file.txt 高级技巧 # 标签管理 # 创建标签 # # 创建轻量标签 git tag v1.0.0 # 创建附注标签 git tag -a v1.0.0 -m \u0026#34;Release version 1.0.0\u0026#34; # 为指定提交创建标签 git tag -a v1.0.0 abc1234 -m \u0026#34;Release version 1.0.0\u0026#34; # 查看所有标签 git tag # 查看匹配模式的标签 git tag -l \u0026#34;v1.*\u0026#34; 管理标签 # # 查看标签信息 git show v1.0.0 # 推送标签到远程 git push origin v1.0.0 # 推送所有标签 git push origin --tags # 删除本地标签 git tag -d v1.0.0 # 删除远程标签 git push origin --delete v1.0.0 子模块（Submodules） # 添加子模块 # # 添加子模块 git submodule add https://github.com/user/repo.git path/to/submodule # 初始化子模块 git submodule init # 更新子模块 git submodule update # 一步完成初始化和更新 git submodule update --init --recursive 管理子模块 # # 查看子模块状态 git submodule status # 更新子模块到最新提交 git submodule update --remote # 进入子模块目录进行操作 cd path/to/submodule git checkout main git pull # 返回主项目并提交子模块更新 cd ../.. git add path/to/submodule git commit -m \u0026#34;Update submodule\u0026#34; 工作树（Worktree） # 工作树允许您同时检出多个分支到不同的目录。\n# 创建新的工作树 git worktree add ../feature-branch feature-branch # 创建新分支的工作树 git worktree add -b new-feature ../new-feature # 查看所有工作树 git worktree list # 删除工作树 git worktree remove ../feature-branch # 清理工作树引用 git worktree prune 网络和安全配置 # 网络配置 # 代理设置 # # 设置 HTTP 代理 git config --global http.proxy http://proxy.company.com:8080 git config --global https.proxy https://proxy.company.com:8080 # 设置 SOCKS 代理 git config --global http.proxy socks5://127.0.0.1:1080 # 为特定域名设置代理 git config --global http.https://github.com.proxy http://proxy.company.com:8080 # 取消代理设置 git config --global --unset http.proxy git config --global --unset https.proxy SSL 配置 # # 跳过 SSL 证书验证（不推荐） git config --global http.sslVerify false # 为特定域名跳过 SSL 验证 git config --global http.https://internal-git.company.com.sslVerify false # 设置自定义 CA 证书 git config --global http.sslCAInfo /path/to/ca-bundle.crt # 设置客户端证书 git config --global http.sslCert /path/to/client.crt git config --global http.sslKey /path/to/client.key 超时设置 # # 设置连接超时 git config --global http.timeout 30 # 设置低速传输超时 git config --global http.lowSpeedLimit 1000 git config --global http.lowSpeedTime 300 性能优化 # 大文件处理 # # 启用 Git LFS（Large File Storage） git lfs install # 跟踪大文件类型 git lfs track \u0026#34;*.psd\u0026#34; git lfs track \u0026#34;*.zip\u0026#34; git lfs track \u0026#34;*.mp4\u0026#34; # 查看 LFS 跟踪的文件 git lfs ls-files # 查看 LFS 状态 git lfs status 仓库优化 # # 垃圾回收 git gc # 积极的垃圾回收 git gc --aggressive # 清理不可达的对象 git prune # 查看仓库大小 git count-objects -vH # 查找大文件 git rev-list --objects --all | git cat-file --batch-check=\u0026#39;%(objecttype) %(objectname) %(objectsize) %(rest)\u0026#39; | sed -n \u0026#39;s/^blob //p\u0026#39; | sort --numeric-sort --key=2 | tail -10 故障排除 # 常见问题解决 # 合并冲突 # # 查看冲突文件 git status # 手动解决冲突后 git add conflicted-file.txt git commit # 使用合并工具 git mergetool # 中止合并 git merge --abort # 查看冲突的详细信息 git diff 分离头指针（Detached HEAD） # # 当前状态：分离头指针 git status # 创建新分支保存更改 git checkout -b new-branch-name # 或者切换回原分支（丢弃更改） git checkout main 误删分支恢复 # # 查看引用日志 git reflog # 恢复删除的分支 git checkout -b recovered-branch abc1234 # 或者直接重新创建分支 git branch recovered-branch abc1234 推送被拒绝 # # 原因：远程有新的提交 # 解决方案1：拉取并合并 git pull origin main git push origin main # 解决方案2：拉取并变基 git pull --rebase origin main git push origin main # 解决方案3：强制推送（危险） git push --force-with-lease origin main 调试和诊断 # 查看详细信息 # # 启用详细输出 git -v command # 查看 Git 版本 git --version # 查看 Git 配置路径 git config --list --show-origin # 查看环境变量 git var -l 性能分析 # # 分析命令执行时间 time git command # 启用性能跟踪 GIT_TRACE=1 git command GIT_TRACE_PERFORMANCE=1 git command GIT_TRACE_SETUP=1 git command 团队协作最佳实践 # 提交规范 # 提交信息格式 # # 推荐格式 \u0026lt;type\u0026gt;(\u0026lt;scope\u0026gt;): \u0026lt;subject\u0026gt; \u0026lt;body\u0026gt; \u0026lt;footer\u0026gt; # 示例 feat(auth): add OAuth2 integration Implement OAuth2 authentication flow with Google and GitHub providers. This allows users to sign in using their existing accounts. Closes #123 提交类型 # feat: 新功能 fix: 修复 bug docs: 文档更新 style: 代码格式化 refactor: 重构 test: 测试相关 chore: 构建过程或辅助工具的变动 分支策略 # Git Flow # # 主分支 main # 生产分支 develop # 开发分支 # 辅助分支 feature/* # 功能分支 release/* # 发布分支 hotfix/* # 热修复分支 GitHub Flow # # 简化流程 main # 主分支 feature/* # 功能分支（直接从 main 创建，完成后合并回 main） 代码审查 # Pull Request 工作流 # # 1. 创建功能分支 git checkout -b feature/new-feature # 2. 开发并提交 git add . git commit -m \u0026#34;feat: implement new feature\u0026#34; # 3. 推送分支 git push origin feature/new-feature # 4. 创建 Pull Request # 5. 代码审查 # 6. 合并到主分支 # 7. 删除功能分支 git branch -d feature/new-feature git push origin --delete feature/new-feature 自动化和工具 # Git Hooks # 常用钩子 # # 提交前检查 cat \u0026gt; .git/hooks/pre-commit \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/sh # 运行代码检查 npm run lint npm run test EOF # 提交信息检查 cat \u0026gt; .git/hooks/commit-msg \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/sh # 检查提交信息格式 commit_regex=\u0026#39;^(feat|fix|docs|style|refactor|test|chore)(\\(.+\\))?: .{1,50}\u0026#39; if ! grep -qE \u0026#34;$commit_regex\u0026#34; \u0026#34;$1\u0026#34;; then echo \u0026#34;Invalid commit message format\u0026#34; exit 1 fi EOF # 设置执行权限 chmod +x .git/hooks/pre-commit chmod +x .git/hooks/commit-msg 别名配置 # # 常用别名 git config --global alias.st status git config --global alias.co checkout git config --global alias.br branch git config --global alias.ci commit git config --global alias.unstage \u0026#39;reset HEAD --\u0026#39; git config --global alias.last \u0026#39;log -1 HEAD\u0026#39; git config --global alias.visual \u0026#39;!gitk\u0026#39; # 高级别名 git config --global alias.lg \u0026#34;log --color --graph --pretty=format:\u0026#39;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)\u0026lt;%an\u0026gt;%Creset\u0026#39; --abbrev-commit\u0026#34; git config --global alias.unstash \u0026#39;stash pop\u0026#39; git config --global alias.uncommit \u0026#39;reset --soft HEAD~1\u0026#39; 总结与最佳实践 # 核心原则 # 频繁提交: 保持小而频繁的提交，便于追踪和回滚 清晰的提交信息: 使用规范的提交信息格式 分支管理: 合理使用分支，保持主分支的稳定性 代码审查: 通过 Pull Request 进行代码审查 备份重要数据: 定期推送到远程仓库 安全建议 # 使用 SSH 密钥: 避免在命令行中暴露密码 谨慎使用强制推送: 使用 --force-with-lease 替代 --force 保护敏感信息: 使用 .gitignore 忽略敏感文件 定期更新: 保持 Git 版本的更新 性能优化 # 合理使用 .gitignore: 避免跟踪不必要的文件 定期清理: 使用 git gc 清理仓库 浅克隆: 对于大仓库使用 --depth 参数 LFS 支持: 对大文件使用 Git LFS 学习资源 # 官方文档: https://git-scm.com/doc Pro Git 书籍: https://git-scm.com/book 交互式学习: https://learngitbranching.js.org/ Git 速查表: https://training.github.com/downloads/github-git-cheat-sheet/ 现代化 Git 工作流 # GitHub CLI 集成 # GitHub CLI 是现代开发者的强大工具：\n# 安装 GitHub CLI # macOS: brew install gh # Windows: winget install GitHub.cli # Linux: 参考官方文档 # 认证 gh auth login # 克隆仓库 gh repo clone owner/repo # 创建仓库 gh repo create my-new-repo --public gh repo create my-new-repo --private --clone # Pull Request 操作 gh pr create --title \u0026#34;Add new feature\u0026#34; --body \u0026#34;Description\u0026#34; gh pr list gh pr view 123 gh pr checkout 123 gh pr merge 123 # Issue 操作 gh issue create --title \u0026#34;Bug report\u0026#34; --body \u0026#34;Description\u0026#34; gh issue list gh issue view 123 gh issue close 123 # 发布管理 gh release create v1.0.0 --title \u0026#34;Version 1.0.0\u0026#34; --notes \u0026#34;Release notes\u0026#34; gh release list gh release download v1.0.0 现代化配置 # 推荐的全局配置 # # 现代化的 Git 配置 cat \u0026gt; ~/.gitconfig \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [user] name = Your Name email = your.email@example.com [init] defaultBranch = main [core] editor = code --wait # 使用 VS Code 作为编辑器 autocrlf = input # Linux/Mac # autocrlf = true # Windows ignorecase = false precomposeunicode = true [pull] rebase = true # 默认使用 rebase 而不是 merge [push] default = simple autoSetupRemote = true # 自动设置远程分支 [merge] tool = vscode conflictstyle = diff3 [mergetool \u0026#34;vscode\u0026#34;] cmd = code --wait $MERGED [diff] tool = vscode [difftool \u0026#34;vscode\u0026#34;] cmd = code --wait --diff $LOCAL $REMOTE [rebase] autoStash = true # 自动暂存未提交的更改 [fetch] prune = true # 自动清理已删除的远程分支 [status] showUntrackedFiles = all [color] ui = auto [alias] # 常用简写 st = status co = checkout br = branch ci = commit sw = switch # 高级别名 lg = log --color --graph --pretty=format:\u0026#39;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)\u0026lt;%an\u0026gt;%Creset\u0026#39; --abbrev-commit unstage = reset HEAD -- uncommit = reset --soft HEAD~1 amend = commit --amend --no-edit # 分支操作 recent = branch --sort=-committerdate cleanup = \u0026#34;!git branch --merged | grep -v \u0026#39;\\\\*\\\\|main\\\\|develop\u0026#39; | xargs -n 1 git branch -d\u0026#34; # 查看操作 who = shortlog -sn what = show --pretty=\u0026#34;format:\u0026#34; --name-only # 同步操作 sync = \u0026#34;!git fetch origin \u0026amp;\u0026amp; git rebase origin/main\u0026#34; update = \u0026#34;!git pull origin main \u0026amp;\u0026amp; git push origin main\u0026#34; EOF 高级工作流场景 # 多人协作冲突解决 # # 场景：多人同时修改同一文件 # 1. 获取最新代码 git fetch origin # 2. 查看冲突情况 git log --oneline --graph origin/main..HEAD git log --oneline --graph HEAD..origin/main # 3. 使用 rebase 解决冲突 git rebase origin/main # 4. 如果有冲突，逐个解决 # 编辑冲突文件，然后： git add conflicted-file.txt git rebase --continue # 5. 推送解决后的代码 git push --force-with-lease origin feature-branch 大型项目的分支管理 # # 创建功能分支 git checkout -b feature/JIRA-123-user-authentication # 定期同步主分支 git fetch origin git rebase origin/main # 提交前的最后检查 git log --oneline origin/main..HEAD # 查看即将推送的提交 git diff origin/main...HEAD # 查看所有更改 # 推送并创建 PR git push origin feature/JIRA-123-user-authentication gh pr create --title \u0026#34;JIRA-123: Implement user authentication\u0026#34; \\ --body \u0026#34;Implements OAuth2 authentication with Google and GitHub providers\u0026#34; 热修复工作流 # # 从生产分支创建热修复分支 git checkout main git pull origin main git checkout -b hotfix/critical-security-fix # 进行修复 git add . git commit -m \u0026#34;fix: resolve critical security vulnerability\u0026#34; # 推送热修复 git push origin hotfix/critical-security-fix # 创建紧急 PR gh pr create --title \u0026#34;URGENT: Critical security fix\u0026#34; \\ --body \u0026#34;Fixes critical security vulnerability\u0026#34; \\ --label \u0026#34;urgent,security\u0026#34; # 合并后，同步到开发分支 git checkout develop git pull origin develop git merge main git push origin develop 代码质量保证 # Pre-commit Hooks 配置 # # 安装 pre-commit pip install pre-commit # 创建 .pre-commit-config.yaml cat \u0026gt; .pre-commit-config.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - id: check-merge-conflict - id: check-json - id: pretty-format-json args: [\u0026#39;--autofix\u0026#39;] - id: check-toml - id: check-xml - id: mixed-line-ending - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black language_version: python3 - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-eslint rev: v8.44.0 hooks: - id: eslint files: \\.(js|ts|jsx|tsx)$ types: [file] - repo: https://github.com/pre-commit/mirrors-prettier rev: v3.0.0 hooks: - id: prettier files: \\.(js|ts|jsx|tsx|json|css|md)$ EOF # 安装 hooks pre-commit install # 手动运行所有文件的检查 pre-commit run --all-files 提交信息规范化 # # 安装 commitizen npm install -g commitizen cz-conventional-changelog # 配置 commitizen echo \u0026#39;{ \u0026#34;path\u0026#34;: \u0026#34;cz-conventional-changelog\u0026#34; }\u0026#39; \u0026gt; ~/.czrc # 使用 commitizen 提交 git add . git cz # 或者 npx cz # 安装 commitlint npm install -g @commitlint/cli @commitlint/config-conventional # 创建 commitlint 配置 echo \u0026#34;module.exports = {extends: [\u0026#39;@commitlint/config-conventional\u0026#39;]}\u0026#34; \u0026gt; commitlint.config.js # 添加 commit-msg hook cat \u0026gt; .git/hooks/commit-msg \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/sh npx commitlint --edit $1 EOF chmod +x .git/hooks/commit-msg 性能优化和维护 # 仓库健康检查 # #!/bin/bash # Git 仓库健康检查脚本 echo \u0026#34;=== Git Repository Health Check ===\u0026#34; # 检查仓库大小 echo \u0026#34;Repository size:\u0026#34; du -sh .git # 检查对象数量 echo -e \u0026#34;\\nObject count:\u0026#34; git count-objects -v # 检查大文件 echo -e \u0026#34;\\nLargest files in repository:\u0026#34; git rev-list --objects --all | \\ git cat-file --batch-check=\u0026#39;%(objecttype) %(objectname) %(objectsize) %(rest)\u0026#39; | \\ sed -n \u0026#39;s/^blob //p\u0026#39; | \\ sort --numeric-sort --key=2 | \\ tail -10 # 检查分支状态 echo -e \u0026#34;\\nBranch status:\u0026#34; git for-each-ref --format=\u0026#39;%(refname:short) %(committerdate)\u0026#39; refs/heads | sort -k2 # 检查远程分支 echo -e \u0026#34;\\nRemote branches:\u0026#34; git branch -r --merged main | grep -v main # 检查未推送的提交 echo -e \u0026#34;\\nUnpushed commits:\u0026#34; git log --branches --not --remotes --oneline # 运行垃圾回收 echo -e \u0026#34;\\nRunning garbage collection...\u0026#34; git gc --auto echo -e \u0026#34;\\nHealth check completed!\u0026#34; 自动化清理脚本 # #!/bin/bash # Git 仓库清理脚本 echo \u0026#34;=== Git Repository Cleanup ===\u0026#34; # 清理已合并的本地分支 echo \u0026#34;Cleaning up merged branches...\u0026#34; git branch --merged main | grep -v \u0026#34;main\\|develop\u0026#34; | xargs -n 1 git branch -d # 清理远程分支引用 echo \u0026#34;Pruning remote branches...\u0026#34; git remote prune origin # 清理 reflog echo \u0026#34;Cleaning reflog...\u0026#34; git reflog expire --expire=30.days --all # 运行垃圾回收 echo \u0026#34;Running garbage collection...\u0026#34; git gc --aggressive --prune=now # 检查仓库完整性 echo \u0026#34;Checking repository integrity...\u0026#34; git fsck --full echo \u0026#34;Cleanup completed!\u0026#34; 团队协作进阶 # 代码审查最佳实践 # # 创建审查友好的提交 # 1. 逻辑分组提交 git add file1.js file2.js git commit -m \u0026#34;feat(auth): add login validation logic\u0026#34; git add test1.js test2.js git commit -m \u0026#34;test(auth): add unit tests for login validation\u0026#34; git add docs/auth.md git commit -m \u0026#34;docs(auth): update authentication documentation\u0026#34; # 2. 使用交互式 rebase 整理提交历史 git rebase -i HEAD~3 # 3. 创建详细的 PR 描述 gh pr create --title \u0026#34;feat(auth): implement OAuth2 authentication\u0026#34; \\ --body \u0026#34;## Changes - Add OAuth2 authentication flow - Implement Google and GitHub providers - Add comprehensive unit tests - Update documentation ## Testing - [ ] Unit tests pass - [ ] Integration tests pass - [ ] Manual testing completed ## Screenshots \u0026lt;!-- 登录流程截图（已移除：截图文件不存在） --\u0026gt; Closes #123\u0026#34; 发布管理 # # 语义化版本发布流程 # 1. 确保在主分支 git checkout main git pull origin main # 2. 创建发布分支 git checkout -b release/v1.2.0 # 3. 更新版本号 npm version minor # 或者手动更新 package.json # 4. 生成变更日志 npx conventional-changelog -p angular -i CHANGELOG.md -s # 5. 提交版本更新 git add . git commit -m \u0026#34;chore(release): bump version to 1.2.0\u0026#34; # 6. 合并到主分支 git checkout main git merge --no-ff release/v1.2.0 # 7. 创建标签 git tag -a v1.2.0 -m \u0026#34;Release version 1.2.0\u0026#34; # 8. 推送到远程 git push origin main git push origin v1.2.0 # 9. 创建 GitHub 发布 gh release create v1.2.0 --title \u0026#34;Version 1.2.0\u0026#34; --notes-file CHANGELOG.md # 10. 清理发布分支 git branch -d release/v1.2.0 故障恢复高级技巧 # 数据恢复场景 # # 场景1：恢复误删的文件 git log --diff-filter=D --summary | grep delete git checkout \u0026lt;commit-before-deletion\u0026gt; -- \u0026lt;deleted-file\u0026gt; # 场景2：恢复误删的分支 git reflog --all | grep \u0026lt;branch-name\u0026gt; git checkout -b \u0026lt;branch-name\u0026gt; \u0026lt;commit-hash\u0026gt; # 场景3：恢复误删的提交 git reflog git cherry-pick \u0026lt;commit-hash\u0026gt; # 场景4：恢复到某个时间点 git log --since=\u0026#34;2023-01-01\u0026#34; --until=\u0026#34;2023-01-02\u0026#34; --oneline git checkout \u0026lt;commit-hash\u0026gt; git checkout -b recovery-branch # 场景5：恢复被覆盖的文件内容 git log -p -- \u0026lt;file-path\u0026gt; | head -50 git show \u0026lt;commit-hash\u0026gt;:\u0026lt;file-path\u0026gt; \u0026gt; recovered-file.txt 高级重写历史 # # 使用 filter-branch 重写历史（谨慎使用） # 从历史中完全删除文件 git filter-branch --force --index-filter \\ \u0026#39;git rm --cached --ignore-unmatch path/to/sensitive-file\u0026#39; \\ --prune-empty --tag-name-filter cat -- --all # 使用 BFG Repo-Cleaner（推荐） # 下载 bfg.jar java -jar bfg.jar --delete-files sensitive-file.txt java -jar bfg.jar --strip-blobs-bigger-than 100M git reflog expire --expire=now --all \u0026amp;\u0026amp; git gc --prune=now --aggressive # 修改作者信息 git filter-branch --env-filter \u0026#39; OLD_EMAIL=\u0026#34;old@email.com\u0026#34; CORRECT_NAME=\u0026#34;Correct Name\u0026#34; CORRECT_EMAIL=\u0026#34;correct@email.com\u0026#34; if [ \u0026#34;$GIT_COMMITTER_EMAIL\u0026#34; = \u0026#34;$OLD_EMAIL\u0026#34; ] then export GIT_COMMITTER_NAME=\u0026#34;$CORRECT_NAME\u0026#34; export GIT_COMMITTER_EMAIL=\u0026#34;$CORRECT_EMAIL\u0026#34; fi if [ \u0026#34;$GIT_AUTHOR_EMAIL\u0026#34; = \u0026#34;$OLD_EMAIL\u0026#34; ] then export GIT_AUTHOR_NAME=\u0026#34;$CORRECT_NAME\u0026#34; export GIT_AUTHOR_EMAIL=\u0026#34;$CORRECT_EMAIL\u0026#34; fi \u0026#39; --tag-name-filter cat -- --branches --tags 总结与展望 # Git 的未来发展 # 性能优化: 持续改进大仓库的性能 用户体验: 更友好的命令行界面和错误信息 安全增强: 更强的安全特性和验证机制 云集成: 更好的云服务集成支持 持续学习建议 # 实践为主: 在实际项目中应用所学技巧 关注更新: 跟踪 Git 的新版本和新特性 社区参与: 参与开源项目，学习最佳实践 工具探索: 尝试新的 Git 相关工具和服务 最终建议 # Git 是现代软件开发的基石，掌握它不仅能提高个人效率，更能促进团队协作。从基础命令开始，逐步深入高级特性，结合实际项目需求，您将能够充分发挥 Git 的强大功能。\n记住：好的版本控制习惯是优秀开发者的标志之一。\n通过掌握这些 Git 技巧和最佳实践，您将能够更高效地进行版本控制和团队协作。记住，Git 是一个强大的工具，需要时间和实践来完全掌握。建议从基础命令开始，逐步学习高级功能，并在实际项目中应用这些技巧。\n","date":"2023年6月23日","externalUrl":null,"permalink":"/posts/git-docs/","section":"博客文章","summary":"\u003cp\u003eGit 是现代软件开发中不可或缺的分布式版本控制系统，它不仅能够追踪代码变更历史，还能支持复杂的团队协作和分支管理。本指南将从基础概念开始，逐步深入到高级技巧，帮助您全面掌握 Git 的使用。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003eGit 基础概念 \n    \u003cdiv id=\"git-基础概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#git-%e5%9f%ba%e7%a1%80%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e什么是 Git \n    \u003cdiv id=\"什么是-git\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-git\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eGit 是由 Linux 之父 Linus Torvalds 创建的分布式版本控制系统，具有以下特点：\u003c/p\u003e","title":"Git 版本控制完整实战指南","type":"posts"},{"content":"","date":"2023年6月23日","externalUrl":null,"permalink":"/categories/%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6/","section":"Categories","summary":"","title":"版本控制","type":"categories"},{"content":"","date":"2023年6月23日","externalUrl":null,"permalink":"/tags/%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6/","section":"Tags","summary":"","title":"版本控制","type":"tags"},{"content":"","date":"2023年6月23日","externalUrl":null,"permalink":"/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/","section":"Categories","summary":"","title":"开发工具","type":"categories"},{"content":"","date":"2023年6月23日","externalUrl":null,"permalink":"/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/","section":"Tags","summary":"","title":"开发工具","type":"tags"},{"content":"","date":"2023年6月23日","externalUrl":null,"permalink":"/tags/%E5%8D%8F%E4%BD%9C%E5%BC%80%E5%8F%91/","section":"Tags","summary":"","title":"协作开发","type":"tags"},{"content":"","date":"2022年12月11日","externalUrl":null,"permalink":"/tags/naiveproxy/","section":"Tags","summary":"","title":"Naiveproxy","type":"tags"},{"content":" 说明 # 懂的自然懂，直接上文档。\nCaddy 编译安装\nwget https://go.dev/dl/go1.19.linux-arm64.tar.gz tar -zxvf go1.19.linux-arm64.tar.gz -C /usr/local/ cat \u0026gt;\u0026gt; /etc/profile \u0026lt;\u0026lt; EOF GO_HOME=/usr/local/go export PATH=\\$PATH:\\$GO_HOME/bin/:/root/go/bin EOF source /etc/profile go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest xcaddy build --with github.com/caddyserver/forwardproxy@caddy2=github.com/klzgrad/forwardproxy@naive mv caddy /usr/local/bin/caddy mkdir /etc/caddy Caddyfile 配置文件创建\n这里需要一个 Tls 证书，自己结合实际情况配置更改一下。\ncat \u0026gt; /etc/caddy/Caddyfile \u0026lt;\u0026lt; EOF { debug #http_port 28080 https_port 2443 #default_bind $(hostname -a).treesir.pub auto_https off servers :2443 { listener_wrappers { http_redirect tls } metrics max_header_size 4096mb protocols h1 h2 h2c h3 } } :2443, $(hostname -a).treesir.pub tls /data/traefik-v2ray/acme-cert/cert/treesir.pub.pem /data/traefik-v2ray/acme-cert/cert/treesir.pub.key route { forward_proxy { basic_auth xxxx fKQ3rdKae7YgzCn3 hide_ip hide_via probe_resistance www.bing.com } reverse_proxy https://www.bing.com #file_server { # root /data/wwwroot/default #} } EOF Systemctl 配置文件创建，并设置开机自启动\ncat \u0026gt; /etc/systemd/system/naive.service \u0026lt;\u0026lt; EOF [Unit] Description=Caddy Documentation=https://caddyserver.com/docs/ After=network.target network-online.target Requires=network-online.target [Service] Type=notify User=root Group=root ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile TimeoutStopSec=5s LimitNOFILE=1048576 LimitNPROC=512 PrivateTmp=true ProtectSystem=full AmbientCapabilities=CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target EOF systemctl enable naive.service --now # 启动和设置开机自启动 解锁查询 # # `amd 架构` wget -O /usr/local/bin/nf https://github.com/sjlleo/netflix-verify/releases/download/v3.1.0/nf_linux_amd64 \u0026amp;\u0026amp; chmod a+x /usr/local/bin/nf \u0026amp;\u0026amp; nf # `arm 架构` wget -O /usr/local/bin/nf https://github.com/sjlleo/netflix-verify/releases/download/v3.1.0-1/nf_linux_arm64 \u0026amp;\u0026amp; chmod a+x /usr/local/bin/nf \u0026amp;\u0026amp; nf 其他 # 关闭 IPV6\necho \u0026#34;1\u0026#34; \u0026gt; /proc/sys/net/ipv6/conf/all/disable_ipv6 ","date":"2022年12月11日","externalUrl":null,"permalink":"/posts/naiveproxy/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e说明 \n    \u003cdiv id=\"说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003e懂的自然懂\u003c/code\u003e，直接上文档。\u003c/p\u003e\u003c/blockquote\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eCaddy 编译安装\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ewget https://go.dev/dl/go1.19.linux-arm64.tar.gz\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003etar -zxvf go1.19.linux-arm64.tar.gz -C /usr/local/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecat \u0026gt;\u0026gt; /etc/profile \u003cspan class=\"s\"\u003e\u0026lt;\u0026lt; EOF\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eGO_HOME=/usr/local/go\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eexport PATH=\\$PATH:\\$GO_HOME/bin/:/root/go/bin\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eEOF\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003esource\u003c/span\u003e /etc/profile\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego install github.com/caddyserver/xcaddy/cmd/xcaddy@latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003excaddy build --with github.com/caddyserver/forwardproxy@caddy2\u003cspan class=\"o\"\u003e=\u003c/span\u003egithub.com/klzgrad/forwardproxy@naive\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emv caddy /usr/local/bin/caddy\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emkdir /etc/caddy\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eCaddyfile 配置文件创建\u003c/p\u003e","title":"NaiveProxy","type":"posts"},{"content":"","date":"2022年12月11日","externalUrl":null,"permalink":"/categories/proxy/","section":"Categories","summary":"","title":"Proxy","type":"categories"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/bash/","section":"Tags","summary":"","title":"Bash","type":"tags"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/shell/","section":"Tags","summary":"","title":"Shell","type":"tags"},{"content":"Shell 是 Linux/Unix 系统的核心组件，既是命令行解释器，也是强大的脚本编程语言。掌握 Shell 编程对于系统管理员、运维工程师和开发者来说至关重要。本指南将从基础概念开始，逐步深入到高级应用，帮助您全面掌握 Shell 的使用技巧。\nShell 基础概念 # 什么是 Shell # Shell 是用户与 Linux 内核交互的接口，它具有以下特点：\n命令解释器: 解释和执行用户输入的命令 编程语言: 支持变量、函数、控制结构等编程特性 脚本执行器: 可以执行包含多个命令的脚本文件 环境管理器: 管理环境变量和系统配置 常见的 Shell 类型 # # 查看系统支持的 Shell cat /etc/shells # 常见的 Shell 类型 /bin/sh # Bourne Shell (最原始的 Shell) /bin/bash # Bourne Again Shell (最常用) /bin/zsh # Z Shell (功能强大) /bin/fish # Friendly Interactive Shell /bin/dash # Debian Almquist Shell /bin/csh # C Shell /bin/tcsh # TENEX C Shell # 查看当前使用的 Shell echo $SHELL echo $0 # 切换 Shell chsh -s /bin/zsh Shell 脚本基础 # 脚本结构 # #!/bin/bash # 这是一个 Shell 脚本示例 # 作者: Your Name # 日期: 2023-01-01 # 描述: 脚本功能说明 # 设置脚本选项 set -e # 遇到错误立即退出 set -u # 使用未定义变量时报错 set -o pipefail # 管道中任何命令失败都会导致整个管道失败 # 脚本内容 echo \u0026#34;Hello, World!\u0026#34; 脚本执行方式 # # 方式1: 直接执行（需要执行权限） chmod +x script.sh ./script.sh # 方式2: 使用解释器执行 bash script.sh sh script.sh # 方式3: 使用 source 或 . 执行（在当前 Shell 中执行） source script.sh . script.sh # 方式4: 使用管道执行 cat script.sh | bash 变量和环境 # 变量定义和使用 # # 变量定义（注意等号两边不能有空格） name=\u0026#34;John\u0026#34; age=25 readonly PI=3.14159 # 只读变量 declare -i count=0 # 整数变量 # 变量使用 echo $name echo ${name} # 推荐使用花括号 echo \u0026#34;Hello, $name!\u0026#34; echo \u0026#34;Age: ${age} years old\u0026#34; # 变量默认值 echo ${name:-\u0026#34;Unknown\u0026#34;} # 如果 name 为空，使用默认值 echo ${name:=\u0026#34;Default\u0026#34;} # 如果 name 为空，设置并使用默认值 echo ${name:?\u0026#34;Variable not set\u0026#34;} # 如果 name 为空，报错退出 # 变量长度 echo ${#name} # 输出变量值的长度 # 变量替换 filename=\u0026#34;document.txt\u0026#34; echo ${filename%.txt} # 删除后缀，输出: document echo ${filename#doc} # 删除前缀，输出: ument.txt 环境变量 # # 常用环境变量 echo $HOME # 用户主目录 echo $PATH # 可执行文件搜索路径 echo $PWD # 当前工作目录 echo $USER # 当前用户名 echo $SHELL # 当前 Shell echo $LANG # 语言设置 echo $PS1 # 命令提示符 # 设置环境变量 export MY_VAR=\u0026#34;value\u0026#34; export PATH=$PATH:/new/path # 查看所有环境变量 env printenv # 取消环境变量 unset MY_VAR 特殊变量 # # 位置参数 echo $0 # 脚本名称 echo $1 # 第一个参数 echo $2 # 第二个参数 echo $# # 参数个数 echo $@ # 所有参数（作为独立字符串） echo $* # 所有参数（作为单个字符串） # 进程相关 echo $$ # 当前进程 PID echo $! # 最后一个后台进程 PID echo $? # 上一个命令的退出状态 # 示例脚本 #!/bin/bash echo \u0026#34;脚本名称: $0\u0026#34; echo \u0026#34;参数个数: $#\u0026#34; echo \u0026#34;所有参数: $@\u0026#34; echo \u0026#34;第一个参数: $1\u0026#34; echo \u0026#34;第二个参数: $2\u0026#34; 文件和目录操作 # 目录导航和管理 # 基础目录操作 # # 目录导航 pwd # 显示当前工作目录 cd /path/to/directory # 切换到指定目录 cd ~ # 切换到用户主目录 cd - # 切换到上一个工作目录 cd .. # 切换到上级目录 cd ../.. # 切换到上两级目录 # 目录创建和删除 mkdir directory # 创建目录 mkdir -p /path/to/deep/dir # 递归创建目录结构 mkdir -m 755 directory # 创建目录并设置权限 rmdir directory # 删除空目录 rm -rf directory # 强制递归删除目录（危险操作） # 目录信息 ls -la # 详细列出文件和目录 ls -lah # 人类可读的文件大小格式 ls -lt # 按修改时间排序 ls -lS # 按文件大小排序 ls -lR # 递归列出所有子目录 高级目录操作 # # 目录栈操作 pushd /path/to/dir # 将目录压入栈并切换 popd # 弹出栈顶目录并切换 dirs # 显示目录栈 # 查找目录 find / -type d -name \u0026#34;dirname\u0026#34; 2\u0026gt;/dev/null locate dirname # 快速查找（需要 updatedb） # 目录大小统计 du -sh directory # 显示目录总大小 du -h --max-depth=1 # 显示当前目录下各子目录大小 du -h | sort -hr # 按大小排序显示 # 目录同步 rsync -av source/ dest/ # 同步目录 rsync -av --delete source/ dest/ # 同步并删除目标中多余文件 文件查看和内容处理 # 文件内容查看 # # 基础查看命令 cat filename # 显示文件全部内容 cat -n filename # 显示内容并加行号 cat -A filename # 显示所有字符（包括不可见字符） # 分页查看 less filename # 分页查看（推荐） more filename # 简单分页查看 head -n 20 filename # 显示前 20 行 tail -n 20 filename # 显示后 20 行 tail -f filename # 实时监控文件变化 tail -F filename # 持续监控（文件轮转后继续） # 高级查看技巧 cat file | nl | less # 带行号分页查看 head -c 100 filename # 显示前 100 个字符 tail -c 100 filename # 显示后 100 个字符 文件内容搜索 # # grep 搜索 grep \u0026#34;pattern\u0026#34; filename # 搜索包含模式的行 grep -i \u0026#34;pattern\u0026#34; filename # 忽略大小写搜索 grep -v \u0026#34;pattern\u0026#34; filename # 反向搜索（不包含模式的行） grep -n \u0026#34;pattern\u0026#34; filename # 显示行号 grep -r \u0026#34;pattern\u0026#34; directory # 递归搜索目录 grep -E \u0026#34;pattern1|pattern2\u0026#34; filename # 使用扩展正则表达式 # 高级搜索 grep -A 3 -B 3 \u0026#34;pattern\u0026#34; filename # 显示匹配行前后 3 行 grep -C 3 \u0026#34;pattern\u0026#34; filename # 显示匹配行前后 3 行 grep -l \u0026#34;pattern\u0026#34; *.txt # 只显示包含模式的文件名 grep -c \u0026#34;pattern\u0026#34; filename # 统计匹配行数 # 多文件搜索 grep -r --include=\u0026#34;*.log\u0026#34; \u0026#34;ERROR\u0026#34; /var/log/ find /path -name \u0026#34;*.txt\u0026#34; -exec grep -l \u0026#34;pattern\u0026#34; {} \\; 文件操作和处理 # 文件创建和编辑 # # 文件创建 touch filename # 创建空文件或更新时间戳 touch -t 202301011200 file # 设置特定时间戳 echo \u0026#34;content\u0026#34; \u0026gt; filename # 创建文件并写入内容 cat \u0026gt; filename \u0026lt;\u0026lt; EOF # 多行内容写入 line 1 line 2 EOF # 文件复制和移动 cp source dest # 复制文件 cp -r source_dir dest_dir # 递归复制目录 cp -p source dest # 保持文件属性 mv source dest # 移动/重命名文件 mv *.txt backup/ # 批量移动文件 文件属性和权限 # # 文件权限 chmod 755 filename # 设置权限（rwxr-xr-x） chmod +x filename # 添加执行权限 chmod -w filename # 移除写权限 chmod u+x,g-w,o-r filename # 复杂权限设置 # 文件所有权 chown user:group filename # 更改所有者和组 chown -R user:group dir/ # 递归更改目录所有权 chgrp group filename # 只更改组 # 文件属性 chattr +i filename # 设置不可修改属性 chattr -i filename # 移除不可修改属性 lsattr filename # 查看文件属性 文件格式和编码 # # 文件格式转换 dos2unix filename # Windows 格式转 Unix 格式 unix2dos filename # Unix 格式转 Windows 格式 iconv -f gbk -t utf8 source.txt \u0026gt; target.txt # 编码转换 # 文件类型检测 file filename # 检查文件类型 file -i filename # 显示 MIME 类型 enca filename # 检查文件编码 # 文件校验 md5sum filename # MD5 校验 sha1sum filename # SHA1 校验 sha256sum filename # SHA256 校验 cksum filename # CRC 校验 文件分割和合并 # # 文件分割 split -b 10M largefile prefix_ # 按大小分割（10MB 每个） split -l 1000 largefile prefix_ # 按行数分割（1000 行每个） split -n 5 largefile prefix_ # 分割成 5 个文件 # 文件合并 cat prefix_* \u0026gt; merged_file # 合并分割的文件 cat file1 file2 file3 \u0026gt; merged # 合并多个文件 # 文件截取 head -c 1M largefile \u0026gt; first_1mb # 截取前 1MB tail -c 1M largefile \u0026gt; last_1mb # 截取后 1MB dd if=largefile of=part bs=1M count=10 # 使用 dd 截取 文本处理和数据操作 # 文本排序和去重 # 排序操作 # # 基础排序 sort filename # 按字典序排序 sort -n filename # 按数值排序 sort -r filename # 反向排序 sort -u filename # 排序并去重 sort -k2 filename # 按第二列排序 sort -t: -k3 -n /etc/passwd # 以冒号分隔，按第三列数值排序 # 高级排序 sort -k1,1 -k2,2n filename # 先按第一列排序，再按第二列数值排序 sort -M filename # 按月份排序（Jan, Feb, Mar...） sort -h filename # 按人类可读的数值排序（1K, 2M, 3G） sort -R filename # 随机排序 sort -c filename # 检查文件是否已排序 # 排序示例 cat \u0026gt; data.txt \u0026lt;\u0026lt; EOF apple 10 banana 5 cherry 15 date 8 EOF sort data.txt # 按字母排序 sort -k2 -n data.txt # 按第二列数值排序 去重操作 # # uniq 命令（需要先排序） sort filename | uniq # 去除重复行 sort filename | uniq -c # 统计每行出现次数 sort filename | uniq -d # 只显示重复的行 sort filename | uniq -u # 只显示不重复的行 # 直接去重（不需要排序） awk \u0026#39;!seen[$0]++\u0026#39; filename # 保持原始顺序去重 文本切割和合并 # cut 命令 # # 按字符位置切割 cut -c1-5 filename # 提取每行的第1-5个字符 cut -c1,3,5 filename # 提取第1、3、5个字符 cut -c5- filename # 从第5个字符开始到行尾 # 按分隔符切割 cut -d: -f1 /etc/passwd # 以冒号分隔，提取第一列 cut -d: -f1,3 /etc/passwd # 提取第1和第3列 cut -d: -f3- /etc/passwd # 从第3列开始到最后 # 实用示例 ps aux | cut -c1-20 # 提取进程信息的前20个字符 echo \u0026#34;192.168.1.100\u0026#34; | cut -d. -f1-3 # 提取IP的前三段 paste 命令 # # 文件合并 paste file1 file2 # 按行合并两个文件（Tab分隔） paste -d, file1 file2 # 用逗号分隔合并 paste -d: file1 file2 file3 # 用冒号分隔合并三个文件 paste -s filename # 将多行合并为一行 # 实用示例 seq 1 5 \u0026gt; numbers.txt seq a e \u0026gt; letters.txt paste numbers.txt letters.txt # 合并数字和字母 文本替换和编辑 # sed 流编辑器 # # 基础替换 sed \u0026#39;s/old/new/\u0026#39; filename # 替换每行第一个匹配 sed \u0026#39;s/old/new/g\u0026#39; filename # 替换所有匹配 sed \u0026#39;s/old/new/2\u0026#39; filename # 替换每行第二个匹配 sed -i \u0026#39;s/old/new/g\u0026#39; filename # 直接修改文件 # 行操作 sed -n \u0026#39;5p\u0026#39; filename # 只打印第5行 sed -n \u0026#39;5,10p\u0026#39; filename # 打印第5-10行 sed \u0026#39;5d\u0026#39; filename # 删除第5行 sed \u0026#39;5,10d\u0026#39; filename # 删除第5-10行 sed \u0026#39;/pattern/d\u0026#39; filename # 删除包含模式的行 # 高级操作 sed \u0026#39;/pattern/s/old/new/g\u0026#39; filename # 只在包含pattern的行中替换 sed \u0026#39;s/^/prefix_/\u0026#39; filename # 在每行开头添加前缀 sed \u0026#39;s/$/suffix/\u0026#39; filename # 在每行结尾添加后缀 sed \u0026#39;G\u0026#39; filename # 在每行后添加空行 # 实用示例 sed \u0026#39;s/[0-9]/X/g\u0026#39; filename # 将所有数字替换为X sed \u0026#39;/^$/d\u0026#39; filename # 删除空行 sed \u0026#39;s/\\s\\+/ /g\u0026#39; filename # 将多个空格替换为单个空格 tr 字符转换 # # 字符替换 tr \u0026#39;a-z\u0026#39; \u0026#39;A-Z\u0026#39; \u0026lt; filename # 小写转大写 tr \u0026#39;A-Z\u0026#39; \u0026#39;a-z\u0026#39; \u0026lt; filename # 大写转小写 tr \u0026#39; \u0026#39; \u0026#39;_\u0026#39; \u0026lt; filename # 空格替换为下划线 # 字符删除 tr -d \u0026#39;0-9\u0026#39; \u0026lt; filename # 删除所有数字 tr -d \u0026#39; \\t\u0026#39; \u0026lt; filename # 删除空格和制表符 tr -d \u0026#39;\\n\u0026#39; \u0026lt; filename # 删除换行符 # 字符压缩 tr -s \u0026#39; \u0026#39; \u0026lt; filename # 压缩连续空格为单个空格 tr -s \u0026#39;\\n\u0026#39; \u0026lt; filename # 压缩连续换行符 # 实用示例 echo \u0026#34;Hello World\u0026#34; | tr \u0026#39; \u0026#39; \u0026#39;\\n\u0026#39; # 将空格替换为换行符 cat /dev/urandom | tr -dc \u0026#39;a-zA-Z0-9\u0026#39; | head -c 32 # 生成32位随机字符串 文本分析和统计 # awk 文本处理 # # 基础用法 awk \u0026#39;{print $1}\u0026#39; filename # 打印第一列 awk \u0026#39;{print $1, $3}\u0026#39; filename # 打印第一和第三列 awk \u0026#39;{print NF}\u0026#39; filename # 打印每行的字段数 awk \u0026#39;{print NR, $0}\u0026#39; filename # 打印行号和内容 # 条件处理 awk \u0026#39;$3 \u0026gt; 100\u0026#39; filename # 打印第三列大于100的行 awk \u0026#39;/pattern/\u0026#39; filename # 打印包含模式的行 awk \u0026#39;$1 == \u0026#34;root\u0026#34;\u0026#39; /etc/passwd # 打印第一列等于root的行 # 计算统计 awk \u0026#39;{sum += $1} END {print sum}\u0026#39; filename # 计算第一列的总和 awk \u0026#39;{sum += $1; count++} END {print sum/count}\u0026#39; filename # 计算平均值 awk \u0026#39;{if($1 \u0026gt; max) max=$1} END {print max}\u0026#39; filename # 找最大值 # 格式化输出 awk \u0026#39;{printf \u0026#34;%-10s %5d\\n\u0026#34;, $1, $2}\u0026#39; filename # 格式化打印 awk \u0026#39;BEGIN{print \u0026#34;Name\\tScore\u0026#34;} {print $1\u0026#34;\\t\u0026#34;$2}\u0026#39; filename # 添加表头 # 实用示例 ps aux | awk \u0026#39;{sum += $6} END {print \u0026#34;Total Memory:\u0026#34;, sum/1024, \u0026#34;MB\u0026#34;}\u0026#39; # 计算内存使用 awk -F: \u0026#39;{print $1}\u0026#39; /etc/passwd | sort # 提取用户名并排序 wc 统计命令 # # 基础统计 wc filename # 显示行数、单词数、字符数 wc -l filename # 只显示行数 wc -w filename # 只显示单词数 wc -c filename # 只显示字符数 wc -m filename # 显示字符数（多字节字符） # 批量统计 wc *.txt # 统计所有txt文件 find . -name \u0026#34;*.log\u0026#34; | xargs wc -l # 统计所有log文件行数 # 实用示例 ps aux | wc -l # 统计进程数 cat /etc/passwd | wc -l # 统计用户数 ls -1 | wc -l # 统计文件数 文件查找和搜索 # find 命令详解 # 基础查找 # # 按名称查找 find /path -name \u0026#34;filename\u0026#34; # 精确匹配文件名 find /path -name \u0026#34;*.txt\u0026#34; # 通配符匹配 find /path -iname \u0026#34;*.TXT\u0026#34; # 忽略大小写匹配 find /path -name \u0026#34;*log*\u0026#34; # 包含log的文件 # 按类型查找 find /path -type f # 查找普通文件 find /path -type d # 查找目录 find /path -type l # 查找符号链接 find /path -type b # 查找块设备 find /path -type c # 查找字符设备 # 按大小查找 find /path -size +100M # 大于100MB的文件 find /path -size -1k # 小于1KB的文件 find /path -size 50c # 等于50字节的文件 find /path -empty # 空文件或空目录 按时间查找 # # 按修改时间查找 find /path -mtime +7 # 7天前修改的文件 find /path -mtime -1 # 1天内修改的文件 find /path -mtime 0 # 今天修改的文件 # 按访问时间查找 find /path -atime +30 # 30天前访问的文件 find /path -atime -7 # 7天内访问的文件 # 按状态改变时间查找 find /path -ctime +1 # 1天前状态改变的文件 # 按分钟查找 find /path -mmin +60 # 60分钟前修改的文件 find /path -mmin -30 # 30分钟内修改的文件 # 时间范围查找 find /path -newermt \u0026#34;2023-01-01\u0026#34; ! -newermt \u0026#34;2023-12-31\u0026#34; # 指定时间范围 按权限和所有者查找 # # 按权限查找 find /path -perm 755 # 权限等于755的文件 find /path -perm -644 # 至少有644权限的文件 find /path -perm /u+w # 用户有写权限的文件 # 按所有者查找 find /path -user username # 属于指定用户的文件 find /path -group groupname # 属于指定组的文件 find /path -uid 1000 # UID为1000的文件 find /path -gid 100 # GID为100的文件 find /path -nouser # 没有有效用户的文件 find /path -nogroup # 没有有效组的文件 高级查找和操作 # # 逻辑操作 find /path -name \u0026#34;*.txt\u0026#34; -and -size +1M # 同时满足两个条件 find /path -name \u0026#34;*.log\u0026#34; -or -name \u0026#34;*.txt\u0026#34; # 满足任一条件 find /path ! -name \u0026#34;*.tmp\u0026#34; # 不匹配条件 # 执行操作 find /path -name \u0026#34;*.tmp\u0026#34; -delete # 删除找到的文件 find /path -name \u0026#34;*.txt\u0026#34; -exec ls -l {} \\; # 对每个文件执行命令 find /path -name \u0026#34;*.log\u0026#34; -exec rm {} \\; # 删除找到的文件 find /path -name \u0026#34;*.txt\u0026#34; -exec grep \u0026#34;pattern\u0026#34; {} \\; # 在找到的文件中搜索 # 批量操作 find /path -name \u0026#34;*.txt\u0026#34; -print0 | xargs -0 rm # 安全删除（处理空格） find /path -name \u0026#34;*.log\u0026#34; | xargs grep \u0026#34;ERROR\u0026#34; # 在多个文件中搜索 find /path -type f -name \u0026#34;*.sh\u0026#34; -exec chmod +x {} \\; # 批量添加执行权限 # 实用示例 find /var/log -name \u0026#34;*.log\u0026#34; -mtime +30 -delete # 删除30天前的日志 find /home -name \u0026#34;core\u0026#34; -type f -delete # 删除core文件 find . -name \u0026#34;*.c\u0026#34; -exec wc -l {} \\; | awk \u0026#39;{sum+=$1} END {print sum}\u0026#39; # 统计C文件总行数 locate 快速查找 # # 基础使用 locate filename # 快速查找文件 locate \u0026#34;*.conf\u0026#34; # 查找配置文件 locate -i filename # 忽略大小写查找 locate -c filename # 只显示匹配数量 # 更新数据库 updatedb # 更新locate数据库（需要root权限） # 限制搜索 locate -l 10 filename # 限制显示10个结果 locate -r \u0026#34;pattern\u0026#34; # 使用正则表达式 # 实用示例 locate passwd | grep etc # 查找/etc下的passwd文件 locate bin/python # 查找python可执行文件 which 和 whereis 命令 # # which - 查找可执行文件路径 which python # 查找python命令路径 which -a python # 显示所有匹配的路径 # whereis - 查找二进制文件、源码和手册页 whereis python # 查找python相关文件 whereis -b python # 只查找二进制文件 whereis -m python # 只查找手册页 whereis -s python # 只查找源码 # type - 显示命令类型 type python # 显示python是什么类型的命令 type -a python # 显示所有匹配项 type -t python # 只显示类型 高级文件操作 # 文件比较和差异 # diff 命令 # # 基础比较 diff file1 file2 # 比较两个文件 diff -u file1 file2 # 统一格式输出 diff -c file1 file2 # 上下文格式输出 diff -y file1 file2 # 并排显示差异 # 目录比较 diff -r dir1 dir2 # 递归比较目录 diff -r --brief dir1 dir2 # 只显示不同的文件名 # 忽略差异 diff -w file1 file2 # 忽略空白字符差异 diff -i file1 file2 # 忽略大小写差异 diff -B file1 file2 # 忽略空行 # 生成补丁 diff -u original.txt modified.txt \u0026gt; changes.patch # 生成补丁文件 patch original.txt \u0026lt; changes.patch # 应用补丁 comm 命令 # # 比较已排序的文件 comm file1 file2 # 显示三列：仅在file1、仅在file2、共同行 comm -1 file1 file2 # 不显示仅在file1中的行 comm -2 file1 file2 # 不显示仅在file2中的行 comm -3 file1 file2 # 不显示共同行 comm -12 file1 file2 # 只显示共同行 # 实用示例 comm -23 \u0026lt;(sort file1) \u0026lt;(sort file2) # 显示file1独有的行 文件监控 # watch 命令 # # 实时监控 watch ls -la # 每2秒执行一次ls命令 watch -n 1 df -h # 每1秒监控磁盘使用 watch -d \u0026#39;ps aux | grep apache\u0026#39; # 高亮显示变化 # 监控文件变化 watch -d -n 1 \u0026#39;ls -la /path\u0026#39; # 监控目录变化 watch \u0026#39;tail -n 20 /var/log/messages\u0026#39; # 监控日志文件 inotify 文件系统监控 # # 安装 inotify-tools yum install -y inotify-tools # CentOS/RHEL apt-get install -y inotify-tools # Ubuntu/Debian # 监控文件变化 inotifywait -m /path/to/file # 持续监控文件 inotifywait -r -m /path/to/dir # 递归监控目录 inotifywait -e modify,create,delete /path # 监控特定事件 # 实用脚本 #!/bin/bash inotifywait -m -r -e modify,create,delete /var/www/html | while read path action file; do echo \u0026#34;File $file in $path was $action at $(date)\u0026#34; done 系统管理和监控 # 进程管理 # 进程查看和分析 # # 基础进程查看 ps aux # 查看所有进程（BSD格式） ps -ef # 查看所有进程（System V格式） ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu # 自定义格式显示 ps -C process_name # 查看特定名称的进程 ps -u username # 查看特定用户的进程 # 进程树显示 pstree # 显示进程树 pstree -p # 显示进程树和PID pstree username # 显示特定用户的进程树 # 进程搜索 pgrep process_name # 查找进程PID pgrep -f \u0026#34;full_command\u0026#34; # 根据完整命令行查找 pgrep -u username # 查找特定用户的进程 pidof process_name # 查找进程PID（另一种方法） # 进程详细信息 cat /proc/PID/status # 查看进程详细状态 cat /proc/PID/cmdline # 查看进程启动命令 cat /proc/PID/environ # 查看进程环境变量 ls -la /proc/PID/fd/ # 查看进程打开的文件描述符 进程控制和信号 # # 进程终止 kill PID # 发送TERM信号终止进程 kill -9 PID # 发送KILL信号强制终止进程 kill -STOP PID # 暂停进程 kill -CONT PID # 继续进程 killall process_name # 杀死所有同名进程 pkill process_name # 根据进程名杀死进程 pkill -f \u0026#34;pattern\u0026#34; # 根据命令行模式杀死进程 # 信号类型 kill -l # 列出所有信号 kill -HUP PID # 重新加载配置（常用于服务） kill -USR1 PID # 用户自定义信号1 kill -USR2 PID # 用户自定义信号2 # 批量进程管理 pkill -u username # 杀死特定用户的所有进程 killall -u username # 杀死特定用户的所有进程 后台进程和任务管理 # # 后台运行 command \u0026amp; # 在后台运行命令 nohup command \u0026amp; # 后台运行，忽略挂起信号 nohup command \u0026gt; output.log 2\u0026gt;\u0026amp;1 \u0026amp; # 后台运行并重定向输出 # 任务管理 jobs # 查看当前shell的后台任务 jobs -l # 显示任务的PID bg %1 # 将任务1放到后台继续运行 fg %1 # 将任务1调到前台 disown %1 # 从当前shell中移除任务1 disown -a # 移除所有任务 # 进程优先级 nice -n 10 command # 以较低优先级运行命令 renice 5 PID # 调整进程优先级 ionice -c 3 PID # 设置IO优先级为idle 系统监控 # 实时监控工具 # # top 系列 top # 实时显示进程状态 top -u username # 只显示特定用户的进程 top -p PID1,PID2 # 只监控特定进程 htop # 更友好的top（需安装） # 系统资源监控 vmstat 1 # 每秒显示系统统计信息 vmstat 1 10 # 每秒显示，共10次 iostat 1 # 每秒显示IO统计 sar -u 1 10 # 每秒显示CPU使用率，共10次 sar -r 1 10 # 每秒显示内存使用率 # 网络监控 iftop # 实时网络流量监控（需安装） nethogs # 按进程显示网络使用（需安装） ss -tuln # 显示网络连接状态 netstat -tuln # 显示网络连接状态（传统命令） # 磁盘IO监控 iotop # 实时显示磁盘IO（需安装） iotop -o # 只显示有IO活动的进程 系统信息查看 # # 基本系统信息 uname -a # 显示系统所有信息 hostname # 显示主机名 hostnamectl # 显示主机名详细信息（systemd系统） uptime # 显示系统运行时间和负载 who # 显示当前登录用户 w # 显示登录用户及其活动 last # 显示最近登录记录 lastlog # 显示所有用户最后登录时间 # 用户和权限信息 id # 显示当前用户和组ID whoami # 显示当前用户名 groups # 显示当前用户所属组 su - username # 切换用户 sudo command # 以管理员权限执行命令 硬件信息查看 # # CPU信息 lscpu # 显示CPU详细信息 cat /proc/cpuinfo # 查看CPU信息 nproc # 显示CPU核心数 # 内存信息 free -h # 显示内存使用情况（人类可读） free -m # 以MB为单位显示内存 cat /proc/meminfo # 详细内存信息 vmstat -s # 内存统计信息 # 磁盘信息 df -h # 显示磁盘使用情况 df -i # 显示inode使用情况 du -sh /path # 显示目录大小 lsblk # 显示块设备信息 fdisk -l # 显示磁盘分区信息（需root） blkid # 显示块设备UUID # 硬件设备信息 lsusb # 显示USB设备 lspci # 显示PCI设备 lshw # 显示硬件详细信息（需安装） dmidecode # 显示DMI/SMBIOS信息（需root） 系统服务管理 # systemd 服务管理 # # 服务状态查看 systemctl status service_name # 查看服务状态 systemctl is-active service_name # 检查服务是否运行 systemctl is-enabled service_name # 检查服务是否开机启动 systemctl list-units --type=service # 列出所有服务 # 服务控制 systemctl start service_name # 启动服务 systemctl stop service_name # 停止服务 systemctl restart service_name # 重启服务 systemctl reload service_name # 重新加载配置 systemctl enable service_name # 设置开机启动 systemctl disable service_name # 取消开机启动 # 服务日志 journalctl -u service_name # 查看服务日志 journalctl -u service_name -f # 实时查看服务日志 journalctl -u service_name --since \u0026#34;1 hour ago\u0026#34; # 查看最近1小时的日志 传统服务管理（SysV） # # service 命令 service service_name status # 查看服务状态 service service_name start # 启动服务 service service_name stop # 停止服务 service service_name restart # 重启服务 # chkconfig 命令 chkconfig --list # 列出所有服务 chkconfig service_name on # 设置开机启动 chkconfig service_name off # 取消开机启动 编辑器和开发工具 # Vim 编辑器 # Vim 基础操作 # # 启动 Vim vim filename # 打开文件 vim +n filename # 打开文件并跳转到第n行 vim +/pattern filename # 打开文件并搜索模式 vim file1 file2 # 打开多个文件 vim -r filename # 恢复异常关闭的文件 vim -O file1 file2 # 垂直分屏打开 vim -o file1 file2 # 水平分屏打开 # 模式切换 i # 进入插入模式（光标前） a # 进入插入模式（光标后） o # 新建行并进入插入模式 ESC # 退出到普通模式 : # 进入命令模式 v # 进入可视模式 V # 进入行可视模式 Ctrl+v # 进入块可视模式 移动和导航 # # 基础移动 h, j, k, l # 左、下、上、右 w # 下一个单词开头 b # 上一个单词开头 e # 当前单词结尾 0 # 行首 ^ # 行首第一个非空字符 $ # 行尾 gg # 文件开头 G # 文件结尾 nG # 跳转到第n行 # 翻页 Ctrl+f # 向下翻页 Ctrl+b # 向上翻页 Ctrl+d # 向下翻半页 Ctrl+u # 向上翻半页 # 搜索和跳转 /pattern # 向下搜索 ?pattern # 向上搜索 n # 下一个匹配 N # 上一个匹配 * # 搜索当前光标下的单词 # # 反向搜索当前光标下的单词 编辑操作 # # 删除 x # 删除当前字符 dd # 删除当前行 ndd # 删除n行 dw # 删除单词 d$ # 删除到行尾 d0 # 删除到行首 # 复制和粘贴 yy # 复制当前行 nyy # 复制n行 yw # 复制单词 y$ # 复制到行尾 p # 粘贴到光标后 P # 粘贴到光标前 # 替换 r # 替换单个字符 R # 进入替换模式 :%s/old/new/g # 全文替换 :%s/old/new/gc # 全文替换（确认） :n,ms/old/new/g # 在n到m行之间替换 # 撤销和重做 u # 撤销 Ctrl+r # 重做 Vim 配置 # # 基础配置（~/.vimrc） set number # 显示行号 set relativenumber # 显示相对行号 set tabstop=4 # Tab宽度 set shiftwidth=4 # 缩进宽度 set expandtab # Tab转空格 set autoindent # 自动缩进 set smartindent # 智能缩进 set hlsearch # 高亮搜索结果 set incsearch # 增量搜索 set ignorecase # 忽略大小写 set smartcase # 智能大小写 set wrap # 自动换行 set ruler # 显示光标位置 set showcmd # 显示命令 set wildmenu # 命令行补全 set clipboard=unnamed # 使用系统剪贴板 # 高级配置 syntax on # 语法高亮 filetype plugin indent on # 文件类型检测 set background=dark # 深色背景 colorscheme desert # 颜色主题 set mouse=a # 启用鼠标 set encoding=utf-8 # 编码设置 分屏和标签 # # 分屏操作 :sp filename # 水平分屏 :vs filename # 垂直分屏 Ctrl+w w # 切换窗口 Ctrl+w h/j/k/l # 移动到指定方向窗口 Ctrl+w +/- # 调整窗口高度 Ctrl+w \u0026gt;/\u0026lt; # 调整窗口宽度 Ctrl+w = # 平均分配窗口大小 :q # 关闭当前窗口 # 标签页操作 :tabnew filename # 新建标签页 :tabn # 下一个标签页 :tabp # 上一个标签页 :tabclose # 关闭标签页 gt # 下一个标签页（普通模式） gT # 上一个标签页（普通模式） 文件压缩和归档 # tar 归档工具 # 基础 tar 操作 # # 创建归档 tar -cvf archive.tar files/ # 创建tar归档 tar -czf archive.tar.gz files/ # 创建gzip压缩归档 tar -cjf archive.tar.bz2 files/ # 创建bzip2压缩归档 tar -cJf archive.tar.xz files/ # 创建xz压缩归档 # 解压归档 tar -xvf archive.tar # 解压tar归档 tar -xzf archive.tar.gz # 解压gzip归档 tar -xjf archive.tar.bz2 # 解压bzip2归档 tar -xJf archive.tar.xz # 解压xz归档 # 查看归档内容 tar -tvf archive.tar # 查看tar归档内容 tar -tzf archive.tar.gz # 查看gzip归档内容 tar -tjf archive.tar.bz2 # 查看bzip2归档内容 tar -tJf archive.tar.xz # 查看xz归档内容 高级 tar 操作 # # 指定目录操作 tar -xvf archive.tar -C /target/ # 解压到指定目录 tar -czf backup.tar.gz -C /source/ . # 从指定目录创建归档 # 排除文件和目录 tar -czf archive.tar.gz --exclude=\u0026#39;*.log\u0026#39; files/ # 排除特定文件 tar -czf archive.tar.gz --exclude-from=exclude.txt files/ # 从文件读取排除列表 tar --exclude=/home/user --exclude=*.tmp -czf backup.tar.gz /home/* # 多重排除 # 时间过滤 tar -N \u0026#34;2023-01-01\u0026#34; -czf new-files.tar.gz /data/ # 只备份指定日期后的文件 # 增量备份 tar -czf backup.tar.gz --listed-incremental=snapshot.file /data/ # 归档操作 tar -rvf archive.tar newfile.txt # 向归档添加文件 tar --delete -f archive.tar unwanted.txt # 从归档删除文件 # 网络传输 tar -czf - /data/ | ssh user@host \u0026#39;tar -xzf - -C /backup/\u0026#39; # 通过SSH传输 压缩工具 # gzip/gunzip # # 压缩文件 gzip filename # 压缩文件（原文件被替换） gzip -k filename # 压缩文件（保留原文件） gzip -9 filename # 最大压缩比 gzip -1 filename # 最快压缩 gzip -r directory/ # 递归压缩目录中的文件 # 解压文件 gunzip filename.gz # 解压文件 gzip -d filename.gz # 解压文件（另一种方法） gzip -dr directory/ # 递归解压目录 # 查看压缩文件 zcat filename.gz # 查看压缩文件内容（不解压） zgrep \u0026#34;pattern\u0026#34; filename.gz # 在压缩文件中搜索 zless filename.gz # 分页查看压缩文件 bzip2/bunzip2 # # 压缩文件 bzip2 filename # 压缩文件 bzip2 -k filename # 压缩文件（保留原文件） bzip2 -9 filename # 最大压缩比 # 解压文件 bunzip2 filename.bz2 # 解压文件 bzip2 -d filename.bz2 # 解压文件（另一种方法） # 查看压缩文件 bzcat filename.bz2 # 查看压缩文件内容 bzgrep \u0026#34;pattern\u0026#34; filename.bz2 # 在压缩文件中搜索 ZIP 压缩 # # 创建ZIP文件 zip archive.zip file1 file2 # 压缩文件 zip -r archive.zip directory/ # 递归压缩目录 zip -9 archive.zip files/ # 最大压缩比 zip -0 archive.zip files/ # 仅存储（不压缩） # 解压ZIP文件 unzip archive.zip # 解压到当前目录 unzip archive.zip -d /target/ # 解压到指定目录 unzip -l archive.zip # 列出ZIP文件内容 unzip -t archive.zip # 测试ZIP文件完整性 # 高级ZIP操作 zip -u archive.zip newfile.txt # 更新ZIP文件 zip -d archive.zip unwanted.txt # 从ZIP删除文件 zip -x \u0026#34;*.log\u0026#34; -r archive.zip dir/ # 排除特定文件 unzip -j archive.zip # 解压时忽略目录结构 其他压缩格式 # RAR（需要安装） # # 压缩RAR rar a archive.rar files/ # 创建RAR归档 rar a -m5 archive.rar files/ # 最大压缩比 # 解压RAR unrar x archive.rar # 解压RAR文件 unrar l archive.rar # 列出RAR文件内容 unrar t archive.rar # 测试RAR文件 Shell 脚本编程 # 控制结构 # 条件判断 # # if 语句 if [ condition ]; then commands elif [ condition2 ]; then commands else commands fi # 条件测试 [ -f file ] # 文件存在且为普通文件 [ -d directory ] # 目录存在 [ -e path ] # 路径存在 [ -r file ] # 文件可读 [ -w file ] # 文件可写 [ -x file ] # 文件可执行 [ -s file ] # 文件非空 [ file1 -nt file2 ] # file1 比 file2 新 [ file1 -ot file2 ] # file1 比 file2 旧 # 字符串比较 [ \u0026#34;$str1\u0026#34; = \u0026#34;$str2\u0026#34; ] # 字符串相等 [ \u0026#34;$str1\u0026#34; != \u0026#34;$str2\u0026#34; ] # 字符串不等 [ -z \u0026#34;$str\u0026#34; ] # 字符串为空 [ -n \u0026#34;$str\u0026#34; ] # 字符串非空 # 数值比较 [ $num1 -eq $num2 ] # 等于 [ $num1 -ne $num2 ] # 不等于 [ $num1 -lt $num2 ] # 小于 [ $num1 -le $num2 ] # 小于等于 [ $num1 -gt $num2 ] # 大于 [ $num1 -ge $num2 ] # 大于等于 # 逻辑操作 [ condition1 ] \u0026amp;\u0026amp; [ condition2 ] # 逻辑与 [ condition1 ] || [ condition2 ] # 逻辑或 [ ! condition ] # 逻辑非 循环结构 # # for 循环 for var in list; do commands done # for 循环示例 for file in *.txt; do echo \u0026#34;Processing $file\u0026#34; done for i in {1..10}; do echo \u0026#34;Number: $i\u0026#34; done for ((i=1; i\u0026lt;=10; i++)); do echo \u0026#34;Counter: $i\u0026#34; done # while 循环 while [ condition ]; do commands done # while 循环示例 counter=1 while [ $counter -le 5 ]; do echo \u0026#34;Count: $counter\u0026#34; ((counter++)) done # until 循环 until [ condition ]; do commands done # 读取文件行 while IFS= read -r line; do echo \u0026#34;Line: $line\u0026#34; done \u0026lt; filename case 语句 # case $variable in pattern1) commands ;; pattern2|pattern3) commands ;; *) default commands ;; esac # case 示例 case $1 in start) echo \u0026#34;Starting service...\u0026#34; ;; stop) echo \u0026#34;Stopping service...\u0026#34; ;; restart) echo \u0026#34;Restarting service...\u0026#34; ;; *) echo \u0026#34;Usage: $0 {start|stop|restart}\u0026#34; exit 1 ;; esac 函数 # 函数定义和调用 # # 函数定义方式1 function_name() { commands return value } # 函数定义方式2 function function_name { commands return value } # 函数调用 function_name arg1 arg2 # 函数示例 backup_file() { local file=$1 local backup_dir=${2:-/backup} if [ -f \u0026#34;$file\u0026#34; ]; then cp \u0026#34;$file\u0026#34; \u0026#34;$backup_dir/$(basename $file).bak\u0026#34; echo \u0026#34;Backup created: $backup_dir/$(basename $file).bak\u0026#34; else echo \u0026#34;Error: File $file not found\u0026#34; return 1 fi } # 调用函数 backup_file \u0026#34;/etc/passwd\u0026#34; \u0026#34;/tmp\u0026#34; 函数参数和返回值 # # 函数参数 function example() { echo \u0026#34;Function name: $0\u0026#34; echo \u0026#34;First argument: $1\u0026#34; echo \u0026#34;Second argument: $2\u0026#34; echo \u0026#34;All arguments: $@\u0026#34; echo \u0026#34;Number of arguments: $#\u0026#34; local local_var=\u0026#34;local value\u0026#34; global_var=\u0026#34;global value\u0026#34; return 0 } # 获取函数返回值 example arg1 arg2 result=$? echo \u0026#34;Function returned: $result\u0026#34; 高级特性 # 数组 # # 数组定义 array=(element1 element2 element3) declare -a array array[0]=\u0026#34;first\u0026#34; array[1]=\u0026#34;second\u0026#34; # 数组操作 echo ${array[0]} # 访问元素 echo ${array[@]} # 所有元素 echo ${#array[@]} # 数组长度 echo ${!array[@]} # 所有索引 # 数组遍历 for element in \u0026#34;${array[@]}\u0026#34;; do echo $element done for index in \u0026#34;${!array[@]}\u0026#34;; do echo \u0026#34;Index: $index, Value: ${array[$index]}\u0026#34; done # 关联数组（Bash 4.0+） declare -A assoc_array assoc_array[\u0026#34;key1\u0026#34;]=\u0026#34;value1\u0026#34; assoc_array[\u0026#34;key2\u0026#34;]=\u0026#34;value2\u0026#34; for key in \u0026#34;${!assoc_array[@]}\u0026#34;; do echo \u0026#34;Key: $key, Value: ${assoc_array[$key]}\u0026#34; done 字符串处理 # string=\u0026#34;Hello World\u0026#34; # 字符串长度 echo ${#string} # 字符串截取 echo ${string:0:5} # 从位置0开始，长度5 echo ${string:6} # 从位置6到结尾 echo ${string: -5} # 最后5个字符 # 字符串替换 echo ${string/World/Universe} # 替换第一个匹配 echo ${string//o/O} # 替换所有匹配 # 字符串删除 echo ${string#Hello } # 删除开头匹配 echo ${string%World} # 删除结尾匹配 # 大小写转换 echo ${string^^} # 转大写 echo ${string,,} # 转小写 权限和安全 # 文件权限管理 # 基础权限 # # 查看权限 ls -l filename # 查看文件权限 ls -ld directory # 查看目录权限 # 修改权限 chmod 755 filename # 数字方式设置权限 chmod u+x filename # 符号方式添加权限 chmod g-w filename # 符号方式移除权限 chmod o=r filename # 符号方式设置权限 # 递归修改权限 chmod -R 755 directory # 递归修改目录权限 find /path -type f -exec chmod 644 {} \\; # 只修改文件权限 find /path -type d -exec chmod 755 {} \\; # 只修改目录权限 # 修改所有者 chown user:group filename # 修改用户和组 chown user filename # 只修改用户 chgrp group filename # 只修改组 chown -R user:group directory # 递归修改 ACL 权限控制 # # 查看 ACL 权限 getfacl filename # 查看文件 ACL 权限 getfacl directory # 查看目录 ACL 权限 # 设置 ACL 权限 setfacl -m u:username:rw filename # 给用户添加读写权限 setfacl -m g:groupname:r filename # 给组添加读权限 setfacl -m o::--- filename # 设置其他用户权限 # 递归设置 ACL setfacl -R -m u:username:rw directory # 递归设置权限 # 删除 ACL 权限 setfacl -x u:username filename # 删除用户的 ACL 权限 setfacl -b filename # 删除所有 ACL 权限 # 默认 ACL（对目录） setfacl -d -m u:username:rw directory # 设置默认 ACL # 从文件恢复 ACL getfacl file1 | setfacl --set-file=- file2 # 复制 ACL 权限 实用脚本示例 # 系统监控脚本 # 系统资源监控 # #!/bin/bash # 系统资源监控脚本 # 设置阈值 CPU_THRESHOLD=80 MEMORY_THRESHOLD=85 DISK_THRESHOLD=90 # 获取系统信息 get_cpu_usage() { top -bn1 | grep \u0026#34;Cpu(s)\u0026#34; | awk \u0026#39;{print $2}\u0026#39; | awk -F\u0026#39;%\u0026#39; \u0026#39;{print $1}\u0026#39; } get_memory_usage() { free | grep Mem | awk \u0026#39;{printf(\u0026#34;%.1f\u0026#34;), $3/$2 * 100.0}\u0026#39; } get_disk_usage() { df -h | awk \u0026#39;$NF==\u0026#34;/\u0026#34;{printf \u0026#34;%d\u0026#34;, $5}\u0026#39; } # 发送告警 send_alert() { local message=\u0026#34;$1\u0026#34; echo \u0026#34;$(date): $message\u0026#34; \u0026gt;\u0026gt; /var/log/system-monitor.log # 可以添加邮件或其他通知方式 # echo \u0026#34;$message\u0026#34; | mail -s \u0026#34;System Alert\u0026#34; admin@example.com } # 主监控逻辑 main() { cpu_usage=$(get_cpu_usage) memory_usage=$(get_memory_usage) disk_usage=$(get_disk_usage) echo \u0026#34;=== System Monitor Report $(date) ===\u0026#34; echo \u0026#34;CPU Usage: ${cpu_usage}%\u0026#34; echo \u0026#34;Memory Usage: ${memory_usage}%\u0026#34; echo \u0026#34;Disk Usage: ${disk_usage}%\u0026#34; # 检查阈值 if (( $(echo \u0026#34;$cpu_usage \u0026gt; $CPU_THRESHOLD\u0026#34; | bc -l) )); then send_alert \u0026#34;High CPU usage: ${cpu_usage}%\u0026#34; fi if (( $(echo \u0026#34;$memory_usage \u0026gt; $MEMORY_THRESHOLD\u0026#34; | bc -l) )); then send_alert \u0026#34;High memory usage: ${memory_usage}%\u0026#34; fi if [ \u0026#34;$disk_usage\u0026#34; -gt \u0026#34;$DISK_THRESHOLD\u0026#34; ]; then send_alert \u0026#34;High disk usage: ${disk_usage}%\u0026#34; fi } main \u0026#34;$@\u0026#34; 日志清理脚本 # #!/bin/bash # 日志清理脚本 LOG_DIRS=(\u0026#34;/var/log\u0026#34; \u0026#34;/opt/app/logs\u0026#34; \u0026#34;/home/user/logs\u0026#34;) RETENTION_DAYS=30 DRY_RUN=false # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in --dry-run) DRY_RUN=true shift ;; --days) RETENTION_DAYS=\u0026#34;$2\u0026#34; shift 2 ;; *) echo \u0026#34;Unknown option: $1\u0026#34; exit 1 ;; esac done # 清理函数 cleanup_logs() { local dir=\u0026#34;$1\u0026#34; local days=\u0026#34;$2\u0026#34; if [ ! -d \u0026#34;$dir\u0026#34; ]; then echo \u0026#34;Directory $dir does not exist\u0026#34; return 1 fi echo \u0026#34;Cleaning logs in $dir older than $days days...\u0026#34; if [ \u0026#34;$DRY_RUN\u0026#34; = true ]; then find \u0026#34;$dir\u0026#34; -name \u0026#34;*.log\u0026#34; -type f -mtime +$days -print find \u0026#34;$dir\u0026#34; -name \u0026#34;*.log.*\u0026#34; -type f -mtime +$days -print else find \u0026#34;$dir\u0026#34; -name \u0026#34;*.log\u0026#34; -type f -mtime +$days -delete find \u0026#34;$dir\u0026#34; -name \u0026#34;*.log.*\u0026#34; -type f -mtime +$days -delete fi } # 主函数 main() { echo \u0026#34;Starting log cleanup...\u0026#34; echo \u0026#34;Retention period: $RETENTION_DAYS days\u0026#34; echo \u0026#34;Dry run: $DRY_RUN\u0026#34; for dir in \u0026#34;${LOG_DIRS[@]}\u0026#34;; do cleanup_logs \u0026#34;$dir\u0026#34; \u0026#34;$RETENTION_DAYS\u0026#34; done echo \u0026#34;Log cleanup completed\u0026#34; } main 备份脚本 # 数据库备份脚本 # #!/bin/bash # MySQL 数据库备份脚本 # 配置 DB_HOST=\u0026#34;localhost\u0026#34; DB_USER=\u0026#34;backup_user\u0026#34; DB_PASS=\u0026#34;backup_password\u0026#34; BACKUP_DIR=\u0026#34;/backup/mysql\u0026#34; RETENTION_DAYS=7 # 创建备份目录 mkdir -p \u0026#34;$BACKUP_DIR\u0026#34; # 获取所有数据库 databases=$(mysql -h \u0026#34;$DB_HOST\u0026#34; -u \u0026#34;$DB_USER\u0026#34; -p\u0026#34;$DB_PASS\u0026#34; -e \u0026#34;SHOW DATABASES;\u0026#34; | grep -Ev \u0026#34;(Database|information_schema|performance_schema|mysql|sys)\u0026#34;) # 备份每个数据库 for db in $databases; do echo \u0026#34;Backing up database: $db\u0026#34; backup_file=\u0026#34;$BACKUP_DIR/${db}_$(date +%Y%m%d_%H%M%S).sql\u0026#34; mysqldump -h \u0026#34;$DB_HOST\u0026#34; -u \u0026#34;$DB_USER\u0026#34; -p\u0026#34;$DB_PASS\u0026#34; \\ --single-transaction \\ --routines \\ --triggers \\ \u0026#34;$db\u0026#34; \u0026gt; \u0026#34;$backup_file\u0026#34; if [ $? -eq 0 ]; then echo \u0026#34;Backup successful: $backup_file\u0026#34; gzip \u0026#34;$backup_file\u0026#34; else echo \u0026#34;Backup failed for database: $db\u0026#34; fi done # 清理旧备份 find \u0026#34;$BACKUP_DIR\u0026#34; -name \u0026#34;*.sql.gz\u0026#34; -mtime +$RETENTION_DAYS -delete echo \u0026#34;Database backup completed\u0026#34; 最佳实践和技巧 # 脚本编写规范 # 脚本模板 # #!/bin/bash # # Script Name: script_name.sh # Description: Brief description of what this script does # Author: Your Name # Date: YYYY-MM-DD # Version: 1.0 # # Usage: ./script_name.sh [options] [arguments] # # Examples: # ./script_name.sh --help # ./script_name.sh --verbose file.txt # # 设置严格模式 set -euo pipefail # 全局变量 readonly SCRIPT_NAME=$(basename \u0026#34;$0\u0026#34;) readonly SCRIPT_DIR=$(cd \u0026#34;$(dirname \u0026#34;${BASH_SOURCE[0]}\u0026#34;)\u0026#34; \u0026amp;\u0026amp; pwd) readonly LOG_FILE=\u0026#34;/var/log/${SCRIPT_NAME%.sh}.log\u0026#34; # 默认配置 VERBOSE=false DRY_RUN=false # 日志函数 log() { echo \u0026#34;[$(date +\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)] $*\u0026#34; | tee -a \u0026#34;$LOG_FILE\u0026#34; } error() { echo \u0026#34;[ERROR] $*\u0026#34; \u0026gt;\u0026amp;2 exit 1 } # 帮助信息 usage() { cat \u0026lt;\u0026lt; EOF Usage: $SCRIPT_NAME [OPTIONS] [ARGUMENTS] Description: Brief description of the script functionality. Options: -h, --help Show this help message -v, --verbose Enable verbose output -n, --dry-run Show what would be done without executing Examples: $SCRIPT_NAME --help $SCRIPT_NAME --verbose file.txt EOF } # 参数解析 parse_args() { while [[ $# -gt 0 ]]; do case $1 in -h|--help) usage exit 0 ;; -v|--verbose) VERBOSE=true shift ;; -n|--dry-run) DRY_RUN=true shift ;; -*) error \u0026#34;Unknown option: $1\u0026#34; ;; *) # 位置参数 break ;; esac done } # 主函数 main() { parse_args \u0026#34;$@\u0026#34; log \u0026#34;Script started\u0026#34; # 脚本主要逻辑 log \u0026#34;Script completed\u0026#34; } # 清理函数 cleanup() { log \u0026#34;Cleaning up...\u0026#34; # 清理临时文件等 } # 信号处理 trap cleanup EXIT trap \u0026#39;error \u0026#34;Script interrupted\u0026#34;\u0026#39; INT TERM # 执行主函数 main \u0026#34;$@\u0026#34; 调试和错误处理 # 调试技巧 # # 启用调试模式 set -x # 显示执行的命令 set +x # 关闭调试模式 # 条件调试 if [ \u0026#34;$DEBUG\u0026#34; = \u0026#34;true\u0026#34; ]; then set -x fi # 函数调试 debug() { if [ \u0026#34;$VERBOSE\u0026#34; = \u0026#34;true\u0026#34; ]; then echo \u0026#34;[DEBUG] $*\u0026#34; \u0026gt;\u0026amp;2 fi } # 错误处理 handle_error() { local exit_code=$? local line_number=$1 echo \u0026#34;Error on line $line_number: exit code $exit_code\u0026#34; \u0026gt;\u0026amp;2 exit $exit_code } trap \u0026#39;handle_error $LINENO\u0026#39; ERR 输入验证 # # 验证参数数量 if [ $# -lt 2 ]; then echo \u0026#34;Error: At least 2 arguments required\u0026#34; usage exit 1 fi # 验证文件存在 validate_file() { local file=\u0026#34;$1\u0026#34; if [ ! -f \u0026#34;$file\u0026#34; ]; then error \u0026#34;File not found: $file\u0026#34; fi } # 验证目录存在 validate_directory() { local dir=\u0026#34;$1\u0026#34; if [ ! -d \u0026#34;$dir\u0026#34; ]; then error \u0026#34;Directory not found: $dir\u0026#34; fi } # 验证数字 validate_number() { local num=\u0026#34;$1\u0026#34; if ! [[ \u0026#34;$num\u0026#34; =~ ^[0-9]+$ ]]; then error \u0026#34;Invalid number: $num\u0026#34; fi } # 验证邮箱格式 validate_email() { local email=\u0026#34;$1\u0026#34; if ! [[ \u0026#34;$email\u0026#34; =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$ ]]; then error \u0026#34;Invalid email format: $email\u0026#34; fi } 性能优化 # 高效的文本处理 # # 使用内置命令替代外部命令 # 慢： result=$(echo \u0026#34;$string\u0026#34; | cut -c1-5) # 快： result=${string:0:5} # 避免不必要的子进程 # 慢： count=$(cat file | wc -l) # 快： count=$(wc -l \u0026lt; file) # 使用数组而不是多次调用 # 慢： for file in $(find /path -name \u0026#34;*.txt\u0026#34;); do process \u0026#34;$file\u0026#34; done # 快： mapfile -t files \u0026lt; \u0026lt;(find /path -name \u0026#34;*.txt\u0026#34;) for file in \u0026#34;${files[@]}\u0026#34;; do process \u0026#34;$file\u0026#34; done 总结 # 核心要点 # 基础扎实: 掌握文件操作、文本处理、进程管理等基础技能 脚本规范: 遵循编码规范，编写可维护的脚本 错误处理: 实现完善的错误处理和日志记录 安全意识: 注意权限管理和输入验证 性能优化: 选择高效的实现方式 学习建议 # 循序渐进: 从基础命令开始，逐步学习高级特性 实践为主: 通过实际项目练习和应用 阅读源码: 学习优秀的开源脚本 持续改进: 不断优化和重构现有脚本 参考资源 # 官方文档: Bash Manual 在线教程: Shell Scripting Tutorial 最佳实践: Google Shell Style Guide 工具推荐: ShellCheck（语法检查工具） 通过掌握这些 Shell 技能，您将能够高效地进行系统管理、自动化运维和日常开发工作。记住，Shell 编程的精髓在于实践，多写多练才能真正掌握这门强大的工具。\ngit{ git clone git@10.10.10.10:gittest.git ./gittest/ # 克隆项目到指定目录 git clone -b develop --depth=1 http://git.a.com/d.git # 克隆指定分支 克隆一层 git status # Show the working tree(工作树) status git log -n 1 --stat # 查看最后一次日志文件 git branch -a # 列出远程跟踪分支(remote-tracking branches)和本地分支 git checkout developing # 切换到developing分支 git checkout -b release # 切换分支没有从当前分支创建 git checkout -b release origin/master # 从远程分支创建本地镜像分支 git push origin --delete release # 从远端删除分区，服务端有可能设置保护不允许删除 git push origin release # 把本地分支提交到远程 git pull # 更新项目 需要cd到项目目录中 git fetch -f -p # 抓取远端代码但不合并到当前 git reset --hard origin/master # 和远端同步分支 git add . # 更新所有文件 git commit -m \u0026quot;gittest up\u0026quot; # 提交操作并添加备注 git push # 正式提交到远程git服务器 git push [-u origin master] # 正式提交到远程git服务器(master分支) git tag [-a] dev-v-0.11.54 [-m 'fix #67'] # 创建tag,名为dev-v-0.11.54,备注fix #67 git tag -l dev-v-0.11.54 # 查看tag(dev-v-0.11.5) git push origin --tags # 提交tag git reset --hard # 本地恢复整个项目 git rm -r -n --cached ./img # -n执行命令时,不会删除任何文件,而是展示此命令要删除的文件列表预览 git rm -r --cached ./img # 执行删除命令 需要commit和push让远程生效 git init --bare smc-content-check.git # 初始化新git项目 需要手动创建此目录并给git用户权限 chown -R git:git smc-content-check.git git config --global credential.helper store # 记住密码 git config [--global] user.name \u0026quot;your name\u0026quot; # 设置你的用户名, 希望在一个特定的项目中使用不同的用户或e-mail地址, 不要--global选项 git config [--global] user.email \u0026quot;your email\u0026quot; # 设置你的e-mail地址, 每次Git提交都会使用该信息 git config [--global] user.name # 查看用户名 git config [--global] user.email # 查看用户e-mail git config --global --edit # 编辑~/.gitconfig(User-specific)配置文件, 值优先级高于/etc/gitconfig(System-wide) git config --edit # 编辑.git/config(Repository specific)配置文件, 值优先级高于~/.gitconfig git cherry-pick \u0026lt;commit id\u0026gt; # 用于把另一个本地分支的commit修改应用到当前分支 需要push到远程 git log --pretty=format:'%h: %s' 9378b62..HEAD # 查看指定范围更新操作 commit id git config --global core.ignorecase false # 设置全局大小写敏感 git ls-remote --heads origin refs/heads/test # 查看 从远端拉一份新的{ # You have not concluded your merge (MERGE_HEAD exists) git拉取失败 git fetch --hard origin/master git reset --hard origin/master } 删除远程分支并新建{ git checkout master git branch -r -d origin/test # 删除远程分支 但有时候并没有删除 可以尝试使用下面的语句 git push origin :test # 推送一个空分支到远程分支，相当于删除远程分支 git branch -d test # 删除本地test分支, -D 强制 git branch -a |grep test git checkout -b test git push origin test git reset --hard origin/test } 迁移git项目{ git branch -r | grep -v '\\-\u0026gt;' | while read remote; do git branch --track \u0026quot;${remote#origin/}\u0026quot; \u0026quot;$remote\u0026quot;; done git fetch --all git pull --all git remote set-url origin git@git.github.cn:server/gw.git git push --all } } 恢复rm删除的文件{ # debugfs针对 ext2 # ext3grep针对 ext3 # extundelete针对 ext4 df -T # 首先查看磁盘分区格式 umount /data/ # 卸载挂载,数据丢失请首先卸载挂载,或重新挂载只读 ext3grep /dev/sdb1 --ls --inode 2 # 记录信息继续查找目录下文件inode信息 ext3grep /dev/sdb1 --ls --inode 131081 # 此处是inode ext3grep /dev/sdb1 --restore-inode 49153 # 记录下inode信息开始恢复目录 } openssl{ openssl rand 15 -base64 # 口令生成 openssl sha1 filename # 哈希算法校验文件 openssl md5 filename # MD5校验文件 openssl base64 filename.txt # base64编码/解码文件(发送邮件附件之类功能会可以使用) openssl base64 -d filename.bin # base64编码/解码二进制文件 openssl enc -aes-128-cbc filename.aes-128-cbc # 加密文档 # 推荐使用的加密算法是bf(Blowfish)和-aes-128-cbc(运行在CBC模式的128位密匙AES加密算法)，加密强度有保障 openssl enc -d -aes-128-cbc -in filename.aes-128-cbc \u0026gt; filename # 解密文档 } }\n2 软件{\nrpm{ rpm -ivh lynx # rpm安装 rpm -e lynx # 卸载包 rpm -e lynx --nodeps # 强制卸载 rpm -qa # 查看所有安装的rpm包 rpm -qa | grep lynx # 查找包是否安装 rpm -ql # 软件包路径 rpm -Uvh # 升级包 rpm --test lynx # 测试 rpm -qc # 软件包配置文档 rpm --initdb # 初始化rpm 数据库 rpm --rebuilddb # 重建rpm数据库 在rpm和yum无响应的情况使用 先 rm -f /var/lib/rpm/__db.00* 在重建 } yum{ yum list # 所有软件列表 yum install 包名 # 安装包和依赖包 yum -y update # 升级所有包版本,依赖关系，系统版本内核都升级 yum -y update 软件包名 # 升级指定的软件包 yum -y upgrade # 不改变软件设置更新软件，系统版本升级，内核不改变 yum search mail # yum搜索相关包 yum grouplist # 软件包组 yum -y groupinstall \u0026quot;Virtualization\u0026quot; # 安装软件包组 repoquery -ql gstreamer # 不安装软件查看包含文件 yum clean all # 清除var下缓存 } yum使用epel源{ # 包下载地址: http://download.fedoraproject.org/pub/epel # 选择版本5\\6\\7 rpm -Uvh http://mirrors.hustunique.com/epel//6/x86_64/epel-release-6-8.noarch.rpm # 自适配版本 yum install epel-release } 自定义yum源{ find /etc/yum.repos.d -name \u0026quot;*.repo\u0026quot; -exec mv {} {}.bak \\; vim /etc/yum.repos.d/yum.repo [yum] #http baseurl=http://10.0.0.1/centos5.5 #挂载iso #mount -o loop CentOS-5.8-x86_64-bin-DVD-1of2.iso /data/iso/ #本地 #baseurl=file:///data/iso/ enable=1 #导入key rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5 } 编译{ 源码安装{ ./configure --help # 查看所有编译参数 ./configure --prefix=/usr/local/ # 配置参数 make # 编译 # make -j 8 # 多线程编译,速度较快,但有些软件不支持 make install # 安装包 make clean # 清除编译结果 } perl程序编译{ perl Makefile.PL make make test make install } python程序编译{ python file.py # 源码包编译安装 python setup.py build python setup.py install } 编译c程序{ gcc -g hello.c -o hello } } }\n3 系统{\nwall # 给其它用户发消息 whereis ls # 搜索程序名，而且只搜索二进制文件 which # 查找命令是否存在,及存放位置 locate # 不是实时查找，查找的结果不精确，但查找速度很快 每天更新 /var/lib/locatedb clear # 清空整个屏幕 reset # 重新初始化屏幕 cal # 显示月历 echo -n 123456 | md5sum # md5加密 mkpasswd # 随机生成密码 -l位数 -C大小 -c小写 -d数字 -s特殊字符 netstat -ntupl | grep port # 是否打开了某个端口 ntpdate cn.pool.ntp.org # 同步时间, pool.ntp.org: public ntp time server for everyone(http://www.pool.ntp.org/zh/) tzselect # 选择时区 #+8=(5 9 1 1) # (TZ='Asia/Shanghai'; export TZ)括号内写入 /etc/profile /sbin/hwclock -w # 时间保存到硬件 /etc/shadow # 账户影子文件 LANG=en # 修改语言 vim /etc/sysconfig/i18n # 修改编码 LANG=\u0026quot;en_US.UTF-8\u0026quot; export LC_ALL=C # 强制字符集 vi /etc/hosts # 查询静态主机名 alias # 别名 watch uptime # 监测命令动态刷新 监视 ipcs -a # 查看Linux系统当前单个共享内存段的最大值 ldconfig # 动态链接库管理命令 ldd `which cmd` # 查看命令的依赖库 dist-upgrade # 会改变配置文件,改变旧的依赖关系，改变系统版本 /boot/grub/grub.conf # grub启动项配置 ps -mfL \u0026lt;PID\u0026gt; # 查看指定进程启动的线程 线程数受 max user processes 限制 ps uxm |wc -l # 查看当前用户占用的进程数 [包括线程] max user processes top -p PID -H # 查看指定PID进程及线程 lsof |wc -l # 查看当前文件句柄数使用数量 open files lsof |grep /lib # 查看加载库文件 sysctl -a # 查看当前所有系统内核参数 sysctl -p # 修改内核参数/etc/sysctl.conf，让/etc/rc.d/rc.sysinit读取生效 strace -p pid # 跟踪系统调用 ps -eo \u0026quot;%p %C %z %a\u0026quot;|sort -k3 -n # 把进程按内存使用大小排序 strace uptime 2\u0026gt;\u0026amp;1|grep open # 查看命令打开的相关文件 grep Hugepagesize /proc/meminfo # 内存分页大小 mkpasswd -l 8 -C 2 -c 2 -d 4 -s 0 # 随机生成指定类型密码 echo 1 \u0026gt; /proc/sys/net/ipv4/tcp_syncookies # 使TCP SYN Cookie 保护生效 # \u0026quot;SYN Attack\u0026quot;是一种拒绝服务的攻击方式 grep Swap /proc/25151/smaps |awk '{a+=$2}END{print a}' # 查询某pid使用的swap大小 redir --lport=33060 --caddr=10.10.10.78 --cport=3306 # 端口映射 yum安装 用supervisor守护 开机启动脚本顺序{ /etc/profile /etc/profile.d/*.sh ~/bash_profile ~/.bashrc /etc/bashrc } 进程管理{ ps -eaf # 查看所有进程 kill -9 PID # 强制终止某个PID进程 kill -15 PID # 安全退出 需程序内部处理信号 cmd \u0026amp; # 命令后台运行 nohup cmd \u0026amp; # 后台运行不受shell退出影响 ctrl+z # 将前台放入后台(暂停) jobs # 查看后台运行程序 bg 2 # 启动后台暂停进程 fg 2 # 调回后台进程 pstree # 进程树 vmstat 1 9 # 每隔一秒报告系统性能信息9次 sar # 查看cpu等状态 lsof file # 显示打开指定文件的所有进程 lsof -i:32768 # 查看端口的进程 renice +1 180 # 把180号进程的优先级加1 exec sh a.sh # 子进程替换原来程序的pid， 避免supervisor无法强制杀死进程 ps{ ps aux |grep -v USER | sort -nk +4 | tail # 显示消耗内存最多的10个运行中的进程，以内存使用量排序.cpu +3 # USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND %CPU # 进程的cpu占用率 %MEM # 进程的内存占用率 VSZ # 进程虚拟大小,单位K(即总占用内存大小,包括真实内存和虚拟内存) RSS # 进程使用的驻留集大小即实际物理内存大小 START # 进程启动时间和日期 占用的虚拟内存大小 = VSZ - RSS ps -eo pid,lstart,etime,args # 查看进程启动时间 } top{ 前五行是系统整体的统计信息。 第一行: 任务队列信息，同 uptime 命令的执行结果。内容如下： 01:06:48 当前时间 up 1:22 系统运行时间，格式为时:分 1 user 当前登录用户数 load average: 0.06, 0.60, 0.48 系统负载，即任务队列的平均长度。 三个数值分别为 1分钟、5分钟、15分钟前到现在的平均值。 第二、三行:为进程和CPU的信息。当有多个CPU时，这些内容可能会超过两行。内容如下： Tasks: 29 total 进程总数 1 running 正在运行的进程数 28 sleeping 睡眠的进程数 0 stopped 停止的进程数 0 zombie 僵尸进程数 Cpu(s): 0.3% us 用户空间占用CPU百分比 1.0% sy 内核空间占用CPU百分比 0.0% ni 用户进程空间内改变过优先级的进程占用CPU百分比 98.7% id 空闲CPU百分比 0.0% wa 等待输入输出的CPU时间百分比 0.0% hi 0.0% si 第四、五行:为内存信息。内容如下： Mem: 191272k total 物理内存总量 173656k used 使用的物理内存总量 17616k free 空闲内存总量 22052k buffers 用作内核缓存的内存量 Swap: 192772k total 交换区总量 0k used 使用的交换区总量 192772k free 空闲交换区总量 123988k cached 缓冲的交换区总量。 内存中的内容被换出到交换区，而后又被换入到内存，但使用过的交换区尚未被覆盖， 该数值即为这些内容已存在于内存中的交换区的大小。 相应的内存再次被换出时可不必再对交换区写入。 进程信息区,各列的含义如下: # 显示各个进程的详细信息 序号 列名 含义 a PID 进程id b PPID 父进程id c RUSER Real user name d UID 进程所有者的用户id e USER 进程所有者的用户名 f GROUP 进程所有者的组名 g TTY 启动进程的终端名。不是从终端启动的进程则显示为 ? h PR 优先级 i NI nice值。负值表示高优先级，正值表示低优先级 j P 最后使用的CPU，仅在多CPU环境下有意义 k %CPU 上次更新到现在的CPU时间占用百分比 l TIME 进程使用的CPU时间总计，单位秒 m TIME+ 进程使用的CPU时间总计，单位1/100秒 n %MEM 进程使用的物理内存百分比 o VIRT 进程使用的虚拟内存总量，单位kb。VIRT=SWAP+RES p SWAP 进程使用的虚拟内存中，被换出的大小，单位kb。 q RES 进程使用的、未被换出的物理内存大小，单位kb。RES=CODE+DATA r CODE 可执行代码占用的物理内存大小，单位kb s DATA 可执行代码以外的部分(数据段+栈)占用的物理内存大小，单位kb t SHR 共享内存大小，单位kb u nFLT 页面错误次数 v nDRT 最后一次写入到现在，被修改过的页面数。 w S 进程状态。 D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程 父进程在但并不等待子进程 x COMMAND 命令名/命令行 y WCHAN 若该进程在睡眠，则显示睡眠中的系统函数名 z Flags 任务标志，参考 sched.h } 列出正在占用swap的进程{ #!/bin/bash echo -e \u0026quot;PID\\t\\tSwap\\t\\tProc_Name\u0026quot; # 拿出/proc目录下所有以数字为名的目录（进程名是数字才是进程，其他如sys,net等存放的是其他信息） for pid in `ls -l /proc | grep ^d | awk '{ print $9 }'| grep -v [^0-9]` do # 让进程释放swap的方法只有一个：就是重启该进程。或者等其自动释放。放 # 如果进程会自动释放，那么我们就不会写脚本来找他了，找他都是因为他没有自动释放。 # 所以我们要列出占用swap并需要重启的进程，但是init这个进程是系统里所有进程的祖先进程 # 重启init进程意味着重启系统，这是万万不可以的，所以就不必检测他了，以免对系统造成影响。 if [ $pid -eq 1 ];then continue;fi grep -q \u0026quot;Swap\u0026quot; /proc/$pid/smaps 2\u0026gt;/dev/null if [ $? -eq 0 ];then swap=$(grep Swap /proc/$pid/smaps \\ | gawk '{ sum+=$2;} END{ print sum }') proc_name=$(ps aux | grep -w \u0026quot;$pid\u0026quot; | grep -v grep \\ | awk '{ for(i=11;i\u0026lt;=NF;i++){ printf(\u0026quot;%s \u0026quot;,$i); }}') if [ $swap -gt 0 ];then echo -e \u0026quot;${pid}\\t${swap}\\t${proc_name}\u0026quot; fi fi done | sort -k2 -n | awk -F'\\t' '{ pid[NR]=$1; size[NR]=$2; name[NR]=$3; } END{ for(id=1;id\u0026lt;=length(pid);id++) { if(size[id]\u0026lt;1024) printf(\u0026quot;%-10s\\t%15sKB\\t%s\\n\u0026quot;,pid[id],size[id],name[id]); else if(size[id]\u0026lt;1048576) printf(\u0026quot;%-10s\\t%15.2fMB\\t%s\\n\u0026quot;,pid[id],size[id]/1024,name[id]); else printf(\u0026quot;%-10s\\t%15.2fGB\\t%s\\n\u0026quot;,pid[id],size[id]/1048576,name[id]); } }' } linux操作系统提供的信号{ kill -l # 查看linux提供的信号 trap \u0026quot;echo aaa\u0026quot; 2 3 15 # shell使用 trap 捕捉退出信号 # 发送信号一般有两种原因: # 1(被动式) 内核检测到一个系统事件.例如子进程退出会像父进程发送SIGCHLD信号.键盘按下control+c会发送SIGINT信号 # 2(主动式) 通过系统调用kill来向指定进程发送信号 # 进程结束信号 SIGTERM 和 SIGKILL 的区别: SIGTERM 比较友好，进程能捕捉这个信号，根据您的需要来关闭程序。在关闭程序之前，您可以结束打开的记录文件和完成正在做的任务。在某些情况下，假如进程正在进行作业而且不能中断，那么进程可以忽略这个SIGTERM信号。 # 如果一个进程收到一个SIGUSR1信号，然后执行信号绑定函数，第二个SIGUSR2信号又来了，第一个信号没有被处理完毕的话，第二个信号就会丢弃。 SIGHUP 1 A # 终端挂起或者控制进程终止 SIGINT 2 A # 键盘终端进程(如control+c) SIGQUIT 3 C # 键盘的退出键被按下 SIGILL 4 C # 非法指令 SIGABRT 6 C # 由abort(3)发出的退出指令 SIGFPE 8 C # 浮点异常 SIGKILL 9 AEF # Kill信号 立刻停止 SIGSEGV 11 C # 无效的内存引用 SIGPIPE 13 A # 管道破裂: 写一个没有读端口的管道 SIGALRM 14 A # 闹钟信号 由alarm(2)发出的信号 SIGTERM 15 A # 终止信号,可让程序安全退出 kill -15 SIGUSR1 30,10,16 A # 用户自定义信号1 SIGUSR2 31,12,17 A # 用户自定义信号2 SIGCHLD 20,17,18 B # 子进程结束自动向父进程发送SIGCHLD信号 SIGCONT 19,18,25 # 进程继续（曾被停止的进程） SIGSTOP 17,19,23 DEF # 终止进程 SIGTSTP 18,20,24 D # 控制终端（tty）上按下停止键 SIGTTIN 21,21,26 D # 后台进程企图从控制终端读 SIGTTOU 22,22,27 D # 后台进程企图从控制终端写 缺省处理动作一项中的字母含义如下: A 缺省的动作是终止进程 B 缺省的动作是忽略此信号，将该信号丢弃，不做处理 C 缺省的动作是终止进程并进行内核映像转储(dump core),内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统，并且进程退出执行，这样做的好处是为程序员提供了方便，使得他们可以得到进程当时执行时的数据值，允许他们确定转储的原因，并且可以调试他们的程序。 D 缺省的动作是停止进程，进入停止状况以后还能重新进行下去，一般是在调试的过程中（例如ptrace系统调用） E 信号不能被捕获 F 信号不能被忽略 } 系统性能状态{ vmstat 1 9 r # 等待执行的任务数。当这个值超过了cpu线程数，就会出现cpu瓶颈。 b # 等待IO的进程数量,表示阻塞的进程。 swpd # 虚拟内存已使用的大小，如大于0，表示机器物理内存不足，如不是程序内存泄露，那么该升级内存。 free # 空闲的物理内存的大小 buff # 已用的buff大小，对块设备的读写进行缓冲 cache # cache直接用来记忆我们打开的文件,给文件做缓冲，(把空闲的物理内存的一部分拿来做文件和目录的缓存，是为了提高 程序执行的性能，当程序使用内存时，buffer/cached会很快地被使用。) inact # 非活跃内存大小，即被标明可回收的内存，区别于free和active -a选项时显示 active # 活跃的内存大小 -a选项时显示 si # 每秒从磁盘读入虚拟内存的大小，如果这个值大于0，表示物理内存不够用或者内存泄露，要查找耗内存进程解决掉。 so # 每秒虚拟内存写入磁盘的大小，如果这个值大于0，同上。 bi # 块设备每秒接收的块数量，这里的块设备是指系统上所有的磁盘和其他块设备，默认块大小是1024byte bo # 块设备每秒发送的块数量，例如读取文件，bo就要大于0。bi和bo一般都要接近0，不然就是IO过于频繁，需要调整。 in # 每秒CPU的中断次数，包括时间中断。in和cs这两个值越大，会看到由内核消耗的cpu时间会越多 cs # 每秒上下文切换次数，例如我们调用系统函数，就要进行上下文切换，线程的切换，也要进程上下文切换，这个值要越小越好，太大了，要考虑调低线程或者进程的数目,例如在apache和nginx这种web服务器中，我们一般做性能测试时会进行几千并发甚至几万并发的测试，选择web服务器的进程可以由进程或者线程的峰值一直下调，压测，直到cs到一个比较小的值，这个进程和线程数就是比较合适的值了。系统调用也是，每次调用系统函数，我们的代码就会进入内核空间，导致上下文切换，这个是很耗资源，也要尽量避免频繁调用系统函数。上下文切换次数过多表示你的CPU大部分浪费在上下文切换，导致CPU干正经事的时间少了，CPU没有充分利用。 us # 用户进程执行消耗cpu时间(user time) us的值比较高时，说明用户进程消耗的cpu时间多，但是如果长期超过50%的使用，那么我们就该考虑优化程序算法或其他措施 sy # 系统CPU时间，如果太高，表示系统调用时间长，例如是IO操作频繁。 id # 空闲 CPU时间，一般来说，id + us + sy = 100,一般认为id是空闲CPU使用率，us是用户CPU使用率，sy是系统CPU使用率。 wt # 等待IOCPU时间。Wa过高时，说明io等待比较严重，这可能是由于磁盘大量随机访问造成的，也有可能是磁盘的带宽出现瓶颈。 如果 r 经常大于4，且id经常少于40，表示cpu的负荷很重。 如果 pi po 长期不等于0，表示内存不足。 如果 b 队列经常大于3，表示io性能不好。 } } 日志管理{ history # 历时命令默认1000条 HISTTIMEFORMAT=\u0026quot;%Y-%m-%d %H:%M:%S \u0026quot; # 让history命令显示具体时间 history -c # 清除记录命令 cat $HOME/.bash_history # 历史命令记录文件 lastb -a # 列出登录系统失败的用户相关信息 清空二进制日志记录文件 echo \u0026gt; /var/log/btmp last # 查看登陆过的用户信息 清空二进制日志记录文件 echo \u0026gt; /var/log/wtmp 默认打开乱码 who /var/log/wtmp # 查看登陆过的用户信息 lastlog # 用户最后登录的时间 tail -f /var/log/messages # 系统日志 tail -f /var/log/secure # ssh日志 } man{ man 2 read # 查看read函数的文档 1 使用者在shell中可以操作的指令或可执行档 2 系统核心可呼叫的函数与工具等 3 一些常用的函数(function)与函数库(library),大部分是C的函数库(libc) 4 装置档案的说明，通常在/dev下的档案 5 设定档或者是某些档案的格式 6 游戏games 7 惯例与协定等，例如linux档案系统、网络协定、ascll code等说明 8 系统管理员可用的管理指令 9 跟kernel有关的文件 } selinux{ sestatus -v # 查看selinux状态 getenforce # 查看selinux模式 setenforce 0 # 设置selinux为宽容模式(可避免阻止一些操作) semanage port -l # 查看selinux端口限制规则 semanage port -a -t http_port_t -p tcp 8000 # 在selinux中注册端口类型 vi /etc/selinux/config # selinux配置文件 SELINUX=enfoceing # 关闭selinux 把其修改为 SELINUX=disabled } 查看剩余内存{ free -m #-/+ buffers/cache: 6458 1649 #6458M为真实使用内存 1649M为真实剩余内存(剩余内存+缓存+缓冲器) #linux会利用所有的剩余内存作为缓存，所以要保证linux运行速度，就需要保证内存的缓存大小 } 系统信息{ uname -a # 查看Linux内核版本信息 cat /proc/version # 查看内核版本 cat /etc/issue # 查看系统版本 lsb_release -a # 查看系统版本 需安装 centos-release locale -a # 列出所有语系 locale # 当前环境变量中所有编码 hwclock # 查看时间 who # 当前在线用户 w # 当前在线用户 whoami # 查看当前用户名 logname # 查看初始登陆用户名 uptime # 查看服务器启动时间 sar -n DEV 1 10 # 查看网卡网速流量 dmesg # 显示开机信息 lsmod # 查看内核模块 } 硬件信息{ more /proc/cpuinfo # 查看cpu信息 lscpu # 查看cpu信息 cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c # 查看cpu型号和逻辑核心数 getconf LONG_BIT # cpu运行的位数 cat /proc/cpuinfo | grep 'physical id' |sort| uniq -c # 物理cpu个数 cat /proc/cpuinfo | grep flags | grep ' lm ' | wc -l # 结果大于0支持64位 cat /proc/cpuinfo|grep flags # 查看cpu是否支持虚拟化 pae支持半虚拟化 IntelVT 支持全虚拟化 more /proc/meminfo # 查看内存信息 dmidecode # 查看全面硬件信息 dmidecode | grep \u0026quot;Product Name\u0026quot; # 查看服务器型号 dmidecode | grep -P -A5 \u0026quot;Memory\\s+Device\u0026quot; | grep Size | grep -v Range # 查看内存插槽 cat /proc/mdstat # 查看软raid信息 cat /proc/scsi/scsi # 查看Dell硬raid信息(IBM、HP需要官方检测工具) lspci # 查看硬件信息 lspci|grep RAID # 查看是否支持raid lspci -vvv |grep Ethernet # 查看网卡型号 lspci -vvv |grep Kernel|grep driver # 查看驱动模块 modinfo tg2 # 查看驱动版本(驱动模块) ethtool -i em1 # 查看网卡驱动版本 ethtool em1 # 查看网卡带宽 } 终端快捷键{ Ctrl+A # 行前 Ctrl+E # 行尾 Ctrl+S # 终端锁屏 Ctrl+Q # 解锁屏 Ctrl+D # 退出 } 开机启动模式{ vi /etc/inittab id:3:initdefault: # 3为多用户命令 #ca::ctrlaltdel:/sbin/shutdown -t3 -r now # 注释此行 禁止 ctrl+alt+del 关闭计算机 } 终端提示显示{ echo $PS1 # 环境变量控制提示显示 PS1='[\\u@ \\H \\w \\A \\@#]\\$' PS1='[\\u@\\h \\W]\\$' export PS1='[\\[\\e[32m\\]\\[\\e[31m\\]\\u@\\[\\e[36m\\]\\h \\w\\[\\e[m\\]]\\$ ' # 高亮显示终端 } 定时任务{ at 5pm + 3 days /bin/ls # 单次定时任务 指定三天后下午5:00执行/bin/ls crontab -e # 编辑周期任务 #分钟 小时 天 月 星期 命令或脚本 1,30 1-3/2 * * * 命令或脚本 \u0026gt;\u0026gt; file.log 2\u0026gt;\u0026amp;1 echo \u0026quot;40 7 * * 2 /root/sh\u0026quot;\u0026gt;\u0026gt;/var/spool/cron/work # 普通用户可直接写入定时任务 crontab -l # 查看自动周期性任务 crontab -r # 删除自动周期性任务 cron.deny和cron.allow # 禁止或允许用户使用周期任务 service crond start|stop|restart # 启动自动周期性服务 * * * * * echo \u0026quot;d\u0026quot; \u0026gt;\u0026gt;d$(date +\\%Y\\%m\\%d).log # 让定时任务直接生成带日期的log 需要转义% } date{ 星期日[SUN] 星期一[MON] 星期二[TUE] 星期三[WED] 星期四[THU] 星期五[FRI] 星期六[SAT] 一月[JAN] 二月[FEB] 三月[MAR] 四月[APR] 五月[MAY] 六月[JUN] 七月[JUL] 八月[AUG] 九月[SEP] 十月[OCT] 十一月[NOV] 十二月[DEC] date -s 20091112 # 设日期 date -s 18:30:50 # 设时间 date -d \u0026quot;7 days ago\u0026quot; +%Y%m%d # 7天前日期 date -d \u0026quot;5 minute ago\u0026quot; +%H:%M # 5分钟前时间 date -d \u0026quot;1 month ago\u0026quot; +%Y%m%d # 一个月前 date -d '1 days' +%Y-%m-%d # 一天后 date -d '1 hours' +%H:%M:%S # 一小时后 date +%Y-%m-%d -d '20110902' # 日期格式转换 date +%Y-%m-%d_%X # 日期和时间 date +%N # 纳秒 date -d \u0026quot;2012-08-13 14:00:23\u0026quot; +%s # 换算成秒计算(1970年至今的秒数) date -d \u0026quot;@1363867952\u0026quot; +%Y-%m-%d-%T # 将时间戳换算成日期 date -d \u0026quot;1970-01-01 UTC 1363867952 seconds\u0026quot; +%Y-%m-%d-%T # 将时间戳换算成日期 date -d \u0026quot;`awk -F. '{print $1}' /proc/uptime` second ago\u0026quot; +\u0026quot;%Y-%m-%d %H:%M:%S\u0026quot; # 格式化系统启动时间(多少秒前) } limits.conf{ ulimit -SHn 65535 # 临时设置文件描述符大小 进程最大打开文件柄数 还有socket最大连接数, 等同配置 nofile ulimit -SHu 65535 # 临时设置用户最大进程数 ulimit -a # 查看 /etc/security/limits.conf # 文件描述符大小 open files # lsof |wc -l 查看当前文件句柄数使用数量 * soft nofile 16384 # 设置太大，进程使用过多会把机器拖死 * hard nofile 32768 # 用户最大进程数 max user processes # echo $((`ps uxm |wc -l`-`ps ux |wc -l`)) 查看当前用户占用的进程数 [包括线程] user soft nproc 16384 user hard nproc 32768 # 如果/etc/security/limits.d/有配置文件，将会覆盖/etc/security/limits.conf里的配置 # 即/etc/security/limits.d/的配置文件里就不要有同样的参量设置 /etc/security/limits.d/90-nproc.conf # centos6.3的默认这个文件会覆盖 limits.conf user soft nproc 16384 user hard nproc 32768 sysctl -p # 修改配置文件后让系统生效 } 随机分配端口范围{ # 本机连其它端口用的 echo \u0026quot;10000 65535\u0026quot; \u0026gt; /proc/sys/net/ipv4/ip_local_port_range } 百万长链接设置{ # 内存消耗需要较大 vim /root/.bash_profile # 添加如下2行,退出bash重新登陆 # 一个进程不能使用超过NR_OPEN文件描述符 echo 20000500 \u0026gt; /proc/sys/fs/nr_open # 当前用户最大文件数 ulimit -n 10000000 } core崩溃文件查看{ gdb core.13844 bt # 查看函数调用信息(堆栈) } libc.so故障修复{ # 由于升级glibc导致libc.so不稳定,突然报错,幸好还有未退出的终端 grep: error while loading shared libraries: /lib64/libc.so.6: ELF file OS ABI invalid # 看看当前系统有多少版本 libc.so ls /lib64/libc-[tab] # 更改环境变量指向其他 libc.so 文件测试 export LD_PRELOAD=/lib64/libc-2.7.so # 如果不改变LD_PRELOAD变量,ln不能用,需要使用 /sbin/sln 命令做链接 # 当前如果好使了，在执行下面强制替换软链接。如不好使，测试其他版本的libc.so文件 ln -f -s /lib64/libc-2.7.so /lib64/libc.so.6 } 无法分配内存 { fork: Cannot allocate memory # 报错不一定是内存不够用，进程数或者线程数满了也会报这个错误， 可以适当增加 kernel.pid_max 的值， cat /proc/sys/kernel/pid_max # 默认3.2w } sudo{ echo myPassword | sudo -S ls /tmp # 直接输入sudo的密码非交互,从标准输入读取密码而不是终端设备 visudo # sudo命令权限添加 /etc/sudoers 用户 别名(可用all)=NOPASSWD:命令1,命令2 user ALL=NOPASSWD:/bin/su # 免root密码切换root身份 wangming linuxfan=NOPASSWD:/sbin/apache start,/sbin/apache restart UserName ALL=(ALL) ALL UserName ALL=(ALL) NOPASSWD: ALL peterli ALL=(ALL) NOPASSWD:/sbin/service Defaults requiretty # sudo不允许后台运行,注释此行既允许 Defaults !visiblepw # sudo不允许远程,去掉!既允许 } grub开机启动项添加{ vim /etc/grub.conf title ms-dos rootnoverify (hd0,0) chainloader +1 } stty{ #stty时一个用来改变并打印终端行设置的常用命令 stty iuclc # 在命令行下禁止输出大写 stty -iuclc # 恢复输出大写 stty olcuc # 在命令行下禁止输出小写 stty -olcuc # 恢复输出小写 stty size # 打印出终端的行数和列数 stty eof \u0026quot;string\u0026quot; # 改变系统默认ctrl+D来表示文件的结束 stty -echo # 禁止回显 stty echo # 打开回显 stty -echo;read;stty echo;read # 测试禁止回显 stty igncr # 忽略回车符 stty -igncr # 恢复回车符 stty erase '#' # 将#设置为退格字符 stty erase '^?' # 恢复退格字符 定时输入{ timeout_read(){ timeout=$1 old_stty_settings=`stty -g`　# save current settings stty -icanon min 0 time 100　# set 10seconds,not 100seconds eval read varname　# =read $varname stty \u0026quot;$old_stty_settings\u0026quot;　# recover settings } read -t 10 varname # 更简单的方法就是利用read命令的-t选项 } 检测用户按键{ #!/bin/bash old_tty_settings=$(stty -g) # 保存老的设置(为什么?). stty -icanon Keypress=$(head -c1) # 或者使用$(dd bs=1 count=1 2\u0026gt; /dev/null) echo \u0026quot;Key pressed was \\\u0026quot;\u0026quot;$Keypress\u0026quot;\\\u0026quot;.\u0026quot; stty \u0026quot;$old_tty_settings\u0026quot; # 恢复老的设置. exit 0 } } iptables{ 内建三个表：nat mangle 和 filter filter预设规则表，有INPUT、FORWARD 和 OUTPUT 三个规则链 vi /etc/sysconfig/iptables # 配置文件 INPUT # 进入 FORWARD # 转发 OUTPUT # 出去 ACCEPT # 将封包放行 REJECT # 拦阻该封包 DROP # 丢弃封包不予处理 -A # 在所选择的链(INPUT等)末添加一条或更多规则 -D # 删除一条 -E # 修改 -p # tcp、udp、icmp 0相当于所有all !取反 -P # 设置缺省策略(与所有链都不匹配强制使用此策略) -s # IP/掩码 (IP/24) 主机名、网络名和清楚的IP地址 !取反 -j # 目标跳转，立即决定包的命运的专用内建目标 -i # 进入的（网络）接口 [名称] eth0 -o # 输出接口[名称] -m # 模块 --sport # 源端口 --dport # 目标端口 iptables -F # 将防火墙中的规则条目清除掉 # 注意: iptables -P INPUT ACCEPT iptables-restore \u0026lt; 规则文件 # 导入防火墙规则 /etc/init.d/iptables save # 保存防火墙设置 /etc/init.d/iptables restart # 重启防火墙服务 iptables -L -n # 查看规则 iptables -t nat -nL # 查看转发 iptables实例{ iptables -L INPUT # 列出某规则链中的所有规则 iptables -X allowed # 删除某个规则链 ,不加规则链，清除所有非内建的 iptables -Z INPUT # 将封包计数器归零 iptables -N allowed # 定义新的规则链 iptables -P INPUT DROP # 定义过滤政策 iptables -A INPUT -s 192.168.1.1 # 比对封包的来源IP # ! 192.168.0.0/24 ! 反向对比 iptables -A INPUT -d 192.168.1.1 # 比对封包的目的地IP iptables -A INPUT -i eth0 # 比对封包是从哪片网卡进入 iptables -A FORWARD -o eth0 # 比对封包要从哪片网卡送出 eth+表示所有的网卡 iptables -A INPUT -p tcp # -p ! tcp 排除tcp以外的udp、icmp。-p all所有类型 iptables -D INPUT 8 # 从某个规则链中删除一条规则 iptables -D INPUT --dport 80 -j DROP # 从某个规则链中删除一条规则 iptables -R INPUT 8 -s 192.168.0.1 -j DROP # 取代现行规则 iptables -I INPUT 8 --dport 80 -j ACCEPT # 插入一条规则 iptables -A INPUT -i eth0 -j DROP # 其它情况不允许 iptables -A INPUT -p tcp -s IP -j DROP # 禁止指定IP访问 iptables -A INPUT -p tcp -s IP --dport port -j DROP # 禁止指定IP访问端口 iptables -A INPUT -s IP -p tcp --dport port -j ACCEPT # 允许在IP访问指定端口 iptables -A INPUT -p tcp --dport 22 -j DROP # 禁止使用某端口 iptables -A INPUT -i eth0 -p icmp -m icmp --icmp-type 8 -j DROP # 禁止icmp端口 iptables -A INPUT -i eth0 -p icmp -j DROP # 禁止icmp端口 iptables -t filter -A INPUT -i eth0 -p tcp --syn -j DROP # 阻止所有没有经过你系统授权的TCP连接 iptables -A INPUT -f -m limit --limit 100/s --limit-burst 100 -j ACCEPT # IP包流量限制 iptables -A INPUT -i eth0 -s 192.168.62.1/32 -p icmp -m icmp --icmp-type 8 -j ACCEPT # 除192.168.62.1外，禁止其它人ping我的主机 iptables -A INPUT -p tcp -m tcp --dport 80 -m state --state NEW -m recent --update --seconds 5 --hitcount 20 --rttl --name WEB --rsource -j DROP # 可防御cc攻击(未测试) } iptables配置实例文件{ # Generated by iptables-save v1.2.11 on Fri Feb 9 12:10:37 2007 *filter :INPUT ACCEPT [637:58967] :FORWARD DROP [0:0] :OUTPUT ACCEPT [5091:1301533] # 允许的IP或IP段访问 建议多个 -A INPUT -s 127.0.0.1 -p tcp -j ACCEPT -A INPUT -s 192.168.0.0/255.255.0.0 -p tcp -j ACCEPT # 开放对外开放端口 -A INPUT -p tcp --dport 80 -j ACCEPT # 指定某端口针对IP开放 -A INPUT -s 192.168.10.37 -p tcp --dport 22 -j ACCEPT # 拒绝所有协议(INPUT允许) -A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,URG RST -j DROP # 允许已建立的或相关连的通行 -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 拒绝ping -A INPUT -p tcp -m tcp -j REJECT --reject-with icmp-port-unreachable COMMIT # Completed on Fri Feb 9 12:10:37 2007 } iptables配置实例{ # 允许某段IP访问任何端口 iptables -A INPUT -s 192.168.0.3/24 -p tcp -j ACCEPT # 设定预设规则 (拒绝所有的数据包，再允许需要的,如只做WEB服务器.还是推荐三个链都是DROP) iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # 注意: 直接设置这三条会掉线 # 开启22端口 iptables -A INPUT -p tcp --dport 22 -j ACCEPT # 如果OUTPUT 设置成DROP的，要写上下面一条 iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT # 注:不写导致无法SSH.其他的端口一样,OUTPUT设置成DROP的话,也要添加一条链 # 如果开启了web服务器,OUTPUT设置成DROP的话,同样也要添加一条链 iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT # 做WEB服务器,开启80端口 ,其他同理 iptables -A INPUT -p tcp --dport 80 -j ACCEPT # 做邮件服务器,开启25,110端口 iptables -A INPUT -p tcp --dport 110 -j ACCEPT iptables -A INPUT -p tcp --dport 25 -j ACCEPT # 允许icmp包通过,允许ping iptables -A OUTPUT -p icmp -j ACCEPT (OUTPUT设置成DROP的话) iptables -A INPUT -p icmp -j ACCEPT (INPUT设置成DROP的话) # 允许loopback!(不然会导致DNS无法正常关闭等问题) IPTABLES -A INPUT -i lo -p all -j ACCEPT (如果是INPUT DROP) IPTABLES -A OUTPUT -o lo -p all -j ACCEPT(如果是OUTPUT DROP) } centos6的iptables基本配置{ *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -p icmp -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -s 222.186.135.61 -p tcp -j ACCEPT -A INPUT -p tcp --dport 80 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited -A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,URG RST -j DROP -A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT } 添加网段转发{ # 例如通过vpn上网 echo 1 \u0026gt; /proc/sys/net/ipv4/ip_forward # 在内核里打开ip转发功能 iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -j MASQUERADE # 添加网段转发 iptables -t nat -A POSTROUTING -s 10.0.0.0/255.0.0.0 -o eth0 -j SNAT --to 192.168.10.158 # 原IP网段经过哪个网卡IP出去 iptables -t nat -nL # 查看转发 } 端口映射{ # 内网通过有外网IP的机器映射端口 # 内网主机添加路由 route add -net 10.10.20.0 netmask 255.255.255.0 gw 10.10.20.111 # 内网需要添加默认网关，并且网关开启转发 # 网关主机 echo 1 \u0026gt; /proc/sys/net/ipv4/ip_forward # 在内核里打开ip转发功能 iptables -t nat -A PREROUTING -d 外网IP -p tcp --dport 9999 -j DNAT --to 10.10.20.55:22 # 进入 iptables -t nat -A POSTROUTING -s 10.10.20.0/24 -j SNAT --to 外网IP # 转发回去 iptables -t nat -nL # 查看转发 } } }\n4 服务{\n/etc/init.d/sendmail start # 启动服务 /etc/init.d/sendmail stop # 关闭服务 /etc/init.d/sendmail status # 查看服务当前状态 /date/mysql/bin/mysqld_safe --user=mysql \u0026amp; # 启动mysql后台运行 /bin/systemctl restart mysqld.service # centos7启动服务 vi /etc/rc.d/rc.local # 开机启动执行 可用于开机启动脚本 /etc/rc.d/rc3.d/S55sshd # 开机启动和关机关闭服务连接 # S开机start K关机stop 55级别 后跟服务名 ln -s -f /date/httpd/bin/apachectl /etc/rc.d/rc3.d/S15httpd # 将启动程序脚本连接到开机启动目录 ipvsadm -ln # lvs查看后端负载机并发 ipvsadm -C # lvs清除规则 xm list # 查看xen虚拟主机列表 virsh # 虚拟化(xen\\kvm)管理工具 yum groupinstall Virtual* ./bin/httpd -M # 查看httpd加载模块 httpd -t -D DUMP_MODULES # rpm包httpd查看加载模块 echo 内容| /bin/mail -s \u0026quot;标题\u0026quot; 收件箱 -f 发件人 # 发送邮件 \u0026quot;`echo \u0026quot;内容\u0026quot;|iconv -f utf8 -t gbk`\u0026quot; | /bin/mail -s \u0026quot;`echo \u0026quot;标题\u0026quot;|iconv -f utf8 -t gbk`\u0026quot; 收件箱 # 解决邮件乱码 /usr/local/nagios/bin/nagios -v /usr/local/nagios/etc/nagios.cfg # 检测nagios配置文件 chkconfig{ chkconfig service on|off|set # 设置非独立服务启状态 chkconfig --level 35 httpd off # 让服务不自动启动 chkconfig --level 35 httpd on # 让服务自动启动 35指的是运行级别 chkconfig --list # 查看所有服务的启动状态 chkconfig --list |grep httpd # 查看某个服务的启动状态 chkconfig –-list [service] # 查看服务的状态 } systemctl{ systemctl is-active *.service # 查看服务是否运行 systemctl is-enabled *.service # 查询服务是否开机启动 systemctl mask *.service # 注销指定服务 systemctl unmask cups.service # 取消注销cups服务 systemctl enable *.service # 开机运行服务 systemctl disable *.service # 取消开机运行 systemctl start *.service # 启动服务 systemctl stop *.service # 停止服务 systemctl restart *.service # 重启服务 systemctl reload *.service # 重新加载服务配置文件 systemctl status *.service # 查询服务运行状态 systemctl --failed # 显示启动失败的服务 systemctl poweroff # 系统关机 systemctl reboot # 重新启动 systemctl rescue # 强制进入救援模式 systemctl emergency # 强制进入紧急救援模式 systemctl list-dependencies # 查看当前运行级别target(mult-user)启动了哪些服务 systemctl list-unit-files # 查看开机启动的状态 journalctl -r -u elasticsearch.service # 查看日志 r倒序 u服务名 /etc/systemd/system/falcon-agent.service [Unit] Description=This is zuiyou monitor agent After=network.target remote-fs.target nss-lookup.target [Service] User= root Type=simple PIDFile=/opt/falcon-agent/var/app.pid ExecStartPre=/usr/bin/rm -f /opt/falcon-agent/var/app.pid ExecStart=/opt/falcon-agent/control start ExecReload=/bin/kill -s HUP $MAINPID KillMode=process KillSignal=SIGQUIT TimeoutStopSec=5 PrivateTmp=true Restart=always LimitNOFILE=infinity [Install] WantedBy=multi-user.target systemctl daemon-reload # 加载配置 } nginx{ yum install -y make gcc openssl-devel pcre-devel bzip2-devel libxml2 libxml2-devel curl-devel libmcrypt-devel libjpeg libjpeg-devel libpng libpng-devel openssl groupadd nginx useradd nginx -g nginx -M -s /sbin/nologin mkdir -p /opt/nginx-tmp wget http://labs.frickle.com/files/ngx_cache_purge-1.6.tar.gz tar fxz ngx_cache_purge-1.6.tar.gz # ngx_cache_purge 清除指定url缓存 # 假设一个URL为 http://192.168.12.133/test.txt # 通过访问 http://192.168.12.133/purge/test.txt 就可以清除该URL的缓存。 tar zxvpf nginx-1.4.4.tar.gz cd nginx-1.4.4 # ./configure --help # --with # 默认不加载 需指定编译此参数才使用 # --without # 默认加载，可用此参数禁用 # --add-module=path # 添加模块的路径 # --add-module=/opt/ngx_module_upstream_check \\ # nginx 代理状态页面 # ngx_module_upstream_check 编译前需要打对应版本补丁 patch -p1 \u0026lt; /opt/nginx_upstream_check_module/check_1.2.6+.patch # --add-module=/opt/ngx_module_memc \\ # 将请求页面数据存放在 memcached中 # --add-module=/opt/ngx_module_lua \\ # 支持lua脚本 yum install lua-devel lua ./configure \\ --user=nginx \\ --group=nginx \\ --prefix=/usr/local/nginx \\ --with-http_ssl_module \\ --with-http_realip_module \\ --with-http_gzip_static_module \\ --with-http_stub_status_module \\ --add-module=/opt/ngx_cache_purge-1.6 \\ --http-client-body-temp-path=/opt/nginx-tmp/client \\ --http-proxy-temp-path=/opt/nginx-tmp/proxy \\ --http-fastcgi-temp-path=/opt/nginx-tmp/fastcgi \\ --http-uwsgi-temp-path=/opt/nginx-tmp/uwsgi \\ --http-scgi-temp-path=/opt/nginx-tmp/scgi make \u0026amp;\u0026amp; make install /usr/local/nginx/sbin/nginx –t # 检查Nginx配置文件 但并不执行 /usr/local/nginx/sbin/nginx -t -c /opt/nginx/conf/nginx.conf # 检查Nginx配置文件 /usr/local/nginx/sbin/nginx # 启动nginx /usr/local/nginx/sbin/nginx -s reload # 重载配置 /usr/local/nginx/sbin/nginx -s stop # 关闭nginx服务 } elasticsearch{ vim /etc/sysctl.conf vm.max_map_count = 262144 vim /etc/security/limits.conf * soft memlock unlimited * hard memlock unlimited sysctl -p curl 'localhost:9200/_cat/health?v' # 健康检查 curl 'localhost:9200/_cat/nodes?v' # 获取集群的节点列表 curl 'localhost:9200/_cat/indices?v' # 列出所有索引 curl 127.0.0.1:9200/indexname -XDELETE # 删除索引 curl -XGET http://localhost:9200/_cat/shards # 查看分片 curl '127.0.0.1:9200/_cat/indices' # 查分片同步 unassigned_shards # 没同步完成 } mysql常用命令{ # mysql 可视化工具 MySQL Workbench mysqlcheck -uroot -p -S mysql.sock --optimize --databases account # 检查、修复、优化MyISAM表 mysqlbinlog slave-relay-bin.000001 # 查看二进制日志 mysqladmin -h myhost -u root -p create dbname # 创建数据库 flush privileges; # 刷新 show databases; # 显示所有数据库 use dbname; # 打开数据库 show tables; # 显示选中数据库中所有的表 desc tables; # 查看表结构 drop database name; # 删除数据库 drop table name; # 删除表 create database name; # 创建数据库 select column from table; # 查询 show processlist; # 查看mysql进程 show full processlist; # 显示进程全的语句 select user(); # 查看所有用户 show slave status\\G; # 查看主从状态 show variables; # 查看所有参数变量 show status; # 运行状态 show table status # 查看表的引擎状态 show grants for user@'%' # 查看用户权限 drop table if exists user # 表存在就删除 create table if not exists user # 表不存在就创建 select host,user,password from user; # 查询用户权限 先use mysql create table ka(ka_id varchar(6),qianshu int); # 创建表 show variables like 'character_set_%'; # 查看系统的字符集和排序方式的设定 show variables like '%timeout%'; # 查看超时相关参数 delete from user where user=''; # 删除空用户 delete from user where user='sss' and host='localhost' ; # 删除用户 drop user 'sss'@'localhost'; # 使用此方法删除用户更为靠谱 ALTER TABLE mytable ENGINE = MyISAM ; # 改变现有的表使用的存储引擎 SHOW TABLE STATUS from dbname where Name='tablename'; # 查询表引擎 mysql -uroot -p -A -ss -h10.10.10.5 -e \u0026quot;show databases;\u0026quot; # shell中获取数据不带表格 -ss参数 CREATE TABLE innodb (id int, title char(20)) ENGINE = INNODB # 创建表指定存储引擎的类型(MyISAM或INNODB) grant replication slave on *.* to 'user'@'%' identified by 'pwd'; # 创建主从复制用户 ALTER TABLE player ADD INDEX weekcredit_faction_index (weekcredit, faction); # 添加索引 alter table name add column accountid(column) int(11) NOT NULL(column); # 插入字段 update host set monitor_state='Y',hostname='xuesong' where ip='192.168.1.1'; # 更新数据 select * from information_schema.processlist where command!='sleep'; # 查看当前进程 select * from atable where name='on' AND t\u0026lt;15 AND host LIKE '10%' limit 1,10; # 多条件查询 show create database ops_deploy; # 查看数据库编码 show create table updatelog; # 查看数据库表编码 alter database ops_deploy CHARACTER SET utf8; # 修改数据库编码 alter table `updatelog` default character set utf8; # 修改表编码 alter table `updatelog` convert to character set utf8; # 修改一张表的所有字段的编码格式 自增表{ create table xuesong (id INTEGER PRIMARY KEY AUTO_INCREMENT, name CHAR(30) NOT NULL, age integer , sex CHAR(15) ); # 创建自增表 insert into xuesong(name,age,sex) values(%s,%s,%s) # 自增插入数据 } 登录mysql的命令{ # 格式： mysql -h 主机地址 -u 用户名 -p 用户密码 mysql -h110.110.110.110 -P3306 -uroot -p mysql -uroot -p -S /data1/mysql5/data/mysql.sock -A --default-character-set=GBK } shell执行mysql命令{ mysql -u root -p'123' xuesong \u0026lt; file.sql # 针对指定库执行sql文件中的语句,好处不需要转义特殊符号,一条语句可以换行.不指定库执行时语句中需要先use mysql -u$username -p$passwd -h$dbhost -P$dbport -A -e \u0026quot; use $dbname; delete from data where date=('$date1'); \u0026quot; # 执行多条mysql命令 mysql -uroot -p -S mysql.sock -e \u0026quot;use db;alter table gift add column accountid int(11) NOT NULL;flush privileges;\u0026quot; 2\u0026gt;\u0026amp;1 |grep -v Warning # 不登陆mysql插入字段 } mysql字符集相关{ show variables like '%character%'; # 查看数据库中设置字符集的参数 # character_set_client、character_set_connection 以及 character_set_results 这几个参数都是客户端的设置 # character_set_system、character_set_server 以及 character_set_database 是指服务器端的设置。 # 而对于这三个服务器端的参数来说的优先级是: # 列级字符集 \u0026gt; 表级字符集 \u0026gt; character_set_database \u0026gt; character_set_server \u0026gt; character_set_system show global variables like '%char%'; #查看RDS实例字符集相关参数设置 show global variables like 'coll%'; #查看当前会话字符序相关参数设置 show character set; #查看实例支持的字符集 show collation; #查看实例支持的字符序 show create table table_name \\G #查看表字符集设置 show create database database_name \\G #查看数据库字符集设置 show create procedure procedure_name \\G #查看存储过程字符集设置 show procedure status \\G #查看存储过程字符集设置 alter database db_name default charset utf8; #修改数据库的字符集 create database db_name character set utf8; #创建数据库时指定字符集 alter table tab_name default charset utf8 collate utf8_general_ci; #修改表字符集和字符序 # 下面三条sql 分别将库 dbsdq , 表 tt2 , 表 tt2 中的 c2 列修改为utf8mb4 字符集 alter database dbsdq character set utf8mb4 collate utf8mb4_unicode_ci; use dbsdq; alter table tt2 character set utf8mb4 collate utf8mb4_unicode_ci; alter table tt2 modify c2 varchar(10) character set utf8mb4; # 修改列时,当前列中的所有行都会立即转化为新的字符集; # alter table 会对表加元数据锁 } 备份数据库{ mysqldump -h host -u root -p --default-character-set=utf8 dbname \u0026gt;dbname_backup.sql # 不包括库名，还原需先创建库，在use mysqldump -h host -u root -p --database --default-character-set=utf8 dbname \u0026gt;dbname_backup.sql # 包括库名，还原不需要创建库 /bin/mysqlhotcopy -u root -p # mysqlhotcopy只能备份MyISAM引擎 mysqldump -u root -p -S mysql.sock --default-character-set=utf8 dbname table1 table2 \u0026gt; /data/db.sql # 备份表 mysqldump -uroot -p123 -d database \u0026gt; database.sql # 备份数据库结构 # 最小权限备份 grant select on db_name.* to dbbackup@\u0026quot;localhost\u0026quot; Identified by \u0026quot;passwd\u0026quot;; # --single-transaction InnoDB有时间戳 只备份开始那一刻的数据,备份过程中的数据不会备份 mysqldump -hlocalhost -P 3306 -u dbbackup --single-transaction -p\u0026quot;passwd\u0026quot; --database dbname \u0026gt;dbname.sql # xtrabackup备份需单独安装软件 优点: 速度快,压力小,可直接恢复主从复制 innobackupex --user=root --password=\u0026quot;\u0026quot; --defaults-file=/data/mysql5/data/my_3306.cnf --socket=/data/mysql5/data/mysql.sock --slave-info --stream=tar --tmpdir=/data/dbbackup/temp /data/dbbackup/ 2\u0026gt;/data/dbbackup/dbbackup.log | gzip 1\u0026gt;/data/dbbackup/db50.tar.gz } 还原数据库{ mysql -h host -u root -p dbname \u0026lt; dbname_backup.sql source 路径.sql # 登陆mysql后还原sql文件 } 赋权限{ # 指定IP: $IP 本机: localhost 所有IP地址: % # 通常指定多条 grant all on zabbix.* to user@\u0026quot;$IP\u0026quot;; # 对现有账号赋予权限 grant select on database.* to user@\u0026quot;%\u0026quot; Identified by \u0026quot;passwd\u0026quot;; # 赋予查询权限(没有用户，直接创建) grant all privileges on database.* to user@\u0026quot;$IP\u0026quot; identified by 'passwd'; # 赋予指定IP指定用户所有权限(不允许对当前库给其他用户赋权限) grant all privileges on database.* to user@\u0026quot;localhost\u0026quot; identified by 'passwd' with grant option; # 赋予本机指定用户所有权限(允许对当前库给其他用户赋权限) grant select, insert, update, delete on database.* to user@'ip'identified by \u0026quot;passwd\u0026quot;; # 开放管理操作指令 revoke all on *.* from user@localhost; # 回收权限 GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, EXECUTE, CREATE ROUTINE, ALTER ROUTINE ON `storemisc_dev`.* TO 'user'@'192.168.%' } 更改密码{ update user set password=password('passwd') where user='root' mysqladmin -u root password 'xuesong' } mysql忘记密码后重置{ cd /data/mysql5 /data/mysql5/bin/mysqld_safe --user=mysql --skip-grant-tables --skip-networking \u0026amp; use mysql; update user set password=password('123123') where user='root'; } mysql主从复制失败恢复{ slave stop; reset slave; change master to master_host='10.10.10.110',master_port=3306,master_user='repl',master_password='repl',master_log_file='master-bin.000010',master_log_pos=107,master_connect_retry=60; slave start; } sql语句使用变量{ use xuesong; set @a=concat('my',weekday(curdate())); # 组合时间变量 set @sql := concat('CREATE TABLE IF NOT EXISTS ',@a,'( id INT(11) NOT NULL )'); # 组合sql语句 select @sql; # 查看语句 prepare create_tb from @sql; # 准备 execute create_tb; # 执行 } 检测mysql主从复制延迟{ 1、在从库定时执行更新主库中的一个timeout数值 2、同时取出从库中的timeout值对比判断从库与主库的延迟 } 死锁{ show OPEN TABLES where In_use \u0026gt; 0; # 查看当前锁信息 show variables like 'innodb_print_all_deadlocks'; # 查看当前死锁参数 set global innodb_print_all_deadlocks = 1; # 设置死锁信息保存到错误日志 innodb_print_all_deadlocks = 1 # conf配置 } mysql慢查询{ select * from information_schema.processlist where command in ('Query') and time \u0026gt;5\\G # 查询操作大于5S的进程 开启慢查询日志{ # 配置文件 /etc/my.conf [mysqld] log-slow-queries=/var/lib/mysql/slowquery.log # 指定日志文件存放位置，可以为空，系统会给一个缺省的文件host_name-slow.log long_query_time=5 # 记录超过的时间，默认为10s 建议0.5S log-queries-not-using-indexes # log下来没有使用索引的query,可以根据情况决定是否开启 可不加 log-long-format # 如果设置了，所有没有使用索引的查询也将被记录 可不加 # 直接修改生效 show variables like \u0026quot;%slow%\u0026quot;; # 查看慢查询状态 set global slow_query_log='ON'; # 开启慢查询日志 变量可能不同，看上句查询出来的变量 } mysqldumpslow慢查询日志查看{ -s # 是order的顺序，包括看了代码，主要有 c,t,l,r和ac,at,al,ar，分别是按照query次数，时间，lock的时间和返回的记录数来排序，前面加了a的时倒序 -t # 是top n的意思，即为返回前面多少条的数据 -g # 后边可以写一个正则匹配模式，大小写不敏感的 mysqldumpslow -s c -t 20 host-slow.log # 访问次数最多的20个sql语句 mysqldumpslow -s r -t 20 host-slow.log # 返回记录集最多的20个sql mysqldumpslow -t 10 -s t -g \u0026quot;left join\u0026quot; host-slow.log # 按照时间返回前10条里面含有左连接的sql语句 show global status like '%slow%'; # 查看现在这个session有多少个慢查询 show variables like '%slow%'; # 查看慢查询日志是否开启，如果slow_query_log和log_slow_queries显示为on，说明服务器的慢查询日志已经开启 show variables like '%long%'; # 查看超时阀值 desc select * from wei where text='xishizhaohua'\\G; # 扫描整张表 tepe:ALL 没有使用索引 key:NULL create index text_index on wei(text); # 创建索引 } Percona Toolkit 慢日志分析工具 } mysql操作次数查询{ select * from information_schema.global_status; com_select com_delete com_insert com_update } } mongodb{ # mongo可视管理工具 studio 3t 一、启动{ # 不启动认证 ./mongod --port 27017 --fork --logpath=/opt/mongodb/mongodb.log --logappend --dbpath=/opt/mongodb/data/ # 启动认证 ./mongod --port 27017 --fork --logpath=/opt/mongodb/mongodb.log --logappend --dbpath=/opt/mongodb/data/ --auth # 配置文件方式启动 cat /opt/mongodb/mongodb.conf port=27017 # 端口号 fork=true # 以守护进程的方式运行，创建服务器进程 auth=true # 开启用户认证 logappend=true # 日志采用追加方式 logpath=/opt/mongodb/mongodb.log # 日志输出文件路径 dbpath=/opt/mongodb/data/ # 数据库路径 shardsvr=true # 设置是否分片 maxConns=600 # 数据库的最大连接数 ./mongod -f /opt/mongodb/mongodb.conf # 其他参数 bind_ip # 绑定IP 使用mongo登录需要指定对应IP journal # 开启日志功能,降低单机故障的恢复时间,取代dur参数 syncdelay # 系统同步刷新磁盘的时间,默认60秒 directoryperdb # 每个db单独存放目录,建议设置.与mysql独立表空间类似 repairpath # 执行repair时的临时目录.如果没开启journal,出现异常重启,必须执行repair操作 # mongodb没有参数设置内存大小.使用os mmap机制缓存数据文件,在数据量不超过内存的情况下,效率非常高.数据量超过系统可用内存会影响写入性能 } 二、关闭{ # 方法一:登录mongodb ./mongo use admin db.shutdownServer() # 方法:kill传递信号 两种皆可 kill -2 pid kill -15 pid } 三、开启认证与用户管理{ ./mongo # 先登录 use admin # 切换到admin库 db.addUser(\u0026quot;root\u0026quot;,\u0026quot;123456\u0026quot;) # 创建用户 db.addUser('zhansan','pass',true) # 如果用户的readOnly为true那么这个用户只能读取数据，添加一个readOnly用户zhansan ./mongo 127.0.0.1:27017/mydb -uroot -p123456 # 再次登录,只能针对用户所在库登录 #虽然是超级管理员，但是admin不能直接登录其他数据库，否则报错 #Fri Nov 22 15:03:21.886 Error: 18 { code: 18, ok: 0.0, errmsg: \u0026quot;auth fails\u0026quot; } at src/mongo/shell/db.js:228 show collections # 查看链接状态 再次登录使用如下命令,显示错误未经授权 db.system.users.find(); # 查看创建用户信息 db.system.users.remove({user:\u0026quot;zhansan\u0026quot;}) # 删除用户 #恢复密码只需要重启mongodb 不加--auth参数 } 四、登录{ 192.168.1.5:28017 # http登录后可查看状态 mongo # 默认登录后打开 test 库 mongo 192.168.1.5:27017/databaseName # 直接连接某个库 不存在则创建 启动认证需要指定对应库才可登录 } 五、查看状态{ #登录后执行命令查看状态 db.runCommand({\u0026quot;serverStatus\u0026quot;:1}) globalLock # 表示全局写入锁占用了服务器多少时间(微秒) mem # 包含服务器内存映射了多少数据,服务器进程的虚拟内存和常驻内存的占用情况(MB) indexCounters # 表示B树在磁盘检索(misses)和内存检索(hits)的次数.如果这两个比值开始上升,就要考虑添加内存了 backgroudFlushing # 表示后台做了多少次fsync以及用了多少时间 opcounters # 包含每种主要擦撞的次数 asserts # 统计了断言的次数 #状态信息从服务器启动开始计算,如果过大就会复位,发送复位，所有计数都会复位,asserts中的roolovers值增加 #mongodb自带的命令 ./mongostat insert #每秒插入量 query #每秒查询量 update #每秒更新量 delete #每秒删除量 locked #锁定量 qr|qw #客户端查询排队长度(读|写) ar|aw #活跃客户端量(读|写) conn #连接数 time #当前时间 mongostat -h 127.0.0.1 --port 27047 --authenticationDatabase admin -u zadmin -p Keaphh9e # 查看mongo状态 mongotop -h 127.0.0.1 --port 27047 --authenticationDatabase admin -u zadmin -p Keaphh9e # 查看mongo集合的统计数据 } 六、常用命令{ db.listCommands() # 当前MongoDB支持的所有命令（同样可通过运行命令db.runCommand({\u0026quot;listCommands\u0026quot; : `1})来查询所有命令） db.runCommand({\u0026quot;buildInfo\u0026quot; : 1}) # 返回MongoDB服务器的版本号和服务器OS的相关信息 db.runCommand({\u0026quot;collStats\u0026quot; : tablename}) # 返回该集合的统计信息，包括数据大小，已分配存储空间大小，索引的大小等 db.runCommand({\u0026quot;dropDatabase\u0026quot; : 1}) # 清空当前数据库的信息，包括删除所有的集合和索引 db.runCommand({\u0026quot;isMaster\u0026quot; : 1}) # 检查本服务器是主服务器还是从服务器 db.runCommand({\u0026quot;ping\u0026quot; : 1}) # 检查服务器链接是否正常。即便服务器上锁，该命令也会立即返回 db.runCommand({\u0026quot;repaireDatabase\u0026quot; : 1}) # 对当前数据库进行修复并压缩，如果数据库特别大，这个命令会非常耗时 db.runCommand({\u0026quot;serverStatus\u0026quot; : 1}) # 查看这台服务器的管理统计信息 # 某些命令必须在admin数据库下运行，如下两个命令： db.runCommand({\u0026quot;renameCollection\u0026quot; : 集合名, \u0026quot;to\u0026quot;：集合名}) # 对集合重命名，注意两个集合名都要是完整的集合命名空间，如foo.bar, 表示数据库foo下的集合bar。 db.runCommand({\u0026quot;listDatabases\u0026quot; : 1}) # 列出服务器上所有的数据库 mongo 172.20.20.1:27072/mdb --eval \u0026quot;db.tb.count();\u0026quot; # shell执行mongo语句 mongo --host 172.20.20.1 --port 27049 rs.config(); # 查看集群配置 rs.status(); # 查看集群节点的状态 db.currentOp() # 获取当前正在执行的操作,可对应命令链接到ip:port db.runCommand( { logRotate : 1 } ) # 日志轮转 rs.slaveOk() # 设置从库shell可读 rs.addArb(\u0026quot;172.16.10.199:27020\u0026quot;); # 添加仲裁节点 rs.add({host: \u0026quot;10.2.2.2:27047\u0026quot;, priority: 0, hidden: true}) # 添加从节点 hidden true隐藏节点[priority必须为0] false不隐藏 rs.remove(\u0026quot;172.20.80.216:27047\u0026quot;); # 删除节点 rs.stepDown(120) # 主库上执行切换为从,120秒后切换回主 show dbs # 查询db use post # 选择db show tables # 查看文档列表 db.tb.drop() # 删除集合 需要权限 db.tb.remove({}) # 删除所有数据 db.tb.count() # 查询文档条数 db.tb.find() # 查看文档内容 db.tb.find({_id:37530555}) # 查询指定id db.tb.find().sort({_id:-1}).limit(1) # 查询文档最后一条 db.tb.find({\u0026quot;processed\u0026quot; : {\u0026quot;$ne\u0026quot; : true}}).limit(1); # 字段不为 true db.tb.find({\u0026quot;processed\u0026quot; : {\u0026quot;$eq\u0026quot; : true}}).limit(1); # 字段为 true db.tb.find({\u0026quot;processed\u0026quot; : {\u0026quot;$exists\u0026quot; : false}}).limit(1); # 字段不存在 db.tb.ensureIndex({\u0026quot;status\u0026quot;:1}, {background:true}) # 后台加索引 db.tb.getIndexes() # 查看索引 db.tb.ensureIndex({\u0026quot;c_type\u0026quot;:1},{backgrounnd:true}) # 后台添加索引 1正向 -1反向 db.tb.dropIndex({\u0026quot;c_type\u0026quot;:1}); # 删除索引 } 七、进程控制{ db.currentOp() # 查看活动进程 db.$cmd.sys.inprog.findOne() # 查看活动进程 与上面一样 opid # 操作进程号 op # 操作类型(查询\\更新) ns # 命名空间,指操作的是哪个对象 query # 如果操作类型是查询,这里将显示具体的查询内容 lockType # 锁的类型,指明是读锁还是写锁 db.killOp(opid值) # 结束进程 db.$cmd.sys.killop.findOne({op:opid值}) # 结束进程 } 八、备份还原{ # mongodump 虽然能不停机备份,但是为了获取实时数据视图的能力,使用fsync命令能在运行时复制数据目录并且不会损坏数据 # fsync会强制服务器将所有缓冲区的数据写入磁盘.配合lock还阻止对数据库的进一步写入,知道释放锁为止 db.runCommand({\u0026quot;fsync\u0026quot;:1,\u0026quot;lock\u0026quot;:1}) # 执行强制更新与写入锁 db.$cmd.sys.unlock.findOne() # 解锁 db.currentOp() # 查看解锁是否正常 mongoexport -d test -c t1 -o t1.dat # 导出JSON格式 -c # 指明导出集合 -d # 使用库 mongoexport -d test -c t1 -csv -f num -o t1.dat # 导出csv格式 -csv # 指明导出csv格式 -f # 指明需要导出那些例 mongoimport -d test -c t1 -file t1.dat # mongoimport还原JSON格式 mongoimport -d test -c t1 -type csv --headerline -file t1.dat # mongoimport还原csv格式数据 --headerline # 指明不导入第一行 因为第一行是列名 mongodump -d test -o /bak/mongodump # mongodump数据备份 mongorestore -d test --drop /bak/mongodump/* # mongorestore恢复 --drop # 恢复前先删除 --gzip # 压缩 # 备份一个表 # --excludeCollection string # 排除指定的集合 要排除多个，使用多个 mongodump --host 127.0.0.1:27080 -d dbname -c tablename -o /data/reports/ mongodump --host 127.0.0.1:27080 -d dbname -c tablename -o /data/reports/reports -u root -p tAvaa5yNUE --authenticationDatabase admin # 恢复一个表 mongorestore --host 127.0.0.1:27080 -d dbname -c tablename --drop --dir=/data/reports/tablename.bson # 在线拷贝一个库 db.copyDatabase(fromdb, todb, fromhost, username, password, mechanism) db.copyDatabase('mate','mate', '172.16.255.176:27047') } 九、修复{ # 当停电或其他故障引起不正常关闭时,会造成部分数据损坏丢失 mongod --repair # 修复操作:启动时候加上 --repair # 修复过程:将所有文档导出,然后马上导入,忽略无效文档.完成后重建索引。时间较长,会丢弃损坏文档 # 修复数据还能起到压缩数据库的作用 db.repairDatabase() # 运行中的mongodb可使用 repairDatabase 修复当前使用的数据库 {\u0026quot;repairDatabase\u0026quot;:1} # 通过驱动程序 } 十、python使用mongodb{ 原文: http://blog.nosqlfan.com/html/2989.html easy_install pymongo # python2.7+ import pymongo connection=pymongo.Connection('localhost',27017) # 创建连接 db = connection.test_database # 切换数据库 collection = db.test_collection # 获取collection # db和collection都是延时创建的，在添加Document时才真正创建 文档添加, _id自动创建 import datetime post = {\u0026quot;author\u0026quot;: \u0026quot;Mike\u0026quot;, \u0026quot;text\u0026quot;: \u0026quot;My first blog post!\u0026quot;, \u0026quot;tags\u0026quot;: [\u0026quot;mongodb\u0026quot;, \u0026quot;python\u0026quot;, \u0026quot;pymongo\u0026quot;], \u0026quot;date\u0026quot;: datetime.datetime.utcnow()} posts = db.posts posts.insert(post) ObjectId('...') 批量插入 new_posts = [{\u0026quot;author\u0026quot;: \u0026quot;Mike\u0026quot;, \u0026quot;text\u0026quot;: \u0026quot;Another post!\u0026quot;, \u0026quot;tags\u0026quot;: [\u0026quot;bulk\u0026quot;, \u0026quot;insert\u0026quot;], \u0026quot;date\u0026quot;: datetime.datetime(2009, 11, 12, 11, 14)}, {\u0026quot;author\u0026quot;: \u0026quot;Eliot\u0026quot;, \u0026quot;title\u0026quot;: \u0026quot;MongoDB is fun\u0026quot;, \u0026quot;text\u0026quot;: \u0026quot;and pretty easy too!\u0026quot;, \u0026quot;date\u0026quot;: datetime.datetime(2009, 11, 10, 10, 45)}] posts.insert(new_posts) [ObjectId('...'), ObjectId('...')] 获取所有collection db.collection_names() # 相当于SQL的show tables 获取单个文档 posts.find_one() 查询多个文档 for post in posts.find(): post 加条件的查询 posts.find_one({\u0026quot;author\u0026quot;: \u0026quot;Mike\u0026quot;}) 高级查询 posts.find({\u0026quot;date\u0026quot;: {\u0026quot;$lt\u0026quot;: \u0026quot;d\u0026quot;}}).sort(\u0026quot;author\u0026quot;) 统计数量 posts.count() 加索引 from pymongo import ASCENDING, DESCENDING posts.create_index([(\u0026quot;date\u0026quot;, DESCENDING), (\u0026quot;author\u0026quot;, ASCENDING)]) 查看查询语句的性能 posts.find({\u0026quot;date\u0026quot;: {\u0026quot;$lt\u0026quot;: \u0026quot;d\u0026quot;}}).sort(\u0026quot;author\u0026quot;).explain()[\u0026quot;cursor\u0026quot;] posts.find({\u0026quot;date\u0026quot;: {\u0026quot;$lt\u0026quot;: \u0026quot;d\u0026quot;}}).sort(\u0026quot;author\u0026quot;).explain()[\u0026quot;nscanned\u0026quot;] } } JDK安装{ vim /etc/profile.d/jdk.sh export JAVA_HOME=/usr/local/jdk1.8.0_151 export PATH=$JAVA_HOME/bin:$PATH . /etc/profile # 加载新的环境变量 jps -ml # 查看java进程 jstat -gc 18381 1s 30 # 查看java进程gc情况 } redis动态加内存{ ./redis-cli -h 10.10.10.11 -p 6401 save # 保存当前快照 config get * # 列出所有当前配置 config get maxmemory # 查看指定配置 config set maxmemory 15360000000 # 动态修改最大内存配置参数 } nfs{ # 依赖rpc服务通信 portmap[centos5] 或 rpcbind[centos6] yum install nfs-utils portmap # centos5安装 yum install nfs-utils rpcbind # centos6安装 vim /etc/exports # 配置文件 # sync # 同步写入 # async # 暂存并非直接写入 # no_root_squash # 开放用户端使用root身份操作 # root_squash # 使用者身份为root则被压缩成匿名使用,即nobody,相对安全 # all_squash # 所有NFS的使用者身份都被压缩为匿名 /data/images 10.10.10.0/24(rw,sync,no_root_squash) service portmap restart # 重启centos5的nfs依赖的rpc服务 service rpcbind restart # 重启centos6的nfs依赖的rpc服务 service nfs restart # 重启nfs服务 确保依赖 portmap 或 rpcbind 服务已启动 service nfs reload # 重载NFS服务配置文件 showmount -e # 服务端查看自己共享的服务 showmount -a # 显示已经与客户端连接上的目录信息 showmount -e 10.10.10.3 # 列出服务端可供使用的NFS共享 客户端测试能否访问nfs服务 mount -t nfs 10.10.10.3:/data/images/ /data/img # 挂载nfs 如果延迟影响大加参数 noac # 服务端的 portmap 或 rpcbind 被停止后，nfs仍然工作正常，但是umout财会提示： not found / mounted or server not reachable 重启服务器的portmap 或 rpcbind 也无济于事。 nfs也要跟着重启，否则nfs工作仍然是不正常的。 # 同时已挂载会造成NFS客户端df卡住和挂载目录无法访问。请先用 mount 查看当前挂载情况，记录挂载信息，在强制卸载挂载目录，重新挂载 umount -f /data/img/ # 强制卸载挂载目录 如还不可以 umount -l /data/img/ nfsstat -c # 客户机发送和拒绝的RPC和NFS调用数目的信息 nfsstat -cn # 显示和打印与客户机NFS调用相关的信息 nfsstat -r # 显示和打印客户机和服务器的与RPC调用相关的信息 nfsstat –s # 显示关于服务器接收和拒绝的RPC和NFS调用数目的信息 } hdfs{ hdfs --help # 所有参数 hdfs dfs -help # 运行文件系统命令在Hadoop文件系统 hdfs dfs -ls /logs # 查看 hdfs dfs -ls /user/ # 查看用户 hdfs dfs -cat hdfs dfs -df hdfs dfs -du hdfs dfs -rm hdfs dfs -tail hdfs dfs –put localSrc dest # 上传文件 hdfs dfsadmin -help # hdfs集群节点管理 hdfs dfsadmin -report # 基本的文件系统统计信息 } }\n5 网络{\nrz # 通过ssh上传小文件 sz # 通过ssh下载小文件 ifconfig eth0 down # 禁用网卡 ifconfig eth0 up # 启用网卡 ifup eth0:0 # 启用网卡 mii-tool em1 # 查看网线是否连接 traceroute www.baidu.com # 测试跳数 vi /etc/resolv.conf # 设置DNS nameserver IP 定义DNS服务器的IP地址 nslookup www.moon.com # 解析域名IP dig -x www.baidu.com # 解析域名IP dig +trace -t A domainname # 跟踪dns dig +short txt hacker.wp.dg.cx # 通过 DNS 来读取 Wikipedia 的hacker词条 host -t txt hacker.wp.dg.cx # 通过 DNS 来读取 Wikipedia 的hacker词条 lynx # 文本上网 wget -P path -O name url # 下载 包名:wgetrc -q 安静 -c 续传 dhclient eth1 # 自动获取IP mtr -r www.baidu.com # 测试网络链路节点响应时间 # trace ping 结合 ipcalc -m \u0026quot;$ip\u0026quot; -p \u0026quot;$num\u0026quot; # 根据IP和主机最大数计算掩码 curl -I www.baidu.com # 查看网页http头 curl -s www.baidu.com # 不显示进度 queryperf -d list -s DNS_IP -l 2 # BIND自带DNS压力测试 [list 文件格式:www.turku.fi A] telnet ip port # 测试端口是否开放,有些服务可直接输入命令得到返回状态 echo \u0026quot;show \u0026quot; |nc $ip $port # 适用于telnet一类登录得到命令返回 nc -l -p port # 监听指定端口 nc -nv -z 10.10.10.11 1080 |grep succeeded # 检查主机端口是否开放 curl -o /dev/null -s -m 10 --connect-timeout 10 -w %{http_code} $URL # 检查页面状态 curl -X POST -d \u0026quot;user=xuesong\u0026amp;pwd=123\u0026quot; http://www.abc.cn/Result # 提交POST请求 curl -s http://20140507.ip138.com/ic.asp # 通过IP138取本机出口外网IP curl http://IP/ -H \u0026quot;X-Forwarded-For: ip\u0026quot; -H \u0026quot;Host: www.ttlsa.com\u0026quot; # 连到指定IP的响应主机,HTTPserver只看 Host字段 ifconfig eth0:0 192.168.1.221 netmask 255.255.255.0 # 增加逻辑IP地址 echo 1 \u0026gt; /proc/sys/net/ipv4/icmp_echo_ignore_all # 禁ping net rpc shutdown -I IP_ADDRESS -U username%password # 远程关掉一台WINDOWS机器 wget --random-wait -r -p -e robots=off -U Mozilla www.example.com # 递归方式下载整个网站 sshpass -p \u0026quot;$pwd\u0026quot; rsync -avzP /dir user@$IP:/dir/ # 指定密码避免交互同步目录 rsync -avzP --delete /dir/ user@$IP:/dir/ # 无差同步目录 可以快速清空大目录,末尾带/同步目录 rsync -avzP -e \u0026quot;ssh -p 22 -e -o StrictHostKeyChecking=no\u0026quot; /dir user@$IP:/dir # 指定ssh参数同步 抓包{ -i eth1 # 只抓经过接口eth1的包 -t # 不显示时间戳 -s 0 # 抓取数据包时默认抓取长度为68字节。加上-S 0 后可以抓到完整的数据包 -c 100 # 只抓取100个数据包 dst port ! 22 # 不抓取目标端口是22的数据包 tcpdump tcp port 22 # 抓包 tcpdump -n -vv udp port 53 # 抓udp的dns包 并显示ip tcpdump port 10001 -A -s0 # 完整显示ascii数据包 tcpdump -i any host x.x.x.x -s 0 -w /tmp/cap.pcap # 对端ip tcpdump -i any -s 0 host 172.20.81.107 or host 172.16.3.72 -C 50 -W 5 -w /tmp/20190122ng.cap } 一次短链接失败故障定位{ # php和python程序调用接口,通过阿里云slb,到后端nginx,偶尔超时,后端nginx无请求,怀疑没到nginx,但通过检查,无法与nginx建立tcp链接 ss -nl |grep :80 # 查看 accept 队列值,短连接应该大一点 watch -n 1 'nstat -z -t 1 | grep -e TcpActiveOpens -e TcpExtListenOverflows -e TcpAttemptFails -e TcpPassiveOpen -e TcpExtTCPSynRetrans -e TcpRetransSegs -e TcpOutSegs -e TcpInSegs' TcpAttemptFails TCP建立链接失败,包括前后端 TcpExtTCPSynRetrans TCP向后端建立链接失败 # nginx 和内核都需要调整才生效,程序监听端口,需要加socket参数 listen 10.87.128.29:51528 default_server backlog=4096; https://m.aliyun.com/yunqi/articles/118472?spm=5176.8091938.0.0.11e86ccF4oOeZ } 网卡流量查看{ watch more /proc/net/dev # 实时监控流量文件系统 累计值 iptraf # 网卡流量查看工具 nethogs -d 5 eth0 eth1 # 按进程实时统计网络流量 epel源nethogs iftop -i eth0 -n -P # 实时流量监控 sar { -n参数有6个不同的开关: DEV | EDEV | NFS | NFSD | SOCK | ALL DEV显示网络接口信息 EDEV显示关于网络错误的统计数据 NFS统计活动的NFS客户端的信息 NFSD统计NFS服务器的信息 SOCK显示套 接字信息 ALL显示所有5个开关 sar -n DEV 1 10 rxpck/s # 每秒钟接收的数据包 txpck/s # 每秒钟发送的数据包 rxbyt/s # 每秒钟接收的字节数 txbyt/s # 每秒钟发送的字节数 rxcmp/s # 每秒钟接收的压缩数据包 txcmp/s # 每秒钟发送的压缩数据包 rxmcst/s # 每秒钟接收的多播数据包 } } netstat{ # 几十万并发的情况下netstat会没有响应，建议使用 ss 命令 -a # 显示所有连接中的Socket -t # 显示TCP连接 -u # 显示UDP连接 -n # 显示所有已建立的有效连接 netstat -anlp # 查看链接 netstat -tnlp # 只查看tcp监听端口 netstat -r # 查看路由表 } ss{ # netstat是遍历/proc下面每个PID目录，ss直接读/proc/net下面的统计信息。所以ss执行的时候消耗资源以及消耗的时间都比netstat少很多 ss -s # 列出当前socket详细信息 ss -l # 显示本地打开的所有端口 ss -tnlp # 显示每个进程具体打开的socket ss -ant # 显示所有TCP socket ss -u -a # 显示所有UDP Socekt ss dst 192.168.119.113 # 匹配远程地址 ss dst 192.168.119.113:http # 匹配远程地址和端口号 ss dst 192.168.119.113:3844 # 匹配远程地址和端口号 ss src 192.168.119.103:16021 # 匹配本地地址和端口号 ss -o state established '( dport = :smtp or sport = :smtp )' # 显示所有已建立的SMTP连接 ss -o state established '( dport = :http or sport = :http )' # 显示所有已建立的HTTP连接 ss -x src /tmp/.X11-unix/* # 找出所有连接X服务器的进程 } 并发数查看{ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' SYN_RECV # 正在等待处理的请求 ESTABLISHED # 正常数据传输状态,既当前并发数 TIME_WAIT # 处理完毕，等待超时结束的请求 CLOSE_WAIT # 客户端异常关闭,没有完成4次挥手 如大量可能存在攻击行为 } ssh{ ssh -p 22 user@192.168.1.209 # 从linux ssh登录另一台linux ssh -p 22 root@192.168.1.209 CMD # 利用ssh操作远程主机 scp -P 22 file root@ip:/dir # 把本地文件拷贝到远程主机 scp -l 100000 file root@ip:/dir # 传输文件到远程，限制速度100M sshpass -p 'pwd' ssh -n root@$IP \u0026quot;echo hello\u0026quot; # 指定密码远程操作 ssh -o StrictHostKeyChecking=no $IP # ssh连接不提示yes ssh -t \u0026quot;su -\u0026quot; # 指定伪终端 客户端以交互模式工作 scp root@192.168.1.209:/RemoteDir /localDir # 把远程指定文件拷贝到本地 pscp -h host.ip /a.sh /opt/sbin/ # 批量传输文件 ssh -N -L2001:remotehost:80 user@somemachine # 用SSH创建端口转发通道 ssh -t host_A ssh host_B # 嵌套使用SSH ssh -t -p 22 $user@$Ip /bin/su - root -c {$Cmd}; # 远程su执行命令 Cmd=\u0026quot;\\\u0026quot;/sbin/ifconfig eth0\\\u0026quot;\u0026quot; ssh-keygen -t rsa # 生成密钥 ssh-copy-id -i xuesong@10.10.10.133 # 传送key vi $HOME/.ssh/authorized_keys # 公钥存放位置 sshfs name@server:/path/to/folder /path/to/mount/point # 通过ssh挂载远程主机上的文件夹 fusermount -u /path/to/mount/point # 卸载ssh挂载的目录 ssh user@host cat /path/to/remotefile | diff /path/to/localfile - # 用DIFF对比远程文件跟本地文件 su - user -c \u0026quot;ssh user@192.168.1.1 \\\u0026quot;echo -e aa |mail -s test mail@163.com\\\u0026quot;\u0026quot; # 切换用户登录远程发送邮件 pssh -h ip.txt -i uptime # 批量执行ssh yum install pssh SSH反向连接{ # 外网A要控制内网B ssh -NfR 1234:localhost:2223 user1@123.123.123.123 -p22 # 将A主机的1234端口和B主机的2223端口绑定，相当于远程端口映射 ss -ant # 这时在A主机上sshd会listen本地1234端口 # LISTEN 0 128 127.0.0.1:1234 *:* ssh localhost -p1234 # 在A主机连接本地1234端口 } } 网卡配置文件{ vi /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 BOOTPROTO=none BROADCAST=192.168.1.255 HWADDR=00:0C:29:3F:E1:EA IPADDR=192.168.1.55 NETMASK=255.255.255.0 NETWORK=192.168.1.0 ONBOOT=yes TYPE=Ethernet GATEWAY=192.168.1.1 #ARPCHECK=no # 进制arp检查 } route { route # 查看路由表 route add default gw 192.168.1.1 dev eth0 # 添加默认路由 route add -net 172.16.0.0 netmask 255.255.0.0 gw 10.39.111.254 # 添加静态路由网关 route del -net 172.16.0.0 netmask 255.255.0.0 gw 10.39.111.254 # 删除静态路由网关 } 静态路由{ vim /etc/sysconfig/static-routes any net 192.168.12.0/24 gw 192.168.0.254 any net 192.168.13.0/24 gw 192.168.0.254 } 解决ssh链接慢{ sed -i 's/GSSAPIAuthentication yes/GSSAPIAuthentication no/' /etc/ssh/sshd_config sed -i '/#UseDNS yes/a\\UseDNS no' /etc/ssh/sshd_config /etc/init.d/sshd reload } nmap{ nmap -PT 192.168.1.1-111 # 先ping在扫描主机开放端口 nmap -O 192.168.1.1 # 扫描出系统内核版本 nmap -sV 192.168.1.1-111 # 扫描端口的软件版本 nmap -sS 192.168.1.1-111 # 半开扫描(通常不会记录日志) nmap -P0 192.168.1.1-111 # 不ping直接扫描 nmap -d 192.168.1.1-111 # 详细信息 nmap -D 192.168.1.1-111 # 无法找出真正扫描主机(隐藏IP) nmap -p 20-30,139,60000- # 端口范围 表示：扫描20到30号端口，139号端口以及所有大于60000的端口 nmap -P0 -sV -O -v 192.168.30.251 # 组合扫描(不ping、软件版本、内核版本、详细信息) # 不支持windows的扫描(可用于判断是否是windows) nmap -sF 192.168.1.1-111 nmap -sX 192.168.1.1-111 nmap -sN 192.168.1.1-111 } 流量切分线路{ # 程序判断进入IP线路，设置服务器路由规则控制返回 vi /etc/iproute2/rt_tables #添加一条策略 252 bgp2 #注意策略的序号顺序 ip route add default via 第二个出口上线IP(非默认网关) dev eth1 table bgp2 ip route add from 本机第二个ip table bgp2 #查看 ip route list table 252 ip rule list #成功后将语句添加开机启动 } snmp{ snmptranslate .1.3.6.1.2.1.1.3.0 # 查看映射关系 DISMAN-EVENT-MIB::sysUpTimeInstance snmpdf -v 1 -c public localhost # SNMP监视远程主机的磁盘空间 snmpnetstat -v 2c -c public -a 192.168.6.53 # SNMP获取指定IP的所有开放端口状态 snmpwalk -v 2c -c public 10.152.14.117 .1.3.6.1.2.1.1.3.0 # SNMP获取主机启动时间 # MIB安装(ubuntu) # sudo apt-get install snmp-mibs-downloader # sudo download-mibs snmpwalk -v 2c -c public 10.152.14.117 sysUpTimeInstance # SNMP通过MIB库获取主机启动时间 } TC流量控制{ # 针对ip段下载速率控制 tc qdisc del dev eth0 root handle 1: # 删除控制1: tc qdisc add dev eth0 root handle 1: htb r2q 1 # 添加控制1: tc class add dev eth0 parent 1: classid 1:1 htb rate 12mbit ceil 15mbit # 设置速率 tc filter add dev eth0 parent 1: protocol ip prio 16 u32 match ip dst 10.10.10.1/24 flowid 1:1 # 指定ip段控制规则 # 检查命令 tc -s -d qdisc show dev eth0 tc class show dev eth0 tc filter show dev eth0 限制上传下载{ tc qdisc del dev tun0 root tc qdisc add dev tun0 root handle 2:0 htb tc class add dev tun0 parent 2:1 classid 2:10 htb rate 30kbps tc class add dev tun0 parent 2:2 classid 2:11 htb rate 30kbps tc qdisc add dev tun0 parent 2:10 handle 1: sfq perturb 1 tc filter add dev tun0 protocol ip parent 2:0 u32 match ip dst 10.18.0.0/24 flowid 2:10 tc filter add dev tun0 parent ffff: protocol ip u32 match ip src 10.18.0.0/24 police rate 30kbps burst 10k drop flowid 2:11 tc qdisc del dev tun0 root # 删除原有策略 tc qdisc add dev tun0 root handle 2:0 htb # 定义最顶层(根)队列规则，并指定 default 类别编号，为网络接口 eth1 绑定一个队列，类型为 htb，并指定了一个 handle 句柄 2:0 用于标识它下面的子类 tc class add dev tun0 parent 2:1 classid 2:10 htb rate 30kbps # 设置一个规则速度是30kbps tc class add dev tun0 parent 2:2 classid 2:11 htb rate 30kbps tc qdisc add dev tun0 parent 2:10 handle 1: sfq perturb 1 # 调用随机公平算法 tc filter add dev tun0 protocol ip parent 2:0 u32 match ip dst 10.18.0.0/24 flowid 2:10 # 规则2:10应用在目标地址上，即下载 tc filter add dev tun0 parent ffff: protocol ip u32 match ip src 10.18.0.0/24 police rate 30kbps burst 10k drop flowid 2:11 # 上传限速 } } }\n6 磁盘{\ndf -Ph # 查看硬盘容量 df -T # 查看磁盘分区格式 df -i # 查看inode节点 如果inode用满后无法创建文件 du -h dir # 检测目录下所有文件大小 du -sh * # 显示当前目录中子目录的大小 mount -l # 查看分区挂载情况 fdisk -l # 查看磁盘分区状态 fdisk /dev/hda3 # 分区 mkfs -t ext4 /dev/hda3 # 格式化分区 fsck -y /dev/sda6 # 对文件系统修复 lsof |grep delete # 释放进程占用磁盘空间 列出进程后，查看文件是否存在，不存在则kill掉此进程 tmpwatch -afv 10 /tmp # 删除10小时内未使用的文件 勿在重要目录使用 cat /proc/filesystems # 查看当前系统支持文件系统 mount -o remount,rw / # 修改只读文件系统为读写 iotop # 进程占用磁盘IO情况 yum install iotop smartctl -H /dev/sda # 检测硬盘状态 # yum install smartmontools smartctl -i /dev/sda # 检测硬盘信息 smartctl -a /dev/sda # 检测所有信息 e2label /dev/sda5 # 查看卷标 e2label /dev/sda5 new-label # 创建卷标 ntfslabel -v /dev/sda8 new-label # NTFS添加卷标 tune2fs -j /dev/sda # ext2分区转ext3分区 tune2fs -l /dev/sda # 查看文件系统信息 mke2fs -b 2048 /dev/sda5 # 指定索引块大小 dumpe2fs -h /dev/sda5 # 查看超级块的信息 mount -t iso9660 /dev/dvd /mnt # 挂载光驱 mount -t ntfs-3g /dev/sdc1 /media/yidong # 挂载ntfs硬盘 mount -t nfs 10.0.0.3:/opt/images/ /data/img # 挂载nfs 需要重载 /etc/init.d/nfs reload 重启需要先启动 portmap 服务 mount -o loop /software/rhel4.6.iso /mnt/ # 挂载镜像文件 磁盘IO性能检测{ iostat -x 1 10 % user # 显示了在用户级(应用程序)执行时生成的 CPU 使用率百分比。 % system # 显示了在系统级(内核)执行时生成的 CPU 使用率百分比。 % idle # 显示了在 CPU 空闲并且系统没有未完成的磁盘 I/O 请求时的时间百分比。 % iowait # 显示了 CPU 空闲期间系统有未完成的磁盘 I/O 请求时的时间百分比。 rrqm/s # 每秒进行 merge 的读操作数目。即 delta(rmerge)/s wrqm/s # 每秒进行 merge 的写操作数目。即 delta(wmerge)/s r/s # 每秒完成的读 I/O 设备次数。即 delta(rio)/s w/s # 每秒完成的写 I/O 设备次数。即 delta(wio)/s rsec/s # 每秒读扇区数。即 delta(rsect)/s wsec/s # 每秒写扇区数。即 delta(wsect)/s rkB/s # 每秒读K字节数。是 rsect/s 的一半，因为每扇区大小为512字节。(需要计算) wkB/s # 每秒写K字节数。是 wsect/s 的一半。(需要计算) avgrq-sz # 平均每次设备I/O操作的数据大小 (扇区)。delta(rsect+wsect)/delta(rio+wio) avgqu-sz # 平均I/O队列长度。即 delta(aveq)/s/1000 (因为aveq的单位为毫秒)。 await # 平均每次设备I/O操作的等待时间 (毫秒)。即 delta(ruse+wuse)/delta(rio+wio) svctm # 平均每次设备I/O操作的服务时间 (毫秒)。即 delta(use)/delta(rio+wio) %util # 一秒中有百分之多少的时间用于 I/O 操作，或者说一秒中有多少时间 I/O 队列是非空的。即 delta(use)/s/1000 (因为use的单位为毫秒) IO性能衡量标准{ 1、 如果 %util 接近 100%，说明产生的I/O请求太多，I/O系统已经满负荷，该磁盘可能存在瓶颈。 2、 idle 小于70% IO压力就较大了,一般读取速度有较多的wait. 3、 同时可以结合 vmstat 查看查看b参数(等待资源的进程数)和wa参数(IO等待所占用的CPU时间的百分比,高过30%时IO压力高) 4、 svctm 一般要小于 await (因为同时等待的请求的等待时间被重复计算了),svctm 的大小一般和磁盘性能有关,CPU/内存的负荷也会对其有影响,请求过多也会间接导致 svctm 的增加. await 的大小一般取决于服务时间(svctm) 以及 I/O 队列的长度和 I/O 请求的发出模式. 如果 svctm 比较接近 await,说明 I/O 几乎没有等待时间;如果 await 远大于 svctm,说明 I/O 队列太长,应用得到的响应时间变慢,如果响应时间超过了用户可以容许的范围,这时可以考虑更换更快的磁盘,调整内核 elevator 算法,优化应用,或者升级 CPU 5、 队列长度(avgqu-sz)也可作为衡量系统 I/O 负荷的指标，但由于 avgqu-sz 是按照单位时间的平均值，所以不能反映瞬间的 I/O 洪水。 } } iotop{ # 监视进程磁盘I/O yum install iotop -o # 只显示有io操作的进程 -b # 批量显示，无交互，主要用作记录到文件。 -n NUM # 显示NUM次，主要用于非交互式模式。 -d SEC # 间隔SEC秒显示一次。 -p PID # 监控的进程pid。 -u USER # 监控的进程用户。 # 左右箭头：改变排序方式，默认是按IO排序。 r # 改变排序顺序。 o # 只显示有IO输出的进程。 p # 进程/线程的显示方式的切换。 a # 显示累积使用量。 q # 退出。 } 创建swap文件方法{ dd if=/dev/zero of=/swap bs=1024 count=4096000 # 创建一个足够大的文件 # count的值等于1024 x 你想要的文件大小, 4096000是4G mkswap /swap # 把这个文件变成swap文件 swapon /swap # 启用这个swap文件 /swap swap swap defaults 0 0 # 在每次开机的时候自动加载swap文件, 需要在 /etc/fstab 文件中增加一行 cat /proc/swaps # 查看swap swapoff -a # 关闭swap swapon -a # 开启swap } 新硬盘挂载{ fdisk /dev/sdc p # 打印分区 d # 删除分区 n # 创建分区，（一块硬盘最多4个主分区，扩展占一个主分区位置。p主分区 e扩展） w # 保存退出 mkfs.ext4 -L 卷标 /dev/sdc1 # 格式化相应分区 mount /dev/sdc1 /mnt # 挂载 vi /etc/fstab # 添加开机挂载分区 LABEL=/data /data ext4 defaults 1 2 # 用卷标挂载 /dev/sdb1 /data4 ext4 defaults 1 2 # 用真实分区挂载 /dev/sdb2 /data4 ext4 noatime,defaults 1 2 第一个数字\u0026quot;1\u0026quot;该选项被\u0026quot;dump\u0026quot;命令使用来检查一个文件系统应该以多快频率进行转储，若不需要转储就设置该字段为0 第二个数字\u0026quot;2\u0026quot;该字段被fsck命令用来决定在启动时需要被扫描的文件系统的顺序，根文件系统\u0026quot;/\u0026quot;对应该字段的值应该为1，其他文件系统应该为2。若该文件系统无需在启动时扫描则设置该字段为0 当以 noatime 选项加载（mount）文件系统时，对文件的读取不会更新文件属性中的atime信息。设置noatime的重要性是消除了文件系统对文件的写操作，文件只是简单地被系统读取。由于写操作相对读来说要更消耗系统资源，所以这样设置可以明显提高服务器的性能.wtime信息仍然有效，任何时候文件被写，该信息仍被更新。 mount -a # 自动加载 fstab 文件挂载，避免配置错误，系统无法重启 } 大磁盘2T和16T分区{ parted /dev/sdb # 针对磁盘分区 (parted) mklabel gpt # 设置为 gpt (parted) print (parted) mkpart primary 0KB 22.0TB # 指定分区大小 Is this still acceptable to you? Yes/No? Yes Ignore/Cancel? Ignore (parted) print Model: LSI MR9271-8i (scsi) Disk /dev/sdb: 22.0TB Sector size (logical/physical): 512B/512B Partition Table: gpt Number Start End Size File system Name Flags 1 17.4kB 22.0TB 22.0TB primary (parted) quit mkfs.ext4 /dev/sdb1 # e2fsprogs升级后支持大于16T硬盘 # 大于16T的单个分区ext4格式化报错，需要升级e2fsprogs Size of device /dev/sdb1 too big to be expressed in 32 bits using a blocksize of 4096. yum -y install xfsprogs mkfs.xfs -f /dev/sdb1 # 大于16T单个分区也可以使用XFS分区,但inode占用很大,对大量的小文件支持不太好 } 阿里云扩容磁盘{ # 进入ECS 本实例磁盘，勾选在线扩容, 选择扩容磁盘 yum install cloud-utils-growpart yum install xfsprogs df -h # 查看目前分区大小 fdisk -l # 查看磁盘设备 growpart /dev/vda 1 # 扩容分区 如果没有分区,默认整块,不需要执行 resize2fs /dev/vda1 # 扩容文件系统 ext4文件系统 xfs_growfs /dev/vda1 # 扩容文件系统 xfs文件系统 df -h # 再查看分区大小,是否扩容 } raid原理与区别{ raid0至少2块硬盘.吞吐量大,性能好,同时读写,但损坏一个就完蛋 raid1至少2块硬盘.相当镜像,一个存储,一个备份.安全性比较高.但是性能比0弱 raid5至少3块硬盘.分别存储校验信息和数据，坏了一个根据校验信息能恢复 raid6至少4块硬盘.两个独立的奇偶系统,可坏两块磁盘,写性能非常差 } }\n7 用户{\nusers # 显示所有的登录用户 groups # 列出当前用户和他所属的组 who -q # 显示所有的登录用户 groupadd # 添加组 useradd user # 建立用户 passwd username # 修改密码 userdel -r # 删除帐号及家目录 chown -R user:group # 修改目录拥有者(R递归) chown y\\.li:mysql # 修改所有者用户中包含点\u0026quot;.\u0026quot; umask # 设置用户文件和目录的文件创建缺省屏蔽值 chgrp # 修改用户组 finger # 查找用户显示信息 echo \u0026quot;xuesong\u0026quot; | passwd user --stdin # 非交互修改密码 useradd -g www -M -s /sbin/nologin www # 指定组并不允许登录的用户,nologin允许使用服务 useradd -g www -M -s /bin/false www # 指定组并不允许登录的用户,false最为严格 useradd -d /data/song -g song song # 创建用户并指定家目录和组 usermod -l newuser olduser # 修改用户名 usermod -g user group # 修改用户所属组 usermod -d dir -m user # 修改用户家目录 usermod -G group user # 将用户添加到附加组 gpasswd -d user group # 从组中删除用户 su - user -c \u0026quot; #cmd1; \u0026quot; # 切换用户执行 恢复密码{ # 即进入单用户模式: 在linux出现grub后，在安装的系统上面按\u0026quot;e\u0026quot;，然后出现grub的配置文件，按键盘移动光标到第二行\u0026quot;Ker……\u0026quot;，再按\u0026quot;e\u0026quot;，然后在这一行的结尾加上：空格 single或者空格1回车，然后按\u0026quot;b\u0026quot;重启，就进入了\u0026quot;单用户模式\u0026quot; } 特殊权限{ s或 S （SUID）：对应数值4 s或 S （SGID）：对应数值2 t或 T ：对应数值1 大S：代表拥有root权限，但是没有执行权限 小s：拥有特权且拥有执行权限，这个文件可以访问系统任何root用户可以访问的资源 T或T（Sticky）：/tmp和 /var/tmp目录供所有用户暂时存取文件，亦即每位用户皆拥有完整的权限进入该目录，去浏览、删除和移动文件 } }\n8 脚本{\n#!/bin/sh # 在脚本第一行脚本头 # sh为当前系统默认shell,可指定为bash等shell shopt # 显示和设置shell中的行为选项 sh -x # 执行过程 sh -n # 检查语法 set -e # 若指令传回值不等于0，则立即退出shell (a=bbk) # 括号创建子shell运行 basename /a/b/c # 从全路径中保留最后一层文件名或目录 dirname # 取路径 $RANDOM # 随机数 $$ # 进程号 source FileName # 在当前bash环境下读取并执行FileName中的命令 # 等同 . FileName sleep 5 # 间隔睡眠5秒 trap # 在接收到信号后将要采取的行动 trap \u0026quot;\u0026quot; 2 3 # 禁止ctrl+c $PWD # 当前目录 $HOME # 家目录 $OLDPWD # 之前一个目录的路径 cd - # 返回上一个目录路径 local ret # 局部变量 yes # 重复打印 yes |rm -i * # 自动回答y或者其他 ls -p /home # 区分目录和文件夹 ls -d /home/ # 查看匹配完整路径 time a.sh # 测试程序执行时间 echo -n aa;echo bb # 不换行执行下一句话 将字符串原样输出 echo -e \u0026quot;s\\tss\\n\\n\\n\u0026quot; # 使转义生效 echo $a | cut -c2-6 # 取字符串中字元 echo {a,b,c}{a,b,c}{a,b,c} # 排列组合(括号内一个元素分别和其他括号内元素组合) echo $((2#11010)) # 二进制转10进制 echo aaa | tee file # 打印同时写入文件 默认覆盖 -a追加 echo {1..10} # 打印10个字符 printf '%10s\\n'|tr \u0026quot; \u0026quot; a # 打印10个字符 pwd | awk -F/ '{ print $2 }' # 返回目录名 tac file |sed 1,3d|tac # 倒置读取文件 # 删除最后3行 tail -3 file # 取最后3行 outtmp=/tmp/$$`date +%s%N`.outtmp # 临时文件定义 :(){ :|:\u0026amp; };: # fork炸弹,系统执行海量的进程,直到系统僵死 echo -e \u0026quot;\\e[32mcolour\\e[0m\u0026quot; # 打印颜色 echo -e \u0026quot;\\033[32mcolour\\033[m\u0026quot; # 打印颜色 echo -e \u0026quot;\\033[0;31mL\\033[0;32mO\\033[0;33mV\\033[0;34mE\\t\\033[0;35mY\\033[0;36mO\\033[0;32mU\\e[m\u0026quot; # 打印颜色 正则表达式{ ^ # 行首定位 $ # 行尾定位 . # 匹配除换行符以外的任意字符 * # 匹配0或多个重复字符 + # 重复一次或更多次 ? # 重复零次或一次 ? # 结束贪婪因子 .*? 表示最小匹配 [] # 匹配一组中任意一个字符 [^] # 匹配不在指定组内的字符 \\ # 用来转义元字符 \u0026lt; # 词首定位符(支持vi和grep) \u0026lt;love \u0026gt; # 词尾定位符(支持vi和grep) love\u0026gt; x\\{m\\} # 重复出现m次 x\\{m,\\} # 重复出现至少m次 x\\{m,n\\} # 重复出现至少m次不超过n次 X? # 匹配出现零次或一次的大写字母 X X+ # 匹配一个或多个字母 X () # 括号内的字符为一组 (ab|de)+ # 匹配一连串的（最少一个） abc 或 def；abc 和 def 将匹配 [[:alpha:]] # 代表所有字母不论大小写 [[:lower:]] # 表示小写字母 [[:upper:]] # 表示大写字母 [[:digit:]] # 表示数字字符 [[:digit:][:lower:]] # 表示数字字符加小写字母 元字符{ \\d # 匹配任意一位数字 \\D # 匹配任意单个非数字字符 \\w # 匹配任意单个字母数字下划线字符，同义词是 [:alnum:] \\W # 匹配非数字型的字符 } 字符类:空白字符{ \\s # 匹配任意的空白符 \\S # 匹配非空白字符 \\b # 匹配单词的开始或结束 \\n # 匹配换行符 \\r # 匹配回车符 \\t # 匹配制表符 \\b # 匹配退格符 \\0 # 匹配空值字符 } 字符类:锚定字符{ \\b # 匹配字边界(不在[]中时) \\B # 匹配非字边界 \\A # 匹配字符串开头 \\Z # 匹配字符串或行的末尾 \\z # 只匹配字符串末尾 \\G # 匹配前一次m//g离开之处 } 捕获{ (exp) # 匹配exp,并捕获文本到自动命名的组里 (?\u0026lt;name\u0026gt;exp) # 匹配exp,并捕获文本到名称为name的组里，也可以写成(?'name'exp) (?:exp) # 匹配exp,不捕获匹配的文本，也不给此分组分配组号 } 零宽断言{ (?=exp) # 匹配exp前面的位置 (?\u0026lt;=exp) # 匹配exp后面的位置 (?!exp) # 匹配后面跟的不是exp的位置 (?\u0026lt;!exp) # 匹配前面不是exp的位置 (?#comment) # 注释不对正则表达式的处理产生任何影响，用于注释 } 特殊字符{ http://en.wikipedia.org/wiki/Ascii_table ^H \\010 \\b ^M \\015 \\r 匹配特殊字符: ctrl+V ctrl不放在按H或M 即可输出^H,用于匹配 } } 流程结构{ if判断{ if [ $a == $b ] then echo \u0026quot;等于\u0026quot; else echo \u0026quot;不等于\u0026quot; fi } case分支选择{ case $xs in 0) echo \u0026quot;0\u0026quot; ;; 1) echo \u0026quot;1\u0026quot; ;; *) echo \u0026quot;其他\u0026quot; ;; esac } while循环{ # while true 等同 while : # 读文件为整行读入 num=1 while [ $num -lt 10 ] do echo $num ((num=$num+2)) done ########################### grep a a.txt | while read a do echo $a done ########################### while read a do echo $a done \u0026lt; a.txt } for循环{ # 读文件已空格分隔 w=`awk -F \u0026quot;:\u0026quot; '{print $1}' c` for d in $w do $d done ########################### for ((i=0;i\u0026lt;${#o[*]};i++)) do echo ${o[$i]} done } until循环{ # 当command不为0时循环 until command do body done } 流程控制{ break N # 跳出几层循环 continue N # 跳出几层循环，循环次数不变 continue # 重新循环次数不变 } } 变量{ A=\u0026quot;a b c def\u0026quot; # 将字符串复制给变量 A=`cmd` # 将命令结果赋给变量 A=$(cmd) # 将命令结果赋给变量 eval a=\\$$a # 间接调用 i=2\u0026amp;\u0026amp;echo $((i+3)) # 计算后打印新变量结果 i=2\u0026amp;\u0026amp;echo $[i+3] # 计算后打印新变量结果 a=$((2\u0026gt;6?5:8)) # 判断两个值满足条件的赋值给变量 $1 $2 $* # 位置参数 *代表所有 env # 查看环境变量 env | grep \u0026quot;name\u0026quot; # 查看定义的环境变量 set # 查看环境变量和本地变量 read name # 输入变量 readonly name # 把name这个变量设置为只读变量,不允许再次设置 readonly # 查看系统存在的只读文件 export name # 变量name由本地升为环境 export name=\u0026quot;RedHat\u0026quot; # 直接定义name为环境变量 export Stat$nu=2222 # 变量引用变量赋值 unset name # 变量清除 export -n name # 去掉只读变量 shift # 用于移动位置变量,调整位置变量,使$3的值赋给$2.$2的值赋予$1 name + 0 # 将字符串转换为数字 number \u0026quot; \u0026quot; # 将数字转换成字符串 a='ee';b='a';echo ${!b} # 间接引用name变量的值 : ${a=\u0026quot;cc\u0026quot;} # 如果a有值则不改变,如果a无值则赋值a变量为cc 数组{ A=(a b c def) # 将变量定义为数組 ${#A[*]} # 数组个数 ${A[*]} # 数组所有元素,大字符串 ${A[@]} # 数组所有元素,类似列表可迭代 ${A[2]} # 脚本的一个参数或数组第三位 } 定义变量类型{ declare 或 typeset -r 只读(readonly一样) -i 整形 -a 数组 -f 函数 -x export declare -i n=0 } 系统变量{ $0 # 脚本启动名(包括路径) $n # 第n个参数,n=1,2,…9 $* # 所有参数列表(不包括脚本本身) $@ # 所有参数列表(独立字符串) $# # 参数个数(不包括脚本本身) $$ # 当前程式的PID $! # 执行上一个指令的PID $? # 执行上一个指令的返回值 } 变量引用技巧{ ${name:+value} # 如果设置了name,就把value显示,未设置则为空 ${name:-value} # 如果设置了name,就显示它,未设置就显示value ${name:?value} # 未设置提示用户错误信息value ${name:=value} # 如未设置就把value设置并显示\u0026lt;写入本地中\u0026gt; ${#A} # 可得到变量中字节 ${A:4:9} # 取变量中第4位到后面9位 ${A:(-1)} # 倒叙取最后一个字符 ${A/www/http} # 取变量并且替换每行第一个关键字 ${A//www/http} # 取变量并且全部替换每行关键字 定义了一个变量： file=/dir1/dir2/dir3/my.file.txt ${file#*/} # 去掉第一条 / 及其左边的字串：dir1/dir2/dir3/my.file.txt ${file##*/} # 去掉最后一条 / 及其左边的字串：my.file.txt ${file#*.} # 去掉第一个 . 及其左边的字串：file.txt ${file##*.} # 去掉最后一个 . 及其左边的字串：txt ${file%/*} # 去掉最后条 / 及其右边的字串：/dir1/dir2/dir3 ${file%%/*} # 去掉第一条 / 及其右边的字串：(空值) ${file%.*} # 去掉最后一个 . 及其右边的字串：/dir1/dir2/dir3/my.file ${file%%.*} # 去掉第一个 . 及其右边的字串：/dir1/dir2/dir3/my # # 是去掉左边(在键盘上 # 在 $ 之左边) # % 是去掉右边(在键盘上 % 在 $ 之右边) # 单一符号是最小匹配﹔两个符号是最大匹配 } } test条件判断{ # 符号 [ ] 等同 test命令 expression为字符串操作{ -n str # 字符串str是否不为空 -z str # 字符串str是否为空 } expression为文件操作{ -a # 并且，两条件为真 -b # 是否块文件 -p # 文件是否为一个命名管道 -c # 是否字符文件 -r # 文件是否可读 -d # 是否一个目录 -s # 文件的长度是否不为零 -e # 文件是否存在 -S # 是否为套接字文件 -f # 是否普通文件 -x # 文件是否可执行，则为真 -g # 是否设置了文件的 SGID 位 -u # 是否设置了文件的 SUID 位 -G # 文件是否存在且归该组所有 -w # 文件是否可写，则为真 -k # 文件是否设置了的粘贴位 -t fd # fd 是否是个和终端相连的打开的文件描述符（fd 默认为 1） -o # 或，一个条件为真 -O # 文件是否存在且归该用户所有 ! # 取反 } expression为整数操作{ expr1 -a expr2 # 如果 expr1 和 expr2 评估为真，则为真 expr1 -o expr2 # 如果 expr1 或 expr2 评估为真，则为真 } 两值比较{ 整数 字符串 -lt \u0026lt; # 小于 -gt \u0026gt; # 大于 -le \u0026lt;= # 小于或等于 -ge \u0026gt;= # 大于或等于 -eq == # 等于 -ne != # 不等于 } test 10 -lt 5 # 判断大小 echo $? # 查看上句test命令返回状态 # 结果0为真,1为假 test -n \u0026quot;hello\u0026quot; # 判断字符串长度是否为0 [ $? -eq 0 ] \u0026amp;\u0026amp; echo \u0026quot;success\u0026quot; || exit　# 判断成功提示,失败则退出 } 重定向{ # 标准输出 stdout 和 标准错误 stderr 标准输入stdin cmd 1\u0026gt; fiel # 把 标准输出 重定向到 file 文件中 cmd \u0026gt; file 2\u0026gt;\u0026amp;1 # 把 标准输出 和 标准错误 一起重定向到 file 文件中 cmd 2\u0026gt; file # 把 标准错误 重定向到 file 文件中 cmd 2\u0026gt;\u0026gt; file # 把 标准错误 重定向到 file 文件中(追加) cmd \u0026gt;\u0026gt; file 2\u0026gt;\u0026amp;1 # 把 标准输出 和 标准错误 一起重定向到 file 文件中(追加) cmd \u0026lt; file \u0026gt;file2 # cmd 命令以 file 文件作为 stdin(标准输入)，以 file2 文件作为 标准输出 cat \u0026lt;\u0026gt;file # 以读写的方式打开 file cmd \u0026lt; file cmd # 命令以 file 文件作为 stdin cmd \u0026lt;\u0026lt; delimiter cmd; #从 stdin 中读入，直至遇到 delimiter 分界符 delimiter\n\u0026gt;\u0026amp;n # 使用系统调用 dup (2) 复制文件描述符 n 并把结果用作标准输出 \u0026lt;\u0026amp;n # 标准输入复制自文件描述符 n \u0026lt;\u0026amp;- # 关闭标准输入（键盘） \u0026gt;\u0026amp;- # 关闭标准输出 n\u0026lt;\u0026amp;- # 表示将 n 号输入关闭 n\u0026gt;\u0026amp;- # 表示将 n 号输出关闭 } 运算符{ $[]等同于$(()) # $[]表示形式告诉shell求中括号中的表达式的值 ~var # 按位取反运算符,把var中所有的二进制为1的变为0,为0的变为1 var\\\u0026lt;\u0026lt;str # 左移运算符,把var中的二进制位向左移动str位,忽略最左端移出的各位,最右端的各位上补上0值,每做一次按位左移就有var乘2 var\u0026gt;\u0026gt;str # 右移运算符,把var中所有的二进制位向右移动str位,忽略最右移出的各位,最左的各位上补0,每次做一次右移就有实现var除以2 var\u0026amp;str # 与比较运算符,var和str对应位,对于每个二进制来说,如果二都为1,结果为1.否则为0 var^str # 异或运算符,比较var和str对应位,对于二进制来说如果二者互补,结果为1,否则为0 var|str # 或运算符,比较var和str的对应位,对于每个二进制来说,如二都该位有一个1或都是1,结果为1,否则为0 运算符优先级{ 级别 运算符 说明 1 =,+=,-=,/=,%=,*=,\u0026amp;=,^=,|=,\u0026lt;\u0026lt;=,\u0026gt;\u0026gt;= # 赋值运算符 2 || # 逻辑或 前面不成功执行 3 \u0026amp;\u0026amp; # 逻辑与 前面成功后执行 4 | # 按位或 5 ^ # 按位异或 6 \u0026amp; # 按位与 7 ==,!= # 等于/不等于 8 \u0026lt;=,\u0026gt;=,\u0026lt;,\u0026gt; # 小于或等于/大于或等于/小于/大于 9 \\\u0026lt;\u0026lt;,\u0026gt;\u0026gt; # 按位左移/按位右移 (无转意符号) 10 +,- # 加减 11 *,/,% # 乘,除,取余 12 ! ,~ # 逻辑非,按位取反或补码 13 -,+ # 正负 } } 数学运算{ $(( )) # 整数运算 + - * / ** # 分別为 \u0026quot;加、減、乘、除、密运算\u0026quot; \u0026amp; | ^ ! # 分別为 \u0026quot;AND、OR、XOR、NOT\u0026quot; 运算 % # 余数运算 let{ let # 运算 let x=16/4 let x=5**5 } expr{ expr 14 % 9 # 整数运算 SUM=`expr 2 \\* 3` # 乘后结果赋值给变量 LOOP=`expr $LOOP + 1` # 增量计数(加循环即可) LOOP=0 expr length \u0026quot;bkeep zbb\u0026quot; # 计算字串长度 expr substr \u0026quot;bkeep zbb\u0026quot; 4 9 # 抓取字串 expr index \u0026quot;bkeep zbb\u0026quot; e # 抓取第一个字符数字串出现的位置 expr 30 / 3 / 2 # 运算符号有空格 expr bkeep.doc : '.*' # 模式匹配(可以使用expr通过指定冒号选项计算字符串中字符数) expr bkeep.doc : '\\(.*\\).doc' # 在expr中可以使用字符串匹配操作，这里使用模式抽取.doc文件附属名 数值测试{ #如果试图计算非整数，则会返回错误 rr=3.4 expr $rr + 1 expr: non-numeric argument rr=5 expr $rr + 1 6 } } bc{ echo \u0026quot;m^n\u0026quot;|bc # 次方计算 seq -s '+' 1000 |bc # 从1加到1000 seq 1 1000 |tr \u0026quot;\\n\u0026quot; \u0026quot;+\u0026quot;|sed 's/+$/\\n/'|bc # 从1加到1000 } } grep{ -c # 显示匹配到得行的数目，不显示内容 -h # 不显示文件名 -i # 忽略大小写 -l # 只列出匹配行所在文件的文件名 -n # 在每一行中加上相对行号 -s # 无声操作只显示报错，检查退出状态 -v # 反向查找 -e # 使用正则表达式 -w # 精确匹配 -wc # 精确匹配次数 -o # 查询所有匹配字段 -P # 使用perl正则表达式 -A3 # 打印匹配行和下三行 -B3 # 打印匹配行和上三行 -C3 # 打印匹配行和上下三行 grep -v \u0026quot;a\u0026quot; txt # 过滤关键字符行 grep -w 'a\\\u0026gt;' txt # 精确匹配字符串 grep -i \u0026quot;a\u0026quot; txt # 大小写敏感 grep \u0026quot;a[bB]\u0026quot; txt # 同时匹配大小写 grep '[0-9]\\{3\\}' txt # 查找0-9重复三次的所在行 grep -E \u0026quot;word1|word2|word3\u0026quot; file # 任意条件匹配 grep word1 file | grep word2 |grep word3 # 同时匹配三个 echo quan@163.com |grep -Po '(?\u0026lt;=@.).*(?=.$)' # 零宽断言截取字符串 #　63.co echo \u0026quot;I'm singing while you're dancing\u0026quot; |grep -Po '\\b\\w+(?=ing\\b)' # 零宽断言匹配 echo 'Rx Optical Power: -5.01dBm, Tx Optical Power: -2.41dBm' |grep -Po '(?\u0026lt;=:).*?(?=d)' # 取出d前面数字 # ?为最小匹配 echo 'Rx Optical Power: -5.01dBm, Tx Optical Power: -2.41dBm' | grep -Po '[-0-9.]+' # 取出d前面数字 # ?为最小匹配 echo '[\u0026quot;mem\u0026quot;,ok],[\u0026quot;hardware\u0026quot;,false],[\u0026quot;filesystem\u0026quot;,false]' |grep -Po '[^\u0026quot;]+(?=\u0026quot;,false)' # 取出false前面的字母 echo '[\u0026quot;mem\u0026quot;,ok],[\u0026quot;hardware\u0026quot;,false],[\u0026quot;filesystem\u0026quot;,false]' |grep -Po '\\w+\u0026quot;,false'|grep -Po '^\\w+' # 取出false前面的字母 grep用于if判断{ if echo abc | grep \u0026quot;a\u0026quot; \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 then echo \u0026quot;abc\u0026quot; else echo \u0026quot;null\u0026quot; fi } } tr{ -c # 用字符串1中字符集的补集替换此字符集，要求字符集为ASCII -d # 删除字符串1中所有输入字符 -s # 删除所有重复出现字符序列，只保留第一个:即将重复出现字符串压缩为一个字符串 [a-z] # a-z内的字符组成的字符串 [A-Z] # A-Z内的字符组成的字符串 [0-9] # 数字串 \\octal # 一个三位的八进制数，对应有效的ASCII字符 [O*n] # 表示字符O重复出现指定次数n。因此[O*2]匹配OO的字符串 tr中特定控制字符表达方式{ \\a Ctrl-G \\007 # 铃声 \\b Ctrl-H \\010 # 退格符 \\f Ctrl-L \\014 # 走行换页 \\n Ctrl-J \\012 # 新行 \\r Ctrl-M \\015 # 回车 \\t Ctrl-I \\011 # tab键 \\v Ctrl-X \\030 } tr A-Z a-z # 将所有大写转换成小写字母 tr \u0026quot; \u0026quot; \u0026quot;\\n\u0026quot; # 将空格替换为换行 tr -s \u0026quot;[\\012]\u0026quot; \u0026lt; plan.txt # 删除空行 tr -s [\u0026quot;\\n\u0026quot;] \u0026lt; plan.txt # 删除空行 tr -s \u0026quot;[\\015]\u0026quot; \u0026quot;[\\n]\u0026quot; \u0026lt; file # 删除文件中的^M，并代之以换行 tr -s \u0026quot;[\\r]\u0026quot; \u0026quot;[\\n]\u0026quot; \u0026lt; file # 删除文件中的^M，并代之以换行 tr -s \u0026quot;[:]\u0026quot; \u0026quot;[\\011]\u0026quot; \u0026lt; /etc/passwd # 替换passwd文件中所有冒号，代之以tab键 tr -s \u0026quot;[:]\u0026quot; \u0026quot;[\\t]\u0026quot; \u0026lt; /etc/passwd # 替换passwd文件中所有冒号，代之以tab键 echo $PATH | tr \u0026quot;:\u0026quot; \u0026quot;\\n\u0026quot; # 增加显示路径可读性 1,$!tr -d '\\t' # tr在vi内使用，在tr前加处理行范围和感叹号('$'表示最后一行) tr \u0026quot;\\r\u0026quot; \u0026quot;\\n\u0026quot;\u0026lt;macfile \u0026gt; unixfile # Mac -\u0026gt; UNIX tr \u0026quot;\\n\u0026quot; \u0026quot;\\r\u0026quot;\u0026lt;unixfile \u0026gt; macfile # UNIX -\u0026gt; Mac tr -d \u0026quot;\\r\u0026quot;\u0026lt;dosfile \u0026gt; unixfile # DOS -\u0026gt; UNIX Microsoft DOS/Windows 约定，文本的每行以回车字符(\\r)并后跟换行符(\\n)结束 awk '{ print $0\u0026quot;\\r\u0026quot; }'\u0026lt;unixfile \u0026gt; dosfile # UNIX -\u0026gt; DOS：在这种情况下，需要用awk，因为tr不能插入两个字符来替换一个字符 } seq{ # 不指定起始数值，则默认为 1 -s # 选项主要改变输出的分格符, 预设是 \\n -w # 等位补全，就是宽度相等，不足的前面补 0 -f # 格式化输出，就是指定打印的格式 seq 10 100 # 列出10-100 seq 1 10 |tac # 倒叙列出 seq -s '+' 90 100 |bc # 从90加到100 seq -f 'dir%g' 1 10 | xargs mkdir # 创建dir1-10 seq -f 'dir%03g' 1 10 | xargs mkdir # 创建dir001-010 } trap{ 信号 说明 HUP(1) # 挂起，通常因终端掉线或用户退出而引发 INT(2) # 中断，通常因按下Ctrl+C组合键而引发 QUIT(3) # 退出，通常因按下Ctrl+\\组合键而引发 ABRT(6) # 中止，通常因某些严重的执行错误而引发 ALRM(14) # 报警，通常用来处理超时 TERM(15) # 终止，通常在系统关机时发送 trap捕捉到信号之后，可以有三种反应方式： 1、执行一段程序来处理这一信号 2、接受信号的默认操作 3、忽视这一信号 第一种形式的trap命令在shell接收到 signal list 清单中数值相同的信号时，将执行双引号中的命令串： trap 'commands' signal-list # 单引号，要在shell探测到信号来的时候才执行命令和变量的替换，时间一直变 trap \u0026quot;commands\u0026quot; signal-list # 双引号，shell第一次设置信号的时候就执行命令和变量的替换，时间不变 } awk{ # 默认是执行打印全部 print $0 # 1为真 打印$0 # 0为假 不打印 -F # 改变FS值(分隔符) ~ # 域匹配 == # 变量匹配 !~ # 匹配不包含 = # 赋值 != # 不等于 += # 叠加 \\b # 退格 \\f # 换页 \\n # 换行 \\r # 回车 \\t # 制表符Tab \\c # 代表任一其他字符 -F\u0026quot;[ ]+|[%]+\u0026quot; # 多个空格或多个%为分隔符 [a-z]+ # 多个小写字母 [a-Z] # 代表所有大小写字母(aAbB...zZ) [a-z] # 代表所有大小写字母(ab...z) [:alnum:] # 字母数字字符 [:alpha:] # 字母字符 [:cntrl:] # 控制字符 [:digit:] # 数字字符 [:graph:] # 非空白字符(非空格、控制字符等) [:lower:] # 小写字母 [:print:] # 与[:graph:]相似，但是包含空格字符 [:punct:] # 标点字符 [:space:] # 所有的空白字符(换行符、空格、制表符) [:upper:] # 大写字母 [:xdigit:] # 十六进制的数字(0-9a-fA-F) [[:digit:][:lower:]] # 数字和小写字母(占一个字符) 内建变量{ $n # 当前记录的第 n 个字段，字段间由 FS 分隔 $0 # 完整的输入记录 ARGC # 命令行参数的数目 ARGIND # 命令行中当前文件的位置 ( 从 0 开始算 ) ARGV # 包含命令行参数的数组 CONVFMT # 数字转换格式 ( 默认值为 %.6g) ENVIRON # 环境变量关联数组 ERRNO # 最后一个系统错误的描述 FIELDWIDTHS # 字段宽度列表 ( 用空格键分隔 ) FILENAME # 当前文件名 FNR # 同 NR ，但相对于当前文件 FS # 字段分隔符 ( 默认是任何空格 ) IGNORECASE # 如果为真（即非 0 值），则进行忽略大小写的匹配 NF # 当前记录中的字段数(列) NR # 当前行数 OFMT # 数字的输出格式 ( 默认值是 %.6g) OFS # 输出字段分隔符 ( 默认值是一个空格 ) ORS # 输出记录分隔符 ( 默认值是一个换行符 ) RLENGTH # 由 match 函数所匹配的字符串的长度 RS # 记录分隔符 ( 默认是一个换行符 ) RSTART # 由 match 函数所匹配的字符串的第一个位置 SUBSEP # 数组下标分隔符 ( 默认值是 /034) BEGIN # 先处理(可不加文件参数) END # 结束时处理 } 内置函数{ gsub(r,s) # 在整个$0中用s替代r 相当于 sed 's///g' gsub(r,s,t) # 在整个t中用s替代r index(s,t) # 返回s中字符串t的第一位置 length(s) # 返回s长度 match(s,r) # 测试s是否包含匹配r的字符串 split(s,a,fs) # 在fs上将s分成序列a sprint(fmt,exp) # 返回经fmt格式化后的exp sub(r,s) # 用$0中最左边最长的子串代替s 相当于 sed 's///' substr(s,p) # 返回字符串s中从p开始的后缀部分 substr(s,p,n) # 返回字符串s中从p开始长度为n的后缀部分 } awk判断{ awk '{print ($1\u0026gt;$2)?\u0026quot;第一排\u0026quot;$1:\u0026quot;第二排\u0026quot;$2}' # 条件判断 括号代表if语句判断 \u0026quot;?\u0026quot;代表then \u0026quot;:\u0026quot;代表else awk '{max=($1\u0026gt;$2)? $1 : $2; print max}' # 条件判断 如果$1大于$2,max值为为$1,否则为$2 awk '{if ( $6 \u0026gt; 50) print $1 \u0026quot; Too high\u0026quot; ;\\ else print \u0026quot;Range is OK\u0026quot;}' file awk '{if ( $6 \u0026gt; 50) { count++;print $3 } \\ else { x+5; print $2 } }' file } awk循环{ awk '{i = 1; while ( i \u0026lt;= NF ) { print NF, $i ; i++ } }' file awk '{ for ( i = 1; i \u0026lt;= NF; i++ ) print NF,$i }' file } awk '/Tom/' file # 打印匹配到得行 awk '/^Tom/{print $1}' # 匹配Tom开头的行 打印第一个字段 awk '$1 !~ /ly$/' # 显示所有第一个字段不是以ly结尾的行 awk '$3 \u0026lt;40' # 如果第三个字段值小于40才打印 awk '$4==90{print $5}' # 取出第四列等于90的第五列 awk '/^(no|so)/' test # 打印所有以模式no或so开头的行 awk '$3 * $4 \u0026gt; 500' # 算术运算(第三个字段和第四个字段乘积大于500则显示) awk '{print NR\u0026quot; \u0026quot;$0}' # 加行号 awk '/tom/,/suz/' # 打印tom到suz之间的行 awk '{a+=$1}END{print a}' # 列求和 awk 'sum+=$1{print sum}' # 将$1的值叠加后赋给sum awk '{a+=$1}END{print a/NR}' # 列求平均值 awk '!s[$1 $3]++' file # 根据第一列和第三列过滤重复行 awk -F'[ :\\t]' '{print $1,$2}' # 以空格、:、制表符Tab为分隔符 awk '{print \u0026quot;'\u0026quot;$a\u0026quot;'\u0026quot;,\u0026quot;'\u0026quot;$b\u0026quot;'\u0026quot;}' # 引用外部变量 awk '{if(NR==52){print;exit}}' # 显示第52行 awk '/关键字/{a=NR+2}a==NR {print}' # 取关键字下第几行 awk 'gsub(/liu/,\u0026quot;aaaa\u0026quot;,$1){print $0}' # 只打印匹配替换后的行 ll | awk -F'[ ]+|[ ][ ]+' '/^$/{print $8}' # 提取时间,空格不固定 awk '{$1=\u0026quot;\u0026quot;;$2=\u0026quot;\u0026quot;;$3=\u0026quot;\u0026quot;;print}' # 去掉前三列 echo aada:aba|awk '/d/||/b/{print}' # 匹配两内容之一 echo aada:abaa|awk -F: '$1~/d/||$2~/b/{print}' # 关键列匹配两内容之一 echo Ma asdas|awk '$1~/^[a-Z][a-Z]$/{print }' # 第一个域匹配正则 echo aada:aaba|awk '/d/\u0026amp;\u0026amp;/b/{print}' # 同时匹配两条件 awk 'length($1)==\u0026quot;4\u0026quot;{print $1}' # 字符串位数 awk '{if($2\u0026gt;3){system (\u0026quot;touch \u0026quot;$1)}}' # 执行系统命令 awk '{sub(/Mac/,\u0026quot;Macintosh\u0026quot;,$0);print}' # 用Macintosh替换Mac awk '{gsub(/Mac/,\u0026quot;MacIntosh\u0026quot;,$1); print}' # 第一个域内用Macintosh替换Mac awk -F '' '{ for(i=1;i\u0026lt;NF+1;i++)a+=$i ;print a}' # 多位数算出其每位数的总和.比如 1234， 得到 10 awk '{ i=$1%10;if ( i == 0 ) {print i}}' # 判断$1是否整除(awk中定义变量引用时不能带 $ ) awk 'BEGIN{a=0}{if ($1\u0026gt;a) a=$1 fi}END{print a}' # 列求最大值 设定一个变量开始为0，遇到比该数大的值，就赋值给该变量，直到结束 awk 'BEGIN{a=11111}{if ($1\u0026lt;a) a=$1 fi}END{print a}' # 求最小值 awk '{if(A)print;A=0}/regexp/{A=1}' # 查找字符串并将匹配行的下一行显示出来，但并不显示匹配行 awk '/regexp/{print A}{A=$0}' # 查找字符串并将匹配行的上一行显示出来，但并不显示匹配行 awk '{if(!/mysql/)gsub(/1/,\u0026quot;a\u0026quot;);print $0}' # 将1替换成a，并且只在行中未出现字串mysql的情况下替换 awk 'BEGIN{srand();fr=int(100*rand());print fr;}' # 获取随机数 awk '{if(NR==3)F=1}{if(F){i++;if(i%7==1)print}}' # 从第3行开始，每7行显示一次 awk '{if(NF\u0026lt;1){print i;i=0} else {i++;print $0}}' # 显示空行分割各段的行数 echo +null:null |awk -F: '$1!~\u0026quot;^+\u0026quot;\u0026amp;\u0026amp;$2!=\u0026quot;null\u0026quot;{print $0}' # 关键列同时匹配 awk -v RS=@ 'NF{for(i=1;i\u0026lt;=NF;i++)if($i) printf $i;print \u0026quot;\u0026quot;}' # 指定记录分隔符 awk '{b[$1]=b[$1]$2}END{for(i in b){print i,b[i]}}' # 列叠加 awk '{ i=($1%100);if ( $i \u0026gt;= 0 ) {print $0,$i}}' # 求余数 awk '{b=a;a=$1; if(NR\u0026gt;1){print a-b}}' # 当前行减上一行 awk '{a[NR]=$1}END{for (i=1;i\u0026lt;=NR;i++){print a[i]-a[i-1]}}' # 当前行减上一行 awk -F: '{name[x++]=$1};END{for(i=0;i\u0026lt;NR;i++)print i,name[i]}' # END只打印最后的结果,END块里面处理数组内容 awk '{sum2+=$2;count=count+1}END{print sum2,sum2/count}' # $2的总和 $2总和除个数(平均值) awk -v a=0 -F 'B' '{for (i=1;i\u0026lt;NF;i++){ a=a+length($i)+1;print a }}' # 打印所以B的所在位置 awk 'BEGIN{ \u0026quot;date\u0026quot; | getline d; split(d,mon) ; print mon[2]}' file # 将date值赋给d，并将d设置为数组mon，打印mon数组中第2个元素 awk 'BEGIN{info=\u0026quot;this is a test2010test!\u0026quot;;print substr(info,4,10);}' # 截取字符串(substr使用) awk 'BEGIN{info=\u0026quot;this is a test2010test!\u0026quot;;print index(info,\u0026quot;test\u0026quot;)?\u0026quot;ok\u0026quot;:\u0026quot;no found\u0026quot;;}' # 匹配字符串(index使用) awk 'BEGIN{info=\u0026quot;this is a test2010test!\u0026quot;;print match(info,/[0-9]+/)?\u0026quot;ok\u0026quot;:\u0026quot;no found\u0026quot;;}' # 正则表达式匹配查找(match使用) awk '{for(i=1;i\u0026lt;=4;i++)printf $i\u0026quot;\u0026quot;FS; for(y=10;y\u0026lt;=13;y++) printf $y\u0026quot;\u0026quot;FS;print \u0026quot;\u0026quot;}' # 打印前4列和后4列 awk 'BEGIN{for(n=0;n++\u0026lt;9;){for(i=0;i++\u0026lt;n;)printf i\u0026quot;x\u0026quot;n\u0026quot;=\u0026quot;i*n\u0026quot; \u0026quot;;print \u0026quot;\u0026quot;}}' # 乘法口诀 awk 'BEGIN{info=\u0026quot;this is a test\u0026quot;;split(info,tA,\u0026quot; \u0026quot;);print length(tA);for(k in tA){print k,tA[k];}}' # 字符串分割(split使用) awk '{if (system (\u0026quot;grep \u0026quot;$2\u0026quot; tmp/* \u0026gt; /dev/null 2\u0026gt;\u0026amp;1\u0026quot;) == 0 ) {print $1,\u0026quot;Y\u0026quot;} else {print $1,\u0026quot;N\u0026quot;} }' a # 执行系统命令判断返回状态 awk '{for(i=1;i\u0026lt;=NF;i++) a[i,NR]=$i}END{for(i=1;i\u0026lt;=NF;i++) {for(j=1;j\u0026lt;=NR;j++) printf a[i,j] \u0026quot; \u0026quot;;print \u0026quot;\u0026quot;}}' # 将多行转多列 netstat -an|awk -v A=$IP -v B=$PORT 'BEGIN{print \u0026quot;Clients\\tGuest_ip\u0026quot;}$4~A\u0026quot;:\u0026quot;B{split($5,ip,\u0026quot;:\u0026quot;);a[ip[1]]++}END{for(i in a)print a[i]\u0026quot;\\t\u0026quot;i|\u0026quot;sort -nr\u0026quot;}' # 统计IP连接个数 cat 1.txt|awk -F\u0026quot; # \u0026quot; '{print \u0026quot;insert into user (user,password,email)values(\u0026quot;\u0026quot;'\\''\u0026quot;$1\u0026quot;'\\'\\,'\u0026quot;\u0026quot;'\\''\u0026quot;$2\u0026quot;'\\'\\,'\u0026quot;\u0026quot;'\\''\u0026quot;$3\u0026quot;'\\'\\)\\;'\u0026quot;}' \u0026gt;\u0026gt;insert_1.txt # 处理sql语句 awk 'BEGIN{printf \u0026quot;what is your name?\u0026quot;;getline name \u0026lt; \u0026quot;/dev/tty\u0026quot; } $1 ~name {print \u0026quot;FOUND\u0026quot; name \u0026quot; on line \u0026quot;, NR \u0026quot;.\u0026quot;} END{print \u0026quot;see you,\u0026quot; name \u0026quot;.\u0026quot;}' file # 两文件匹配 取本机IP{ /sbin/ifconfig |awk -v RS=\u0026quot;Bcast:\u0026quot; '{print $NF}'|awk -F: '/addr/{print $2}' /sbin/ifconfig |awk '/inet/\u0026amp;\u0026amp;$2!~\u0026quot;127.0.0.1\u0026quot;{split($2,a,\u0026quot;:\u0026quot;);print a[2]}' /sbin/ifconfig |awk -v RS='inet addr:' '$1!=\u0026quot;eth0\u0026quot;\u0026amp;\u0026amp;$1!=\u0026quot;127.0.0.1\u0026quot;{print $1}'|awk '{printf\u0026quot;%s|\u0026quot;,$0}' /sbin/ifconfig |awk '{printf(\u0026quot;line %d,%s\\n\u0026quot;,NR,$0)}' # 指定类型(%d数字,%s字符) } 查看磁盘空间{ df -h|awk -F\u0026quot;[ ]+|%\u0026quot; '$5\u0026gt;14{print $5}' df -h|awk 'NR!=1{if ( NF == 6 ) {print $5} else if ( NF == 5) {print $4} }' df -h|awk 'NR!=1 \u0026amp;\u0026amp; /%/{sub(/%/,\u0026quot;\u0026quot;);print $(NF-1)}' df -h|sed '1d;/ /!N;s/\\n//;s/ \\+/ /;' #将磁盘分区整理成一行 可直接用 df -P } 排列打印{ awk 'END{printf \u0026quot;%-10s%-10s\\n%-10s%-10s\\n%-10s%-10s\\n\u0026quot;,\u0026quot;server\u0026quot;,\u0026quot;name\u0026quot;,\u0026quot;123\u0026quot;,\u0026quot;12345\u0026quot;,\u0026quot;234\u0026quot;,\u0026quot;1234\u0026quot;}' txt awk 'BEGIN{printf \u0026quot;|%-10s|%-10s|\\n|%-10s|%-10s|\\n|%-10s|%-10s|\\n\u0026quot;,\u0026quot;server\u0026quot;,\u0026quot;name\u0026quot;,\u0026quot;123\u0026quot;,\u0026quot;12345\u0026quot;,\u0026quot;234\u0026quot;,\u0026quot;1234\u0026quot;}' awk 'BEGIN{ print \u0026quot; *** 开 始 *** \u0026quot;; print \u0026quot;+-----------------+\u0026quot;; printf \u0026quot;|%-5s|%-5s|%-5s|\\n\u0026quot;,\u0026quot;id\u0026quot;,\u0026quot;name\u0026quot;,\u0026quot;ip\u0026quot;; } $1!=1 \u0026amp;\u0026amp; NF==4{printf \u0026quot;|%-5s|%-5s|%-5s|\\n\u0026quot;,$1,$2,$3\u0026quot; \u0026quot;$11} END{ print \u0026quot;+-----------------+\u0026quot;; print \u0026quot; *** 结 束 *** \u0026quot; }' txt } awk经典题{ 分析图片服务日志，把日志（每个图片访问次数*图片大小的总和）排行，也就是计算每个url的总访问大小 说明：本题生产环境应用：这个功能可以用于IDC网站流量带宽很高，然后通过分析服务器日志哪些元素占用流量过大，进而进行优化或裁剪该图片，压缩js等措施。 本题需要输出三个指标： 【被访问次数】 【访问次数*单个被访问文件大小】 【文件名（带URL）】 测试数据 59.33.26.105 - - [08/Dec/2010:15:43:56 +0800] \u0026quot;GET /static/images/photos/2.jpg HTTP/1.1\u0026quot; 200 11299 awk '{array_num[$7]++;array_size[$7]+=$10}END{for(i in array_num) {print array_num[i]\u0026quot; \u0026quot;array_size[i]\u0026quot; \u0026quot;i}}' } awk练习题{ wang 4 cui 3 zhao 4 liu 3 liu 3 chang 5 li 2 1 通过第一个域找出字符长度为4的 2 当第二列值大于3时，创建空白文件，文件名为当前行第一个域$1 (touch $1) 3 将文档中 liu 字符串替换为 hong 4 求第二列的和 5 求第二列的平均值 6 求第二列中的最大值 7 将第一列过滤重复后，列出每一项，每一项的出现次数，每一项的大小总和 1、字符串长度 awk 'length($1)==\u0026quot;4\u0026quot;{print $1}' 2、执行系统命令 awk '{if($2\u0026gt;3){system (\u0026quot;touch \u0026quot;$1)}}' 3、gsub(/r/,\u0026quot;s\u0026quot;,域) 在指定域(默认$0)中用s替代r (sed 's///g') awk '{gsub(/liu/,\u0026quot;hong\u0026quot;,$1);print $0}' a.txt 4、列求和 awk '{a+=$2}END{print a}' 5、列求平均值 awk '{a+=$2}END{print a/NR}' awk '{a+=$2;b++}END{print a,a/b}' 6、列求最大值 awk 'BEGIN{a=0}{if($2\u0026gt;a) a=$2 }END{print a}' 7、将第一列过滤重复列出每一项，每一项的出现次数，每一项的大小总和 awk '{a[$1]++;b[$1]+=$2}END{for(i in a){print i,a[i],b[i]}}' } awk处理复杂日志{ 6.19： DHB_014_号百总机服务业务日报：广州 到达数异常！ DHB_023_号百漏话提醒日报：珠海 到达数异常！ 6.20： DHB_014_号百总机服务业务日报：广州 到达数异常！到 awk -F '[_ ：]+' 'NF\u0026gt;2{print $4,$1\u0026quot;_\u0026quot;$2,b |\u0026quot;sort\u0026quot;;next}{b=$1}' # 当前行NF小于等于2 只针对{print $4,$1\u0026quot;_\u0026quot;$2,b |\u0026quot;sort\u0026quot;;next} 有效 即 6.19：行跳过此操作, {b=$1} 仍然执行 # 当前行NF大于2 执行到 next 强制跳过本行，即跳过后面的 {b=$1} 广州 DHB_014 6.19 } } sed{ # 先读取资料、存入模式空间、对其进行编辑、再输出、再用下一行替换模式空间内容 # 调试工具sedsed (参数 -d) http://aurelio.net/sedsed/sedsed-1.0 -n # 输出由编辑指令控制(取消默认的输出,必须与编辑指令一起配合) -i # 直接对文件操作 -e # 多重编辑 -r # 正则可不转移特殊字符 b # 跳过匹配的行 p # 打印 d # 删除 s # 替换 g # 配合s全部替换 i # 行前插入 a # 行后插入 r # 读 y # 转换 q # 退出 \u0026amp; # 代表查找的串内容 * # 任意多个 前驱字符(前导符) ? # 0或1个 最小匹配 没加-r参数需转义 \\? $ # 最后一行 .* # 匹配任意多个字符 \\(a\\) # 保存a作为标签1(\\1) 模式空间{ # 模式空间(两行两行处理) 模式匹配的范围，一般而言，模式空间是输入文本中某一行，但是可以通过使用N函数把多于一行读入模式空间 # 暂存空间里默认存储一个空行 n # 读入下一行(覆盖上一行) h # 把模式空间里的行拷贝到暂存空间 H # 把模式空间里的行追加到暂存空间 g # 用暂存空间的内容替换模式空间的行 G # 把暂存空间的内容追加到模式空间的行后 x # 将暂存空间的内容于模式空间里的当前行互换 ！ # 对其前面的要匹配的范围取反 D # 删除当前模式空间中直到并包含第一个换行符的所有字符(/.*/匹配模式空间中所有内容，匹配到就执行D,没匹配到就结束D) N # 追加下一个输入行到模式空间后面并在第二者间嵌入一个换行符，改变当前行号码,模式匹配可以延伸跨域这个内嵌换行 p # 打印模式空间中的直到并包含第一个换行的所有字符 } 标签函数{ : lable # 建立命令标记，配合b，t函数使用跳转 b lable # 分支到脚本中带有标记的地方，如果分支不存在则分支到脚本的末尾。 t labe # 判断分支，从最后一行开始，条件一旦满足或者T,t命令，将导致分支到带有标号的命令出，或者到脚本末尾。与b函数不同在于t在执行跳转前会先检查其前一个替换命令是否成功，如成功，则执行跳转。 sed -e '{:p1;/A/s/A/AA/;/B/s/B/BB/;/[AB]\\{10\\}/b;b p1;}' # 文件内容第一行A第二行B:建立标签p1;两个替换函数(A替换成AA,B替换成BB)当A或者B达到10个以后调用b,返回 echo 'sd f f [a b c cddd eee]' | sed ':n;s#\\(\\[[^ ]*\\) *#\\1#;tn' # 标签函数t使用方法,替换[]里的空格 echo \u0026quot;198723124.03\u0026quot;|sed -r ':a;s/([0-9]+)([0-9]{3})/\\1,\\2/;ta' # 每三个字符加一个逗号 } 引用外部变量{ sed -n ''$a',10p' sed -n \u0026quot;\u0026quot;$a\u0026quot;,10p\u0026quot; } sed 10q # 显示文件中的前10行 (模拟\u0026quot;head\u0026quot;) sed -n '$=' # 计算行数(模拟 \u0026quot;wc -l\u0026quot;) sed -n '5,/^no/p' # 打印从第5行到以no开头行之间的所有行 sed -i \u0026quot;/^$f/d\u0026quot; a # 删除匹配行 sed -i '/aaa/,$d' # 删除匹配行到末尾 sed -i \u0026quot;s/=/:/\u0026quot; c # 直接对文本替换 sed -i \u0026quot;/^pearls/s/$/j/\u0026quot; # 找到pearls开头在行尾加j sed '/1/,/3/p' file # 打印1和3之间的行 sed -n '1p' file # 取出指定行 sed '5i\\aaa' file # 在第5行之前插入行 sed '5a\\aaa' file # 在第5行之后抽入行 echo a|sed -e '/a/i\\b' # 在匹配行前插入一行 echo a|sed -e '/a/a\\b' # 在匹配行后插入一行 echo a|sed 's/a/\u0026amp;\\nb/g' # 在匹配行后插入一行 seq 10| sed -e{1,3}'s/./a/' # 匹配1和3行替换 sed -n '/regexp/!p' # 只显示不匹配正则表达式的行 sed '/regexp/d' # 只显示不匹配正则表达式的行 sed '$!N;s/\\n//' # 将每两行连接成一行 sed '/baz/s/foo/bar/g' # 只在行中出现字串\u0026quot;baz\u0026quot;的情况下将\u0026quot;foo\u0026quot;替换成\u0026quot;bar\u0026quot; sed '/baz/!s/foo/bar/g' # 将\u0026quot;foo\u0026quot;替换成\u0026quot;bar\u0026quot;，并且只在行中未出现字串\u0026quot;baz\u0026quot;的情况下替换 echo a|sed -e 's/a/#\u0026amp;/g' # 在a前面加#号 sed 's/foo/bar/4' # 只替换每一行中的第四个字串 sed 's/\\(.*\\)foo/\\1bar/' # 替换每行最后一个字符串 sed 's/\\(.*\\)foo\\(.*foo\\)/\\1bar\\2/' # 替换倒数第二个字符串 sed 's/[0-9][0-9]$/\u0026amp;5' # 在以[0-9][0-9]结尾的行后加5 sed -n ' /^eth\\|em[01][^:]/{n;p;}' # 匹配多个关键字 sed -n -r ' /eth|em[01][^:]/{n;p;}' # 匹配多个关键字 echo -e \u0026quot;1\\n2\u0026quot;|xargs -i -t sed 's/^/1/' {} # 同时处理多个文件 sed '/west/,/east/s/$/*VACA*/' # 修改west和east之间的所有行，在结尾处加*VACA* sed 's/[^1-9]*\\([0-9]\\+\\).*/\\1/' # 取出第一组数字，并且忽略掉开头的0 sed -n '/regexp/{g;1!p;};h' # 查找字符串并将匹配行的上一行显示出来，但并不显示匹配行 sed -n ' /regexp/{n;p;}' # 查找字符串并将匹配行的下一行显示出来，但并不显示匹配行 sed -n 's/\\(mar\\)got/\\1ianne/p' # 保存\\(mar\\)作为标签1 sed -n 's/\\([0-9]\\+\\).*\\(t\\)/\\2\\1/p' # 保存多个标签 sed -i -e '1,3d' -e 's/1/2/' # 多重编辑(先删除1-3行，在将1替换成2) sed -e 's/@.*//g' -e '/^$/d' # 删除掉@后面所有字符，和空行 sed -n -e \u0026quot;{s/^ *[0-9]*//p}\u0026quot; # 打印并删除正则表达式的那部分内容 echo abcd|sed 'y/bd/BE/' # 匹配字符替换 sed '/^#/b;y/y/P/' 2 # 非#号开头的行替换字符 sed '/suan/r readfile' # 找到含suan的行，在后面加上读入的文件内容 sed -n '/no/w writefile' # 找到含no的行，写入到指定文件中 sed '/regex/G' # 在匹配式样行之后插入一空行 sed '/regex/{x;p;x;G;}' # 在匹配式样行之前和之后各插入一空行 sed 'n;d' # 删除所有偶数行 sed 'G;G' # 在每一行后面增加两空行 sed '/^$/d;G' # 在输出的文本中每一行后面将有且只有一空行 sed 'n;n;n;n;G;' # 在每5行后增加一空白行 sed -n '5~5p' # 只打印行号为5的倍数 seq 1 30|sed '5~5s/.*/a/' # 倍数行执行替换 sed -n '3,${p;n;n;n;n;n;n;}' # 从第3行开始，每7行显示一次 sed -n 'h;n;G;p' # 奇偶调换 seq 1 10|sed '1!G;h;$!d' # 倒叙排列 ls -l|sed -n '/^.rwx.*/p' # 查找属主权限为7的文件 sed = filename | sed 'N;s/\\n/\\t/' # 为文件中的每一行进行编号(简单的左对齐方式) sed 's/^[ \\t]*//' # 将每一行前导的\u0026quot;空白字符\u0026quot;(空格，制表符)删除,使之左对齐 sed 's/^[ \\t]*//;s/[ \\t]*$//' # 将每一行中的前导和拖尾的空白字符删除 sed '/{abc,def\\}\\/\\[111,222]/s/^/00000/' # 匹配需要转行的字符: } / [ echo abcd\\\\nabcde |sed 's/\\\\n/@/g' |tr '@' '\\n' # 将换行符转换为换行 cat tmp|awk '{print $1}'|sort -n|sed -n '$p' # 取一列最大值 sed -n '{s/^[^\\/]*//;s/\\:.*//;p}' /etc/passwd # 取用户家目录(匹配不为/的字符和匹配:到结尾的字符全部删除) sed = filename | sed 'N;s/^/ /; s/ *\\(.\\{6,\\}\\)\\n/\\1 /' # 对文件中的所有行编号(行号在左，文字右端对齐) /sbin/ifconfig |sed 's/.*inet addr:\\(.*\\) Bca.*/\\1/g' |sed -n '/eth/{n;p}' # 取所有IP 修改keepalive配置剔除后端服务器{ sed -i '/real_server.*10.0.1.158.*8888/,+8 s/^/#/' keepalived.conf sed -i '/real_server.*10.0.1.158.*8888/,+8 s/^#//' keepalived.conf } 模仿rev功能{ echo 123 |sed '/\\n/!G;s/\\(.\\)\\(.*\\n\\)/\u0026amp;\\2\\1/;//D;s/.//;' /\\n/!G; # 没有\\n换行符，要执行G,因为保留空间中为空，所以在模式空间追加一空行 s/\\(.\\)\\(.*\\n\\)/\u0026amp;\\2\\1/; # 标签替换 \u0026amp;\\n23\\n1$ (关键在于\u0026amp; ,可以让后面//匹配到空行) //D; # D 命令会引起循环删除模式空间中的第一部分，如果删除后，模式空间中还有剩余行，则返回 D 之前的命令，重新执行，如果 D 后，模式空间中没有任何内容，则将退出。 //D 匹配空行执行D,如果上句s没有匹配到,//也无法匹配到空行, \u0026quot;//D;\u0026quot;命令结束 s/.//; # D结束后,删除开头的 \\n } } xargs{ # 命令替换 -t 先打印命令，然后再执行 -i 用每项替换 {} find / -perm +7000 | xargs ls -l # 将前面的内容，作为后面命令的参数 seq 1 10 |xargs -i date -d \u0026quot;{} days \u0026quot; +%Y-%m-%d # 列出10天日期 } dialog菜单{ # 默认将所有输出用 stderr 输出，不显示到屏幕 使用参数 --stdout 可将选择赋给变量 # 退出状态 0正确 1错误 窗体类型{ --calendar # 日历 --checklist # 允许你显示一个选项列表，每个选项都可以被单独的选择 (复选框) --form # 表单,允许您建立一个带标签的文本字段，并要求填写 --fselect # 提供一个路径，让你选择浏览的文件 --gauge # 显示一个表，呈现出完成的百分比，就是显示出进度条。 --infobox # 显示消息后，（没有等待响应）对话框立刻返回，但不清除屏幕(信息框) --inputbox # 让用户输入文本(输入框) --inputmenu # 提供一个可供用户编辑的菜单（可编辑的菜单框） --menu # 显示一个列表供用户选择(菜单框) --msgbox(message) # 显示一条消息,并要求用户选择一个确定按钮(消息框) --password # 密码框，显示一个输入框，它隐藏文本 --pause # 显示一个表格用来显示一个指定的暂停期的状态 --radiolist # 提供一个菜单项目组，但是只有一个项目，可以选择(单选框) --tailbox # 在一个滚动窗口文件中使用tail命令来显示文本 --tailboxbg # 跟tailbox类似，但是在background模式下操作 --textbox # 在带有滚动条的文本框中显示文件的内容 (文本框) --timebox # 提供一个窗口，选择小时，分钟，秒 --yesno(yes/no) # 提供一个带有yes和no按钮的简单信息框 } 窗体参数{ --separate-output # 对于chicklist组件,输出结果一次输出一行,得到结果不加引号 --ok-label \u0026quot;提交\u0026quot; # 确定按钮名称 --cancel-label \u0026quot;取消\u0026quot; # 取消按钮名称 --title \u0026quot;标题\u0026quot; # 标题名称 --stdout # 将所有输出用 stdout 输出 --backtitle \u0026quot;上标\u0026quot; # 窗体上标 --no-shadow # 去掉窗体阴影 --menu \u0026quot;菜单名\u0026quot; 20 60 14 # 菜单及窗口大小 --clear # 完成后清屏操作 --no-cancel # 不显示取消项 --insecure # 使用星号来代表每个字符 --begin \u0026lt;y\u0026gt; \u0026lt;x\u0026gt; # 指定对话框左上角在屏幕的上的做坐标 --timeout \u0026lt;秒\u0026gt; # 超时,返回的错误代码255,如果用户在指定的时间内没有给出相应动作,就按超时处理 --defaultno # 使选择默认为no --default-item \u0026lt;str\u0026gt; # 设置在一份清单，表格或菜单中的默认项目。通常在框中的第一项是默认 --sleep 5 # 在处理完一个对话框后静止(延迟)的时间(秒) --max-input size # 限制输入的字符串在给定的大小之内。如果没有指定，默认是2048 --keep-window # 退出时不清屏和重绘窗口。当几个组件在同一个程序中运行时，对于保留窗口内容很有用的 } dialog --title \u0026quot;Check me\u0026quot; --checklist \u0026quot;Pick Numbers\u0026quot; 15 25 3 1 \u0026quot;one\u0026quot; \u0026quot;off\u0026quot; 2 \u0026quot;two\u0026quot; \u0026quot;on\u0026quot; # 多选界面[方括号] dialog --title \u0026quot;title\u0026quot; --radiolist \u0026quot;checklist\u0026quot; 20 60 14 tag1 \u0026quot;item1\u0026quot; on tag2 \u0026quot;item2\u0026quot; off # 单选界面(圆括号) dialog --title \u0026quot;title\u0026quot; --menu \u0026quot;MENU\u0026quot; 20 60 14 tag1 \u0026quot;item1\u0026quot; tag2 \u0026quot;item2\u0026quot; # 单选界面 dialog --title \u0026quot;Installation\u0026quot; --backtitle \u0026quot;Star Linux\u0026quot; --gauge \u0026quot;Linux Kernel\u0026quot; 10 60 50 # 进度条 dialog --title \u0026quot;标题\u0026quot; --backtitle \u0026quot;Dialog\u0026quot; --yesno \u0026quot;说明\u0026quot; 20 60 # 选择yes/no dialog --title \u0026quot;公告标题\u0026quot; --backtitle \u0026quot;Dialog\u0026quot; --msgbox \u0026quot;内容\u0026quot; 20 60 # 公告 dialog --title \u0026quot;hey\u0026quot; --backtitle \u0026quot;Dialog\u0026quot; --infobox \u0026quot;Is everything okay?\u0026quot; 10 60 # 显示讯息后立即离开 dialog --title \u0026quot;hey\u0026quot; --backtitle \u0026quot;Dialog\u0026quot; --inputbox \u0026quot;Is okay?\u0026quot; 10 60 \u0026quot;yes\u0026quot; # 输入对话框 dialog --title \u0026quot;Array 30\u0026quot; --backtitle \u0026quot;All \u0026quot; --textbox /root/txt 20 75 # 显示文档内容 dialog --title \u0026quot;Add\u0026quot; --form \u0026quot;input\u0026quot; 12 40 4 \u0026quot;user\u0026quot; 1 1 \u0026quot;\u0026quot; 1 15 15 0 \u0026quot;name\u0026quot; 2 1 \u0026quot;\u0026quot; 2 15 15 0 # 多条输入对话框 dialog --title \u0026quot;Password\u0026quot; --insecure --passwordbox \u0026quot;请输入密码\u0026quot; 10 35 # 星号显示输入--insecure dialog --stdout --title \u0026quot;日历\u0026quot; --calendar \u0026quot;请选择\u0026quot; 0 0 9 1 2010 # 选择日期 dialog --title \u0026quot;title\u0026quot; --menu \u0026quot;MENU\u0026quot; 20 60 14 tag1 \u0026quot;item1\u0026quot; tag2 \u0026quot;item2\u0026quot; 2\u0026gt;tmp # 取到结果放到文件中(以标准错误输出结果) a=`dialog --title \u0026quot;title\u0026quot; --stdout --menu \u0026quot;MENU\u0026quot; 20 60 14 tag1 \u0026quot;item1\u0026quot; tag2 \u0026quot;item2\u0026quot;` # 选择操作赋给变量(使用标准输出) dialog菜单实例{ while : do clear menu=`dialog --title \u0026quot;title\u0026quot; --stdout --menu \u0026quot;MENU\u0026quot; 20 60 14 1 system 2 custom` [ $? -eq 0 ] \u0026amp;\u0026amp; echo \u0026quot;$menu\u0026quot; || exit # 判断dialog执行,取消退出 while : do case $menu in 1) list=\u0026quot;1a \u0026quot;item1\u0026quot; 2a \u0026quot;item2\u0026quot;\u0026quot; # 定义菜单列表变量 ;; 2) list=\u0026quot;1b \u0026quot;item3\u0026quot; 2b \u0026quot;item4\u0026quot;\u0026quot; ;; esac result=`dialog --title \u0026quot;title\u0026quot; --stdout --menu \u0026quot;MENU\u0026quot; 20 60 14 $list` [ $? -eq 0 ] \u0026amp;\u0026amp; echo \u0026quot;$result\u0026quot; || break # 判断dialog执行,取消返回菜单,注意:配合上层菜单循环 read done done } } select菜单{ # 输入项不在菜单自动会提示重新输入 select menuitem in pick1 pick2 pick3 退出 do echo $menuitem case $menuitem in 退出) exit ;; *) select area in area1 area2 area3 返回 do echo $area case $area in 返回) break ;; *) echo \u0026quot;对$area操作\u0026quot; ;; esac done ;; esac done } shift{ ./cs.sh 1 2 3 #!/bin/sh until [ $# -eq 0 ] do echo \u0026quot;第一个参数为: $1 参数个数为: $#\u0026quot; #shift 命令执行前变量 $1 的值在shift命令执行后不可用 shift done } getopts给脚本加参数{ #!/bin/sh while getopts :ab: name do case $name in a) aflag=1 ;; b) bflag=1 bval=$OPTARG ;; \\?) echo \u0026quot;USAGE:`basename $0` [-a] [-b value]\u0026quot; exit 1 ;; esac done if [ ! -z $aflag ] ; then echo \u0026quot;option -a specified\u0026quot; echo \u0026quot;$aflag\u0026quot; echo \u0026quot;$OPTIND\u0026quot; fi if [ ! -z $bflag ] ; then echo \u0026quot;option -b specified\u0026quot; echo \u0026quot;$bflag\u0026quot; echo \u0026quot;$bval\u0026quot; echo \u0026quot;$OPTIND\u0026quot; fi echo \u0026quot;here $OPTIND\u0026quot; shift $(($OPTIND -1)) echo \u0026quot;$OPTIND\u0026quot; echo \u0026quot; `shift $(($OPTIND -1))` \u0026quot; } tclsh{ set foo \u0026quot;a bc\u0026quot; # 定义变量 set b {$a}; # 转义 b的值为\u0026quot; $a \u0026quot; ,而不是变量结果 set a 3; incr a 3; # 数字的自增. 将a加3,如果要减3,则为 incr a –3; set c [expr 20/5]; # 计算 c的值为4 puts $foo; # 打印变量 set qian(123) f; # 定义数组 set qian(1,1,1) fs; # 多维数组 parray qian; # 打印数组的所有信息 string length $qian; # 将返回变量qian的长度 string option string1 string2; # 字符相关串操作 # option 的操作选项: # compare 按照字典的排序方式进行比较。根据string1 \u0026lt;,=,\u0026gt;string2分别返回-1,0,1 # first 返回string2中第一次出现string1的位置，如果没有出现string1则返回-1 # last 和first相反 # trim 从string1中删除开头和结尾的出现在string2中的字符 # tolower 返回string1中的所有字符被转换为小写字符后的新字符串 # toupper 返回string1中的所有字符串转换为大写后的字符串 # length 返回string1的长度 set a 1;while {$a \u0026lt; 3} { set a [incr a 1;]; };puts $a # 判断变量a小于3既循环 for {initialization} {condition} {increment} {body} # 初始化变量,条件,增量,具体操作 for {set i 0} {$i \u0026lt; 10} {incr i} {puts $i;} # 将打印出0到9 if { 表达式 } { #运算; } else { #其他运算; } switch $x { 字符串1 { 操作1 ;} 字符串2 { 操作2 ;} } foreach element {0 m n b v} { # 将在一组变元中进行循环，并且每次都将执行他的循环体 switch $element { # 判断element的值 } } expect交互{ exp_continue # 多个spawn命令时并行 interact # 执行完成后保持交互状态，把控制权交给控制台 expect \u0026quot;password:\u0026quot; # 判断关键字符 send \u0026quot;passwd\\r\u0026quot; # 执行交互动作，与手工输入密码的动作等效。字符串结尾加\u0026quot;\\r\u0026quot; ssh后sudo{ #!/bin/bash #sudo注释下行允许后台运行 #Defaults requiretty #sudo去掉!允许远程 #Defaults !visiblepw /usr/bin/expect -c ' set timeout 5 spawn ssh -o StrictHostKeyChecking=no xuesong1@192.168.42.128 \u0026quot;sudo grep xuesong1 /etc/passwd\u0026quot; expect { \u0026quot;passphrase\u0026quot; { send_user \u0026quot;sshkey\\n\u0026quot; send \u0026quot;xuesong\\r\u0026quot;; expect { \u0026quot;sudo\u0026quot; { send_user \u0026quot;sudo\\n\u0026quot; send \u0026quot;xuesong\\r\u0026quot; interact } eof { send_user \u0026quot;sudo eof\\n\u0026quot; } } } \u0026quot;password:\u0026quot; { send_user \u0026quot;ssh\\n\u0026quot; send \u0026quot;xuesong\\r\u0026quot;; expect { \u0026quot;sudo\u0026quot; { send_user \u0026quot;sudo\\n\u0026quot; send \u0026quot;xuesong\\r\u0026quot; interact } eof { send_user \u0026quot;sudo eof\\n\u0026quot; } } } \u0026quot;sudo\u0026quot; { send_user \u0026quot;sudo\\n\u0026quot; send \u0026quot;xuesong\\r\u0026quot; interact } eof { send_user \u0026quot;ssh eof\\n\u0026quot; } } ' } ssh执行命令操作{ /usr/bin/expect -c \u0026quot; proc jiaohu {} { send_user expect_start expect { password { send ${RemotePasswd}\\r; send_user expect_eof expect { \\\u0026quot;does not exist\\\u0026quot; { send_user expect_failure exit 10 } password { send_user expect_failure exit 5 } Password { send ${RemoteRootPasswd}\\r; send_user expect_eof expect { incorrect { send_user expect_failure exit 6 } eof } } eof } } passphrase { send ${KeyPasswd}\\r; send_user expect_eof expect { \\\u0026quot;does not exist\\\u0026quot; { send_user expect_failure exit 10 } passphrase{ send_user expect_failure exit 7 } Password { send ${RemoteRootPasswd}\\r; send_user expect_eof expect { incorrect { send_user expect_failure exit 6 } eof } } eof } } Password { send ${RemoteRootPasswd}\\r; send_user expect_eof expect { incorrect { send_user expect_failure exit 6 } eof } } \\\u0026quot;No route to host\\\u0026quot; { send_user expect_failure exit 4 } \\\u0026quot;Invalid argument\\\u0026quot; { send_user expect_failure exit 8 } \\\u0026quot;Connection refused\\\u0026quot; { send_user expect_failure exit 9 } \\\u0026quot;does not exist\\\u0026quot; { send_user expect_failure exit 10 } \\\u0026quot;Connection timed out\\\u0026quot; { send_user expect_failure exit 11 } timeout { send_user expect_failure exit 3 } eof } } set timeout $TimeOut switch $1 { Ssh_Cmd { spawn ssh -t -p $Port -o StrictHostKeyChecking=no $RemoteUser@$Ip /bin/su - root -c \\\\\\\u0026quot;$Cmd\\\\\\\u0026quot; jiaohu } Ssh_Script { spawn scp -P $Port -o StrictHostKeyChecking=no $ScriptPath $RemoteUser@$Ip:/tmp/${ScriptPath##*/}; jiaohu spawn ssh -t -p $Port -o StrictHostKeyChecking=no $RemoteUser@$Ip /bin/su - root -c \\\\\\\u0026quot;/bin/sh /tmp/${ScriptPath##*/}\\\\\\\u0026quot; ; jiaohu } Scp_File { spawn scp -P $Port -o StrictHostKeyChecking=no -r $ScpPath $RemoteUser@$Ip:${ScpRemotePath}; jiaohu } } \u0026quot; state=`echo $?` } 交互双引号引用较长变量{ #!/bin/bash RemoteUser=xuesong12 Ip=192.168.1.2 RemotePasswd=xuesong Cmd=\u0026quot;/bin/echo \u0026quot;$PubKey\u0026quot; \u0026gt; \u0026quot;$RemoteKey\u0026quot;/authorized_keys\u0026quot; /usr/bin/expect -c \u0026quot; set timeout 10 spawn ssh -o StrictHostKeyChecking=no $RemoteUser@$Ip {$Cmd}; expect { password: { send_user RemotePasswd\\n send ${RemotePasswd}\\r; interact; } eof { send_user eof\\n } } \u0026quot; } telnet交互{ #!/bin/bash Ip=\u0026quot;10.0.1.53\u0026quot; a=\u0026quot;\\{\\'method\\'\\:\\'doLogin\\'\\,\\'params\\'\\:\\{\\'uName\\'\\:\\'bobbietest\\'\\}\u0026quot; /usr/bin/expect -c\u0026quot; set timeout 15 spawn telnet ${Ip} 8000 expect \u0026quot;Escape\u0026quot; send \u0026quot;${a}\\\\r\u0026quot; expect { -re \u0026quot;\\\u0026quot;err.*none\\\u0026quot;\u0026quot; { exit 0 } timeout { exit 1 } eof { exit 2 } } \u0026quot; echo $? } 模拟ssh登录{ #好处:可加载环境变量 #!/bin/bash Ip='192.168.1.6' # 循环就行 RemoteUser='user' # 普通用户 RemotePasswd='userpasswd' # 普通用户的密码 RemoteRootPasswd='rootpasswd' /usr/bin/expect -c \u0026quot; set timeout -1 spawn ssh -t -p $Port -o StrictHostKeyChecking=no $RemoteUser@$Ip expect { password { send_user RemotePasswd send ${RemotePasswd}\\r; expect { \\\u0026quot;does not exist\\\u0026quot; { send_user \\\u0026quot;root user does not exist\\n\\\u0026quot; exit 10 } password { send_user \\\u0026quot;user passwd error\\n\\\u0026quot; exit 5 } Last { send \\\u0026quot;su - batch\\n\\\u0026quot; expect { Password { send_user RemoteRootPasswd send ${RemoteRootPasswd}\\r; expect { \\\u0026quot;]#\\\u0026quot; { send \\\u0026quot;sh /tmp/update.sh update\\n \\\u0026quot; expect { \\\u0026quot;]#\\\u0026quot; { send_user ${Ip}_Update_Done\\n } eof } } } } } } } } \\\u0026quot;No route to host\\\u0026quot; { send_user \\\u0026quot;host not found\\n\\\u0026quot; exit 4 } \\\u0026quot;Invalid argument\\\u0026quot; { send_user \\\u0026quot;incorrect parameter\\n\\\u0026quot; exit 8 } \\\u0026quot;Connection refused\\\u0026quot; { send_user \\\u0026quot;invalid port parameters\\n\\\u0026quot; exit 9 } \\\u0026quot;does not exist\\\u0026quot; { send_user \\\u0026quot;root user does not exist\\\u0026quot; exit 10 } timeout { send_user \\\u0026quot;connection timeout \\n\\\u0026quot; exit 3 } eof } \u0026quot; state=`echo $?` } } } }\n9 实例{\n从1叠加到100{ echo $[$(echo +{1..100})] echo $[(100+1)*(100/2)] seq -s '+' 100 |bc } 判断参数是否为空-空退出并打印null{ #!/bin/sh echo $1 name=${1:?\u0026quot;null\u0026quot;} echo $name } 循环数组{ for ((i=0;i\u0026lt;${#o[*]};i++)) do echo ${o[$i]} done } 判断路径{ if [ -d /root/Desktop/text/123 ];then echo \u0026quot;找到了123\u0026quot; if [ -d /root/Desktop/text ] then echo \u0026quot;找到了text\u0026quot; else echo \u0026quot;没找到text\u0026quot; fi else echo \u0026quot;没找到123文件夹\u0026quot; fi } 找出出现次数最多{ awk '{print $1}' file|sort |uniq -c|sort -k1r } 判断脚本参数是否正确{ ./test.sh -p 123 -P 3306 -h 127.0.0.1 -u root #!/bin/sh if [ $# -ne 8 ];then echo \u0026quot;USAGE: $0 -u user -p passwd -P port -h host\u0026quot; exit 1 fi while getopts :u:p:P:h: name do case $name in u) mysql_user=$OPTARG ;; p) mysql_passwd=$OPTARG ;; P) mysql_port=$OPTARG ;; h) mysql_host=$OPTARG ;; *) echo \u0026quot;USAGE: $0 -u user -p passwd -P port -h host\u0026quot; exit 1 ;; esac done if [ -z $mysql_user ] || [ -z $mysql_passwd ] || [ -z $mysql_port ] || [ -z $mysql_host ] then echo \u0026quot;USAGE: $0 -u user -p passwd -P port -h host\u0026quot; exit 1 fi echo $mysql_user $mysql_passwd $mysql_port $mysql_host #结果 root 123 3306 127.0.0.1 } 正则匹配邮箱{ ^[_a-z0-9-]+(\\.[_a-z0-9-]+)*@[a-z0-9-]+(\\.[a-z0-9-]+)*(\\.[a-z]{2,4})$ } 打印表格{ #!/bin/sh clear awk 'BEGIN{ print \u0026quot;+--------------------+--------------------+\u0026quot;; printf \u0026quot;|%-20s|%-20s|\\n\u0026quot;,\u0026quot;Name\u0026quot;,\u0026quot;Number\u0026quot;; print \u0026quot;+--------------------+--------------------+\u0026quot;; }' a=`grep \u0026quot;^[A-Z]\u0026quot; a.txt |sort +1 -n |awk '{print $1\u0026quot;:\u0026quot;$2}'` #cat a.txt |sort +1 -n |while read list for list in $a do name=`echo $list |awk -F: '{print $1}'` number=`echo $list |awk -F: '{print $2}'` awk 'BEGIN{printf \u0026quot;|%-20s|%-20s|\\n\u0026quot;,\u0026quot;'\u0026quot;$name\u0026quot;'\u0026quot;,\u0026quot;'\u0026quot;$number\u0026quot;'\u0026quot;; print \u0026quot;+--------------------+--------------------+\u0026quot;; }' done awk 'BEGIN{ print \u0026quot; *** The End *** \u0026quot; print \u0026quot; \u0026quot; }' } 判断日期是否合法{ #!/bin/sh while read a do if echo $a | grep -q \u0026quot;-\u0026quot; \u0026amp;\u0026amp; date -d $a +%Y%m%d \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 then if echo $a | grep -e '^[0-9]\\{4\\}-[01][0-9]-[0-3][0-9]$' then break else echo \u0026quot;您输入的日期不合法，请从新输入！\u0026quot; fi else echo \u0026quot;您输入的日期不合法，请从新输入！\u0026quot; fi done echo \u0026quot;日期为$a\u0026quot; } 打印日期段所有日期{ #!/bin/bash qsrq=20010101 jsrq=20010227 n=0 \u0026gt;tmp while :;do current=$(date +%Y%m%d -d\u0026quot;$n day $qsrq\u0026quot;) if [[ $current == $jsrq ]];then echo $current \u0026gt;\u0026gt;tmp;break else echo $current \u0026gt;\u0026gt;tmp ((n++)) fi done rq=`awk 'NR==1{print}' tmp` } 数学计算的小算法{ #!/bin/sh A=1 B=1 while [ $A -le 10 ] do SUM=`expr $A \\* $B` echo \u0026quot;$SUM\u0026quot; if [ $A = 10 ] then B=`expr $B + 1` A=1 fi A=`expr $A + 1` done } 多行合并{ sed '{N;s/\\n//}' file # 将两行合并一行(去掉换行符) awk '{printf(NR%2!=0)?$0\u0026quot; \u0026quot;:$0\u0026quot; \\n\u0026quot;}' # 将两行合并一行 awk '{printf\u0026quot;%s \u0026quot;,$0}' # 将所有行合并 awk '{if (NR%4==0){print $0} else {printf\u0026quot;%s \u0026quot;,$0}}' file # 将4行合并为一行(可扩展) } 横竖转换{ cat a.txt | xargs # 列转行 cat a.txt | xargs -n1 # 行转列 } 竖行转横行{ cat file|tr '\\n' ' ' echo $(cat file) #!/bin/sh for i in `cat file` do a=${a}\u0026quot; \u0026quot;${i} done echo $a } 取用户的根目录{ #! /bin/bash while read name pass uid gid gecos home shell do echo $home done \u0026lt; /etc/passwd } 远程打包{ ssh -n $ip 'find '$path' /data /opt -type f -name \u0026quot;*.sh\u0026quot; -or -name \u0026quot;*.py\u0026quot; -or -name \u0026quot;*.pl\u0026quot; |xargs tar zcvpf /tmp/data_backup.tar.gz' } 把汉字转成encode格式{ echo 论坛 | tr -d \u0026quot;\\n\u0026quot; | xxd -i | sed -e \u0026quot;s/ 0x/%/g\u0026quot; | tr -d \u0026quot; ,\\n\u0026quot; %c2%db%cc%b3 echo 论坛 | tr -d \u0026quot;\\n\u0026quot; | xxd -i | sed -e \u0026quot;s/ 0x/%/g\u0026quot; | tr -d \u0026quot; ,\\n\u0026quot; | tr \u0026quot;[a-f]\u0026quot; \u0026quot;[A-F]\u0026quot; # 大写的 %C2%DB%CC%B3 } 把目录带有大写字母的文件名改为全部小写{ #!/bin/bash for f in *;do mv $f `echo $f |tr \u0026quot;[A-Z]\u0026quot; \u0026quot;[a-z]\u0026quot;` done } 查找连续多行，在不连续的行前插入{ #/bin/bash lastrow=null i=0 cat incl|while read line do i=`expr $i + 1` if echo \u0026quot;$lastrow\u0026quot; | grep \u0026quot;#include \u0026lt;[A-Z].h\u0026gt;\u0026quot; then if echo \u0026quot;$line\u0026quot; | grep -v \u0026quot;#include \u0026lt;[A-Z].h\u0026gt;\u0026quot; then sed -i ''$i'i\\\\/\\/All header files are include' incl i=`expr $i + 1` fi fi lastrow=\u0026quot;$line\u0026quot; done } 查询数据库其它引擎{ #/bin/bash path1=/data/mysql/data/ dbpasswd=db123 #MyISAM或InnoDB engine=InnoDB if [ -d $path1 ];then dir=`ls -p $path1 |awk '/\\/$/'|awk -F'/' '{print $1}'` for db in $dir do number=`mysql -uroot -p$dbpasswd -A -S \u0026quot;$path1\u0026quot;mysql.sock -e \u0026quot;use ${db};show table status;\u0026quot; |grep -c $engine` if [ $number -ne 0 ];then echo \u0026quot;${db}\u0026quot; fi done fi } 批量修改数据库引擎{ #/bin/bash for db in test test1 test3 do tables=`mysql -uroot -pdb123 -A -S /data/mysql/data/mysql.sock -e \u0026quot;use $db;show tables;\u0026quot; |awk 'NR != 1{print}'` for table in $tables do mysql -uroot -pdb123 -A -S /data/mysql/data/mysql.sock -e \u0026quot;use $db;alter table $table engine=MyISAM;\u0026quot; done done } 将shell取到的数据插入mysql数据库{ mysql -u$username -p$passwd -h$dbhost -P$dbport -A -e \u0026quot; use $dbname; insert into data values ('','$ip','$date','$time','$data') \u0026quot; } 两日期间隔天数{ D1=`date -d '20070409' +\u0026quot;%s\u0026quot;` D2=`date -d '20070304 ' +\u0026quot;%s\u0026quot;` D3=$(($D1 - $D2)) echo $(($D3/60/60/24)) } while执行ssh只循环一次{ cat - # 让cat读连接文件stdin的信息 seq 10 | while read line; do ssh localhost \u0026quot;cat -\u0026quot;; done # 显示的9次是被ssh吃掉的 seq 10 | while read line; do ssh -n localhost \u0026quot;cat -\u0026quot;; done # ssh加上-n参数可避免只循环一次 } ssh批量执行命令{ #版本1 #!/bin/bash while read line do Ip=`echo $line|awk '{print $1}'` Passwd=`echo $line|awk '{print $2}'` ssh -n localhost \u0026quot;cat -\u0026quot; sshpass -p \u0026quot;$Passwd\u0026quot; ssh -n -t -o StrictHostKeyChecking=no root@$Ip \u0026quot;id\u0026quot; done\u0026lt;iplist.txt #版本2 #!/bin/bash Iplist=`awk '{print $1}' iplist.txt` for Ip in $Iplist do Passwd=`awk '/'$Ip'/{print $2}' iplist.txt` sshpass -p \u0026quot;$Passwd\u0026quot; ssh -n -t -o StrictHostKeyChecking=no root@$Ip \u0026quot;id\u0026quot; done } 在同一位置打印字符{ #!/bin/bash echo -ne \u0026quot;\\t\u0026quot; for i in `seq -w 100 -1 1` do echo -ne \u0026quot;$i\\b\\b\\b\u0026quot;; # 关键\\b退格 sleep 1; done } 多进程后台并发简易控制{ #!/bin/bash test () { echo $a sleep 5 } for a in `seq 1 30` do test \u0026amp; echo $! ((num++)) if [ $num -eq 6 ];then echo \u0026quot;wait...\u0026quot; wait num=0 fi done wait } shell并发{ #!/bin/bash tmpfile=$$.fifo # 创建管道名称 mkfifo $tmpfile # 创建管道 exec 4\u0026lt;\u0026gt;$tmpfile # 创建文件标示4，以读写方式操作管道$tmpfile rm $tmpfile # 将创建的管道文件清除 thred=4 # 指定并发个数 seq=(1 2 3 4 5 6 7 8 9 21 22 23 24 25 31 32 33 34 35) # 创建任务列表 # 为并发线程创建相应个数的占位 { for (( i = 1;i\u0026lt;=${thred};i++ )) do echo; # 因为read命令一次读取一行，一个echo默认输出一个换行符，所以为每个线程输出一个占位换行 done } \u0026gt;\u0026amp;4 # 将占位信息写入管道 for id in ${seq} # 从任务列表 seq 中按次序获取每一个任务 do read # 读取一行，即fd4中的一个占位符 (./ur_command ${id};echo \u0026gt;\u0026amp;4 ) \u0026amp; # 在后台执行任务ur_command 并将任务 ${id} 赋给当前任务；任务执行完后在fd4种写入一个占位符 done \u0026lt;\u0026amp;4 # 指定fd4为整个for的标准输入 wait # 等待所有在此shell脚本中启动的后台任务完成 exec 4\u0026gt;\u0026amp;- # 关闭管道 } shell并发函数{ function ConCurrentCmd() { #进程数 Thread=30 #列表文件 CurFileName=iplist.txt #定义fifo文件 FifoFile=\u0026quot;$$.fifo\u0026quot; #新建一个fifo类型的文件 mkfifo $FifoFile #将fd6与此fifo类型文件以读写的方式连接起来 exec 6\u0026lt;\u0026gt;$FifoFile rm $FifoFile #事实上就是在文件描述符6中放置了$Thread个回车符 for ((i=0;i\u0026lt;=$Thread;i++));do echo;done \u0026gt;\u0026amp;6 #此后标准输入将来自fd5 exec 5\u0026lt;$CurFileName #开始循环读取文件列表中的行 Count=0 while read -u5 line do read -u6 let Count+=1 # 此处定义一个子进程放到后台执行 # 一个read -u6命令执行一次,就从fd6中减去一个回车符，然后向下执行 # fd6中没有回车符的时候,就停在这了,从而实现了进程数量控制 { echo $Count #这段代码框就是执行具体的操作了 function echo \u0026gt;\u0026amp;6 #当进程结束以后,再向fd6中追加一个回车符,即补上了read -u6减去的那个 } \u0026amp; done #等待所有后台子进程结束 wait #关闭fd6 exec 6\u0026gt;\u0026amp;- #关闭fd5 exec 5\u0026gt;\u0026amp;- } 并发例子{ #!/bin/bash pnum=3 task () { echo \u0026quot;$u start\u0026quot; sleep 5 echo \u0026quot;$u done\u0026quot; } FifoFile=\u0026quot;$$.fifo\u0026quot; mkfifo $FifoFile exec 6\u0026lt;\u0026gt;$FifoFile rm $FifoFile for ((i=0;i\u0026lt;=$pnum;i++));do echo;done \u0026gt;\u0026amp;6 for u in `seq 1 20` do read -u6 { task [ $? -eq 0 ] \u0026amp;\u0026amp; echo \u0026quot;${u} 次成功\u0026quot; || echo \u0026quot;${u} 次失败\u0026quot; echo \u0026gt;\u0026amp;6 } \u0026amp; done wait exec 6\u0026gt;\u0026amp;- } } 函数{ ip(){ echo \u0026quot;a 1\u0026quot;|awk '$1==\u0026quot;'\u0026quot;$1\u0026quot;'\u0026quot;{print $2}' } web=a ip $web } 检测软件包是否存在{ rpm -q dialog \u0026gt;/dev/null if [ \u0026quot;$?\u0026quot; -ge 1 ];then echo \u0026quot;install dialog,Please wait...\u0026quot; yum -y install dialog rpm -q dialog \u0026gt;/dev/null [ $? -ge 1 ] \u0026amp;\u0026amp; echo \u0026quot;dialog installation failure,exit\u0026quot; \u0026amp;\u0026amp; exit echo \u0026quot;dialog done\u0026quot; read fi } 游戏维护菜单-修改配置文件{ #!/bin/bash conf=serverlist.xml AreaList=`awk -F '\u0026quot;' '/\u0026lt;s/{print $2}' $conf` select area in $AreaList 全部 退出 do echo \u0026quot;\u0026quot; echo $area case $area in 退出) exit ;; *) select operate in \u0026quot;修改版本号\u0026quot; \u0026quot;添加维护中\u0026quot; \u0026quot;删除维护中\u0026quot; \u0026quot;返回菜单\u0026quot; do echo \u0026quot;\u0026quot; echo $operate case $operate in 修改版本号) echo 请输入版本号 while read version do if echo $version | grep -w 10[12][0-9][0-9][0-9][0-9][0-9][0-9] then break fi echo 请从新输入正确的版本号 done case $area in 全部) case $version in 101*) echo \u0026quot;请确认操作对 $area 体验区 $operate\u0026quot; read sed -i 's/101[0-9][0-9][0-9][0-9][0-9][0-9]/'$version'/' $conf ;; 102*) echo \u0026quot;请确认操作对 $area 正式区 $operate\u0026quot; read sed -i 's/102[0-9][0-9][0-9][0-9][0-9][0-9]/'$version'/' $conf ;; esac ;; *) type=`awk -F '\u0026quot;' '/'$area'/{print $14}' $conf |cut -c1-3` readtype=`echo $version |cut -c1-3` if [ $type != $readtype ] then echo \u0026quot;版本号不对应，请从新操作\u0026quot; continue fi echo \u0026quot;请确认操作对 $area 区 $operate\u0026quot; read awk -F '\u0026quot;' '/'$area'/{print $12}' $conf |xargs -i sed -i '/'{}'/s/10[12][0-9][0-9][0-9][0-9][0-9][0-9]/'$version'/' $conf ;; esac ;; 添加维护中) case $area in 全部) echo \u0026quot;请确认操作对 $area 区 $operate\u0026quot; read awk -F '\u0026quot;' '/\u0026lt;s/{print $2}' $conf |xargs -i sed -i 's/'{}'/\u0026amp;维护中/' $conf ;; *) echo \u0026quot;请确认操作对 $area 区 $operate\u0026quot; read sed -i 's/'$area'/\u0026amp;维护中/' $conf ;; esac ;; 删除维护中) case $area in 全部) echo \u0026quot;请确认操作对 $area 区 $operate\u0026quot; read sed -i 's/维护中//' $conf ;; *) echo \u0026quot;请确认操作对 $area 区 $operate\u0026quot; read sed -i '/'$area'/s/维护中//' $conf ;; esac ;; 返回菜单) break ;; esac done ;; esac echo \u0026quot;回车重新选择区\u0026quot; done } keepalive剔除后端服务{ #!/bin/bash #行数请自定义,默认8行 if [ X$2 == X ];then echo \u0026quot;error: IP null\u0026quot; read exit fi case $1 in del) sed -i '/real_server.*'$2'.*8888/,+8 s/^/#/' /etc/keepalived/keepalived.conf /etc/init.d/keepalived reload ;; add) sed -i '/real_server.*'$2'.*8888/,+8 s/^#//' /etc/keepalived/keepalived.conf /etc/init.d/keepalived reload ;; *) echo \u0026quot;Parameter error\u0026quot; ;; esac } 抓取系统中负载最高的进程{ #!/bin/bash LANG=C PATH=/sbin:/usr/sbin:/bin:/usr/bin interval=1 length=86400 for i in $(seq 1 $(expr ${length} / ${interval}));do date LANG=C ps -eT -o%cpu,pid,tid,ppid,comm | grep -v CPU | sort -n -r | head -20 date LANG=C cat /proc/loadavg { LANG=C ps -eT -o%cpu,pid,tid,ppid,comm | sed -e 's/^ *//' | tr -s ' ' | grep -v CPU | sort -n -r | cut -d ' ' -f 1 | xargs -I{} echo -n \u0026quot;{} + \u0026quot; \u0026amp;\u0026amp; echo ' 0'; } | bc -l sleep ${interval} done fuser -k $0 } 申诉中国反垃圾邮件联盟黑名单{ #!/bin/bash IpList=`awk '$1!~\u0026quot;^#\u0026quot;\u0026amp;\u0026amp;$1!=\u0026quot;\u0026quot;{print $1}' host.list` QueryAdd='http://www.anti-spam.org.cn/Rbl/Query/Result' ComplaintAdd='http://www.anti-spam.org.cn/Rbl/Getout/Submit' CONTENT='我们是一家正规的XXX。xxxxxxx。恳请将我们的发送服务器IP移出黑名单。谢谢！ 处理措施： 1.XXXX。 2.XXXX。' CORP='abc.com' WWW='www.abc.cm' NAME='def' MAIL='def@163.com.cn' TEL='010-50000000' LEVEL='0' for Ip in $IpList do Status=`curl -d \u0026quot;IP=$Ip\u0026quot; $QueryAdd |grep 'Getout/ShowForm?IP=' |grep -wc '申诉脱离'` if [ $Status -ge 1 ];then IpStatus=\u0026quot;黑名单中\u0026quot; results=`curl -d \u0026quot;IP=${Ip}\u0026amp;CONTENT=${CONTENT}\u0026amp;CORP=${CORP}\u0026amp;WWW=${WWW}\u0026amp;NAME=${NAME}\u0026amp;MAIL=${MAIL}\u0026amp;TEL=${TEL}\u0026amp;LEVEL=${LEVEL}\u0026quot; $ComplaintAdd |grep -E '您的黑名单脱离申请已提交|该IP的脱离申请已被他人提交|申请由于近期内有被拒绝的记录'` echo $results if echo $results | grep '您的黑名单脱离申请已提交' \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 then complaint='申诉成功' elif echo $results | grep '该IP的脱离申请已被他人提交' \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 then complaint='申诉重复' elif echo $results | grep '申请由于近期内有被拒绝的记录' \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 then complaint='申诉拒绝' else complaint='异常' fi else IpStatus='正常' complaint='无需申诉' fi echo \u0026quot;$Ip $IpStatus $complaint\u0026quot; \u0026gt;\u0026gt; $(date +%Y%m%d_%H%M%S).log done }\nWeb Server in Awk{ #gawk -f file BEGIN { x = 1 # script exits if x \u0026lt; 1 port = 8080 # port number host = \u0026quot;/inet/tcp/\u0026quot; port \u0026quot;/0/0\u0026quot; # host string url = \u0026quot;http://localhost:\u0026quot; port # server url status = 200 # 200 == OK reason = \u0026quot;OK\u0026quot; # server response RS = ORS = \u0026quot;\\r\\n\u0026quot; # header line terminators doc = Setup() # html document len = length(doc) + length(ORS) # length of document while (x) { if ($1 == \u0026quot;GET\u0026quot;) RunApp(substr($2, 2)) if (! x) break print \u0026quot;HTTP/1.0\u0026quot;, status, reason |\u0026amp; host print \u0026quot;Connection: Close\u0026quot; |\u0026amp; host print \u0026quot;Pragma: no-cache\u0026quot; |\u0026amp; host print \u0026quot;Content-length:\u0026quot;, len |\u0026amp; host print ORS doc |\u0026amp; host close(host) # close client connection host |\u0026amp; getline # wait for new client request } # server terminated... doc = Bye() len = length(doc) + length(ORS) print \u0026quot;HTTP/1.0\u0026quot;, status, reason |\u0026amp; host print \u0026quot;Connection: Close\u0026quot; |\u0026amp; host print \u0026quot;Pragma: no-cache\u0026quot; |\u0026amp; host print \u0026quot;Content-length:\u0026quot;, len |\u0026amp; host print ORS doc |\u0026amp; host close(host) } function Setup() { tmp = \u0026quot;\u0026lt;html\u0026gt;\\ \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Simple gawk server\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\\ \u0026lt;body\u0026gt;\\ \u0026lt;p\u0026gt;\u0026lt;a href=\u0026quot; url \u0026quot;/xterm\u0026gt;xterm\u0026lt;/a\u0026gt;\\ \u0026lt;p\u0026gt;\u0026lt;a href=\u0026quot; url \u0026quot;/xcalc\u0026gt;xcalc\u0026lt;/a\u0026gt;\\ \u0026lt;p\u0026gt;\u0026lt;a href=\u0026quot; url \u0026quot;/xload\u0026gt;xload\u0026lt;/a\u0026gt;\\ \u0026lt;p\u0026gt;\u0026lt;a href=\u0026quot; url \u0026quot;/exit\u0026gt;terminate script\u0026lt;/a\u0026gt;\\ \u0026lt;/body\u0026gt;\\ \u0026lt;/html\u0026gt;\u0026quot; return tmp } function Bye() { tmp = \u0026quot;\u0026lt;html\u0026gt;\\ \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Simple gawk server\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\\ \u0026lt;body\u0026gt;\u0026lt;p\u0026gt;Script Terminated...\u0026lt;/body\u0026gt;\\ \u0026lt;/html\u0026gt;\u0026quot; return tmp } function RunApp(app) { if (app == \u0026quot;xterm\u0026quot;) {system(\u0026quot;xterm\u0026amp;\u0026quot;); return} if (app == \u0026quot;xcalc\u0026quot; ) {system(\u0026quot;xcalc\u0026amp;\u0026quot;); return} if (app == \u0026quot;xload\u0026quot; ) {system(\u0026quot;xload\u0026amp;\u0026quot;); return} if (app == \u0026quot;exit\u0026quot;) {x = 0} } } }\n10 经验{ 1.服务上线,在启动注册流量时大量报错, 下游服务摘除,重启后, 上游还用原有的链接去链接, 导致请求失败. 2.systemd守护的进程,在tmp下找不到对应文件, 配置安全tmp项PrivateTmp改为false PrivateTmp=false 3.统一服务内部调用关系,一个服务对应一个域名 4.统一服务服务返回的状态码,报警只需要针对5xx就可以发现问题. 5.在服务雪崩后,恢复服务,用户可能有大量重试,所以放流量也要小比例放流量,逐步恢复 }\n不定期更新下载地址： https://github.com/liquanzhou/ops_doc\n请勿删除信息, 植入广告, 抵制不道德行为\n","date":"2022年12月2日","externalUrl":null,"permalink":"/posts/shell-docs/","section":"博客文章","summary":"\u003cp\u003eShell 是 Linux/Unix 系统的核心组件，既是命令行解释器，也是强大的脚本编程语言。掌握 Shell 编程对于系统管理员、运维工程师和开发者来说至关重要。本指南将从基础概念开始，逐步深入到高级应用，帮助您全面掌握 Shell 的使用技巧。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003eShell 基础概念 \n    \u003cdiv id=\"shell-基础概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#shell-%e5%9f%ba%e7%a1%80%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e什么是 Shell \n    \u003cdiv id=\"什么是-shell\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-shell\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eShell 是用户与 Linux 内核交互的接口，它具有以下特点：\u003c/p\u003e","title":"Shell 脚本编程完整实战指南","type":"posts"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/%E8%84%9A%E6%9C%AC%E7%BC%96%E7%A8%8B/","section":"Tags","summary":"","title":"脚本编程","type":"tags"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/categories/%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86/","section":"Categories","summary":"","title":"系统管理","type":"categories"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86/","section":"Tags","summary":"","title":"系统管理","type":"tags"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/categories/%E8%BF%90%E7%BB%B4/","section":"Categories","summary":"","title":"运维","type":"categories"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/%E8%BF%90%E7%BB%B4%E8%87%AA%E5%8A%A8%E5%8C%96/","section":"Tags","summary":"","title":"运维自动化","type":"tags"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/go/","section":"Tags","summary":"","title":"Go","type":"tags"},{"content":"Go 语言是 Google 开发的现代编程语言，专为构建简单、可靠、高效的软件而设计。本指南将从基础概念开始，逐步深入到高级特性和实际应用，帮助您全面掌握 Go 语言的开发技能。\nGo 语言概述 # 什么是 Go # Go（也称为 Golang）是一种开源的编程语言，由 Google 在 2009 年发布。它具有以下核心特性：\n设计理念 # 简洁性: 语法简单，易于学习和使用 高效性: 编译速度快，运行性能优秀 并发性: 原生支持并发编程 可靠性: 强类型系统，内存安全 可扩展性: 适合构建大型分布式系统 主要特点 # // Go 语言的核心特性示例 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) // 1. 简洁的语法 func main() { // 2. 强类型系统 var message string = \u0026#34;Hello, Go!\u0026#34; // 3. 类型推断 count := 42 // 4. 并发编程 go func() { fmt.Println(\u0026#34;并发执行\u0026#34;) }() // 5. 内置垃圾回收 fmt.Printf(\u0026#34;%s, Count: %d\\n\u0026#34;, message, count) time.Sleep(time.Second) } Go 的应用领域 # 1. 云原生和微服务 # // 微服务示例 package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;log\u0026#34; ) type User struct { ID int `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` } func userHandler(w http.ResponseWriter, r *http.Request) { user := User{ID: 1, Name: \u0026#34;Alice\u0026#34;} json.NewEncoder(w).Encode(user) } func main() { http.HandleFunc(\u0026#34;/user\u0026#34;, userHandler) log.Println(\u0026#34;Server starting on :8080\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 2. 系统编程和工具开发 # 容器技术: Docker、Kubernetes 数据库: InfluxDB、CockroachDB 监控工具: Prometheus、Grafana 网络工具: Caddy、Traefik 3. 区块链和加密货币 # 以太坊: Go-Ethereum (Geth) Hyperledger Fabric 各种区块链项目 开发环境搭建 # 安装 Go # # 1. 下载 Go # 访问 https://golang.org/dl/ 下载对应平台的安装包 # 2. 设置环境变量 export GOROOT=/usr/local/go export GOPATH=$HOME/go export PATH=$PATH:$GOROOT/bin:$GOPATH/bin # 3. 验证安装 go version go env 配置开发环境 # # 创建工作空间 mkdir -p $GOPATH/{src,bin,pkg} # 初始化模块 mkdir hello-go \u0026amp;\u0026amp; cd hello-go go mod init hello-go # 创建主文件 cat \u0026gt; main.go \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; package main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;Hello, Go!\u0026#34;) } EOF # 运行程序 go run main.go # 构建可执行文件 go build -o hello main.go ./hello IDE 和编辑器配置 # 推荐的开发工具:\nVS Code + Go 扩展 GoLand (JetBrains) Vim/Neovim + vim-go Emacs + go-mode // VS Code 配置示例 (.vscode/settings.json) { \u0026#34;go.useLanguageServer\u0026#34;: true, \u0026#34;go.formatTool\u0026#34;: \u0026#34;goimports\u0026#34;, \u0026#34;go.lintTool\u0026#34;: \u0026#34;golangci-lint\u0026#34;, \u0026#34;go.testFlags\u0026#34;: [\u0026#34;-v\u0026#34;], \u0026#34;go.buildFlags\u0026#34;: [\u0026#34;-race\u0026#34;], \u0026#34;editor.formatOnSave\u0026#34;: true } Go 语言基础语法 # 程序结构 # 包（Package） # Go 程序由包组成，程序从 main 包开始运行：\n// 包声明 package main // 导入语句 import ( \u0026#34;fmt\u0026#34; \u0026#34;math\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;time\u0026#34; ) // 函数声明 func main() { fmt.Println(\u0026#34;Hello, World!\u0026#34;) } 导入包的方式 # // 1. 标准导入 import \u0026#34;fmt\u0026#34; import \u0026#34;math\u0026#34; // 2. 分组导入（推荐） import ( \u0026#34;fmt\u0026#34; \u0026#34;math\u0026#34; \u0026#34;net/http\u0026#34; ) // 3. 别名导入 import ( f \u0026#34;fmt\u0026#34; m \u0026#34;math\u0026#34; ) // 4. 匿名导入（只执行包的 init 函数） import _ \u0026#34;database/sql/driver\u0026#34; // 5. 点导入（不推荐） import . \u0026#34;fmt\u0026#34; 可见性规则 # Go 使用首字母大小写来控制可见性：\npackage mypackage // 公开的（可导出的） var PublicVar = \u0026#34;可以被其他包访问\u0026#34; func PublicFunction() {} type PublicStruct struct { PublicField string } // 私有的（不可导出的） var privateVar = \u0026#34;只能在包内访问\u0026#34; func privateFunction() {} type privateStruct struct { privateField string } 变量和常量 # 变量声明 # package main import \u0026#34;fmt\u0026#34; func main() { // 1. 完整声明 var name string = \u0026#34;Alice\u0026#34; var age int = 30 // 2. 类型推断 var city = \u0026#34;Beijing\u0026#34; var temperature = 25.5 // 3. 短变量声明（只能在函数内使用） country := \u0026#34;China\u0026#34; isStudent := true // 4. 多变量声明 var x, y int = 10, 20 a, b := 1, 2 // 5. 零值 var defaultInt int // 0 var defaultString string // \u0026#34;\u0026#34; var defaultBool bool // false fmt.Printf(\u0026#34;Name: %s, Age: %d\\n\u0026#34;, name, age) fmt.Printf(\u0026#34;City: %s, Temperature: %.1f\\n\u0026#34;, city, temperature) fmt.Printf(\u0026#34;Country: %s, IsStudent: %t\\n\u0026#34;, country, isStudent) fmt.Printf(\u0026#34;Coordinates: (%d, %d)\\n\u0026#34;, x, y) fmt.Printf(\u0026#34;Values: %d, %d\\n\u0026#34;, a, b) fmt.Printf(\u0026#34;Defaults: %d, \u0026#39;%s\u0026#39;, %t\\n\u0026#34;, defaultInt, defaultString, defaultBool) } 常量声明 # package main import \u0026#34;fmt\u0026#34; // 包级常量 const ( Pi = 3.14159 Version = \u0026#34;1.0.0\u0026#34; ) // iota 枚举器 const ( Sunday = iota // 0 Monday // 1 Tuesday // 2 Wednesday // 3 Thursday // 4 Friday // 5 Saturday // 6 ) // 复杂的 iota 用法 const ( _ = iota // 跳过第一个值 KB = 1 \u0026lt;\u0026lt; (10 * iota) // 1024 MB // 1048576 GB // 1073741824 ) func main() { // 函数内常量 const greeting = \u0026#34;Hello\u0026#34; fmt.Printf(\u0026#34;Pi: %.5f\\n\u0026#34;, Pi) fmt.Printf(\u0026#34;Version: %s\\n\u0026#34;, Version) fmt.Printf(\u0026#34;Friday: %d\\n\u0026#34;, Friday) fmt.Printf(\u0026#34;1GB = %d bytes\\n\u0026#34;, GB) fmt.Printf(\u0026#34;Greeting: %s\\n\u0026#34;, greeting) } 数据类型 # 基本类型 # package main import \u0026#34;fmt\u0026#34; func main() { // 布尔类型 var isActive bool = true // 整数类型 var age int = 25 var count int32 = 100 var bigNumber int64 = 9223372036854775807 // 无符号整数 var positive uint = 42 var smallPositive uint8 = 255 // 浮点数 var price float32 = 19.99 var precise float64 = 3.141592653589793 // 复数 var complex1 complex64 = 1 + 2i var complex2 complex128 = 3 + 4i // 字符串 var name string = \u0026#34;Go语言\u0026#34; var multiline string = `这是一个 多行字符串` // 字节和字符 var b byte = \u0026#39;A\u0026#39; // byte 是 uint8 的别名 var r rune = \u0026#39;中\u0026#39; // rune 是 int32 的别名，表示 Unicode 码点 fmt.Printf(\u0026#34;Boolean: %t\\n\u0026#34;, isActive) fmt.Printf(\u0026#34;Integers: %d, %d, %d\\n\u0026#34;, age, count, bigNumber) fmt.Printf(\u0026#34;Unsigned: %d, %d\\n\u0026#34;, positive, smallPositive) fmt.Printf(\u0026#34;Floats: %.2f, %.15f\\n\u0026#34;, price, precise) fmt.Printf(\u0026#34;Complex: %v, %v\\n\u0026#34;, complex1, complex2) fmt.Printf(\u0026#34;String: %s\\n\u0026#34;, name) fmt.Printf(\u0026#34;Multiline: %s\\n\u0026#34;, multiline) fmt.Printf(\u0026#34;Byte: %c (%d), Rune: %c (%d)\\n\u0026#34;, b, b, r, r) } 复合类型 # 数组 # package main import \u0026#34;fmt\u0026#34; func main() { // 数组声明和初始化 var arr1 [5]int // 零值数组 var arr2 = [5]int{1, 2, 3, 4, 5} // 完整初始化 var arr3 = [...]int{1, 2, 3} // 自动推断长度 var arr4 = [5]int{1: 10, 3: 30} // 指定索引初始化 // 多维数组 var matrix [3][3]int matrix[0] = [3]int{1, 2, 3} matrix[1] = [3]int{4, 5, 6} matrix[2] = [3]int{7, 8, 9} fmt.Printf(\u0026#34;arr1: %v\\n\u0026#34;, arr1) fmt.Printf(\u0026#34;arr2: %v\\n\u0026#34;, arr2) fmt.Printf(\u0026#34;arr3: %v\\n\u0026#34;, arr3) fmt.Printf(\u0026#34;arr4: %v\\n\u0026#34;, arr4) fmt.Printf(\u0026#34;matrix: %v\\n\u0026#34;, matrix) // 数组操作 fmt.Printf(\u0026#34;Length of arr2: %d\\n\u0026#34;, len(arr2)) fmt.Printf(\u0026#34;arr2[2]: %d\\n\u0026#34;, arr2[2]) } 切片（Slice） # package main import \u0026#34;fmt\u0026#34; func main() { // 切片声明和初始化 var slice1 []int // nil 切片 var slice2 = []int{1, 2, 3, 4, 5} // 字面量初始化 var slice3 = make([]int, 5) // 使用 make 创建 var slice4 = make([]int, 3, 10) // 指定长度和容量 // 从数组创建切片 arr := [5]int{1, 2, 3, 4, 5} slice5 := arr[1:4] // [2, 3, 4] slice6 := arr[:3] // [1, 2, 3] slice7 := arr[2:] // [3, 4, 5] slice8 := arr[:] // [1, 2, 3, 4, 5] fmt.Printf(\u0026#34;slice1: %v (len=%d, cap=%d)\\n\u0026#34;, slice1, len(slice1), cap(slice1)) fmt.Printf(\u0026#34;slice2: %v (len=%d, cap=%d)\\n\u0026#34;, slice2, len(slice2), cap(slice2)) fmt.Printf(\u0026#34;slice3: %v (len=%d, cap=%d)\\n\u0026#34;, slice3, len(slice3), cap(slice3)) fmt.Printf(\u0026#34;slice4: %v (len=%d, cap=%d)\\n\u0026#34;, slice4, len(slice4), cap(slice4)) // 切片操作 slice2 = append(slice2, 6, 7, 8) // 追加元素 fmt.Printf(\u0026#34;After append: %v\\n\u0026#34;, slice2) // 切片复制 slice9 := make([]int, len(slice2)) copy(slice9, slice2) fmt.Printf(\u0026#34;Copied slice: %v\\n\u0026#34;, slice9) // 切片遍历 for i, v := range slice2 { fmt.Printf(\u0026#34;Index %d: %d\\n\u0026#34;, i, v) } } 映射（Map） # package main import \u0026#34;fmt\u0026#34; func main() { // Map 声明和初始化 var map1 map[string]int // nil map var map2 = make(map[string]int) // 空 map var map3 = map[string]int{ // 字面量初始化 \u0026#34;apple\u0026#34;: 5, \u0026#34;banana\u0026#34;: 3, \u0026#34;orange\u0026#34;: 8, } // Map 操作 map2[\u0026#34;go\u0026#34;] = 100 map2[\u0026#34;python\u0026#34;] = 85 map2[\u0026#34;java\u0026#34;] = 90 fmt.Printf(\u0026#34;map1: %v\\n\u0026#34;, map1) fmt.Printf(\u0026#34;map2: %v\\n\u0026#34;, map2) fmt.Printf(\u0026#34;map3: %v\\n\u0026#34;, map3) // 检查键是否存在 value, exists := map2[\u0026#34;go\u0026#34;] fmt.Printf(\u0026#34;go: %d, exists: %t\\n\u0026#34;, value, exists) value, exists = map2[\u0026#34;rust\u0026#34;] fmt.Printf(\u0026#34;rust: %d, exists: %t\\n\u0026#34;, value, exists) // 删除键 delete(map2, \u0026#34;java\u0026#34;) fmt.Printf(\u0026#34;After delete java: %v\\n\u0026#34;, map2) // 遍历 Map for key, value := range map3 { fmt.Printf(\u0026#34;%s: %d\\n\u0026#34;, key, value) } // 只遍历键 for key := range map3 { fmt.Printf(\u0026#34;Key: %s\\n\u0026#34;, key) } } 结构体（Struct） # package main import \u0026#34;fmt\u0026#34; // 定义结构体 type Person struct { Name string Age int Email string Address Address // 嵌套结构体 } type Address struct { Street string City string Country string PostCode string } // 结构体方法 func (p Person) String() string { return fmt.Sprintf(\u0026#34;%s (%d years old)\u0026#34;, p.Name, p.Age) } func (p *Person) UpdateAge(newAge int) { p.Age = newAge } func main() { // 结构体初始化 var p1 Person // 零值初始化 p2 := Person{ Name: \u0026#34;Alice\u0026#34;, Age: 30, Email: \u0026#34;alice@example.com\u0026#34;, Address: Address{ Street: \u0026#34;123 Main St\u0026#34;, City: \u0026#34;Beijing\u0026#34;, Country: \u0026#34;China\u0026#34;, PostCode: \u0026#34;100000\u0026#34;, }, } p3 := Person{ Name: \u0026#34;Bob\u0026#34;, Age: 25, } // 使用字段名初始化 p4 := Person{ Name: \u0026#34;Charlie\u0026#34;, Age: 35, Email: \u0026#34;charlie@example.com\u0026#34;, } fmt.Printf(\u0026#34;p1: %+v\\n\u0026#34;, p1) fmt.Printf(\u0026#34;p2: %+v\\n\u0026#34;, p2) fmt.Printf(\u0026#34;p3: %s\\n\u0026#34;, p3) // 访问和修改字段 p3.Email = \u0026#34;bob@example.com\u0026#34; p3.Address.City = \u0026#34;Shanghai\u0026#34; // 调用方法 fmt.Printf(\u0026#34;Before update: %s\\n\u0026#34;, p4) p4.UpdateAge(36) fmt.Printf(\u0026#34;After update: %s\\n\u0026#34;, p4) // 结构体指针 p5 := \u0026amp;Person{Name: \u0026#34;David\u0026#34;, Age: 28} fmt.Printf(\u0026#34;p5: %+v\\n\u0026#34;, p5) fmt.Printf(\u0026#34;p5.Name: %s\\n\u0026#34;, p5.Name) // 自动解引用 } 字符 # 这些单词表示 Unicode 字符的类别：\nnewline = /* Unicode 代码点 U+000A */ . unicode_char = /* 排除换行以外的任意 Unicode 代码点 */ . unicode_letter = /* 一个字母（\u0026#34;Letter\u0026#34;）类型的 Unicode 代码点 */ . unicode_digit = /* 一个数字（\u0026#34;Number, decimal digit\u0026#34;）类型的 Unicode 代码点 */ . 在 Unicode8.0 标准中，第 4.5 章节 “一般类别” 中定义了字符的类别。Go 能够处理任何字符集，包括 Lu，Li，Lt，Lm 或 Lo 作为 Unicode 字母，还可以把数字字符集 Nd 当作 Unicode 数字处理。\n字母和数字 # 我们认为下划线 _ （U+005F）是一个字母：\nletter = unicode_letter | \u0026#34;_\u0026#34; . decimal_digit = \u0026#34;0\u0026#34; … \u0026#34;9\u0026#34; . octal_digit = \u0026#34;0\u0026#34; … \u0026#34;7\u0026#34; . hex_digit = \u0026#34;0\u0026#34; … \u0026#34;9\u0026#34; | \u0026#34;A\u0026#34; … \u0026#34;F\u0026#34; | \u0026#34;a\u0026#34; … \u0026#34;f\u0026#34; . 词汇元素 # 注释 # 注释是程序的说明文档。在 Go 中有两种形式：\n单行注释从 // 开始直到行末结束。 通用注释从 /* 开始直到 */ 结束。 注释不能嵌套在其他注释、字符串和 rune 的字面值中。不包含换行符的通用注释之间通过空格符连接，其他情况下每段注释都会另起一行。\n词汇元素 # 词汇元素构成了 Go 语言的词汇表。它有四种类型：标识符、关键字、操作符/标点符号、字面值。空白符可以是空格（U+0020）、水平制表符（U+0009）、换行符（U+000D）或换行符（U+000A）。它本身会被忽略，一般用来区分不同的词汇元素。换行符或文件终止符（EOF）还可能触发编译程序在源代码的行末或文件末尾追加分号。在分解源代码的词汇元素的过程中，会把当前可以形成有效词汇元素的最长字符序列作为下一个词汇元素。\n分号 # 正规语法在很多产生式中使用分号 \u0026ldquo;;\u0026rdquo; 作为终结符。Go 程序中遵循下面两条规则省略了大部分的分号：\n当某行的最后一个词汇元素是以下元素时自动补全分号： 一个标识符。\n一个整数，浮点数，虚数，rune 或字符串字面值。\n关键字 break、continue、fallthrough 和 return 其中之一。\n操作符/标点符号 ++，--，)，] 和 } 其中之一。\n为了支持独占一行的复杂语句，会省略与 \u0026ldquo;)\u0026rdquo; 或 \u0026ldquo;}\u0026rdquo; 相邻的分号。 为了反应惯用用途，本篇文档的所有例子都基于以上规则省略分号。\n标识符 # 标识符表示程序实体单元，例如：变量、类型。一个标识符由一个或多个字母和数字组成。标识符的首字符必须为字母。\nidentifier = letter { letter | unicode_digit } . a _x9 ThisVariableIsExported αβ Go 已经预定义了一些标识符。\n关键字 # 以下关键字是预留的，它们不能作为标识符：\nbreak default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var 操作符和标点符号 # 以下字符序列用于表示操作符（包括赋值运算符）和标点符号：\n+ \u0026amp; += \u0026amp;= \u0026amp;\u0026amp; == != ( ) - | -= |= || \u0026lt; \u0026lt;= [ ] * ^ *= ^= \u0026lt;- \u0026gt; \u0026gt;= { } / \u0026lt;\u0026lt; /= \u0026lt;\u0026lt;= ++ = := , ; % \u0026gt;\u0026gt; %= \u0026gt;\u0026gt;= -- ! ... . : \u0026amp;^ \u0026amp;^= 整型字面值 # 整型字面值是一个数字序列，相当于整型常量。可以使用前缀指定非小数进制：0 表示八进制，0x/0X 表示十六进制。在十六进制字面值中，字母 a-f 和 A-F 都表示数字 10-15。\nint_lit = decimal_lit | octal_lit | hex_lit . decimal_lit = ( \u0026#34;1\u0026#34; … \u0026#34;9\u0026#34; ) { decimal_digit } . octal_lit = \u0026#34;0\u0026#34; { octal_digit } . hex_lit = \u0026#34;0\u0026#34; ( \u0026#34;x\u0026#34; | \u0026#34;X\u0026#34; ) hex_digit { hex_digit } . 42 0600 0xBadFace 170141183460469231731687303715884105727 浮点字面值 # 浮点字面值是一个小数，相当于浮点数常量。它由整数部分，小数点，小数部分和指数部分构成。整数部分和小数部分用小数点链接；指数部分由 e / E 字符后接一个有符号指数构成。整数部分和小数部分可以省略其一；小数点和指数部分可以省略其一。\nfloat_lit = decimals \u0026#34;.\u0026#34; [ decimals ] [ exponent ] | decimals exponent | \u0026#34;.\u0026#34; decimals [ exponent ] . decimals = decimal_digit { decimal_digit } . exponent = ( \u0026#34;e\u0026#34; | \u0026#34;E\u0026#34; ) [ \u0026#34;+\u0026#34; | \u0026#34;-\u0026#34; ] decimals . 0. 72.40 072.40 // == 72.40 2.71828 1.e+0 6.67428e-11 1E6 .25 .12345E+5 虚数字面值 # 虚数字面值是一个小数，相当于复数常量中的虚数部分。它由浮点数或者整数后接小写字母 i 构成。\nimaginary_lit = (decimals | float_lit) \u0026#34;i\u0026#34; . 0i 011i // == 11i 0.i 2.71828i 1.e+0i 6.67428e-11i 1E6i .25i .12345E+5i Rune 字面值 # rune 类型字面值相当于一个 rune 常量。它是一个表示 Unicode 代码点的整数。rune 类型字面值表示为用单引号包裹的一个或多个字符，像 \u0026lsquo;x\u0026rsquo; 或 \u0026lsquo;\\n\u0026rsquo;。在单引号中除了换行符和未转义的单引号其他的字符都可以直接显示。单引号包裹的字符的值和字符在 Unicode 编码中的值相等，而以反斜线开头的多字符序列会把值翻译成多种格式。\n使用引号表示单字符是最简单的方式；因为 Go 的源文本是 UTF-8 编码，一个整数可能代表多个 UTF-8 字节。例如， \u0026lsquo;a\u0026rsquo; 可以使用单字节表示字符 a，Unicode 编码 U+0061，值 0x61，而 \u0026lsquo;ä\u0026rsquo; 是两字节表示分音符的 a，Unicode 编码 U+00E4，值 0xe4。\n反斜线能将任意值编码成 ASCII 文本。有四种方式将整数值表示为数字常量：\\x 后接两个十六进制数；\\u 后接四个十六进制数；\\U 后接八个十六进制数。 \\ 后接三个八进制数。每种情况下都使用相应进制来表示字面量的整数值。\n虽然这四种方式都以整数表示，但它们的有效区间并不相同。八进制只能表示 0 - 255 以内的整数。十六进制满可以满足需求。\\u 和 \\U 都可以表示 Unicode 代码点，不过其中的一些值是无效的，特别是 0x10FFFF 以上的值。\n反斜线结合以下字符具有特殊含义：\n\\a U+0007 alert or bell \\b U+0008 退格符 \\f U+000C form feed \\n U+000A line feed or newline \\r U+000D carriage return \\t U+0009 水平制表符 \\v U+000b 垂直制表符 \\\\ U+005c 反斜线 \\\u0026#39; U+0027 单引号 (只在 rune 字面值中有效) \\\u0026#34; U+0022 双引号 (只在字符串字面值中有效) 其他所有以反斜线开头的序列在 rune 的规则中都是非法的。\nrune_lit = \u0026#34;\u0026#39;\u0026#34; ( unicode_value | byte_value ) \u0026#34;\u0026#39;\u0026#34; . unicode_value = unicode_char | little_u_value | big_u_value | escaped_char . byte_value = octal_byte_value | hex_byte_value . octal_byte_value = `\\` octal_digit octal_digit octal_digit . hex_byte_value = `\\` \u0026#34;x\u0026#34; hex_digit hex_digit . little_u_value = `\\` \u0026#34;u\u0026#34; hex_digit hex_digit hex_digit hex_digit . big_u_value = `\\` \u0026#34;U\u0026#34; hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit . escaped_char = `\\` ( \u0026#34;a\u0026#34; | \u0026#34;b\u0026#34; | \u0026#34;f\u0026#34; | \u0026#34;n\u0026#34; | \u0026#34;r\u0026#34; | \u0026#34;t\u0026#34; | \u0026#34;v\u0026#34; | `\\` | \u0026#34;\u0026#39;\u0026#34; | `\u0026#34;` ) . \u0026#39;a\u0026#39; \u0026#39;ä\u0026#39; \u0026#39;本\u0026#39; \u0026#39;\\t\u0026#39; \u0026#39;\\000\u0026#39; \u0026#39;\\007\u0026#39; \u0026#39;\\377\u0026#39; \u0026#39;\\x07\u0026#39; \u0026#39;\\xff\u0026#39; \u0026#39;\\u12e4\u0026#39; \u0026#39;\\U00101234\u0026#39; \u0026#39;\\\u0026#39;\u0026#39; // 包含单引号的 rune 字面值 \u0026#39;aa\u0026#39; // 无效: 太多字符 \u0026#39;\\xa\u0026#39; // 无效: 缺少十六进制数 \u0026#39;\\0\u0026#39; // 无效: 缺少八进制数 \u0026#39;\\uDFFF\u0026#39; // 无效: surrogate half \u0026#39;\\U00110000\u0026#39; // 无效: 非法的 Unicode 代码点 字符串字面量 # 字符串字面量表示从字符序列中获取的字符串常量。它有两种格式：原始字符串字面量和解释型字符串字面量。\n原始字符串是由反引号包裹（foo）。字符串中除反引号以外的其他字符都会显示出来。原生字符串由反引号之间的（默认 UTF-8 编码）的字符组成。它的值为引号内未经解释（默认 UTF-8 编码）所有字符；尤其是，反斜线再字符串中没有特殊意义并且字符串中保留换行符。在原始字符串的值中会丢弃回车键返回 \u0026lsquo;\\r\u0026rsquo; 字符。\n解释型字符串由双引号之间的字符组成（\u0026ldquo;bar\u0026rdquo;）。除了换行符和双引号其他字符都会显示出来。双引号之间的文本组成字面量的值。反斜线的转义规则与 rune 字面量基本相同（不同的是 \\’ 非法，而 \u0026quot; 合法）。三位八进制数（\\nnn）和两位十六进制数（\\xnn）换码符的值表示相应字符串的字节。其他的换码符都表示字符各自的 UTF-8 编码（可能是多字节）。因此字符串 \\377 和 \\xFF 都表示值为 0xFF=255 的单个字节，而 ÿ, \\u00FF, \\U000000FF 和 \\xc3\\xbf 表示 UTF-8 编码字符 U+00FF 的两个字节 0xc3 0xbf。\nstring_lit = raw_string_lit | interpreted_string_lit . raw_string_lit = \u0026#34;`\u0026#34; { unicode_char | newline } \u0026#34;`\u0026#34; . interpreted_string_lit = `\u0026#34;` { unicode_value | byte_value } `\u0026#34;` . `abc` // 等价于 \u0026#34;abc\u0026#34; `\\n \\n` // 等价于 \u0026#34;\\\\n\\n\\\\n\u0026#34; \u0026#34;\\n\u0026#34; \u0026#34;\\\u0026#34;\u0026#34; // 等价于 `\u0026#34;` \u0026#34;Hello, world!\\n\u0026#34; \u0026#34;日本語\u0026#34; \u0026#34;\\u65e5本\\U00008a9e\u0026#34; \u0026#34;\\xff\\u00FF\u0026#34; \u0026#34;\\uD800\u0026#34; // 无效: surrogate half \u0026#34;\\U00110000\u0026#34; // 无效: 无效的 Unicode 代码点 这些例子都表示相同的字符串：\n\u0026#34;日本語\u0026#34; // UTF-8 文本 `日本語` // UTF-8 文本作为原生字面值 \u0026#34;\\u65e5\\u672c\\u8a9e\u0026#34; // 确定的 Unicode 代码点 \u0026#34;\\U000065e5\\U0000672c\\U00008a9e\u0026#34; // 确定的 Unicode 代码点 \u0026#34;\\xe6\\x97\\xa5\\xe6\\x9c\\xac\\xe8\\xaa\\x9e\u0026#34; // 确定的 UTF-8 字节 如果源代码中使用两个代码点表示一个字符，例如带音标的字母，把它放在 rune 中会报错（它不是单代码点）。并且在字符串中会显示两个代码点。\n常量 # 常量分为：布尔型，rune型，整型，浮点型，复数型，字符串型。其中 rune，整型，浮点型，复数型统称为数字常量。\n常量的值可以表示为一个 rune字面量，整数字面量，浮点数字面量，虚数字面量，字符串字面量，表示常量的标识符，常量表达式，一个转换结果为常量的类型转换，和一些返回值为常量的内置函数(接受任何值的unsafe.Sizeof，接受部分表达式的cap 或 len，接受虚数常量的real 和 imag，接受数字常量的 complex)。布尔类型的值为预定义常量 true 或 false，预定义的标识符 iota 表示一个整型常量。\n一般情况下复数常量是常量表达式的一种形式。会在常量表达式章节详细讨论。\n数字常量可以表示任意精度的确定值而且不会溢出。因此，没有常量可以表示非 0，无穷大和非数字值。\n常量可以指定类型也可以不指定类型。字面值常量，true，false，iota，和只包含无类型常量操作的常量表达式是无类型的。\n常量可以通过常量声明和转换时显式的指定具体类型，也可以隐式的在变量声明、赋值或作为表达式操作元时隐式的指定具体类型。如果常量的值和他的类型不匹配，会报错。\n无类型常量由一个默认的类型，这个类型会根据使用常量时的上下文进行隐式转换。例如：短变量声明 i := 0 没有指定 i 的类型。无类型常量的默认类型可以是：bool，rune，int，float64，complex128 或者 string，具体选择哪种类型由常量的值决定。\n实现限制：虽然数字常量在 Go 中是任意精度，不过编译器在实现时会在内部限制精度。这意味着每个编译器实现都要：\n至少保证整形常量有 256 位\n浮点数常量（包括复数常量）都要保证至少 256 位的主体部分和至少 16 位的有符号指数部分\n如果不能表示给定整数的精度抛出错误\n如果浮点数或复数溢出抛出错误\n如果由于精度限制不能表示浮点数或者复数进行舍入\n这些要求同时作用于字面量常量额和常量表达式的结果。\n变量 # 变量是一个用来储存值的位置。根据不同的变量类型，可以保存不同的值。\n变量声明，函数参数和返回值，声明的函数签名，函数字面值都会为命名变量预留储存空间。调用内置的 new 函数或获取复合字面值的地址都会在运行时为变量分配存储空间。这种匿名变量是通过（可能是隐式的）指针间接引用的。\n像数组，切片和结构体类型的变量，它们内部都包含很多元素或字段，而且这些元素和字段都可以直接被访问。数组和切片中的每个元素的行为和单独的变量基本相同。\n变量的静态类型可以通过变量声明、提供给 new 的类型、复合字面值、结构体变量声明的元素类型以上几种方式确定。通过new或者类型初始化。接口类型的变量也有一个明确的动态类型，这个动态类型是在运行时赋值给变量的具体值类型（特例：预声明的 nil 是无类型的）。动态类型在程序的执行过程中可能并不相同，但是接口变量的值是可以分配给相同静态类型的变量。\nvar x interface{} // x 的静态类型为 interface{} 值为 nil var v *T // v 的静态类型为 *T 值为 nil x = 42 // x 的动态类型为 int 值为 42 x = v // x 动态类型为 *T 值为 (*T)(nil) 在表达式中使用变量可以取出变量的值；这个值就是变量最近一次被赋予的值。如果没有对变量赋过值，那么他的值是该类型的零值。\n类型 # 类型是一个集合，集合包括值和针对值的操作\u0026amp;方法。一个类型可以使用类型名来表示。类型有多种表现形式：如果存在类型名，可以使用类型名表示，或者也可以使用根据已有类型组合成的类型字面值。\nType = TypeName | TypeLit | \u0026#34;(\u0026#34; Type \u0026#34;)\u0026#34; . TypeName = identifier | QualifiedIdent . TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType . Go 已经预先声明了某些类型的名称。并引入了类型声明。复合类型（数组、结构体、指针、函数、接口、切片、map、channel）可以使用他们的类型字面值。\n每个类型T都有一个底层类型。如果T是预定义类型或者类型字面值。那么底层类型就是他自身。否则，T的底层类型就是它再类型声明时引用到的类型。\ntype ( A1 = string A2 = A1 ) type ( B1 string B2 B1 B3 []B1 B4 B3 ) string，A1，A2，B1，B2 的底层类型是 string。[]B1，B3，B4 的下游类型是[]B1。\n方法集 # 类型可能会有一个与之关联的方法集。接口类型的方法集就可以使用自身表示。对于其他类型，类型 T 的方法集由所有接收者类型为 T 的方法组成。而对应指针类型 *T 的方法集由所有接收者类型为 T 或 *T 的方法组成。如果是结构体类型且含有嵌入字段，那么方法集中可能还会包含更多的方法，具体请看结构体类型章节。其他类型的方法集都为空。方法集中的每个方法都有唯一且不为空的方法名。\n类型的方法集用来确定类型实现的接口和以类型作为接收者能够调用的方法。\n布尔类型 # 布尔类型表示预定义常量 true 和 false 表示布尔真实值的集合。预定义的布尔类型为 bool；它是通过类型声明创建的。\n数字类型 # 一个数字类型相当于整型和浮点型的所有值的集合。预定义的数字类型包括：\nuint8 8 位无符号整数集合 (0 to 255) uint16 16 位无符号整数集合 (0 to 65535) uint32 32 位无符号整数集合 (0 to 4294967295) uint64 64 位无符号整数集合 (0 to 18446744073709551615) int8 8 位有符号整数集合 (-128 to 127) int16 16 位有符号整数集合 (-32768 to 32767) int32 32 位有符号整数集合 (-2147483648 to 2147483647) int64 64 位有符号整数集合 (-9223372036854775808 to 9223372036854775807) float32 IEEE-754 32 位浮点数集合 float64 IEEE-754 64 位浮点数集合 complex64 实部虚部都为 float32 的复数集合 complex128 实部虚部都为 float64 的复数集合 byte uint8 的别名 rune int32 的别名 n 位整数的值具有 n 比特的宽度并用补码表示。\n以下几种预定义类型由具体平台实现指定长度：\nuint 32 或 64 位 int 和 uint 位数相同 uintptr 能够容纳指针值的无符号整数 为了避免移植性问题，除了被 uint8 的别名 byte 和 int32 的别名 rune，其他所有的数字类型都是通过类型声明定义。当在表达式中使用不同的数字类型需要进行类型转换。例如：int32 和 int 不是相同的类型，即使他们在指定的平台上是相等的。\n字符串类型 # 字符串类型表示字符串的值类型。字符串的值是一个字节序列（有可能为空）。字符串一旦创建就无法修改它的值。预定义的字符串类型是 string，它是通过类型声明定义的。\n可以使用内置函数 len 获取字符串长度。如果字符串是常量那么它的长度在编译时也为常量。可以通过数字下标 0～len(s)-1 访问字符串字节。获取字符串的地址是非法操作；如果 s[i] 是字符串的第 i 个字节，那么 \u0026amp;s[i] 是无效的。\n数组类型 # 数组是一定数量的单一类型元素序列，而这个单一类型叫做元素类型。元素的个数表示元素的长度，它永远不是负数。\nArrayType = \u0026#34;[\u0026#34; ArrayLength \u0026#34;]\u0026#34; ElementType . ArrayLength = Expression . ElementType = Type . 长度是数组类型的一部分；它是一个类型为 int 的非负常量。可以用内置函数 len 获取数组的长度。元素可以通过下标 0～len(a)-1 访问。数组一般都是一维的，不过也可以是多维的。\n[32]byte [2*N] struct { x, y int32 } [1000]*float64 [3][5]int [2][2][2]float64 // same as [2]([2]([2]float64)) 切片类型 # 切片描述了底层数组的一个连续片段并提供对连续片段内元素的访问。切片类型表示元素类型的数组的所有切片的集合。没有被初始化的切片用 nil 表示。\nSliceType = \u0026#34;[\u0026#34; \u0026#34;]\u0026#34; ElementType . 与数组一样，切片的可以使用索引访问并且有长度，切片的长度可以通过内置的 len 函数获取；与数组不同的是它的长度在运行时是可以变化的。我们可以通过下标 0～len(s)-1 来访问切片内的元素。切片的索引可能会小于相同元素再底层数组的索引。\n切片一旦初始化，那么就有一个与之对应的底层数组保存切片中的元素。切片和底层的数组还有其他指向该数组的切片共享相同的储存空间；而不同的数组总是有着不同的存储空间。\n切片的底层数组可能会延伸到切片末尾以外，切片的容积等于切片现在的长度加上数组中切片还没使用的长度；可以从原始切片中切出一个长度与容量相等的切片。切片的容量可以通过内置的 cap(a) 函数来获取。可以通过函数make来创建一个T类型的新切片。\n使用内置函数 make 可以出实话给定元素类型 T 的切片。make 函数接收三个参数：切片类型、切片长度、切片容积，其中切片容积是可选参数。make 创建的切片会在底层分配一个切片所引用的新数组。\nmake([]T, length, capacity) make 的作用就是创建新数组并切分它，所以下面两种写法是等价的：\nmake([]int, 50, 100) new([100]int)[0:50] 与数组相同，切片一般是一维的，不过也可以复合成多维。数组中的数组都必须是相同的长度，但是切片中的切片长度是动态变化的，不过切片中的切片需要单独初始化。\n结构体类型 # 结构体是一个命名元素序列，命名元素也叫做字段，每个字段都对应一个名称和类型，字段的名字可以是显式指定的（标识符列表）也可以是隐式的（嵌入字段）。在结构体中非空字段具有唯一性。\nStructType = \u0026#34;struct\u0026#34; \u0026#34;{\u0026#34; { FieldDecl \u0026#34;;\u0026#34; } \u0026#34;}\u0026#34; . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ \u0026#34;*\u0026#34; ] TypeName . Tag = string_lit . // 空结构体. struct {} // 6个字段的结构体. struct { x, y int u float32 _ float32 // padding A *[]int F func() } 一个指定了类型而没有指定名称的字段叫做嵌入字段，嵌入字段必须指定类型名 T 或指向非接口类型的指针类型 *T，其中 T 不能为指针类型。或者一个非接口类型的指针。并且T本身不能为指针类型。这种情况下会把类型名作为字段的名字。\n// 一个包含 4 个嵌入字段 T1, *T2, P.T3 和 *P.T4 的结构体 struct { T1 // 字段名为 T1 *T2 // 字段名为 T2 P.T3 // 字段名为 T3 *P.T4 // 字段名为 T4 x, y int // 字段名为 x 和 y } 以下声明是错误的因为字段名称必须唯一。\nstruct { T // 嵌入字段 *T 与 *P.T 冲突 *T // 嵌入字段 T 与 *P.T 冲突 *P.T // 嵌入字段 T 与 *T 冲突 } 如果 x.f 是表示该字段或方法 f 的合法选择器，则会调用结构 x 中嵌入字段的字段或方法 f。\n从嵌入字段组合来的字段与结构体原来的字段行为基本相同，只是不能在结构体的复合字面值中直接使用。\n给定一个结构体 S 和一个类型 T，依据以下规则生成组合后的方法集：\n如果 S 包含嵌入字段 T，则 S 和 *S 的方法集包括接收者为 T 的方法集，而 *S 包括 接收者为 *T 的方法集。 如果 S 包含字段 T。那么S和S均包含接收者为 T 和 *T 的所有方法集。 声明字段时可以给该字段添加一个字符串的 tag。这个 tag 将会成为它所对应字段的一个属性。空 tag 和缺省 tag 是相同的。tag 的值可以通过反射的接口获取，可以作为类型结构体的类型定义的一部分，也可以忽略。\nstruct { x, y float64 \u0026#34;\u0026#34; // 空 tag 和缺省 tag 相同 name string \u0026#34;any string is permitted as a tag\u0026#34; _ [4]byte \u0026#34;ceci n\u0026#39;est pas un champ de structure\u0026#34; } // 结构体对应一个 TimeStamp 的 protocol buffer. // tag 字符串中定义了 protocol buffer 字段对应的数字; // 一般使用 reflect 包读取他们. struct { microsec uint64 `protobuf:\u0026#34;1\u0026#34;` serverIP6 uint64 `protobuf:\u0026#34;2\u0026#34;` } 指针类型 # 指针类型表示所有指向给定类型变量的指针集合。这个指定的类型叫做指针的基础类型。没有初始化的指针值为nil。\nPointerType = \u0026#34;*\u0026#34; BaseType . BaseType = Type . *Point *[4]int 函数类型 # 函数类型可以表示所有具有相同参数类型和返回值类型的函数。未初始化的函数类型值为 nil。\nFunctionType = \u0026#34;func\u0026#34; Signature . Signature = Parameters [ Result ] . Result = Parameters | Type . Parameters = \u0026#34;(\u0026#34; [ ParameterList [ \u0026#34;,\u0026#34; ] ] \u0026#34;)\u0026#34; . ParameterList = ParameterDecl { \u0026#34;,\u0026#34; ParameterDecl } . ParameterDecl = [ IdentifierList ] [ \u0026#34;...\u0026#34; ] Type . 在参数和返回值列表中，标识符列表必须同时存在或缺省。如果存在，那么每个名字都表示指定类型的一个参数/返回值，这些标识符必须非空并且不能重复。如果缺省，指定类型的参数/返回值使用对应的类型表示。参数列表和返回值列表一般都是需要加括号，不过在只有一个缺省返回值时，它可以不使用括号。\n函数的最后一个参数可以添加前缀 ...。包含这种参数的函数叫做变参函数，它可以接收零个或多个参数。\nfunc() func(x int) int func(a, _ int, z float32) bool func(a, b int, z float32) (bool) func(prefix string, values ...int) func(a, b int, z float64, opt ...interface{}) (success bool) func(int, int, float64) (float64, *[]int) func(n int) func(p *T) 接口类型 # 接口类型指定了一个方法集。一个接口类型变量可以保存任何方法集是该接口超集的类型。我们可以认为类型实现了接口。没有初始化的接口类型值为 nil。\nInterfaceType = \u0026#34;interface\u0026#34; \u0026#34;{\u0026#34; { MethodSpec \u0026#34;;\u0026#34; } \u0026#34;}\u0026#34; . MethodSpec = MethodName Signature | InterfaceTypeName . MethodName = identifier . InterfaceTypeName = TypeName . 在接口类型的方法集中，每个方法的名称必须是非空且唯一。\n// A simple File interface interface { Read(b Buffer) bool Write(b Buffer) bool Close() } 接口可以由多个类型实现，例如：类型 S1 和类型 S2 都有以下方法集：\nfunc (p T) Read(b Buffer) bool { return … } func (p T) Write(b Buffer) bool { return … } func (p T) Close() { … } （这里的类型 T 可以表示 S1 也可以表示 S2 ） S1 和 S2 都实现了接口 File，而不用管类型是否还有其他方法。\n一个类型实现了任何方法集的为其子集的接口。因此它可能实现了多个不同接口。例如：所有的类型都实现了空接口：\ninterface{} 与之相似，思考下面这个定义为 Locker 的接口：\ntype Locker interface { Lock() Unlock() } 如果 S1 和 S2 也实现了它：\nfunc (p T) Lock() { … } func (p T) Unlock() { … } 那它们就实现了两个接口 Locker 和 File。\n一个接口 T 可以使用另一个接口 E 来指定方法。这种方式叫做将接口 E 嵌入进接口 T。它把 E 中所有的方法（包括导出和未导出的方法）全部添加进接口 T。\ntype ReadWriter interface { Read(b Buffer) bool Write(b Buffer) bool } type File interface { ReadWriter // 与添加 ReadWriter 接口中的方法是等价的 Locker // 与添加 Locker 接口中的方法是等价的 Close() } type LockedFile interface { Locker File // 无效: Lock, Unlock 不是唯一的 Lock() // 无效: Lock 不是唯一的 } 接口 T 不能递归的嵌入进自己或已经嵌入过它的接口。\n// 无效: Bad 不能嵌入它自己 type Bad interface { Bad } // 无效: Bad1 不能嵌入已经引用它的 Bad2 type Bad1 interface { Bad2 } type Bad2 interface { Bad1 } Map类型 # map 类型是一种以唯一值作为键的无序集合。\nMapType = \u0026#34;map\u0026#34; \u0026#34;[\u0026#34; KeyType \u0026#34;]\u0026#34; ElementType . KeyType = Type . map的键类型必须能使用比较运算符 == 和 != 进行比较。因此它的键类型不能是函数，map，或者切片。如果键是接口类型，那么比较运算符必须能比较他的动态值。如果不能会抛出一个运行时错误。\nmap[string]int map[*T]struct{ x, y float64 } map[string]interface{} map中元素的个数叫做它的长度。对于一个map m。它的长度可以通过内置函数 len 获得，而且它的长度可能再运行时发生变化。map 可以再运行时添加和取回元素，页可以使用内置函数 delete移除元素。\n可以使用内置函数 make 初始化一个新的且为空的 map。它能指定 map 的类型和预留的空间：\nmake(map[string]int) make(map[string]int, 100) map 的预留空间不会固定住 map 的长度；它可以通过添加一定数量的元素来增加自己的长度（nil map 不能添加元素）。nil map 和空 map 是相等的，只是 nil map 不能添加元素。\nChannel类型 # channel提供一种手段在并发执行的函数间发送和接收指定类型的值。没有初始化的 channel 是nil。\nChannelType = ( \u0026#34;chan\u0026#34; | \u0026#34;chan\u0026#34; \u0026#34;\u0026lt;-\u0026#34; | \u0026#34;\u0026lt;-\u0026#34; \u0026#34;chan\u0026#34; ) ElementType . 操作符 \u0026lt;- 可以指定 channel 的数据流动方向。如果没有指定方向，channel 默认是双向的。channel 可以通过转换和赋值来限制只读和只写。\nchan T // 可以接收和发送 T 类型的数据 chan\u0026lt;- float64 // 只能发送 float64 类型的值 \u0026lt;-chan int // 只能接收 \u0026lt;- 与最左侧的 chan 关联：\nchan\u0026lt;- chan int // 等价于 chan\u0026lt;- (chan int) chan\u0026lt;- \u0026lt;-chan int // 等价于 chan\u0026lt;- (\u0026lt;-chan int) \u0026lt;-chan \u0026lt;-chan int // 等价于 \u0026lt;-chan (\u0026lt;-chan int) chan (\u0026lt;-chan int) 可以通过内置的 make 函数初始化 channel。make 函数可以指定channel的类型和容量。\nmake(chan int, 100) 容量是设置了最大能缓存元素的数量。如果没有设置容量或值为 0，channel 就是没有缓存的，这时只有当发送者和接收者都准备好后才会传输数据。而带缓存的 channel 在缓存没有满的时候依然可以成功发送数据，当缓存不为空的时候可以成功接收到数据，值为 nil 的 channel 不能传输数据。\n可以通过内置函数 close 关闭 channel。在接收端的第二个返回值可以用来提示接收者在关闭的 channel 是否还包含数据。\nchannel 可以在发送语句，接收操作中使用。可以不考虑同步性直接在多个 goroutine 中对 channel 调用内置函数 len 和 cap 。channel 的行为和 FIFO 队列相同。举个例子，一个 goruntine 发送数据，另一个 goruntine 接收他们，接收数据的顺序和发送数据的顺序是相同的。\n类型的属性和值 # 类型标识 # 两个类型可能相同也可能不同。\n定义的类型都是不同类型。如果两个类型的底层类型在结构上是相同的，那它们也是相等的。总的来说：\n2 个数组的长度和元素类型相同，那么它们就是相同类型。\n如果两个切片的元素类型相同那么它们就是相同类型。\n如果两个结构体字段顺序相同，并且字段名称、字段类型和 tag 都相同那么它们就是相等的。非导出字段的字段名在不同的包中总是不同的。\n如果两个指针的基础类型相同那么他们具有相同类型。\n如果两个函数具有相同的参数和返回值列表，并且他们的类型相同那么他们就是相同的，参数的名称不一定要相同。\n如果两个接口的方法集完全相同（方法的顺序）。\n如果两个 map 类型的键类型和值类型相同那它们就是相等的。\n如果两个 channel 类型包含的对象类型和 channel 的方向都是相同的那它们就是相同的。\n给出下列声明：\ntype ( A0 = []string A1 = A0 A2 = struct{ a, b int } A3 = int A4 = func(A3, float64) *A0 A5 = func(x int, _ float64) *[]string ) type ( B0 A0 B1 []string B2 struct{ a, b int } B3 struct{ a, c int } B4 func(int, float64) *B0 B5 func(x int, y float64) *A1 ) type\tC0 = B0 这些类型是相等的：\nA0, A1, and []string A2 and struct{ a, b int } A3 and int A4, func(int, float64) *[]string, and A5 B0, B0, and C0 []int and []int struct{ a, b *T5 } and struct{ a, b *T5 } func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5 B0 和 B1 不是一种类型因为它们是通过类型定义方式分别定义的；func(int, float64) *B0 和 func(x int, y float64) *[]string 是不同的，因为 B0 和 []string 不是相同类型。\n可分配性 # 在以下情况下，可以将 x 分配给类型为 T 的变量（把 x 分配给 T）：\nx 的类型为 T\nx 的类型 V 和 T 有相同的底层类型并且类型 T 或 V 至少一个定义的类型\nT 是一个接口类型并且 x 实现了 T\nx 是一个 channel，并且 T 是channel类型，类型V和类型T有相同的元素类型，并且 2 种类型至少有一种不是定义的类型\nx 等于 nil 并且 T 是一个指针，函数，切片，map，channel 或接口类型\nx 是一个可以表示 T 类型值的无类型常量\n代表性 # 满足以下条件时可以用 T 类型的值表示常量 x：\nT 值的集合包括 x\nT 是浮点型，而 x 在没有溢出的情况下能够近似成 T 类型。近似规则使用 IEEE 754 round-to-even，负零和无符号的零相同。需要注意的是，常量的值不会为负零，NaN，或无限值。\nT 为复数类型，并且 x 的 real(x) 和 imag(x) 部分由复数类型对应的浮点类型（float32 或 float64 ）组成。\nx T x 可以表示 T 的值，因为： \u0026#39;a\u0026#39; byte 97 在 byte 类型值的集合中 97 rune rune 是 int32 的别名，97 在 32 位整型值的集合中 \u0026#34;foo\u0026#34; string \u0026#34;foo\u0026#34; 在字符串值的集合中 1024 int16 1024 在 16 位整型值的集合中 42.0 byte 42 在 8 位无符号整型值的集合中 1e10 uint64 10000000000 在 64 位无符号整型值的集合中 2.718281828459045 float32 2.718281828459045 的近似值 2.7182817 在 float32 类型值的集合中 -1e-1000 float64 -1e-1000 的近视值 IEEE -0.0，等于 0 0i int 0 是整型值 (42 + 0i) float32 42.0 (0 虚部) 在 float32 类型值的集合中 x T x 不能表示 T 的值，因为： 0 bool 0 不在布尔值的集合中 \u0026#39;a\u0026#39; string \u0026#39;a\u0026#39; 是 rune 类型, 它不在字符串类型的值集合中 1024 byte 1024 不在 8 位无符号整型值的集合中 -1 uint16 -1 不在 16 位无符号整型值的集合中 1.1 int 1.1 不是整型值 42i float32 (0 + 42i) 不在 float32 类型值的集合中 1e1000 float64 1e1000 取近似值时会溢出成 IEEE 代码块 # 代码块是用大括号括起来的声明和语句。\nBlock = \u0026#34;{\u0026#34; StatementList \u0026#34;}\u0026#34; . StatementList = { Statement \u0026#34;;\u0026#34; } . 除了源码中显式的代码块，也有一些隐式的代码块。\n包含所有的Go代码的全局代码块。\n包含所有包的代码的包代码块。\n包含文件内的所有代码的文件代码块。\n每个 if，switch和 for 的范围都会形成隐式的块。\n每个 switch 和 select 条件都有自己的代码块。\n代码块可以嵌套并且影响作用域。\n声明和作用域 # 一段声明可以给常量，类型，变量，函数，标签，和包绑定标识符。程序中每个标识符都需要声明。相同标识符不能在同一个代码块中声明2次。并且相同标识符不能同时在文件和 package 代码块中声明。\n空标识符可以和其他标识符一样在声明中使用。不过它不绑定标识符，等于没有声明。在 package 代码块中 init 标识符只能用做 init 函数的标识符，就像空标识符一样，它不会引入新的绑定。\nDeclaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl | MethodDecl . 声明过的标识符的作用域就是声明标识符所在的作用域。\ngo使用块来规定词汇的方位：\n预定义的标识符具有全局作用域。\n所有定义的顶级标识符具有包作用域。\nimport进来的包的名字标识符具有文件作用域。\n方法的接收者，函数参数，返回值变量具有函数作用域。\n函数内定义的参量和变量标识符的作用域是标识符被声明到容纳他的块结束。\n一个代码块中声明的标识符可以在它内部的代码块中重新声明。在内部代码块的作用域中标识符表示在内部代码块中声明的实体。\npakcage 语句不属于声明。包名不会出现在任何的作用域中。它的作用只是用来标识属于相同包的多个文件并在导入时指定默认包名。\n标签的作用域 # 可以使用标签语句来声明标签，并且可以在 break，continue，goto 语法中使用。如果只声明但没有使用标签时非法的。标签的作用域只有定义时的函数体，早递归函数体中没有作用。\n空标识符 # 空标识符使用下划线 _ 代表。与一般的非空标识符不同，它作为匿名标识符在声明，运算元和赋值语句中都有特殊含义。\n预定义的标识符 # 以下标识符已经在全局作用域中预先声明：\nTypes: bool byte complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr Constants: true false iota Zero value: nil Functions: append cap close complex copy delete imag len make new panic print println real recover 导出标识符 # 标识符可以导出供其他包使用。在以下两种情况同时满足时标识符是导出的：\n标识符的首字母是大写（Unicode 的 Lu 类） 标识符声明在包作用域或者它是字段名/方法名。 其他任何标识符都不是导出的。\n标识符的唯一性 # 给定一个标识符集合，一个标识符与集合中的每个标识符都不相同，那就认为这个标识符是唯一的。假设有两个标识符，如果它们的拼写不同，或者它们在不同的包中并没有导出，那它们就是不同标识符。相反，其他情况下都认为标识符是相同的。\n常量声明 # 常量声明使用常量表达式绑定一系列标识符。标识符的数量必须等于表达式的数量。左侧第 n 个标识符绑定右侧第 n 个表达式的值。\nConstDecl = \u0026#34;const\u0026#34; ( ConstSpec | \u0026#34;(\u0026#34; { ConstSpec \u0026#34;;\u0026#34; } \u0026#34;)\u0026#34; ) . ConstSpec = IdentifierList [ [ Type ] \u0026#34;=\u0026#34; ExpressionList ] . IdentifierList = identifier { \u0026#34;,\u0026#34; identifier } . ExpressionList = Expression { \u0026#34;,\u0026#34; Expression } . 如果给定类型，常量会指定类型，并且表达式的值必须能对这个类型进行赋值。\n如果没有给定类型。常量会转换成相应的表达式类型。如果表达式的值是无类型常量，那么声明的常量也是无类型的，并且常量的标识符代表常量的值。例如：即使小数部分是 0，只要表达式是浮点数字面值，常量标识符也表示为浮点数常量。\nconst Pi float64 = 3.14159265358979323846 const zero = 0.0 // 无类型浮点数常量 const ( size int64 = 1024 eof = -1 // 无类型整型常量 ) const a, b, c = 3, 4, \u0026#34;foo\u0026#34; // a = 3, b = 4, c = \u0026#34;foo\u0026#34;, 无类型整型和字符串常量 const u, v float32 = 0, 3 // u = 0.0, v = 3.0 括号内的常量声明列表的表达式除了第一个必须声明其他表达式可以不写。空的表达式列表的值和类型都和前面的非空表达式相同。缺省的表达式列表等价于重复之前的表达式。标识符的数量必须等于表达式的数量。iota常量生成器是一个可以快速生成序列值的机制。\nconst ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Partyday numberOfDays // 非导出常量 ) Iota # 在常量声明中，预定义的标识符 iota 表示连续的无类型整型常量。它的值为常量声明中每个常量定义的位置（从零开始）。它能够用来生成一个关联常量集合：\nconst ( // iota is reset to 0 c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2 ) const ( // iota is reset to 0 a = 1 \u0026lt;\u0026lt; iota // a == 1 b = 1 \u0026lt;\u0026lt; iota // b == 2 c = 3 // c == 3 (没有使用 iota 不过它的值依然递增) d = 1 \u0026lt;\u0026lt; iota // d == 8 ) const ( // iota is reset to 0 u = iota * 42 // u == 0 (无类型整型常量) v float64 = iota * 42 // v == 42.0 (float64 类型常量) w = iota * 42 // w == 84 (无类型整型常量) ) const x = iota // x == 0 (iota 被重置) const y = iota // y == 0 (iota 被重置) 根据定义，在同一个常量定义中多次使用 iota 会得到相同的值：\nconst ( bit0, mask0 = 1 \u0026lt;\u0026lt; iota, 1\u0026lt;\u0026lt;iota - 1 // bit0 == 1, mask0 == 0 (iota == 0) bit1, mask1 // bit1 == 2, mask1 == 1 (iota == 1) _, _ // (iota == 2, unused) bit3, mask3 // bit3 == 8, mask3 == 7 (iota == 3) ) 最后一个例子利用了最后一个非空表达式列表的隐式重复。\n类型声明 # 类型声明为类型绑定一个标识符。类型声明有2种方式：类型声明和别名声明。\nTypeDecl = \u0026#34;type\u0026#34; ( TypeSpec | \u0026#34;(\u0026#34; { TypeSpec \u0026#34;;\u0026#34; } \u0026#34;)\u0026#34; ) . TypeSpec = AliasDecl | TypeDef . Alias声明 # 别名声明给指定类型绑定一个标识符名称。\nAliasDecl = identifier \u0026#34;=\u0026#34; Type . 在标识符作用域内，它作为类型的别名。\ntype ( nodeList = []*Node // nodeList 和 []*Node 是相同类型 Polar = polar // Polar 和 polar 表示相同类型 ) Type 定义 # 类型定义会创建一个新类型并绑定一个标识符，新类型与给定类型具有相同的底层类型和操作。\nTypeDef = identifier Type . 这个类型叫做定义类型，它和其他所有类型都不相同，包括创建它的类型。\ntype ( Point struct{ x, y float64 } // Point 和 struct{ x, y float64 } 是不同类型 polar Point // polar 和 Point 表示不同类型 ) type TreeNode struct { left, right *TreeNode value *Comparable } type Block interface { BlockSize() int Encrypt(src, dst []byte) Decrypt(src, dst []byte) } 定义类型可以关联该类型的方法。它不会继承原来类型的任何方法。但是接口类型的方法集和类型的结构没有改变。\n// Mutex 是一个拥有 Lock 和 Unlock 两个方法的数据类型。 type Mutex struct { /* Mutex fields */ } func (m *Mutex) Lock() { /* Lock implementation */ } func (m *Mutex) Unlock() { /* Unlock implementation */ } // NewMutex 与 Mutex 结构相同不过方法集为空。 type NewMutex Mutex // PtrMutex 的底层类型 *Mutex 的方法集没有改变， // 但是 PtrMutex 的方法集为空。 type PtrMutex *Mutex // *PrintableMutex 包含嵌入字段 Mutex 的 Lock 和 Unlock 方法。 type PrintableMutex struct { Mutex } // MyBlock 是与 Block 有相同方法集的接口类型 type MyBlock Block 类型定义可以定义方法集不同的布尔值、数字和字符串类型：\ntype TimeZone int const ( EST TimeZone = -(5 + iota) CST MST PST ) func (tz TimeZone) String() string { return fmt.Sprintf(\u0026#34;GMT%+dh\u0026#34;, tz) } 变量声明 # 变量声明可以创建一个或多个变量，并绑定对应的标识符、指定类型和初始值。\nVarDecl = \u0026#34;var\u0026#34; ( VarSpec | \u0026#34;(\u0026#34; { VarSpec \u0026#34;;\u0026#34; } \u0026#34;)\u0026#34; ) . VarSpec = IdentifierList ( Type [ \u0026#34;=\u0026#34; ExpressionList ] | \u0026#34;=\u0026#34; ExpressionList ) . var i int var U, V, W float64 var k = 0 var x, y float32 = -1, -2 var ( i int u, v, s = 2.0, 3.0, \u0026#34;bar\u0026#34; ) var re, im = complexSqrt(-1) var _, found = entries[name] // map lookup; only interested in \u0026#34;found\u0026#34; 如果给定一个表达式列表。变量会根据赋值规则使用表达式进行初始化。否则，每个变量都会初始化成变量类型的零值。\n如果指定类型，变量会为指定类型。如果没有指定类型，变量会使用分配的初始值类型。如果初始值为无类型常量，它会转换成初始值的默认类型。如果是一个无类型布尔值，那么变量的类型就是 bool。值 nil 不能给没有指定类型的变量赋值。\nvar d = math.Sin(0.5) // d is float64 var i = 42 // i is int var t, ok = x.(T) // t is T, ok is bool var n = nil // illegal 实现的限制：在函数体内声明的变量如果没有使用过编译器需要报错。\n短变量声明 # 短变量声明的语法:\nShortVarDecl = IdentifierList \u0026#34;:=\u0026#34; ExpressionList . 它比正常使用初始化表达式进行变量声明的方式要短，而且不指定类型：\n\u0026#34;var\u0026#34; IdentifierList = ExpressionList . i, j := 0, 10 f := func() int { return 7 } ch := make(chan int) r, w := os.Pipe(fd) // os.Pipe() 返回两个值 _, y, _ := coord(p) // coord() 返回三个值，我们只关注 y 和常规变量声明不同，即使之前在相同代码块中声明过的变量，也可以在短变量重新声明相同类型的变量，并且保证至少会有一个新的非空变量。总之，只应该在多变量短声明的时候重新声明变量，重新声明并不会使用新的变量，而是给变量分配新值。\nfield1, offset := nextField(str, 0) field2, offset := nextField(str, offset) // 重新声明 offset a, a := 1, 2 // 非法：声明了 a 两次并且没有新的变量 短变量声明只能在函数中使用，例如在 if、for、switch语句的上下文中声明临时变量。\n函数声明 # 函数声明为函数绑定标识符。\nFunctionDecl = \u0026#34;func\u0026#34; FunctionName Signature [ FunctionBody ] . FunctionName = identifier . FunctionBody = Block . 如果函数指定了返回参数。函数体的语句必须以终止语句结束。\nfunc IndexRune(s string, r rune) int { for i, c := range s { if c == r { return i } } // 无效：缺少 return 语句 } 函数声明可以没有函数体。这样的声明提供一个函数声明，并由其他外部实现，例如汇编脚本。\nfunc min(x int, y int) int { if x \u0026lt; y { return x } return y } func flushICache(begin, end uintptr) // 由外部实现 方法声明 # 方法是一个带接收者的函数，方法声明为方法绑定标识符作为方法名并指定方法对应的接收者类型。\nMethodDecl = \u0026#34;func\u0026#34; Receiver MethodName Signature [ FunctionBody ] . Receiver = Parameters . 接收者通过在方法增加一个额外的参数来指定。这个参数必须是一个非可变参数。它的类型必须是 T 或者 T 的指针（可能包含括号）。T 被称作接收者的基础类型；它不能是指针或接口类型，并且只能在同一个包中定义方法。声明后，我们认为方法绑定了基础类型，并且可以通过 T 或 *T 选择器访问方法名。\n非空的接收者标识符在方法签名中必须是唯一的。如果接收者的值没有在该方法中使用，那么接收者标识符可以省略。函数和方法的参数也是一样。\n对于一个基础类型。绑定的非空的方法名必须是唯一的。如果基础类型是一个结构体，非空的方法名也不能与结构体字段重复。\n给定一个Point类型。声明：\nfunc (p *Point) Length() float64 { return math.Sqrt(p.x * p.x + p.y * p.y) } func (p *Point) Scale(factor float64) { p.x *= factor p.y *= factor } 为类型 *Point绑定了2个方法 Length 和 Scale。\n方法的类型就是以接收者作为第一个参数的函数类型，例如 Scale 方法：\nfunc(p *Point, factor float64) 但是以这种方式声明的函数并不是方法。\n表达式 # 表达式通过针对运算元使用运算符和函数来获取计算值。\n运算元 # 运算元代表表达式中的一个简单的。运算元可以是字面值，非空标识符。或括号表达式。\n空标识符只能出现在赋值声明的左侧。\nOperand = Literal | OperandName | MethodExpr | \u0026#34;(\u0026#34; Expression \u0026#34;)\u0026#34; . Literal = BasicLit | CompositeLit | FunctionLit . BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit . OperandName = identifier | QualifiedIdent. 修饰标识符 # 修饰标识符是以包名作为前缀修饰的标识符。包名和标识符都不能为空。\nQualifiedIdent = PackageName \u0026#34;.\u0026#34; identifier . 修饰标识符可以用来访问不同包（需要先导入）中的标识符。标识符必须是导出的并在包级代码块声明才能够被访问。\nmath.Sin\t// 表示 math 包中的 Sin 函数 复合字面值 # 复合字面值能为结构体、数组、切片和 map 初始化值。它每次只能创建一个值。字面值由一个字面值类型和使用括号括起来的元素列表组成。元素前也可以声明元素对应的键。\nCompositeLit = LiteralType LiteralValue . LiteralType = StructType | ArrayType | \u0026#34;[\u0026#34; \u0026#34;...\u0026#34; \u0026#34;]\u0026#34; ElementType | SliceType | MapType | TypeName . LiteralValue = \u0026#34;{\u0026#34; [ ElementList [ \u0026#34;,\u0026#34; ] ] \u0026#34;}\u0026#34; . ElementList = KeyedElement { \u0026#34;,\u0026#34; KeyedElement } . KeyedElement = [ Key \u0026#34;:\u0026#34; ] Element . Key = FieldName | Expression | LiteralValue . FieldName = identifier . Element = Expression | LiteralValue . 字面值类型的底层类型必须是一个结构体，数组，切片或 map 类型（如果没有指定类型名就会强制执行这个约束）。元素的类型和键都必须能够分配给相应的字段的元素和键类型；没有额外的类型转换。键可以表示结构体的字段名，切片和数组的索引，map 类型的键。对于 map 字面值，所有的元素都必须有键。如果相同字段名或常量值的键对应多个元素就会报错。如果 map 类型的键为非常量类型，请看求值顺序章节。\n结构体字面值遵循以下规则：\n在结构体中，键必须是它的字段名。\n不包含任何键的元素列表的顺序需要与结构体字段的声明顺序相同。\n如果一个元素指定了键，那么所有的元素都必须指定键。\n包含键的元素列表不需要指定结构体的每个字字段，缺省字段会使用字段类型的零值。\n字面值可以不指定元素；这样的字面值等于该类型的零值。\n指定非本包的非导出字段会报错。\n给定声明：\ntype Point3D struct { x, y, z float64 } type Line struct { p, q Point3D } 我们可以使用这种写法：\norigin := Point3D{} // Point3D 的零值 line := Line{origin, Point3D{y: -4, z: 12.3}} // line.q.x 的零值 数组和切片遵循以下规则：\n每个元素都关联一个数字索引标记元素再数组中的位置。\n给元素指定的键会作为它的索引。键必须是能够表示非负的 int 类型值的常量；如果是指定类型的常量，那么常量必须是整型。\n元素没有指定键时会使用之前的索引加一。如果第一个元素没有指定键，它的索引为零。\n对复合字面值取址会生成指向由字面量初始化的变量的指针。\nvar pointer *Point3D = \u0026amp;Point3D{y: 1000} 数组字面值需要在类型中指定数组的长度。如果提供的元素少于数组的长度，那么缺少元素的位置将会使用元素类型的零值替代。如果索引超过数组的长度会报错。… 表示数组的长度等于最大元素索引加一。\nbuffer := [10]string{} // len(buffer) == 10 intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6 days := [...]string{\u0026#34;Sat\u0026#34;, \u0026#34;Sun\u0026#34;} // len(days) == 2 切片字面值底层其实就是数组字面值。因此它的长度和容量都是元素的最大索引加一。切片字面值的格式为：\n[]T{x1, x2, … xn} 可以在数组上进行切片操作从而获得切片：\ntmp := [n]T{x1, x2, … xn} tmp[0 : n] 在一个数组、切片或 map 类型 T 中。元素或者 map 的键可能有自己的字面值类型，如果字面值类型和元素或者键类型相同，那么对应的类型标识符可以省略。与之类似，如果元素或键的类型为 *T，那么它们的 \u0026amp;T 也可以省略。\n[...]Point{{1.5, -3.5}, {0, 0}} // same as [...]Point{Point{1.5, -3.5}, Point{0, 0}} [][]int{{1, 2, 3}, {4, 5}} // same as [][]int{[]int{1, 2, 3}, []int{4, 5}} [][]Point{{{0, 1}, {1, 2}}} // same as [][]Point{[]Point{Point{0, 1}, Point{1, 2}}} map[string]Point{\u0026#34;orig\u0026#34;: {0, 0}} // same as map[string]Point{\u0026#34;orig\u0026#34;: Point{0, 0}} map[Point]string{{0, 0}: \u0026#34;orig\u0026#34;} // same as map[Point]string{Point{0, 0}: \u0026#34;orig\u0026#34;} type PPoint *Point [2]*Point{{1.5, -3.5}, {}} // same as [2]*Point{\u0026amp;Point{1.5, -3.5}, \u0026amp;Point{}} [2]PPoint{{1.5, -3.5}, {}} // same as [2]PPoint{PPoint(\u0026amp;Point{1.5, -3.5}), PPoint(\u0026amp;Point{})} 当复合字面值使用字面值类型的类型名格式出现在 if、for 或 switch 语句的关键字和括号之间并且没有使用圆括号包裹的时候，会引发语法歧义。在这种特殊的情况下字面值的括号会被认为是语句的代码块。为了避免歧义，复合字面值必须用括号括起来。\nif x == (T{a,b,c}[i]) { … } if (x == T{a,b,c}[i]) { … } 下面是合法的数组、切片和 map 的例子：\n// list of prime numbers primes := []int{2, 3, 5, 7, 9, 2147483647} // vowels[ch] is true if ch is a vowel vowels := [128]bool{\u0026#39;a\u0026#39;: true, \u0026#39;e\u0026#39;: true, \u0026#39;i\u0026#39;: true, \u0026#39;o\u0026#39;: true, \u0026#39;u\u0026#39;: true, \u0026#39;y\u0026#39;: true} // the array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1} filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1} // frequencies in Hz for equal-tempered scale (A4 = 440Hz) noteFrequency := map[string]float32{ \u0026#34;C0\u0026#34;: 16.35, \u0026#34;D0\u0026#34;: 18.35, \u0026#34;E0\u0026#34;: 20.60, \u0026#34;F0\u0026#34;: 21.83, \u0026#34;G0\u0026#34;: 24.50, \u0026#34;A0\u0026#34;: 27.50, \u0026#34;B0\u0026#34;: 30.87, } 函数字面值 # 函数字面值表示一个匿名函数。\nFunctionLit = \u0026#34;func\u0026#34; Function . func(a, b int, z float64) bool { return a*b \u0026lt; int(z) } 函数字面值能分配给变量或直接调用。\n函数字面值是一个闭包。它可以引用包裹函数中的变量，这些变量在包裹函数和函数字面值之间是共享的。并且它会一直存在直到生命周期结束。\n主要表达式 # 主要表达式是一元和二元表达式的运算元。\nPrimaryExpr = Operand | Conversion | PrimaryExpr Selector | PrimaryExpr Index | PrimaryExpr Slice | PrimaryExpr TypeAssertion | PrimaryExpr Arguments . Selector = \u0026#34;.\u0026#34; identifier . Index = \u0026#34;[\u0026#34; Expression \u0026#34;]\u0026#34; . Slice = \u0026#34;[\u0026#34; [ Expression ] \u0026#34;:\u0026#34; [ Expression ] \u0026#34;]\u0026#34; | \u0026#34;[\u0026#34; [ Expression ] \u0026#34;:\u0026#34; Expression \u0026#34;:\u0026#34; Expression \u0026#34;]\u0026#34; . TypeAssertion = \u0026#34;.\u0026#34; \u0026#34;(\u0026#34; Type \u0026#34;)\u0026#34; . Arguments = \u0026#34;(\u0026#34; [ ( ExpressionList | Type [ \u0026#34;,\u0026#34; ExpressionList ] ) [ \u0026#34;...\u0026#34; ] [ \u0026#34;,\u0026#34; ] ] \u0026#34;)\u0026#34; . x 2 (s + \u0026#34;.txt\u0026#34;) f(3.1415, true) Point{1, 2} m[\u0026#34;foo\u0026#34;] s[i : j + 1] obj.color f.p[i].x() 选择器 # 对于一个 x 不是包名的主要表达式，选择器表达式：\nx.f 表示 x 的字段或方法 f（有时为 *x）。标识符 f 叫做（字段/方法）选择器。它不能是空标识符。选择器表达式的类型就是 f 的类型。如果 x 是包名。请参考修饰标识符。\n选择器 f 可以表示类型 T 的方法或字段 f。也可以表示类型 T 的嵌入方法或字段 f。访问 f 所需穿过的嵌套层数叫做它在类型 T 中的深度。声明在 T 中的字段或方法的深度为 0。声明在 T 的嵌入字段 A 中的方法或字段的深度等于 f 在 A 中的深度加一。\n选择器遵循以下原则：\n对于非指针/接口类型 T/*T 的值 x，x.f 表示第一层的方法/字段。如果在第一层没有对应的 f，选择器表达式就是非法的。\n对于接口类型 I 的值 x，x.f表示动态值 x 的方法名 f。如果接口 I 的方法集中没有 f 方法，选择器就是非法的。\n作为例外，如果 x 是一个指针类型并且 (*x).f 是合法的选择器表达式（只能表示字段，不能表示方法）。那么(*x).f 可以简写成 x.f。\n在其他情况下，x.f 都是非法的。\n如果x是指针类型，并且值为 nil，其中 f 为结构体字段。赋值或取值 x.f 会引起运行时恐慌。\n如果x是接口类型，并且值为 nil。调用 x.f 会引起运行时恐慌。\n例如给定声明：\ntype T0 struct { x int } func (*T0) M0() type T1 struct { y int } func (T1) M1() type T2 struct { z int T1 *T0 } func (*T2) M2() type Q *T2 var t T2 // with t.T0 != nil var p *T2 // with p != nil and (*p).T0 != nil var q Q = p 结果：\nt.z // t.z t.y // t.T1.y t.x // (*t.T0).x p.z // (*p).z p.y // (*p).T1.y p.x // (*(*p).T0).x q.x // (*(*q).T0).x (*q).x is a valid field selector p.M0() // ((*p).T0).M0() M0 expects *T0 receiver p.M1() // ((*p).T1).M1() M1 expects T1 receiver p.M2() // p.M2() M2 expects *T2 receiver t.M2() // (\u0026amp;t).M2() M2 expects *T2 receiver, see section on Calls 但是下面这种方式是不合法的：\nq.M0() // (*q).M0 is valid but not a field selector 方法表达式 # 如果 M 在类型 T 的方法集中。那么 T.M 就是能够正常调用的函数。使用与 M 相同的参数只是在参数列表的最前面增加了接收者参数。\nMethodExpr = ReceiverType \u0026#34;.\u0026#34; MethodName . ReceiverType = TypeName | \u0026#34;(\u0026#34; \u0026#34;*\u0026#34; TypeName \u0026#34;)\u0026#34; | \u0026#34;(\u0026#34; ReceiverType \u0026#34;)\u0026#34; . 假设结构体 T 有两个方法。接收者类型为 T 的 Mv 方法和接收者类型为 *T 的 Mp 方法：\ntype T struct { a int } func (tv T) Mv(a int) int { return 0 } // value receiver func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver var t T 表达式\nT.Mv 将会生成一个等价于 Mv 方法只是第一个参数显式声明接受者的函数。它的签名为：\nfunc(tv T, a int) int 这个函数能够通过接收者正常调用，以下5种方式是等价的：\nt.Mv(7) T.Mv(t, 7) (T).Mv(t, 7) f1 := T.Mv; f1(t, 7) f2 := (T).Mv; f2(t, 7) 与之类似：\n(*T).Mp 生成表示 Mp 的函数签名：\nfunc(tp *T, f float32) float32 对于一个把值作为接收者的方法，我们可以显式的从指针接收者获得函数：\n(*T).Mv 生成表示 Mv 的函数签名：\nfunc(tv *T, a int) int 这样的函数会通过接收者间接的创建一个值作为接收者传入底层方法中。方法内不能修改接收者的值，因为它的地址是在函数的调用栈里面。\n最后一个例子。把值作为接收者函数当做指针作为接收者的方法是非法的，因为指针接收者的方法集中不包含值类型的方法集。\n通过函数调用语法从方法中获取函数的值。接收者作为调用函数的第一个参数。给定 f :=T.Mv，f 作为f(t,7) 进行调用而不是 t.f(7)。想创建一个绑定接收者的函数可以使用函数字面值或者方法值。\n在接口类型中定义函数获取函数值是合法的。最终的函数调用会使用接口类型作为接收者。\n方法值 # 如果表达式 x 拥有静态类型 T 并且 M 在类型 T 的方法集中。x.M 叫做方法值。方法值 x.M 是一个函数值，这个函数和 x.M 拥有相同的参数列表。表达式 x 在计算方法值时会被保存和计算，这个拷贝的副本会作为任何接下来调用的接收者。\n类型 T 可能是接口类型也可能不是接口类型。\n与方法表达式中讲过的一样，假设类型 T 有两个方法：接收者类型为 T 的 Mv 和接受者类型为 *T 的 Mp ：\ntype T struct { a int } func (tv T) Mv(a int) int { return 0 } // value receiver func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver var t T var pt *T func makeT() T 表达式：\nt.Mv 生成一个类型的函数值：\nfunc(int) int 以下两种调用是等价的：\nt.Mv(7) f := t.Mv; f(7) 相似的，表达式：\npt.Mp 生成一个类型的函数值：\nfunc(float32) float32 与选择器相同，使用指针调用以值作为接收者的非接口方法会自动将指针解引用：pt.Mv 等价于 (*pt).Mv。\n与方法调用相同，使用值调用以指针作为接收者的非接口方法会自动对值取址：pt.Mv 等价于 (\u0026amp;pt).Mv。\nf := t.Mv; f(7) // like t.Mv(7) f := pt.Mp; f(7) // like pt.Mp(7) f := pt.Mv; f(7) // like (*pt).Mv(7) f := t.Mp; f(7) // like (\u0026amp;t).Mp(7) f := makeT().Mp // invalid: result of makeT() is not addressable 尽管上面使用的都是非接口类型的例子，不过对于接口类型同样适用。\nvar i interface { M(int) } = myVal f := i.M; f(7) // like i.M(7) index表达式 # 主要表达式格式：\na[x] 可以表示数组元素、数组的指针、切片、字符串或 map 类型 a 索引 x 对应的值。x 称作索引或者 map 的键。遵循以下规则：\n如果a不是 map 类型：\n索引 x 必须是整型或无类型常量。\n常量索引必须是非负数且可以使用 int 类型表示。\n无类型的常量索引会作为 int 型的值。\n索引 x 的范围在 0\u0026lt;=x\u0026lt;len(a) 内，否则就是越界。\n对于数组类型 A：\n常量索引必须在合法范围内。\n如果 x 在运行时越界会引起运行时恐慌。\na[x] 表示数组在索引 x 处的元素。a[x] 的类型就是 A 的元素类型。\n对于数组的指针类型：\n可以使用 a[x] 表示 (*a)[x]。 对于切片类型 S：\n如果 x 在运行时越界会引起运行时恐慌。 a[x] 表示切片在索引 x 处的元素。a[x] 的类型就是 S 的元素类型。 对于字符串类型：\n如果字符串 a 为常量，那么常量索引必须在合法范围内。\n如果 x 在运行时越界会引起运行时恐慌。\na[x] 表示索引 x 处的非常量字节，它是byte类型。\n不能对 a[x] 分配值。\n对于 map 类型 M：\n必须保证 x 的类型能够给 M 的键分配值。\n如果map包含键为 x 的值，a[x] 就是 map 中键 x 对应的值，它的类型就是 M 的元素类型。\n如果 map 值为 nil 或不包含这个实体，那么 a[x] 为 M 元素类型的零值。\n否则 a[x] 就是非法的。\n基于 map[K]V 类型 a 的索引表达式可以使用特殊格式的赋值和初始化语法。\nv, ok = a[x] v, ok := a[x] var v, ok = a[x] 它会额外生成一个无类型的布尔值。如果 ok 是 true，那么代表在map中有该键，如果没有 ok 为 false。\n给一个值为 nil 的 map 类型变量赋值会导致运行时恐慌。\n切片表达式 # 切片表达式可以基于字符串、数组、数组指针、切片创建字符串子串或切片。它有两种变体，一种是简单的格式是指定开始和结束位置，完全格式的语法还可以指定容量。\n####### 简单切片表达式\n对于数组、字符串、指针数组、切片 a，主要表达式：\na[low:high] 可以构造字符串子串或切片。索引 low 和 high 决定结果切片中的元素。结果切片的索引从 0 开始，长度为 high - low。从数组切分出的切片 s 拥有类型 []int，长度为 3 ，容积为 4。\na := [5]int{1, 2, 3, 4, 5} s := a[1:4] s[0] == 2 s[1] == 3 s[2] == 4 为了方便起见，索引值都可以缺省。当 low 缺省时默认从 0 开始。当缺 high 缺省时默认的取切片的长度。\na[2:] // same as a[2 : len(a)] a[:3] // same as a[0 : 3] a[:] // same as a[0 : len(a)] 如果 a 是一个数组指针，那么 a[low:high] 可以表示 (*a)[low : high]。\n对于数组或者字符串，索引的范围是0\u0026lt;=low\u0026lt;=high\u0026lt;=len(a)。对于切片，最大的索引值可以为切片的容量，而不是切片的长度。常量索引必须为非负数，且能够转换成 int 类型。对于数组或者常量字符串。常量索引值必须在合法范围内。如果2个索引都是常量。low 必须小于 high。如果索引在运行时访问了非法内存，程序会发生运行时恐慌。\n除了无类型字符串，对于切片和字符串的操作结果是非常量类型的值，它的类型与运算元相同。如果运算元为无类型字符串，那么结果类型会为 string。如果把数组作为运算元，它必须是可寻址的，并且获得的切片和原数组具有同一元素类型。\n如果切片运算元为 nil，那么结果也是 nil。否则结果切片会和运算元共享相同的底层无类型数组。\n完全切片表达式 # 对于数组，数组指针或非字符串切片，主要表达式为：\na[low : high : max] 它会构造一个同类型切片，并具有与简单切片表达式的 a[low:high] 相同的长度和元素。另外，它还可以把切片的容量设置为 max - low。这时只有第一个索引可以为缺省值，默认为零。从数组中获得切片以后：\na := [5]int{1, 2, 3, 4, 5} t := a[1:3:5] 切片 t 为 []int 类型，长度为 2，容量为 4，并且元素为：\nt[0] == 2 t[1] == 3 和简单切片表达式一样，如果 a 是数组指针 ，那么 a[low:high:max] 可以简写为 (*a)[low:high:max]。如果切分操作元是数组，那么这个数组必须是可以寻址的。\n如果索引必须在 0 \u0026lt;= low \u0026lt;= high \u0026lt;= max \u0026lt;= cap(a) 范围内。常量索引不能是负数并且能够使用 int 类型表示；对于数组，索引必须在合法范围内。如果有多个索引都是常量的，那么所有索引都需要在合法范围内。如果索引是非法的，会引起运行时恐慌。\n类型断言 # 对于接口类型 x 和类型 T，主要表达式：\nx.(T) 可以断言 x 不是 nil 且 x 的值是 T 类型。标记 x.(T) 叫做类型断言。\n更确切的说，如果 T 不是接口类型，那么 x.(T) 将会断言动态类型 x 的类型是不是 T。\n这时，T 必须实现了 x 的（接口）类型。否则断言会是非法的因为 x 不能保存 T 类型的值。如果 T 是接口类型，那么可以断言动态类型 x 是否实现了 T 接口。\n如果类型断言成功，表达式的值为 x 的值，但它的类型是T。如果断言失败，将会导致运行时恐慌。换句话说，即使 x 是运行时确定的，x.(T) 也必须是编程时就确认存在的。\nvar x interface{} = 7 // x 拥有动态类型 int 值为 7 i := x.(int) // i 为 int 类型值为 7 type I interface { m() } func f(y I) { s := y.(string) // 非法: 字符串没有实现接口 I （缺少 m 方法） r := y.(io.Reader) // r 拥有接口 io.Reader 所以 y 的动态类型必须同时实现 I 和 io.Reader … } 类型断言可以使用特定格式的赋值和初始化语句。\nv, ok = x.(T) v, ok := x.(T) var v, ok = x.(T) var v, ok T1 = x.(T) 这时将会额外生成一个无类型的布尔值。如果断言成功，ok返回 true，否则是 false。并且 v 会是 T 类型的零值。这时不会有恐慌发生。\n调用 # 给定函数类型为 F 的表达式 f：\nf(a1, a2, … an) 可以使用 a1,a2\u0026hellip;an 来调用函数 f。除一种特殊情况之外，函数参数必须是对应 F 函数参数类型的单值表达式，且在函数调用前就已经完成求值。表达式的结果类型是 f 的结果类型。函数调用和方法调用相似，只是方法额外需要一个接收者类型。\nmath.Atan2(x, y) // function call var pt *Point pt.Scale(3.5) // method call with receiver pt 在函数调用中，函数的值和参数是按照顺序求值的。在计算之后作为参数会传进函数，函数开始执行。当函数执行完成后返回的参数将会返回给函数的调用者。\n调用值为 nil 的函数会导致运行时恐慌。\n作为特例，如果函数或者方法的返回值等于参数列表的个数，那么会嵌套调用。这将把返回值直接赋值给下一次调用函数的参数。\nfunc Split(s string, pos int) (string, string) { return s[0:pos], s[pos:] } func Join(s, t string) string { return s + t } if Join(Split(value, len(value)/2)) != value { log.Panic(\u0026#34;test fails\u0026#34;) } 如果 x 的方法集中包含 m 那么 x.m() 是合法的。并且参数列表和 m 的参数列表相同。如果x是可寻址的，那么那么x指针的方法集(\u0026amp;x).m()可以简写成x.m()。\nvar p Point p.Scale(3.5) 没有方法类型，也没有方法字面值。\n通过 ... 来传递参数 # 如果 f 的最后一个参数 p 的类型是 ...T。那么在函数内部 p 参数的类型就是 []T。如果 f 调用时没有传入 p 对应的参数，那么p为 nil。否则这些参数会以切片方式传入，在新的底层切片中。切片中的类型都是能赋值给类型 T 的值。这个切片的长度和容量在不同的调用中有所不同。\n给定函数调用：\nfunc Greeting(prefix string, who ...string) Greeting(\u0026#34;nobody\u0026#34;) Greeting(\u0026#34;hello:\u0026#34;, \u0026#34;Joe\u0026#34;, \u0026#34;Anna\u0026#34;, \u0026#34;Eileen\u0026#34;) 在 Greeting 中，第一次调用时，who是 nil 类型。而在第二次调用时是[]string{\u0026quot;Joe\u0026quot;, \u0026quot;Anna\u0026quot;, \u0026quot;Eileen\u0026quot;}。\n如果在调用的时候的最后一个参数是[]T，那么我们可以使用...来将切片中的值依次赋值给参数列表。\n给定切片s并且调用:\ns := []string{\u0026#34;James\u0026#34;, \u0026#34;Jasmine\u0026#34;} Greeting(\u0026#34;goodbye:\u0026#34;, s...) z 在 Greeting。中 who 会和切片 s 共享相同的底层数组。\n操作符 # 操作符用来连接运算元。\nExpression = UnaryExpr | Expression binary_op Expression . UnaryExpr = PrimaryExpr | unary_op UnaryExpr . binary_op = \u0026#34;||\u0026#34; | \u0026#34;\u0026amp;\u0026amp;\u0026#34; | rel_op | add_op | mul_op . rel_op = \u0026#34;==\u0026#34; | \u0026#34;!=\u0026#34; | \u0026#34;\u0026lt;\u0026#34; | \u0026#34;\u0026lt;=\u0026#34; | \u0026#34;\u0026gt;\u0026#34; | \u0026#34;\u0026gt;=\u0026#34; . add_op = \u0026#34;+\u0026#34; | \u0026#34;-\u0026#34; | \u0026#34;|\u0026#34; | \u0026#34;^\u0026#34; . mul_op = \u0026#34;*\u0026#34; | \u0026#34;/\u0026#34; | \u0026#34;%\u0026#34; | \u0026#34;\u0026lt;\u0026lt;\u0026#34; | \u0026#34;\u0026gt;\u0026gt;\u0026#34; | \u0026#34;\u0026amp;\u0026#34; | \u0026#34;\u0026amp;^\u0026#34; . unary_op = \u0026#34;+\u0026#34; | \u0026#34;-\u0026#34; | \u0026#34;!\u0026#34; | \u0026#34;^\u0026#34; | \u0026#34;*\u0026#34; | \u0026#34;\u0026amp;\u0026#34; | \u0026#34;\u0026lt;-\u0026#34; . 比较运算符在此处讨论。对于其他二元操作符，两个操作元的类型必须是相同的，除了位移和无类型常量。针对常量的操作，请看常量表达式章节。\n除了位移操作，如果其中一个操作符是无类型常量，而另个不是，那么无类型的常量会转换成另一个运算元的类型。\n在右移表达式中的运算元必须是无符号的整数或者可以转换成 uint 的无类型的常量。如果左移一个无类型常量那么结果依然是无类型的。他首先会转换成指定类型。\nvar s uint = 33 var i = 1\u0026lt;\u0026lt;s // 1 has type int var j int32 = 1\u0026lt;\u0026lt;s // 1 has type int32; j == 0 var k = uint64(1\u0026lt;\u0026lt;s) // 1 has type uint64; k == 1\u0026lt;\u0026lt;33 var m int = 1.0\u0026lt;\u0026lt;s // 1.0 has type int; m == 0 if ints are 32bits in size var n = 1.0\u0026lt;\u0026lt;s == j // 1.0 has type int32; n == true var o = 1\u0026lt;\u0026lt;s == 2\u0026lt;\u0026lt;s // 1 and 2 have type int; o == true if ints are 32bits in size var p = 1\u0026lt;\u0026lt;s == 1\u0026lt;\u0026lt;33 // illegal if ints are 32bits in size: 1 has type int, but 1\u0026lt;\u0026lt;33 overflows int var u = 1.0\u0026lt;\u0026lt;s // illegal: 1.0 has type float64, cannot shift var u1 = 1.0\u0026lt;\u0026lt;s != 0 // illegal: 1.0 has type float64, cannot shift var u2 = 1\u0026lt;\u0026lt;s != 1.0 // illegal: 1 has type float64, cannot shift var v float32 = 1\u0026lt;\u0026lt;s // illegal: 1 has type float32, cannot shift var w int64 = 1.0\u0026lt;\u0026lt;33 // 1.0\u0026lt;\u0026lt;33 is a constant shift expression 运算符优先级 # 一元运算符拥有最高优先级。++ 和 \u0026ndash; 是语句而不是表达式，他们在运算符的优先级之外。所以 (*p)++ 和 *p++ 是一样的。\n二元运算符有 5 个优先级。乘法运算符在最高级，紧接着是加法运算符。比较运算符，\u0026amp;\u0026amp; 运算符，最后是 ||。\nPrecedence Operator 5 * / % \u0026lt;\u0026lt; \u0026gt;\u0026gt; \u0026amp; \u0026amp;^ 4 + - | ^ 3 == != \u0026lt; \u0026lt;= \u0026gt; \u0026gt;= 2 \u0026amp;\u0026amp; 1 || 相同优先级的二元运算符的执行顺序是由左到右。例如 x/y*z和(x/y)*z 是一样的。\n+x 23 + 3*x[i] x \u0026lt;= f() ^a \u0026gt;\u0026gt; b f() || g() x == y+1 \u0026amp;\u0026amp; \u0026lt;-chanPtr \u0026gt; 0 算数运算符 # 算数运算符应用在 2 个数字值之间，别切生成一个相同类型的值作为第一个运算元。四种算数运算符(+,-,*,/)应用在数字，浮点，复合类型之中。+ 也可以用于字符串。位运算和位移运算只适用于整数。\n+ sum integers, floats, complex values, strings - difference integers, floats, complex values * product integers, floats, complex values / quotient integers, floats, complex values % remainder integers \u0026amp; bitwise AND integers | bitwise OR integers ^ bitwise XOR integers \u0026amp;^ bit clear (AND NOT) integers \u0026lt;\u0026lt; left shift integer \u0026lt;\u0026lt; unsigned integer \u0026gt;\u0026gt; right shift integer \u0026gt;\u0026gt; unsigned integer 数字运算符 # 对于两个整数 x 和 y。整数商 q=x/y 和余数 r=x%y 遵循以下规律。\nx = q*y + r and |r| \u0026lt; |y| x/y 截断为 0。\nx y x / y x % y 5 3 1 2 -5 3 -1 -2 5 -3 -1 2 -5 -3 1 -2 作为这个规则的例外情况，如果 x 非常大，那么 q=x/-1 等于 x。\nx, q int8 -128 int16 -32768 int32 -2147483648 int64 -9223372036854775808 如果除数是一个常量。那么它不能是 0，如果除数在运行时为 0，会导致运行时恐慌。如果除数是负数并且除数是：\nx x / 4 x % 4 x \u0026gt;\u0026gt; 2 x \u0026amp; 3 11 2 3 2 3 -11 -2 -3 -3 1 位移运算符移动左侧运算元右侧元算元指定的位数。如果左侧是有符号整型，那它就实现了位移运算，如果是无符号整数使用逻辑位移。位移运算没有上限，位移操作让左边运算元位移 n 个 1。x\u0026lt;\u0026lt;1 和 x*2 是相等的。并且 x\u0026gt;\u0026gt;1 和 x/2 是相同的。\n对于整数运算元，一元运算符+-^定义如下：\n+x is 0 + x -x negation is 0 - x ^x bitwise complement is m ^ x with m = \u0026#34;all bits set to 1\u0026#34; for unsigned x and m = -1 for signed x 整型溢出 # 对于无符号的值，运算符+-*和\u0026laquo;都是2禁止运算。这里的n是无符号类型的宽度，无符号整型将会丢弃溢出的位，并且程序将会返回wrap around。\n对于有符号的整数，操作符+=*\u0026laquo;都会溢出并且值存在，并且代表相应的有符号的值。在运算时不会抛出异常。标一起不会报错。所以不是所有情况下x\u0026lt;x+1都成立。\n浮点数运算符 # 对于浮点数和其他复杂数字，+x和x是一样的，-x是x的对立面。除了IEEE-754还没有指定浮点数除0或者复数的结果。是否抛出异常将会依赖其具体实现。\n一种实现可以合并多个浮点操作进一个操作，有可能是夸语句的，并且他的结果可能和依次单独执行的结果不一样。1个浮点数类型将会转变成目标的精度，防止四舍五入的融合。\n// FMA allowed for computing r, because x*y is not explicitly rounded: r = x*y + z r = z; r += x*y t = x*y; r = t + z *p = x*y; r = *p + z r = x*y + float64(z) // FMA disallowed for computing r, because it would omit rounding of x*y: r = float64(x*y) + z r = z; r += float64(x*y) t = float64(x*y); r = t + z 字符串 # 字符串可以使用+和+=操作符。\ns := \u0026#34;hi\u0026#34; + string(c) s += \u0026#34; and good bye\u0026#34; 字符串想家将会创建一个新的字符串。\n比较运算符 # 比较运算符比较连个运算元，并且生成一个无类型的布尔值。\n== equal != not equal \u0026lt; less \u0026lt;= less or equal \u0026gt; greater \u0026gt;= greater or equal 在任何比较运算元中2种类型必须是可以分配的。\n使用等于运算符==和!=的运算元必须是可比较的。使用顺序运算符\u0026lt;,\u0026lt;=,\u0026gt;和\u0026gt;=必须是可比较的。这些限制导致比较运算符被定义成以下的方式。\n布尔值是可比较的，两个布尔值当他们同为true或者false的使用是相等的\n整数值是可比较和排序的\n浮点数是可比较和排序的，具体定义在IEEE-754标准中。\n复数是可比较的，2个复数当实部和虚部都相等时就是相等的。\n字符串是可以比较和排序的。是按照字节顺序排序。\n指针式可以排序的，连个指针当指向相同变量时是相同的，或者他们2个都是nil。指向一个为非配的变量的结果是未定义的。\nchannel是可比较的。当两个管道是用同一个make出来的，或者都是nil时时相等的。\n接口值时可以比较的，2个接口值时相等的如果2个标识符的动态类型是一样的或者他们都是nil。\n一个非接口类型的值x和一个接口类型的值T在非接口类型是可以比较的并且非接口类型实现了接口是是可以比较的。当他们的动态类型类型相同时时相等的。\n当结构体内的所有字段都是可以比较的时候，他是可以比较的。连个结构体的值当非空字段都相等时他们是相等的。\n数组类型的值时可比较的，如果数组的原属时可以比较的，那么当数组的所有值是相等的时候他们就是相等的。\n使用两个动态类型的标识符来比较接口的值。如果这个类型的值时不可比较的，那么将会引起一个panic。这个行为不仅仅时接口，数组结构体接口字段都有这个问题。\n切片，map，和函数值都是不可比较的，然而，作为一个特殊的例子，切片，map和函数的值的nil时可以比较的，指针，channel和接口的值nil也是可以比较的。\nconst c = 3 \u0026lt; 4 // c is the untyped boolean constant true type MyBool bool var x, y int var ( // The result of a comparison is an untyped boolean. // The usual assignment rules apply. b3 = x == y // b3 has type bool b4 bool = x == y // b4 has type bool b5 MyBool = x == y // b5 has type MyBool ) 逻辑操作符 # 逻辑运算符使用布尔值值，并且生成一个相同类型的结果值作为操作元。右面的操作元计算是有条件的。\n\u0026amp;\u0026amp; conditional AND p \u0026amp;\u0026amp; q is \u0026#34;if p then q else false\u0026#34; || conditional OR p || q is \u0026#34;if p then true else q\u0026#34; ! NOT !p is \u0026#34;not p\u0026#34; 地址操作符 # 以类型 T 的 x 作为运算元，取址操作 \u0026amp;x 会生成一个类型为 *T 并指向 x 的指针。运算元必须是能够取址的，它可以是一个变量，指针，切片的取值操作；或是一个可取址结构体的字段选择器；或是对于可取址数组的索引取值操作。作为寻址能力的例外，x 可能是一个复合字面值。如果对 x 进行取址操作将会 panic，\u0026amp;x 也会 panic。\n对于一个 *T 类型的运算元 x，指针解引用 *x 表示 x 指向的 T 类型。如果 x 为 nil，那么解引用 *x 会 panic。\n\u0026amp;x \u0026amp;a[f(2)] \u0026amp;Point{2, 3} *p *pf(x) var x *int = nil *x // causes a run-time panic \u0026amp;*x // causes a run-time panic 接收操作符 # 对于管道类型的运算元 ch，接收操作 \u0026lt;-ch 返回值是管道 ch 接收到的值。带方向的管道需要有接受权限，接收操作的类型也是通道的元素类型。表达式会一直阻塞直到接收到返回值。从 nil 通道接收值会一直阻塞。从一个已经关闭的通道接收数据会在其他数据都被接收以后生成该通道元素类型的零值。\nv1 := \u0026lt;-ch v2 = \u0026lt;-ch f(\u0026lt;-ch) \u0026lt;-strobe // wait until clock pulse and discard received value 接收数据的表达式可以使用赋值表达式。\nx, ok = \u0026lt;-ch x, ok := \u0026lt;-ch var x, ok = \u0026lt;-ch var x, ok T = \u0026lt;-ch 它还可以生成一个额外的无类型布尔值来表示通道是否关闭。如果 ok 为 true 说明获取到的是发送到通道内的数据，而 false 它就返回一个零值因为通道内没有元素且已经关闭。\n类型转换 # 类型转换表达式 T(x) 其中 T 代表类型，x 代表可以转换成 T 类型的表达式。\nConversion = Type \u0026#34;(\u0026#34; Expression [ \u0026#34;,\u0026#34; ] \u0026#34;)\u0026#34; . 如果类型是以 * 或 \u0026lt;- 开头，或以关键字 func 开头并且没有返回值列表，那么它必须用括号括起来避免歧义：\n*Point(p) // same as *(Point(p)) (*Point)(p) // p is converted to *Point \u0026lt;-chan int(c) // same as \u0026lt;-(chan int(c)) (\u0026lt;-chan int)(c) // c is converted to \u0026lt;-chan int func()(x) // function signature func() x (func())(x) // x is converted to func() (func() int)(x) // x is converted to func() int func() int(x) // x is converted to func() int (unambiguous) 常量 x 可以在可以用类型 T 表示时自动转换。作为一个特例，整数常量 x 可以转换成字符串类型就和非常量 x 一样。\n对常量的转换会生成一个指定类型的常量。\nuint(iota) // iota value of type uint float32(2.718281828) // 2.718281828 of type float32 complex128(1) // 1.0 + 0.0i of type complex128 float32(0.49999999) // 0.5 of type float32 float64(-1e-1000) // 0.0 of type float64 string(\u0026#39;x\u0026#39;) // \u0026#34;x\u0026#34; of type string string(0x266c) // \u0026#34;♬\u0026#34; of type string MyString(\u0026#34;foo\u0026#34; + \u0026#34;bar\u0026#34;) // \u0026#34;foobar\u0026#34; of type MyString string([]byte{\u0026#39;a\u0026#39;}) // not a constant: []byte{\u0026#39;a\u0026#39;} is not a constant (*int)(nil) // not a constant: nil is not a constant, *int is not a boolean, numeric, or string type int(1.2) // illegal: 1.2 cannot be represented as an int string(65.0) // illegal: 65.0 is not an integer constant 非常量 x 可以在以下情况下转换成类型 T：\nx 可以给类型 T 赋值\n忽略的结构体标签，x 的类型和 T 具有相同的底层类型\n忽略的结构体标签，x 的类型和 T 都是指针类型，并且指针所指的类型具有相同的底层类型\nx 的类型和 T 都是整数或者浮点数类型\nx 的类型和 T 都是复数类型\nx 是一个字符串而 T 时字节切片或者 rune 切片\n在比较两个结构体类型的时候会忽略结构体标签：\ntype Person struct { Name string Address *struct { Street string City string } } var data *struct { Name string `json:\u0026#34;name\u0026#34;` Address *struct { Street string `json:\u0026#34;street\u0026#34;` City string `json:\u0026#34;city\u0026#34;` } `json:\u0026#34;address\u0026#34;` } var person = (*Person)(data) // ignoring tags, the underlying types are identical 这个规则也适用于数字类型与字符串类型间的相互转换。这个转换可能会改变 x 的值并且会增加运行时消耗。包 unsafe 实现了这个功能底层的限制。\n数字之间的转换 # 对于非常量的数字转换，需要遵守以下规则：\n在转换整型数字时，如果是一个有符号整型，它是继承有符号的无限精度；否则就不用继承符号。转换时会截断数字以适应类型的大小。例如：如果 v:=uint16(0x10F0)，然后 ``uint32(int8(v)) == 0xFFFFFFF0 。类型转换总是生成有效值，并且永远不会溢出。\n如果要将浮点数转换成整型，会丢弃小数部分（截断为零）。\n如果要将整型或浮点型转换成浮点数类型，或或者一个复数转换成其他复数类型，结果会四舍五入成指定精度。例如： 可以使用超出IEEE-754 32位数的附加精度来存储float32类型的变量x的值，但float32（x）表示将x的值舍入为32位精度的结果。x + 0.1 会使用超过 32 位的精度，而 float32(x+0.1) 不会。\n在所有浮点数和复数的非常量转换中，如果结构类型不能成功表示数据，那么结果将会依赖于具体平台实现。\n字符串的类型转换 # 转换一个有符号或者无符号的整型值会转换成对应的 UTF-8 表示整型值。不在范围内的 Unicode 代码点会转换成 \u0026ldquo;\\uFFFD\u0026rdquo;。 string(\u0026#39;a\u0026#39;) // \u0026#34;a\u0026#34; string(-1) // \u0026#34;\\ufffd\u0026#34; == \u0026#34;\\xef\\xbf\\xbd\u0026#34; string(0xf8) // \u0026#34;\\u00f8\u0026#34; == \u0026#34;ø\u0026#34; == \u0026#34;\\xc3\\xb8\u0026#34; type MyString string MyString(0x65e5) // \u0026#34;\\u65e5\u0026#34; == \u0026#34;日\u0026#34; == \u0026#34;\\xe6\\x97\\xa5\u0026#34; 将字节切片转换成字符串类型会生成一个由切片元素组成的字符串 string([]byte{\u0026#39;h\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;\\xc3\u0026#39;, \u0026#39;\\xb8\u0026#39;}) // \u0026#34;hellø\u0026#34; string([]byte{}) // \u0026#34;\u0026#34; string([]byte(nil)) // \u0026#34;\u0026#34; type MyBytes []byte string(MyBytes{\u0026#39;h\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;\\xc3\u0026#39;, \u0026#39;\\xb8\u0026#39;}) // \u0026#34;hellø\u0026#34; 将 rune 切片转换成字符串类型会生成一个由切片元素组成的字符串 string([]rune{0x767d, 0x9d6c, 0x7fd4}) // \u0026#34;\\u767d\\u9d6c\\u7fd4\u0026#34; == \u0026#34;白鵬翔\u0026#34; string([]rune{}) // \u0026#34;\u0026#34; string([]rune(nil)) // \u0026#34;\u0026#34; type MyRunes []rune string(MyRunes{0x767d, 0x9d6c, 0x7fd4}) // \u0026#34;\\u767d\\u9d6c\\u7fd4\u0026#34; == \u0026#34;白鵬翔\u0026#34; 将字符串转换成字节切片会生成由字符串中每个字节组成的切片 []byte(\u0026#34;hellø\u0026#34;) // []byte{\u0026#39;h\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;\\xc3\u0026#39;, \u0026#39;\\xb8\u0026#39;} []byte(\u0026#34;\u0026#34;) // []byte{} MyBytes(\u0026#34;hellø\u0026#34;) // []byte{\u0026#39;h\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;\\xc3\u0026#39;, \u0026#39;\\xb8\u0026#39;} 将字符串转换成 rune 切片会生成由字符串中每个 Unicode 代码点组成的切片 []rune(MyString(\u0026#34;白鵬翔\u0026#34;)) // []rune{0x767d, 0x9d6c, 0x7fd4} []rune(\u0026#34;\u0026#34;) // []rune{} MyRunes(\u0026#34;白鵬翔\u0026#34;) // []rune{0x767d, 0x9d6c, 0x7fd4} 常量表达式 # 常量表达式只包含常量运算元并且在编译程序时就已经计算完成。\n无类型布尔值，数值和字符串常量都可以当作运算元。除了位置操作符，如果二元运算符石不同类型的常量，操作元，和非布尔值，和即将在接下来出现的：整型，rune，浮点数和复数类型。例如：一个无类型整型常量减去无类型复数常量，结果为复数常量。\n一个常量的比较运算会生成无类型的布尔常量。如果左移运算是一个无类型常量，结果会是一个整型常量。它会和原来常量为相同类型。其他与无类型常量的运算都会生成相同类型的结果（布尔值，整型，浮点数，复数，字符串常量）。\nconst a = 2 + 3.0 // a == 5.0 (untyped floating-point constant) const b = 15 / 4 // b == 3 (untyped integer constant) const c = 15 / 4.0 // c == 3.75 (untyped floating-point constant) const Θ float64 = 3/2 // Θ == 1.0 (type float64, 3/2 is integer division) const Π float64 = 3/2. // Π == 1.5 (type float64, 3/2. is float division) const d = 1 \u0026lt;\u0026lt; 3.0 // d == 8 (untyped integer constant) const e = 1.0 \u0026lt;\u0026lt; 3 // e == 8 (untyped integer constant) const f = int32(1) \u0026lt;\u0026lt; 33 // illegal (constant 8589934592 overflows int32) const g = float64(2) \u0026gt;\u0026gt; 1 // illegal (float64(2) is a typed floating-point constant) const h = \u0026#34;foo\u0026#34; \u0026gt; \u0026#34;bar\u0026#34; // h == true (untyped boolean constant) const j = true // j == true (untyped boolean constant) const k = \u0026#39;w\u0026#39; + 1 // k == \u0026#39;x\u0026#39; (untyped rune constant) const l = \u0026#34;hi\u0026#34; // l == \u0026#34;hi\u0026#34; (untyped string constant) const m = string(k) // m == \u0026#34;x\u0026#34; (type string) const Σ = 1 - 0.707i // (untyped complex constant) const Δ = Σ + 2.0e-4 // (untyped complex constant) const Φ = iota*1i - 1/1i // (untyped complex constant) 对一个无类型整数，rune，或浮点数应用内置的 complex 函数会生成无类型的复数常量。\nconst ic = complex(0, c) // ic == 3.75i (untyped complex constant) const iΘ = complex(0, Θ) // iΘ == 1i (type complex128) 常量表达式总是一个明确的值；中间值和常量自己可以比语言所支持的精度更高，下面的声明是合法的：\nconst Huge = 1 \u0026lt;\u0026lt; 100 // Huge == 1267650600228229401496703205376 (untyped integer constant) const Four int8 = Huge \u0026gt;\u0026gt; 98 // Four == 4 (type int8) 常量的除法的除数不能为 0:\n3.14 / 0.0 // illegal: division by zero 定义了类型的常量的精度必须根据常量类型定义。所以下面的常量表达式是非法的：\nuint(-1) // -1 cannot be represented as a uint int(3.14) // 3.14 cannot be represented as an int int64(Huge) // 1267650600228229401496703205376 cannot be represented as an int64 Four * 300 // operand 300 cannot be represented as an int8 (type of Four) Four * 100 // product 400 cannot be represented as an int8 (type of Four) 补码使用的一元操作符 ^ 对于非常量的匹配模式：补码对于无符号常量为 1，对于有符号和无类型常量为 -1。\n^1 // untyped integer constant, equal to -2 uint8(^1) // illegal: same as uint8(-2), -2 cannot be represented as a uint8 ^uint8(1) // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE) int8(^1) // same as int8(-2) ^int8(1) // same as -1 ^ int8(1) = -2 实现限制：编译器在处理无类型浮点数和复数时会取近似值；具体请看常量章节。这个取近似值的操作在浮点数在整数上下文时会产生无效值，即使在计算过后是一个整型。\n运算优先级 # 在包级别，初始化的依赖性由变量声明的初始化表达式顺序决定。否则，当计算表达式内的操作数时，赋值，返回语句，所有函数调用，方法调用，和通信操作都会由左向右计算。\n例如，在函数作用域中的赋值：\ny[f()], ok = g(h(), i()+x[j()], \u0026lt;-c), k() 函数调用和通信的发生顺序为：f()，h()，i()，j()，\u0026lt;-c，g() 和 k()。但是对 y 和 x 的取值操作没有指定。\na := 1 f := func() int { a++; return a } x := []int{a, f()} // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified m := map[int]int{a: 1, a: 2} // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified n := map[int]int{a: f()} // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified 在包级别，依赖的初始化顺序会覆盖这个从左向右的规则：\nvar a, b, c = f() + v(), g(), sqr(u()) + v() func f() int { return c } func g() int { return a } func sqr(x int) int { return x*x } // functions u and v are independent of all other variables and functions 语句 # 语句控制程序的执行。\nStatement = Declaration | LabeledStmt | SimpleStmt | GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt | FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt | DeferStmt . SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl . 终止语句 # 终止语句会阻止相同代码块中下面所有语句的执行。以下语句属于终止语句：\nreturn 和 goto 语句\n对内置 panic 函数的调用\n代码块结束\nif 语句中：\nelse 分支\n所有分支末尾\nfor语句中：\nbreak 语句和循环结束\nswitch 语句：\n在 switch 语句中没有 break 语句，\n有一个默认的 case\n语句列表中的每个 case 语句和有可能存在的 fallthrough 语句\nselect 语句中：\n没有 break 语句\n每个 case 中的语句列表，如果包含默认 case\n所有其他语句都不是中断语句。\n如果语句序列不为空并且最后一个非空语句是终止语句，那么语句序列就以终结语句结尾。\n空语句 # 空语句不做任何事情。\nEmptyStmt = . 标签语句 # 标签语句可以作为 goto，break 和 continue 语句的目标。\nLabeledStmt = Label \u0026#34;:\u0026#34; Statement . Label = identifier . Error: log.Panic(\u0026#34;error encountered\u0026#34;) 表达式语句 # 除了特定的内置函数，一般的函数、方法和接收操作都可以出现在表达式语句的上下文中。这些语句可以使用括号括起来。\nExpressionStmt = Expression . 下面的内置函数不允许出现在语句的上下文中：\nappend cap complex imag len make new real unsafe.Alignof unsafe.Offsetof unsafe.Sizeof h(x+y) f.Close() \u0026lt;-ch (\u0026lt;-ch) len(\u0026#34;foo\u0026#34;) // illegal if len is the built-in function 发送语句 # 发送语句可以向通道发送一个值。通道表达式必须是通道类型，通道方向必须允许发送操作，并且值类型是可以分配给通道元素通道类型。\nSendStmt = Channel \u0026#34;\u0026lt;-\u0026#34; Expression . Channel = Expression . 通道类型和值表达式会在发送之前求值。发送操作会一致阻塞，直到可以进行发送操作。如果接收者已经准备好向没有缓存的通道发送值可以立即执行。如果通道内还有缓存空间，向通道内发送值也会立即执行。向关闭的通道发送数据会导致运行时恐慌。像值为 nil 的通道发送数据会一直阻塞。\nch \u0026lt;- 3 // send value 3 to channel ch 递增/递减语句 # “++” 和 “\u0026ndash;” 语句可以递增或者递减运算元一个无类型常量 1。作为一个赋值语句，运算元必须是可寻址的或者 map 的索引表达式。\nIncDecStmt = Expression ( \u0026#34;++\u0026#34; | \u0026#34;--\u0026#34; ) . 下面的赋值语句在语义上是等价的：\nIncDec statement Assignment x++ x += 1 x-- x -= 1 赋值 # Assignment = ExpressionList assign_op ExpressionList . assign_op = [ add_op | mul_op ] \u0026#34;=\u0026#34; . 所有左侧运算元都必须是可寻址的、map 索引表达式或空标识符其中之一。运算元可以用括号括起来。\nx = 1 *p = f() a[i] = 23 (k) = \u0026lt;-ch // same as: k = \u0026lt;-ch 对于赋值操作 x op= y 其中 op 为二元运算符，它和 x=x op (y) 是等价的，不过它只计算一次 x。op= 是单独的一个词汇单元，在赋值操作中左侧表达式和右侧表达式必须都是单值表达式，并且左侧表达式不能是空白标识符。\na[i] \u0026lt;\u0026lt;= 2 i \u0026amp;^= 1\u0026lt;\u0026lt;n 元祖赋值语句会把运算返回的多个值分别分配给变量列表。它有两种格式，第一种：它是返回多值的表达式，例如函数调用、通道和 map 运算、类型断言。左侧运算元的数量必须等于返回值的数量。如果函数返回两个值：\nx, y = f() 它会将第一个返回值分配给 x ，把第二个返回值分配给 y。第二种格式中，左侧运算元的数量必须等于右侧运算元的数量。每个表达式都只能返回单一值，右侧第 n 个值会赋值给左侧第 n 个变量。\none, two, three = \u0026#39;一\u0026#39;, \u0026#39;二\u0026#39;, \u0026#39;三\u0026#39; 空标识符可以在分配时忽略一个右面位置的表达式：\n_ = x // evaluate x but ignore it x, _ = f() // evaluate f() but ignore second result value 赋值分为两个阶段。首先会计算左侧运算元的索引表达式和指针的解引用工作并以一定顺序计算右侧表达式的值。\n然后依次对左侧运算元赋值。\na, b = b, a // exchange a and b x := []int{1, 2, 3} i := 0 i, x[i] = 1, 2 // set i = 1, x[0] = 2 i = 0 x[i], i = 2, 1 // set x[0] = 2, i = 1 x[0], x[0] = 1, 2 // set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end) x[1], x[3] = 4, 5 // set x[1] = 4, then panic setting x[3] = 5. type Point struct { x, y int } var p *Point x[2], p.x = 6, 7 // set x[2] = 6, then panic setting p.x = 7 i = 2 x = []int{3, 5, 7} for i, x[i] = range x { // set i, x[2] = 0, x[0] break } // after this loop, i == 0 and x == []int{3, 5, 3} 在赋值语句中每个值都必须能分配给左侧指定类型的值。除了以下特例：\n任何类型都能分配给空标识符。\n如果把无类型常量分配给接口类型或者空标识符，它会转换成默认类型。\n如果无类型的布尔值分配给了接口类型或者空标识符，它会先转换成 bool 类型。\nif 语句 # if 语句根据布尔值表达式的值来决定执行条件分支的代码。如果表达式为真，就执行 if 分支内的代码，否则执行 else 分支的代码。\nIfStmt = \u0026#34;if\u0026#34; [ SimpleStmt \u0026#34;;\u0026#34; ] Expression Block [ \u0026#34;else\u0026#34; ( IfStmt | Block ) ] . if x \u0026gt; max { x = max } 表达式可能先于普通语句，它会在表达式求值之前发生。\nif x := f(); x \u0026lt; y { return x } else if x \u0026gt; z { return z } else { return y } switch 语句 # for 语句 # for 语句可以用来重复执行一段代码。它有三种格式：迭代器可以是单一条件、for 分句或者 range 语句。\nForStmt = \u0026#34;for\u0026#34; [ Condition | ForClause | RangeClause ] Block . Condition = Expression . 单一条件的 for 语句 # 这种情况下 for 会在条件为 true 时一直重复。条件会在每次迭代时都重新计算。如果没有指定条件，默认一直为 true。\nfor a \u0026lt; b { a *= 2 } 带分句的 for 语句 # 带分句的 for 语句也是由条件控制，只是它有一个初始化和寄送的过程。例如赋值、递增或者递减语句。初始化语句可以是短变量声明，但是寄送语句不能。在初始化语句中声明的变量可以在迭代过程中使用。\nForClause = [ InitStmt ] \u0026#34;;\u0026#34; [ Condition ] \u0026#34;;\u0026#34; [ PostStmt ] . InitStmt = SimpleStmt . PostStmt = SimpleStmt . for i := 0; i \u0026lt; 10; i++ { f(i) } 如果初始化语句非空，它会在进入迭代前执行一次；post 语句在每次循环后都会执行一次。在只有条件的情况下可以省略分号。如果缺省条件语句，默认为 true。\nfor cond { S() } is the same as for ; cond ; { S() } for { S() } is the same as for true { S() } 带 range 分句的 for 语句 # 带 range 分句的 for 语句可以访问数组、切片、字符串、map 的所有元素，还可以从通道中接收值。迭代获得元素分配给了相应的迭代变量并执行代码块。\nRangeClause = [ ExpressionList \u0026#34;=\u0026#34; | IdentifierList \u0026#34;:=\u0026#34; ] \u0026#34;range\u0026#34; Expression . 右侧的 range 分句表达式叫做 range 表达式，它可能是数组、数组的指针、切片、字符串、map 或通道接收者类型。在分配时，左侧运算元必须是可寻址的或者 map 的索引表达式；它们作为迭代变量。如果 range 表达式是一个通道类型，至少需要有一个变量，它也可以有两个变量。如果迭代变量是空标识符，就代表在分句中不存在该标识符。\nRange expression 1st value 2nd value array or slice a [n]E, *[n]E, or []E index i int a[i] E string s string type index i int see below rune map m map[K]V key k K m[k] V channel c chan E, \u0026lt;-chan E element e E var testdata *struct { a *[7]int } for i, _ := range testdata.a { // testdata.a is never evaluated; len(testdata.a) is constant // i ranges from 0 to 6 f(i) } var a [10]string for i, s := range a { // type of i is int // type of s is string // s == a[i] g(i, s) } var key string var val interface {} // element type of m is assignable to val m := map[string]int{\u0026#34;mon\u0026#34;:0, \u0026#34;tue\u0026#34;:1, \u0026#34;wed\u0026#34;:2, \u0026#34;thu\u0026#34;:3, \u0026#34;fri\u0026#34;:4, \u0026#34;sat\u0026#34;:5, \u0026#34;sun\u0026#34;:6} for key, val = range m { h(key, val) } // key == last map key encountered in iteration // val == map[key] var ch chan Work = producer() for w := range ch { doWork(w) } // empty a channel for range ch {} Go 语句 # go 语句会开始在相同地址空间中的单独 goroutine 中调用函数。\nGoStmt = \u0026#34;go\u0026#34; Expression . 表达式必须是函数或者方法调用；它不能使用括号括起来，调用内置函数有表达式语句的限制。\n函数的值和参数会按顺序在调用的 goroutine 中求值。不像普通的函数调用，程序不会等待函数调用完成，而是直接开启一个新的 goroutine 执行函数。函数退出时，goroutine 也会退出。函数的任何返回值都会被丢弃。\ngo Server() go func(ch chan\u0026lt;- bool) { for { sleep(10); ch \u0026lt;- true }} (c) select 语句 # select 语句会在接收/发送操作集中选择一个执行。它看起来和 switch 很像，只不过是专门针对通信操作的。\nSelectStmt = \u0026#34;select\u0026#34; \u0026#34;{\u0026#34; { CommClause } \u0026#34;}\u0026#34; . CommClause = CommCase \u0026#34;:\u0026#34; StatementList . CommCase = \u0026#34;case\u0026#34; ( SendStmt | RecvStmt ) | \u0026#34;default\u0026#34; . RecvStmt = [ ExpressionList \u0026#34;=\u0026#34; | IdentifierList \u0026#34;:=\u0026#34; ] RecvExpr . RecvExpr = Expression . 接收表达式可以将接收表达式的值分配给一个或两个变量。接收表达式必须是一个接收运算元（可以使用括号括起来）。它最多允许有一个 default 语句。\nselect 语句执行以下几个步骤：\n对于 select 语句的所有分句，接收操作的通道运算元、通道、发送语句的右侧表达式都会执行一次操作。\n如果一个或多个通信同时发生，它会通过一致性随机选择一个执行。如果没有 default 语句，select 语句会一直阻塞。\n除了 default 分句，其他分句只有在开始进行通信的时候才会执行。\n如果 select 分句是一个接收语句，它可以给变量分配值。\n执行 select 分句内的内容。\n如果向 nil 通道发送信息在没有 default 分句的情况下会一直阻塞。\nvar a []int var c, c1, c2, c3, c4 chan int var i1, i2 int select { case i1 = \u0026lt;-c1: print(\u0026#34;received \u0026#34;, i1, \u0026#34; from c1\\n\u0026#34;) case c2 \u0026lt;- i2: print(\u0026#34;sent \u0026#34;, i2, \u0026#34; to c2\\n\u0026#34;) case i3, ok := (\u0026lt;-c3): // same as: i3, ok := \u0026lt;-c3 if ok { print(\u0026#34;received \u0026#34;, i3, \u0026#34; from c3\\n\u0026#34;) } else { print(\u0026#34;c3 is closed\\n\u0026#34;) } case a[f()] = \u0026lt;-c4: // same as: // case t := \u0026lt;-c4 //\ta[f()] = t default: print(\u0026#34;no communication\\n\u0026#34;) } for { // send random sequence of bits to c select { case c \u0026lt;- 0: // note: no statement, no fallthrough, no folding of cases case c \u0026lt;- 1: } } select {} // block forever return 语句 # return 语句会终止函数 F 的执行并可选的返回一个或多个返回值。所有的滞后函数都会在 F 返回到它的调用者之前执行。\nReturnStmt = \u0026#34;return\u0026#34; [ ExpressionList ] . 如果函数没有返回值类型，return 不能返回任何值。\nfunc noResult() { return } 有三种方式能够返回指定类型的值：\n返回值可以直接在 return 语句中列出。每个表达式都必须返回一个值并且能够分配给相应的返回值类型。 func simpleF() int { return 2 } func complexF1() (re float64, im float64) { return -7.0, -4.0 } return 语句的表达式列表可以是一个返回多值的函数调用。这时会使用临时变量来获取函数调用的返回值并直接将其作为 return 语句的表达式列表。 func complexF2() (re float64, im float64) { return complexF1() } 如果制定了返回值的标识符那么 return 的表达式列表可以为空。返回值参数会作为普通的本地变量按需分配。return 语句会直接返回它们。 func complexF3() (re float64, im float64) { re = 7.0 im = 4.0 return } func (devnull) Write(p []byte) (n int, _ error) { n = len(p) return } 不管如何声明，所有的返回值都会在进入函数前提前初始化成类型的零值。return 语句会在所有 defer 函数之前指定返回值。\n实现限制：编译器不允许在覆盖了命名返回值的作用域中直接返回。\nfunc f(n int) (res int, err error) { if _, err := f(n-1); err != nil { return // invalid return statement: err is shadowed } return } break 语句 # break 语句会在 for、switch 或 select 语句内部退出到相同函数的某个位置。\nBreakStmt = \u0026#34;break\u0026#34; [ Label ] . 如果想指定标签，它必须出现在它所中止的 for、switch 或 select 语句旁。\nOuterLoop: for i = 0; i \u0026lt; n; i++ { for j = 0; j \u0026lt; m; j++ { switch a[i][j] { case nil: state = Error break OuterLoop case item: state = Found break OuterLoop } } } continue 语句 # continue 语句会提前 for 语句的下一次迭代。for 语句必须和 continue 在相同函数中。\nRowLoop: for y, row := range rows { for x, data := range row { if data == endOfRow { continue RowLoop } row[x] = data + bias(x, y) } } goto 语句 # goto 会将程序跳转到相同函数的指定标签处。\nGotoStmt = \u0026#34;goto\u0026#34; Label . goto Error goto 语句不允许跳过作用域内程序变量的初始化工作。\ngoto L // BAD v := 3 L: 上面的程序是错误的，因为它跳过了变量 v 的初始化过程。\nif n%2 == 1 { goto L1 } for n \u0026gt; 0 { f() n-- L1: f() n-- } 标签作用域外的 goto 语句不能跳转到标签处，所以上面的代码是错误的。\nFallthrough 语句 # fallthrough 语句会跳转到 switch 语句中的下一个 case 分句中。它应该只在最后一个非空分句中使用。\nFallthroughStmt = \u0026#34;fallthrough\u0026#34; . Defer 语句 # defer 语句会在包裹函数返回后触发函数调用。这里的返回泛指函数因为 return 语句终止、到达函数末尾或者当前 goroutine 触发运行时恐慌。\nDeferStmt = \u0026#34;defer\u0026#34; Expression . 表达式必须是函数或者方法调用；它不能使用括号括起来，调用内置函数会有一些限制。\n每次执行 defer 语句执行时都会计算函数的参数和值，但是并不会调用函数。相反，函数的调用是在包裹函数返回后进行，它们的执行顺序与声明顺序正好相反。如果 defer 对应的函数值为 nil，会在调用函数的时候导致运行时恐慌而不是声明 defer 语句的时候。\n例如：当 defer 函数为函数字面值且包裹函数具有命名结果值，此时，我们在defer 函数中可以访问和修改命名的结果值。defer 函数的所有返回值都会被忽略。\nlock(l) defer unlock(l) // unlocking happens before surrounding function returns // prints 3 2 1 0 before surrounding function returns for i := 0; i \u0026lt;= 3; i++ { defer fmt.Print(i) } // f returns 1 func f() (result int) { defer func() { result++ }() return 0 } 内置函数 # 内置函数是预定义的。调用他们和其他函数一样只是他们接受一个类型而不是一个表达式。\n内置函数没有标准的 Go 类型，所以他们只能作为调用表达式；而不能作为函数的值。\nClose # 对于管道类型 c，内置函数 close(c) 意味着不在有数据插入到管道中。如果 c 是一个只接收数据的管道，会发生错误。向已经关闭的发送数据或者重复关闭已经关闭的管道会导致运行时恐慌。关闭 nil 管道会引起运行时恐慌。调用 close 后所有之前发送的数据都能接收到，并且在最后不会阻塞而返回零值。多值的接收操作能够返回接收到的数据和表示管道是否关闭的布尔值。\n长度和容积 # 内置函数 len 和 cap 可以接收多种类型的参数，并且返回一个 int 类型结果值。函数的实现能够确保结果值不会溢出。\nCall Argument type Result len(s) string type string length in bytes [n]T, *[n]T array length (== n) []T slice length map[K]T map length (number of defined keys) chan T number of elements queued in channel buffer cap(s) [n]T, *[n]T array length (== n) []T slice capacity chan T channel buffer capacity 切片的容积底层数组包含的元素个数。在任何情况下都有以下关系：\n0 \u0026lt;= len(s) \u0026lt;= cap(s) nil 切片，map，或者 channel 的长度都为 0。nil 切片，管道的容积都为 0。\n表达式 len(x) 在 s 是字符串常量时也为常量。如果 s 为数组或者指向数组的指针并且表达式 s 不包含 channel 接收器或者函数调用那么 len(s) 和 cap(s) 也是常量；在这个情况下 s 时不能求值的。其他情况下 len 和 cap 不是常量并且 s 是可以求值的。\nconst ( c1 = imag(2i) // imag(2i) = 2.0 is a constant c2 = len([10]float64{2}) // [10]float64{2} contains no function calls c3 = len([10]float64{c1}) // [10]float64{c1} contains no function calls c4 = len([10]float64{imag(2i)}) // imag(2i) is a constant and no function call is issued c5 = len([10]float64{imag(z)}) // invalid: imag(z) is a (non-constant) function call ) var z complex128 内存分配 # 内置函数 new 接收一个类型 T，它会在运行时给变量分配内存，并且返回一个指向类型 T 的 *T 类型指针。变量的初始化在初始化值章节中介绍。\nnew(T) 例如：\ntype S struct { a int; b float64 } new(S) 给 S 类型的变量分配空间，并初始化它（a=0，b=0.0），并且返回一个 *S 类型值保存变量所在的位置。\n创建切片，map 和 管道 # 内置函数 make 以一个类型作为参数，它必须是一个切片，map 或者管道类型，它返回一个 T 类型的值，而不是（*T）类型，它会按初始化值章节描述的方式进行初始化。\nCall Type T Result make(T, n) slice slice of type T with length n and capacity n make(T, n, m) slice slice of type T with length n and capacity m make(T) map map of type T make(T, n) map map of type T with initial space for approximately n elements make(T) channel unbuffered channel of type T make(T, n) channel buffered channel of type T, buffer size n n 和 m 必须是整数类型或者无类型常量。一个常量参数不能为负数并且该值在 int 类型的范围内；如果它是无类型常量，会被转换成 int 类型。如果 n 和 m 都是常量，那么 n 必须大于 m。如果 n 是负数或者大于 m 会引发运行时 panic。\ns := make([]int, 10, 100) // slice with len(s) == 10, cap(s) == 100 s := make([]int, 1e3) // slice with len(s) == cap(s) == 1000 s := make([]int, 1\u0026lt;\u0026lt;63) // illegal: len(s) is not representable by a value of type int s := make([]int, 10, 0) // illegal: len(s) \u0026gt; cap(s) c := make(chan int, 10) // channel with a buffer size of 10 m := make(map[string]int, 100) // map with initial space for approximately 100 elements 使用 make 来指定大小初始化 map 类型将会创建一个预留 n 个元素空间的 map 类型。更详细的行为依赖于具体实现。\n追加或者拷贝切片 # 内置函数 append 和 copy 可以进行切片的通用操作。对于这两个函数，一个是拷贝内存，一个是引用内存。\n可变参数的函数 append 可以向切片 s 中追加一个或多个 x 值，并返回这个切片。传进 ...T 的值会根据参数传值。作为特例，append 在 s 为 []byte 切片时，可以使用字符串后面跟 ... 作为参数。\n如果 s 的容积容纳不下这些元素，那么 append 会分配一个新的足够大的数组。否则会使用原来的底层数组。\ns0 := []int{0, 0} s1 := append(s0, 2) // append a single element s1 == []int{0, 0, 2} s2 := append(s1, 3, 5, 7) // append multiple elements s2 == []int{0, 0, 2, 3, 5, 7} s3 := append(s2, s0...) // append a slice s3 == []int{0, 0, 2, 3, 5, 7, 0, 0} s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0} var t []interface{} t = append(t, 42, 3.1415, \u0026#34;foo\u0026#34;) // t == []interface{}{42, 3.1415, \u0026#34;foo\u0026#34;} var b []byte b = append(b, \u0026#34;bar\u0026#34;...) // append string contents b == []byte{\u0026#39;b\u0026#39;, \u0026#39;a\u0026#39;, \u0026#39;r\u0026#39; } copy 函数从 src 拷贝原属到 dst 并且返回拷贝元素的个数。参数中所有的元素类型必须是 T 类型或者能转换成 T 的类型。拷贝元素的数量是 len(src) 和 len(dst) 中的较小值。作为特例，copy 可以从 string 类型拷贝元素到 []byte 类型。这会把字符串中的元素拷贝到字节切片中。\ncopy(dst, src []T) int copy(dst []byte, src string) int 例：\nvar a = [...]int{0, 1, 2, 3, 4, 5, 6, 7} var s = make([]int, 6) var b = make([]byte, 5) n1 := copy(s, a[0:]) // n1 == 6, s == []int{0, 1, 2, 3, 4, 5} n2 := copy(s, s[2:]) // n2 == 4, s == []int{2, 3, 4, 5, 4, 5} n3 := copy(b, \u0026#34;Hello, World!\u0026#34;) // n3 == 5, b == []byte(\u0026#34;Hello\u0026#34;) 删除 map 中的元素 # 内置函数 delete 移除 map 类型 m 中的键值 k。k 的类型必须是能够转换成 m 键类型的类型。\ndelete(m, k) // remove element m[k] from map m 如果 map 类型 m 是 nil 或者 m[k] 不存在，那么 delete 函数不做任何事情。\n操作复数 # 有三个函数可以组装或者分解复数。内置函数 complex 会构造一个复数，real 和 imag 会分解出复数的实部和虚部。\ncomplex(realPart, imaginaryPart floatT) complexT real(complexT) floatT imag(complexT) floatT 参数的类型和返回值类型是对应的。对于 complex，两个参数必须是相同的浮点类型，并返回由相同浮点数组成的复数类型。complex64 是 float32 对应的类型，complex128 是 float64 对应的参数类型。如果参数是一个无类型常量，它会转换成另一个参数的类型。如果两个参数都是无类型常量，他们必须实数或者虚数部分为零，并且它会返回一个无类型的复数常量。\nreal 和 imag 函数和 complex 正好相反的，所以对于一个值复数类型 Z 的值 z，z==Z(complex(real(z),imag(z)))。\n如果这么操作都是常量，那么返回的值也是常量。\nvar a = complex(2, -2) // complex128 const b = complex(1.0, -1.4) // untyped complex constant 1 - 1.4i x := float32(math.Cos(math.Pi/2)) // float32 var c64 = complex(5, -x) // complex64 var s uint = complex(1, 0) // untyped complex constant 1 + 0i can be converted to uint _ = complex(1, 2\u0026lt;\u0026lt;s) // illegal: 2 assumes floating-point type, cannot shift var rl = real(c64) // float32 var im = imag(a) // float64 const c = imag(b) // untyped constant -1.4 _ = imag(3 \u0026lt;\u0026lt; s) // illegal: 3 assumes complex type, cannot shift 处理 panic # 两个内置函数 panic 和 recover，可以抛出和处理运行时 panic 和程序的错误条件。\nfunc panic(interface{}) func recover() interface{} 当执行 F 函数时，显式的调用 panic或者运行时 panic 都会中断 F 的执行。但是 F 中的延迟函数还会执行。接下来调用 F 函数处的延迟函数也会执行，一直到顶级的延迟函数。鉴于这点，程序关闭并且错误条件可以抛出。包括 panic 中的值。这个顺序叫做 panicking。\npanic(42) panic(\u0026#34;unreachable\u0026#34;) panic(Error(\u0026#34;cannot parse\u0026#34;)) recover 函数允许程序从一个 panicking 中恢复执行。假设函数 G 延迟执行函数 D ，在 D 中调用 recover 这时如果在 G 执行时发生 panic 会在 D 中恢复。当函数执行到 D，recover 的返回值会返回 panic 对应的错误，并且终止 panicking 。在这个情况下 G 函数和 panic 之间的代码不会执行。任何在 D 中 G 之前的延迟函数会返回到调用者。\n在下面两种情况下 recover 会返回 nil：\npanic 的参数为 nil\n携程里没有发生 panic\nrecover 不是在延迟函数中执行\n本例中的 protect 函数会在 g 发生 panic 的时候恢复执行。\nfunc protect(g func()) { defer func() { log.Println(\u0026#34;done\u0026#34;) // Println executes normally even if there is a panic if x := recover(); x != nil { log.Printf(\u0026#34;run time panic: %v\u0026#34;, x) } }() log.Println(\u0026#34;start\u0026#34;) g() } 初始化 # 这个实现提供了多个内置函数来帮助进行初始化。这些函数用来输出信息但是不确定会一直存在于语言中，他们都没有返回值。\nFunction Behavior print prints all arguments; formatting of arguments is implementation-specific println like print but prints spaces between arguments and a newline at the end 实现限制：print 和 println 不接受除了布尔值，数字，字符串以外的其他类型。\n程序的初始化和执行 # 零值 # 当为变量分配内存空间时，不管是声明还是调用 new 或者使用字面值和 make 初始化，只要创建了一个新值变量都会有一个默认值。这样的元素和值会使用它类型的零值：false 是布尔值的零值，0 为数值类型零值，\u0026quot;\u0026quot; 为字符串零值，nil 为指针，函数，接口，切片，频道，字典。初始化会递归完成，所以结构体里的数组中的元素也都会有它自己的零值。\n下面两个声明时相等的：\nvar i int var i int = 0 请看下面的声明：\ntype T struct { i int; f float64; next *T } t := new(T) t.i == 0 t.f == 0.0 t.next == nil 这和下面的声明时同等效果的：\nvar t T 包的初始化 # 保级变量会按声明的顺序进行初始化，如果依赖其他变量，则会在其他变量之后进行初始化。\n更确切的说，如果包级变量还没初始化并且没有初始化表达式或者表达式中不包含对其他未初始化变量的依赖，那么会认为它正在等待初始化。初始化过程会从最早声明的变量开始向下一个包级变量重复，直到没有需要初始化的变量。\n如果在初始化过程完成后还有未初始化的变量，那么这些变量可能是循环初始化了，这事程序不是合法的。\n在多个文件中变量的声明顺序会依据编译时文件出现的顺序：声明在第一个文件中的变量优先于第二个文件中声明的变量，依此类推。\n对依赖关系的分析不会根据变量的具体值，它只分析在源码中是否引用了其他变量。例如，如果变量 x 的初始化表达式引用了变量 y 那么 x 就依赖于 y：\n引用一个变量或者函数中用到了一个变量\n引用了一个方法值 m 或者方法表达式 t.m (这里的静态类型 t 不是借口类型，并且方法 m 是 t 方法集中的方法)。t.m 的返回值不会在此时影响。\n变量，函数，或者方法 x 依赖变量 y\n依赖分析会在每个包中执行；他只考虑当前包中的析变量，函数，和方法。\n例如，给定声明：\nvar ( a = c + b b = f() c = f() d = 3 ) func f() int { d++ return d } 初始化顺序为 d，b，c，a。\n变量可以在包中声明的初始化函数 init 中进行初始化，它没有参数和返回值。\nfunc init() {} 可以为每个包定义多个该函数，甚至在一个文件中也可以。并且不会声明该该标识符。因此 init 函数不能在程序中调用。\n还未导入的包会先初始化包级的变量然后按照 init 函数在源码中的顺序调用，它可能在包的多个文件中。如果需要导入一个包，它会在初始化自己之前先初始化这个需要导入的包。如果导入一个包多次，那这个包只会初始化一次。导入的包不能存在循环引用。\n包的初始化——变量初始化和对 init 函数的调用会按顺序发生在同一个 goroutine 中。 init 函数可能会启动其他 goroutine。不过一般 init 函数都是按序进行初始化的：它只在上一步已经执行完成时才会调用下一个步骤。\n确保初始化行为是可以复现的，构建系统鼓励在同一个包中包含多个文件这些文件在编译器中会以字母排序。\n程序执行 # 一个完整的程序由一个 main 包导入所有需要的包。main 包必须以 main 作为包名并且声明一个没有参数和返回值的 main 函数。\nfunc main() {} 程序先初始化 main 包然后调用 main 函数。当 main 函数返回时，程序就会退出。它不会等待其他 goroutines 完成。\n错误 # 预定义的错误类型为：\ntype error interface { Error() string } 它是表示错误信息的常规接口，nil 代表没有发生错误。例如，在文件中读取数据可以定义为：\nfunc Read(f *File, b []byte) (n int, err error) 运行时恐慌 # 运行时错误（例如数组的越界访问）会造成运行时恐慌，它和以 runtime.Error 接口实现调用内置的 panic 函数一样。runtime.Error 满足预定义的 error 接口。不同的错误值代表不同的运行时错误条件。\npackage runtime type Error interface { error // and perhaps other methods } 系统相关 # unsafe 包 # unsafe 是编译器已知的内置包，可以通过导入路径 unsafe 访问包内容，提供 unsafe 包目的是支持底层编程（包括操作非 Go 类型的数据结构）。使用 unsafe 包必须自己保证类型安全而且它有可能破坏程序的移植性。unsafe 包提供了以下接口：\npackage unsafe type ArbitraryType int // 任意一个 Go 类型；它不是一个具体的类型。 type Pointer *ArbitraryType func Alignof(variable ArbitraryType) uintptr func Offsetof(selector ArbitraryType) uintptr func Sizeof(variable ArbitraryType) uintptr Pointer 是一个指针类型，但是不能解引用 Pointer 的值。所有底层类型 uintptr 的指针和值都能转换成 Pointer 类型，反之亦然。Pointer 和 uintptr 之间的转换效果由具体实现定义。\nvar f float64 bits = *(*uint64)(unsafe.Pointer(\u0026amp;f)) type ptr unsafe.Pointer bits = *(*uint64)(ptr(\u0026amp;f)) var p ptr = nil 假设变量 v 由 var v = x 定义。Alignof 以表达式 x 作为参数并返回 x 的对齐字节数。Sizeof 以表达式 x 作为参数并返回 x 的大小。\n函数 Offsetof 以选择器 s.f（ s 或者 *s 结构体中的 f 字段）作为参数，返回字段相对结构体首地址的位置。如果 f 是一个嵌入字段，那 f 必须可以直接访问（不能通过指针进行间接访问）。对于结构体 s 的 f 字段：\nuintptr(unsafe.Pointer(\u0026amp;s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(\u0026amp;s.f)) 计算机的体系结构要求对齐内存地址（对于一个变量的地址有多种因素影响对齐）。Alignof 函数获取一个人和类型的表达式并返回变量对齐的字节数。对于变量 x：\nuintptr(unsafe.Pointer(\u0026amp;x)) % unsafe.Alignof(x) == 0 编译时 uintptr 类型常量表达式会调用 Alignof，Offsetof，和 Sizeof。\n确定的大小和对齐字节数 # 对于数字类型，确定有以下尺寸：\ntype size in bytes byte, uint8, int8 1 uint16, int16 2 uint32, int32, float32 4 uint64, int64, float64, complex64 8 complex128 16 Go 中规定的最小对齐特性：\n对于任意变量类型 x：unsafe.Alignof(x) 至少为 1。\n对于结构体类型：unsafe.Alignof(x) 是所有内部字段 unsafe.Alignof(x.f) 的最大值，并且至少为 1。\n对于数组类型：unsafe.Alignof(x) 和数组元素类型的 alignment 相同。\n结构体（数组）在内部没有字段（元素）的时候大小为 0。两个所占空间大小为 0 的不同变量可能在内存中拥有相同地址。\nGo 语言实战指南 # 函数和方法 # 函数定义和调用 # package main import \u0026#34;fmt\u0026#34; // 基本函数 func greet(name string) { fmt.Printf(\u0026#34;Hello, %s!\\n\u0026#34;, name) } // 带返回值的函数 func add(a, b int) int { return a + b } // 多返回值函数 func divide(a, b float64) (float64, error) { if b == 0 { return 0, fmt.Errorf(\u0026#34;division by zero\u0026#34;) } return a / b, nil } // 命名返回值 func calculate(a, b int) (sum, product int) { sum = a + b product = a * b return // 裸返回 } // 可变参数函数 func sum(numbers ...int) int { total := 0 for _, num := range numbers { total += num } return total } // 高阶函数 func apply(fn func(int, int) int, a, b int) int { return fn(a, b) } func main() { // 函数调用 greet(\u0026#34;Alice\u0026#34;) result := add(3, 5) fmt.Printf(\u0026#34;3 + 5 = %d\\n\u0026#34;, result) // 处理多返回值 quotient, err := divide(10, 3) if err != nil { fmt.Printf(\u0026#34;Error: %v\\n\u0026#34;, err) } else { fmt.Printf(\u0026#34;10 / 3 = %.2f\\n\u0026#34;, quotient) } // 命名返回值 s, p := calculate(4, 6) fmt.Printf(\u0026#34;Sum: %d, Product: %d\\n\u0026#34;, s, p) // 可变参数 total := sum(1, 2, 3, 4, 5) fmt.Printf(\u0026#34;Sum of 1,2,3,4,5 = %d\\n\u0026#34;, total) // 高阶函数 multiply := func(x, y int) int { return x * y } result = apply(multiply, 4, 5) fmt.Printf(\u0026#34;4 * 5 = %d\\n\u0026#34;, result) // 匿名函数 func(msg string) { fmt.Println(\u0026#34;Anonymous function:\u0026#34;, msg) }(\u0026#34;Hello\u0026#34;) } 方法 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;math\u0026#34; ) // 定义结构体 type Circle struct { Radius float64 } type Rectangle struct { Width, Height float64 } // 值接收者方法 func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius } func (c Circle) Circumference() float64 { return 2 * math.Pi * c.Radius } // 指针接收者方法 func (c *Circle) Scale(factor float64) { c.Radius *= factor } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (r *Rectangle) Scale(factor float64) { r.Width *= factor r.Height *= factor } // 接口定义 type Shape interface { Area() float64 } type Scalable interface { Scale(float64) } // 计算形状面积的函数 func printArea(s Shape) { fmt.Printf(\u0026#34;Area: %.2f\\n\u0026#34;, s.Area()) } func main() { // 创建对象 circle := Circle{Radius: 5} rectangle := Rectangle{Width: 4, Height: 6} // 调用方法 fmt.Printf(\u0026#34;Circle area: %.2f\\n\u0026#34;, circle.Area()) fmt.Printf(\u0026#34;Circle circumference: %.2f\\n\u0026#34;, circle.Circumference()) // 修改对象（需要指针接收者） fmt.Printf(\u0026#34;Original circle radius: %.2f\\n\u0026#34;, circle.Radius) circle.Scale(2) fmt.Printf(\u0026#34;Scaled circle radius: %.2f\\n\u0026#34;, circle.Radius) // 接口使用 printArea(circle) printArea(rectangle) // 类型断言 var shape Shape = circle if c, ok := shape.(Circle); ok { fmt.Printf(\u0026#34;It\u0026#39;s a circle with radius %.2f\\n\u0026#34;, c.Radius) } } 接口和多态 # 接口定义和实现 # package main import \u0026#34;fmt\u0026#34; // 定义接口 type Writer interface { Write([]byte) (int, error) } type Reader interface { Read([]byte) (int, error) } // 组合接口 type ReadWriter interface { Reader Writer } // 空接口 type Any interface{} // 实现接口的结构体 type FileWriter struct { filename string } func (fw FileWriter) Write(data []byte) (int, error) { fmt.Printf(\u0026#34;Writing to file %s: %s\\n\u0026#34;, fw.filename, string(data)) return len(data), nil } type ConsoleWriter struct{} func (cw ConsoleWriter) Write(data []byte) (int, error) { fmt.Printf(\u0026#34;Console output: %s\\n\u0026#34;, string(data)) return len(data), nil } // 使用接口的函数 func writeData(w Writer, data string) { w.Write([]byte(data)) } // 类型断言和类型选择 func describe(i interface{}) { switch v := i.(type) { case int: fmt.Printf(\u0026#34;Integer: %d\\n\u0026#34;, v) case string: fmt.Printf(\u0026#34;String: %s\\n\u0026#34;, v) case FileWriter: fmt.Printf(\u0026#34;FileWriter for: %s\\n\u0026#34;, v.filename) default: fmt.Printf(\u0026#34;Unknown type: %T\\n\u0026#34;, v) } } func main() { // 创建实现了接口的对象 fileWriter := FileWriter{filename: \u0026#34;output.txt\u0026#34;} consoleWriter := ConsoleWriter{} // 多态使用 writeData(fileWriter, \u0026#34;Hello, File!\u0026#34;) writeData(consoleWriter, \u0026#34;Hello, Console!\u0026#34;) // 接口变量 var writer Writer writer = fileWriter writeData(writer, \u0026#34;Using interface variable\u0026#34;) // 类型断言 if fw, ok := writer.(FileWriter); ok { fmt.Printf(\u0026#34;Successfully asserted as FileWriter: %s\\n\u0026#34;, fw.filename) } // 空接口使用 describe(42) describe(\u0026#34;Hello\u0026#34;) describe(fileWriter) describe([]int{1, 2, 3}) } 并发编程 # Goroutines # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) // 简单的 goroutine func sayHello(name string) { for i := 0; i \u0026lt; 3; i++ { fmt.Printf(\u0026#34;Hello from %s - %d\\n\u0026#34;, name, i) time.Sleep(100 * time.Millisecond) } } // 使用 WaitGroup 等待 goroutines 完成 func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 确保在函数结束时调用 Done() fmt.Printf(\u0026#34;Worker %d starting\\n\u0026#34;, id) time.Sleep(time.Second) fmt.Printf(\u0026#34;Worker %d done\\n\u0026#34;, id) } // 并发计算 func calculate(start, end int, result chan\u0026lt;- int) { sum := 0 for i := start; i \u0026lt;= end; i++ { sum += i } result \u0026lt;- sum } func main() { fmt.Println(\u0026#34;=== 基本 Goroutines ===\u0026#34;) // 启动 goroutines go sayHello(\u0026#34;Alice\u0026#34;) go sayHello(\u0026#34;Bob\u0026#34;) // 主 goroutine 也做一些工作 sayHello(\u0026#34;Main\u0026#34;) fmt.Println(\u0026#34;\\n=== 使用 WaitGroup ===\u0026#34;) var wg sync.WaitGroup // 启动多个 worker for i := 1; i \u0026lt;= 3; i++ { wg.Add(1) // 增加等待计数 go worker(i, \u0026amp;wg) } wg.Wait() // 等待所有 goroutines 完成 fmt.Println(\u0026#34;All workers completed\u0026#34;) fmt.Println(\u0026#34;\\n=== 并发计算 ===\u0026#34;) // 使用 goroutines 并发计算 result1 := make(chan int) result2 := make(chan int) go calculate(1, 1000, result1) go calculate(1001, 2000, result2) sum1 := \u0026lt;-result1 sum2 := \u0026lt;-result2 fmt.Printf(\u0026#34;Sum 1-1000: %d\\n\u0026#34;, sum1) fmt.Printf(\u0026#34;Sum 1001-2000: %d\\n\u0026#34;, sum2) fmt.Printf(\u0026#34;Total: %d\\n\u0026#34;, sum1+sum2) } Channels # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) // 生产者函数 func producer(ch chan\u0026lt;- int) { for i := 1; i \u0026lt;= 5; i++ { fmt.Printf(\u0026#34;Producing: %d\\n\u0026#34;, i) ch \u0026lt;- i time.Sleep(500 * time.Millisecond) } close(ch) // 关闭 channel } // 消费者函数 func consumer(ch \u0026lt;-chan int) { for value := range ch { // 从 channel 读取直到关闭 fmt.Printf(\u0026#34;Consuming: %d\\n\u0026#34;, value) time.Sleep(300 * time.Millisecond) } } // 工作池模式 func workerPool() { jobs := make(chan int, 100) results := make(chan int, 100) // 启动 3 个 worker for w := 1; w \u0026lt;= 3; w++ { go func(id int) { for job := range jobs { fmt.Printf(\u0026#34;Worker %d processing job %d\\n\u0026#34;, id, job) time.Sleep(time.Second) results \u0026lt;- job * 2 } }(w) } // 发送 5 个任务 for j := 1; j \u0026lt;= 5; j++ { jobs \u0026lt;- j } close(jobs) // 收集结果 for r := 1; r \u0026lt;= 5; r++ { result := \u0026lt;-results fmt.Printf(\u0026#34;Result: %d\\n\u0026#34;, result) } } func main() { fmt.Println(\u0026#34;=== 基本 Channel 使用 ===\u0026#34;) // 创建 channel ch := make(chan int) // 启动生产者和消费者 go producer(ch) consumer(ch) fmt.Println(\u0026#34;\\n=== 缓冲 Channel ===\u0026#34;) // 缓冲 channel buffered := make(chan string, 2) buffered \u0026lt;- \u0026#34;Hello\u0026#34; buffered \u0026lt;- \u0026#34;World\u0026#34; fmt.Println(\u0026lt;-buffered) fmt.Println(\u0026lt;-buffered) fmt.Println(\u0026#34;\\n=== Select 语句 ===\u0026#34;) ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(1 * time.Second) ch1 \u0026lt;- \u0026#34;from ch1\u0026#34; }() go func() { time.Sleep(2 * time.Second) ch2 \u0026lt;- \u0026#34;from ch2\u0026#34; }() for i := 0; i \u0026lt; 2; i++ { select { case msg1 := \u0026lt;-ch1: fmt.Println(\u0026#34;Received:\u0026#34;, msg1) case msg2 := \u0026lt;-ch2: fmt.Println(\u0026#34;Received:\u0026#34;, msg2) case \u0026lt;-time.After(3 * time.Second): fmt.Println(\u0026#34;Timeout\u0026#34;) } } fmt.Println(\u0026#34;\\n=== 工作池模式 ===\u0026#34;) workerPool() } 同步原语 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;sync/atomic\u0026#34; \u0026#34;time\u0026#34; ) // 使用 Mutex 保护共享资源 type SafeCounter struct { mu sync.Mutex value int } func (c *SafeCounter) Inc() { c.mu.Lock() defer c.mu.Unlock() c.value++ } func (c *SafeCounter) Value() int { c.mu.Lock() defer c.mu.Unlock() return c.value } // 使用 RWMutex 的缓存 type Cache struct { mu sync.RWMutex data map[string]string } func NewCache() *Cache { return \u0026amp;Cache{ data: make(map[string]string), } } func (c *Cache) Get(key string) (string, bool) { c.mu.RLock() defer c.mu.RUnlock() value, exists := c.data[key] return value, exists } func (c *Cache) Set(key, value string) { c.mu.Lock() defer c.mu.Unlock() c.data[key] = value } // 使用 Once 确保只执行一次 var once sync.Once var instance *Cache func GetCacheInstance() *Cache { once.Do(func() { fmt.Println(\u0026#34;Creating cache instance\u0026#34;) instance = NewCache() }) return instance } func main() { fmt.Println(\u0026#34;=== Mutex 示例 ===\u0026#34;) counter := \u0026amp;SafeCounter{} var wg sync.WaitGroup // 启动多个 goroutines 并发增加计数器 for i := 0; i \u0026lt; 10; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j \u0026lt; 100; j++ { counter.Inc() } }() } wg.Wait() fmt.Printf(\u0026#34;Final counter value: %d\\n\u0026#34;, counter.Value()) fmt.Println(\u0026#34;\\n=== RWMutex 示例 ===\u0026#34;) cache := NewCache() // 写入数据 cache.Set(\u0026#34;key1\u0026#34;, \u0026#34;value1\u0026#34;) cache.Set(\u0026#34;key2\u0026#34;, \u0026#34;value2\u0026#34;) // 并发读取 for i := 0; i \u0026lt; 5; i++ { wg.Add(1) go func(id int) { defer wg.Done() if value, exists := cache.Get(\u0026#34;key1\u0026#34;); exists { fmt.Printf(\u0026#34;Reader %d: %s\\n\u0026#34;, id, value) } }(i) } wg.Wait() fmt.Println(\u0026#34;\\n=== Once 示例 ===\u0026#34;) // 多次调用，但只初始化一次 for i := 0; i \u0026lt; 3; i++ { cache := GetCacheInstance() fmt.Printf(\u0026#34;Cache instance: %p\\n\u0026#34;, cache) } fmt.Println(\u0026#34;\\n=== Atomic 示例 ===\u0026#34;) var atomicCounter int64 // 使用原子操作 for i := 0; i \u0026lt; 10; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j \u0026lt; 100; j++ { atomic.AddInt64(\u0026amp;atomicCounter, 1) } }() } wg.Wait() fmt.Printf(\u0026#34;Atomic counter value: %d\\n\u0026#34;, atomic.LoadInt64(\u0026amp;atomicCounter)) } 错误处理 # 基本错误处理 # package main import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;strconv\u0026#34; ) // 自定义错误类型 type ValidationError struct { Field string Message string } func (e ValidationError) Error() string { return fmt.Sprintf(\u0026#34;validation error in field \u0026#39;%s\u0026#39;: %s\u0026#34;, e.Field, e.Message) } // 返回错误的函数 func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New(\u0026#34;division by zero\u0026#34;) } return a / b, nil } // 使用自定义错误 func validateAge(age int) error { if age \u0026lt; 0 { return ValidationError{ Field: \u0026#34;age\u0026#34;, Message: \u0026#34;age cannot be negative\u0026#34;, } } if age \u0026gt; 150 { return ValidationError{ Field: \u0026#34;age\u0026#34;, Message: \u0026#34;age cannot be greater than 150\u0026#34;, } } return nil } // 错误包装 func parseAndValidateAge(ageStr string) error { age, err := strconv.Atoi(ageStr) if err != nil { return fmt.Errorf(\u0026#34;failed to parse age: %w\u0026#34;, err) } if err := validateAge(age); err != nil { return fmt.Errorf(\u0026#34;age validation failed: %w\u0026#34;, err) } return nil } func main() { fmt.Println(\u0026#34;=== 基本错误处理 ===\u0026#34;) // 处理除法错误 result, err := divide(10, 0) if err != nil { fmt.Printf(\u0026#34;Error: %v\\n\u0026#34;, err) } else { fmt.Printf(\u0026#34;Result: %.2f\\n\u0026#34;, result) } result, err = divide(10, 2) if err != nil { fmt.Printf(\u0026#34;Error: %v\\n\u0026#34;, err) } else { fmt.Printf(\u0026#34;Result: %.2f\\n\u0026#34;, result) } fmt.Println(\u0026#34;\\n=== 自定义错误 ===\u0026#34;) // 测试年龄验证 ages := []int{-5, 25, 200} for _, age := range ages { if err := validateAge(age); err != nil { fmt.Printf(\u0026#34;Age %d: %v\\n\u0026#34;, age, err) // 类型断言检查错误类型 if ve, ok := err.(ValidationError); ok { fmt.Printf(\u0026#34; Field: %s, Message: %s\\n\u0026#34;, ve.Field, ve.Message) } } else { fmt.Printf(\u0026#34;Age %d: valid\\n\u0026#34;, age) } } fmt.Println(\u0026#34;\\n=== 错误包装 ===\u0026#34;) // 测试错误包装 testCases := []string{\u0026#34;25\u0026#34;, \u0026#34;abc\u0026#34;, \u0026#34;-10\u0026#34;, \u0026#34;200\u0026#34;} for _, ageStr := range testCases { if err := parseAndValidateAge(ageStr); err != nil { fmt.Printf(\u0026#34;Input \u0026#39;%s\u0026#39;: %v\\n\u0026#34;, ageStr, err) // 检查是否包含特定错误 var ve ValidationError if errors.As(err, \u0026amp;ve) { fmt.Printf(\u0026#34; Contains ValidationError: %s\\n\u0026#34;, ve.Message) } } else { fmt.Printf(\u0026#34;Input \u0026#39;%s\u0026#39;: valid\\n\u0026#34;, ageStr) } } } Panic 和 Recover # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; ) // 可能发生 panic 的函数 func riskyFunction(x int) { if x == 0 { panic(\u0026#34;x cannot be zero\u0026#34;) } fmt.Printf(\u0026#34;Processing x = %d\\n\u0026#34;, x) } // 使用 recover 处理 panic func safeCall(x int) { defer func() { if r := recover(); r != nil { fmt.Printf(\u0026#34;Recovered from panic: %v\\n\u0026#34;, r) // 打印堆栈信息 buf := make([]byte, 1024) n := runtime.Stack(buf, false) fmt.Printf(\u0026#34;Stack trace:\\n%s\\n\u0026#34;, buf[:n]) } }() riskyFunction(x) } // 资源清理示例 func processFile(filename string) { fmt.Printf(\u0026#34;Opening file: %s\\n\u0026#34;, filename) defer func() { fmt.Printf(\u0026#34;Closing file: %s\\n\u0026#34;, filename) if r := recover(); r != nil { fmt.Printf(\u0026#34;Error processing file %s: %v\\n\u0026#34;, filename, r) } }() if filename == \u0026#34;bad.txt\u0026#34; { panic(\u0026#34;corrupted file\u0026#34;) } fmt.Printf(\u0026#34;Processing file: %s\\n\u0026#34;, filename) } func main() { fmt.Println(\u0026#34;=== Panic 和 Recover ===\u0026#34;) // 正常调用 safeCall(5) // 会触发 panic 的调用 safeCall(0) // 程序继续执行 fmt.Println(\u0026#34;Program continues...\u0026#34;) fmt.Println(\u0026#34;\\n=== 资源清理 ===\u0026#34;) files := []string{\u0026#34;good.txt\u0026#34;, \u0026#34;bad.txt\u0026#34;, \u0026#34;another.txt\u0026#34;} for _, file := range files { processFile(file) fmt.Println(\u0026#34;---\u0026#34;) } } 包管理和模块 # Go Modules # # 初始化模块 go mod init example.com/myproject # 添加依赖 go get github.com/gin-gonic/gin go get github.com/stretchr/testify@v1.7.0 # 查看依赖 go list -m all # 更新依赖 go get -u github.com/gin-gonic/gin # 移除未使用的依赖 go mod tidy # 下载依赖到本地缓存 go mod download # 验证依赖 go mod verify 创建和使用包 # // 文件: math/calculator.go package math import \u0026#34;errors\u0026#34; // Calculator 结构体 type Calculator struct { history []Operation } // Operation 表示一个计算操作 type Operation struct { Type string A, B float64 Result float64 } // New 创建新的计算器 func New() *Calculator { return \u0026amp;Calculator{ history: make([]Operation, 0), } } // Add 加法运算 func (c *Calculator) Add(a, b float64) float64 { result := a + b c.addToHistory(\u0026#34;add\u0026#34;, a, b, result) return result } // Subtract 减法运算 func (c *Calculator) Subtract(a, b float64) float64 { result := a - b c.addToHistory(\u0026#34;subtract\u0026#34;, a, b, result) return result } // Divide 除法运算 func (c *Calculator) Divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New(\u0026#34;division by zero\u0026#34;) } result := a / b c.addToHistory(\u0026#34;divide\u0026#34;, a, b, result) return result, nil } // History 获取计算历史 func (c *Calculator) History() []Operation { return c.history } // 私有方法 func (c *Calculator) addToHistory(op string, a, b, result float64) { c.history = append(c.history, Operation{ Type: op, A: a, B: b, Result: result, }) } // 文件: main.go package main import ( \u0026#34;fmt\u0026#34; \u0026#34;example.com/myproject/math\u0026#34; ) func main() { // 使用自定义包 calc := math.New() // 执行计算 sum := calc.Add(10, 5) fmt.Printf(\u0026#34;10 + 5 = %.2f\\n\u0026#34;, sum) diff := calc.Subtract(10, 3) fmt.Printf(\u0026#34;10 - 3 = %.2f\\n\u0026#34;, diff) quotient, err := calc.Divide(10, 2) if err != nil { fmt.Printf(\u0026#34;Error: %v\\n\u0026#34;, err) } else { fmt.Printf(\u0026#34;10 / 2 = %.2f\\n\u0026#34;, quotient) } // 查看历史 fmt.Println(\u0026#34;\\nCalculation History:\u0026#34;) for i, op := range calc.History() { fmt.Printf(\u0026#34;%d. %s: %.2f, %.2f = %.2f\\n\u0026#34;, i+1, op.Type, op.A, op.B, op.Result) } } 网络编程 # HTTP 服务器 # package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;strconv\u0026#34; \u0026#34;time\u0026#34; ) // User 结构体 type User struct { ID int `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` Email string `json:\u0026#34;email\u0026#34;` } // 模拟数据库 var users = []User{ {ID: 1, Name: \u0026#34;Alice\u0026#34;, Email: \u0026#34;alice@example.com\u0026#34;}, {ID: 2, Name: \u0026#34;Bob\u0026#34;, Email: \u0026#34;bob@example.com\u0026#34;}, } // 中间件：日志记录 func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() next(w, r) log.Printf(\u0026#34;%s %s %v\u0026#34;, r.Method, r.URL.Path, time.Since(start)) } } // 中间件：CORS func corsMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set(\u0026#34;Access-Control-Allow-Origin\u0026#34;, \u0026#34;*\u0026#34;) w.Header().Set(\u0026#34;Access-Control-Allow-Methods\u0026#34;, \u0026#34;GET, POST, PUT, DELETE\u0026#34;) w.Header().Set(\u0026#34;Access-Control-Allow-Headers\u0026#34;, \u0026#34;Content-Type\u0026#34;) if r.Method == \u0026#34;OPTIONS\u0026#34; { w.WriteHeader(http.StatusOK) return } next(w, r) } } // 处理器函数 func getUsersHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) json.NewEncoder(w).Encode(users) } func getUserHandler(w http.ResponseWriter, r *http.Request) { idStr := r.URL.Query().Get(\u0026#34;id\u0026#34;) if idStr == \u0026#34;\u0026#34; { http.Error(w, \u0026#34;Missing id parameter\u0026#34;, http.StatusBadRequest) return } id, err := strconv.Atoi(idStr) if err != nil { http.Error(w, \u0026#34;Invalid id parameter\u0026#34;, http.StatusBadRequest) return } for _, user := range users { if user.ID == id { w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) json.NewEncoder(w).Encode(user) return } } http.Error(w, \u0026#34;User not found\u0026#34;, http.StatusNotFound) } func createUserHandler(w http.ResponseWriter, r *http.Request) { if r.Method != \u0026#34;POST\u0026#34; { http.Error(w, \u0026#34;Method not allowed\u0026#34;, http.StatusMethodNotAllowed) return } var user User if err := json.NewDecoder(r.Body).Decode(\u0026amp;user); err != nil { http.Error(w, \u0026#34;Invalid JSON\u0026#34;, http.StatusBadRequest) return } // 生成新 ID user.ID = len(users) + 1 users = append(users, user) w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(user) } func main() { // 注册路由和中间件 http.HandleFunc(\u0026#34;/users\u0026#34;, corsMiddleware(loggingMiddleware(getUsersHandler))) http.HandleFunc(\u0026#34;/user\u0026#34;, corsMiddleware(loggingMiddleware(getUserHandler))) http.HandleFunc(\u0026#34;/user/create\u0026#34;, corsMiddleware(loggingMiddleware(createUserHandler))) // 静态文件服务 http.Handle(\u0026#34;/static/\u0026#34;, http.StripPrefix(\u0026#34;/static/\u0026#34;, http.FileServer(http.Dir(\u0026#34;./static/\u0026#34;)))) fmt.Println(\u0026#34;Server starting on :8080\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } HTTP 客户端 # package main import ( \u0026#34;bytes\u0026#34; \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; ) type User struct { ID int `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` Email string `json:\u0026#34;email\u0026#34;` } // HTTP 客户端封装 type APIClient struct { baseURL string httpClient *http.Client } func NewAPIClient(baseURL string) *APIClient { return \u0026amp;APIClient{ baseURL: baseURL, httpClient: \u0026amp;http.Client{ Timeout: 30 * time.Second, }, } } func (c *APIClient) GetUsers() ([]User, error) { resp, err := c.httpClient.Get(c.baseURL + \u0026#34;/users\u0026#34;) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf(\u0026#34;API error: %s\u0026#34;, resp.Status) } var users []User if err := json.NewDecoder(resp.Body).Decode(\u0026amp;users); err != nil { return nil, err } return users, nil } func (c *APIClient) GetUser(id int) (*User, error) { url := fmt.Sprintf(\u0026#34;%s/user?id=%d\u0026#34;, c.baseURL, id) resp, err := c.httpClient.Get(url) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf(\u0026#34;user not found\u0026#34;) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf(\u0026#34;API error: %s\u0026#34;, resp.Status) } var user User if err := json.NewDecoder(resp.Body).Decode(\u0026amp;user); err != nil { return nil, err } return \u0026amp;user, nil } func (c *APIClient) CreateUser(user User) (*User, error) { jsonData, err := json.Marshal(user) if err != nil { return nil, err } resp, err := c.httpClient.Post( c.baseURL+\u0026#34;/user/create\u0026#34;, \u0026#34;application/json\u0026#34;, bytes.NewBuffer(jsonData), ) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf(\u0026#34;API error: %s - %s\u0026#34;, resp.Status, string(body)) } var createdUser User if err := json.NewDecoder(resp.Body).Decode(\u0026amp;createdUser); err != nil { return nil, err } return \u0026amp;createdUser, nil } func main() { client := NewAPIClient(\u0026#34;http://localhost:8080\u0026#34;) // 获取所有用户 fmt.Println(\u0026#34;=== 获取所有用户 ===\u0026#34;) users, err := client.GetUsers() if err != nil { fmt.Printf(\u0026#34;Error: %v\\n\u0026#34;, err) return } for _, user := range users { fmt.Printf(\u0026#34;ID: %d, Name: %s, Email: %s\\n\u0026#34;, user.ID, user.Name, user.Email) } // 获取特定用户 fmt.Println(\u0026#34;\\n=== 获取用户 ID=1 ===\u0026#34;) user, err := client.GetUser(1) if err != nil { fmt.Printf(\u0026#34;Error: %v\\n\u0026#34;, err) } else { fmt.Printf(\u0026#34;User: %+v\\n\u0026#34;, user) } // 创建新用户 fmt.Println(\u0026#34;\\n=== 创建新用户 ===\u0026#34;) newUser := User{ Name: \u0026#34;Charlie\u0026#34;, Email: \u0026#34;charlie@example.com\u0026#34;, } createdUser, err := client.CreateUser(newUser) if err != nil { fmt.Printf(\u0026#34;Error: %v\\n\u0026#34;, err) } else { fmt.Printf(\u0026#34;Created user: %+v\\n\u0026#34;, createdUser) } } 测试 # 单元测试 # // 文件: calculator.go package calculator import \u0026#34;errors\u0026#34; type Calculator struct{} func (c Calculator) Add(a, b int) int { return a + b } func (c Calculator) Subtract(a, b int) int { return a - b } func (c Calculator) Multiply(a, b int) int { return a * b } func (c Calculator) Divide(a, b int) (int, error) { if b == 0 { return 0, errors.New(\u0026#34;division by zero\u0026#34;) } return a / b, nil } // 文件: calculator_test.go package calculator import ( \u0026#34;testing\u0026#34; ) func TestCalculator_Add(t *testing.T) { calc := Calculator{} tests := []struct { name string a, b int expected int }{ {\u0026#34;positive numbers\u0026#34;, 2, 3, 5}, {\u0026#34;negative numbers\u0026#34;, -2, -3, -5}, {\u0026#34;mixed numbers\u0026#34;, -2, 3, 1}, {\u0026#34;zero\u0026#34;, 0, 5, 5}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := calc.Add(tt.a, tt.b) if result != tt.expected { t.Errorf(\u0026#34;Add(%d, %d) = %d; expected %d\u0026#34;, tt.a, tt.b, result, tt.expected) } }) } } func TestCalculator_Divide(t *testing.T) { calc := Calculator{} t.Run(\u0026#34;normal division\u0026#34;, func(t *testing.T) { result, err := calc.Divide(10, 2) if err != nil { t.Errorf(\u0026#34;Unexpected error: %v\u0026#34;, err) } if result != 5 { t.Errorf(\u0026#34;Divide(10, 2) = %d; expected 5\u0026#34;, result) } }) t.Run(\u0026#34;division by zero\u0026#34;, func(t *testing.T) { _, err := calc.Divide(10, 0) if err == nil { t.Error(\u0026#34;Expected error for division by zero\u0026#34;) } }) } // 基准测试 func BenchmarkCalculator_Add(b *testing.B) { calc := Calculator{} for i := 0; i \u0026lt; b.N; i++ { calc.Add(100, 200) } } // 示例测试 func ExampleCalculator_Add() { calc := Calculator{} result := calc.Add(2, 3) fmt.Println(result) // Output: 5 } 集成测试 # // 文件: server_test.go package main import ( \u0026#34;bytes\u0026#34; \u0026#34;encoding/json\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;net/http/httptest\u0026#34; \u0026#34;testing\u0026#34; ) func TestGetUsersHandler(t *testing.T) { req, err := http.NewRequest(\u0026#34;GET\u0026#34;, \u0026#34;/users\u0026#34;, nil) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() handler := http.HandlerFunc(getUsersHandler) handler.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf(\u0026#34;handler returned wrong status code: got %v want %v\u0026#34;, status, http.StatusOK) } var users []User if err := json.Unmarshal(rr.Body.Bytes(), \u0026amp;users); err != nil { t.Errorf(\u0026#34;Could not parse response: %v\u0026#34;, err) } if len(users) == 0 { t.Error(\u0026#34;Expected at least one user\u0026#34;) } } func TestCreateUserHandler(t *testing.T) { user := User{ Name: \u0026#34;Test User\u0026#34;, Email: \u0026#34;test@example.com\u0026#34;, } jsonData, _ := json.Marshal(user) req, err := http.NewRequest(\u0026#34;POST\u0026#34;, \u0026#34;/user/create\u0026#34;, bytes.NewBuffer(jsonData)) if err != nil { t.Fatal(err) } req.Header.Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) rr := httptest.NewRecorder() handler := http.HandlerFunc(createUserHandler) handler.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusCreated { t.Errorf(\u0026#34;handler returned wrong status code: got %v want %v\u0026#34;, status, http.StatusCreated) } var createdUser User if err := json.Unmarshal(rr.Body.Bytes(), \u0026amp;createdUser); err != nil { t.Errorf(\u0026#34;Could not parse response: %v\u0026#34;, err) } if createdUser.Name != user.Name { t.Errorf(\u0026#34;Expected name %s, got %s\u0026#34;, user.Name, createdUser.Name) } } 最佳实践和性能优化 # 代码规范 # package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;time\u0026#34; ) // 1. 使用有意义的命名 type UserService struct { repository UserRepository logger Logger } type UserRepository interface { GetUser(ctx context.Context, id int) (*User, error) CreateUser(ctx context.Context, user *User) error } type Logger interface { Info(msg string, fields ...interface{}) Error(msg string, err error, fields ...interface{}) } // 2. 使用 context 进行超时控制 func (s *UserService) GetUserWithTimeout(id int) (*User, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() return s.repository.GetUser(ctx, id) } // 3. 错误处理最佳实践 func (s *UserService) ProcessUser(id int) error { user, err := s.GetUserWithTimeout(id) if err != nil { s.logger.Error(\u0026#34;failed to get user\u0026#34;, err, \u0026#34;user_id\u0026#34;, id) return fmt.Errorf(\u0026#34;processing user %d: %w\u0026#34;, id, err) } // 处理用户逻辑 s.logger.Info(\u0026#34;user processed successfully\u0026#34;, \u0026#34;user_id\u0026#34;, id, \u0026#34;user_name\u0026#34;, user.Name) return nil } // 4. 使用构造函数 func NewUserService(repo UserRepository, logger Logger) *UserService { return \u0026amp;UserService{ repository: repo, logger: logger, } } // 5. 资源清理 func (s *UserService) ProcessFile(filename string) error { file, err := os.Open(filename) if err != nil { return fmt.Errorf(\u0026#34;opening file %s: %w\u0026#34;, filename, err) } defer file.Close() // 确保资源被释放 // 处理文件 return nil } 性能优化技巧 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;sync\u0026#34; ) // 1. 使用对象池减少内存分配 var stringBuilderPool = sync.Pool{ New: func() interface{} { return \u0026amp;strings.Builder{} }, } func efficientStringConcat(strs []string) string { sb := stringBuilderPool.Get().(*strings.Builder) defer func() { sb.Reset() stringBuilderPool.Put(sb) }() for _, str := range strs { sb.WriteString(str) } return sb.String() } // 2. 预分配切片容量 func processLargeDataset(size int) []int { // 好的做法：预分配容量 result := make([]int, 0, size) for i := 0; i \u0026lt; size; i++ { result = append(result, i*2) } return result } // 3. 使用缓冲 channel func efficientProducerConsumer() { // 使用缓冲 channel 减少阻塞 ch := make(chan int, 100) // 生产者 go func() { defer close(ch) for i := 0; i \u0026lt; 1000; i++ { ch \u0026lt;- i } }() // 消费者 for value := range ch { // 处理数据 _ = value } } func main() { // 示例使用 strs := []string{\u0026#34;Hello\u0026#34;, \u0026#34; \u0026#34;, \u0026#34;World\u0026#34;, \u0026#34;!\u0026#34;} result := efficientStringConcat(strs) fmt.Println(result) data := processLargeDataset(1000) fmt.Printf(\u0026#34;Processed %d items\\n\u0026#34;, len(data)) efficientProducerConsumer() } 总结 # Go 语言的优势 # 简洁性: 语法简单，易于学习和维护 性能: 编译型语言，运行效率高 并发: 原生支持 goroutines 和 channels 工具链: 完善的开发工具和包管理 跨平台: 支持多种操作系统和架构 学习路径建议 # 基础语法: 掌握变量、类型、控制结构 面向对象: 理解结构体、方法、接口 并发编程: 学习 goroutines、channels、同步原语 标准库: 熟悉常用包（fmt、strings、time、net/http 等） 实战项目: 构建 Web 服务、CLI 工具、微服务 进阶方向 # Web 开发: Gin、Echo、Fiber 等框架 微服务: gRPC、服务发现、配置管理 云原生: Docker、Kubernetes、Prometheus 数据库: GORM、sqlx、Redis 客户端 性能优化: 内存管理、并发优化、性能分析 推荐资源 # 官方文档: golang.org Go Tour: tour.golang.org Effective Go: golang.org/doc/effective_go Go Blog: blog.golang.org Awesome Go: awesome-go.com 通过系统学习和实践，您将能够熟练使用 Go 语言开发高质量的软件项目。Go 语言的设计哲学是\u0026quot;少即是多\u0026quot;，专注于解决实际问题，这使得它成为现代软件开发的优秀选择。\n","date":"2022年12月2日","externalUrl":null,"permalink":"/posts/golang-docs/","section":"博客文章","summary":"\u003cp\u003eGo 语言是 Google 开发的现代编程语言，专为构建简单、可靠、高效的软件而设计。本指南将从基础概念开始，逐步深入到高级特性和实际应用，帮助您全面掌握 Go 语言的开发技能。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003eGo 语言概述 \n    \u003cdiv id=\"go-语言概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#go-%e8%af%ad%e8%a8%80%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e什么是 Go \n    \u003cdiv id=\"什么是-go\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-go\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eGo（也称为 Golang）是一种开源的编程语言，由 Google 在 2009 年发布。它具有以下核心特性：\u003c/p\u003e","title":"Go 语言完整实战指南","type":"posts"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/golang/","section":"Tags","summary":"","title":"Golang","type":"tags"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/","section":"Categories","summary":"","title":"编程语言","type":"categories"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/","section":"Tags","summary":"","title":"编程语言","type":"tags"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/","section":"Tags","summary":"","title":"并发编程","type":"tags"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/categories/%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/","section":"Categories","summary":"","title":"开发指南","type":"categories"},{"content":"","date":"2022年12月2日","externalUrl":null,"permalink":"/tags/%E7%B3%BB%E7%BB%9F%E7%BC%96%E7%A8%8B/","section":"Tags","summary":"","title":"系统编程","type":"tags"},{"content":"","date":"2021年10月12日","externalUrl":null,"permalink":"/tags/kube-vip/","section":"Tags","summary":"","title":"Kube-Vip","type":"tags"},{"content":"","date":"2021年10月12日","externalUrl":null,"permalink":"/tags/loadbalance-/","section":"Tags","summary":"","title":"Loadbalance ","type":"tags"},{"content":"","date":"2021年10月12日","externalUrl":null,"permalink":"/tags/rke/","section":"Tags","summary":"","title":"Rke","type":"tags"},{"content":" 背景 # Kube-Vip 最初是为 Kubernetes 控制平面提供 HA 解决方案而创建的，随着时间的推移，它已经发展为将相同的功能合并到 Kubernetes 的 LoadBalancer 类型的 Service 中。Kube-Vip 特点 如下：\nVIP 地址可以是 IPv4 或 IPv6 带有 ARP（第2层）或 BGP（第3层）的控制平面 使用领导选举或 raft 控制平面 带有 kubeadm（静态 Pod）的控制平面 HA 带有 K3s/和其他（DaemonSets）的控制平面 HA 使用 ARP 领导者选举的 Service LoadBalancer（第 2 层） 通过 BGP 使用多个节点的 Service LoadBalancer 每个命名空间或全局的 Service LoadBalancer 地址池 Service LoadBalancer 地址通过 UPNP 暴露给网关 集群 master 节点一般不会很多，kube-vip 只需在 k8s 中的控制平面部署。这里使用方法采用 静态 pod + ARP 来简单实现。在大规模的场景下可以尝试使用 BGP ，这需要相应的 BGP 服务来做支撑。\n集群角色说明 # IP地址 主机名称 集群角色 192.168.66.90 master01 controlplane、worker、etcd、kube-vip 192.168.66.91 master02 controlplane、worker、etcd、kube-vip 192.168.66.92 master03 controlplane、worker、etcd、kube-vip 192.168.66.93 node01 worker 192.168.66.101 kube-vip.treesir.pub kube-vip 虚拟 ip 地址 使用 RKE 部署集群 # 部署前各主机的优化工作 # 节点初始化，基础优化\n参考链接\n更改主机名称，（各节点执行）\nhostnamectl set-hostname xxxx 将各主机名称写入至各 /etc/hosts 文件中 （各节点执行）\necho \u0026#34;192.168.66.90 master01 192.168.66.91 master02 192.168.66.92 master03 192.168.66.93 node01\u0026#34; \u0026gt;\u0026gt; /etc/hosts 安装 rke \u0026amp; kubectl （主控节点执行）\nwget -O /usr/local/bin/rke https://github.com/rancher/rke/releases/download/v1.3.1/rke_linux-amd64 wget -O /usr/local/bin/kubectl \u0026#34;https://dl.k8s.io/release/`curl -L -s https://dl.k8s.io/release/stable.txt`/bin/linux/amd64/kubectl\u0026#34; chmod a+x /usr/local/bin/* # 添加可执行权限 rke 集群所需用户 （各节点执行）\n因 Centos/Redhat 发行版本下，rke 不支持使用 rook，这里需要单独创建一个 rke 用户，并加入至 docker 组，让其可以执行 docker 命令。\nuseradd rke -G docker echo \u0026#39;123456\u0026#39;|passwd rke --stdin # 初始化 rke 用户密码为 123456 配置 rke 支持使用免密钥管理（主控节点执行）\nyum install -y sshpass su - rke # 切换至 rke 用户，生成公私钥 ssh-keygen # 一路回车，生成 for i in `seq 1 3`;do sshpass -p \u0026#39;123456\u0026#39; ssh-copy-id -o StrictHostKeyChecking=no master0\u0026#34;$i\u0026#34;;done \\ \u0026amp;\u0026amp; sshpass -p \u0026#39;123456\u0026#39; ssh-copy-id -o StrictHostKeyChecking=no node01 配置 kubectl 命令补全功能（主控节点执行）\necho \u0026#39;source \u0026lt;(kubectl completion bash)\u0026#39; \u0026gt;\u0026gt; /etc/profile source /etc/profile 部署 rke 集群 # 创建存放 静态 pod 文件夹 /etc/kubernetes/manifest/ ;（各节点执行）\nmkdir -p /etc/kubernetes/manifest/ 创建 rke 集群配置文件 （主控节点执行）\n在 192.168.66.90 节点中切换至 rke 用户，生成集群描述配置文件 cluster.yml。\n#### #### RKE Kubernetes 安装模板 #### nodes: # master01 配置 - address: 192.168.66.90 user: rke role: - controlplane - etcd - worker ssh_key_path: ~/.ssh/id_rsa hostname_override: master01 port: 22 # master02 配置 - address: 192.168.66.91 user: rke role: - worker - etcd - controlplane ssh_key_path: ~/.ssh/id_rsa hostname_override: master02 port: 22 # master03 配置 - address: 192.168.66.92 user: rke role: - worker - etcd - controlplane hostname_override: master03 ssh_key_path: ~/.ssh/id_rsa # node01 配置 - address: 192.168.66.93 user: rke role: - worker ssh_key_path: ~/.ssh/id_rsa hostname_override: node01 port: 22 kubernetes_version: v1.21.5-rancher1-1 # 私有仓库 ## 当设置`is_default: true`后，构建集群时会自动在配置的私有仓库中拉取镜像 ## 如果使用的是DockerHub镜像仓库，则可以省略`url`或将其设置为`docker.io` ## 如果使用内部公开仓库，则可以不用设置用户名和密码 private_registries: - url: idocker.io user: admin password: 123456 is_default: true services: etcd: # 开启自动备份 ## rke版本小于0.2.x或rancher版本小于v2.2.0时使用 snapshot: true creation: 5m0s retention: 24h # 扩展参数 extra_args: auto-compaction-retention: 240 #(单位小时) quota-backend-bytes: \u0026#39;6442450944\u0026#39; kube-api: # cluster_ip范围 ## 这必须与kube-controller中的service_cluster_ip_range匹配 service_cluster_ip_range: 10.43.0.0/16 # NodePort映射的端口范围 service_node_port_range: 30000-32767 # Pod安全策略 pod_security_policy: false # kubernetes API server扩展参数 ## 这些参数将会替换默认值 extra_args: watch-cache: true default-watch-cache-size: 1500 # 事件保留时间，默认1小时 event-ttl: 1h0m0s # 默认值400，设置0为不限制，一般来说，每25~30个Pod有15个并行 max-requests-inflight: 800 # 默认值200，设置0为不限制 max-mutating-requests-inflight: 400 # kubelet操作超时，默认5s kubelet-timeout: 5s # 启用审计日志到标准输出 audit-log-path: \u0026#34;-\u0026#34; # 增加删除workers的数量 delete-collection-workers: 3 # 将日志输出的级别设置为debug模式 v: 4 # Rancher 2用户注意事项：如果在创建Rancher Launched Kubernetes时使用配置文件配置集群，则`kube_controller`服务名称应仅包含下划线。这仅适用于Rancher v2.0.5和v2.0.6。 kube-controller: # Pods_ip范围 cluster_cidr: 10.42.0.0/16 # cluster_ip范围 ## 这必须与kube-api中的service_cluster_ip_range相同 service_cluster_ip_range: 10.43.0.0/16 extra_args: # 修改每个节点子网大小(cidr掩码长度)，默认为24，可用IP为254个；23，可用IP为510个；22，可用IP为1022个； node-cidr-mask-size: \u0026#39;22\u0026#39; # 控制器定时与节点通信以检查通信是否正常，周期默认5s node-monitor-period: \u0026#39;5s\u0026#39; ## 当节点通信失败后，再等一段时间kubernetes判定节点为notready状态。 ## 这个时间段必须是kubelet的nodeStatusUpdateFrequency(默认10s)的整数倍， ## 其中N表示允许kubelet同步节点状态的重试次数，默认40s。 node-monitor-grace-period: \u0026#39;20s\u0026#39; ## 再持续通信失败一段时间后，kubernetes判定节点为unhealthy状态，默认1m0s。 node-startup-grace-period: \u0026#39;30s\u0026#39; ## 再持续失联一段时间，kubernetes开始迁移失联节点的Pod，默认5m0s。 pod-eviction-timeout: \u0026#39;1m\u0026#39; # 默认5. 同时同步的deployment的数量。 concurrent-deployment-syncs: 5 # 默认5. 同时同步的endpoint的数量。 concurrent-endpoint-syncs: 5 # 默认20. 同时同步的垃圾收集器工作器的数量。 concurrent-gc-syncs: 20 # 默认10. 同时同步的命名空间的数量。 concurrent-namespace-syncs: 10 # 默认5. 同时同步的副本集的数量。 concurrent-replicaset-syncs: 5 # 默认5m0s. 同时同步的资源配额数。（新版本中已弃用） # concurrent-resource-quota-syncs: 5m0s # 默认1. 同时同步的服务数。 concurrent-service-syncs: 1 # 默认5. 同时同步的服务帐户令牌数。 concurrent-serviceaccount-token-syncs: 5 # 默认5. 同时同步的复制控制器的数量 #concurrent-rc-syncs: 5 # 默认30s. 同步deployment的周期。 deployment-controller-sync-period: 30s # 默认15s。同步PV和PVC的周期。 pvclaimbinder-sync-period: 15s kubelet: # 集群搜索域 cluster_domain: cluster.local # 内部DNS服务器地址 cluster_dns_server: 10.43.0.254 # 禁用swap fail_swap_on: false # 扩展变量 extra_args: # 支持静态Pod。在主机/etc/kubernetes/目录下创建manifest目录，Pod YAML文件放在/etc/kubernetes/manifest/目录下 pod-manifest-path: \u0026#34;/etc/kubernetes/manifest/\u0026#34; # 指定pause镜像 pod-infra-container-image: \u0026#39;rancher/pause:3.1\u0026#39; # 传递给网络插件的MTU值，以覆盖默认值，设置为0(零)则使用默认的1460 network-plugin-mtu: \u0026#39;1500\u0026#39; # 修改节点最大Pod数量 max-pods: \u0026#34;250\u0026#34; # 密文和配置映射同步时间，默认1分钟 sync-frequency: \u0026#39;3s\u0026#39; # Kubelet进程可以打开的文件数（默认1000000）,根据节点配置情况调整 max-open-files: \u0026#39;2000000\u0026#39; # 与apiserver会话时的并发数，默认是10 kube-api-burst: \u0026#39;30\u0026#39; # 与apiserver会话时的 QPS,默认是5，QPS = 并发量/平均响应时间 kube-api-qps: \u0026#39;15\u0026#39; # kubelet默认一次拉取一个镜像，设置为false可以同时拉取多个镜像， # 前提是存储驱动要为overlay2，对应的Dokcer也需要增加下载并发数，参考[docker配置](/rancher2x/install-prepare/best-practices/docker/) serialize-image-pulls: \u0026#39;false\u0026#39; # 拉取镜像的最大并发数，registry-burst不能超过registry-qps ， # 仅当registry-qps大于0(零)时生效，(默认10)。如果registry-qps为0则不限制(默认5)。 registry-burst: \u0026#39;10\u0026#39; registry-qps: \u0026#39;0\u0026#39; cgroups-per-qos: \u0026#39;true\u0026#39; cgroup-driver: \u0026#39;cgroupfs\u0026#39; # 节点资源预留 enforce-node-allocatable: \u0026#39;pods\u0026#39; system-reserved: \u0026#39;cpu=0.25,memory=200Mi\u0026#39; kube-reserved: \u0026#39;cpu=0.25,memory=1500Mi\u0026#39; # POD驱逐，这个参数只支持内存和磁盘。 ## 硬驱逐阈值 ### 当节点上的可用资源降至保留值以下时，就会触发强制驱逐。强制驱逐会强制kill掉POD，不会等POD自动退出。 eviction-hard: \u0026#39;memory.available\u0026lt;300Mi,nodefs.available\u0026lt;10%,imagefs.available\u0026lt;15%,nodefs.inodesFree\u0026lt;5%\u0026#39; ## 软驱逐阈值 ### 以下四个参数配套使用，当节点上的可用资源少于这个值时但大于硬驱逐阈值时候，会等待eviction-soft-grace-period设置的时长； ### 等待中每10s检查一次，当最后一次检查还触发了软驱逐阈值就会开始驱逐，驱逐不会直接Kill POD，先发送停止信号给POD，然后等待eviction-max-pod-grace-period设置的时长； ### 在eviction-max-pod-grace-period时长之后，如果POD还未退出则发送强制kill POD\u0026#34; # 指定kubelet多长时间向master发布一次节点状态。注意: 它必须与kube-controller中的nodeMonitorGracePeriod一起协调工作。(默认 10s) node-status-update-frequency: 10s # 设置cAdvisor全局的采集行为的时间间隔，主要通过内核事件来发现新容器的产生。默认1m0s global-housekeeping-interval: 1m0s # 每个已发现的容器的数据采集频率。默认10s housekeeping-interval: 10s # 所有运行时请求的超时，除了长时间运行的 pull, logs, exec and attach。超时后，kubelet将取消请求，抛出错误，然后重试。(默认2m0s) runtime-request-timeout: 2m0s # 指定kubelet计算和缓存所有pod和卷的卷磁盘使用量的间隔。默认为1m0s volume-stats-agg-period: 1m0s # 可以选择定义额外的卷绑定到服务 #extra_binds: # - \u0026#34;/usr/libexec/kubernetes/kubelet-plugins:/usr/libexec/kubernetes/kubelet-plugins\u0026#34; # - \u0026#34;/etc/iscsi:/etc/iscsi\u0026#34; # - \u0026#34;/sbin/iscsiadm:/sbin/iscsiadm\u0026#34; kubeproxy: extra_args: # 默认使用iptables进行数据转发，如果要启用ipvs，则此处设置为`ipvs` proxy-mode: \u0026#34;ipvs\u0026#34; # 与kubernetes apiserver通信并发数,默认10 kube-api-burst: 20 # 与kubernetes apiserver通信时使用QPS，默认值5，QPS=并发量/平均响应时间 kube-api-qps: 10 extra_binds: scheduler: extra_args: {} extra_binds: [] extra_env: [] # 目前，只支持x509验证 ## 您可以选择创建额外的SAN(主机名或IP)以添加到API服务器PKI证书。 ## 如果要为control plane servers使用负载均衡器，这很有用。 authentication: strategy: \u0026#34;x509|webhook\u0026#34; webhook: # config_file: \u0026#34;....\u0026#34; cache_timeout: 5s sans: # 此处配置备用域名或IP，当主域名或者IP无法访问时，可通过备用域名或IP访问 - \u0026#34;192.168.66.101\u0026#34; - \u0026#34;kube-vip.treesir.pub\u0026#34; # Kubernetes认证模式 ## Use `mode: rbac` 启用 RBAC ## Use `mode: none` 禁用 认证 authorization: mode: rbac # 如果要设置Kubernetes云提供商，需要指定名称和配置，非云主机则留空； cloud_provider: # Add-ons是通过kubernetes jobs来部署。 在超时后，RKE将放弃重试获取job状态。以秒为单位。 addon_job_timeout: 30 # 有几个网络插件可以选择：`flannel、canal、calico`，Rancher2默认canal network: # rke v1.0.4+ 可用，如果选择canal网络驱动，需要设置mtu为1450 mtu: 1450 plugin: calico # 目前只支持nginx ingress controller ## 可以设置`provider: none`来禁用ingress controller ingress: provider: nginx node_selector: ingress: yes options: map-hash-bucket-size: \u0026#34;1024\u0026#34; ssl-protocols: SSLv2 # 配置dns上游dns服务器 ## 可用rke版本 v0.2.0 dns: provider: coredns upstreamnameservers: - 192.168.66.2 - 223.5.5.5 node_selector: dns: yes rke 启动集群\nrke up --config ./cluster.yml 部署集群，使用 kubectl 进行管理\nmkdir ~/.kube/ ln -s `echo $PWD`/kube_config_cluster.yaml ~/.kube/config kubectl version # 看到有下面输出，表示集群搭建成功 Client Version: version.Info{Major:\u0026#34;1\u0026#34;, Minor:\u0026#34;22\u0026#34;, GitVersion:\u0026#34;v1.22.2\u0026#34;, GitCommit:\u0026#34;8b5a19147530eaac9476b0ab82980b4088bbc1b2\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, BuildDate:\u0026#34;2021-09-15T21:38:50Z\u0026#34;, GoVersion:\u0026#34;go1.16.8\u0026#34;, Compiler:\u0026#34;gc\u0026#34;, Platform:\u0026#34;linux/amd64\u0026#34;} Server Version: version.Info{Major:\u0026#34;1\u0026#34;, Minor:\u0026#34;21\u0026#34;, GitVersion:\u0026#34;v1.21.5\u0026#34;, GitCommit:\u0026#34;aea7bbadd2fc0cd689de94a54e5b7b758869d691\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, BuildDate:\u0026#34;2021-09-15T21:04:16Z\u0026#34;, GoVersion:\u0026#34;go1.16.8\u0026#34;, Compiler:\u0026#34;gc\u0026#34;, Platform:\u0026#34;linux/amd64\u0026#34;} 配置 给 node 添加 lable，让 coredns \u0026amp; nginx ingress 在节点中启动\nkubectl label node node01 dns=yes \\ \u0026amp;\u0026amp; kubectl label node master03 dns=yes kubectl label node node01 ingress=yes \\ \u0026amp;\u0026amp; kubectl label node master03 ingress=yes 集群添加 Kube-Vip 组件 # 将 rke 生成的 kube_config_cluster.yaml 配置文件，Copy 至其他 master 节点中 (master01 节点执行)\n这一步的目的是： kube-vip pod 中需要访问自身节点中 apiserver ，来检测当前节点是否正常。\nRKE_WORKSPACE=/home/rke for i in `seq 1 3`;do scp -o StrictHostKeyChecking=no \u0026#34;${RKE_WORKSPACE}\u0026#34;/kube_config_cluster.yaml master0\u0026#34;$i\u0026#34;:/etc/kubernetes/admin.conf;done # Copy rke 集群配置文件至 rke 家目录中 更改各节点的 /etc/kubernetes/admin.conf 配置文件 (各 master 节点执行)\n更改当前 node 所连接的 apiserver 为 当前节点自身；示例替换 192.168.66.90 根据实际情况进行更改此地址\nsed -i \u0026#34;s#192.168.66.90#$HOSTNAME#g\u0026#34; /etc/kubernetes/admin.conf 生成 kube-vip pod yaml 至 静态 pod 文件夹下 (各 master 节点执行)\n绑定网卡为 eth0， VIP 地址设置 192.168.66.101。更具实际情况更改使用。\nINTERFACE=eth0 # 绑定的网卡 VIP=192.168.66.101 # apiserver VIP 地址 VIP_HOSTNAME=\u0026#39;kube-vip.treesir.pub\u0026#39; # VIP 域名 IMAGE=\u0026#39;ghcr.io/kube-vip/kube-vip:v0.3.8\u0026#39; # kubevip 使用镜像 cat \u0026gt; /etc/kubernetes/manifest/kube-vip.yaml \u0026lt;\u0026lt; EOF apiVersion: v1 kind: Pod metadata: creationTimestamp: null name: kube-vip namespace: kube-system spec: containers: - args: - manager env: - name: vip_arp value: \u0026#34;true\u0026#34; - name: vip_interface value: ${INTERFACE} - name: port value: \u0026#34;6443\u0026#34; - name: vip_cidr value: \u0026#34;32\u0026#34; - name: cp_enable value: \u0026#34;true\u0026#34; - name: cp_namespace value: kube-system - name: vip_ddns value: \u0026#34;false\u0026#34; - name: svc_enable value: \u0026#34;true\u0026#34; - name: vip_leaderelection value: \u0026#34;true\u0026#34; - name: vip_leaseduration value: \u0026#34;5\u0026#34; - name: vip_renewdeadline value: \u0026#34;3\u0026#34; - name: vip_retryperiod value: \u0026#34;1\u0026#34; - name: vip_address value: ${VIP} image: \u0026#34;${IMAGE}\u0026#34; imagePullPolicy: Always name: kube-vip resources: {} securityContext: capabilities: add: - NET_ADMIN - NET_RAW - SYS_TIME volumeMounts: - mountPath: /etc/kubernetes/admin.conf name: kubeconfig - mountPath: /etc/hosts name: hosts readOnly: true hostNetwork: true volumes: - hostPath: path: /etc/kubernetes/admin.conf name: kubeconfig - name: hosts hostPath: path: /etc/hosts type: File status: {} EOF 也可以使用下面这种方法进行渲染生成， yaml 文件，再对生成的 pod 文件进行修饰处理。\nINTERFACE=eth0 # 绑定的网卡 VIP=192.168.66.101 # apiserver VIP 地址 docker run -it --net=host --rm --name vip plndr/kube-vip:v0.3.8 \\ \u0026#34;manifest\u0026#34; \u0026#34;pod\u0026#34; \u0026#34;--interface\u0026#34; \\ \u0026#34;$INTERFACE\u0026#34; \u0026#34;--vip\u0026#34; \u0026#34;$VIP\u0026#34; \\ \u0026#34;--controlplane\u0026#34; \u0026#34;--services\u0026#34; \\ \u0026#34;--arp\u0026#34; \u0026#34;--leaderElection\u0026#34; | tee /etc/kubernetes/manifest/kube-vip.yaml 检查容器日志启动情况\ndocker ps|grep kube-vip|grep -v \u0026#39;pause\u0026#39;|awk \u0026#39;{print $1}\u0026#39;|xargs -I {} docker logs -f -n 100 {} master01\nmaster02\nmaster03\n从上述 kube-vip pod 的日志中，可以看到 集群已正常启动，且 leader 为 master01\nKube-Vip 与客户端 kubectl 集成 # kube-vip 部署好之后，客户端 kubectl 使用的配置文件中 apiserver 地址，默认还是 rke 主控节点的地址，如主控节点此时 down 机，客户端 kubectl 将不能继续访问 apiserver，更改为 vip 的域名即可解决此问题。对于 rke 集群内部 使用的 apiserver 地址，使用的是 kubernetes.default.svc.cluster.local 其在内部就实现了 高可用性。\n添加 vip 域名kube-vip.treesir.pub 至 hosts 文件中\n如有 DNS 基础设施服务器，添加一条 A 记录也可以，不想用域名的话，可以直接写 192.168.66.101。如出现 x509 证书错误，确认是否在 rke 的 authentication 配置项中，将 vip 地址信息，加入至 证书链中。\necho \u0026#39;192.168.66.101 kube-vip.treesir.pub\u0026#39; \u0026gt;\u0026gt; /etc/hosts 更改 kubectl ~/.kube/config 配置文件\nsed -i \u0026#34;s#$HOSTNAME#kube-vip.treesir.pub#g\u0026#34; ~/.kube/config 使用 vip 地址正常访问到集群信息\n测试 Kube-Vip 的高可用性 # 测试高可用，由于使用 rke 部署集群，所有的组件多是在 docker 中，那么我们只需要将 docker 服务进行关停，就可以模拟 节点 down 机。\n模拟 master01 节点 down 机\nsystemctl disable docker \\ \u0026amp;\u0026amp; service docker stop \\ \u0026amp;\u0026amp; reboot # 未了防止 docker 容器未正常停止，我们多系统进行一次重启，docker 不随系统进行启动，达到目的。 观察其他节点 kube-vip 日志情况\ndocker ps|grep kube-vip|grep -v \u0026#39;pause\u0026#39;|awk \u0026#39;{print $1}\u0026#39;|xargs -I {} docker logs -f -n 100 {} 可以看到 leader 从 master01 变更为了 master03，切换动作在秒级内完成，用户基本无感知。\n测试访问集群\n测试集群访问，没有收到丝毫的影响\nKube-Vip 作为 Loadbalance # 在将 Kube-Vip 作为 Loadbalance 资源对象进行使用时，需要部署一个 kube-vip-cloud-provider，官方 项目地址\n部署 kube-vip-cloud-provider\nkubectl apply -f https://raw.githubusercontent.com/kube-vip/kube-vip-cloud-provider/main/manifest/kube-vip-cloud-controller.yaml 创建 IP 地址池，kubevip-cm.yaml 配置文件\nkube-vip Loadbalance 的地址池，可以针对 当个命名空间 (cidr-\u0026lt;namespace\u0026gt;/range-\u0026lt;namespace\u0026gt;) 和全局 (cidr-global/range-global) 进行设置，支持方式可以通过 子网掩码 \u0026amp; 地址范围，具体可以参考 此文档\ncat \u0026lt;\u0026lt; EOF |tr -s \u0026#34;n\u0026#34; | tee kubevip-cm.yaml | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: kubevip namespace: kube-system data: range-global: 192.168.66.210-192.168.66.219 EOF 上述资源清单中我 允许 Loadbalance 在未指定 loadBalancerIP 时，使用 192.168.66.210 ~ 192.168.66.219 10 个 IP 地址。\n创建 nginx-deployment.yaml 资源清单\ncat \u0026lt;\u0026lt; EOF |tr -s \u0026#34;n\u0026#34; | tee nginx-deployment.yaml | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx resources: limits: memory: \u0026#34;128Mi\u0026#34; cpu: \u0026#34;500m\u0026#34; ports: - containerPort: 80 EOF kubectl get po --watch # 等待 pod 启动完成 测试将 nginx pod 已 LoadBalancer service 类型进行暴露\n下面资源清单文件中不指定 loadBalancerIP 的话，默认使用 configmap 中 range-global 地址池\ncat \u0026lt;\u0026lt; EOF |tr -s \u0026#34;n\u0026#34; | tee nginx-ingress-lb | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: nginx-lb spec: selector: app: nginx ports: - port: 80 targetPort: 80 type: LoadBalancer EOF kubectl get svc -o wide 从上图中，测试使用 LoadBalancer 自动获取到的 vip 地址，来访问 pod，可以看到链路是正常可以走通的。\n查看 kube-vip leader 日志\n从上图中，可以看到 kube-vip leader 新增了刚才，nginx-lb LoadBalancer 资源对象自动获取到的 vip 地址，并将其绑定在了自己的 eth0 接口中。\n除了可以使用 地址池之外，还可以将 loadBalancerIP 写成静态的，方法如下\napiVersion: v1 kind: Service metadata: name: nginx-lb spec: selector: app: nginx ports: - port: 80 targetPort: 80 type: LoadBalancer loadBalancerIP: \u0026#34;192.168.66.101\u0026#34; 验证 kube-vip LoadBalancer 流量特征 # kube-vip 流量会将未匹配的流量，全路由给 leader ，匹配到的流量将转发给对应的 service。这样说可能有点抽象，这里进行实际验证一下。这里就已刚才上面 nginx-lb，自动获取的 192.168.66.210 vip 地址做示例。\n验证无法匹配的流量\n这里我们使用 ssh 进行远程验证，ssh 默认工作在 22 端口，集群中目前 我们是没有匹配的 service 规则。\nssh root@192.168.66.210 从图中，我们可以验证流量是给了 kube-vip 的 leader 上的 sshd，但是还不能确定。我们让 kube-vip leader 主动发生切换动作，再远程来看看。\n关闭 master03 中的 kube-vip\n把对应节点中的 /etc/kubernetes/manifest/ 下资源清单给移走，kubelet 会自动删除对应 pod。\nmv /etc/kubernetes/manifest/kube-vip.yaml /tmp/ 现在变成 leader 变成 master02 了 再远程一下看看。\n验证匹配的流量\n这里将 LoadBalancer service 中的 端口改为 22，同样已 ssh 的端口进行测试\ncat \u0026lt;\u0026lt; EOF |tr -s \u0026#34;n\u0026#34; | tee nginx-ingress-lb | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: nginx-lb spec: selector: app: nginx ports: - port: 22 # 改成 ssh port targetPort: 80 type: LoadBalancer EOF 参考文档 # https://www.treesir.pub/post/kube-vip-deploy-ha-k8s-cluster/\nhttps://kube-vip.io/usage/on-prem/\n总结 # 在以前的情况下，实现 HA 需要部署外部的服务来支持，比如：nginx、haproxy、keepalived、lvs ； 通过 kube-vip 在没有其他额外的节点的情况下可以使用简单的配置实现集群外部访问 apiserver 的高可用， 并且能够很方便的对接 Kubernetes 的 LoadBalancer Service，如果我们将 ingress service 绑定在 LoadBalancer vip 中，就实现了外部流量访问的高可用性。在上生产前还是需要针对应用场景进行一下性能测试，在某些应用场景下没有专门的负载均衡器功能那么强大，且目前自身暂还不支持流量的负载功能。\n","date":"2021年10月12日","externalUrl":null,"permalink":"/posts/rke-kubevip-loadbalance/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e背景 \n    \u003cdiv id=\"背景\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%83%8c%e6%99%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003eKube-Vip\u003c/code\u003e 最初是为 Kubernetes 控制平面提供 HA 解决方案而创建的，随着时间的推移，它已经发展为将相同的功能合并到 Kubernetes 的 LoadBalancer 类型的 Service 中。Kube-Vip \u003ccode\u003e特点\u003c/code\u003e 如下：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eVIP 地址可以是 IPv4 或 IPv6\u003c/li\u003e\n\u003cli\u003e带有 ARP（第2层）或 BGP（第3层）的控制平面\u003c/li\u003e\n\u003cli\u003e使用领导选举或 \u003ccode\u003eraft\u003c/code\u003e 控制平面\u003c/li\u003e\n\u003cli\u003e带有 kubeadm（静态 Pod）的控制平面 HA\u003c/li\u003e\n\u003cli\u003e带有 K3s/和其他（DaemonSets）的控制平面 HA\u003c/li\u003e\n\u003cli\u003e使用 ARP 领导者选举的 Service LoadBalancer（第 2 层）\u003c/li\u003e\n\u003cli\u003e通过 BGP 使用多个节点的 Service LoadBalancer\u003c/li\u003e\n\u003cli\u003e每个命名空间或全局的 Service LoadBalancer 地址池\u003c/li\u003e\n\u003cli\u003eService LoadBalancer 地址通过 UPNP 暴露给网关\u003c/li\u003e\n\u003c/ul\u003e\u003c/blockquote\u003e\n\u003cp\u003e集群 master 节点一般不会很多，kube-vip 只需在 k8s 中的控制平面部署。这里使用方法采用 \u003ccode\u003e静态 pod\u003c/code\u003e + \u003ccode\u003eARP  \u003c/code\u003e来简单实现。在大规模的场景下可以尝试使用 BGP ，这需要相应的 BGP 服务来做支撑。\u003c/p\u003e","title":"Rke 集群集成 Kube-Vip 实现 Loadbalance Service 资源的使用","type":"posts"},{"content":"","date":"2021年9月25日","externalUrl":null,"permalink":"/tags/coredns/","section":"Tags","summary":"","title":"Coredns","type":"tags"},{"content":"CoreDNS 是现代化的 DNS 服务器，以其插件化架构、高性能和易配置性成为 Kubernetes 默认 DNS 解决方案。本指南将从基础部署到企业级应用，全面介绍 CoreDNS 的部署、配置、优化和管理。\nCoreDNS 概述 # 什么是 CoreDNS # CoreDNS 是用 Go 语言编写的现代化 DNS 服务器，具有以下核心特性：\n核心优势 # 插件化架构: 基于 Caddy 框架，支持丰富的插件生态 云原生设计: Kubernetes 1.13+ 默认 DNS 服务器 高性能: Go 语言编写，支持高并发处理 配置简单: 使用 Corefile DSL 语法，易于理解和维护 服务发现: 支持多种服务发现机制 可观测性: 内置监控和日志功能 应用场景 # graph TD A[\"CoreDNS 应用场景\"] --\u003e B[\"企业内网 DNS\"] A --\u003e C[\"Kubernetes 集群 DNS\"] A --\u003e D[\"服务发现\"] A --\u003e E[\"负载均衡\"] A --\u003e F[\"DNS 代理/转发\"] A --\u003e G[\"广告拦截\"] A --\u003e H[\"安全过滤\"] B --\u003e B1[\"内网域名解析\"] B --\u003e B2[\"主机名管理\"] B --\u003e B3[\"服务注册\"] C --\u003e C1[\"Pod 域名解析\"] C --\u003e C2[\"Service 发现\"] C --\u003e C3[\"跨命名空间通信\"] D --\u003e D1[\"微服务注册\"] D --\u003e D2[\"动态配置\"] D --\u003e D3[\"健康检查\"] 插件生态系统 # CoreDNS 的强大之处在于其丰富的插件系统：\n核心插件 # 插件类别 插件名称 功能描述 基础功能 hosts 静态主机记录 file 区域文件解析 forward DNS 转发 cache DNS 缓存 服务发现 kubernetes K8s 服务发现 etcd etcd 服务发现 consul Consul 服务发现 负载均衡 loadbalance 负载均衡 health 健康检查 监控日志 log 访问日志 metrics Prometheus 指标 trace 链路追踪 安全功能 acl 访问控制 dnssec DNSSEC 支持 blocklist 域名黑名单 环境准备 # 系统要求 # # 支持的操作系统 - Linux (推荐 Ubuntu 20.04+, CentOS 8+) - Windows Server 2019+ - macOS 10.15+ # 硬件要求 - CPU: 1 核心以上 - 内存: 512MB 以上（推荐 2GB+） - 磁盘: 100MB 以上 - 网络: 支持 UDP/TCP 53 端口 # 依赖软件 - systemd (Linux 服务管理) - Docker (容器化部署) - Kubernetes (集群部署) 网络规划 # # 示例环境配置 主服务器: 192.168.1.10 (主 DNS) 备用服务器: 192.168.1.11 (备 DNS) 管理网段: 192.168.1.0/24 服务网段: 10.0.0.0/16 二进制部署方式 # 自动化安装脚本 # #!/bin/bash # CoreDNS 自动化安装脚本 set -euo pipefail # 配置变量 COREDNS_VERSION=\u0026#34;1.11.1\u0026#34; INSTALL_DIR=\u0026#34;/usr/local/bin\u0026#34; CONFIG_DIR=\u0026#34;/etc/coredns\u0026#34; DATA_DIR=\u0026#34;/var/lib/coredns\u0026#34; LOG_DIR=\u0026#34;/var/log/coredns\u0026#34; USER=\u0026#34;coredns\u0026#34; GROUP=\u0026#34;coredns\u0026#34; # 颜色输出 RED=\u0026#39;\\033[0;31m\u0026#39; GREEN=\u0026#39;\\033[0;32m\u0026#39; YELLOW=\u0026#39;\\033[1;33m\u0026#39; NC=\u0026#39;\\033[0m\u0026#39; log_info() { echo -e \u0026#34;${GREEN}[INFO]${NC} $1\u0026#34; } log_warn() { echo -e \u0026#34;${YELLOW}[WARN]${NC} $1\u0026#34; } log_error() { echo -e \u0026#34;${RED}[ERROR]${NC} $1\u0026#34; } # 检测系统架构 detect_arch() { local arch=$(uname -m) case $arch in x86_64) echo \u0026#34;amd64\u0026#34; ;; aarch64|arm64) echo \u0026#34;arm64\u0026#34; ;; armv7l) echo \u0026#34;arm\u0026#34; ;; *) log_error \u0026#34;Unsupported architecture: $arch\u0026#34; exit 1 ;; esac } # 检测操作系统 detect_os() { if [[ \u0026#34;$OSTYPE\u0026#34; == \u0026#34;linux-gnu\u0026#34;* ]]; then echo \u0026#34;linux\u0026#34; elif [[ \u0026#34;$OSTYPE\u0026#34; == \u0026#34;darwin\u0026#34;* ]]; then echo \u0026#34;darwin\u0026#34; else log_error \u0026#34;Unsupported OS: $OSTYPE\u0026#34; exit 1 fi } # 下载 CoreDNS download_coredns() { local os=$(detect_os) local arch=$(detect_arch) local download_url=\u0026#34;https://github.com/coredns/coredns/releases/download/v${COREDNS_VERSION}/coredns_${COREDNS_VERSION}_${os}_${arch}.tgz\u0026#34; log_info \u0026#34;Downloading CoreDNS v${COREDNS_VERSION} for ${os}/${arch}...\u0026#34; # 创建临时目录 local temp_dir=$(mktemp -d) cd \u0026#34;$temp_dir\u0026#34; # 下载并解压 if command -v wget \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then wget -q \u0026#34;$download_url\u0026#34; -O coredns.tgz elif command -v curl \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then curl -sL \u0026#34;$download_url\u0026#34; -o coredns.tgz else log_error \u0026#34;Neither wget nor curl found. Please install one of them.\u0026#34; exit 1 fi tar -xzf coredns.tgz # 安装二进制文件 sudo mv coredns \u0026#34;$INSTALL_DIR/\u0026#34; sudo chmod +x \u0026#34;$INSTALL_DIR/coredns\u0026#34; # 清理临时文件 cd / rm -rf \u0026#34;$temp_dir\u0026#34; log_info \u0026#34;CoreDNS binary installed to $INSTALL_DIR/coredns\u0026#34; } # 创建用户和目录 setup_user_and_dirs() { log_info \u0026#34;Creating user and directories...\u0026#34; # 创建用户 if ! id \u0026#34;$USER\u0026#34; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then sudo useradd --system --no-create-home --shell /sbin/nologin \u0026#34;$USER\u0026#34; log_info \u0026#34;Created user: $USER\u0026#34; fi # 创建目录 sudo mkdir -p \u0026#34;$CONFIG_DIR\u0026#34; \u0026#34;$DATA_DIR\u0026#34; \u0026#34;$LOG_DIR\u0026#34; sudo chown \u0026#34;$USER:$GROUP\u0026#34; \u0026#34;$CONFIG_DIR\u0026#34; \u0026#34;$DATA_DIR\u0026#34; \u0026#34;$LOG_DIR\u0026#34; sudo chmod 755 \u0026#34;$CONFIG_DIR\u0026#34; \u0026#34;$DATA_DIR\u0026#34; \u0026#34;$LOG_DIR\u0026#34; log_info \u0026#34;Created directories: $CONFIG_DIR, $DATA_DIR, $LOG_DIR\u0026#34; } # 生成配置文件 generate_config() { log_info \u0026#34;Generating CoreDNS configuration...\u0026#34; sudo tee \u0026#34;$CONFIG_DIR/Corefile\u0026#34; \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # CoreDNS 主配置文件 # 监听所有接口的 53 端口 .:53 { # 绑定地址 bind 0.0.0.0 # 静态主机记录 hosts { # 自定义主机记录 ttl 60 reload 1m fallthrough } # DNS 转发 forward . 8.8.8.8 8.8.4.4 1.1.1.1 { max_fails 3 expire 10s health_check 5s policy sequential } # DNS 缓存 cache { success 65536 3600 300 denial 8192 600 60 prefetch 1 60m 10% } # 自动重载配置 reload 6s # 负载均衡 loadbalance round_robin # 日志记录 log { class error } # 错误处理 errors # 健康检查 health :8080 # Prometheus 监控 prometheus :9153 } # 内网域名解析 local.lan:53 { bind 0.0.0.0 file /etc/coredns/zones/local.lan.zone log { class all } errors } EOF sudo chown \u0026#34;$USER:$GROUP\u0026#34; \u0026#34;$CONFIG_DIR/Corefile\u0026#34; log_info \u0026#34;Generated Corefile at $CONFIG_DIR/Corefile\u0026#34; } # 创建区域文件 create_zone_files() { log_info \u0026#34;Creating zone files...\u0026#34; sudo mkdir -p \u0026#34;$CONFIG_DIR/zones\u0026#34; # 创建本地域名区域文件 sudo tee \u0026#34;$CONFIG_DIR/zones/local.lan.zone\u0026#34; \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; $ORIGIN local.lan. $TTL 300 @ IN SOA ns1.local.lan. admin.local.lan. ( 2023120101 ; Serial 3600 ; Refresh 1800 ; Retry 604800 ; Expire 300 ; Minimum TTL ) @ IN NS ns1.local.lan. ns1 IN A 192.168.1.10 ; 示例主机记录 router IN A 192.168.1.1 server1 IN A 192.168.1.10 server2 IN A 192.168.1.11 EOF sudo chown -R \u0026#34;$USER:$GROUP\u0026#34; \u0026#34;$CONFIG_DIR/zones\u0026#34; log_info \u0026#34;Created zone files in $CONFIG_DIR/zones/\u0026#34; } # 主函数 main() { log_info \u0026#34;Starting CoreDNS installation...\u0026#34; # 检查权限 if [[ $EUID -ne 0 ]] \u0026amp;\u0026amp; ! sudo -n true 2\u0026gt;/dev/null; then log_error \u0026#34;This script requires sudo privileges\u0026#34; exit 1 fi download_coredns setup_user_and_dirs generate_config create_zone_files log_info \u0026#34;CoreDNS installation completed!\u0026#34; log_info \u0026#34;Next steps:\u0026#34; log_info \u0026#34;1. Review configuration: $CONFIG_DIR/Corefile\u0026#34; log_info \u0026#34;2. Create systemd service: systemctl enable coredns\u0026#34; log_info \u0026#34;3. Start service: systemctl start coredns\u0026#34; } # 执行主函数 main \u0026#34;$@\u0026#34; 手动安装步骤 # 1. 下载和安装二进制文件 # # 设置版本和架构 COREDNS_VERSION=\u0026#34;1.11.1\u0026#34; ARCH=$(uname -m) # 根据架构设置下载链接 case $ARCH in x86_64) DOWNLOAD_ARCH=\u0026#34;amd64\u0026#34; ;; aarch64|arm64) DOWNLOAD_ARCH=\u0026#34;arm64\u0026#34; ;; armv7l) DOWNLOAD_ARCH=\u0026#34;arm\u0026#34; ;; *) echo \u0026#34;Unsupported architecture: $ARCH\u0026#34; exit 1 ;; esac # 下载 CoreDNS wget \u0026#34;https://github.com/coredns/coredns/releases/download/v${COREDNS_VERSION}/coredns_${COREDNS_VERSION}_linux_${DOWNLOAD_ARCH}.tgz\u0026#34; # 解压和安装 tar -xzf \u0026#34;coredns_${COREDNS_VERSION}_linux_${DOWNLOAD_ARCH}.tgz\u0026#34; sudo mv coredns /usr/local/bin/ sudo chmod +x /usr/local/bin/coredns # 验证安装 /usr/local/bin/coredns -version 2. 创建用户和目录结构 # # 创建系统用户 sudo useradd --system --no-create-home --shell /sbin/nologin coredns # 创建目录结构 sudo mkdir -p /etc/coredns/{zones,conf.d} sudo mkdir -p /var/lib/coredns sudo mkdir -p /var/log/coredns # 设置权限 sudo chown -R coredns:coredns /etc/coredns /var/lib/coredns /var/log/coredns sudo chmod 755 /etc/coredns /var/lib/coredns /var/log/coredns 3. 高级配置文件 # # 创建主配置文件 sudo tee /etc/coredns/Corefile \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # CoreDNS 主配置文件 # 全局配置块 .:53 { # 绑定地址和端口 bind 0.0.0.0 # 静态主机记录 hosts /etc/coredns/hosts { ttl 60 reload 1m fallthrough } # 区域文件 file /etc/coredns/zones/local.lan.zone local.lan # DNS 转发配置 forward . 8.8.8.8 8.8.4.4 1.1.1.1 { max_fails 3 expire 10s health_check 5s policy sequential prefer_udp } # 缓存配置 cache { success 65536 3600 300 denial 8192 600 60 prefetch 1 60m 10% serve_stale } # 负载均衡 loadbalance round_robin # 自动重载 reload 6s # 日志配置 log { class error file /var/log/coredns/error.log } # 错误处理 errors { consolidate 5m \u0026#34;.* i/o timeout\u0026#34; consolidate 30s \u0026#34;.*\u0026#34; } # 健康检查端点 health :8080 { lameduck 5s } # Prometheus 监控 prometheus :9153 # 访问控制 acl { allow net 192.168.0.0/16 allow net 10.0.0.0/8 allow net 172.16.0.0/12 block net 0.0.0.0/0 } } # 内网域名配置 internal.local:53 { bind 0.0.0.0 file /etc/coredns/zones/internal.local.zone log { class all file /var/log/coredns/internal.log } errors } # 反向解析配置 1.168.192.in-addr.arpa:53 { bind 0.0.0.0 file /etc/coredns/zones/192.168.1.rev log { class all file /var/log/coredns/reverse.log } errors } EOF # 创建主机记录文件 sudo tee /etc/coredns/hosts \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # 静态主机记录 192.168.1.1 router.local gateway.local 192.168.1.10 dns1.local ns1.local 192.168.1.11 dns2.local ns2.local 192.168.1.20 web.local www.local 192.168.1.21 api.local 192.168.1.22 db.local database.local EOF # 设置权限 sudo chown coredns:coredns /etc/coredns/Corefile /etc/coredns/hosts 4. 创建 systemd 服务 # # 创建现代化的 systemd 服务文件 sudo tee /etc/systemd/system/coredns.service \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Unit] Description=CoreDNS DNS Server Documentation=https://coredns.io/manual/toc/ After=network-online.target Wants=network-online.target AssertFileIsExecutable=/usr/local/bin/coredns [Service] Type=simple User=coredns Group=coredns # 安全配置 NoNewPrivileges=true PrivateTmp=true PrivateDevices=true ProtectHome=true ProtectSystem=strict ReadWritePaths=/var/lib/coredns /var/log/coredns /etc/coredns # 能力配置 CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID # 资源限制 LimitNOFILE=1048576 LimitNPROC=1048576 LimitCORE=infinity # 工作目录 WorkingDirectory=/etc/coredns # 启动命令 ExecStart=/usr/local/bin/coredns -conf=/etc/coredns/Corefile ExecReload=/bin/kill -SIGUSR1 $MAINPID # 重启策略 Restart=on-failure RestartSec=5 KillMode=mixed KillSignal=SIGINT # 日志配置 StandardOutput=journal StandardError=journal SyslogIdentifier=coredns [Install] WantedBy=multi-user.target EOF # 创建服务覆盖目录 sudo mkdir -p /etc/systemd/system/coredns.service.d # 创建环境变量配置 sudo tee /etc/systemd/system/coredns.service.d/environment.conf \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Service] Environment=\u0026#34;GOMAXPROCS=2\u0026#34; Environment=\u0026#34;GODEBUG=madvdontneed=1\u0026#34; EOF # 重载 systemd 配置 sudo systemctl daemon-reload 5. 日志轮转配置 # # 创建 logrotate 配置 sudo tee /etc/logrotate.d/coredns \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; /var/log/coredns/*.log { daily missingok rotate 30 compress delaycompress notifempty create 644 coredns coredns postrotate /bin/systemctl reload coredns.service \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 || true endscript } EOF 6. 启动和管理服务 # # 启动服务 sudo systemctl daemon-reload sudo systemctl enable coredns.service sudo systemctl start coredns.service # 检查服务状态 sudo systemctl status coredns.service # 查看日志 sudo journalctl -u coredns.service -f # 重载配置 sudo systemctl reload coredns.service # 重启服务 sudo systemctl restart coredns.service 容器化部署方式 # Docker 部署 # 基础 Docker 部署 # # 创建配置目录 mkdir -p ~/coredns/{config,zones,logs} # 创建 Dockerfile cat \u0026gt; ~/coredns/Dockerfile \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; FROM coredns/coredns:1.11.1 # 添加自定义配置 COPY Corefile /etc/coredns/ COPY zones/ /etc/coredns/zones/ # 创建非 root 用户 RUN adduser -D -s /bin/sh coredns # 设置权限 RUN chown -R coredns:coredns /etc/coredns USER coredns EXPOSE 53/udp 53/tcp 8080/tcp 9153/tcp ENTRYPOINT [\u0026#34;/coredns\u0026#34;] CMD [\u0026#34;-conf\u0026#34;, \u0026#34;/etc/coredns/Corefile\u0026#34;] EOF # 创建配置文件 cat \u0026gt; ~/coredns/config/Corefile \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; .:53 { bind 0.0.0.0 hosts { ttl 60 reload 1m fallthrough } forward . 8.8.8.8 8.8.4.4 { max_fails 3 expire 10s health_check 5s } cache { success 65536 3600 300 denial 8192 600 60 prefetch 1 60m 10% } reload 6s log errors health :8080 prometheus :9153 } EOF # 构建镜像 cd ~/coredns docker build -t custom-coredns:latest . # 运行容器 docker run -d \\ --name coredns \\ --restart unless-stopped \\ -p 53:53/udp \\ -p 53:53/tcp \\ -p 8080:8080/tcp \\ -p 9153:9153/tcp \\ -v $(pwd)/config:/etc/coredns:ro \\ -v $(pwd)/logs:/var/log/coredns \\ custom-coredns:latest Docker Compose 部署 # # docker-compose.yml version: \u0026#39;3.8\u0026#39; services: coredns: image: coredns/coredns:1.11.1 container_name: coredns restart: unless-stopped ports: - \u0026#34;53:53/udp\u0026#34; - \u0026#34;53:53/tcp\u0026#34; - \u0026#34;8080:8080/tcp\u0026#34; # Health check - \u0026#34;9153:9153/tcp\u0026#34; # Metrics volumes: - ./config/Corefile:/etc/coredns/Corefile:ro - ./config/zones:/etc/coredns/zones:ro - ./logs:/var/log/coredns command: [\u0026#34;-conf\u0026#34;, \u0026#34;/etc/coredns/Corefile\u0026#34;] networks: - dns-network healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;curl\u0026#34;, \u0026#34;-f\u0026#34;, \u0026#34;http://localhost:8080/health\u0026#34;] interval: 30s timeout: 10s retries: 3 start_period: 40s logging: driver: \u0026#34;json-file\u0026#34; options: max-size: \u0026#34;10m\u0026#34; max-file: \u0026#34;3\u0026#34; # 可选：添加 Prometheus 监控 prometheus: image: prom/prometheus:latest container_name: prometheus restart: unless-stopped ports: - \u0026#34;9090:9090\u0026#34; volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus-data:/prometheus command: - \u0026#39;--config.file=/etc/prometheus/prometheus.yml\u0026#39; - \u0026#39;--storage.tsdb.path=/prometheus\u0026#39; - \u0026#39;--web.console.libraries=/etc/prometheus/console_libraries\u0026#39; - \u0026#39;--web.console.templates=/etc/prometheus/consoles\u0026#39; - \u0026#39;--storage.tsdb.retention.time=200h\u0026#39; - \u0026#39;--web.enable-lifecycle\u0026#39; networks: - dns-network # 可选：添加 Grafana 可视化 grafana: image: grafana/grafana:latest container_name: grafana restart: unless-stopped ports: - \u0026#34;3000:3000\u0026#34; environment: - GF_SECURITY_ADMIN_PASSWORD=admin123 volumes: - grafana-data:/var/lib/grafana - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources:ro networks: - dns-network networks: dns-network: driver: bridge volumes: prometheus-data: grafana-data: Kubernetes 部署 # 基础 Kubernetes 部署 # # coredns-namespace.yaml apiVersion: v1 kind: Namespace metadata: name: coredns-system labels: name: coredns-system --- # coredns-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: coredns-config namespace: coredns-system data: Corefile: | .:53 { bind 0.0.0.0 kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } hosts { ttl 60 reload 1m fallthrough } forward . 8.8.8.8 8.8.4.4 { max_fails 3 expire 10s health_check 5s policy sequential } cache { success 65536 3600 300 denial 8192 600 60 prefetch 1 60m 10% } reload 6s log errors health :8080 prometheus :9153 } --- # coredns-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: coredns-system labels: app: coredns spec: replicas: 2 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 selector: matchLabels: app: coredns template: metadata: labels: app: coredns spec: serviceAccountName: coredns containers: - name: coredns image: coredns/coredns:1.11.1 imagePullPolicy: IfNotPresent args: [\u0026#34;-conf\u0026#34;, \u0026#34;/etc/coredns/Corefile\u0026#34;] ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 8080 name: health protocol: TCP - containerPort: 9153 name: metrics protocol: TCP livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 60 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 readinessProbe: httpGet: path: /ready port: 8181 initialDelaySeconds: 10 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 3 resources: limits: memory: 170Mi cpu: 100m requests: memory: 70Mi cpu: 50m volumeMounts: - name: config-volume mountPath: /etc/coredns readOnly: true securityContext: allowPrivilegeEscalation: false capabilities: add: - NET_BIND_SERVICE drop: - ALL readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 volumes: - name: config-volume configMap: name: coredns-config items: - key: Corefile path: Corefile dnsPolicy: Default --- # coredns-service.yaml apiVersion: v1 kind: Service metadata: name: coredns namespace: coredns-system labels: app: coredns annotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;9153\u0026#34; spec: type: ClusterIP clusterIP: 10.96.0.10 # 固定 ClusterIP ports: - name: dns port: 53 targetPort: 53 protocol: UDP - name: dns-tcp port: 53 targetPort: 53 protocol: TCP - name: metrics port: 9153 targetPort: 9153 protocol: TCP selector: app: coredns --- # coredns-serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: coredns namespace: coredns-system --- # coredns-clusterrole.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: coredns rules: - apiGroups: - \u0026#34;\u0026#34; resources: - endpoints - services - pods - namespaces verbs: - list - watch - apiGroups: - discovery.k8s.io resources: - endpointslices verbs: - list - watch --- # coredns-clusterrolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: coredns roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: coredns subjects: - kind: ServiceAccount name: coredns namespace: coredns-system 高可用配置 # 主从架构部署 # 主服务器配置 (192.168.1.10) # # 主服务器 Corefile cat \u0026gt; /etc/coredns/Corefile \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; .:53 { bind 0.0.0.0 # 区域传输配置 transfer { to 192.168.1.11 # 从服务器 IP } # 主区域文件 file /etc/coredns/zones/local.lan.zone local.lan { transfer to 192.168.1.11 } hosts { ttl 60 reload 1m fallthrough } forward . 8.8.8.8 8.8.4.4 { max_fails 3 expire 10s health_check 5s policy sequential } cache { success 65536 3600 300 denial 8192 600 60 prefetch 1 60m 10% } reload 6s log errors health :8080 prometheus :9153 } EOF 从服务器配置 (192.168.1.11) # # 从服务器 Corefile cat \u0026gt; /etc/coredns/Corefile \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; .:53 { bind 0.0.0.0 # 从区域配置 secondary local.lan { transfer from 192.168.1.10 transfer to * } hosts { ttl 60 reload 1m fallthrough } forward . 8.8.8.8 8.8.4.4 { max_fails 3 expire 10s health_check 5s policy sequential } cache { success 65536 3600 300 denial 8192 600 60 prefetch 1 60m 10% } reload 6s log errors health :8080 prometheus :9153 } EOF 负载均衡配置 # HAProxy 配置 # # 安装 HAProxy sudo apt update \u0026amp;\u0026amp; sudo apt install -y haproxy # 配置 HAProxy sudo tee /etc/haproxy/haproxy.cfg \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; global daemon chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin stats timeout 30s user haproxy group haproxy defaults mode tcp timeout connect 5000ms timeout client 50000ms timeout server 50000ms option tcplog # DNS 负载均衡 frontend dns_frontend bind *:53 mode tcp default_backend dns_backend backend dns_backend mode tcp balance roundrobin option tcp-check tcp-check connect port 8080 tcp-check expect string \u0026#34;OK\u0026#34; server dns1 192.168.1.10:53 check port 8080 inter 5s rise 2 fall 3 server dns2 192.168.1.11:53 check port 8080 inter 5s rise 2 fall 3 # 统计页面 frontend stats bind *:8404 mode http stats enable stats uri /stats stats refresh 30s stats admin if TRUE EOF # 启动 HAProxy sudo systemctl enable haproxy sudo systemctl start haproxy Keepalived 高可用 # # 主服务器 Keepalived 配置 sudo tee /etc/keepalived/keepalived.conf \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; vrrp_script chk_coredns { script \u0026#34;/usr/local/bin/check_coredns.sh\u0026#34; interval 2 weight -2 fall 3 rise 2 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 110 advert_int 1 authentication { auth_type PASS auth_pass coredns123 } virtual_ipaddress { 192.168.1.100/24 } track_script { chk_coredns } } EOF # 创建健康检查脚本 sudo tee /usr/local/bin/check_coredns.sh \u0026gt; /dev/null \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash curl -f http://localhost:8080/health \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 exit $? EOF sudo chmod +x /usr/local/bin/check_coredns.sh # 从服务器配置（priority 改为 100） # 启动 Keepalived sudo systemctl enable keepalived sudo systemctl start keepalived 监控和告警 # Prometheus 监控配置 # # prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s rule_files: - \u0026#34;coredns_rules.yml\u0026#34; scrape_configs: - job_name: \u0026#39;coredns\u0026#39; static_configs: - targets: [\u0026#39;192.168.1.10:9153\u0026#39;, \u0026#39;192.168.1.11:9153\u0026#39;] scrape_interval: 15s metrics_path: /metrics alerting: alertmanagers: - static_configs: - targets: - alertmanager:9093 告警规则配置 # # coredns_rules.yml groups: - name: coredns rules: - alert: CoreDNSDown expr: up{job=\u0026#34;coredns\u0026#34;} == 0 for: 5m labels: severity: critical annotations: summary: \u0026#34;CoreDNS instance is down\u0026#34; description: \u0026#34;CoreDNS instance {{ $labels.instance }} has been down for more than 5 minutes.\u0026#34; - alert: CoreDNSHighQueryRate expr: rate(coredns_dns_requests_total[5m]) \u0026gt; 1000 for: 2m labels: severity: warning annotations: summary: \u0026#34;High DNS query rate\u0026#34; description: \u0026#34;CoreDNS instance {{ $labels.instance }} is receiving {{ $value }} queries per second.\u0026#34; - alert: CoreDNSHighErrorRate expr: rate(coredns_dns_responses_total{rcode!=\u0026#34;NOERROR\u0026#34;}[5m]) / rate(coredns_dns_responses_total[5m]) \u0026gt; 0.1 for: 5m labels: severity: critical annotations: summary: \u0026#34;High DNS error rate\u0026#34; description: \u0026#34;CoreDNS instance {{ $labels.instance }} has error rate of {{ $value | humanizePercentage }}.\u0026#34; - alert: CoreDNSCacheHitRateLow expr: rate(coredns_cache_hits_total[5m]) / (rate(coredns_cache_hits_total[5m]) + rate(coredns_cache_misses_total[5m])) \u0026lt; 0.8 for: 10m labels: severity: warning annotations: summary: \u0026#34;Low DNS cache hit rate\u0026#34; description: \u0026#34;CoreDNS instance {{ $labels.instance }} has cache hit rate of {{ $value | humanizePercentage }}.\u0026#34; Grafana 仪表板 # { \u0026#34;dashboard\u0026#34;: { \u0026#34;id\u0026#34;: null, \u0026#34;title\u0026#34;: \u0026#34;CoreDNS Dashboard\u0026#34;, \u0026#34;tags\u0026#34;: [\u0026#34;coredns\u0026#34;, \u0026#34;dns\u0026#34;], \u0026#34;timezone\u0026#34;: \u0026#34;browser\u0026#34;, \u0026#34;panels\u0026#34;: [ { \u0026#34;id\u0026#34;: 1, \u0026#34;title\u0026#34;: \u0026#34;DNS Queries per Second\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;graph\u0026#34;, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;rate(coredns_dns_requests_total[5m])\u0026#34;, \u0026#34;legendFormat\u0026#34;: \u0026#34;{{ instance }}\u0026#34; } ] }, { \u0026#34;id\u0026#34;: 2, \u0026#34;title\u0026#34;: \u0026#34;DNS Response Codes\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;graph\u0026#34;, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;rate(coredns_dns_responses_total[5m])\u0026#34;, \u0026#34;legendFormat\u0026#34;: \u0026#34;{{ rcode }}\u0026#34; } ] }, { \u0026#34;id\u0026#34;: 3, \u0026#34;title\u0026#34;: \u0026#34;Cache Hit Rate\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;singlestat\u0026#34;, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;rate(coredns_cache_hits_total[5m]) / (rate(coredns_cache_hits_total[5m]) + rate(coredns_cache_misses_total[5m]))\u0026#34; } ] } ] } } 管理和维护 # 日常管理脚本 # #!/bin/bash # CoreDNS 管理脚本 COREDNS_CONFIG=\u0026#34;/etc/coredns/Corefile\u0026#34; HOSTS_FILE=\u0026#34;/etc/coredns/hosts\u0026#34; ZONES_DIR=\u0026#34;/etc/coredns/zones\u0026#34; # 颜色定义 RED=\u0026#39;\\033[0;31m\u0026#39; GREEN=\u0026#39;\\033[0;32m\u0026#39; YELLOW=\u0026#39;\\033[1;33m\u0026#39; NC=\u0026#39;\\033[0m\u0026#39; log_info() { echo -e \u0026#34;${GREEN}[INFO]${NC} $1\u0026#34; } log_warn() { echo -e \u0026#34;${YELLOW}[WARN]${NC} $1\u0026#34; } log_error() { echo -e \u0026#34;${RED}[ERROR]${NC} $1\u0026#34; } # 添加主机记录 add_host() { local ip=\u0026#34;$1\u0026#34; local hostname=\u0026#34;$2\u0026#34; if [[ -z \u0026#34;$ip\u0026#34; || -z \u0026#34;$hostname\u0026#34; ]]; then log_error \u0026#34;Usage: add_host \u0026lt;ip\u0026gt; \u0026lt;hostname\u0026gt;\u0026#34; return 1 fi # 检查 IP 格式 if ! [[ \u0026#34;$ip\u0026#34; =~ ^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$ ]]; then log_error \u0026#34;Invalid IP address format: $ip\u0026#34; return 1 fi # 检查是否已存在 if grep -q \u0026#34;$hostname\u0026#34; \u0026#34;$HOSTS_FILE\u0026#34; 2\u0026gt;/dev/null; then log_warn \u0026#34;Hostname $hostname already exists\u0026#34; return 1 fi # 添加记录 echo \u0026#34;$ip $hostname\u0026#34; \u0026gt;\u0026gt; \u0026#34;$HOSTS_FILE\u0026#34; log_info \u0026#34;Added host record: $ip -\u0026gt; $hostname\u0026#34; # 重载配置 reload_config } # 删除主机记录 remove_host() { local hostname=\u0026#34;$1\u0026#34; if [[ -z \u0026#34;$hostname\u0026#34; ]]; then log_error \u0026#34;Usage: remove_host \u0026lt;hostname\u0026gt;\u0026#34; return 1 fi # 删除记录 if sed -i \u0026#34;/$hostname/d\u0026#34; \u0026#34;$HOSTS_FILE\u0026#34;; then log_info \u0026#34;Removed host record: $hostname\u0026#34; reload_config else log_error \u0026#34;Failed to remove host record: $hostname\u0026#34; return 1 fi } # 列出主机记录 list_hosts() { log_info \u0026#34;Current host records:\u0026#34; if [[ -f \u0026#34;$HOSTS_FILE\u0026#34; ]]; then cat \u0026#34;$HOSTS_FILE\u0026#34; | grep -v \u0026#39;^#\u0026#39; | grep -v \u0026#39;^$\u0026#39; else log_warn \u0026#34;Hosts file not found: $HOSTS_FILE\u0026#34; fi } # 重载配置 reload_config() { if systemctl is-active --quiet coredns; then if systemctl reload coredns; then log_info \u0026#34;CoreDNS configuration reloaded\u0026#34; else log_error \u0026#34;Failed to reload CoreDNS configuration\u0026#34; return 1 fi else log_warn \u0026#34;CoreDNS service is not running\u0026#34; fi } # 检查服务状态 check_status() { log_info \u0026#34;CoreDNS Service Status:\u0026#34; systemctl status coredns --no-pager log_info \u0026#34;Health Check:\u0026#34; if curl -f http://localhost:8080/health \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_info \u0026#34;Health check: PASSED\u0026#34; else log_error \u0026#34;Health check: FAILED\u0026#34; fi log_info \u0026#34;DNS Query Test:\u0026#34; if dig @localhost google.com \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_info \u0026#34;DNS query test: PASSED\u0026#34; else log_error \u0026#34;DNS query test: FAILED\u0026#34; fi } # 查看日志 view_logs() { local lines=\u0026#34;${1:-50}\u0026#34; log_info \u0026#34;CoreDNS Logs (last $lines lines):\u0026#34; journalctl -u coredns -n \u0026#34;$lines\u0026#34; --no-pager } # 备份配置 backup_config() { local backup_dir=\u0026#34;/var/backups/coredns\u0026#34; local timestamp=$(date +%Y%m%d_%H%M%S) mkdir -p \u0026#34;$backup_dir\u0026#34; tar -czf \u0026#34;$backup_dir/coredns_config_$timestamp.tar.gz\u0026#34; \\ -C /etc coredns/ log_info \u0026#34;Configuration backed up to: $backup_dir/coredns_config_$timestamp.tar.gz\u0026#34; } # 性能统计 show_stats() { log_info \u0026#34;CoreDNS Performance Statistics:\u0026#34; if command -v curl \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;Prometheus Metrics:\u0026#34; curl -s http://localhost:9153/metrics | grep -E \u0026#34;(coredns_dns_requests_total|coredns_dns_responses_total|coredns_cache_hits_total)\u0026#34; else log_warn \u0026#34;curl not found, cannot fetch metrics\u0026#34; fi } # 主函数 main() { case \u0026#34;${1:-}\u0026#34; in \u0026#34;add-host\u0026#34;) add_host \u0026#34;$2\u0026#34; \u0026#34;$3\u0026#34; ;; \u0026#34;remove-host\u0026#34;) remove_host \u0026#34;$2\u0026#34; ;; \u0026#34;list-hosts\u0026#34;) list_hosts ;; \u0026#34;reload\u0026#34;) reload_config ;; \u0026#34;status\u0026#34;) check_status ;; \u0026#34;logs\u0026#34;) view_logs \u0026#34;$2\u0026#34; ;; \u0026#34;backup\u0026#34;) backup_config ;; \u0026#34;stats\u0026#34;) show_stats ;; *) echo \u0026#34;Usage: $0 {add-host|remove-host|list-hosts|reload|status|logs|backup|stats}\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34;Commands:\u0026#34; echo \u0026#34; add-host \u0026lt;ip\u0026gt; \u0026lt;hostname\u0026gt; - Add a host record\u0026#34; echo \u0026#34; remove-host \u0026lt;hostname\u0026gt; - Remove a host record\u0026#34; echo \u0026#34; list-hosts - List all host records\u0026#34; echo \u0026#34; reload - Reload CoreDNS configuration\u0026#34; echo \u0026#34; status - Check CoreDNS status\u0026#34; echo \u0026#34; logs [lines] - View CoreDNS logs\u0026#34; echo \u0026#34; backup - Backup configuration\u0026#34; echo \u0026#34; stats - Show performance statistics\u0026#34; exit 1 ;; esac } main \u0026#34;$@\u0026#34; 自动化运维脚本 # #!/bin/bash # CoreDNS 自动化运维脚本 # 健康检查和自动恢复 health_check_and_recovery() { local max_retries=3 local retry_count=0 while [[ $retry_count -lt $max_retries ]]; do if curl -f http://localhost:8080/health \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_info \u0026#34;Health check passed\u0026#34; return 0 else log_warn \u0026#34;Health check failed, attempt $((retry_count + 1))/$max_retries\u0026#34; # 尝试重启服务 systemctl restart coredns sleep 10 ((retry_count++)) fi done log_error \u0026#34;Health check failed after $max_retries attempts\u0026#34; # 发送告警 send_alert \u0026#34;CoreDNS health check failed after $max_retries attempts\u0026#34; return 1 } # 性能监控 performance_monitor() { local cpu_usage=$(ps -o %cpu -p $(pgrep coredns) --no-headers | awk \u0026#39;{sum+=$1} END {print sum}\u0026#39;) local memory_usage=$(ps -o %mem -p $(pgrep coredns) --no-headers | awk \u0026#39;{sum+=$1} END {print sum}\u0026#39;) log_info \u0026#34;CoreDNS Performance: CPU: ${cpu_usage}%, Memory: ${memory_usage}%\u0026#34; # 检查性能阈值 if (( $(echo \u0026#34;$cpu_usage \u0026gt; 80\u0026#34; | bc -l) )); then log_warn \u0026#34;High CPU usage detected: ${cpu_usage}%\u0026#34; send_alert \u0026#34;CoreDNS high CPU usage: ${cpu_usage}%\u0026#34; fi if (( $(echo \u0026#34;$memory_usage \u0026gt; 80\u0026#34; | bc -l) )); then log_warn \u0026#34;High memory usage detected: ${memory_usage}%\u0026#34; send_alert \u0026#34;CoreDNS high memory usage: ${memory_usage}%\u0026#34; fi } # 发送告警 send_alert() { local message=\u0026#34;$1\u0026#34; local webhook_url=\u0026#34;YOUR_WEBHOOK_URL\u0026#34; if [[ -n \u0026#34;$webhook_url\u0026#34; ]]; then curl -X POST -H \u0026#39;Content-type: application/json\u0026#39; \\ --data \u0026#34;{\\\u0026#34;text\\\u0026#34;:\\\u0026#34;🚨 CoreDNS Alert: $message\\\u0026#34;}\u0026#34; \\ \u0026#34;$webhook_url\u0026#34; fi # 记录到系统日志 logger -t coredns-monitor \u0026#34;$message\u0026#34; } # 定时任务配置 setup_cron_jobs() { # 添加 cron 任务 (crontab -l 2\u0026gt;/dev/null; echo \u0026#34;*/5 * * * * /usr/local/bin/coredns-manage.sh health-check\u0026#34;) | crontab - (crontab -l 2\u0026gt;/dev/null; echo \u0026#34;0 2 * * * /usr/local/bin/coredns-manage.sh backup\u0026#34;) | crontab - (crontab -l 2\u0026gt;/dev/null; echo \u0026#34;*/10 * * * * /usr/local/bin/coredns-manage.sh performance\u0026#34;) | crontab - log_info \u0026#34;Cron jobs configured for automated monitoring\u0026#34; } 测试和验证 # 功能测试 # #!/bin/bash # CoreDNS 功能测试脚本 # 测试配置 DNS_SERVER=\u0026#34;192.168.1.10\u0026#34; TEST_DOMAINS=(\u0026#34;google.com\u0026#34; \u0026#34;github.com\u0026#34; \u0026#34;local.lan\u0026#34;) INTERNAL_DOMAINS=(\u0026#34;server1.local.lan\u0026#34; \u0026#34;api.internal.local\u0026#34;) # 基础 DNS 解析测试 test_dns_resolution() { log_info \u0026#34;Testing DNS resolution...\u0026#34; for domain in \u0026#34;${TEST_DOMAINS[@]}\u0026#34;; do if dig @\u0026#34;$DNS_SERVER\u0026#34; \u0026#34;$domain\u0026#34; +short \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_info \u0026#34;✓ DNS resolution test passed for $domain\u0026#34; else log_error \u0026#34;✗ DNS resolution test failed for $domain\u0026#34; return 1 fi done } # 内网域名测试 test_internal_domains() { log_info \u0026#34;Testing internal domain resolution...\u0026#34; for domain in \u0026#34;${INTERNAL_DOMAINS[@]}\u0026#34;; do if dig @\u0026#34;$DNS_SERVER\u0026#34; \u0026#34;$domain\u0026#34; +short \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_info \u0026#34;✓ Internal domain test passed for $domain\u0026#34; else log_warn \u0026#34;⚠ Internal domain test failed for $domain (may be expected)\u0026#34; fi done } # 性能测试 test_performance() { log_info \u0026#34;Running performance test...\u0026#34; # 使用 dnsperf 进行性能测试（如果可用） if command -v dnsperf \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;google.com A\u0026#34; \u0026gt; /tmp/test_queries.txt echo \u0026#34;github.com A\u0026#34; \u0026gt;\u0026gt; /tmp/test_queries.txt echo \u0026#34;stackoverflow.com A\u0026#34; \u0026gt;\u0026gt; /tmp/test_queries.txt dnsperf -s \u0026#34;$DNS_SERVER\u0026#34; -d /tmp/test_queries.txt -l 10 -c 10 rm -f /tmp/test_queries.txt else log_warn \u0026#34;dnsperf not available, skipping performance test\u0026#34; fi } # 缓存测试 test_cache() { log_info \u0026#34;Testing DNS cache...\u0026#34; # 第一次查询 time1=$(dig @\u0026#34;$DNS_SERVER\u0026#34; google.com | grep \u0026#34;Query time\u0026#34; | awk \u0026#39;{print $4}\u0026#39;) # 第二次查询（应该从缓存返回） time2=$(dig @\u0026#34;$DNS_SERVER\u0026#34; google.com | grep \u0026#34;Query time\u0026#34; | awk \u0026#39;{print $4}\u0026#39;) log_info \u0026#34;First query: ${time1}ms, Second query: ${time2}ms\u0026#34; if [[ \u0026#34;$time2\u0026#34; -lt \u0026#34;$time1\u0026#34; ]]; then log_info \u0026#34;✓ Cache test passed (second query faster)\u0026#34; else log_warn \u0026#34;⚠ Cache test inconclusive\u0026#34; fi } # 负载测试 test_load() { log_info \u0026#34;Running load test...\u0026#34; # 并发查询测试 for i in {1..50}; do dig @\u0026#34;$DNS_SERVER\u0026#34; \u0026#34;test$i.google.com\u0026#34; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 \u0026amp; done wait log_info \u0026#34;✓ Load test completed (50 concurrent queries)\u0026#34; } # 运行所有测试 run_all_tests() { log_info \u0026#34;Starting CoreDNS comprehensive tests...\u0026#34; test_dns_resolution test_internal_domains test_cache test_load test_performance log_info \u0026#34;All tests completed!\u0026#34; } # 执行测试 run_all_tests 故障排除指南 # 常见问题和解决方案 # # 1. 服务启动失败 troubleshoot_startup() { log_info \u0026#34;Troubleshooting startup issues...\u0026#34; # 检查配置文件语法 if /usr/local/bin/coredns -conf=/etc/coredns/Corefile -validate; then log_info \u0026#34;✓ Configuration syntax is valid\u0026#34; else log_error \u0026#34;✗ Configuration syntax error\u0026#34; return 1 fi # 检查端口占用 if netstat -tulpn | grep :53 \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_warn \u0026#34;Port 53 is already in use:\u0026#34; netstat -tulpn | grep :53 fi # 检查权限 if [[ -r /etc/coredns/Corefile ]]; then log_info \u0026#34;✓ Configuration file is readable\u0026#34; else log_error \u0026#34;✗ Configuration file permission issue\u0026#34; fi } # 2. DNS 解析失败 troubleshoot_resolution() { log_info \u0026#34;Troubleshooting DNS resolution...\u0026#34; # 检查上游 DNS for upstream in 8.8.8.8 8.8.4.4 1.1.1.1; do if dig @\u0026#34;$upstream\u0026#34; google.com +short \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_info \u0026#34;✓ Upstream DNS $upstream is reachable\u0026#34; else log_error \u0026#34;✗ Upstream DNS $upstream is unreachable\u0026#34; fi done # 检查本地解析 if dig @127.0.0.1 google.com +short \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_info \u0026#34;✓ Local DNS resolution working\u0026#34; else log_error \u0026#34;✗ Local DNS resolution failed\u0026#34; fi } # 3. 性能问题 troubleshoot_performance() { log_info \u0026#34;Troubleshooting performance issues...\u0026#34; # 检查系统资源 local cpu_usage=$(top -bn1 | grep \u0026#34;Cpu(s)\u0026#34; | awk \u0026#39;{print $2}\u0026#39; | awk -F\u0026#39;%\u0026#39; \u0026#39;{print $1}\u0026#39;) local memory_usage=$(free | grep Mem | awk \u0026#39;{printf(\u0026#34;%.1f\u0026#34;), $3/$2 * 100.0}\u0026#39;) log_info \u0026#34;System CPU usage: ${cpu_usage}%\u0026#34; log_info \u0026#34;System memory usage: ${memory_usage}%\u0026#34; # 检查 CoreDNS 进程 local coredns_pid=$(pgrep coredns) if [[ -n \u0026#34;$coredns_pid\u0026#34; ]]; then local coredns_cpu=$(ps -o %cpu -p \u0026#34;$coredns_pid\u0026#34; --no-headers) local coredns_mem=$(ps -o %mem -p \u0026#34;$coredns_pid\u0026#34; --no-headers) log_info \u0026#34;CoreDNS CPU usage: ${coredns_cpu}%\u0026#34; log_info \u0026#34;CoreDNS memory usage: ${coredns_mem}%\u0026#34; fi } # 4. 网络连接问题 troubleshoot_network() { log_info \u0026#34;Troubleshooting network connectivity...\u0026#34; # 检查监听端口 if netstat -tulpn | grep \u0026#34;:53.*coredns\u0026#34; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_info \u0026#34;✓ CoreDNS is listening on port 53\u0026#34; else log_error \u0026#34;✗ CoreDNS is not listening on port 53\u0026#34; fi # 检查防火墙 if command -v ufw \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then if ufw status | grep \u0026#34;53.*ALLOW\u0026#34; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log_info \u0026#34;✓ Firewall allows DNS traffic\u0026#34; else log_warn \u0026#34;⚠ Firewall may be blocking DNS traffic\u0026#34; fi fi } # 综合故障排除 comprehensive_troubleshoot() { log_info \u0026#34;Running comprehensive troubleshooting...\u0026#34; troubleshoot_startup troubleshoot_resolution troubleshoot_performance troubleshoot_network # 生成诊断报告 generate_diagnostic_report } # 生成诊断报告 generate_diagnostic_report() { local report_file=\u0026#34;/tmp/coredns_diagnostic_$(date +%Y%m%d_%H%M%S).txt\u0026#34; { echo \u0026#34;CoreDNS Diagnostic Report\u0026#34; echo \u0026#34;========================\u0026#34; echo \u0026#34;Generated: $(date)\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34;System Information:\u0026#34; uname -a echo \u0026#34;\u0026#34; echo \u0026#34;CoreDNS Version:\u0026#34; /usr/local/bin/coredns -version echo \u0026#34;\u0026#34; echo \u0026#34;Service Status:\u0026#34; systemctl status coredns --no-pager echo \u0026#34;\u0026#34; echo \u0026#34;Configuration:\u0026#34; cat /etc/coredns/Corefile echo \u0026#34;\u0026#34; echo \u0026#34;Recent Logs:\u0026#34; journalctl -u coredns -n 50 --no-pager echo \u0026#34;\u0026#34; echo \u0026#34;Network Status:\u0026#34; netstat -tulpn | grep :53 echo \u0026#34;\u0026#34; echo \u0026#34;Process Information:\u0026#34; ps aux | grep coredns echo \u0026#34;\u0026#34; } \u0026gt; \u0026#34;$report_file\u0026#34; log_info \u0026#34;Diagnostic report generated: $report_file\u0026#34; } 性能优化 # 配置优化 # # 高性能 Corefile 配置 cat \u0026gt; /etc/coredns/Corefile.optimized \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; .:53 { bind 0.0.0.0 # 优化的缓存配置 cache { success 65536 7200 300 # 增大缓存大小和 TTL denial 16384 1800 60 # 增大否定缓存 prefetch 2 60m 20% # 增强预取 serve_stale # 提供过期缓存 } # 优化的转发配置 forward . 8.8.8.8 8.8.4.4 1.1.1.1 1.0.0.1 { max_fails 2 expire 5s health_check 3s policy sequential prefer_udp max_concurrent 1000 } # 负载均衡 loadbalance round_robin # 减少日志级别 log { class error } # 错误处理优化 errors { consolidate 5m \u0026#34;.* i/o timeout\u0026#34; consolidate 30s \u0026#34;.*\u0026#34; } # 健康检查 health :8080 { lameduck 5s } # 监控 prometheus :9153 # 自动重载 reload 30s # 减少重载频率 } EOF 系统优化 # # 系统参数优化 cat \u0026gt; /etc/sysctl.d/99-coredns.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # 网络优化 net.core.rmem_default = 262144 net.core.rmem_max = 16777216 net.core.wmem_default = 262144 net.core.wmem_max = 16777216 net.core.netdev_max_backlog = 5000 net.ipv4.udp_mem = 102400 873800 16777216 net.ipv4.udp_rmem_min = 8192 net.ipv4.udp_wmem_min = 8192 # 文件描述符 fs.file-max = 1048576 # 进程限制 kernel.pid_max = 4194304 EOF # 应用配置 sysctl -p /etc/sysctl.d/99-coredns.conf # 用户限制优化 cat \u0026gt; /etc/security/limits.d/coredns.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; coredns soft nofile 1048576 coredns hard nofile 1048576 coredns soft nproc 1048576 coredns hard nproc 1048576 EOF 总结 # 部署方式对比 # 部署方式 优势 劣势 适用场景 二进制部署 性能最优、资源占用少、配置灵活 管理复杂、更新麻烦 生产环境、高性能要求 Docker 部署 部署简单、环境隔离、易于管理 性能略低、资源开销 开发测试、快速部署 Kubernetes 部署 高可用、自动扩缩容、服务发现 复杂度高、资源要求高 云原生环境、大规模集群 最佳实践总结 # 配置管理\n使用版本控制管理配置文件 实施配置验证和测试 建立配置变更流程 监控告警\n部署 Prometheus + Grafana 监控 配置关键指标告警 建立故障响应流程 高可用设计\n部署多实例负载均衡 实施健康检查和自动故障转移 定期备份配置和数据 性能优化\n合理配置缓存策略 优化系统参数 监控性能指标 安全加固\n实施访问控制 定期更新版本 监控安全事件 进阶学习资源 # 官方文档: CoreDNS Documentation 插件开发: CoreDNS Plugin Development 社区资源: CoreDNS GitHub 最佳实践: CNCF CoreDNS Best Practices 通过本指南的学习和实践，您将能够成功部署和管理企业级的 CoreDNS 服务，为您的基础设施提供稳定、高效的 DNS 解析服务。\n","date":"2021年9月25日","externalUrl":null,"permalink":"/posts/coredns-deployment/","section":"博客文章","summary":"\u003cp\u003eCoreDNS 是现代化的 DNS 服务器，以其插件化架构、高性能和易配置性成为 Kubernetes 默认 DNS 解决方案。本指南将从基础部署到企业级应用，全面介绍 CoreDNS 的部署、配置、优化和管理。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003eCoreDNS 概述 \n    \u003cdiv id=\"coredns-概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#coredns-%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e什么是 CoreDNS \n    \u003cdiv id=\"什么是-coredns\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-coredns\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\u003ca\n  href=\"https://github.com/coredns/coredns\"\n    target=\"_blank\"\n  \u003eCoreDNS\u003c/a\u003e 是用 Go 语言编写的现代化 DNS 服务器，具有以下核心特性：\u003c/p\u003e","title":"CoreDNS 完整部署与管理指南","type":"posts"},{"content":"","date":"2021年9月25日","externalUrl":null,"permalink":"/tags/dns/","section":"Tags","summary":"","title":"Dns","type":"tags"},{"content":"","date":"2021年9月25日","externalUrl":null,"permalink":"/categories/dns/","section":"Categories","summary":"","title":"DNS","type":"categories"},{"content":"","date":"2021年9月25日","externalUrl":null,"permalink":"/tags/systemd/","section":"Tags","summary":"","title":"Systemd","type":"tags"},{"content":"","date":"2021年9月25日","externalUrl":null,"permalink":"/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AE%A1%E7%90%86/","section":"Tags","summary":"","title":"服务器管理","type":"tags"},{"content":"","date":"2021年9月25日","externalUrl":null,"permalink":"/tags/%E5%AE%B9%E5%99%A8%E5%8C%96/","section":"Tags","summary":"","title":"容器化","type":"tags"},{"content":"","date":"2021年9月25日","externalUrl":null,"permalink":"/categories/%E7%BD%91%E7%BB%9C%E6%9C%8D%E5%8A%A1/","section":"Categories","summary":"","title":"网络服务","type":"categories"},{"content":"","date":"2021年9月25日","externalUrl":null,"permalink":"/tags/%E7%BD%91%E7%BB%9C%E6%9C%8D%E5%8A%A1/","section":"Tags","summary":"","title":"网络服务","type":"tags"},{"content":"Go 语言中的结构体（Struct）是一种用户自定义的数据类型，用于将不同类型的数据组合在一起。虽然 Go 不是传统的面向对象语言，但通过结构体和方法，我们可以实现面向对象编程的核心概念。\n什么是结构体 # 结构体是 Go 语言中实现数据封装的主要方式，它具有以下特点：\n数据聚合: 将相关的数据字段组合在一起 类型安全: 编译时检查字段类型 方法绑定: 可以为结构体定义方法 内存效率: 字段在内存中连续存储 基础结构体定义 # 简单结构体示例 # 让我们通过一个图书管理的例子来学习结构体的使用：\npackage main import \u0026#34;fmt\u0026#34; // Book 定义图书结构体 type Book struct { id int // 图书ID title string // 书名 author string // 作者 subject string // 主题 } 构造函数模式 # 在 Go 中，通常使用工厂函数来创建结构体实例，这是一种最佳实践：\n// NewBook 创建新的图书实例（构造函数） // 使用 New 开头的函数名是 Go 的约定 func NewBook(id int, title, author, subject string) *Book { return \u0026amp;Book{ id: id, title: title, author: author, subject: subject, } } 设计说明:\n返回指针类型 *Book 避免不必要的内存拷贝 使用具名字段初始化提高代码可读性 构造函数可以添加参数验证逻辑 方法定义 # 为结构体定义方法来实现行为：\n// String 实现 fmt.Stringer 接口，用于格式化输出 func (book *Book) String() string { return fmt.Sprintf(\u0026#34;ID: %d, 书名: %s, 作者: %s, 主题: %s\u0026#34;, book.id, book.title, book.author, book.subject) } // GetTitle 获取书名（Getter 方法） func (book *Book) GetTitle() string { return book.title } // SetTitle 设置书名（Setter 方法） func (book *Book) SetTitle(title string) { if title == \u0026#34;\u0026#34; { fmt.Println(\u0026#34;警告: 书名不能为空\u0026#34;) return } book.title = title } // GetInfo 获取图书详细信息 func (book *Book) GetInfo() map[string]interface{} { return map[string]interface{}{ \u0026#34;id\u0026#34;: book.id, \u0026#34;title\u0026#34;: book.title, \u0026#34;author\u0026#34;: book.author, \u0026#34;subject\u0026#34;: book.subject, } } 辅助函数 # // printBook 打印图书信息的辅助函数 func printBook(book *Book) { fmt.Printf(\u0026#34;id=%d,title=%s,author=%s,subject=%s\\n\u0026#34;, book.id, book.title, book.author, book.subject) book.id = 1000 } func main() { var book1 *Book book1 = new(Book) book1.id = 1001 book1.title = \u0026#34;go in action\u0026#34; book1.author = \u0026#34;james\u0026#34; book1.subject = \u0026#34;about golang\u0026#34; fmt.Println(book1) fmt.Println(book1.String()) book2 := Book{ id: 1002, title: \u0026#34;python in action\u0026#34;, author: \u0026#34;jordan\u0026#34;, subject: \u0026#34;about python\u0026#34;, } // book2 := Book{ // 1002, // \u0026#34;python in action\u0026#34;, // \u0026#34;jordan\u0026#34;, // \u0026#34;about python\u0026#34;, // } fmt.Println(book2) fmt.Println(\u0026#34;book2.title=\u0026#34;, book2.title) printBook(\u0026amp;book2) fmt.Println(book2) fmt.Println(book2.String()) book3 := NewBook(1004, \u0026#34;Java\u0026#34;, \u0026#34;gsl\u0026#34;, \u0026#34;Java in action\u0026#34;) fmt.Println(book3.String()) } ","date":"2021年8月15日","externalUrl":null,"permalink":"/posts/go-lang-struct/","section":"博客文章","summary":"\u003cp\u003eGo 语言中的结构体（Struct）是一种用户自定义的数据类型，用于将不同类型的数据组合在一起。虽然 Go 不是传统的面向对象语言，但通过结构体和方法，我们可以实现面向对象编程的核心概念。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是结构体 \n    \u003cdiv id=\"什么是结构体\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af%e7%bb%93%e6%9e%84%e4%bd%93\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e结构体是 Go 语言中实现数据封装的主要方式，它具有以下特点：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e数据聚合\u003c/strong\u003e: 将相关的数据字段组合在一起\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e类型安全\u003c/strong\u003e: 编译时检查字段类型\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e方法绑定\u003c/strong\u003e: 可以为结构体定义方法\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e内存效率\u003c/strong\u003e: 字段在内存中连续存储\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e基础结构体定义 \n    \u003cdiv id=\"基础结构体定义\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%9f%ba%e7%a1%80%e7%bb%93%e6%9e%84%e4%bd%93%e5%ae%9a%e4%b9%89\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e简单结构体示例 \n    \u003cdiv id=\"简单结构体示例\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ae%80%e5%8d%95%e7%bb%93%e6%9e%84%e4%bd%93%e7%a4%ba%e4%be%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e让我们通过一个图书管理的例子来学习结构体的使用：\u003c/p\u003e","title":"Go 语言结构体（Struct）详解与实践","type":"posts"},{"content":"","date":"2021年8月15日","externalUrl":null,"permalink":"/tags/struct/","section":"Tags","summary":"","title":"Struct","type":"tags"},{"content":"","date":"2021年8月15日","externalUrl":null,"permalink":"/tags/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/","section":"Tags","summary":"","title":"面向对象","type":"tags"},{"content":"","date":"2021年8月3日","externalUrl":null,"permalink":"/tags/dokcer-compose/","section":"Tags","summary":"","title":"Dokcer-Compose","type":"tags"},{"content":"","date":"2021年8月3日","externalUrl":null,"permalink":"/tags/email/","section":"Tags","summary":"","title":"Email","type":"tags"},{"content":"","date":"2021年8月3日","externalUrl":null,"permalink":"/tags/install/","section":"Tags","summary":"","title":"Install","type":"tags"},{"content":" 背景 # 由于 Gmail 邮箱在国内无法使用 且 国内手机 号码无法继续注册账号原因，最近想着折腾一下 自建一台 邮件服务器 玩玩。正好在国外有台 vps (搬瓦工的)，比较空闲，目前且用来做扶墙使用，有点浪费，决定使用此台服务器搭建 邮件服务器 。选择的部署方案选择了较为轻便的 docker，在了解网上主流的实现镜像后 analogic/poste.io \u0026amp; mailserver/docker-mailserver，由于目前 vps 资源为 2c 1g 的低配置，试着跑了一下前者，资源占用非常大(可能 默认开了什么功能什么的)， 在当前有限的配置下有点跑不动。了解后者这个 项目 后，可以在最低 1c 512m 下运行，最后决定选用后者 docker-mailserver 进行部署。\n环境说明 # Email 服务器: 23.105.194.216 (搬瓦工 vps)\n使用容器镜像: mailserver/docker-mailserver:latest\n系统版本: CentOS Linux release 7.9.2009 (Core)\n内核版本: 4.14.129-bbrplus\nDocker Version: 20.10.7\nDocker Compose Version: 1.29.2\nDNS 服务商: aliyun\n基础介绍 # 常见邮件服务端口 \u0026amp; 协议 介绍 # 详细介绍信息， 查看此文档\n协议 端口 SMTP 25/587/465 IMAP 143/993 POP3 110/995 常见 邮件软件 和 安全软件 介绍 # 软件名称 简介 sendmail 用于发邮件。资格最老的邮局，所有Linux发行版基本都带。但是配置麻烦。 postfix Wietse Venema 觉得 sendmail 配置太麻烦了，就开发了一个 \u0026ldquo;简化配置版 sendmail\u0026rdquo;，即postfix。支持smtp协议。 dovecot 用于收邮件，支持imap/pop3。 spamassasin 垃圾邮件过滤器。可以自订规则。 clamav 邮件杀毒工具。 opendkim 生成dkim签名。有什么用？详见下面的“反垃圾邮件技术”。 fail2ban 防止别人暴力破解用户名密码的工具。 反垃圾邮件技术介绍 # 邮件反垃圾技术（Anti-Spam）是指防止垃圾邮件（Spam）的技术手段。垃圾邮件通常是指发送大量不需要或不想要的邮件，这些邮件经常包含欺诈或诈骗信息、色情内容或恶意软件等。为了防止这些邮件占据用户的收件箱，邮件服务提供商和企业采用了一系列技术手段来防止垃圾邮件。常见的邮件反垃圾技术包括：\nSPF（Sender Policy Framework）技术。SPF 用于验证发送邮件的服务器是否在域名的授权列表中。如果不在，则认为是垃圾邮件。通过在域名的 DNS 中添加 TXT 记录，可以指定哪些服务器可以发送该域名的邮件。 DKIM（DomainKeys Identified Mail）技术。DKIM 是一种数字签名技术，通过在邮件头中添加一个签名，可以验证邮件是否被篡改过。使用 DKIM 技术可以有效防止垃圾邮件和欺诈邮件。 DMARC（Domain-based Message Authentication, Reporting and Conformance）技术。DMARC 技术是 SPF 和 DKIM 的加强版，它通过在域名的 DNS 中添加 TXT 记录，指定如何处理验证失败的邮件，可以有效地降低垃圾邮件和欺诈邮件的数量。 邮件过滤技术。邮件过滤技术是指通过人工智能算法和机器学习技术，对邮件进行分类和判断，判断哪些邮件是垃圾邮件，然后将其过滤掉。 黑名单和白名单技术。黑名单和白名单技术是一种基于地址列表的技术，通过将发件人的地址添加到黑名单或白名单中，来防止垃圾邮件。 Greylisting 技术。Greylisting 是一种基于时间的技术，当服务器接收到一个新的邮件时，将其延迟一段时间（通常是数分钟），然后再重新发送。由于大多数垃圾邮件发送服务器不会尝试重新发送，因此这种技术可以有效地过滤掉垃圾邮件。 通过这些技术的组合应用，可以有效地降低垃圾邮件的数量，保障用户的邮箱安全和隐私。\n安装部署 # 这里安装部署我们使用 Docker Compose，使用较低版本还不行，建议是按照下面的步骤 安装最新 的版本，如你已安装和配置，对下面部分配置做跳过即可。\n安装前准备工作 # 安装 docker\ncurl -fsSL https://get.docker.com -o get-docker.sh # 使用脚本安装 sudo sh get-docker.sh cat \u0026gt; /etc/docker/daemon.json \u0026lt;\u0026lt; EOF `# 配置优化` { \u0026#34;oom-score-adjust\u0026#34;: -1000, \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34;, \u0026#34;max-file\u0026#34;: \u0026#34;3\u0026#34; }, \u0026#34;max-concurrent-downloads\u0026#34;: 10, \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=systemd\u0026#34;], \u0026#34;max-concurrent-uploads\u0026#34;: 10, \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34;, \u0026#34;storage-opts\u0026#34;: [ \u0026#34;overlay2.override_kernel_check=true\u0026#34; ] } EOF systemctl start docker \\ `# 启动 并设置开机自启` \u0026amp;\u0026amp; systemctl enable docker \\ \u0026amp;\u0026amp; systemctl status docker 安装 docker-compose\ncurl -L \u0026#34;https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)\u0026#34; -o /usr/local/bin/docker-compose \\ \u0026amp;\u0026amp; chmod +x /usr/local/bin/docker-compose \\ \u0026amp;\u0026amp; ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose \\ \u0026amp;\u0026amp; docker-compose --version 阿里云设置 DNS 记录\n这里 设置的 DNS 未包含 DKIM 签名记录。这个记录需要在后面，使用脚本进行生成。\nDNS 类型 记录名 记录值 TTL A mail 23.105.194.216 30 分钟 MX @ mail.treesir.pub |10 (优先级 10) 30 分钟 TXT @ v=spf1 mx ~all 30 分钟 TXT _dmarc v=DMARC1; p=quarantine; rua=mailto:dmarc.report@treesir.pub; ruf=mailto:dmarc.report@treesir.pub; fo=0; adkim=r; aspf=r; pct=100; rf=afrf; ri=86400; sp=quarantine 30 分钟 表中 treesir.pub 注意替换为你自己的 主域名。\nVPS 服务器设置 反向解析 PTR (rdns) 记录\n使用 certbot 生成 letsencrypt 证书\nyum install -y certbot certbot certonly --manual --preferred-challenge dns -d mail.treesir.pub # `此 命令会生成 一条 TXT 记录记录用于验证` 将生成的 TXT 记录添加至 DNS 解析，添加后 使用 dig 进行检查解析是否生效后。生效 没有问题后 回车 继续。注意此条记录 不要删除，后续 续签证书时 还需要用到，正常情况下会在 /etc/letsencrypt/archive/xxx/ 下生成对应域名的证书。\nyum install bind-utils `# 安装 dig 命令` dig TXT _acme-challenge.mail.treesir.pub `# 检查 解析` 配置定时续签证书\n注意添加 crontab 定时任务时，检查一下 crond 服务器 是否有在正常运行。\ncrontab -e `# 打开 crontab 配置文件` 0 5 * * 1 /usr/bin/certbot renew --quiet service crond status `# 检查服务是否正常运行` 部署 docker-mailserver 容器镜像 # Clone 仓库地址 # git clone https://github.com/docker-mailserver/docker-mailserver.git 更改后的配置展示 # docker-compose.yml 文件\nversion: \u0026#39;3.8\u0026#39; services: mailserver: image: docker.io/mailserver/docker-mailserver:latest hostname: mail domainname: treesir.pub container_name: mailserver env_file: mailserver.env dns: 223.5.5.5 ports: - \u0026#34;25:25\u0026#34; - \u0026#34;143:143\u0026#34; - \u0026#34;587:587\u0026#34; - \u0026#34;993:993\u0026#34; - \u0026#34;110:110\u0026#34; - \u0026#34;995:995\u0026#34; volumes: - ./data/maildata:/var/mail - ./data/mailstate:/var/mail-state - ./data/maillogs:/var/log/mail - /etc/localtime:/etc/localtime:ro - ./config/:/tmp/docker-mailserver - /etc/letsencrypt/archive/mail.treesir.pub:/tmp/ssl:ro restart: always stop_grace_period: 1m cap_add: [ \u0026#34;NET_ADMIN\u0026#34;, \u0026#34;SYS_PTRACE\u0026#34; ] mailserver.env 文件\n这里由于文件较长，不方便演示，且打印未注释的代码段\ncat mailserver.env |egrep -v \u0026#39;^$|^#\u0026#39; OVERRIDE_HOSTNAME= DMS_DEBUG=1 SUPERVISOR_LOGLEVEL= ONE_DIR=1 POSTMASTER_ADDRESS=yangzun@treesir.pub ENABLE_UPDATE_CHECK=1 UPDATE_CHECK_INTERVAL=1d PERMIT_DOCKER=network NETWORK_INTERFACE= TLS_LEVEL= SPOOF_PROTECTION= ENABLE_SRS=0 ENABLE_POP3=1 ENABLE_CLAMAV=0 ENABLE_AMAVIS=1 AMAVIS_LOGLEVEL=0 ENABLE_FAIL2BAN=0 FAIL2BAN_BLOCKTYPE=drop ENABLE_MANAGESIEVE= POSTSCREEN_ACTION=enforce SMTP_ONLY= SSL_TYPE=manual SSL_CERT_PATH=/tmp/ssl/fullchain1.pem SSL_KEY_PATH=/tmp/ssl/privkey1.pem SSL_ALT_CERT_PATH= SSL_ALT_KEY_PATH= VIRUSMAILS_DELETE_DELAY= ENABLE_POSTFIX_VIRTUAL_TRANSPORT= POSTFIX_DAGENT= POSTFIX_MAILBOX_SIZE_LIMIT=102400000 ENABLE_QUOTAS=1 POSTFIX_MESSAGE_SIZE_LIMIT=102400000 PFLOGSUMM_TRIGGER= PFLOGSUMM_RECIPIENT= PFLOGSUMM_SENDER= LOGWATCH_INTERVAL= LOGWATCH_RECIPIENT= REPORT_RECIPIENT=0 REPORT_SENDER= REPORT_INTERVAL=daily POSTFIX_INET_PROTOCOLS=all ENABLE_SPAMASSASSIN=0 SPAMASSASSIN_SPAM_TO_INBOX=1 MOVE_SPAM_TO_JUNK=1 SA_TAG=2.0 SA_TAG2=6.31 SA_KILL=6.31 SA_SPAM_SUBJECT=***SPAM***** ENABLE_FETCHMAIL=0 FETCHMAIL_POLL=300 ENABLE_LDAP= LDAP_START_TLS= LDAP_SERVER_HOST= LDAP_SEARCH_BASE= LDAP_BIND_DN= LDAP_BIND_PW= LDAP_QUERY_FILTER_USER= LDAP_QUERY_FILTER_GROUP= LDAP_QUERY_FILTER_ALIAS= LDAP_QUERY_FILTER_DOMAIN= DOVECOT_TLS= DOVECOT_USER_FILTER= DOVECOT_PASS_FILTER= DOVECOT_MAILBOX_FORMAT=maildir DOVECOT_AUTH_BIND= ENABLE_POSTGREY=0 POSTGREY_DELAY=300 POSTGREY_MAX_AGE=35 POSTGREY_TEXT=Delayed by Postgrey POSTGREY_AUTO_WHITELIST_CLIENTS=5 ENABLE_SASLAUTHD=0 SASLAUTHD_MECHANISMS= SASLAUTHD_MECH_OPTIONS= SASLAUTHD_LDAP_SERVER= SASLAUTHD_LDAP_BIND_DN= SASLAUTHD_LDAP_PASSWORD= SASLAUTHD_LDAP_SEARCH_BASE= SASLAUTHD_LDAP_FILTER= SASLAUTHD_LDAP_START_TLS= SASLAUTHD_LDAP_TLS_CHECK_PEER= SASLAUTHD_LDAP_TLS_CACERT_FILE= SASLAUTHD_LDAP_TLS_CACERT_DIR= SASLAUTHD_LDAP_PASSWORD_ATTR= SASL_PASSWD= SASLAUTHD_LDAP_AUTH_METHOD= SASLAUTHD_LDAP_MECH= SRS_SENDER_CLASSES=envelope_sender SRS_EXCLUDE_DOMAINS= SRS_SECRET= DEFAULT_RELAY_HOST= RELAY_HOST= RELAY_PORT=25 RELAY_USER= RELAY_PASSWORD= 上面的 配置含义说明，请参考 此文档\n启动容器 \u0026amp; 后续配置工作 # docker-compose up -d `# 启动容器` docker-compose logs -f `# 观察容器 相关日志` 添加 email user # 服务器在公网，这里就不要使用 弱密码了，防止 hacker 进行 暴力破解。\n./setup.sh email add yangzun@treesir.pub \u0026#34;xxx\u0026#34; `# 添加 邮件账号及密码` ./setup.sh email update yangzun@treesir.pub \u0026#34;xxx\u0026#34; `# 更新 邮件账号及密码` 生成 DKIM 签名记录 # 注意下面的配置中，设置了 keysize 参数。原因是默认使用的 keysize 大小为 4096 ，在 aliyun中 无法进行正常的添加。\n./setup.sh config dkim keysize 2048 cat config/opendkim/keys/treesir.pub/mail.txt `# 查看 DKIM 签名记录` mail._domainkey IN TXT ( \u0026#34;v=DKIM1; h=sha256; k=rsa; \u0026#34; \u0026#34;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ojqOcyeDlOn6TcpXCsU92xul6d54wlx/UwPTuE0aywFc+ihyKGAm9D8nmroneN7gf82qTtDbJiHghzRlf6JdpR3kM4ipWKaNRSlUL/64HQqrMeEWx5ErpcgwrXxKWI/rcQ7Rjg2BliP6ayJiEflH0FOtxfgLHnYEcSKupmCV8znM4rZ/LHx9RDwc7o8jWujey6h9zrYPXyqim\u0026#34; \u0026#34;obGSB0PZGNQhe7mWRefMraFGgnNq+PrtEnmaOFxH2rG1Qh2hhMkeqJsH56yx9f1mxWYTX7r9FtvweGRb+GJNfi6a4vpDrTCffxx6XvGrq032i7VqHpmiaUUuM3j2x1DwHrIbpWTQIDAQAB\u0026#34; ) ; ----- DKIM key mail for treesir.pub 注意生成的记录，需要进行合并起来 才能使用。\n添加 DKIM 记录值至 aliyun # DNS 类型 记录名 记录值 TTL TXT mail._domainkey v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ojqOcyeDlOn6TcpXCsU92xul6d54wlx/UwPTuE0aywFc+ihyKGAm9D8nmroneN7gf82qTtDbJiHghzRlf6JdpR3kM4ipWKaNRSlUL/64HQqrMeEWx5ErpcgwrXxKWI/rcQ7Rjg2BliP6ayJiEflH0FOtxfgLHnYEcSKupmCV8znM4rZ/LHx9RDwc7o8jWujey6h9zrYPXyqimobGSB0PZGNQhe7mWRefMraFGgnNq+PrtEnmaOFxH2rG1Qh2hhMkeqJsH56yx9f1mxWYTX7r9FtvweGRb+GJNfi6a4vpDrTCffxx6XvGrq032i7VqHpmiaUUuM3j2x1DwHrIbpWTQIDAQAB 30 分钟 验证测试 # 测试的平台为 MacOS \u0026amp; Windows 两个平台 ，由于先前测试使用的工具为 Foxmail 会出现无法发送邮件问题 ，所以这里 MacOS 中使用的 邮件工具为 自带应用 Mail ，Windows 邮件工具使用 eM Clien。\n证书验证测试 # 在进行邮件测试前，可以使用 此链接 中的工具，测试 是否支持 tls 验证。\nMacOS \u0026amp; Windows 添加邮件记录 # macos\nwindow\neM Clien 工具会自动为我们配置 IMAP \u0026amp; SMTP 地址信息。\n测试邮件发送 # Windows to MacOS\n正常收到邮件了\nMacos to Windows\n回复一个邮件 进行测试\n测试 邮件收发 一切正常。\nSend to Gmail\n测试发送邮件至 Gmail\n正常收到邮件\n参考文档 # https://www.itmanbu.com/docker-mail-server.html https://wiki.dovecot.org/TestInstallation https://docker-mailserver.github.io/docker-mailserver/edge/ 总结 # 一开始使用的 工具是 Foxmail 工具进行测试，卡在无法发送邮件这里，以为是自己的证书配置出错了，经过一系类的折腾、排错，花了一天左右的时间总算是搭建出来了。看到其相关文档，好像还可以与 LDAP 进行集成，这就不错了，有时间 继续研究一下。\n","date":"2021年8月3日","externalUrl":null,"permalink":"/posts/docker-deploy-mailserver/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e背景 \n    \u003cdiv id=\"背景\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%83%8c%e6%99%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e由于 \u003ccode\u003eGmail\u003c/code\u003e 邮箱在国内无法使用 且 \u003ccode\u003e国内手机\u003c/code\u003e 号码无法继续注册账号原因，最近想着折腾一下 自建一台 \u003ccode\u003e邮件服务器\u003c/code\u003e 玩玩。正好在国外有台 vps (搬瓦工的)，比较空闲，目前且用来做扶墙使用，有点浪费，决定使用此台服务器搭建 \u003ccode\u003e邮件服务器\u003c/code\u003e 。选择的部署方案选择了较为轻便的 \u003ccode\u003edocker\u003c/code\u003e，在了解网上主流的实现镜像后 \u003ccode\u003eanalogic/poste.io\u003c/code\u003e \u0026amp; \u003ccode\u003emailserver/docker-mailserver\u003c/code\u003e，由于目前 vps 资源为 \u003ccode\u003e2c 1g\u003c/code\u003e 的低配置，试着跑了一下前者，资源占用非常大(可能 默认开了什么功能什么的)， 在当前有限的配置下有点跑不动。了解后者这个 \u003ca\n  href=\"https://github.com/docker-mailserver/docker-mailserver/\"\n    target=\"_blank\"\n  \u003e项目\u003c/a\u003e 后，可以在最低 \u003ccode\u003e1c 512m\u003c/code\u003e 下运行，最后决定选用后者 \u003ccode\u003edocker-mailserver\u003c/code\u003e 进行部署。\u003c/p\u003e","title":"使用 Docker Compose 5分钟 部署 一台邮件服务器","type":"posts"},{"content":"","date":"2021年7月29日","externalUrl":null,"permalink":"/tags/flannel/","section":"Tags","summary":"","title":"Flannel","type":"tags"},{"content":"","date":"2021年7月29日","externalUrl":null,"permalink":"/categories/k3s/","section":"Categories","summary":"","title":"K3s","type":"categories"},{"content":" 背景说明 # 在 之前搭建 的 k3s 集群中因为某些原因我将 openwrt 节点，进行了系统重装，更改固件为了 esir 高大全的 op 固件，由于其 固件中没有将 vxlan 模块编译进内核当中，而 k3s 默认 使用的 cni 为 flannel 的 vxlan 模式，导致在初始化节点的时候会出现错误，导致节点添加不成功，我们知道原生 flannel 支持的模式不只单单只有 vxlan，还支持 host-gw、udp 模式。进行查阅 k3s 相关资料，看到 k3s 是支持切换多种网络模式的，于是决定将 flannel 模式更改为 host-gw 。\n节点说明 # IP 地址 节点名称 机型 配置 操作系统 节点角色 192.168.8.1 openwrt 占美 (机型不详) 4c 2g ( cpu N2940 ) openWrt（X86_64 esir 高大全） node/agent 192.168.8.112 n1 斐讯 N1 4c 2g Armbian ( 5.0.2-aml-s905) master/server 192.168.8.113 n2 斐讯 N1 4c 2g Armbian ( 5.0.2-aml-s905) node/agent 192.168.8.114 n3 斐讯 N1 4c 2g Armbian ( 5.0.2-aml-s905) node/agent 更改模式为 host-gw # 由于我目前的环境，n1、n2、n3 已组建为了 k3s 集群，使用的网络模式为 默认模式 vxlan ，这里需要进行更改即可。而 openwrt 这个节点是需要加入的节点，打算将其加入为 agent 节点。组建的集群规模为: 一主三从 。\n原规模 集群更改模式 # 这里指 n1、n2、n3 这三台节点。下面步骤分别在各节点，进行相同操作进行更改。\nvim `systemctl status k3s 2\u0026gt;\u0026amp;1|grep loaded|awk -F \u0026#39;[(;]\u0026#39; \u0026#39;{print $2}\u0026#39;` # 编辑 k3s 配置文件, 加入下述配置 ... \u0026#39;--flannel-backend\u0026#39; \\ \u0026#39;host-gw\u0026#39; ... 重启生效 # systemctl daemon-reload \\ \u0026amp;\u0026amp; systemctl restart k3s.service 检查是否生效 # apt install sshpass for i in `seq 1 3`;do sshpass -p \u0026#39;xxxx\u0026#39; ssh -t -o StrictHostKeyChecking=no n\u0026#34;${i}\u0026#34; \u0026#39;cat /var/lib/rancher/k3s/agent/etc/flannel/net-conf.json\u0026#39;;done 可以看到，即 /var/lib/rancher/k3s/agent/etc/flannel/net-conf.json 这个文件中，显示 type 为 host-gw 即 可以\n跨集群通讯也是没有问题的。\n配置加入节点 # 原集群测试没有问题后，尝试对 openwrt 节点进行纳入集群中，进行管理。\n测试启动 # 这里 openwrt 安装 k3s 的步骤，部分省略，可以参考我早期整理的 文档\nk3s agent --server https://192.168.8.112:6443 --token K106ccb265579f1e40038844787c9ce4dd8528b6c58e26894e953661c9a907ef5d2::server:4843c59507b6840cb1c3bea454570f85 --docker --kube-apiserver-arg service-node-port-range=40000-65000 --no-deploy traefik --write-kubeconfig ~/.kube/config --write-kubeconfig-mode 666 测试启动后，在 master 节点中查看，相关节点是否就绪。可以看到我这里是没有问题的。\n配置 k3s 为服务，并设置开机自启 # 相关配置文件展示\ncat /etc/config/k3s config globals \u0026#39;globals\u0026#39; option opts \u0026#39;--server https://192.168.8.112:6443 --flannel-backend host-gw --token K106ccb265579f1e40038844787c9ce4dd8528b6c58e26894e953661c9a907ef5d2::server:4843c59507b6840cb1c3bea454570f85 --docker --kube-apiserver-arg service-node-port-range=40000-65000 --no-deploy traefik --write-kubeconfig ~/.kube/config --write-kubeconfig-mode 666\u0026#39; option root \u0026#39;/application/k3s\u0026#39; cat /etc/init.d/k3s #!/bin/sh /etc/rc.common START=60 STOP=20 PIDFILE=/var/run/k3s.pid EXEC=\u0026#34;/usr/bin/k3s\u0026#34; ensure_cgroup_mount() { # Unmount /sys/fs/cgroup if mounted as cgroup grep -q \u0026#39; /sys/fs/cgroup cgroup\u0026#39; /proc/self/mounts \u0026amp;\u0026amp; umount /sys/fs/cgroup grep -q \u0026#39; /sys/fs/cgroup tmpfs\u0026#39; /proc/self/mounts \\ || mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup for sys in $(awk \u0026#39;!/^#/ { if ($4 == 1) print $1 }\u0026#39; /proc/cgroups); do mnt=\u0026#34;/sys/fs/cgroup/$sys\u0026#34; grep -q \u0026#34;cgroup $mnt \u0026#34; /proc/self/mounts \u0026amp;\u0026amp; continue mkdir -p \u0026#34;$mnt\u0026#34; mount -n -t cgroup -o $sys cgroup \u0026#34;$mnt\u0026#34; done } start() { ensure_cgroup_mount start-stop-daemon -S -b -x \u0026#34;$EXEC\u0026#34; -m -p \u0026#34;$PIDFILE\u0026#34; \\ -- agent $(uci get k3s.globals.opts) \\ --data-dir $(uci get k3s.globals.root) } stop() { start-stop-daemon -K -p \u0026#34;$PIDFILE\u0026#34; 设置开机自启\nchmod a+x /etc/init.d/k3s /etc/init.d/k3s start \\ \u0026amp;\u0026amp; /etc/init.d/k3s enable 防火墙策略开放 # 由于 openwrt 中对，网络进行的 策略管理，这里我们要想 跨节点与 openwrt 中的 pod 进行通讯时，还需要将对应的 接口 进行开放一下。步骤实现方式在之前 整理 的 文档当中。\n测试 openwrt pod 网络通讯 # 这里已我目前集群中的 blog 服务为示例进行测试。使用 dashboard 为 lens 5.x。\n删除 pod ，触发 pod 重新调度，使其调度到 openwrt 节点当中。\n在 n1 节点当中，测试访问 openwrt 节点 pod，查看 pod 之间网络是否通畅。\n访问对应的 pod ip，正常返回 http 状态码 200, 即表示网络通畅且正常，此次集群升级结束。\n参考文档 # https://rancher.com/docs/k3s/latest/en/installation/install-options/server-config/#networking\n总结 # 由于我这里在家里面搭的是一套 娱乐 性集群，部署的服务也是一些轻量级的应用，对性能什么的要求不高。 如果是生产环境的话，可能对相关的性能表现是比较在意，默认的 k3s kube-porxy 的模式是 使用的 iptables，一般生产是建议使用 ipvs 性能好一下，如有需求可以参加此篇 文档 进行实现。\n","date":"2021年7月29日","externalUrl":null,"permalink":"/posts/k3s-change-flannel-cni-model/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e背景说明 \n    \u003cdiv id=\"背景说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%83%8c%e6%99%af%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e在 \u003ca\n  href=\"https://www.treesir.pub/post/n1-openwrt-k3s-deploy/\"\n    target=\"_blank\"\n  \u003e之前搭建\u003c/a\u003e 的 k3s 集群中因为某些原因我将 openwrt 节点，进行了系统重装，更改固件为了 \u003ccode\u003eesir\u003c/code\u003e 高大全的 op 固件，由于其 固件中没有将 \u003ccode\u003evxlan\u003c/code\u003e 模块编译进内核当中，而 k3s \u003ccode\u003e默认\u003c/code\u003e 使用的 cni 为 \u003ccode\u003eflannel 的 vxlan\u003c/code\u003e 模式，导致在初始化节点的时候会出现错误，导致节点添加不成功，我们知道原生 flannel 支持的模式不只单单只有 vxlan，还支持 \u003cstrong\u003ehost-gw\u003c/strong\u003e、\u003cstrong\u003eudp\u003c/strong\u003e 模式。进行查阅 k3s 相关资料，看到 k3s 是支持切换多种网络模式的，于是决定将 flannel 模式更改为 \u003ccode\u003ehost-gw\u003c/code\u003e 。\u003c/p\u003e","title":"K3s 集群修改 Flannel CNI 插件网络模式","type":"posts"},{"content":"","date":"2021年7月29日","externalUrl":null,"permalink":"/tags/n1/","section":"Tags","summary":"","title":"N1","type":"tags"},{"content":"","date":"2021年7月29日","externalUrl":null,"permalink":"/categories/openwrt/","section":"Categories","summary":"","title":"Openwrt","type":"categories"},{"content":"","date":"2021年7月29日","externalUrl":null,"permalink":"/categories/%E7%BD%91%E7%BB%9C/","section":"Categories","summary":"","title":"网络","type":"categories"},{"content":"","date":"2021年7月22日","externalUrl":null,"permalink":"/tags/fix/","section":"Tags","summary":"","title":"Fix","type":"tags"},{"content":"","date":"2021年7月22日","externalUrl":null,"permalink":"/tags/jenkins/","section":"Tags","summary":"","title":"Jenkins","type":"tags"},{"content":" 问题描述 # 在 DevOps 集成环境的测试环境中，发现多条 Pipeline 持续处于构建超时状态。初步排查以为是依赖版本兼容性问题导致 pip 无法找到合适版本，与开发团队联合排查后发现：开发环境正常，测试环境异常，且使用相同的代码和 Dockerfile 文件，排除了版本相关问题。具体错误表现如下：\n检查 Nexus3 私服日志\ndocker logs -f --tail 100 nexus3 检查对应 blob 中的文件内容发现为空，结合日志分析，初步判断是 blob storage 对应的存储数据丢失导致。\n修复过程 # 方法一：重建数据库索引 # 尝试对数据库进行重建索引修复。Nexus3 内部使用 OrientDB 数据库，相关文档可供参考。\ndocker exec -it nexus3 bash cd /nexus-data java -jar /opt/sonatype/nexus/lib/support/nexus-orient-console.jar # 用户名/密码: admin admin（默认密码） CONNECT PLOCAL:/nexus-data/db/component/ admin admin REBUILD INDEX * REPAIR DATABASE --fix-graph REPAIR DATABASE --fix-links REPAIR DATABASE --fix-ridbags REPAIR DATABASE --fix-bonsai DISCONNECT 重新触发 Pipeline 测试，问题依然存在，此方法无效。\n方法二：备份恢复数据 # 备份 Nexus3 数据，删除旧数据，然后基于备份数据进行恢复。\n创建备份数据任务\n选择备份导出数据库\n设置备份任务\n启动备份任务\n等待任务执行成功\n由于导出过程中被中断，数据无法导出，此方法暂时放弃。\n方法三：Blob Storage 元数据修复 # 创建任务对 Blob Storage 存储进行元数据修复。\n创建修复 Blob 元数据任务\n选择需要修复的 Blob Storage\n等待任务执行完毕。\n再次执行数据重建索引任务\ndocker exec -it nexus3 bash cd /nexus-data java -jar /opt/sonatype/nexus/lib/support/nexus-orient-console.jar # 用户名/密码: admin admin（默认密码） CONNECT PLOCAL:/nexus-data/db/component/ admin admin REBUILD INDEX * REPAIR DATABASE --fix-graph REPAIR DATABASE --fix-links REPAIR DATABASE --fix-ridbags REPAIR DATABASE --fix-bonsai DISCONNECT 重建索引后出现新问题\n部分包显示 404 错误。\n修复依赖拉取 404 问题 # 创建重建私服浏览和搜索功能的任务。\n创建相关任务\n选择对所有仓库生效\n启动重建任务\n等待任务结束\n再次启动 Pipeline 测试，仍然报错，开启 TRACE 级别日志查看详情\n注意：TRACE 日志级别建议仅在测试时开启，使用后及时关闭，日志内容过多。\n从日志中可以看到 Could not dispatch event AssetCreatedEvent 报错。在社区寻找解决方案，暂未找到合适的方法。\n继续尝试重建索引\ndocker exec -it nexus3 bash cd /nexus-data java -jar /opt/sonatype/nexus/lib/support/nexus-orient-console.jar # 用户名/密码: admin admin（默认密码） CONNECT PLOCAL:/nexus-data/db/component/ admin admin REBUILD INDEX * 执行重建索引时直接报错，提示发现重复的 key。\n执行删除操作，重启系统后将自动完成索引重建\ndrop class browse_node DISCONNECT exit 再次进行数据库重建索引及修复操作\ndocker exec -it nexus3 bash cd /nexus-data java -jar /opt/sonatype/nexus/lib/support/nexus-orient-console.jar # 用户名/密码: admin admin（默认密码） CONNECT PLOCAL:/nexus-data/db/component/ admin admin REBUILD INDEX * REPAIR DATABASE --fix-graph REPAIR DATABASE --fix-links REPAIR DATABASE --fix-ridbags REPAIR DATABASE --fix-bonsai DISCONNECT docker restart nexus3 再次执行时，已不再出现重复 key 的报错。\n重启后查看系统日志\n从日志中可以看到，Nexus 正在对存储库浏览树进行重建工作，此时已从日志中看出重建完成。\n观察后续日志输出无相关错误，尝试触发 Pipeline 测试\n可以看到已能正常拉取 PyPI 包依赖。\n参考链接 # https://community.sonatype.com/t/unable-to-reach-metadata-file-instead-get-http-404-nof-found-error/5357 https://issues.sonatype.org/browse/NEXUS-21814?jql=text%20~%20%22AssetCreatedEvent%22 https://orientdb.com/docs/last/index.html?q=delete 总结 # 经过此次事件，发现 Nexus3 的 bug 确实较多，建议生产环境使用社区反响较好、bug 较少的稳定版本。\n此类问题我在一年前也遇到过，当时采用的解决方式是创建新的 Blob 存储，将有问题的私服进行迁移。这种方式相对简单，但如果有多个私服共用一个 Blob 存储时，数据需要重新上传，迁移过程会比较麻烦和繁琐。\n建议：为不同环境和不同类型的私服设置独立的 Blob Storage，以分散风险。\n","date":"2021年7月22日","externalUrl":null,"permalink":"/posts/nexus3-pypi-blob-storage-fix/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e问题描述 \n    \u003cdiv id=\"问题描述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%97%ae%e9%a2%98%e6%8f%8f%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在 DevOps 集成环境的测试环境中，发现多条 Pipeline 持续处于构建超时状态。初步排查以为是依赖版本兼容性问题导致 pip 无法找到合适版本，与开发团队联合排查后发现：开发环境正常，测试环境异常，且使用相同的代码和 Dockerfile 文件，排除了版本相关问题。具体错误表现如下：\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"image-20210721170700491\" src=\"https://cdn.treesir.pub/img/image-20210721170700491.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e检查 Nexus3 私服日志\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker logs -f --tail \u003cspan class=\"m\"\u003e100\u003c/span\u003e nexus3\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"image-20210721171037634\" src=\"https://cdn.treesir.pub/img/image-20210721171037634.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"Nexus3 PyPI 私服 Blob Storage 异常修复记录","type":"posts"},{"content":"","date":"2021年7月22日","externalUrl":null,"permalink":"/tags/pipeline/","section":"Tags","summary":"","title":"Pipeline","type":"tags"},{"content":"","date":"2021年7月22日","externalUrl":null,"permalink":"/tags/pypi/","section":"Tags","summary":"","title":"Pypi","type":"tags"},{"content":"","date":"2021年7月20日","externalUrl":null,"permalink":"/tags/jupyterlab/","section":"Tags","summary":"","title":"Jupyterlab","type":"tags"},{"content":" 背景 # 在所关联项目 的 jenkins pipeline ci 流水线中，开发告诉我 生成的 pip 包中未有生成相关的 labextension 依赖文件夹，导致 pip 包虽然是安装成功了，但仍是一个不可用的状态。开发人员告诉我在 linux \u0026amp; windows 环境下 均是可以正常生成相关依赖文件夹的，问我是不是哪里配置有点问题，思索了一下 和他说会不会是 jenkins slave 使用的 是 容器 的原因？给了他一个启动命令，测试在容器里面生成一下包看看，启动命令如下所示:\ndocker run -it --rm --name test --entrypoint /bin/bash idocker.io/base/jenkins/inbound-agent:4.3-8-jdk11 后续经过开发一番操作后，仍然是可以生成正常依赖包，这就让我感觉有点奇怪了，于是就有了此篇文档，关于我对此问题的逐一定位。\n逐一排查定位 # 启动容器，尝试执行相关命令 # 容器启动命令，使用和上面一样的命令，覆盖默认的 entrypoint 语法，改用 /bin/bash ，模拟 jenkins slave 真实的打包过程。\n使用 jenkins pipeline 后的 日志输出\n观察对应的日志输出，在执行 python3 setup.py bdist_wheel 后有出现 jlpm install 对应的命令才能生成出正常可使用的 pip 依赖包。\n测试 pipeline (一) # 此 pipeline 目的是检查，相关的 版本是不是一致。\n#!groovy @Library(\u0026#39;jenkinslibrary@master\u0026#39;) _ def todingtalk = new org.devops.todingtalk() def tools = new org.devops.tools() pipeline{ agent { kubernetes{ cloud \u0026#39;rancher-dev\u0026#39; yaml libraryResource(\u0026#39;jenkins/slave.yaml\u0026#39;) } } stages{ stage(\u0026#39;Clean \\u2756\u0026#39;) { steps { script{ cleanWs() } } } stage(\u0026#39;GetBuildUser \\u2756\u0026#39;) { steps { script { tools.GetBuildUser() sh \u0026#39;\u0026#39;\u0026#39; jlpm --version jupyter --version \u0026#39;\u0026#39;\u0026#39; println \u0026#34;${currentBuild.durationString.replace(\u0026#39; and counting\u0026#39;, \u0026#39;\u0026#39;)}\u0026#34; } } } } } 比对相关软件版本，打印的输出是一样的， 依赖版本 \u0026amp; jenkins slave 相关镜像 的使用应该是没有什么问题的。\n测试 pipeline (二) # 检查版本一致后，尝试再次进行一次打包\n#!groovy @Library(\u0026#39;jenkinslibrary@master\u0026#39;) _ def todingtalk = new org.devops.todingtalk() def tools = new org.devops.tools() pipeline{ agent { kubernetes{ cloud \u0026#39;rancher-dev\u0026#39; yaml libraryResource(\u0026#39;jenkins/slave.yaml\u0026#39;) } } stages{ stage(\u0026#39;Clean \\u2756\u0026#39;) { steps { script{ cleanWs() } } } stage(\u0026#34;CheckOut \\u2756\u0026#34;){ steps{ script{ tools.PrintMes(\u0026#34;代码检出\u0026#34;,\u0026#34;green\u0026#34;) checkout([ $class: \u0026#39;GitSCM\u0026#39;, branches: [[name: \u0026#34;dev\u0026#34;]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [ [credentialsId: \u0026#39;4852cf10-0ced-4b88-bdbe-22edac8f4de0\u0026#39;, url: \u0026#34;http://glb.ac.com/diting/jupyter-resource-usage-server.git\u0026#34; ] ] ]) } } } stage(\u0026#39;GetBuildUser \\u2756\u0026#39;) { steps { script { tools.GetBuildUser() sh \u0026#39;\u0026#39;\u0026#39; jlpm --version jupyter --version rm -rf .git \\ \u0026amp;\u0026amp; python3 setup.py bdist_wheel \u0026#39;\u0026#39;\u0026#39; println \u0026#34;${currentBuild.durationString.replace(\u0026#39; and counting\u0026#39;, \u0026#39;\u0026#39;)}\u0026#34; } } } } } 日志输出可以看到，一样的效果没有执行 jlpm 相关子命令，确认了 镜像 和 软件版本不是问题因素。\n测试 pipeline (三) # 尝试 使用 nohup 进行后台运行，看看是不是 那个 终端参数设置错误 问题导致。\n#!groovy @Library(\u0026#39;jenkinslibrary@master\u0026#39;) _ def todingtalk = new org.devops.todingtalk() def tools = new org.devops.tools() pipeline{ agent { kubernetes{ cloud \u0026#39;rancher-dev\u0026#39; yaml libraryResource(\u0026#39;jenkins/slave.yaml\u0026#39;) } } stages{ stage(\u0026#39;Clean \\u2756\u0026#39;) { steps { script{ cleanWs() } } } stage(\u0026#34;CheckOut \\u2756\u0026#34;){ steps{ script{ tools.PrintMes(\u0026#34;代码检出\u0026#34;,\u0026#34;green\u0026#34;) checkout([ $class: \u0026#39;GitSCM\u0026#39;, branches: [[name: \u0026#34;dev\u0026#34;]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [ [credentialsId: \u0026#39;4852cf10-0ced-4b88-bdbe-22edac8f4de0\u0026#39;, url: \u0026#34;http://glb.ac.com/diting/jupyter-resource-usage-server.git\u0026#34; ] ] ]) } } } stage(\u0026#39;GetBuildUser \\u2756\u0026#39;) { steps { script { tools.GetBuildUser() sh \u0026#39;\u0026#39;\u0026#39; jlpm --version jupyter --version rm -rf .git nohup python3 setup.py bdist_wheel \u0026gt;\u0026gt; nohup.out 2\u0026gt;\u0026amp;1 \u0026amp; sleep 10 \\ \u0026amp;\u0026amp; head -n 100 nohup.out \u0026#39;\u0026#39;\u0026#39; println \u0026#34;${currentBuild.durationString.replace(\u0026#39; and counting\u0026#39;, \u0026#39;\u0026#39;)}\u0026#34; } } } } } 后台的日志输出中，还是没有运行 jlpm 相关子命令，排除了 终端参数设置错误的问题。但是仔细观察相关的日志后，发现 在打包过程中在读取 MANIFEST.in 的逻辑时，是有几个警告信息，是不是和 MANIFEST.in 里面的类容有关系呢？\n查看对应的 MANIFEST.in 文件内容\n观察 MANIFEST.in 这个文件内容，看到此文件中有 关联 .git 的逻辑说明，是不是 pipeline 中有做 rm -rm .git步骤，从而影响到打包了呢？\n测试 pipeline (四) # 将 rm -rf .git 步骤去掉，再次执行一下打包逻辑。\n#!groovy @Library(\u0026#39;jenkinslibrary@master\u0026#39;) _ def todingtalk = new org.devops.todingtalk() def tools = new org.devops.tools() pipeline{ agent { kubernetes{ cloud \u0026#39;rancher-dev\u0026#39; yaml libraryResource(\u0026#39;jenkins/slave.yaml\u0026#39;) } } stages{ stage(\u0026#39;Clean \\u2756\u0026#39;) { steps { script{ cleanWs() } } } stage(\u0026#34;CheckOut \\u2756\u0026#34;){ steps{ script{ tools.PrintMes(\u0026#34;代码检出\u0026#34;,\u0026#34;green\u0026#34;) checkout([ $class: \u0026#39;GitSCM\u0026#39;, branches: [[name: \u0026#34;dev\u0026#34;]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [ [credentialsId: \u0026#39;4852cf10-0ced-4b88-bdbe-22edac8f4de0\u0026#39;, url: \u0026#34;http://glb.ac.com/diting/jupyter-resource-usage-server.git\u0026#34; ] ] ]) } } } stage(\u0026#39;GetBuildUser \\u2756\u0026#39;) { steps { script { tools.GetBuildUser() sh \u0026#39;\u0026#39;\u0026#39; jlpm --version jupyter --version python3 setup.py bdist_wheel \u0026#39;\u0026#39;\u0026#39; println \u0026#34;${currentBuild.durationString.replace(\u0026#39; and counting\u0026#39;, \u0026#39;\u0026#39;)}\u0026#34; } } } } } 此时再次查看 对应流水线的日志输出时，惊喜的发现 已有 jlpm 命令执行记录的输出。为验证 包的完整性，我将对应的 whl 包进行了下载并使用工具进行解压查看，看到已正常生成对应的 labextension 依赖文件夹。后续修改了 线上 pipeline 逻辑后，rebuild 后，让开发进行检查后，是正常的了，此致问题解决。\n总结 # 在处理技术问题的过程中，个人感觉，第一个是需要多多观察相关程序的日志输出，其实很多解决问题的方法多在日志中有表现，很多人就是不会去检查日志。第二个是要了解其相关技术的 实现原理。做技术的光知道怎么用是不够的，实现原理也需要进行了解才行，这样碰到稀奇古怪的问题后，能够洞察分析问题的本质，精准定位并解决。\ntoDo # ","date":"2021年7月20日","externalUrl":null,"permalink":"/posts/pipeline-jupyter-labextension-build-fix/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e背景 \n    \u003cdiv id=\"背景\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%83%8c%e6%99%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e在所关联项目 的 jenkins pipeline ci 流水线中，开发告诉我 生成的 pip 包中未有生成相关的 \u003ccode\u003elabextension\u003c/code\u003e 依赖文件夹，导致 pip 包虽然是安装成功了，但仍是一个不可用的状态。开发人员告诉我在 \u003ccode\u003elinux\u003c/code\u003e \u0026amp; \u003ccode\u003ewindows\u003c/code\u003e 环境下 均是可以正常生成相关依赖文件夹的，问我是不是哪里配置有点问题，思索了一下 和他说会不会是 jenkins slave 使用的 是 \u003ccode\u003e容器\u003c/code\u003e 的原因？给了他一个启动命令，测试在容器里面生成一下包看看，启动命令如下所示:\u003c/p\u003e","title":"记录一次 JupyterLab 插件打包问题的修复","type":"posts"},{"content":"","date":"2021年7月13日","externalUrl":null,"permalink":"/categories/efk/","section":"Categories","summary":"","title":"Efk","type":"categories"},{"content":" 背景 # 在日常使用 efk 日志系统的过程中，每天造成的日志索引量是巨大的，需要进行对相关的索引进行定期清理，来缓解后端储存的占用。如何实现在 kubernetes 中进行优雅的定期清理？本文将介绍使用 kubernetes 中的 Cronjob 资源对象进行其需求的实现。\n常规情况下清理 n day 以前的数据\ncurl -XDELETE http://elasticsearch-logging.kube-system:9200/logstash-`date -d\u0026#34;n days ago\u0026#34; +\u0026#34;%Y.%m.%d\u0026#34;` 这种方法，是网上比较常规的方式，如某一天忘记执行了，就会出现数据未清理干净的情况，有点不优雅，如何解决？\n进行优化后的 清理策略\ncleanPrefixPath=\u0026#39;k8s-\u0026#39; agoCleanTime=`date -d \u0026#34;n days ago\u0026#34; +%s` for i in `curl http://user:pass@es_url/_cat/indices?v 2\u0026gt;\u0026amp;1 |awk -F \u0026#39;[ ]+\u0026#39; \u0026#39;{print $3}\u0026#39;|sort -n |grep -v \u0026#39;^\\.\u0026#39;|egrep \u0026#34;${cleanPrefixPath}[0-9]+.[0-9]+.[0-9]+\u0026#34;`;do secTime=`echo ${i#*-}|tr \u0026#39;.\u0026#39; \u0026#39;-\u0026#39;|xargs -I {} date -d \u0026#34;{}\u0026#34; +%s` if [ \u0026#34;${secTime}\u0026#34; -le \u0026#34;${agoCleanTime}\u0026#34; ];then curl -XDELETE http://user:pass@es_url/\u0026#34;${i}\u0026#34; fi done 转换为Yaml资源清单后呢？ # # es-clean-cronjob.yaml apiVersion: v1 kind: ConfigMap metadata: name: es-auth-up data: user: \u0026#34;elastic\u0026#34; password: \u0026#34;xxxxx\u0026#34; es_url: \u0026#34;elasticsearch-data.logging:9200\u0026#34; --- apiVersion: batch/v1beta1 # or batch/v1 kind: CronJob metadata: name: es-clean-job spec: schedule: \u0026#34;0 12 * * *\u0026#34; # 每天中午 12:00 执行 jobTemplate: spec: template: spec: containers: - name: es-clean-job image: idocker.io/centos:7 imagePullPolicy: IfNotPresent env: - name: ES_USER valueFrom: configMapKeyRef: name: es-auth-up key: user - name: ES_PASS valueFrom: configMapKeyRef: name: es-auth-up key: password - name: ES_URL valueFrom: configMapKeyRef: name: es-auth-up key: es_url command: [\u0026#34;/bin/bash\u0026#34;] args: - -c - | set -eu ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime cleanPrefixPath=\u0026#39;k8s-\u0026#39; # 删除前缀, 设置为空时将对所有索引生效 agoCleanTime=`date -d \u0026#34;30 days ago\u0026#34; +%s` # 删除 30 天以前的日志 for i in `curl http://\u0026#34;${ES_USER}\u0026#34;:\u0026#34;${ES_PASS}\u0026#34;@\u0026#34;${ES_URL}\u0026#34;/_cat/indices?v 2\u0026gt;\u0026amp;1 |awk -F \u0026#39;[ ]+\u0026#39; \u0026#39;{print $3}\u0026#39;|sort -n |grep -v \u0026#39;^\\.\u0026#39;|egrep \u0026#34;${cleanPrefixPath}[0-9]+.[0-9]+.[0-9]+\u0026#34;`;do secTime=`echo ${i#*-}|tr \u0026#39;.\u0026#39; \u0026#39;-\u0026#39;|xargs -I {} date -d \u0026#34;{}\u0026#34; +%s` if [ \u0026#34;${secTime}\u0026#34; -le \u0026#34;${agoCleanTime}\u0026#34; ];then curl -XDELETE http://\u0026#34;${ES_USER}\u0026#34;:\u0026#34;${ES_PASS}\u0026#34;@\u0026#34;${ES_URL}\u0026#34;/\u0026#34;${i}\u0026#34; fi done restartPolicy: Never backoffLimit: 2 执行创建\nkubectl apply -f ./es-clean-cronjob.yaml -n logging 示例的索引结构\n执行时的日志输出\n问题记录 # Cronjob 时区问题 ToDo # ","date":"2021年7月13日","externalUrl":null,"permalink":"/posts/cronjob-clean-es-index/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e背景 \n    \u003cdiv id=\"背景\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%83%8c%e6%99%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e在日常使用 \u003ccode\u003eefk\u003c/code\u003e 日志系统的过程中，每天造成的日志索引量是巨大的，需要进行对相关的索引进行定期清理，来缓解后端储存的占用。如何实现在 kubernetes 中进行优雅的定期清理？本文将介绍使用 kubernetes 中的 \u003ccode\u003eCronjob\u003c/code\u003e 资源对象进行其需求的实现。\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e\u003cstrong\u003e常规情况下清理 \u003ccode\u003en\u003c/code\u003e day 以前的数据\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl -XDELETE http://elasticsearch-logging.kube-system:9200/logstash-\u003cspan class=\"sb\"\u003e`\u003c/span\u003edate -d\u003cspan class=\"s2\"\u003e\u0026#34;n days ago\u0026#34;\u003c/span\u003e +\u003cspan class=\"s2\"\u003e\u0026#34;%Y.%m.%d\u0026#34;\u003c/span\u003e\u003cspan class=\"sb\"\u003e`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e这种方法，是网上比较常规的方式，如某一天忘记执行了，就会出现数据未清理干净的情况，有点不优雅，如何解决？\u003c/p\u003e","title":"使用 Cronjob 定时清理 ElasticSearch 中的日志索引","type":"posts"},{"content":" 安装 wkhtmltopdf # 使用 brew 进行安装\nbrew install --cask wkhtmltopdf 将目录下的所有 html 文件一键转换为 pdf 格式\nfor f in *.html; do wkhtmltopdf --load-error-handling ignore -n --enable-local-file-access $f \u0026#34;$f.pdf\u0026#34;; done 参考文档 # https://segmentfault.com/a/1190000018988358\nhttps://github.com/wkhtmltopdf/wkhtmltopdf\n","date":"2021年7月12日","externalUrl":null,"permalink":"/posts/macos-html-to-pdf/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e安装 wkhtmltopdf \n    \u003cdiv id=\"安装-wkhtmltopdf\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85-wkhtmltopdf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e使用 brew 进行安装\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebrew install --cask wkhtmltopdf\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e将目录下的所有 \u003ccode\u003ehtml\u003c/code\u003e 文件一键转换为 pdf 格式\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e f in *.html\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e wkhtmltopdf --load-error-handling ignore -n --enable-local-file-access  \u003cspan class=\"nv\"\u003e$f\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$f\u003c/span\u003e\u003cspan class=\"s2\"\u003e.pdf\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003edone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\n\u003ch2 class=\"relative group\"\u003e参考文档 \n    \u003cdiv id=\"参考文档\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%82%e8%80%83%e6%96%87%e6%a1%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca\n  href=\"https://segmentfault.com/a/1190000018988358\"\n    target=\"_blank\"\n  \u003ehttps://segmentfault.com/a/1190000018988358\u003c/a\u003e\u003c/p\u003e","title":"Macos 一键 使用 wkhtmltopdf 将 html 转换为 pdf","type":"posts"},{"content":"","date":"2021年7月12日","externalUrl":null,"permalink":"/categories/unix/","section":"Categories","summary":"","title":"Unix","type":"categories"},{"content":"","date":"2021年7月12日","externalUrl":null,"permalink":"/tags/wkhtmltopdf/","section":"Tags","summary":"","title":"Wkhtmltopdf","type":"tags"},{"content":"","date":"2021年7月8日","externalUrl":null,"permalink":"/tags/autoscaling/","section":"Tags","summary":"","title":"Autoscaling","type":"tags"},{"content":" VPA 简介 # 什么是 VPA？ # Vertical Pod Autoscaler（VPA，垂直 Pod 自动伸缩器）是 Kubernetes 的一个组件，它可以根据 Pod 的实际资源使用情况，自动调整 Pod 的 CPU 和内存请求值（requests）。\n为什么需要 VPA？ # 在实际生产环境中，我们经常遇到以下问题：\n问题 影响 VPA 解决方案 资源配置过高 浪费集群资源，成本增加 自动降低资源请求 资源配置过低 应用性能差，频繁重启 自动提高资源请求 手动调优困难 耗时耗力，容易出错 基于历史数据自动优化 动态负载变化 静态配置无法适应 持续监控和调整 VPA vs HPA # 特性 VPA（垂直伸缩） HPA（水平伸缩） 伸缩方向 调整单个 Pod 的资源 调整 Pod 的数量 适用场景 单体应用、有状态应用 无状态应用、微服务 资源类型 CPU、内存 主要基于 CPU 伸缩速度 需要重启 Pod 相对较快 兼容性 不能同时使用 可以组合使用 VPA 工作原理 # VPA 由三个核心组件组成：\n1. VPA Recommender（推荐器） # 作用：监控 Pod 的资源使用情况 功能：基于历史数据和当前使用情况生成资源建议 算法：使用统计学方法分析资源使用模式 2. VPA Updater（更新器） # 作用：决定是否需要更新 Pod 的资源配置 功能：比较当前配置与推荐值，触发更新操作 策略：支持多种更新策略（自动、初始、关闭） 3. VPA Admission Controller（准入控制器） # 作用：在 Pod 创建时应用 VPA 推荐的资源配置 功能：拦截 Pod 创建请求，修改资源配置 时机：Pod 重启或新建时生效 环境要求 # 系统要求 # 组件 版本要求 说明 Kubernetes v1.16+ 支持 VPA CRD Metrics Server v0.3.6+ 提供资源使用数据 OpenSSL v1.1.1+ CentOS 7 需要升级 操作系统 CentOS 7+ 或其他 Linux 发行版 本教程环境 # Kubernetes 版本：v1.20.4 操作系统：CentOS 7 VPA 版本：v0.9.2 Metrics Server 版本：v0.5.0 📚 项目地址：Kubernetes VPA GitHub\nVPA 安装部署 # 前置条件检查 # 在安装 VPA 之前，需要确保集群满足以下条件：\n# 检查 Kubernetes 版本 kubectl version --short # 检查集群节点状态 kubectl get nodes # 检查是否已安装 Metrics Server kubectl get deployment metrics-server -n kube-system 步骤 1：升级 OpenSSL（CentOS 7 专用） # 为什么需要升级？ # CentOS 7 默认的 OpenSSL 版本较低，VPA 安装脚本需要更高版本的 OpenSSL 支持。\n升级步骤 # # 下载 OpenSSL 1.1.1k 源码 wget http://mirrors.ibiblio.org/openssl/source/openssl-1.1.1k.tar.gz # 解压并进入目录 tar xf openssl-1.1.1k.tar.gz cd openssl-1.1.1k/ # 配置编译选项 ./config --prefix=/usr/local/openssl --openssldir=/usr/local/openssl # 编译安装（使用所有 CPU 核心加速编译） make -j$(nproc) sudo make install # 备份原有 OpenSSL sudo mv /usr/bin/openssl{,.bak} # 创建软链接 sudo ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl sudo ln -s /usr/local/openssl/lib/libssl.so.1.1 /usr/lib64/libssl.so.1.1 sudo ln -s /usr/local/openssl/lib/libcrypto.so.1.1 /usr/lib64/libcrypto.so.1.1 # 更新动态链接库缓存 sudo ldconfig # 验证安装 openssl version 预期输出：\nOpenSSL 1.1.1k 25 Mar 2021 ⚠️ 注意：此步骤仅适用于 CentOS 7，其他系统可能不需要\n步骤 2：安装 Metrics Server # 为什么需要 Metrics Server？ # Metrics Server 是 VPA 的重要依赖，它提供：\n资源使用数据：CPU 和内存的实时使用情况 历史数据收集：为 VPA 推荐算法提供数据基础 API 支持：支持 kubectl top 命令 安装步骤 # 📚 项目地址：Metrics Server GitHub\n# 下载并安装 Metrics Server v0.5.0 kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.5.0/components.yaml 配置 Metrics Server # 由于测试环境可能存在证书问题，需要添加不安全的 TLS 配置：\n# 编辑 Metrics Server 部署配置 kubectl edit deployment metrics-server -n kube-system 在 args 部分添加以下参数：\nspec: template: spec: containers: - name: metrics-server args: - --cert-dir=/tmp - --secure-port=443 - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --kubelet-use-node-status-port - --metric-resolution=15s - --kubelet-insecure-tls # 添加此行，跳过 TLS 验证 验证安装 # # 等待 Pod 启动完成 watch kubectl get pods -l k8s-app=metrics-server -n kube-system # 验证 Metrics Server 功能 kubectl top nodes 预期输出：\nNAME CPU(cores) CPU% MEMORY(bytes) MEMORY% node1 567m 7% 4009Mi 27% node2 613m 8% 5181Mi 35% node3 1446m 19% 1683Mi 11% node4 278m 3% 1380Mi 9% # 检查 Pod 资源使用情况 kubectl top pods --all-namespaces ✅ 成功标志：能够正常显示节点和 Pod 的资源使用情况\n步骤 3：安装 VPA # 下载 VPA 源码 # # 克隆 Kubernetes Autoscaler 项目 git clone https://github.com/kubernetes/autoscaler.git # 进入 VPA 目录 cd autoscaler/vertical-pod-autoscaler/ # 查看可用版本（可选） git tag | grep vpa | tail -5 安装 VPA 组件 # VPA 提供了一键安装脚本：\n# 执行安装脚本（默认安装 v0.9.2） ./hack/vpa-up.sh 安装过程截图：\n验证 VPA 安装 # # 检查 VPA 相关 Pod 状态 kubectl get pods -n kube-system | grep vpa 预期输出：\nvpa-admission-controller-6cd546c4f-dvcf9 1/1 Running 0 72s vpa-recommender-6855ff754-9q2dw 1/1 Running 0 72s vpa-updater-9fd7bfbd5-h7l7n 1/1 Running 0 72s VPA 组件说明 # 组件 作用 端口 vpa-recommender 分析资源使用情况，生成推荐值 8080 vpa-updater 决定何时更新 Pod 资源配置 8080 vpa-admission-controller 在 Pod 创建时应用推荐配置 8000 检查 VPA CRD # # 验证 VPA 自定义资源定义 kubectl get crd | grep verticalpodautoscaler # 查看 VPA CRD 详情 kubectl describe crd verticalpodautoscalers.autoscaling.k8s.io 检查 VPA 服务 # # 查看 VPA 相关服务 kubectl get svc -n kube-system | grep vpa # 查看 VPA Webhook 配置 kubectl get validatingwebhookconfiguration | grep vpa kubectl get mutatingwebhookconfiguration | grep vpa ✅ 安装成功标志：所有 VPA 组件 Pod 状态为 Running，且 CRD 正常创建\nVPA 使用实战 # 创建测试环境 # # 创建专用命名空间 kubectl create namespace vpa-demo 示例 1：基础 VPA 使用 # 部署测试应用 # 我们使用 VPA 官方提供的 hamster 示例应用：\n# 应用示例配置 kubectl apply -f examples/hamster.yaml -n vpa-demo 让我们查看 hamster.yaml 的内容：\n# hamster.yaml 内容解析 apiVersion: apps/v1 kind: Deployment metadata: name: hamster spec: replicas: 2 selector: matchLabels: app: hamster template: metadata: labels: app: hamster spec: containers: - name: hamster image: k8s.gcr.io/ubuntu-slim:0.1 resources: requests: cpu: 100m # 初始 CPU 请求：100 毫核 memory: 50Mi # 初始内存请求：50MB command: [\u0026#34;/bin/sh\u0026#34;] args: - \u0026#34;-c\u0026#34; - \u0026#34;while true; do timeout 10s yes \u0026gt;/dev/null; sleep 5s; done\u0026#34; --- apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: hamster-vpa spec: targetRef: apiVersion: \u0026#34;apps/v1\u0026#34; kind: Deployment name: hamster updatePolicy: updateMode: \u0026#34;Auto\u0026#34; # 自动更新模式 查看初始资源配置 # # 获取 Pod 名称 POD_NAME=$(kubectl get pods -n vpa-demo -l app=hamster -o jsonpath=\u0026#39;{.items[0].metadata.name}\u0026#39;) # 查看初始资源配置 kubectl get pod $POD_NAME -n vpa-demo -o yaml | grep -A 5 \u0026#39;resources:\u0026#39; 初始配置输出：\nresources: requests: cpu: 100m memory: 50Mi 观察 VPA 推荐 # # 查看 VPA 状态和推荐 kubectl describe vpa hamster-vpa -n vpa-demo 等待自动更新 # VPA 需要收集一段时间的数据才会触发更新（通常 1-2 分钟）：\n# 监控 Pod 变化 watch kubectl get pods -n vpa-demo 更新过程截图：\n查看更新后的资源配置 # # 获取新 Pod 名称 NEW_POD_NAME=$(kubectl get pods -n vpa-demo -l app=hamster -o jsonpath=\u0026#39;{.items[0].metadata.name}\u0026#39;) # 查看更新后的资源配置 kubectl get pod $NEW_POD_NAME -n vpa-demo -o yaml | grep -A 5 \u0026#39;resources:\u0026#39; 更新后配置输出：\nresources: requests: cpu: 548m # CPU 请求增加到 548 毫核 memory: 262144k # 内存请求增加到约 256MB 分析资源变化 # 资源类型 初始值 更新后 变化倍数 原因分析 CPU 100m 548m 5.48x 应用 CPU 使用率较高 Memory 50Mi 256Mi 5.12x 内存使用超出初始配置 📊 数据说明：VPA 基于实际资源使用情况，自动调整了资源请求值，确保应用有足够的资源运行\n示例 2：VPA 更新策略 # VPA 支持多种更新策略，让我们创建不同策略的示例：\nOff 模式（仅推荐） # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: hamster-vpa-off namespace: vpa-demo spec: targetRef: apiVersion: \u0026#34;apps/v1\u0026#34; kind: Deployment name: hamster updatePolicy: updateMode: \u0026#34;Off\u0026#34; # 仅提供推荐，不自动更新 EOF Initial 模式（仅新 Pod） # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: hamster-vpa-initial namespace: vpa-demo spec: targetRef: apiVersion: \u0026#34;apps/v1\u0026#34; kind: Deployment name: hamster updatePolicy: updateMode: \u0026#34;Initial\u0026#34; # 仅对新创建的 Pod 生效 EOF 资源策略配置 # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: hamster-vpa-policy namespace: vpa-demo spec: targetRef: apiVersion: \u0026#34;apps/v1\u0026#34; kind: Deployment name: hamster updatePolicy: updateMode: \u0026#34;Auto\u0026#34; resourcePolicy: containerPolicies: - containerName: hamster minAllowed: cpu: 100m memory: 50Mi maxAllowed: cpu: 1000m memory: 500Mi controlledResources: [\u0026#34;cpu\u0026#34;, \u0026#34;memory\u0026#34;] EOF VPA 监控和调试 # 查看 VPA 推荐详情 # # 查看详细的 VPA 状态 kubectl describe vpa hamster-vpa -n vpa-demo # 查看 VPA 推荐的 JSON 格式 kubectl get vpa hamster-vpa -n vpa-demo -o json | jq \u0026#39;.status.recommendation\u0026#39; 查看 VPA 组件日志 # # 查看 Recommender 日志 kubectl logs -n kube-system -l app=vpa-recommender # 查看 Updater 日志 kubectl logs -n kube-system -l app=vpa-updater # 查看 Admission Controller 日志 kubectl logs -n kube-system -l app=vpa-admission-controller VPA 指标监控 # # 查看 VPA 推荐指标 curl http://\u0026lt;vpa-recommender-ip\u0026gt;:8080/metrics # 查看 VPA 更新指标 curl http://\u0026lt;vpa-updater-ip\u0026gt;:8080/metrics VPA 最佳实践 # 适用场景 # ✅ 适合使用 VPA 的场景 # 单体应用：难以水平扩展的应用 有状态应用：数据库、缓存等 资源密集型应用：机器学习、数据处理 开发测试环境：快速优化资源配置 成本优化：减少资源浪费 ❌ 不适合使用 VPA 的场景 # 高可用要求：不能容忍 Pod 重启 快速扩展需求：需要快速响应负载变化 微服务架构：更适合使用 HPA 实时系统：对延迟敏感的应用 配置建议 # 1. 更新策略选择 # 模式 使用场景 优缺点 Auto 生产环境，可容忍重启 自动化程度高，但会重启 Pod Initial 新部署的应用 对现有 Pod 无影响 Off 观察和分析阶段 安全，但需要手动应用 2. 资源限制设置 # resourcePolicy: containerPolicies: - containerName: app minAllowed: cpu: 100m # 防止资源过低 memory: 128Mi maxAllowed: cpu: 2000m # 防止资源过高 memory: 4Gi controlledResources: [\u0026#34;cpu\u0026#34;, \u0026#34;memory\u0026#34;] controlledValues: \u0026#34;RequestsAndLimits\u0026#34; # 同时控制 requests 和 limits 3. 监控和告警 # # 推荐配置 Prometheus 监控规则 groups: - name: vpa.rules rules: - alert: VPARecommendationChanged expr: increase(vpa_recommender_recommendations_total[5m]) \u0026gt; 0 labels: severity: info annotations: summary: \u0026#34;VPA recommendation changed for {{ $labels.vpa_name }}\u0026#34; 故障排查 # 常见问题 # 问题 可能原因 解决方案 VPA 不生效 Metrics Server 未安装 安装并配置 Metrics Server Pod 频繁重启 推荐值变化过于频繁 调整推荐算法参数 资源推荐不合理 历史数据不足 等待更长时间收集数据 Admission Controller 失败 Webhook 配置错误 检查 Webhook 配置 调试命令 # # 检查 VPA 状态 kubectl get vpa --all-namespaces # 查看 VPA 事件 kubectl get events --field-selector involvedObject.kind=VerticalPodAutoscaler # 检查 Webhook 配置 kubectl get validatingwebhookconfiguration vpa-webhook-config -o yaml # 测试 VPA API kubectl api-resources | grep verticalpodautoscaler 总结与展望 # VPA 的价值 # VPA 作为 Kubernetes 生态系统中的重要组件，为资源优化提供了自动化解决方案：\n主要优势 # 🎯 精确优化：基于实际使用数据进行资源调整 💰 成本节约：避免资源过度配置，降低成本 🔄 自动化管理：减少手动调优的工作量 📊 数据驱动：基于历史数据做出科学决策 当前限制 # 🔄 需要重启 Pod：更新资源配置时必须重启 ⚠️ 实验性功能：仍在快速发展中，稳定性有待提升 🚫 不适合有状态应用：重启可能导致服务中断 ⏱️ 响应延迟：需要时间收集数据才能做出调整 未来发展方向 # 技术改进 # In-Place 资源更新：无需重启 Pod 即可调整资源 更智能的算法：结合机器学习提升推荐准确性 更好的集成：与 HPA、集群自动伸缩器更好协作 实时调整：支持更快速的资源调整响应 生产就绪 # 随着 Kubernetes 的发展，VPA 正在向生产就绪的方向发展：\n更稳定的 API 更完善的监控和告警 更丰富的配置选项 更好的文档和工具支持 建议 # 对于开发者 # 从测试环境开始：先在非生产环境验证效果 监控资源使用：建立完善的监控体系 渐进式采用：从不重要的应用开始尝试 持续优化：根据实际效果调整配置 对于运维团队 # 制定策略：明确哪些应用适合使用 VPA 建立流程：制定 VPA 配置和维护流程 培训团队：确保团队了解 VPA 的工作原理 准备回滚：制定问题发生时的回滚策略 VPA 虽然还在发展中，但已经展现出巨大的潜力。随着技术的不断成熟，它将成为 Kubernetes 资源管理的重要工具，帮助我们构建更高效、更经济的容器化应用。\n🚀 展望未来：期待 VPA 能够实现真正的动态资源调整，无需重启 Pod 即可优化资源配置，这将是容器编排技术的重大突破！\n","date":"2021年7月8日","externalUrl":null,"permalink":"/posts/k8s-vpa/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eVPA 简介 \n    \u003cdiv id=\"vpa-简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#vpa-%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 VPA？ \n    \u003cdiv id=\"什么是-vpa\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-vpa\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eVertical Pod Autoscaler（VPA，垂直 Pod 自动伸缩器）是 Kubernetes 的一个组件，它可以根据 Pod 的实际资源使用情况，自动调整 Pod 的 CPU 和内存请求值（requests）。\u003c/p\u003e","title":"Kubernetes VPA 垂直 Pod 自动伸缩完整指南","type":"posts"},{"content":"","date":"2021年7月8日","externalUrl":null,"permalink":"/tags/pod/","section":"Tags","summary":"","title":"Pod","type":"tags"},{"content":"","date":"2021年7月8日","externalUrl":null,"permalink":"/tags/vpa/","section":"Tags","summary":"","title":"Vpa","type":"tags"},{"content":"","date":"2021年6月24日","externalUrl":null,"permalink":"/tags/containerd/","section":"Tags","summary":"","title":"Containerd","type":"tags"},{"content":"","date":"2021年6月24日","externalUrl":null,"permalink":"/tags/kubeadm/","section":"Tags","summary":"","title":"Kubeadm","type":"tags"},{"content":" 方案介绍 # 在私有环境中部署 Kubernetes 高可用集群时，传统方案需要准备硬件或软件负载均衡器来创建多控制平面集群。通常我们会选择使用 HAProxy + Keepalived 来实现这个功能：创建 2 个负载均衡器虚拟机，分配一个 VIP（虚拟 IP），通过 VIP 将流量重定向到后端的 Kubernetes 控制平面节点。架构如下图所示：\n使用 Kube-VIP 的架构如下：\n可以看到，使用 Kube-VIP 后架构更加优雅，无需额外的负载均衡服务器。\n环境配置 # 节点信息 # IP 地址 主机名 操作系统 内核版本 192.168.8.81 master-01 CentOS Linux release 7.9.2009 5.4.128-1.el7.elrepo.x86_64 192.168.8.82 master-02 CentOS Linux release 7.9.2009 5.4.128-1.el7.elrepo.x86_64 192.168.8.83 master-03 CentOS Linux release 7.9.2009 5.4.128-1.el7.elrepo.x86_64 192.168.8.84 node-01 CentOS Linux release 7.9.2009 5.4.128-1.el7.elrepo.x86_64 192.168.8.85 node-02 CentOS Linux release 7.9.2009 5.4.128-1.el7.elrepo.x86_64 192.168.8.86 node-03 CentOS Linux release 7.9.2009 5.4.128-1.el7.elrepo.x86_64 192.168.8.100 (Kube-VIP 虚拟IP) - - - 软件版本 # 组件 版本 Kubernetes v1.20.5 Containerd v1.4.6 Rancher v2.4.15 集群部署 # 以下节点初始化操作需要在所有节点上执行。\n节点初始化 # 配置主机名解析 # cat \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt; EOF 192.168.8.81 master-01 192.168.8.82 master-02 192.168.8.83 master-03 192.168.8.84 node-01 192.168.8.85 node-02 192.168.8.86 node-03 EOF 关闭 Swap # swapoff -a sed -i \u0026#39;s/.*swap.*/#\u0026amp;/\u0026#39; /etc/fstab 设置主机名 # hostnamectl set-hostname xxx # 例如：hostnamectl set-hostname master-01 关闭防火墙 # systemctl stop firewalld systemctl disable firewalld 关闭 SELinux # sed -i \u0026#39;s#SELINUX=enforcing#SELINUX=disabled#g\u0026#39; /etc/selinux/config grep -i ^selinux= /etc/selinux/config setenforce 0 getenforce 更新内核 # 参考文档步骤\n配置内核参数 # sudo yum install -y conntrack ipvsadm ipset jq sysstat curl iptables cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF chmod a+x /etc/rc.local cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF modprobe overlay modprobe br_netfilter # 加载内核模块 cat \u0026gt; /etc/rc.local \u0026lt;\u0026lt; EOF modprobe overlay modprobe br_netfilter EOF sysctl -p /etc/sysctl.d/99-kubernetes-cri.conf 启用 IPVS 内核模块 # cat \u0026gt; /etc/sysconfig/modules/ipvs.modules \u0026lt;\u0026lt;EOF #!/bin/bash modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 EOF chmod 755 /etc/sysconfig/modules/ipvs.modules \\ \u0026amp;\u0026amp; bash /etc/sysconfig/modules/ipvs.modules \\ \u0026amp;\u0026amp; lsmod | grep -e ip_vs -e nf_conntrack_ipv4 安装和配置 Containerd # sudo yum install -y yum-utils device-mapper-persistent-data lvm2 \\ \u0026amp;\u0026amp; sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo \\ \u0026amp;\u0026amp; sudo yum makecache fast yum install containerd -y containerd config default \u0026gt; /etc/containerd/config.toml sed -i \u0026#34;s#k8s.gcr.io#registry.cn-hangzhou.aliyuncs.com/google_containers#g\u0026#34; /etc/containerd/config.toml sed -i \u0026#39;/containerd.runtimes.runc.options/a\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ SystemdCgroup = true\u0026#39; /etc/containerd/config.toml sed -i \u0026#34;s#https://registry-1.docker.io#https://registry.cn-hangzhou.aliyuncs.com#g\u0026#34; /etc/containerd/config.toml systemctl daemon-reload systemctl enable containerd systemctl restart containerd 安装 crictl 客户端 # crictl 是 Containerd 的命令行客户端工具\nwget https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.21.0/crictl-v1.21.0-linux-amd64.tar.gz sudo tar zxvf crictl-v1.21.0-linux-amd64.tar.gz -C /usr/local/bin cat \u0026gt; /etc/crictl.yaml \u0026lt;\u0026lt; EOF runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: true EOF 安装 Kubernetes 组件 # cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=0 repo_gpgcheck=0 gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF yum list kubeadm --showduplicates | sort -r # 查看所有可用的 kubeadm 版本 yum install -y kubeadm-1.20.5 kubelet-1.20.5 kubectl-1.20.5 systemctl enable kubelet # 启用 kubelet 服务自启动 可选：配置 kubectl 命令补全功能\nyum install -y bash-completion source /usr/share/bash-completion/bash_completion source \u0026lt;(kubectl completion bash) echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc Kubernetes 集群初始化 # 使用 kubeadm 首先初始化 master-01 节点，操作步骤如下：\n生成 kubeadm 初始化配置文件 # 注意：由于控制平面地址配置为 VIP 地址，如果 Kube-VIP 尚未部署，kubeadm init 操作将一直处于 pending 状态直至超时。因此需要在执行初始化操作后立即部署 Kube-VIP。\nkubeadm config print init-defaults \u0026gt; init_kubelet.yaml # 生成默认的初始化配置文件 # 修改后的初始化配置文件内容如下： cat init_kubelet.yaml apiVersion: kubeadm.k8s.io/v1beta2 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration nodeRegistration: criSocket: /run/containerd/containerd.sock --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta2 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: type: CoreDNS etcd: local: dataDir: /var/lib/etcd imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers kind: ClusterConfiguration controlPlaneEndpoint: \u0026#34;192.168.8.100:6443\u0026#34; kubernetesVersion: v1.20.5 networking: dnsDomain: cluster.local serviceSubnet: 10.96.0.0/12 scheduler: {} --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd #protectKernelDefaults: false --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs kubeadm init --config init_kubelet.yaml --upload-certs 这里记录一个踩坑点，就是配置文件中所示的 “protectKernelDefaults”，添加了这个配置项后，会出现 kubelet 奔溃问题，将其注释掉后问题就解决了，下面图片中为官方文档中给出的注解。\n部署 Kube-VIP 静态 Pod # kubelet 静态 Pod 目录为 /etc/kubernetes/manifests/，只需将对应的 YAML 文件生成到此目录下，Pod 的生命周期将由 kubelet 管理。\nexport VIP=192.168.8.100 # VIP 地址 export INTERFACE=eth0 ctr image pull docker.io/plndr/kube-vip:0.3.1 mkdir -p /etc/kubernetes/manifests ctr run --rm --net-host docker.io/plndr/kube-vip:0.3.1 vip \\ /kube-vip manifest pod \\ --interface $INTERFACE \\ --vip $VIP \\ --controlplane \\ --services \\ --arp \\ --leaderElection | tee /etc/kubernetes/manifests/kube-vip.yaml 安装 CNI 网络插件 # 集群初始化后，需要安装网络插件才能正常使用。CNI 插件有多种选择，这里以 Cilium 为例。\ncurl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash helm repo add cilium https://helm.cilium.io/ helm install cilium cilium/cilium --version 1.9.4 \\ --namespace kube-system 添加其他控制平面节点 # 复制证书文件到其他控制平面节点 # ssh-keygen # 生成 SSH 密钥对 ssh-copy-id root@\u0026#34;node_id\u0026#34; # 配置免密登录 USER=root # 可自定义用户 CONTROL_PLANE_IPS=\u0026#34;192.168.8.82 192.168.8.83\u0026#34; for host in ${CONTROL_PLANE_IPS}; do ssh \u0026#34;${USER}\u0026#34;@$host \u0026#34;mkdir -p /etc/kubernetes/pki/ /etc/kubernetes/pki/etcd/\u0026#34; scp /etc/kubernetes/pki/ca.crt \u0026#34;${USER}\u0026#34;@$host:/etc/kubernetes/pki/ scp /etc/kubernetes/pki/ca.key \u0026#34;${USER}\u0026#34;@$host:/etc/kubernetes/pki/ scp /etc/kubernetes/pki/sa.key \u0026#34;${USER}\u0026#34;@$host:/etc/kubernetes/pki/ scp /etc/kubernetes/pki/sa.pub \u0026#34;${USER}\u0026#34;@$host:/etc/kubernetes/pki/ scp /etc/kubernetes/pki/front-proxy-ca.crt \u0026#34;${USER}\u0026#34;@$host:/etc/kubernetes/pki/ scp /etc/kubernetes/pki/front-proxy-ca.key \u0026#34;${USER}\u0026#34;@$host:/etc/kubernetes/pki/ scp /etc/kubernetes/pki/etcd/ca.crt \u0026#34;${USER}\u0026#34;@$host:/etc/kubernetes/pki/etcd/ scp /etc/kubernetes/pki/etcd/ca.key \u0026#34;${USER}\u0026#34;@$host:/etc/kubernetes/pki/etcd/ done 控制平面节点加入集群 # kubeadm join 192.168.8.100:6443 --token hash.hash\\ --discovery-token-ca-cert-hash sha256:hash \\ --control-plane --certificate-key key master-02 加入集群\nmaster-03 加入集群\n重要提醒：新加入集群的 Master 节点需要部署 Kube-VIP 静态 Pod，以确保集群具备高可用特性。请在每个新加入的控制平面节点上执行上述 Kube-VIP 部署步骤。\n​\t添加 Worker 节点 # kubeadm join 192.168.8.100:6443 --token hash.hash\\ --discovery-token-ca-cert-hash sha256:hash 可选配置优化 # 移除 Master 节点污点 # 生产环境中请谨慎操作，建议保留 Master 节点污点以避免业务 Pod 调度到控制平面节点。\nkubectl taint nodes --all node-role.kubernetes.io/master- 参考文档 # 使用 kube-vip 实现 Kubernetes 高可用负载均衡 使用 kube-vip 搭建高可用 Kubernetes 集群 Say Good Bye to HAProxy and Keepalived with kube-vip ","date":"2021年6月24日","externalUrl":null,"permalink":"/posts/kube-vip-deploy-ha-k8s-cluster/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e方案介绍 \n    \u003cdiv id=\"方案介绍\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%a1%88%e4%bb%8b%e7%bb%8d\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在私有环境中部署 Kubernetes 高可用集群时，传统方案需要准备硬件或软件负载均衡器来创建多控制平面集群。通常我们会选择使用 HAProxy + Keepalived 来实现这个功能：创建 2 个负载均衡器虚拟机，分配一个 VIP（虚拟 IP），通过 VIP 将流量重定向到后端的 Kubernetes 控制平面节点。架构如下图所示：\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"20210616142006\" src=\"https://cdn.treesir.pub/img/20210616142006.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"使用 Kube-VIP 部署高可用 Kubernetes 集群","type":"posts"},{"content":"","date":"2021年6月15日","externalUrl":null,"permalink":"/tags/kaniko/","section":"Tags","summary":"","title":"Kaniko","type":"tags"},{"content":"在 Kubernetes 环境中构建 Docker 镜像一直是一个挑战，传统的 Docker-in-Docker 方案存在安全风险和复杂性问题。Kaniko 作为 Google 开源的容器镜像构建工具，提供了一个安全、高效的解决方案。\n什么是 Kaniko # Kaniko 是由 Google 开发的开源工具，专门用于在容器或 Kubernetes 集群内构建容器镜像。它的核心优势包括：\n主要特性 # 无需 Docker Daemon: 不依赖 Docker 守护进程，避免了特权容器的安全风险 Kubernetes 原生: 专为 Kubernetes 环境设计，可以作为 Pod 运行 安全性: 在用户空间执行，不需要特权权限 标准兼容: 完全兼容 Dockerfile 语法和 Docker 镜像格式 多平台支持: 支持推送到各种容器镜像仓库 使用场景 # CI/CD 流水线: 在 Kubernetes 集群中构建镜像 安全环境: 需要避免特权容器的生产环境 云原生应用: 完全容器化的开发和部署流程 快速开始 # 方法一：使用 Docker 命令行构建 # 这种方法适合快速测试和本地开发环境：\n第一步：准备认证配置 # # 创建 Docker 仓库认证信息（请替换为您的实际凭据） echo -n \u0026#34;your_username:your_password\u0026#34; | base64 # 输出示例：eWFuZ3p1bjoxMjM0NTY= # 创建 Docker 配置文件 cat \u0026gt; config.json \u0026lt;\u0026lt; EOF { \u0026#34;auths\u0026#34;: { \u0026#34;https://your-registry.com/v2/\u0026#34;: { \u0026#34;auth\u0026#34;: \u0026#34;eWFuZ3p1bjoxMjM0NTY=\u0026#34; } } } EOF 安全提示:\n请将示例中的用户名和密码替换为您的实际凭据 在生产环境中，建议使用 Kubernetes Secret 来管理认证信息 第二步：执行镜像构建 # # 创建简单的 Dockerfile 并使用 Kaniko 构建 echo -e \u0026#39;FROM alpine \\nRUN echo \u0026#34;created from standard input\u0026#34;\u0026#39; \u0026gt; Dockerfile | \\ tar -cf - Dockerfile | gzip -9 | \\ docker run --rm \\ --interactive \\ -v $(pwd):/workspace \\ -v $(pwd)/config.json:/kaniko/.docker/config.json:ro \\ gcr.io/kaniko-project/executor:v1.6.0 \\ --context tar://stdin \\ --destination=your-registry.com/kaniko-build:v1.0.0 \\ --skip-tls-verify 参数说明:\n--context tar://stdin: 从标准输入读取构建上下文 --destination: 指定构建完成后推送的镜像地址 --skip-tls-verify: 跳过 TLS 证书验证（仅用于测试环境） -v $(pwd)/config.json:/kaniko/.docker/config.json:ro: 挂载认证配置文件 图：Kaniko 镜像构建过程的控制台输出\n基于 kubernetes 中 pod 构建镜像 # 创建 localPV # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: Immediate # Immediate or WaitForFirstConsumer --- apiVersion: v1 kind: PersistentVolume metadata: name: kaniko-pv spec: storageClassName: local capacity: storage: 10Gi volumeMode: Filesystem accessModes: - ReadWriteOnce local: path: /data/kaniko/data/ nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - node3 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: kaniko-pvc spec: storageClassName: local accessModes: - ReadWriteOnce resources: requests: storage: 10Gi EOF 将之前创建的 config.json \u0026amp; dockerfile 创建为 secret 资源对象 # kubectl create secret generic kaniko-secret --from-file=./config.json kubectl create secret generic kaniko-build-dockerfile --from-file=./Dockerfile kubectl create secret generic kaniko-secret \\ --from-file=config.json=./config.json \\ --from-file=Dockerfile=./Dockerfile pod 资源清单 # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: kaniko spec: containers: - name: kaniko image: idocker.io/kaniko-project/executor:v1.6.0 args: - \u0026#34;--dockerfile=/kaniko/.docker/Dockerfile\u0026#34; - \u0026#34;--context=dir://workspace\u0026#34; - \u0026#34;--destination=idocker.io/kaniko-build:v1.0.1\u0026#34; - \u0026#34;--skip-tls-verify\u0026#34; volumeMounts: - name: docker-secret readOnly: true mountPath: /kaniko/.docker - name: dockerfile-storage mountPath: /workspace restartPolicy: Never volumes: - name: dockerfile-storage persistentVolumeClaim: claimName: kaniko-pvc - name: docker-secret secret: secretName: kaniko-secret EOF 基于 python 项目进行实战构建 # Dockerfile 所需文件的准备 # cd /data/kaniko/data git init git remote add origin https://github.com/cdryzun/python-dockerfile-build.git git pull https://github.com/cdryzun/python-dockerfile-build.git master git pull git branch --set-upstream-to=origin/master master # rm -rf .git # 删除无用隐藏文件 使用 pod 进行构建 # kubectl delete po kaniko # 删除之前测试 pod cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: kaniko spec: containers: - name: kaniko image: idocker.io/kaniko-project/executor:v1.6.0 args: - \u0026#34;--dockerfile=/workspace/Dockerfile\u0026#34; - \u0026#34;--context=/workspace/\u0026#34; - \u0026#34;--destination=idocker.io/python-demo:v1.0.1\u0026#34; - \u0026#34;--skip-tls-verify\u0026#34; volumeMounts: - name: docker-secret readOnly: true mountPath: /kaniko/.docker - name: dockerfile-storage mountPath: /workspace restartPolicy: Never volumes: - name: dockerfile-storage persistentVolumeClaim: claimName: kaniko-pvc - name: docker-secret secret: secretName: kaniko-secret EOF 测试构建的容器 # docker run -it --name test -d --rm -p 18080:8080 idocker.io/python-demo:v1.0.1 curl 127.0.0.1:18080 Hello World 请求监听端口，可以看到容器正常进行了输出，表示容器正常可以正常使用。\n参考文档 # https://github.com/GoogleContainerTools/kaniko\nhttps://www.baeldung.com/ops/kaniko\nToDo # ","date":"2021年6月15日","externalUrl":null,"permalink":"/posts/kaniko-image-build/","section":"博客文章","summary":"\u003cp\u003e在 Kubernetes 环境中构建 Docker 镜像一直是一个挑战，传统的 Docker-in-Docker 方案存在安全风险和复杂性问题。Kaniko 作为 Google 开源的容器镜像构建工具，提供了一个安全、高效的解决方案。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 Kaniko \n    \u003cdiv id=\"什么是-kaniko\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-kaniko\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eKaniko 是由 Google 开发的开源工具，专门用于在容器或 Kubernetes 集群内构建容器镜像。它的核心优势包括：\u003c/p\u003e","title":"使用 Kaniko 在 Kubernetes 中构建 Docker 镜像","type":"posts"},{"content":"","date":"2021年6月10日","externalUrl":null,"permalink":"/categories/docker-compose/","section":"Categories","summary":"","title":"Docker Compose","type":"categories"},{"content":"","date":"2021年6月10日","externalUrl":null,"permalink":"/tags/https/","section":"Tags","summary":"","title":"Https","type":"tags"},{"content":"","date":"2021年6月10日","externalUrl":null,"permalink":"/tags/mkcert/","section":"Tags","summary":"","title":"Mkcert","type":"tags"},{"content":"","date":"2021年6月10日","externalUrl":null,"permalink":"/tags/traefik/","section":"Tags","summary":"","title":"Traefik","type":"tags"},{"content":" 环境说明 # 软件版本 # 操作系统：CentOS 7.9.2009 Docker 版本：20.10.7 Docker Compose 版本：1.18.0 Mkcert 版本：v1.4.3 端口规划 # Nexus 私服端口规划 # 私服名称 私服作用 私服类型 私服端口 docker-custom 存放自定义 push 的镜像，与项目环境无关 hosted 8086 idocker.io 代理仓库和 custom 仓库的集合 group 8082 docker-dev 存放项目 dev 环境镜像 hosted 8083 docker-qa 存放项目 qa 环境镜像 hosted 8084 docker-prod 存放项目 prod 环境镜像 hosted 8085 除了上述 Docker 私服端口外，还有 Nexus3 管理面板端口 8081\nDocker 代理仓库列表 # 名称 私服类型 说明 地址 docker-google proxy Google 公开镜像（需要科学上网） https://gcr.io docker-k8s proxy Kubernetes 官方 Google 镜像源（需要科学上网） https://k8s.gcr.io docker-aliyun proxy 阿里云同步 Docker 官方源（存在部分镜像未同步问题） https://7bezldxe.mirror.aliyuncs.com docker-official proxy Docker Hub 官方镜像地址（限制带宽：匿名用户100次，认证用户200次） https://registry-1.docker.io Traefik Ingress 端口规划 # Ingress 名称 Ingress 作用 Ingress 端口 http HTTP 站点入口 80 https HTTPS TLS 证书站点入口 443 使用 Docker Compose 一键部署私服和 Traefik # 本次部署所依赖的工具和配置文件已整理至 GitHub 仓库 中，文档中的步骤均已在 CentOS 7 系统中验证通过。其他平台及架构只需替换为相应的软件包版本即可。\nDocker Compose 安装和准备工作 # yum install -y docker-compose # CentOS 下直接使用 yum 安装 克隆代码 # git clone https://github.com/cdryzun/traefik-nexus.git 生成证书 # ⚠️ 注意：使用 mkcert 生成证书时，cert-file 和 key-file 的文件名称有特定要求。例如为域名 example.io 生成证书文件时，文件前缀应该为 example-io，否则会导致 Traefik 在引用证书时匹配不上，从而使用默认证书。\nmkcert 版本列表请查看。\ncd traefik-nexus chmod a+x ./mkcert # Linux amd64 架构，请确认与操作系统架构相同 ./mkcert --version v1.4.3 mkdir -p certs # 创建存放证书的文件夹 # 生成域名证书 ./mkcert -ecdsa -cert-file certs/treesir-pub.crt -key-file certs/treesir-pub.key \u0026#34;treesir.pub\u0026#34; \u0026#34;*.treesir.pub\u0026#34; \u0026#34;treesir.lan\u0026#34; \u0026#34;*.treesir.lan\u0026#34; # 生成 Docker 私服使用证书 ./mkcert -ecdsa -cert-file certs/idocker-io.crt -key-file certs/idocker-io.key \u0026#34;idocker.io\u0026#34; \u0026#34;*.idocker.io\u0026#34; \u0026#34;idocker.lan\u0026#34; \u0026#34;*.idocker.lan\u0026#34; # 确认证书是否存在 ls certs/ idocker-io.crt idocker-io.key treesir-pub.crt treesir-pub.key 生成证书后，检查确认 traefik/traefik.yml 配置文件中的配置是否对应：\ncat traefik/traefik.yml ... tls: certificates: # 将生成的证书加到 Traefik TLS 证书池，自动匹配 - certFile: /certs/treesir-pub.crt keyFile: /certs/treesir-pub.key certificates: - certFile: /certs/idocker-io.crt keyFile: /certs/idocker-io.key options: default: minVersion: VersionTLS12 cipherSuites: - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - TLS_RSA_WITH_AES_256_GCM_SHA384 stores: # 配置默认证书 default: defaultCertificate: certFile: /certs/treesir-pub.crt keyFile: /certs/treesir-pub.key ... 检查 Docker Compose 部署文件 # cat docker-compose.yml version: \u0026#34;3\u0026#34; services: traefik: restart: always image: traefik:v2.4.8 container_name: traefik ports: - \u0026#34;80:80\u0026#34; - \u0026#34;443:443\u0026#34; volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./certs:/certs:ro - ./traefik/traefik.yml:/traefik.yml:ro labels: - traefik.enable=true - traefik.http.routers.traefik.entrypoints=http,https - traefik.http.routers.traefik.rule=Host(`traefik.treesir.pub`) # Traefik Dashboard 关联域名 - traefik.http.routers.traefik.tls=true - traefik.http.routers.traefik.service=api@internal nexus: restart: always image: sonatype/nexus3:3.30.1 container_name: nexus3 privileged: true environment: - INSTALL4J_ADD_VM_PARAMS=-Xms4g -Xmx4g -XX:MaxDirectMemorySize=6g # JVM 参数，针对 8G 内存机器，按比例和实际需求调整 volumes: - /etc/localtime:/etc/localtime:ro - ./nexus-data:/nexus-data labels: - traefik.enable=true - traefik.http.routers.1.entrypoints=http,https - traefik.http.routers.1.rule=Host(`mirror.treesir.pub`) \u0026amp;\u0026amp; PathPrefix(`/`) - traefik.http.routers.1.tls=true - traefik.http.routers.1.service=one - traefik.http.services.one.loadbalancer.server.port=8081 - traefik.http.routers.2.entrypoints=http,https - traefik.http.routers.2.rule=Host(`idocker.io`) || (Host(`idocker.io`) \u0026amp;\u0026amp; Method(`GET`)) - traefik.http.routers.2.tls=true - traefik.http.routers.2.service=two - traefik.http.services.two.loadbalancer.server.port=8082 - traefik.http.routers.3.entrypoints=http,https - traefik.http.routers.3.rule=(Host(`idocker.io`) \u0026amp;\u0026amp; Path(`/v1/search`)) || (Host(`idocker.io`) \u0026amp;\u0026amp; Method(`PUT`,`HEAD`,`POST`,`PATCH`)) - traefik.http.routers.3.priority=100 - traefik.http.routers.3.tls=true - traefik.http.routers.3.service=three - traefik.http.services.three.loadbalancer.server.port=8086 - traefik.http.routers.4.rule=Host(`dev.idocker.io`) \u0026amp;\u0026amp; PathPrefix(`/`) - traefik.http.routers.4.tls=true - traefik.http.routers.4.service=four - traefik.http.services.four.loadbalancer.server.port=8083 - traefik.http.routers.5.rule=Host(`qa.idocker.io`) \u0026amp;\u0026amp; PathPrefix(`/`) - traefik.http.routers.5.tls=true - traefik.http.routers.5.service=five - traefik.http.services.five.loadbalancer.server.port=8084 - traefik.http.routers.6.rule=Host(`prod.idocker.io`) \u0026amp;\u0026amp; PathPrefix(`/`) - traefik.http.routers.6.tls=true - traefik.http.routers.6.service=six - traefik.http.services.six.loadbalancer.server.port=8085 networks: default: external: name: docker 启动服务并导入 CA 证书 # 使用 Docker Compose 启动服务 # # 创建 Nexus 数据存储目录，开放权限防止报错 mkdir -p ./nexus-data chmod 777 -R ./nexus-data # 创建网络 docker network create docker # 启动服务 docker-compose up -d # 检查日志，确认有无错误 docker-compose logs -f 添加 Hosts 记录 # # macOS 系统 sudo -- sh -c \u0026#34;echo \u0026#39;192.168.8.88 idocker.io dev.idocker.io qa.idocker.io prod.idocker.io mirror.treesir.pub traefik.treesir.pub\u0026#39; \u0026gt;\u0026gt; /etc/hosts\u0026#34; 导入 CA 证书并添加信任 # yum install -y lrzsz sz -y $(mkcert -CAROOT)/rootCA.pem 双击证书文件，添加到系统中：\n双击打开，设置为永远信任：\n测试访问 Nexus Dashboard 管理界面：\n查看 Nexus Dashboard 初始化 admin 密码：\ndocker exec nexus3 bash -c \u0026#34;cat /nexus-data/admin.password\u0026#34; Docker 私服测试使用 # 在测试之前，请检查是否已创建对应的 Docker 私服仓库，且对应端口正常监听。由于此部分之前已有相关整理，故此处省略，参考步骤请查看文档。\n检查配置 # 创建 idocker.io 可供测试使用的仓库列表：\nTraefik 中的配置检查：\n信任 Docker 私服 # cat /etc/docker/daemon.json { ... \u0026#34;insecure-registries\u0026#34;: [\u0026#34;idocker.io\u0026#34;] ... } docker login idocker.io 查看私服镜像情况 # idocker.io Group 私服：\ndocker-custom Hosted 私服：\n总结 # 通过本教程，我们成功实现了：\n使用 Docker Compose 一键部署 Nexus3 Docker 私服 配置 Traefik 作为反向代理，实现多域名和端口的统一管理 使用 mkcert 生成本地 HTTPS 证书，确保安全访问 配置多环境的 Docker 私服仓库（dev、qa、prod） 实现了私服的 HTTPS 访问和证书自动匹配 这套方案提供了完整的企业级 Docker 私服解决方案，支持多环境隔离和安全的 HTTPS 访问。\n参考文档 # Traefik v1 到 v2 迁移指南 Traefik 快速开始 Traefik Docker Compose ACME TLS 用户指南 Traefik Local 项目 Traefik 2 TLS 101 ","date":"2021年6月10日","externalUrl":null,"permalink":"/posts/nexus-use-traefik-proxy/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e软件版本 \n    \u003cdiv id=\"软件版本\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bd%af%e4%bb%b6%e7%89%88%e6%9c%ac\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e操作系统：CentOS 7.9.2009\u003c/li\u003e\n\u003cli\u003eDocker 版本：20.10.7\u003c/li\u003e\n\u003cli\u003eDocker Compose 版本：1.18.0\u003c/li\u003e\n\u003cli\u003eMkcert 版本：v1.4.3\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e端口规划 \n    \u003cdiv id=\"端口规划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ab%af%e5%8f%a3%e8%a7%84%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eNexus 私服端口规划 \n    \u003cdiv id=\"nexus-私服端口规划\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#nexus-%e7%a7%81%e6%9c%8d%e7%ab%af%e5%8f%a3%e8%a7%84%e5%88%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e私服名称\u003c/th\u003e\n          \u003cth\u003e私服作用\u003c/th\u003e\n          \u003cth\u003e私服类型\u003c/th\u003e\n          \u003cth\u003e私服端口\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003edocker-custom\u003c/td\u003e\n          \u003ctd\u003e存放自定义 push 的镜像，与项目环境无关\u003c/td\u003e\n          \u003ctd\u003ehosted\u003c/td\u003e\n          \u003ctd\u003e8086\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eidocker.io\u003c/td\u003e\n          \u003ctd\u003e代理仓库和 custom 仓库的集合\u003c/td\u003e\n          \u003ctd\u003egroup\u003c/td\u003e\n          \u003ctd\u003e8082\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003edocker-dev\u003c/td\u003e\n          \u003ctd\u003e存放项目 dev 环境镜像\u003c/td\u003e\n          \u003ctd\u003ehosted\u003c/td\u003e\n          \u003ctd\u003e8083\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003edocker-qa\u003c/td\u003e\n          \u003ctd\u003e存放项目 qa 环境镜像\u003c/td\u003e\n          \u003ctd\u003ehosted\u003c/td\u003e\n          \u003ctd\u003e8084\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003edocker-prod\u003c/td\u003e\n          \u003ctd\u003e存放项目 prod 环境镜像\u003c/td\u003e\n          \u003ctd\u003ehosted\u003c/td\u003e\n          \u003ctd\u003e8085\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cblockquote\u003e\n\u003cp\u003e除了上述 Docker 私服端口外，还有 Nexus3 管理面板端口 8081\u003c/p\u003e","title":"使用 Docker Compose 部署 Nexus3 Docker 私服并配置 Traefik 代理","type":"posts"},{"content":"","date":"2021年6月8日","externalUrl":null,"permalink":"/tags/kubekey/","section":"Tags","summary":"","title":"Kubekey","type":"tags"},{"content":"","date":"2021年6月8日","externalUrl":null,"permalink":"/tags/rancher/","section":"Tags","summary":"","title":"Rancher","type":"tags"},{"content":" 环境配置 # Kubernetes 版本：v1.20.4 (KubeKey 部署) 操作系统：CentOS 7.9.2009 Rancher 版本：v2.4.15 准备 Kubernetes 测试环境 # 本次使用 KubeKey 进行一键部署。KubeKey 底层集群部署基于 kubeadm，详细信息可参考 GitHub 地址。\n编译安装 KubeKey # 系统初始化步骤省略，编译时需要使用 Docker 容器，请事先安装。系统初始化步骤可参考文档\nyum install -y git git clone https://github.com/kubesphere/kubekey.git \\ \u0026amp;\u0026amp; cd kubekey ./build.sh -p # 执行编译，如需交叉编译，需要在脚本中添加对应环境变量 cp -a output/kk /usr/local/bin/ kk version # 如输出以下信息，则表示安装成功 version.BuildInfo{Version:\u0026#34;latest+unreleased\u0026#34;, GitCommit:\u0026#34;f3f9e2e2d001a1b35883f5baea07912bb636db56\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, GoVersion:\u0026#34;go1.14.7\u0026#34;} 部署集群 # mkdir -p ~/kubekey-workspace kk create config --with-kubernetes v1.20.4 # 生成配置文件 cat config-sample.yaml apiVersion: kubekey.kubesphere.io/v1alpha1 kind: Cluster metadata: name: sample spec: hosts: - {name: node1, address: 192.168.8.70, internalAddress: 192.168.8.70, user: root, password: 123456} - {name: node2, address: 192.168.8.71, internalAddress: 192.168.8.71, user: root, password: 123456} roleGroups: etcd: - node1 master: - node1 worker: - node1 - node2 controlPlaneEndpoint: domain: lb.kubesphere.local address: \u0026#34;\u0026#34; port: 6443 kubernetes: version: v1.20.4 imageRepo: kubesphere clusterName: cluster.local network: plugin: calico kubePodsCIDR: 10.233.64.0/18 kubeServiceCIDR: 10.233.0.0/18 registry: registryMirrors: [] insecureRegistries: [] addons: [] yum install socat conntrack -y # 安装依赖 kk create cluster -f ./config-sample.yaml # 创建集群 +-------+------+------+---------+----------+-------+-------+-----------+---------+------------+-------------+------------------+--------------+ | name | sudo | curl | openssl | ebtables | socat | ipset | conntrack | docker | nfs client | ceph client | glusterfs client | time | +-------+------+------+---------+----------+-------+-------+-----------+---------+------------+-------------+------------------+--------------+ | node2 | y | y | y | y | y | y | y | 20.10.7 | | | | CST 09:39:04 | | node1 | y | y | y | y | y | y | y | 20.10.7 | | | | CST 09:39:04 | +-------+------+------+---------+----------+-------+-------+-----------+---------+------------+-------------+------------------+--------------+ This is a simple check of your environment. Before installation, you should ensure that your machines meet all requirements specified at https://github.com/kubesphere/kubekey#requirements-and-recommendations Continue this installation? [yes/no]: yes # 输入 yes Rancher 导入集群 # 导入步骤省略。问题现象如下：Dashboard 界面提示 Scheduler 和 Controller Manager 组件不健康。\n通过命令行查看组件状态：\nkubectl get cs Warning: v1 ComponentStatus is deprecated in v1.19+ NAME STATUS MESSAGE ERROR scheduler Unhealthy Get \u0026#34;http://127.0.0.1:10251/healthz\u0026#34;: dial tcp 127.0.0.1:10251: connect: connection refused controller-manager Unhealthy Get \u0026#34;http://127.0.0.1:10252/healthz\u0026#34;: dial tcp 127.0.0.1:10252: connect: connection refused etcd-0 Healthy {\u0026#34;health\u0026#34;:\u0026#34;true\u0026#34;} 问题分析与修复 # 问题原因 # 在较新版本的 kubeadm 部署的集群中，默认关闭了 HTTP 通信端口，导致健康检查时无法通信，自检失败。\n解决方案 # 目前有两种解决思路：\n方案一：将自检调用的端口更改为 HTTPS 方案二：重新启用 HTTP 端口监听 本文介绍较为简单的方案二进行修复。\n安全提示：此方法存在一定的安全风险，请根据实际环境评估使用。\n修复 HTTP 端口监听 # 由于使用 kubeadm 部署集群，只需修改对应的静态 Pod YAML 文件配置即可。\n修复 Scheduler 组件 # vi /etc/kubernetes/manifests/kube-scheduler.yaml # 编辑 Scheduler 配置文件 ... spec: containers: - command: - kube-scheduler - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf - --bind-address=0.0.0.0 - --feature-gates=CSINodeInfo=true,VolumeSnapshotDataSource=true,ExpandCSIVolumes=true,RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true - --kubeconfig=/etc/kubernetes/scheduler.conf - --leader-elect=true # - --port=0 # 将此行注释掉 ... ### 修复 Controller Manager 组件 ```bash vi /etc/kubernetes/manifests/kube-controller-manager.yaml # 编辑 Controller Manager 配置文件 ... spec: containers: - command: - kube-controller-manager - --allocate-node-cidrs=true - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf - --bind-address=0.0.0.0 - --client-ca-file=/etc/kubernetes/pki/ca.crt - --cluster-cidr=10.233.64.0/18 - --cluster-name=cluster.local - --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt - --cluster-signing-key-file=/etc/kubernetes/pki/ca.key - --controllers=*,bootstrapsigner,tokencleaner - --experimental-cluster-signing-duration=87600h - --feature-gates=CSINodeInfo=true,VolumeSnapshotDataSource=true,ExpandCSIVolumes=true,RotateKubeletServerCertificate=true - --kubeconfig=/etc/kubernetes/controller-manager.conf - --leader-elect=true - --node-cidr-mask-size=24 ## - --port=0 # 将此行注释掉 ... 快速修复方法 # 也可以使用 sed 命令一键修复：\nsed -i \u0026#39;s/.*--port=0.*/#\u0026amp;/\u0026#39; /etc/kubernetes/manifests/kube-controller-manager.yaml sed -i \u0026#39;s/.*--port=0.*/#\u0026amp;/\u0026#39; /etc/kubernetes/manifests/kube-scheduler.yaml 验证修复结果 # 修复完成后，再次查看 Dashboard 界面，可以看到错误提示已消失：\n通过命令行验证组件状态：\nkubectl get cs Warning: v1 ComponentStatus is deprecated in v1.19+ NAME STATUS MESSAGE ERROR controller-manager Healthy ok scheduler Healthy ok etcd-0 Healthy {\u0026#34;health\u0026#34;:\u0026#34;true\u0026#34;} 可以看到 Scheduler 和 Controller Manager 组件状态已恢复为 Healthy。\n","date":"2021年6月8日","externalUrl":null,"permalink":"/posts/rancher-import-cluster-fix/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境配置 \n    \u003cdiv id=\"环境配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eKubernetes 版本\u003c/strong\u003e：v1.20.4 (KubeKey 部署)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e操作系统\u003c/strong\u003e：CentOS 7.9.2009\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRancher 版本\u003c/strong\u003e：v2.4.15\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e准备 Kubernetes 测试环境 \n    \u003cdiv id=\"准备-kubernetes-测试环境\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%86%e5%a4%87-kubernetes-%e6%b5%8b%e8%af%95%e7%8e%af%e5%a2%83\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e本次使用 KubeKey 进行一键部署。KubeKey 底层集群部署基于 kubeadm，详细信息可参考 \u003ca\n  href=\"https://github.com/kubesphere/kubekey.git\"\n    target=\"_blank\"\n  \u003eGitHub 地址\u003c/a\u003e。\u003c/p\u003e","title":"修复 Rancher 导入集群时 Scheduler 和 Controller Manager 不健康问题","type":"posts"},{"content":"","date":"2021年6月1日","externalUrl":null,"permalink":"/categories/ingress/","section":"Categories","summary":"","title":"Ingress","type":"categories"},{"content":"","date":"2021年6月1日","externalUrl":null,"permalink":"/tags/localpv/","section":"Tags","summary":"","title":"Localpv","type":"tags"},{"content":"","date":"2021年6月1日","externalUrl":null,"permalink":"/tags/postgres/","section":"Tags","summary":"","title":"Postgres","type":"tags"},{"content":"","date":"2021年6月1日","externalUrl":null,"permalink":"/tags/redis/","section":"Tags","summary":"","title":"Redis","type":"tags"},{"content":" 环境说明 # Kubernetes Version: v1.20.4 Postgres Version: 12.7 Redis Version: 5.0.9 因在官方提供的 gitlab-ce 镜像中，内置了 Postgres \u0026amp; Redis 的安装，在实际生产使用过程中，想让其连接使用外部统一的服务进行使用，来合理统一的管理，并有效降低对应资源使用率，这里使用 dokcer 非官方镜像: sameersbn/gitlab:13.12.1 进行使用\nPostgres \u0026amp; Redis 的安装 # postgres \u0026amp; redis 的安装，使用了 oneinstack 一键工具，进行编译安装的管理\nwget http://mirrors.linuxeye.com/oneinstack-full.tar.gz tar xzf oneinstack-full.tar.gz cd oneinstack ./install.sh # 交互式选择安装 redis \u0026amp; postgres 数据即可 由于 gitlab 中使用了 postgres 中的 扩展组件，这里还需要进行编译安装一下，步骤如下:\ncd /data/scripts/oneinstack/src/postgresql-12.7/contrib/ \u0026amp;\u0026amp; make -j8 \u0026amp;\u0026amp; make install # 对应目录，按实际境况更改一下 未编译安装，创建扩展时报错提示：\npostgres 数据库初始化准备 # su - postgres psql CREATE USER gitlab WITH PASSWORD \u0026#39;123456\u0026#39;; CREATE DATABASE gitlab_production OWNER gitlab; # 创建 registry 数据库 ALTER USER gitlab with createdb; \\c gitlab_production # 进入刚才创建的数据库 CREATE EXTENSION pg_trgm; CREATE EXTENSION btree_gist; GRANT ALL PRIVILEGES ON DATABASE gitlab_production to gitlab; GRANT ALL PRIVILEGES ON all tables in schema public TO gitlab; \\l ; postgres=# \\q # 退出； 如在安装过程中，想清理重置一下 数据库配置，可执行以下命令:\nsu - postgres psql drop database gitlab_production; drop owned by gitlab; drop role gitlab; redis 监听地址优化 # 默认 redis 安装后，且监听在 127.0.0.1 之上，即且本地可使用，这里需要进行更改一下监听地址。\nsed -i \u0026#34;s#bind 127.0.0.1#bind 0.0.0.0#g\u0026#34; # 更改为监听所有，非推荐做法。 Gitlab Kubernetes 中的安装 # 准备好 postgres \u0026amp; redis 并做好了，对应的初始化步骤后，就可以进行在 k8s 中进行安装部署了。下面示例的 部署文件中，包涵了 关联集成 smtp \u0026amp; openLdap，并创建使用 traefik 进行对应页面的暴露使用。\n创建部署 namespace # kubectl create ns gitlab 部署 gitlab.yaml 部署文件如下所示 # apiVersion: v1 kind: PersistentVolume metadata: name: gitlab-pv spec: storageClassName: local # Local PV capacity: storage: 100Gi volumeMode: Filesystem accessModes: - ReadWriteOnce local: path: /data/gitlab/data/ nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - node2 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: gitlab-pvc namespace: gitlab spec: storageClassName: local accessModes: - ReadWriteOnce resources: requests: storage: 100Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: gitlab namespace: gitlab labels: name: gitlab spec: selector: matchLabels: name: gitlab template: metadata: name: gitlab labels: name: gitlab spec: initContainers: - name: fix-permissions image: busybox command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;chown -R 1000:1000 /home/git/data\u0026#34;] securityContext: privileged: true volumeMounts: - name: data mountPath: /home/git/data containers: - name: gitlab image: sameersbn/gitlab:13.12.1 imagePullPolicy: IfNotPresent env: - name: TZ value: Asia/Shanghai - name: GITLAB_TIMEZONE value: Beijing - name: GITLAB_SECRETS_DB_KEY_BASE value: long-and-random-alpha-numeric-string - name: GITLAB_SECRETS_SECRET_KEY_BASE value: long-and-random-alpha-numeric-string - name: GITLAB_SECRETS_OTP_KEY_BASE value: long-and-random-alpha-numeric-string - name: GITLAB_ROOT_PASSWORD value: treesir123 - name: GITLAB_ROOT_EMAIL value: amoaloas@gmail.com - name: GITLAB_HOST value: gitlab.treesir.pub - name: GITLAB_PORT value: \u0026#34;80\u0026#34; - name: GITLAB_SSH_PORT value: \u0026#34;22\u0026#34; - name: GITLAB_NOTIFY_ON_BROKEN_BUILDS value: \u0026#34;true\u0026#34; - name: GITLAB_NOTIFY_PUSHER value: \u0026#34;false\u0026#34; - name: GITLAB_BACKUP_SCHEDULE value: daily - name: GITLAB_BACKUP_TIME value: 01:00 - name: DB_TYPE value: postgres - name: DB_HOST value: 192.168.8.88 - name: DB_PORT value: \u0026#39;5432\u0026#39; - name: DB_USER value: gitlab - name: DB_PASS value: \u0026#34;123456\u0026#34; - name: DB_NAME value: gitlab_production - name: REDIS_HOST value: 192.168.8.88 - name: REDIS_PORT value: \u0026#34;6379\u0026#34; - name: SMTP_ENABLED # 配置开启 smtp value: \u0026#39;true\u0026#39; - name: SMTP_DOMAIN value: mail.163.com - name: SMTP_HOST value: smtp.163.com - name: SMTP_PORT value: \u0026#39;465\u0026#39; - name: SMTP_USER value: xxx@163.com - name: SMTP_PASS value: xxx - name: SMTP_TLS value: \u0026#39;true\u0026#39; - name: LDAP_ENABLED value: \u0026#39;true\u0026#39; - name: LDAP_HOST value: 192.168.8.1 - name: LDAP_UID value: uid - name: LDAP_BIND_DN value: cn=admin,dc=treesir,dc=pub - name: LDAP_PASS value: \u0026#39;123456\u0026#39; - name: LDAP_ACTIVE_DIRECTORY value: \u0026#39;false\u0026#39; - name: LDAP_ALLOW_USERNAME_OR_EMAIL_LOGIN value: \u0026#39;false\u0026#39; - name: LDAP_BASE value: ou=users,dc=treesir,dc=pub ports: - name: http containerPort: 80 - name: ssh containerPort: 22 volumeMounts: - mountPath: /home/git/data name: data readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 60 timeoutSeconds: 1 resources: limits: cpu: 4000m memory: 6144Mi requests: cpu: 1000m memory: 2048Mi volumes: - name: data persistentVolumeClaim: claimName: gitlab-pvc --- apiVersion: v1 kind: Service metadata: name: gitlab namespace: gitlab labels: name: gitlab spec: ports: - name: http port: 80 targetPort: http - name: ssh port: 22 targetPort: ssh selector: name: gitlab --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: gitlab namespace: gitlab spec: entryPoints: - web routes: - kind: Rule match: Host(`gitlab.treesir.pub`) services: - name: gitlab port: 80 更加高级扩展功能，请查看此 容器 的环境变量 配置表，注意使用 localPv 进行关联部署的话，请注意一下，在对应的节点中是否有对应的 目录存在。\n等待初始化完成 # watch kubectl get po -n gitlab 测试使用 ldap 进行使用 # 参考文档 # https://www.treesir.pub/post/ingress-traefik/\nhttps://www.treesir.pub/post/docker-deploy-ldap/\nhttps://www.treesir.pub/post/gitlab-deploy/\nhttps://github.com/sameersbn/docker-gitlab\n","date":"2021年6月1日","externalUrl":null,"permalink":"/posts/kubernetes-deploy-gitlab/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eKubernetes Version: \u003ccode\u003ev1.20.4\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003ePostgres Version:  \u003ccode\u003e12.7\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eRedis Version: \u003ccode\u003e5.0.9\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e因在官方提供的 \u003ccode\u003egitlab-ce\u003c/code\u003e 镜像中，内置了 Postgres \u0026amp; Redis 的安装，在实际生产使用过程中，想让其连接使用外部统一的服务进行使用，来合理统一的管理，并有效降低对应资源使用率，这里使用 dokcer 非官方镜像: \u003ccode\u003esameersbn/gitlab:13.12.1\u003c/code\u003e 进行使用\u003c/p\u003e","title":"在 Kubernetes 中使用 localPv 部署 Gitlab","type":"posts"},{"content":"","date":"2021年5月27日","externalUrl":null,"permalink":"/categories/fix/","section":"Categories","summary":"","title":"Fix","type":"categories"},{"content":" 问题描述 # 在 Rancher 管理的 Kubernetes 集群中，有时会出现副本集中的 Pod 部署失败的情况。如下图所示，失败的 Pod 数量可能达到上千个，手动逐一删除非常耗时。\n解决方案 # 批量删除失败的 Pod # 使用 kubectl 命令批量删除状态为 MatchNodeSelector 的失败 Pod：\nIFS=\u0026#39; \u0026#39; for i in `kubectl get po --all-namespaces | grep -i \u0026#39;MatchNodeSelector\u0026#39;`; do kubectl delete po `echo $i | awk \u0026#39;{print $2}\u0026#39;` -n `echo $i | awk \u0026#39;{print $1}\u0026#39;` done 命令说明：\nkubectl get po --all-namespaces：获取所有命名空间中的 Pod grep -i 'MatchNodeSelector'：筛选出状态为 MatchNodeSelector 的 Pod awk '{print $2}'：提取 Pod 名称 awk '{print $1}'：提取命名空间名称 kubectl delete po：删除指定的 Pod 执行结果 # 其他常见失败状态 # 除了 MatchNodeSelector 外，还可以根据需要删除其他状态的失败 Pod：\n删除 ImagePullBackOff 状态的 Pod # for i in `kubectl get po --all-namespaces | grep -i \u0026#39;ImagePullBackOff\u0026#39;`; do kubectl delete po `echo $i | awk \u0026#39;{print $2}\u0026#39;` -n `echo $i | awk \u0026#39;{print $1}\u0026#39;` done 删除 CrashLoopBackOff 状态的 Pod # for i in `kubectl get po --all-namespaces | grep -i \u0026#39;CrashLoopBackOff\u0026#39;`; do kubectl delete po `echo $i | awk \u0026#39;{print $2}\u0026#39;` -n `echo $i | awk \u0026#39;{print $1}\u0026#39;` done 删除 Pending 状态的 Pod # for i in `kubectl get po --all-namespaces | grep -i \u0026#39;Pending\u0026#39;`; do kubectl delete po `echo $i | awk \u0026#39;{print $2}\u0026#39;` -n `echo $i | awk \u0026#39;{print $1}\u0026#39;` done 注意事项 # 谨慎操作：删除 Pod 前请确认这些 Pod 确实是失败的且可以安全删除 备份重要数据：如果 Pod 中包含重要数据，请先进行备份 检查依赖关系：确保删除这些 Pod 不会影响其他服务的正常运行 分批处理：如果失败 Pod 数量过多，建议分批删除以避免对集群造成过大压力 预防措施 # 为了减少失败 Pod 的产生，建议：\n资源配置：合理配置 Pod 的资源请求和限制 节点标签：正确配置节点标签和 Pod 的节点选择器 镜像管理：确保容器镜像可用且版本正确 监控告警：设置监控告警及时发现和处理 Pod 异常 ","date":"2021年5月27日","externalUrl":null,"permalink":"/posts/quick-delete-rancher-failed-pod/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e问题描述 \n    \u003cdiv id=\"问题描述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%97%ae%e9%a2%98%e6%8f%8f%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在 Rancher 管理的 Kubernetes 集群中，有时会出现副本集中的 Pod 部署失败的情况。如下图所示，失败的 Pod 数量可能达到上千个，手动逐一删除非常耗时。\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"image-20210527085545254\" src=\"https://cdn.treesir.pub/img/image-20210527085545254.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决方案 \n    \u003cdiv id=\"解决方案\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e批量删除失败的 Pod \n    \u003cdiv id=\"批量删除失败的-pod\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%89%b9%e9%87%8f%e5%88%a0%e9%99%a4%e5%a4%b1%e8%b4%a5%e7%9a%84-pod\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e使用 kubectl 命令批量删除状态为 \u003ccode\u003eMatchNodeSelector\u003c/code\u003e 的失败 Pod：\u003c/p\u003e","title":"快速删除 Rancher 中失败的 Pod 资源","type":"posts"},{"content":"","date":"2021年5月26日","externalUrl":null,"permalink":"/categories/centos/","section":"Categories","summary":"","title":"Centos","type":"categories"},{"content":"","date":"2021年5月26日","externalUrl":null,"permalink":"/tags/gocron/","section":"Tags","summary":"","title":"Gocron","type":"tags"},{"content":" 部署环境说明 # 操作系统: CentOS release 7.8.2003 Gocron Version: v1.5.3 Mysql Version: 5.5 Gocron 说明 # gocron 是使用 go 语言开发的轻量级定时任务集中调度和管理系统, 可以用于替代 linux-crontab。\n项目 github 地址\nmanage 端安装配置 # 在 Gocron 中数据的存储是放置在 mysql 数据库中的，我们这里需要配置一下 MySQl 的使用环境及权限。如果数据库已经事先安装，那么就只需要添加对应的库和用户使用权限即可。\n手动使用二进制进行安装 # 下载对应版本安装包\nwget https://github.com/ouqiang/gocron/releases/download/v1.5.3/gocron-node-v1.5.3-linux-amd64.tar.gz # 下载对应压缩包 # 解压 复制文件到指定文件夹下 进行使用 tar xf gocron-v1.5.3-linux-amd64.tar.gz cd gocron-linux-amd64/ \\cp -a gocron /usr/local/bin/ gocron -v 配置为服务并设置自启动\n管理端启动后，服务默认监听端口是在 tcp 至上的 5920 端口\nmkdir -p /application/gocron-manage cat \u0026gt; /usr/lib/systemd/system/gocron-manage.service \u0026lt;\u0026lt; EOF [Unit] Description=gocron web manage Documentation=https://github.com/ouqiang/gocron After=network.target [Service] Type=simple User=root ExecStart=/usr/local/bin/gocron web Restart=on-failure WorkingDirectory=/application/gocron-manage [Install] WantedBy=multi-user.target EOF 启动 manage 端并设置开机自启动\nsystemctl start gocron-manage.service \\ \u0026amp;\u0026amp; systemctl status gocron-manage.service \\ \u0026amp;\u0026amp; systemctl enable gocron-manage.service 数据库的连接初始化 # 创建程序连接数据库的对应用户使用权限\nCREATE DATABASE `gocron` CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; # 创建 程序连接数据库 grant all on `gocron`.* to cron_app@\u0026#39;%\u0026#39; identified by \u0026#39;xMPJ8Xkv1\u0026#39;; # 创建程序使用用户，并将对应库权限授予此用户进行使用 flush privileges; agent/node 端安装配置 # 手动使用二进制进行安装 # 安装准备\nwget https://github.com/ouqiang/gocron/releases/download/v1.5.3/gocron-node-v1.5.3-linux-amd64.tar.gz # agent tar xf gocron-node-v1.5.3-linux-amd64.tar.gz \\ \u0026amp;\u0026amp; cd gocron-node-linux-amd64 \\ \u0026amp;\u0026amp; cp gocron-node /usr/local/bin # 添加至环境变量中 gocron-node --version # 测试效果 配置为服务并设置开机自启动\nmkdir -p /application/gocron-agent cat \u0026gt; /usr/lib/systemd/system/gocron-agent.service \u0026lt;\u0026lt; EOF [Unit] Description=gocron agent/node server Documentation=https://github.com/ouqiang/gocron After=network.target [Service] Type=simple User=root ExecStart=/usr/local/bin/gocron-node -allow-root WorkingDirectory=/application/gocron-agent Restart=on-failure [Install] WantedBy=multi-user.target EOF # 设置开机自启动 systemctl start gocron-agent.service \\ \u0026amp;\u0026amp; systemctl status gocron-agent.service \\ \u0026amp;\u0026amp; systemctl enable gocron-agent.service 注意agent端 默认监听在 tcp 协议上的 5921 （\u0026ndash;help 查看帮助文档 修改端口）\n问题记录 # 日志提示主机名称过长 # 具体日志表现如下所示:\n​\t解决方法，就是更改对应 表结构，进行解决。\nALTER TABLE cron_task_log MODIFY hostname text NOT NULL ; To Do # ","date":"2021年5月26日","externalUrl":null,"permalink":"/posts/gocron-install/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e部署环境说明 \n    \u003cdiv id=\"部署环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%83%a8%e7%bd%b2%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e操作系统: \u003ccode\u003eCentOS release 7.8.2003 \u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eGocron Version: \u003ccode\u003ev1.5.3\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eMysql Version: \u003ccode\u003e5.5\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eGocron 说明 \n    \u003cdiv id=\"gocron-说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#gocron-%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003egocron\u003c/code\u003e 是使用 \u003ccode\u003ego\u003c/code\u003e 语言开发的轻量级定时任务集中调度和管理系统, 可以用于替代 linux-crontab。\u003c/p\u003e","title":"Gocron 实践安装，实现统一定时任务管理平台","type":"posts"},{"content":"","date":"2021年5月25日","externalUrl":null,"permalink":"/tags/helm/","section":"Tags","summary":"","title":"Helm","type":"tags"},{"content":"","date":"2021年5月25日","externalUrl":null,"permalink":"/tags/mariadb/","section":"Tags","summary":"","title":"Mariadb","type":"tags"},{"content":" 环境说明 # helm version: v3.3.1 kubernetes: v1.17.9 使用 helm chart: bitnami/mariadb 操作系统: CentOS 7.8.2003 helm 部署 mariadb 前的准备 # 添加 helm 私服 # helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update # 更新仓库索引 helm search repo mariadb bitnami/mariadb 9.3.12 10.5.10 Fast, reliable, scalable, and easy to use open-... 对 chart 进行 定制更改优化 # 下载对应 chart 文件\nmkdir -p /data/helm/mariadb # create workspace cd /data/helm/mariadb helm fetch bitnami/mariadb tar xf mariadb*.tgz # 解压文件 编辑更改 secondary 对应 statefulset 模板文件\n因为我们这里使用的是自己创建的 localPv 进行数据的持久存储，而在默认的 secondary 对应的模板文件中，是没有指定使用 现有pvc 的逻辑，所有我们这里需要将这部分逻辑给添加一下。\nvi mariadb/templates/secondary/statefulset.yaml ... {{- if not .Values.secondary.persistence.enabled }} - name: data emptyDir: {} {{- else if and .Values.secondary.persistence.enabled .Values.secondary.persistence.existingClaim }} # 添加 else if 语句 - name: data persistentVolumeClaim: claimName: {{ tpl .Values.secondary.persistence.existingClaim . }} {{- else }} .. 创建 localPv # 这里我们手动创建 两个pv \u0026amp; pvc ，因为我们部署的是主程集群 primary \u0026amp; secondary 多需要进行对应数据的持久化，并本地将其绑定在了节点 mn105 \u0026amp; mn106上进行使用。\n创建 StorageClass \u0026amp; pv 资源\n对应 部署 yaml 文件如下所示，示例文件中分别创建了 localPv 的 StorageClass 资源对象，并将对应卷绑定策略设置为了 Immediate 立即绑定。如果使用的是 WaitForFirstConsumer 模式的话，是指原本实时发生的 pvc 和 pv 的绑定过程，被延迟到对应 Pod 第一次调度的时候在调度器中进行，详情请查阅此 文档。\ncat \u0026gt; local-pv.yaml \u0026lt;\u0026lt; EOF apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: Immediate # Immediate or WaitForFirstConsumer --- apiVersion: v1 kind: PersistentVolume metadata: name: mariadb-primary-pv-local labels: role: primary spec: capacity: storage: 100Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /data/k8s/localpv/mariadb-master nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - mn105 --- apiVersion: v1 kind: PersistentVolume metadata: name: mariadb-secondary-pv-local labels: role: secondary spec: capacity: storage: 100Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /data/k8s/localpv/mariadb-slave nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - mn106 EOF kubectl apply -f ./local-pv.yaml # 执行资源对象的创建 storageclass.storage.k8s.io/local-storage created persistentvolume/mariadb-primary-pv-local created persistentvolume/mariadb-secondary-pv-local created kubectl get pv|grep Available mariadb-primary-pv-local 100Gi RWO Retain Available local-storage 32s mariadb-secondary-pv-local 100Gi RWO Retain Available local-storage 32s 创建 localPv 指定文件夹 # 注意: localPv 创建时指定的文件夹，不会自己去创建。当pod调度时发现对应绑定的文件夹不存在时，会导致 pod 无法正常被调度。\nmn105 节点\nmkdir -p /data/k8s/localpv/mariadb-master mn106节点\nmkdir -p /data/k8s/localpv/mariadb-slave 创建 localPv pvc 资源对象 # kubectl create ns mariadb # 创建部署命名空间 cat \u0026gt; local-pvc.yaml \u0026lt;\u0026lt; EOF kind: PersistentVolumeClaim apiVersion: v1 metadata: name: mariadb-primary-pvc-local namespace: mariadb labels: type: local spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Gi storageClassName: local-storage selector: matchLabels: role: primary --- kind: PersistentVolumeClaim apiVersion: v1 metadata: name: mariadb-secondary-pvc-local namespace: mariadb labels: type: local spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Gi storageClassName: local-storage selector: matchLabels: role: secondary EOF kubectl apply -f ./local-pvc.yaml # 创建资源 persistentvolumeclaim/mariadb-primary-pvc-local created persistentvolumeclaim/mariadb-secondary-pvc-local created helm 部署对应资源 # 创建 prod-values.yaml 部署文件 # 可以使用如下命令进行查看默认的 values.yaml 配置文件\nhelm show values bitnami/mariadb \u0026gt; values.yaml cat \u0026gt; prod-values.yaml \u0026lt;\u0026lt; EOF architecture: replication auth: rootPassword: \u0026#34;123456\u0026#34; # root 密码 database: ancun # 初始化添加的 数据库 username: \u0026#34;ancun\u0026#34; password: \u0026#34;123456\u0026#34; replicationUser: replicator replicationPassword: \u0026#34;123456\u0026#34; primary: replicaCount: 1 configuration: |- [mysqld] skip-name-resolve explicit_defaults_for_timestamp basedir=/opt/bitnami/mariadb plugin_dir=/opt/bitnami/mariadb/plugin port=3306 socket=/opt/bitnami/mariadb/tmp/mysql.sock tmpdir=/opt/bitnami/mariadb/tmp max_allowed_packet=16M bind-address=0.0.0.0 pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid log-error=/opt/bitnami/mariadb/logs/mysqld.log character-set-server=UTF8 collation-server=utf8_general_ci [client] port=3306 socket=/opt/bitnami/mariadb/tmp/mysql.sock default-character-set=UTF8 plugin_dir=/opt/bitnami/mariadb/plugin [manager] port=3306 socket=/opt/bitnami/mariadb/tmp/mysql.sock pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid resources: limits: memory: 8000Mi cpu: 2000m requests: memory: 4000Mi cpu: 1000m livenessProbe: enabled: true initialDelaySeconds: 120 periodSeconds: 10 timeoutSeconds: 1 failureThreshold: 3 successThreshold: 1 readinessProbe: enabled: true initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 1 failureThreshold: 3 successThreshold: 1 extraFlags: \u0026#34;--max-connect-errors=1000 --max_connections=155\u0026#34; extraEnvVars: - name: TZ value: \u0026#34;Asia/Shanghai\u0026#34; persistence: enabled: true existingClaim: mariadb-primary-pvc-local accessModes: - ReadWriteOnce size: 100Gi service: type: NodePort port: 3306 secondary: replicaCount: 1 configuration: |- [mysqld] skip-name-resolve explicit_defaults_for_timestamp basedir=/opt/bitnami/mariadb port=3306 socket=/opt/bitnami/mariadb/tmp/mysql.sock tmpdir=/opt/bitnami/mariadb/tmp max_allowed_packet=16M bind-address=0.0.0.0 pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid log-error=/opt/bitnami/mariadb/logs/mysqld.log character-set-server=UTF8 collation-server=utf8_general_ci [client] port=3306 socket=/opt/bitnami/mariadb/tmp/mysql.sock default-character-set=UTF8 [manager] port=3306 socket=/opt/bitnami/mariadb/tmp/mysql.sock pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid resources: limits: memory: 8000Mi cpu: 2000m requests: memory: 4000Mi cpu: 1000m livenessProbe: enabled: true initialDelaySeconds: 120 periodSeconds: 10 timeoutSeconds: 1 failureThreshold: 3 successThreshold: 1 readinessProbe: enabled: true initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 1 failureThreshold: 3 successThreshold: 1 extraFlags: \u0026#34;--max-connect-errors=1000 --max_connections=155\u0026#34; extraEnvVars: - name: TZ value: \u0026#34;Asia/Shanghai\u0026#34; persistence: enabled: true existingClaim: mariadb-secondary-pvc-local accessModes: - ReadWriteOnce size: 100Gi service: type: NodePort port: 3306 metrics: enabled: false image: registry: docker.io repository: bitnami/mysqld-exporter tag: 0.12.1-debian-10-r444 pullPolicy: IfNotPresent annotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;9104\u0026#34; resources: limits: memory: 256Mi cpu: 100m requests: memory: 256Mi cpu: 100m serviceMonitor: enabled: true interval: 30s EOF 执行应用的部署 # ls local-pvc.yaml local-pv.yaml mariadb mariadb-9.3.12.tgz prod-values.yaml helm upgrade --install mariadb -f ./prod-values.yaml -n mariadb ./mariadb/ 这里部署后，需要注意一下，在从库中默认是只读的，root 除外。为了进行验证我们可以进入 从库 容器查看一下对应进程的 args\n获取外部连接地址 # 在 helm 部署文件中，我们把对应 service 资源类型设置为了 NodePort。外部连接的话，只需要获取到对应随机的端口号，组合集群中任意节点ip进行连接即可。\n获取 NodePort 端口号\nkubectl get svc -n mariadb NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE mariadb-primary NodePort 10.101.91.176 \u0026lt;none\u0026gt; 3306:31011/TCP 3h12m mariadb-secondary NodePort 10.97.222.104 \u0026lt;none\u0026gt; 3306:31848/TCP 3h12m 安装后的测试 # 测试主从复制 # 这里测试使用的用户均已 root 用户进行\n在主中创建数据库，查看 slave 这边是否有对应同步\n在主中对应数据库创建表，查看 slave 这边是否有对应同步\nuse testing; create table test_tb (id int); 上面测试输出结果，可以看到正常进行了同步\nslave 端只读 # 此次测试在 slave 端进行，并使用 helm 文件中创建的用户 ancun 进行\nmysql -uancun -p show databases; use ancun; create table test_tb (id int); ERROR 1290 (HY000): The MariaDB server is running with the --read-only option so it cannot execute this statement 参考文档及链接 # https://github.com/bitnami/charts/tree/master/bitnami/mariadb/#installing-the-chart\nhttps://github.com/bitnami/bitnami-docker-mariadb/issues/174\n总结 # 主从集群部署完毕后，这里还有两个问题需要提及和记录一下，第一个: 由于使用的是 localPv 并做了节点亲和，pod 使用对应 localpv 的 pvc 进行数据卷挂载时，将会 继承 其亲和调度策略。如果这时对应 primary(master) 节点主机出现 down 机，主从集群将会被破坏且无法使用 kubernetes 调度策略实现 pod 自恢复，在生产环境中 有条件 的话，这里建议是进行使用分布式存储，可满足 pod 调度在 任意 节点 那种。第二个: 由于 localPv 使用的是对应主机中的 磁盘目录进行挂载，如果此时磁盘出现损坏，同样会导致集群被破坏，且 数据可能丢失问题 ，建议做好定期的数据备份，并对 对应磁盘使用 raid 去提高磁盘稳定和可靠性。\n","date":"2021年5月25日","externalUrl":null,"permalink":"/posts/helm-k8s-deploy-mariadb/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003ehelm version: \u003ccode\u003ev3.3.1\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003ekubernetes: \u003ccode\u003ev1.17.9\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e使用 helm chart:  \u003ccode\u003ebitnami/mariadb\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e操作系统: \u003ccode\u003eCentOS 7.8.2003\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003ehelm 部署 \u003ccode\u003emariadb\u003c/code\u003e 前的准备 \n    \u003cdiv id=\"helm-部署-mariadb-前的准备\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#helm-%e9%83%a8%e7%bd%b2-mariadb-%e5%89%8d%e7%9a%84%e5%87%86%e5%a4%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e添加 helm 私服 \n    \u003cdiv id=\"添加-helm-私服\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b7%bb%e5%8a%a0-helm-%e7%a7%81%e6%9c%8d\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehelm repo add bitnami https://charts.bitnami.com/bitnami\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehelm repo update  \u003cspan class=\"c1\"\u003e# 更新仓库索引\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehelm search repo mariadb\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebitnami/mariadb         9.3.12          10.5.10         Fast, reliable, scalable, and easy to use open-...\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e对 chart 进行 定制更改优化 \n    \u003cdiv id=\"对-chart-进行-定制更改优化\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9-chart-%e8%bf%9b%e8%a1%8c-%e5%ae%9a%e5%88%b6%e6%9b%b4%e6%94%b9%e4%bc%98%e5%8c%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e下载对应 chart 文件\u003c/strong\u003e\u003c/p\u003e","title":"使用 Helm 配合 localPV 在 K8s 中部署 Mariadb 主程复制集群","type":"posts"},{"content":"","date":"2021年5月24日","externalUrl":null,"permalink":"/tags/spinnaker/","section":"Tags","summary":"","title":"Spinnaker","type":"tags"},{"content":" 环境说明 # Kubernetes Version: v1.17.9\n操作系统: CentOS 7.8.2003 Helm Version: v3.2.1\nSpinnaker Version: 1.26.3\nspinnaker 版本说明\nspinnaker 组件说明 # Deck：前端web页面 端口9000\nGate：API网关，所有程序通过gate与spinnaker通信。 端口8084\nOrca：编排引擎，定义管道或任务，并管理阶段和任务，协调其他Spinnaker服务。 端口8083\nClouddriver： 云厂商适配器，负责对云厂商的变更资源调用。 端口7002\nFront50：用于保存应用程序、管道、项目和通知的元数据。 端口8080\nRosco：为各种运供应商生成不可变的VM镜像。 端口 8087\nIgor: 持续集成系统集成，触发管道。端口 8088\nEcho：消息通知，负责发送通知。端口 8089\nFiat: 认证授权服务。端口 7003\nKayenta：自动化的金丝雀分析。端口 8090\nHalyard： Spinnaker生命周期配置管理工具。端口 8064\n组件关联图 如下所示\n使用 helm 安装部署 # 注意 示例演示环境使用 openwrt 进行扶墙处理；在使用 helm 部署前，首先需要确认你的梯子够不够稳，如果不稳的话建议使用的是手动部署。\nhelm 添加仓库\nhelm repo add stable https://charts.helm.sh/stable helm repo update helm search repo spinnaker NAME CHART VERSION APP VERSION DESCRIPTION stable/spinnaker 2.2.6 1.16.2 DEPRECATED - Open source, multi-cloud continuou... 创建 values.yaml 部署文件\n示例部署使用了 Nfs storageClass 作为 pvc \u0026amp; pv 的管理。生产环境不太建议。\n原 helm values.yaml 部署文件可以使用此 链接 查看，或 使用下面命令进行打印输出。\nhelm show values stable/spinnaker 修改完成后的 prod-values.yaml 文件查看\ncat \u0026gt; prod-values.yaml \u0026lt;\u0026lt; EOF halyard: spinnakerVersion: 1.26.3 image: repository: gcr.io/spinnaker-marketplace/halyard tag: 1.32.0 persistence: enabled: true storageClass: nfs-retain minio: enabled: true image: tag: RELEASE.2020-01-03T19-12-21Z service: type: ClusterIP accessKey: admin secretKey: spinnaker persistence: enabled: true storageClass: \u0026#34;nfs-retain\u0026#34; redis: enabled: true password: spinnaker master: persistence: storageClass: nfs-retain cluster: enabled: false EOF kubectl create ns spinnaker helm upgrade --install spinnaker -f ./prod-values.yaml -n spinnaker stable/spinnaker 使用 traefik 暴露前端页面 # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: spin-deck namespace: spinnaker spec: entryPoints: - web routes: - match: Host(\\`spinnaker.treesir.pub\\`) \u0026amp;\u0026amp; PathPrefix(\\`/\\`) kind: Rule services: - name: spin-deck port: 9000 EOF cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: spin-gate namespace: spinnaker spec: entryPoints: - web routes: - match: Host(\\`spin-gate.treesir.pub\\`) \u0026amp;\u0026amp; PathPrefix(\\`/\\`) kind: Rule services: - name: spin-gate port: 8084 EOF 添加 host 记录\nsudo vi /etc/hosts 192.168.8.30 spinnaker.treesir.pub # 解析至对应 ip ## 设置deck与gate的域名 kubectl exec -it spinnaker-spinnaker-halyard-0 bash -n spinnaker hal config security ui edit --override-base-url http://spinnaker.treesir.pub hal config security api edit --override-base-url http://spin-gate.treesir.pub hal deploy apply # 应用应用生效 配置管理 openLdap # kubectl exec -it spinnaker-spinnaker-halyard-0 bash -n spinnaker hal config security authn ldap edit \\ --user-search-base \u0026#39;ou=users,dc=treesir,dc=pub\u0026#39; \\ --url \u0026#39;ldap://192.168.8.1:389\u0026#39; \\ --user-search-filter \u0026#39;cn={0}\u0026#39; \\ --manager-dn \u0026#39;cn=admin,dc=treesir,dc=pub\u0026#39; \\ --manager-password \u0026#39;123456\u0026#39; hal config security authn ldap enable hal deploy apply # 执行生效 备份 # 创建备份\nhal backup create 恢复备份\nhal backup restore --backup-path /home/spinnaker/halyard-xxxx.tar 删除部署 # hal deploy clean 请注意，此命令将破坏目标部署环境中的所有 Spinnaker 组件。因此，请谨慎使用它并备份您的配置，以防您要还原它。\n删除 Spinnaker 后，通过运行下面命令删除 halyard，下面命令在 helm 部署的集群中 不适应 ，在 helm 中 可以直接执行 helm delete xx 进行删除\nsudo ~/.hal/uninstall.sh 授权管理 # Fiat 组件是 Spinnaker 的授权（authz）微服务。它可以授予用户执行管道，查看基础结构等访问权限。默认情况下处于禁用状态。与身份验证非常相似，Spinnaker 允许使用各种可插入的授权机制。使用 Fiat 主要可以实现如下功能：\n限制对特定的clouddriver account 访问。（读、写）\n限制对特定的APP 应用程序访问。（读、写、执行）\n使用触发器对访问控制的程序运行管道。\n支持角色提供者中定期更新用户角色。\n当资源没有 定义允许谁访问资源时，就被认为是不受限制的。\n如果集群账户不受限制，则任何用户都可以使用该帐户部署新应用程序。\n如果应用程序不受限制，则任何用户都可以将该应用程序部署到其他帐户中。还能看到服务器组内的基本信息，例如实例名称。\nSpinnaker中的每个权限可以赋予角色，而不能是哪一个用户。\n一个帐户下可以包含多个应用程序，而应用程序也可以跨越多个帐户。\n授予对帐户的访问权不会同时授予对应用程序的访问权，反之亦然。\nSpinnaker支持： YamlFile、GitHubTeams、GoogleGroups、LDAPGroups、SAMLGroups。\nYamlFile： 使用yaml文件描述用户与角色的绑定。\nLDAPGroups：将用户与其在LDAP中所属组绑定\n授权(根据LDAP组进行同步) # 使用 关联 openLdap 命令方式 # 与下面 yaml 方式部署 二选一\nhal config security authz ldap edit \\ --url \u0026#39;ldap://192.168.8.1:389/dc=treesir,dc=pub\u0026#39; \\ --manager-dn \u0026#39;cn=admin,dc=treesir,dc=pub\u0026#39; \\ --manager-password \u0026#39;123456\u0026#39; \\ --user-dn-pattern \u0026#39;cn={0}\u0026#39; \\ --group-search-base \u0026#39;ou=groups\u0026#39; \\ --group-search-filter \u0026#39;uniqueMember={0}\u0026#39; \\ --group-role-attributes \u0026#39;cn\u0026#39; \\ --user-search-filter \u0026#39;cn={0}\u0026#39; hal config security authz edit --type ldap hal config security authz enable cat ~/.hal/config # 检查对应配置文件 ... authz: groupMembership: service: LDAP google: roleProviderType: GOOGLE github: roleProviderType: GITHUB file: roleProviderType: FILE ldap: roleProviderType: LDAP url: ldap://192.168.8.1:389/dc=treesir,dc=pub managerDn: cn=admin,dc=treesir,dc=pub managerPassword: \u0026#39;123456\u0026#39; userDnPattern: cn={0} groupSearchBase: ou=groups userSearchFilter: cn={0} groupSearchFilter: uniqueMember={0} groupRoleAttributes: cn enabled: true ... hal deploy apply # 执行生效 生效后，可以选择使用下面的 traefik 对 认证授权组件暴露出来，或者是使用内部ip进行访问\n测试是否有生效\ncurl -X POST http://spin-fiat.treesir.pub/roles/sync curl http://spin-fiat.treesir.pub/authorize/userid 2\u0026gt;\u0026amp;1|grep -A 10 roles userid 对应用户需要在 前端页面进行登录，不然会提示接口 404\n使用 yaml 方式 # #使用Yaml文件 hal config security authz enable hal config security authz file edit --file-path=$HOME/userrole.yaml hal config security authz edit --type file 认证授权服务组件 fiat 使用 traefik 进行暴露 # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: spin-fiat namespace: spinnaker spec: entryPoints: - web routes: - match: Host(\\`spin-fiat.treesir.pub\\`) \u0026amp;\u0026amp; PathPrefix(\\`/\\`) kind: Rule services: - name: spin-fiat port: 7003 EOF 授权管理 # 现在我们有两个组，设置 ops 组只读，devops 组可以读写\nkubectl exec -it spinnaker-spinnaker-halyard-0 bash -n spinnaker hal config provider kubernetes account edit default \\ --add-read-permission devops,ops \\ --add-write-permission devops # 下面这两个可以指定删除权限 --remove-read-permission --remove-write-permission cat ~/.hal/config # 检查配置文件 ... kubernetes: enabled: true accounts: - name: default requiredGroupMembership: [] providerVersion: V2 permissions: READ: - devops WRITE: - devops ... hal deploy apply # 执行生效 使用 yaml 定义角色和用户 # 1.首先先关闭LDAP权限。 hal config security authz disable vi $HOME/userrole.yml users: - username: yangzun roles: - devops - bar - foo - username: test roles: - test - bar - username: anonymous roles: [] 2.配置Yaml文件 hal config security authz enable hal config security authz file edit --file-path=$HOME/userrole.yml hal config security authz edit --type file cat ~/.hal/config ... authz: groupMembership: service: FILE google: roleProviderType: GOOGLE github: roleProviderType: GITHUB file: roleProviderType: FILE path: /home/spinnaker/userrole.yml enabled: true ... hal deploy apply # 执行生效 创建管理员权限\nvi ~/.hal/default/profiles/fiat-local.yml fiat: admin: roles: - devops-admin # 将此组 添加管理员权限 vi $HOME/userrole.yml # 用户添加进组 users: - username: yangzun roles: - devops - bar - foo - devops-admin # 设置使用刚添加的权限 ... hal deploy apply 关联 jenkins # kubectl exec -it spinnaker-spinnaker-halyard-0 bash -n spinnaker hal config ci jenkins enable hal config ci jenkins master add jenkins-master \\ --address http://ci.treesir.pub \\ --username yangzun \\ --password 117a723c9b729b2256e3ffc40c3d2b2156 ## 启用csrf hal config ci jenkins master edit jenkins-master --csrf true cat ~/.hal/config ... ci: jenkins: enabled: true masters: - name: jenkins-master permissions: {} address: http://ci.treesir.pub username: yangzun password: 117a723c9b729b2256e3ffc40c3d2b2156 csrf: true ... hal deploy apply # 应用并生效 在 Jenkins 中安装Strict Crumb Issuer插件\n全局安全设置中 开启对应参数\n测试；在 spinnaker 中 新建 jenkins 类型触发 ，查看是否可以看到对应 jenkins 中的 pipeline\n消息通知之邮件通知 # kubectl exec -it spinnaker-spinnaker-halyard-0 bash -n spinnaker cd /home/spinnaker/.hal/default/profiles cat \u0026gt;\u0026gt; settings-local.js \u0026lt;\u0026lt; EOF window.spinnakerSettings.notifications.email.enabled = true; EOF cat \u0026gt; echo-local.yml \u0026lt;\u0026lt; EOF mail: enabled: true from: amoaloas@163.com spring: mail: host: smtp.163.com username: amoaloas@163.com password: FZXIQXXO protocol: smtp default-encoding: utf-8 properties: mail: display: sendname: SpinnakerAdmin smtp: port: 465 auth: true starttls: enable: true required: true ssl: enable: true transport: protocol: smtp debug: true EOF hal deploy apply # 应用生效 更改时区 # hal config edit --timezone \u0026#39;Asia/Shanghai\u0026#39; hal deploy apply # 应用生效 开启制品空间 # 开启http\n参考文档 hal config artifact http enable echo ${USERNAME}:${PASSWORD} \u0026gt; $USERNAME_PASSWORD_FILE hal config artifact http account add my-http-account \\ --username-password-file $USERNAME_PASSWORD_FILE # 添加对应账号密码 开启特征功能 # hal config features edit --pipeline-templates true hal config features edit --artifacts true hal config features edit --managed-pipeline-templates-v2-ui true 开启对 pipeline 的权限管理 # ~/.hal/default/profiles/orca-local.yml tasks: useManagedServiceAccounts: true ~/.hal/default/profiles/settings-local.js window.spinnakerSettings.feature.managedServiceAccounts = true; hal deploy apply # 应用生效 添加 k8s 集群 # hal config provider kubernetes enable hal config provider kubernetes account add k3s \\ --context $(kubectl config current-context --kubeconfig ~/.kube/k3s) \\ --service-account true \\ --omit-namespaces=kube-system,kube-public \\ --kubeconfig-file ~/.kube/k3s \\ --provider-version v2 hal config deploy edit --type distributed --account-name k3s CONTEXT=$(kubectl config current-context) hal config provider kubernetes account add default \\ --context $CONTEXT 添加 dokcer 镜像仓库 # hal config provider docker-registry enable --no-validate hal config provider docker-registry account add my-harborregistry \\ --address https://harbor.treesir.pub \\ --username yangzun \\ --password 123456 helm 部署 spinnaker 添加 集群 # kubectl get po --kubeconfig ~/.kube/k3s --all-namespaces # 测试 对应 config 文件是否有效 kubectl create secret generic --from-file=$HOME/.kube/config kube-config -n spinnaker kubeConfig: enabled: true secretName: kube-config secretKey: config contexts: == - my-context # This is the context from the list above that you would like # to deploy Spinnaker itself to. deploymentContext: my-context 手动启动 halyard # 部署 minio\nkubectl create ns devops-minio helm repo add minio https://helm.min.io/ helm repo update cat \u0026gt; prod-values.yaml \u0026lt;\u0026lt; EOF image: tag: RELEASE.2021-03-17T02-33-02Z accessKey: \u0026#34;admin\u0026#34; secretKey: \u0026#34;ancun123\u0026#34; persistence: existingClaim: \u0026#34;devops-minio-pvc\u0026#34; accessMode: ReadWriteOnce size: 100Gi service: type: NodePort buckets: - name: spinnaker policy: none purge: false EOF cat \u0026gt; devops-minio-pvPvc.yaml \u0026lt;\u0026lt; EOF apiVersion: v1 kind: PersistentVolume metadata: name: devops-minio-pv spec: storageClassName: local-storage # Local PV capacity: storage: 100Gi volumeMode: Filesystem accessModes: - ReadWriteOnce local: path: /data/devops/minio nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - mn105 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: devops-minio-pvc namespace: devops-minio spec: storageClassName: local-storage accessModes: - ReadWriteOnce resources: requests: storage: 100Gi EOF mkdir -p /data/devops/minio # 创建 localPv 目录 kubectl apply -f ./devops-minio-pvPvc.yaml docker pull gcr.io/spinnaker-marketplace/halyard:1.32.0 mkdir ~/.hal chmod -R 777 ~/.hal docker run -itd --name halyard \\ -v /root/.hal:/home/spinnaker/.hal \\ -v /root/.kube:/home/spinnaker/.kube \\ --restart=always \\ --net=host \\ idocker.io/spinnaker-marketplace/halyard:1.32.0 docker exec -it halyard bash hal config version edit --version 1.25.5 hal config edit --timezone Asia/Shanghai hal config storage s3 edit \\ --endpoint http://s3.treesir.pub \\ --access-key-id admin \\ --secret-access-key 12345678 \\ --bucket spinnaker \\ --path-style-access true hal config storage edit --type s3 hal config security ui edit --override-base-url http://spinnaker.treesir.pub hal config security api edit --override-base-url http://spin-gate.treesir.pub hal config provider docker-registry enable hal config provider kubernetes enable hal config provider kubernetes account add default \\ --context $(kubectl config current-context) \\ --service-account true \\ --omit-namespaces=kube-system,kube-public,cattle-system,cattle-prometheus \\ --provider-version v2 \\ --no-validate hal config deploy edit \\ --account-name default \\ --type distributed \\ --location spinnaker # 部署集群 hal deploy apply # 执行生效 todo # ","date":"2021年5月24日","externalUrl":null,"permalink":"/posts/spinnaker-helm-installd/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eKubernetes Version: \u003ccode\u003ev1.17.9\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e操作系统: \u003ccode\u003eCentOS 7.8.2003 \u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eHelm Version: \u003ccode\u003ev3.2.1\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eSpinnaker Version: \u003ccode\u003e1.26.3\u003c/code\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca\n  href=\"https://spinnaker.io/community/releases/versions/\"\n    target=\"_blank\"\n  \u003espinnaker 版本说明\u003c/a\u003e\u003c/p\u003e\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003espinnaker 组件说明 \n    \u003cdiv id=\"spinnaker-组件说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#spinnaker-%e7%bb%84%e4%bb%b6%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eDeck：前端web页面  端口9000\u003c/p\u003e","title":"使用 Helm 部署 Spinnaker 持续部署(CD)平台","type":"posts"},{"content":" 环境说明 # helm version: v3.3.1 kubernetes: v1.17.9 nexus: 3.29.0 部署 # 准备 storageClass # 非 必要 操作，可以选择手动创建 pvc \u0026amp; pv，如果想部署 nfsStorageClass 的话，请参考早期整理的 文档。此篇文档实战部署步骤中基于 nfsStorageClass 进行实现。如果是用于 生产环境 的话，还是建议使用 ssd 或 iops 较高的 磁盘 作为数据盘使用，数据盘管理方式可以使用 localPv or hostPath 绑定到对应节点进行运行。\n使用 helm 进行安装 # 添加 helm 仓库 # helm repo add stable https //charts.helm.sh/stable helm repo update 设置部署 values.yaml 配置文件 # 可以执行下面命令进行查看 默认 模板部署文件，或点击此 链接 查看。\nhelm show values stable/sonatype-nexus 修改后，完整 values.yaml 文件展示\ncat \u0026gt; prod-values.yaml \u0026lt;\u0026lt; EOF statefulset: # 使用 statefulset 进行部署 enabled: true nexus: imageName: sonatype/nexus3 imageTag: 3.30.1 imagePullPolicy: IfNotPresent env: - name: install4jAddVmParams value: \u0026#34;-Xms1200M -Xmx1200M -XX:MaxDirectMemorySize=2G -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap\u0026#34; - name: NEXUS_SECURITY_RANDOMPASSWORD value: \u0026#34;false\u0026#34; - name: TZ value: \u0026#34;Asia/Shanghai\u0026#34; resources: limits: cpu: 4 memory: 8192Mi requests: cpu: 250m memory: 4800Mi persistence: enabled: true storageClass: \u0026#34;nfs-retain\u0026#34; storageSize: \u0026#34;100Gi\u0026#34; service: name: nexus-service enabled: true serviceType: ClusterIP ports: - name: manage # 此处为 service 对应暴露的端口， 如需暴露其他端口时，向下进行添加即可。 targetPort: 8081 port: 8081 - name: docker-proxy targetPort: 8082 port: 8082 - name: docker-dev targetPort: 8083 port: 8083 - name: docker-qa targetPort: 8084 port: 8084 - name: docker-prod targetPort: 8085 port: 8085 - name: docker-custom targetPort: 8086 port: 8086 nexusProxy: enabled: false EOF 创建部署应用 # kubectl create ns nexus helm upgrade --install nexus -f ./prod-values.yaml -n nexus stable/sonatype-nexus # 部署安装 使用 traefik 将其暴露 # traefik 部署安装使用，请参考 此篇文档\ncat nexus-ingressroute.yaml apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: nexus-mirror namespace: nexus spec: entryPoints: - web routes: - match: Host(`mirror.treesir.pub`) \u0026amp;\u0026amp; PathPrefix(`/`) kind: Rule services: - name: nexus-service port: 8081 kubectl apply -f ./nexus-ingressroute.yaml 初始化U/P: admin: admin123\nDocker 私服的配置与优化 # 此步骤文档，记录创建 custom、dev、qa 、prod 环境的 docker 私服，并配置常见 docker proxy 类型私服，进行中间镜像的缓存进行使用。\n仓库规划 # docker 私服端口 使用规划 # 私服名称 私服作用 私服类型 私服端口 docker-custom 存放，自定义 push的 镜像，与项目环境无关 hostd 8086 idocker.io 代理仓库 \u0026amp; custom 仓库的集合 group 8082 docker-dev 存放与项目dev环境镜像 hostd 8083 docker-qa 存放与项目qa环境镜像 hostd 8084 docker-prod 存放与项目prod环境镜像 hostd 8085 docker 代理仓库列表 # 名称 私服类型 说明 地址 docker-google proxy google 公开镜像 （需扶墙） https://gcr.io docker-k8s proxy kubernetes 的官方 google 镜像源（需扶墙） https://k8s.gcr.io docker-aliyun proxy aliyun 同步 docker 官方源 （存在部分镜像未同步问题） https://7bezldxe.mirror.aliyuncs.com docker-official proxy dockerhub 官方镜像地址（限制带宽，触发条件：匿名用户100次，认证用户200次） https://registry-1.docker.io 私服创建 # 创建 hostd 类型私服 # 且示例创建 docker-custom , 其他仓库创建步骤是一样的，且需更具实际使用情况 更改 Deployment policy 策略 \u0026amp; 是否开启 匿名拉取镜像 即可，如在 prod 环境下，设置策略为 Disable redeploy ，即不允许对已存在的镜像做覆盖操作，push 时将提示错误。\n创建 docker 使用的 blob 存储\n建议对不同仓库类型的 私服做一下，blob存储的隔离 (可选)\n选择创建 hostd 类型 docker 私服\n创建 proxy 类型代理私服 # 且示例创建 docker-official 私服，因官方对匿名用户做了拉取到100次后，将降低带宽的使用，我们可以在配置的过程中，为其添加用户认证来增加拉取限制。\n选择创建 proxy 类型 docker 私服\n创建 group 类型私服 # 创建完成 对应 的 proxy 类型的私服后，就可以进行 group 类型私服， 一组仓库的集合的创建了。\n组集合列表，如下所示\n创建 group 组 idocker.io\n配置 neuxs3 开启 使用 docker的认证管理 # 默认情况下，docker 认证在 neuxs3 中是没有被打开的，这里需要配置开启一下才行，不然会导致 执行 docker login 不成功。\n​\t使用 ingress 对端口进行暴露 # 在使用 traefik 暴露的docker私服中，因默认开启了 443 端口的监听，此时 docker 私服将默认使用 websecure 入口上的 ingressroute 路由规则，如不添加对应证书的话，将导致 match 上的规则不被匹配\n创建 tls 证书生成脚本 # gen-cert.sh 证书签发脚本，如下所示:\n#!/bin/bash # scripts from https://www.jianshu.com/p/52fedb82ef53 usage() { echo \u0026#34;Usage: $0 [-a [rsa|ecc]] [-d \u0026lt;domain\u0026gt;] [-n \u0026lt;name\u0026gt;] [-h]\u0026#34; echo \u0026#34; Options:\u0026#34; echo \u0026#34; -a algorithm.[rsa|ecc]\u0026#34; echo \u0026#34; -d domain.example: xxx.com,abc.org,*.abc.org\u0026#34; echo \u0026#34; -n server key name\u0026#34; echo \u0026#34; -h help\u0026#34; exit 1 } srv_key_name=\u0026#34;server\u0026#34; while getopts \u0026#34;a:d:n:h\u0026#34; arg #选项后面的冒号表示该选项需要参数 do case $arg in a) alg=$OPTARG #算法 ;; d) all_domain=$OPTARG #域名,逗号分隔 ;; n) srv_key_name=$OPTARG #服务器证书名称 ;; h) usage exit 0 ;; ?) #当有不认识的选项的时候arg为? usage exit 1 ;; esac done domain=\u0026#34;domain.com\u0026#34; san=\u0026#34;DNS:*.${domain},DNS:${domain}\u0026#34; if [ -n \u0026#34;${all_domain}\u0026#34; ]; then #分割域名 OLD_IFS=\u0026#34;$IFS\u0026#34; IFS=\u0026#34;,\u0026#34; domain_array=($all_domain) IFS=\u0026#34;$OLD_IFS\u0026#34; domain_len=${#domain_array[@]} domain=${domain_array[0]} san=\u0026#34;\u0026#34; for ((i=0;i\u0026lt;domain_len;i++)) { if [ $i = 0 ];then san=\u0026#34;DNS:${domain_array[i]}\u0026#34; else san=\u0026#34;${san},DNS:${domain_array[i]}\u0026#34; fi } fi ca_subj=\u0026#34;/C=CN/ST=Hubei/L=Wuhan/O=MY/CN=MY CA\u0026#34; server_subj=\u0026#34;/C=CN/ST=Hubei/L=Wuhan/O=MY/CN=${domain}\u0026#34; #其中C是Country，ST是state，L是local，O是Organization，OU是Organization Unit，CN是common name days=14610 # 有效期40年 echo \u0026#34;san:${san}\u0026#34; sdir=\u0026#34;certs\u0026#34; ca_key_file=\u0026#34;${sdir}/ca.key\u0026#34; ca_crt_file=\u0026#34;${sdir}/ca.crt\u0026#34; srv_key_file=\u0026#34;${sdir}/${srv_key_name}.key\u0026#34; srv_csr_file=\u0026#34;${sdir}/${srv_key_name}.csr\u0026#34; srv_crt_file=\u0026#34;${sdir}/${srv_key_name}.crt\u0026#34; srv_p12_file=\u0026#34;${sdir}/${srv_key_name}.p12\u0026#34; srv_fullchain_file=\u0026#34;${sdir}/${srv_key_name}-fullchain.crt\u0026#34; cfg_san_file=\u0026#34;${sdir}/san.cnf\u0026#34; #algorithm config if [[ ${alg} = \u0026#34;rsa\u0026#34; ]] ; then rsa_len=2048 elif [[ ${alg} = \u0026#34;ecc\u0026#34; ]] ; then ecc_name=prime256v1 else usage exit 1 fi #ifend echo \u0026#34;algorithm:${alg}\u0026#34; mkdir -p ${sdir} if [ ! -f \u0026#34;${ca_key_file}\u0026#34; ]; then echo \u0026#34;------------- gen ca key-----------------------\u0026#34; if [[ ${alg} = \u0026#34;rsa\u0026#34; ]] ; then openssl genrsa -out ${ca_key_file} ${rsa_len} elif [[ ${alg} = \u0026#34;ecc\u0026#34; ]] ; then openssl ecparam -out ${ca_key_file} -name ${ecc_name} -genkey fi #ifend openssl req -new -x509 -days ${days} -key ${ca_key_file} -out ${ca_crt_file} -subj \u0026#34;${ca_subj}\u0026#34; fi if [ ! -f \u0026#34;${srv_key_file}\u0026#34; ]; then echo \u0026#34;------------- gen server key-----------------------\u0026#34; if [[ ${alg} = \u0026#34;rsa\u0026#34; ]] ; then openssl genrsa -out ${srv_key_file} ${rsa_len} elif [[ ${alg} = \u0026#34;ecc\u0026#34; ]] ; then openssl ecparam -genkey -name ${ecc_name} -out ${srv_key_file} fi #ifend openssl req -new -sha256 -key ${srv_key_file} -out ${srv_csr_file} -subj \u0026#34;${server_subj}\u0026#34; printf \u0026#34;[ SAN ]\\nauthorityKeyIdentifier=keyid,issuer\\nbasicConstraints=CA:FALSE\\nkeyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment\\nsubjectAltName=${san}\u0026#34; \u0026gt; ${cfg_san_file} openssl x509 -req -days ${days} -sha256 -CA ${ca_crt_file} -CAkey ${ca_key_file} -CAcreateserial -in ${srv_csr_file} -out ${srv_crt_file} -extfile ${cfg_san_file} -extensions SAN cat ${srv_crt_file} ${ca_crt_file} \u0026gt; ${srv_fullchain_file} openssl pkcs12 -export -inkey ${srv_key_file} -in ${srv_crt_file} -CAfile ${ca_crt_file} -chain -out ${srv_p12_file} fi 脚本使用用法\n./gen-cert.sh -a 算法 -d 域名 -n 证书文件名 执行脚本生成 tls 证书文件 # 由于 traefik 在使用 secret 资源对象引用证书文件时，名称必须是 tls.crt 和 tls.key，这里我们在创建的时候就将名称设置为 tls\nchmod a+x gen-cert.sh ./gen-cert.sh -a ecc -d idocker.io,*.idocker.io -n tls ls certs/ # 在对应目录下，有如下文件即可 ca.crt ca.key ca.srl san.cnf tls.crt tls.csr tls-fullchain.crt tls.key tls.p12 将生成的证书，使用 secret 资源对象进行存储 # kubectl create secret tls idocker-tls --cert=certs/tls.crt --key=certs/tls.key -n nexus kubectl get secret idocker-tls -n nexus -o yaml 更新 traefik ingress 资源对象 # 最终，完整 nexus-ingressroute.yaml 配置文件如下所示:\napiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: nexus-mirror namespace: nexus spec: entryPoints: - web routes: - match: Host(`mirror.treesir.pub`) \u0026amp;\u0026amp; PathPrefix(`/`) kind: Rule services: - name: nexus-service port: 8081 --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: nexus-idocker-http namespace: nexus spec: entryPoints: - websecure routes: - match: Host(`idocker.io`) || (Host(`idocker.io`) \u0026amp;\u0026amp; Method(`GET`)) kind: Rule services: - name: nexus-service port: 8082 - match: (Host(`idocker.io`) \u0026amp;\u0026amp; Path(`/v1/search`)) || (Host(`idocker.io`) \u0026amp;\u0026amp; Method(`PUT`,`HEAD`,`POST`,`PATCH`)) kind: Rule priority: 100 # 权重值提高，在执行 curl https://idocker.io/v1/search?q=nginx 时，确认路由至 dokcer-custom 私服中 services: - name: nexus-service port: 8086 - match: Host(`dev.idocker.io`) \u0026amp;\u0026amp; PathPrefix(`/`) kind: Rule services: - name: nexus-service port: 8083 - match: Host(`qa.idocker.io`) \u0026amp;\u0026amp; PathPrefix(`/`) kind: Rule services: - name: nexus-service port: 8084 - match: Host(`prod.idocker.io`) \u0026amp;\u0026amp; PathPrefix(`/`) kind: Rule services: - name: nexus-service port: 8085 tls: secretName: idocker-tls --- apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: redirect-https namespace: nexus spec: redirectScheme: scheme: https --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: nexus-idocker-https namespace: nexus spec: entryPoints: - web routes: - match: HostRegexp(`idocker.io`, `{subdomain:[a-z]+}.idocker.io`) kind: Rule services: - name: nexus-service port: 8082 middlewares: - name: redirect-https 私服的使用 # ingressroute 创建完成后，就是测试对应私服的使用了，直接拿来使用还不行，会提示证书报错，解决方法列表如下:\n将对应 CA 证书文件，复制到 /etc/docker/certs.d/idocker.io 目录即可，没有此目录则选择创建它\nmkdir -p /etc/docker/certs.d/idocker.io scp node1:/data/helm/nexus/certs/ca.crt /etc/docker/certs.d/idocker.io/ mkdir -p /etc/docker/certs.d/qa.idocker.io cp /etc/docker/certs.d/idocker.io/ca.crt /etc/docker/certs.d/qa.idocker.io/ 由于，不支持对泛域名的一次认证，如多个子域名时，还需进行一个多次的 copy 操作。\n在对应 docker 配置中添加 --insecure-registry 参数，来忽略证书的校验，常见做法是:\n将配置项更改添加至 /etc/docker/daemon.json 配置文件中\ncat /etc/docker/daemon.json { \u0026#34;insecure-registries\u0026#34;: [ \u0026#34;idocker.io\u0026#34;, \u0026#34;dev.idocker.io\u0026#34;, \u0026#34;qa.idocker.io\u0026#34;, \u0026#34;prod.idocker.io\u0026#34; ] } 或者是在 /usr/lib/systemd/system/docker.service 启动文件中的， ExecStart 配置项中添加启动参数\nExecStart=/usr/bin/dockerd --insecure-registry idocker.io,dev.idocker.io... 参考文档 # nexus 其他高级使用说明，请参考 此篇文档\nhttps://doc.traefik.io/traefik/routing/routers/\nhttps://www.jianshu.com/p/52fedb82ef53\nTodo # ","date":"2021年5月23日","externalUrl":null,"permalink":"/posts/helm-k8s-deploy-nexus/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003ehelm version: \u003ccode\u003ev3.3.1\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003ekubernetes: \u003ccode\u003ev1.17.9\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003enexus: \u003ccode\u003e3.29.0\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e部署 \n    \u003cdiv id=\"部署\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%83%a8%e7%bd%b2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e准备 storageClass \n    \u003cdiv id=\"准备-storageclass\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%86%e5%a4%87-storageclass\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e非 \u003ccode\u003e必要\u003c/code\u003e 操作，可以选择手动创建 pvc \u0026amp; pv，如果想部署 \u003ccode\u003enfsStorageClass\u003c/code\u003e 的话，请参考早期整理的 \u003ca\n  href=\"https://www.treesir.pub/post/k8s-nfs-strage-class/\"\n    target=\"_blank\"\n  \u003e文档\u003c/a\u003e。此篇文档实战部署步骤中基于 \u003ccode\u003enfsStorageClass \u003c/code\u003e 进行实现。如果是用于 \u003ccode\u003e生产环境\u003c/code\u003e 的话，还是建议使用 ssd 或 iops 较高的 \u003ccode\u003e磁盘\u003c/code\u003e 作为数据盘使用，数据盘管理方式可以使用 localPv or hostPath 绑定到对应节点进行运行。\u003c/p\u003e","title":"使用 helm 在 Kubernetes 中部署 Nexus 私服","type":"posts"},{"content":"","date":"2021年5月19日","externalUrl":null,"permalink":"/categories/alertmanage/","section":"Categories","summary":"","title":"Alertmanage","type":"categories"},{"content":"","date":"2021年5月19日","externalUrl":null,"permalink":"/tags/exporter/","section":"Tags","summary":"","title":"Exporter","type":"tags"},{"content":"","date":"2021年5月19日","externalUrl":null,"permalink":"/tags/k8s/","section":"Tags","summary":"","title":"K8s","type":"tags"},{"content":"","date":"2021年5月19日","externalUrl":null,"permalink":"/tags/operator/","section":"Tags","summary":"","title":"Operator","type":"tags"},{"content":"","date":"2021年5月19日","externalUrl":null,"permalink":"/categories/prometheus/","section":"Categories","summary":"","title":"Prometheus","type":"categories"},{"content":"","date":"2021年5月19日","externalUrl":null,"permalink":"/tags/prometheus/","section":"Tags","summary":"","title":"Prometheus","type":"tags"},{"content":" 环境说明 # 此文档为 rancher monitor 使用系列的 第三篇 ，主要介绍与 dingtalk 关联配置告警通知、prometheus 告警阈值的配置使用\n配置安装 dingtalk webhook # webhook github 地址\n安装 kustomize # curl -s \u0026#34;https://raw.githubusercontent.com/\\ kubernetes-sigs/kustomize/master/hack/install_kustomize.sh\u0026#34; | bash mv kustomize /usr/local/bin/ kustomize version {Version:kustomize/v4.1.2 GitCommit:a5914abad89e0b18129eaf1acc784f9fe7d21439 BuildDate:2021-04-15T20:38:06Z GoOs:linux GoArch:amd64} 使用 kustomize 部署 deployment # clone 代码\nyum install -y git # 安装 git git clone https://github.com/timonwong/prometheus-webhook-dingtalk.git cd prometheus-webhook-dingtalk/contrib/k8s/ 创建 dingtalk 自定义机器人\n安全设置这里我们选择 加签 处理\n复制机器人 webhook 信息填入至配置文件中\nvi config/config.yaml 部署 yaml 配置文件\nsed -i \u0026#34;s#monitoring#prometheus#g\u0026#34; kustomization.yaml # 修改 配置文件中指定部署的 命名空间 kustomize build|kubectl apply -f - # 执行部署 configmap/alertmanager-webhook-dingtalk-f5hkbg7g25 created service/alertmanager-webhook-dingtalk created deployment.apps/alertmanager-webhook-dingtalk created 测试是否可以正常发送通知\nkubectl get po alertmanager-webhook-dingtalk-5c7b48fd9d-bcpxb -n prometheus -o wide # 获取 pod ip curl \u0026#39;http://10.233.90.19:8060/dingtalk/webhook1/send\u0026#39; \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#39;{\u0026#34;Status\u0026#34;: \u0026#34;testing\u0026#34;}\u0026#39; 正常将信息发送至 机器人，而且还做了转 大写 处理，这是因为我们默认使用的 template 里面做了操作导致。\nRancher alertmanage 配置 # alertmanage 的安装非常简单 只需要在 rancher 对应的 dashboard 中设置开启通知， 就会自动帮我去创建相应的 alertmanage clustert。\n关联前面部署的 dingtalk webhook # 点击通知\n添加通知\n选择添加 webhook 类型通知，名称随意设置，webhook 的地址，我们这里配置使用 k8s 内部域名，测试时无法解析此域名属于正常现象，因为所部署的 rancher 并不在当前k8s中，只要保证后面的 alertmanage可以正常访问即可，确认后点击添加。\n手动测试，告警是否有生效 # 点击告警\n选择一组告警组 关联刚才添加的通知\n拉到最下面 设置为我们上面添加的 告警\n可以看到此时我们关联告警后，operator 后端为我们自动去创建了 alertmanger 手动将阈值降低，触发告警\n这里我们已 cpu 一分钟平均负载情况，作为示例，更改为 使用到 5% 就触发告警，持续时间更改为 1s\n等待一会后，已触发告警的发送了\n这里告警出来后，alertmanger 这边还需要一段时间进行处理，趁这个时间，我们把 alertmanager 的 service 类型更改为前面 prometheus 一样的 NodePort 类型。\nkubectl edit svc access-alertmanager -n cattle-prometheus ... app: alertmanager chart: alertmanager-0.0.1 release: cluster-alerting sessionAffinity: None type: NodePort ... 我们使用 对应的 NodePort 进行访问 alertmanger 的 dashboard\n可以看到 alertmanger 中也已有对应的告警通知，再次查看一下 dingtalk 这边也正常将通知出来了。\n当我们把 阈值还原时，恢复通知也正常发送了出来\n通知优化及阈值告警配置 # 阈值告警配置 # alert rule 请参考 此链接。\n**在对应的告警组或新键的告警组中，参考上面链接中的 rule 配置添加即可 **\n这里需要注意一下的就是，使用 表达式 的这种方法相对更加灵活一些，还有就是需要注意一下 rancher 中也有逻辑判断符的，这里需要注意一下，不要 重复 添加了。\n通知优化 # 图标链接更改优化\n可以看到点击图标跳转的这个地址不对，对于故障的定位排除，点击就能跳转到正确的页面还是非常有之必要的。\n修改 prometheus 资源对象进行添加 externalUrl 配置即可\nkubectl edit prometheus -n cattle-prometheus ... securityContext: fsGroup: 2000 runAsNonRoot: true runAsUser: 1000 serviceAccountName: cluster-monitoring externalUrl: http://192.168.8.30:32209 ... 检查对应的 yaml 可以看到 args 中 已添加了 --web.external-url 配置了, alertmanager 的配置添加也是一样的，同样添加 externalUrl 字段即可。\nTo Do # \u0026hellip;..\n","date":"2021年5月19日","externalUrl":null,"permalink":"/posts/rancher-monitor-config-third/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e此文档为 rancher \u003ccode\u003emonitor\u003c/code\u003e  使用系列的 \u003ccode\u003e第三篇\u003c/code\u003e ，主要介绍与 \u003ccode\u003edingtalk\u003c/code\u003e 关联配置告警通知、prometheus 告警阈值的配置使用\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e配置安装 dingtalk webhook \n    \u003cdiv id=\"配置安装-dingtalk-webhook\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%85%8d%e7%bd%ae%e5%ae%89%e8%a3%85-dingtalk-webhook\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca\n  href=\"https://github.com/timonwong/prometheus-webhook-dingtalk\"\n    target=\"_blank\"\n  \u003ewebhook github 地址\u003c/a\u003e\u003c/p\u003e","title":"Rancher 开启监控后的，阈值告警配置说明 (三)","type":"posts"},{"content":"","date":"2021年5月17日","externalUrl":null,"permalink":"/tags/metrics/","section":"Tags","summary":"","title":"Metrics","type":"tags"},{"content":" 环境说明 # kubernetes version: v1.17.9 rancher dashboard: v2.3.5 操作系统: centos 7.8.2003 基础环境配置 # 使用 kubekey 部署 kubernetes 集群 # 由于篇幅原因，此处省略部署说明，请参考较早期的 文档说明\nrancher 单机部署 # 安装 docker-compose\nyum install -y docker-compose 创建 workspace 目录\nmkdir -p /data/docker-compose/rancher 创建配置文件 \u0026amp; 启动容器\ncd /data/docker-compose/rancher cat \u0026gt; docker-compose.yaml \u0026lt;\u0026lt; EOF version: \u0026#34;3\u0026#34; services: rancher: container_name: rancher image: rancher/rancher:v2.3.5 # 这里注意一下版本，请使用 2.0.x - 2.4.x 的版本，因为在 2.5 版本后之后的版本，对 dashboard 界面和管理方式做了大幅修改。 volumes: - /var/lib/rancher:/var/lib/rancher # rancher 数据持久化 ports: - \u0026#34;80:80/tcp\u0026#34; - \u0026#34;443:443/tcp\u0026#34; privileged: true restart: unless-stopped EOF docker-compose up -d # 启动对应容器 docker-compose logs -f # 启动后观察 一下启动日志，有无错误信息 省略 rancher 导入集群操作，请参考较之前的 文档\n安装 nfs classStorage # 此处同样省略，请参照早期 文档\n开启监控 # 首先为集群开启监控 # rancher 需要开启系统层面 的监控，才能逐个打开项目级别的监控。\n开启系统层面的监控\n点击选择的集群\n这里数据持久时间为 168h , 一般开发环境是够用了，生产环境按需延长就好。使用存储类为 nfs，如有条件建议更换为分布式存储，如 ceph，解决 nfs 单点故障问题。资源限制这里做了增大处理，毕竟 prometheus 数据是先暂存到内存中，在一定时间后定期刷新到磁盘上去的，还是比较吃内存的，为避免出现 oomkill 情况，建议最少也的配置一个 4g 内存。\n等待所有 pod 启动完成，命名空间为 cattle-prometheus\n可以看到，再次登录页面查看时，页面也已经丰富了许多。这里需要特别主要以下这个聚合图标，蓝色 为集群资源限制中 limits 所预留占用的资源的聚合，绿色 为集群目前正在使用的资源情况。\n开启项目级别监控 # 这个就是 rancher 中的项目，属于是 rancher 中对 一组 namespace 集合的抽象。\n选取需要开启监控的项目\n与系统层面的监控类似，这个多是基于 operator 中的 prometheus 资源对象实现，后面会有详细的配置说明。\n监控系统优化 # 更改 service 模式为 NodePort # 虽然 rancher 对 prometheus operator 做了一层 多租户 的处理，但是 granfana 却并没有做关联 rancher 的多租户处理， grafana 默认 U/P admin/admin。个人 觉得监控系统的多租户不是很好用，使用起来也有点有点鸡肋。这里演示配置将 service 更改为 NodePort 模式，绕过 rancher 多租户进行使用。\n查看 目前 service 资源对象\nkubectl get svc -n cattle-prometheus access-grafana ClusterIP 10.233.54.97 \u0026lt;none\u0026gt; 80/TCP 24m access-prometheus ClusterIP 10.233.30.75 \u0026lt;none\u0026gt; 80/TCP 24m expose-grafana-metrics ClusterIP None \u0026lt;none\u0026gt; 3000/TCP 24m expose-kubelets-metrics ClusterIP None \u0026lt;none\u0026gt; 10250/TCP 24m expose-kubernetes-metrics ClusterIP None \u0026lt;none\u0026gt; 8080/TCP,8081/TCP 24m expose-node-metrics ClusterIP None \u0026lt;none\u0026gt; 9796/TCP 24m expose-operator-metrics ClusterIP None \u0026lt;none\u0026gt; 47323/TCP 24m expose-prometheus-metrics ClusterIP None \u0026lt;none\u0026gt; 9090/TCP 24m prometheus-operated ClusterIP None \u0026lt;none\u0026gt; 9090/TCP 24m 更改 access-grafana \u0026amp; access-prometheus 这两个 service 资源对象，除了这两个资源对象外，应该还有一个 access-alertmanager 才对，不过这个我们后面才会介绍使用到，这里不做过多的赘述。\nkubectl edit svc access-prometheus access-grafana -n cattle-prometheus ... selector: app: prometheus chart: prometheus-0.0.1 release: cluster-monitoring sessionAffinity: ClientIP sessionAffinityConfig: clientIP: timeoutSeconds: 10800 type: NodePort # 将这个字段更改为 NodePort 即可 ... 访问一下 prometheus\n安装 metrics-server # 如果我们后面需要使用 HPA ，就需要在集群中安装 Metrics Server 服务，要安装 Metrics Server 就需要开启 apiserver 的 Aggregator，因为 Metrics Server 就是通过该代理进行扩展的，如果使用的是 Kubeadm ( kubekey 也是基于 kubeadm ) 搭建的，默认就已经是开启状态了，如果是二进制方式安装的集群，需要单独配置 kube-apsierver 添加参数，详细说明，请参考 此篇文档\n执行安装\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-app: metrics-server name: metrics-server namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: k8s-app: metrics-server rbac.authorization.k8s.io/aggregate-to-admin: \u0026#34;true\u0026#34; rbac.authorization.k8s.io/aggregate-to-edit: \u0026#34;true\u0026#34; rbac.authorization.k8s.io/aggregate-to-view: \u0026#34;true\u0026#34; name: system:aggregated-metrics-reader rules: - apiGroups: - metrics.k8s.io resources: - pods - nodes verbs: - get - list - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: k8s-app: metrics-server name: system:metrics-server rules: - apiGroups: - \u0026#34;\u0026#34; resources: - pods - nodes - nodes/stats - namespaces - configmaps verbs: - get - list - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: k8s-app: metrics-server name: metrics-server-auth-reader namespace: kube-system roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: extension-apiserver-authentication-reader subjects: - kind: ServiceAccount name: metrics-server namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: k8s-app: metrics-server name: metrics-server:system:auth-delegator roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:auth-delegator subjects: - kind: ServiceAccount name: metrics-server namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: k8s-app: metrics-server name: system:metrics-server roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:metrics-server subjects: - kind: ServiceAccount name: metrics-server namespace: kube-system --- apiVersion: v1 kind: Service metadata: labels: k8s-app: metrics-server name: metrics-server namespace: kube-system spec: ports: - name: https port: 443 protocol: TCP targetPort: https selector: k8s-app: metrics-server --- apiVersion: apps/v1 kind: Deployment metadata: labels: k8s-app: metrics-server name: metrics-server namespace: kube-system spec: selector: matchLabels: k8s-app: metrics-server strategy: rollingUpdate: maxUnavailable: 0 template: metadata: labels: k8s-app: metrics-server spec: containers: - args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --kubelet-use-node-status-port - --kubelet-insecure-tls image: k8s.gcr.io/metrics-server/metrics-server:v0.4.4 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 3 httpGet: path: /livez port: https scheme: HTTPS periodSeconds: 10 name: metrics-server ports: - containerPort: 4443 name: https protocol: TCP readinessProbe: failureThreshold: 3 httpGet: path: /readyz port: https scheme: HTTPS periodSeconds: 10 securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 volumeMounts: - mountPath: /tmp name: tmp-dir nodeSelector: kubernetes.io/os: linux priorityClassName: system-cluster-critical serviceAccountName: metrics-server volumes: - emptyDir: {} name: tmp-dir --- apiVersion: apiregistration.k8s.io/v1 kind: APIService metadata: labels: k8s-app: metrics-server name: v1beta1.metrics.k8s.io spec: group: metrics.k8s.io groupPriorityMinimum: 100 insecureSkipTLSVerify: true service: name: metrics-server namespace: kube-system version: v1beta1 versionPriority: 100 EOF 检查 pod 启动情况\nkubectl get pods -n kube-system -l k8s-app=metrics-server NAME READY STATUS RESTARTS AGE metrics-server-75b868857d-bpbb6 1/1 Running 0 75s kubectl top nodes NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% node1 422m 5% 1865Mi 12% node2 241m 3% 1598Mi 10% node3 317m 4% 1737Mi 11% node4 236m 3% 1123Mi 7% 显示有指标即正常\n配置 targets 自动发现说明 # 可以看到在上面发现的列表中，并没有 etcd 、 controller-manager 、 scheduler 、 apserver、coredns 这几个指标的出现，而且中间不妨出现了一些无法发现指标的配置项。下面介绍如何对这些缺失指标的修复，与无用指标的删除配置说明。\n清理无用指标 # 清理 istio 对应 envoy 服务发现，这个是 rancher monitor chart 中默认渲染的，这里我们在渲染时，关闭掉渲染即可。\n系统层面监控 添加配置\n添加应答配置\nprometheus.istioMonitoring.enabled=false exporter-fluentd.enabled=false 保存并应该配置，等待生效后，已找不到 与 istio 有关的配置项。\n添加 targets 指标发现 # 下面介绍的几种 targets 指标发现，使用与 集群 内外的 metrics，参照下面的配置说明，添加配置即可。\n添加 etcd 指标发现 # 添加 etcd service 资源对象\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: etcd-k8s namespace: kube-system labels: k8s-app: etcd spec: type: ClusterIP clusterIP: None ports: - name: port port: 2381 --- apiVersion: v1 kind: Endpoints metadata: name: etcd-k8s namespace: kube-system labels: k8s-app: etcd subsets: - addresses: - ip: 192.168.8.30 # 指定etcd节点地址，集群节点继续向下添加 nodeName: node1 ports: - name: port port: 2381 - addresses: - ip: 192.168.8.31 nodeName: node2 ports: - name: port port: 2381 - addresses: - ip: 192.168.8.32 nodeName: node3 ports: - name: port port: 2381 EOF 创建 etcd ServiceMonitor 资源对象\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: etcd-k8s namespace: cattle-prometheus labels: k8s-app: etcd-k8s spec: jobLabel: k8s-app endpoints: - port: port interval: 15s selector: matchLabels: k8s-app: etcd namespaceSelector: matchNames: - kube-system EOF 等待 operator 自动将配置加载完成，可以看到这里已经有对应的配置项了，但是好像无法抓取到 metrics 指标。这是因为默认 etcd 的 metrics 监听在 127.0.0.1 的地址上，我们拿的是 192.168.8.0/24 的地址去访问就自然无法访问到了。\n更改 etcd metrics 指标监听地址\n这里示例使用的是 kubekey 部署的集群，etcd 不是使用的 kubeadm 中静态pod进行管理的，更改配置文件为 /etc/etcd.env , 如果你是使用的 kubeadm 只需要修改静态pod配置文件 /etc/kubernetes/manifests/etcd.yaml 中的 listen-metrics-urls 配置项为 --listen-metrics-urls=http://0.0.0.0:2381 即可。\necho \u0026#39;# add coustom settings ETCD_LISTEN_METRICS_URLS=http://0.0.0.0:2381\u0026#39; \u0026gt;\u0026gt; /etc/etcd.env # 添加配置 service etcd restart # 重启服务生效 其他 etcd 环境变量说明， 请参考如下 文档\n再次刷新查看，指标已正常显示抓取。\n添加 controller-manager 指标抓取 # 添加 controller-manager service 资源对象\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: namespace: kube-system name: kube-controller-manager labels: k8s-app: kube-controller-manager spec: selector: component: kube-controller-manager # 这里标签，一定需要后后面的 pod 进行对应上 ports: - name: http-metrics port: 10252 targetPort: 10252 EOF 创建 controller-manager ServiceMonitor 资源对象\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: k8s-app: kube-controller-manager name: kube-controller-manager namespace: cattle-prometheus spec: endpoints: - interval: 30s metricRelabelings: - action: drop regex: kubelet_(pod_worker_latency_microseconds|pod_start_latency_microseconds|cgroup_manager_latency_microseconds|pod_worker_start_latency_microseconds|pleg_relist_latency_microseconds|pleg_relist_interval_microseconds|runtime_operations|runtime_operations_latency_microseconds|runtime_operations_errors|eviction_stats_age_microseconds|device_plugin_registration_count|device_plugin_alloc_latency_microseconds|network_plugin_operations_latency_microseconds) sourceLabels: - __name__ - action: drop regex: scheduler_(e2e_scheduling_latency_microseconds|scheduling_algorithm_predicate_evaluation|scheduling_algorithm_priority_evaluation|scheduling_algorithm_preemption_evaluation|scheduling_algorithm_latency_microseconds|binding_latency_microseconds|scheduling_latency_seconds) sourceLabels: - __name__ - action: drop regex: apiserver_(request_count|request_latencies|request_latencies_summary|dropped_requests|storage_data_key_generation_latencies_microseconds|storage_transformation_failures_total|storage_transformation_latencies_microseconds|proxy_tunnel_sync_latency_secs) sourceLabels: - __name__ - action: drop regex: kubelet_docker_(operations|operations_latency_microseconds|operations_errors|operations_timeout) sourceLabels: - __name__ - action: drop regex: reflector_(items_per_list|items_per_watch|list_duration_seconds|lists_total|short_watches_total|watch_duration_seconds|watches_total) sourceLabels: - __name__ - action: drop regex: etcd_(helper_cache_hit_count|helper_cache_miss_count|helper_cache_entry_count|request_cache_get_latencies_summary|request_cache_add_latencies_summary|request_latencies_summary) sourceLabels: - __name__ - action: drop regex: transformation_(transformation_latencies_microseconds|failures_total) sourceLabels: - __name__ - action: drop regex: (admission_quota_controller_adds|crd_autoregistration_controller_work_duration|APIServiceOpenAPIAggregationControllerQueue1_adds|AvailableConditionController_retries|crd_openapi_controller_unfinished_work_seconds|APIServiceRegistrationController_retries|admission_quota_controller_longest_running_processor_microseconds|crdEstablishing_longest_running_processor_microseconds|crdEstablishing_unfinished_work_seconds|crd_openapi_controller_adds|crd_autoregistration_controller_retries|crd_finalizer_queue_latency|AvailableConditionController_work_duration|non_structural_schema_condition_controller_depth|crd_autoregistration_controller_unfinished_work_seconds|AvailableConditionController_adds|DiscoveryController_longest_running_processor_microseconds|autoregister_queue_latency|crd_autoregistration_controller_adds|non_structural_schema_condition_controller_work_duration|APIServiceRegistrationController_adds|crd_finalizer_work_duration|crd_naming_condition_controller_unfinished_work_seconds|crd_openapi_controller_longest_running_processor_microseconds|DiscoveryController_adds|crd_autoregistration_controller_longest_running_processor_microseconds|autoregister_unfinished_work_seconds|crd_naming_condition_controller_queue_latency|crd_naming_condition_controller_retries|non_structural_schema_condition_controller_queue_latency|crd_naming_condition_controller_depth|AvailableConditionController_longest_running_processor_microseconds|crdEstablishing_depth|crd_finalizer_longest_running_processor_microseconds|crd_naming_condition_controller_adds|APIServiceOpenAPIAggregationControllerQueue1_longest_running_processor_microseconds|DiscoveryController_queue_latency|DiscoveryController_unfinished_work_seconds|crd_openapi_controller_depth|APIServiceOpenAPIAggregationControllerQueue1_queue_latency|APIServiceOpenAPIAggregationControllerQueue1_unfinished_work_seconds|DiscoveryController_work_duration|autoregister_adds|crd_autoregistration_controller_queue_latency|crd_finalizer_retries|AvailableConditionController_unfinished_work_seconds|autoregister_longest_running_processor_microseconds|non_structural_schema_condition_controller_unfinished_work_seconds|APIServiceOpenAPIAggregationControllerQueue1_depth|AvailableConditionController_depth|DiscoveryController_retries|admission_quota_controller_depth|crdEstablishing_adds|APIServiceOpenAPIAggregationControllerQueue1_retries|crdEstablishing_queue_latency|non_structural_schema_condition_controller_longest_running_processor_microseconds|autoregister_work_duration|crd_openapi_controller_retries|APIServiceRegistrationController_work_duration|crdEstablishing_work_duration|crd_finalizer_adds|crd_finalizer_depth|crd_openapi_controller_queue_latency|APIServiceOpenAPIAggregationControllerQueue1_work_duration|APIServiceRegistrationController_queue_latency|crd_autoregistration_controller_depth|AvailableConditionController_queue_latency|admission_quota_controller_queue_latency|crd_naming_condition_controller_work_duration|crd_openapi_controller_work_duration|DiscoveryController_depth|crd_naming_condition_controller_longest_running_processor_microseconds|APIServiceRegistrationController_depth|APIServiceRegistrationController_longest_running_processor_microseconds|crd_finalizer_unfinished_work_seconds|crdEstablishing_retries|admission_quota_controller_unfinished_work_seconds|non_structural_schema_condition_controller_adds|APIServiceRegistrationController_unfinished_work_seconds|admission_quota_controller_work_duration|autoregister_depth|autoregister_retries|kubeproxy_sync_proxy_rules_latency_microseconds|rest_client_request_latency_seconds|non_structural_schema_condition_controller_retries) sourceLabels: - __name__ - action: drop regex: etcd_(debugging|disk|request|server).* sourceLabels: - __name__ port: http-metrics jobLabel: k8s-app namespaceSelector: matchNames: - kube-system selector: matchLabels: k8s-app: kube-controller-manager # 这里需要与 service 的标签进行对应 EOF 添加 scheduler 指标抓取 # 添加 scheduler service 资源对象\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: namespace: kube-system name: kube-scheduler labels: k8s-app: kube-scheduler spec: selector: component: kube-scheduler # 同上指标需要与 pod 标签进行对应 ports: - name: http-metrics port: 10251 targetPort: 10251 EOF 创建 scheduler ServiceMonitor 资源对象\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: k8s-app: kube-scheduler name: kube-scheduler namespace: cattle-prometheus spec: endpoints: - interval: 30s port: http-metrics jobLabel: k8s-app namespaceSelector: matchNames: - kube-system selector: matchLabels: k8s-app: kube-scheduler EOF 添加 coredns 指标抓取 # 默认 coredns 的 service 资源对象已被创建，只需要创建 ServiceMonitor 对象即可\n创建 ServiceMonitor 资源对象\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: k8s-app: coredns name: coredns namespace: cattle-prometheus spec: endpoints: - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token interval: 15s port: metrics jobLabel: k8s-app namespaceSelector: matchNames: - kube-system selector: matchLabels: k8s-app: kube-dns EOF 添加 apiserver 指标抓取 # 检查 apiserver service 资源对象\n创建 apiserver ServiceMonitor 资源对象\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: k8s-app: apiserver name: kube-apiserver namespace: cattle-prometheus spec: endpoints: - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token interval: 30s metricRelabelings: - action: drop regex: kubelet_(pod_worker_latency_microseconds|pod_start_latency_microseconds|cgroup_manager_latency_microseconds|pod_worker_start_latency_microseconds|pleg_relist_latency_microseconds|pleg_relist_interval_microseconds|runtime_operations|runtime_operations_latency_microseconds|runtime_operations_errors|eviction_stats_age_microseconds|device_plugin_registration_count|device_plugin_alloc_latency_microseconds|network_plugin_operations_latency_microseconds) sourceLabels: - __name__ - action: drop regex: scheduler_(e2e_scheduling_latency_microseconds|scheduling_algorithm_predicate_evaluation|scheduling_algorithm_priority_evaluation|scheduling_algorithm_preemption_evaluation|scheduling_algorithm_latency_microseconds|binding_latency_microseconds|scheduling_latency_seconds) sourceLabels: - __name__ - action: drop regex: apiserver_(request_count|request_latencies|request_latencies_summary|dropped_requests|storage_data_key_generation_latencies_microseconds|storage_transformation_failures_total|storage_transformation_latencies_microseconds|proxy_tunnel_sync_latency_secs) sourceLabels: - __name__ - action: drop regex: kubelet_docker_(operations|operations_latency_microseconds|operations_errors|operations_timeout) sourceLabels: - __name__ - action: drop regex: reflector_(items_per_list|items_per_watch|list_duration_seconds|lists_total|short_watches_total|watch_duration_seconds|watches_total) sourceLabels: - __name__ - action: drop regex: etcd_(helper_cache_hit_count|helper_cache_miss_count|helper_cache_entry_count|request_cache_get_latencies_summary|request_cache_add_latencies_summary|request_latencies_summary) sourceLabels: - __name__ - action: drop regex: transformation_(transformation_latencies_microseconds|failures_total) sourceLabels: - __name__ - action: drop regex: (admission_quota_controller_adds|crd_autoregistration_controller_work_duration|APIServiceOpenAPIAggregationControllerQueue1_adds|AvailableConditionController_retries|crd_openapi_controller_unfinished_work_seconds|APIServiceRegistrationController_retries|admission_quota_controller_longest_running_processor_microseconds|crdEstablishing_longest_running_processor_microseconds|crdEstablishing_unfinished_work_seconds|crd_openapi_controller_adds|crd_autoregistration_controller_retries|crd_finalizer_queue_latency|AvailableConditionController_work_duration|non_structural_schema_condition_controller_depth|crd_autoregistration_controller_unfinished_work_seconds|AvailableConditionController_adds|DiscoveryController_longest_running_processor_microseconds|autoregister_queue_latency|crd_autoregistration_controller_adds|non_structural_schema_condition_controller_work_duration|APIServiceRegistrationController_adds|crd_finalizer_work_duration|crd_naming_condition_controller_unfinished_work_seconds|crd_openapi_controller_longest_running_processor_microseconds|DiscoveryController_adds|crd_autoregistration_controller_longest_running_processor_microseconds|autoregister_unfinished_work_seconds|crd_naming_condition_controller_queue_latency|crd_naming_condition_controller_retries|non_structural_schema_condition_controller_queue_latency|crd_naming_condition_controller_depth|AvailableConditionController_longest_running_processor_microseconds|crdEstablishing_depth|crd_finalizer_longest_running_processor_microseconds|crd_naming_condition_controller_adds|APIServiceOpenAPIAggregationControllerQueue1_longest_running_processor_microseconds|DiscoveryController_queue_latency|DiscoveryController_unfinished_work_seconds|crd_openapi_controller_depth|APIServiceOpenAPIAggregationControllerQueue1_queue_latency|APIServiceOpenAPIAggregationControllerQueue1_unfinished_work_seconds|DiscoveryController_work_duration|autoregister_adds|crd_autoregistration_controller_queue_latency|crd_finalizer_retries|AvailableConditionController_unfinished_work_seconds|autoregister_longest_running_processor_microseconds|non_structural_schema_condition_controller_unfinished_work_seconds|APIServiceOpenAPIAggregationControllerQueue1_depth|AvailableConditionController_depth|DiscoveryController_retries|admission_quota_controller_depth|crdEstablishing_adds|APIServiceOpenAPIAggregationControllerQueue1_retries|crdEstablishing_queue_latency|non_structural_schema_condition_controller_longest_running_processor_microseconds|autoregister_work_duration|crd_openapi_controller_retries|APIServiceRegistrationController_work_duration|crdEstablishing_work_duration|crd_finalizer_adds|crd_finalizer_depth|crd_openapi_controller_queue_latency|APIServiceOpenAPIAggregationControllerQueue1_work_duration|APIServiceRegistrationController_queue_latency|crd_autoregistration_controller_depth|AvailableConditionController_queue_latency|admission_quota_controller_queue_latency|crd_naming_condition_controller_work_duration|crd_openapi_controller_work_duration|DiscoveryController_depth|crd_naming_condition_controller_longest_running_processor_microseconds|APIServiceRegistrationController_depth|APIServiceRegistrationController_longest_running_processor_microseconds|crd_finalizer_unfinished_work_seconds|crdEstablishing_retries|admission_quota_controller_unfinished_work_seconds|non_structural_schema_condition_controller_adds|APIServiceRegistrationController_unfinished_work_seconds|admission_quota_controller_work_duration|autoregister_depth|autoregister_retries|kubeproxy_sync_proxy_rules_latency_microseconds|rest_client_request_latency_seconds|non_structural_schema_condition_controller_retries) sourceLabels: - __name__ - action: drop regex: etcd_(debugging|disk|request|server).* sourceLabels: - __name__ - action: drop regex: apiserver_admission_controller_admission_latencies_seconds_.* sourceLabels: - __name__ - action: drop regex: apiserver_admission_step_admission_latencies_seconds_.* sourceLabels: - __name__ - action: drop regex: apiserver_request_duration_seconds_bucket;(0.15|0.25|0.3|0.35|0.4|0.45|0.6|0.7|0.8|0.9|1.25|1.5|1.75|2.5|3|3.5|4.5|6|7|8|9|15|25|30|50) sourceLabels: - __name__ - le port: https scheme: https tlsConfig: caFile: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt serverName: kubernetes jobLabel: component namespaceSelector: matchNames: - default selector: matchLabels: component: apiserver # 与上面查询到的 labels 进行匹配 provider: kubernetes EOF 总结 # 基础的指标抓取 和 基础优化就完成了，如后续需要添加指标，参照上面的添加 抓取指标步骤照葫芦画瓢 ，创建 ServiceMonitor 资源对象即可，当然还有另外一种添加配置的方法，后面与 exporter 集成使用时会做使用说明。\n","date":"2021年5月17日","externalUrl":null,"permalink":"/posts/rancher-monitor-config-first/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003ekubernetes version: \u003ccode\u003ev1.17.9\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003erancher dashboard: \u003ccode\u003ev2.3.5\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e操作系统:  \u003ccode\u003ecentos 7.8.2003 \u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e基础环境配置 \n    \u003cdiv id=\"基础环境配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%9f%ba%e7%a1%80%e7%8e%af%e5%a2%83%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e使用 kubekey 部署 kubernetes 集群 \n    \u003cdiv id=\"使用-kubekey-部署-kubernetes-集群\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8-kubekey-%e9%83%a8%e7%bd%b2-kubernetes-%e9%9b%86%e7%be%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e由于篇幅原因，此处省略部署说明，请参考较早期的 \u003ca\n  href=\"http://hugo.blog:1313/post/kubekey-install-k8s/\"\n    target=\"_blank\"\n  \u003e文档说明\u003c/a\u003e\u003c/p\u003e","title":"Rancher 开启监控，及生产应用的优化配置工作说明 (一)","type":"posts"},{"content":" 环境说明 # 上期 介绍了 rancher prometheus operator 的安装和基础 targets 的修复工作。有的时候我们抓取的 exporter metrics 并不在同集群且与集群没有任何关联时，应该怎么和 rancher monitor 进行关联配置呢？下面文档将配置展示部署外部 exporter , 的安装说明、指标抓取、 和监控系统的关联。\nexporter 列表 # 此 exporter列表使用文档为不同时期 整理归纳，阅读过程中存在一定的 时间差异。被官方文档收录收集的完整 exporter 列表 查看。\nscripts exporter # github 仓库地址\n安装配置 # 为保证 exporter 灵活性，部署未使用 容器化 部署\n下载软件包\nwget https://github.com/adhocteam/script_exporter/releases/download/v1.1.0/script_exporter-1.1.0.linux-amd64.tar.gz tar xf script_exporter-1.1.0.linux-amd64.tar.gz mkdir -p /application/script_exporter/{conf,bin} cp script_exporter-1.1.0.linux-amd64/script_exporter /application/script_exporter/bin/ 创建相关配置文件并测试启动\ncat \u0026gt; /application/script_exporter/conf/script-exporter.yml \u0026lt;\u0026lt; EOF scripts: # 此配置文件 创建一个whoami的脚本 当程序不是root用户执行时抛出异常 - name: \u0026#39;whoami\u0026#39; script: if [ \\`whoami\\` != \u0026#39;root\u0026#39; ];then exit 1 ;fi EOF # 使用 root 用户启动测试程序 /application/script_exporter/bin/script_exporter -config.file /application/script_exporter/conf/script-exporter.yml # 测试启动 curl http://localhost:9172/probe?pattern=.* # 测试触发脚本的 metrics 指标暴露 script_duration_seconds{script=\u0026#34;whoami\u0026#34;} 0.002529 script_success{script=\u0026#34;whoami\u0026#34;} 1 # 这里由于我使用的是 root 启动的 指标显示为 1,下面我们尝试更为 非 root 用户启动看看 useradd exporter # 添加测试用户 exporter su - exporter # 切换为 exporter 用户启动程序 # 使用 exporter 用户启动测试程序 /application/script_exporter/bin/script_exporter -config.file /application/script_exporter/conf/script-exporter.yml curl http://localhost:9172/probe?pattern=.* # 再次触发脚本执行 script_duration_seconds{script=\u0026#34;whoami\u0026#34;} 0.004814 script_success{script=\u0026#34;whoami\u0026#34;} 0 # 可以放回为 非 0，即错误验证码 从上面测试的结果我们可以得出结论，那就是 执行脚本返回了非0状态码 metrics 对象 指标项就会是 0, 那么我们更具此条规律添加脚本即可。下面示例展示一个生产环境使用的 配置文件。\ncat script-exporter.yml scripts: - name: \u0026#39;1.44-raid-check\u0026#39; script: if ! ssh 192.168.1.44 \u0026#34;if [ \\`MegaCli -PDList -aAll -NoLog | grep \u0026#39;Firmware state\u0026#39;|wc -l \\` -ne \\`MegaCli -PDList -aAll -NoLog | egrep \u0026#39;Online,|Hotspare,\u0026#39;|wc -l\\` ] ;then exit 1 ;fi\u0026#34;;then exit 1 ;fi 此脚本，使用 ssh 远程至目标机器 192.168.1.44，检查磁盘整列raid是否有掉盘情况，远程使用 ssh-keygen \u0026amp; ssh-copy-id 做了 免密钥 处理，所有不需要输入密码。\n配置为 systemctl 服务，并设置开启自启动 # 注意使用 root 启动进程的话，存在一定的安全隐患，那么可以使用 特定的用户进行启动，来规避安全风险。\ngroupadd -r exporter # useradd -r -g exporter -s /sbin/nologin -M exporter # 此操作，设置 exporter 无法使用终端，为保证配置灵活性不建议执行。 chown exporter:exporter -R /application/script_exporter/ # 将文件对应赋予给此用户 cat \u0026gt; /usr/lib/systemd/system/script-exporter.service \u0026lt;\u0026lt; EOF [Unit] Description=Script_Exporter Documentation=https://github.com/adhocteam/script_exporter After=network.target [Service] Type=simple User=root ExecStart=/application/script_exporter/bin/script_exporter -config.file /application/script_exporter/conf/script-exporter.yml -web.listen-address=:9172 -web.telemetry-path=/metrics -config.shell=/bin/sh Restart=on-failure [Install] WantedBy=multi-user.target EOF systemctl start script-exporter.service \\ \u0026amp;\u0026amp; systemctl enable script-exporter.service \\ \u0026amp;\u0026amp; systemctl status script-exporter.service 关联 prometheus 指标抓取 # 此示例使用普通的 prometheus，配置文件进行关联\n- job_name: \u0026#39;script_exporter\u0026#39; scrape_interval: 15s metrics_path: /probe params: pattern: [\u0026#39;.\u0026#39;] static_configs: - targets: [\u0026#39;exporter_ip:exporter_port\u0026#39;\u0026#39;] labels: demo instance: \u0026#39;exporter_ip:exporter_port\u0026#39; relabel_configs: - target_label: script replacement: service 配置集成 rancher 中的prometheus operator # 更改对应的 secrets 资源对象即可，secrets 资源对象中对配置文件做了一层 base64 加密\nkubectl get secrets prometheus-cluster-monitoring-additional-scrape-configs -n cattle-prometheus -o yaml| awk -F \u0026#39;:\u0026#39; \u0026#39;NR==3{print $2}\u0026#39;|tr -d \u0026#39; \u0026#39;|base64 -d 更改 secrets 中配置\nkubectl get secrets prometheus-cluster-monitoring-additional-scrape-configs -n cattle-prometheus -o yaml| awk -F \u0026#39;:\u0026#39; \u0026#39;NR==3{print $2}\u0026#39;|tr -d \u0026#39; \u0026#39;|base64 -d \u0026gt; prometheus-cluster-monitoring-additional-scrape-configs.yaml # 重定向至 文件中方便更改 vim prometheus-cluster-monitoring-additional-scrape-configs.yaml ... - job_name: \u0026#39;scripts-demo\u0026#39; metrics_path: /probe params: pattern: [\u0026#39;.*\u0026#39;] # 执行所有脚本，也可以使用正则表达式进行匹配 如 \u0026#34;.*raid-check\u0026#34; static_configs: - targets: - 192.168.8.88:9172 # 指定为对应 exporter 监听地址，更改后进行保存 cat prometheus-cluster-monitoring-additional-scrape-configs.yaml |base64 |tr -d \u0026#39;\\n\u0026#39; kubectl edit secrets prometheus-cluster-monitoring-additional-scrape-configs -n cattle-prometheus # 将上面的结果输出进行替换 kubectl get secrets prometheus-cluster-monitoring-additional-scrape-configs -n cattle-prometheus -o yaml| awk -F \u0026#39;:\u0026#39; \u0026#39;NR==3{print $2}\u0026#39;|tr -d \u0026#39; \u0026#39;|base64 -d | tail -n 10 # 检查是否生效 稍等片刻等待配置文件生效\n从上述结果展示中，可以看到正常关联上了。\nphp-fpm_exporter # 此处省略 lnmp 环境的安装，网上提供很多 一键安装 面板，如需实现自行安装配置即可。\ngithub 地址\n配置添加 php 参数 # egrep \u0026#39;/ping|/status\u0026#39; /usr/local/php/etc/php-fpm.d/walle.conf pm.status_path = /status ping.path = /ping 添加配置 关联 nginx # [root@hadoopname ~]# cat /usr/local/nginx/conf/conf.d/ cobra.conf jumpserver.conf official.conf php_status.conf walle.conf zabbix.conf [root@hadoopname ~]# cat /usr/local/nginx/conf/conf.d/php_status.conf server { listen 9010; allow 127.0.0.1; allow 192.168.8.0/24; deny all; location ~ ^/(status|ping)$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } nohup php-fpm-exporter --addr 0.0.0.0:9190 --endpoint http://127.0.0.1:9010/status \u0026gt; /tmp/php-fpm-exporter.log 2\u0026gt;\u0026amp;1 \u0026amp; sudo firewall-cmd --zone=public --add-port=9190/tcp --permanent firewall-cmd --reload # 测试启动 配置关联为 systemd 服务，并配置自启 # cat \u0026gt; /usr/lib/systemd/system/php-fpm-exporter.service \u0026lt;\u0026lt;EOF [Unit] Description=php-fpm-exporter Documentation=https://github.com/hipages/php-fpm_exporter After=network.target [Service] Type=simple User=root ExecStart=/usr/local/bin/php-fpm-exporter --addr 0.0.0.0:9190 --endpoint http://127.0.0.1:9010/status Restart=on-failure [Install] WantedBy=multi-user.target EOF systemctl daemon-reload \\ \u0026amp;\u0026amp; systemctl start php-fpm-exporter \\ \u0026amp;\u0026amp; systemctl status php-fpm-exporter systemctl enable php-fpm-exporter # 正常启动后，配置开机自启动 win_exporter # github地址\nmsiexec /i wmi_exporter-0.7.0-amd64.msi ENABLED_COLLECTORS=cpu,cs,logical_disk,net,os,service,system,textfile,memory,tcp LISTEN_PORT=9010 # 下载软件包后，使用可执行程序注册为服务 blackbox_exporter # 使用 helm 进行安装 # 添加配置 chart 应用仓库\nhelm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update 初始化 values.yaml 部署文件\nhelm show values prometheus-community/prometheus-blackbox-exporter \u0026gt; values.yaml # 将默认配置重定向至文件 使用 修改精简化处理后的 values.yaml 部署\ncat \u0026gt; values.yaml \u0026lt;\u0026lt; EOF config: modules: http_2xx: prober: http timeout: 5s http: basic_auth: username: \u0026#34;admin\u0026#34; # 配置账号密码 防止 http 探针出现 \u0026#34;401\u0026#34; 状态码，导致不通过 password: \u0026#34;admin\u0026#34; http_post_2xx: prober: http timeout: 5s http: method: POST basic_auth: username: \u0026#34;admin\u0026#34; password: \u0026#34;admin\u0026#34; tcp_connect: prober: tcp timeout: 5s pop3s_banner: prober: tcp tcp: query_response: - expect: \u0026#34;^+OK\u0026#34; tls: true tls_config: insecure_skip_verify: false ssh_banner: prober: tcp timeout: 5s tcp: query_response: - expect: \u0026#34;^SSH-2.0-\u0026#34; smtp_starttls: prober: tcp timeout: 5s tcp: query_response: - expect: \u0026#34;^220 \u0026#34; - send: \u0026#34;EHLO prober\u0026#34; - expect: \u0026#34;^250-STARTTLS\u0026#34; - send: \u0026#34;STARTTLS\u0026#34; - expect: \u0026#34;^220\u0026#34; - starttls: true - send: \u0026#34;EHLO prober\u0026#34; - expect: \u0026#34;^250-AUTH\u0026#34; - send: \u0026#34;QUIT\u0026#34; irc_banner: prober: tcp timeout: 5s tcp: query_response: - send: \u0026#34;NICK prober\u0026#34; - send: \u0026#34;USER prober prober prober :prober\u0026#34; - expect: \u0026#34;PING :([^ ]+)\u0026#34; send: \u0026#34;PONG \u0026#34; - expect: \u0026#34;^:[^ ]+ 001\u0026#34; icmp_test: prober: icmp timeout: 5s icmp: preferred_ip_protocol: ip4 dns_test: prober: dns timeout: 5s dns: query_name: \u0026#34;kubernetes.default.svc.cluster.local\u0026#34; preferred_ip_protocol: ip4 ip_protocol_fallback: false validate_answer_rrs: fail_if_matches_regexp: [test] http_header_match_origin: prober: http timeout: 5s http: method: GET headers: Origin: example.com fail_if_header_not_matches: - header: Access-Control-Allow-Origin regexp: \u0026#39;(\\*|example\\.com)\u0026#39; allow_missing: false allowIcmp: true # 允许使用使用 icmp 协议，默认为 未打开状态 EOF kubectl create ns prometheus helm upgrade --install blackbox -f ./values.yaml prometheus-community/prometheus-blackbox-exporter -n prometheus 安装后与 prometheus operator 的集成 # 同样与上面 scripts exporter 关联步骤类似，更改 secrets 资源对象\n重定向至文件中方便进行更改\nkubectl get secrets prometheus-cluster-monitoring-additional-scrape-configs -n cattle-prometheus -o yaml| awk -F \u0026#39;:\u0026#39; \u0026#39;NR==3{print $2}\u0026#39;|tr -d \u0026#39; \u0026#39;|base64 -d \u0026gt; prometheus-cluster-monitoring-additional-scrape-configs.yaml 在重定向生成的文件中追加配置\ncat prometheus-cluster-monitoring-additional-scrape-configs.yaml .... - job_name: \u0026#39;http-blackbox\u0026#39; metrics_path: /probe params: module: [http_2xx] #使用 http 模块 static_configs: - targets: - 192.168.8.1 labels: # 配置标签 group: http net: local - targets: - www.baidu.com labels: group: http net: public relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: blackbox-prometheus-blackbox-exporter.prometheus:9115 # 使用 k8s 内部域名进行通讯 - job_name: \u0026#39;icmp-ping\u0026#39; metrics_path: /probe params: module: [icmp_test] static_configs: - targets: - 192.168.8.1 labels: dc: \u0026#39;ancun-local\u0026#39; group: \u0026#39;icmp\u0026#39; instance: \u0026#39;icmp-status\u0026#39; relabel_configs: - source_labels: [__address__] regex: (.*)(:80)? target_label: __param_target replacement: ${1} - source_labels: [__param_target] target_label: instance - source_labels: [__param_target] regex: (.*) target_label: ping replacement: ${1} - source_labels: [] regex: .* target_label: __address__ replacement: blackbox-prometheus-blackbox-exporter.prometheus:9115 - job_name: \u0026#39;tcp-port-status\u0026#39; metrics_path: /probe params: module: [tcp_connect] static_configs: - targets: - 192.168.8.1:80 - 192.168.8.1:9000 labels: group: tcp net: \u0026#39;local\u0026#39; type: nginx - targets: - 192.168.1.31:32379 labels: group: tcp net: \u0026#39;local\u0026#39; type: \u0026#39;redis\u0026#39; - targets: - 192.168.1.50:3306 - 192.168.1.51:3306 - 192.168.1.33:3306 - 192.168.1.232:3306 labels: group: tcp net: \u0026#39;local\u0026#39; type: \u0026#39;mysql\u0026#39; relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__address__] regex: (.*):(.*) target_label: host_ip replacement: $1 - source_labels: [__address__] regex: (.*):(.*) target_label: host_port replacement: $2 - target_label: __address__ replacement: blackbox-prometheus-blackbox-exporter.prometheus:9115 cat prometheus-cluster-monitoring-additional-scrape-configs.yaml |base64 |tr -d \u0026#39;\\n\u0026#39; # 对配置文件进行 base 转码 进行 secrets 资源对象的更改\ncat prometheus-cluster-monitoring-additional-scrape-configs.yaml |base64 |tr -d \u0026#39;\\n\u0026#39; # 对配置文件进行 base 转码 检查 prometheus operator 配置是否有更新 # 可以看到所添加的配置项已 在 dashboard 页面有所展示了\n我们在 查询页面输入 probe_success 看看探针执行情况\n可以看到可以正常看到探针检查情况，标注的两处探针检查未通过说明: 1 位置，检查未通过是因为 blackbox 配置的 basic_auth 认证密钥不匹配导致，2 位置为 无法访问的局域网，属于正常情况。\ntraefik metrcis 关联 # 省略 traefik 的安装部署操作，详情请参考较 早期文档。需要确认配置已将 metrcis 指标已打开，下面演示使用 helm 部署的 traefik 为其添加暴露 metrics 指标操作。\nhelm 安装 traefik，并配置开启 metrcis # git clone https://github.com/traefik/traefik-helm-chart cd traefik-helm-chart cat \u0026gt; prod-values.yaml \u0026lt;\u0026lt; EOF ingressRoute: dashboard: enabled: false # 关闭渲染 dashboard，改用手动创建 # Configure ports ports: web: port: 8000 hostPort: 80 # 使用 hostport 模式 websecure: port: 8443 hostPort: 443 # 使用 hostport 模式 service: enabled: false # 改用 hostpath 模式后，service 渲染可取消 logs: general: level: ERROR # 设置日志级别，建议 tolerations: - key: \u0026#34;node-role.kubernetes.io/master\u0026#34; operator: \u0026#34;Equal\u0026#34; effect: \u0026#34;NoSchedule\u0026#34; # 容忍污点 nodeSelector: # 节点亲和，固定到标签 master01 节点上 kubernetes.io/hostname: \u0026#34;node1\u0026#34; additionalArguments: # 添加额外暴露 指标的参数 - --entryPoints.metrics.address=:8082 - --metrics.prometheus.entryPoint=metrics EOF kubectl create ns traefik helm upgrade --install traefik -n traefik -f ./prod-values.yaml ./traefik/ kubectl get po -n traefik -o wide|awk \u0026#39;NR==2{print $6}\u0026#39;|xargs -t -I{} curl http://{}:8082/metrics # 测试是否有指标暴露 创建 serviceMinitor 资源配置关联 operator # cat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: traefik-metrics namespace: traefik labels: k8s-app: traefik spec: type: ClusterIP ports: - name: port port: 8082 selector: app.kubernetes.io/name: traefik --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: traefik-k8s namespace: cattle-prometheus labels: k8s-app: traefik spec: jobLabel: k8s-app endpoints: - port: port interval: 15s selector: matchLabels: k8s-app: traefik namespaceSelector: matchNames: - traefik EOF 配置 grafana dashboard # 进入system 项目下，点击 服务发现, 点击访问 grafana 的 nodePort 地址\n点击导入 ID为 11462 的 traefik dashboard，官方社区提供的 dashboard 搜索地址\n如提示插件未安装情况，进入对应容器使用下面命令进行安装即可。\ngrafana-cli plugins install xxxx 总结 # 此篇文档主要介绍了外部 exporter 与 rancher 中 monitor 的配置关联工作的另外一种方法，那就是直接更改 secret 资源对象。对比使用 前面 的使用创建的serviceMonitor 资源对象，此方法相较于没有那么优雅，为保证 prometheus operator 使用理念，如果不是像 scripts exporter \u0026amp; blackbox exporter 这种配置比较繁琐麻烦的使用 serviceMonitor关联难管理的 ，还是 偏向建议 于使用 serviceMonitor 资源对象进行配置关联。\n","date":"2021年5月17日","externalUrl":null,"permalink":"/posts/rancher-monitor-config-second/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca\n  href=\"https://www.treesir.pub/post/rancher-monitor-config-first/\"\n    target=\"_blank\"\n  \u003e\u003ccode\u003e上期\u003c/code\u003e\u003c/a\u003e 介绍了 \u003ccode\u003erancher prometheus operator\u003c/code\u003e 的安装和基础 targets 的修复工作。有的时候我们抓取的 exporter metrics 并不在同集群且与集群没有任何关联时，应该怎么和 rancher \u003ccode\u003emonitor\u003c/code\u003e 进行关联配置呢？下面文档将配置展示部署外部  \u003ccode\u003eexporter \u003c/code\u003e, 的安装说明、指标抓取、 和监控系统的关联。\u003c/p\u003e","title":"Rancher 开启监控后，exporter/metrics 的添加说明 (二)","type":"posts"},{"content":"","date":"2021年5月16日","externalUrl":null,"permalink":"/tags/backup/","section":"Tags","summary":"","title":"Backup","type":"tags"},{"content":"","date":"2021年5月16日","externalUrl":null,"permalink":"/tags/docker-compose/","section":"Tags","summary":"","title":"Docker-Compose","type":"tags"},{"content":"","date":"2021年5月16日","externalUrl":null,"permalink":"/tags/velero/","section":"Tags","summary":"","title":"Velero","type":"tags"},{"content":" 环境准备 # 安装基础依赖 # 安装 Docker Compose：\nyum install -y docker-compose 部署 MinIO 对象存储 # 创建工作目录并配置 MinIO：\nmkdir -p /data/docker-compose/minio \u0026amp;\u0026amp; cd /data/docker-compose/minio cat \u0026gt; docker-compose.yaml \u0026lt;\u0026lt; EOF version: \u0026#34;2.0\u0026#34; services: minio: image: minio/minio:RELEASE.2021-03-17T02-33-02Z # 使用稳定版本镜像 container_name: minio hostname: minio restart: always tty: true ports: - \u0026#34;9000:9000\u0026#34; volumes: - /application/minio/data:/data - /application/minio/config:/root/.minio environment: - \u0026#34;MINIO_ACCESS_KEY=admin\u0026#34; - \u0026#34;MINIO_SECRET_KEY=12345678\u0026#34; command: \u0026#34;server /data\u0026#34; EOF docker-compose up -d # 启动容器 docker-compose logs -f # 查看容器启动日志 更多环境变量配置请参考 MinIO 官方文档\n安装 Velero # 下载并安装 Velero 客户端 # mkdir -p /data/velero \u0026amp;\u0026amp; cd /data/velero # 创建 AWS 凭证文件 cat \u0026gt; credentials-velero \u0026lt;\u0026lt; EOF [default] aws_access_key_id = admin aws_secret_access_key = 12345678 EOF # 下载 Velero 二进制文件 wget https://github.com/vmware-tanzu/velero/releases/download/v1.6.0/velero-v1.6.0-linux-amd64.tar.gz # 解压并安装 tar xf velero-v1.6.0-linux-amd64.tar.gz cp -a velero-v1.6.0-linux-amd64/velero /usr/local/bin/ # 验证安装 velero version 预期输出：\nClient: Version: v1.6.0 Git commit: 5bd70fd8eef316d220317245e46dc6016c348dce \u0026lt;error getting server version: no matches for kind \u0026#34;ServerStatusRequest\u0026#34; in version \u0026#34;velero.io/v1\u0026#34;\u0026gt; 注意：此时显示服务端错误是正常的，因为还未在集群中安装 Velero 服务端组件。\n在 Kubernetes 集群中部署 Velero # 创建命名空间 # kubectl create ns velero 安装 Velero 服务端组件 # velero install \\ --image velero/velero:v1.2.0 \\ --provider aws \\ --bucket velero \\ --namespace velero \\ --secret-file ./credentials-velero \\ --velero-pod-cpu-request 200m \\ --velero-pod-mem-request 200Mi \\ --velero-pod-cpu-limit 1000m \\ --velero-pod-mem-limit 1000Mi \\ --use-volume-snapshots=false \\ --use-restic \\ --restic-pod-cpu-request 200m \\ --restic-pod-mem-request 200Mi \\ --restic-pod-cpu-limit 1000m \\ --restic-pod-mem-limit 1000Mi \\ --plugins velero/velero-plugin-for-aws:v1.2.0 \\ --backup-location-config region=minio,s3ForcePathStyle=\u0026#34;true\u0026#34;,s3Url=http://nps.treesir.pub:1181 参数说明：\n--image：指定 Velero 镜像版本 --provider：存储提供商（使用 AWS S3 兼容接口） --bucket：存储桶名称 --secret-file：AWS 凭证文件路径 --use-volume-snapshots=false：禁用卷快照功能 --use-restic：启用 Restic 进行文件系统备份 --backup-location-config：配置备份存储位置 基本使用 # 创建备份 # 创建集群备份，排除系统命名空间：\nvelero backup create all-tidy --exclude-namespaces kube-system,ingress-nginx,cattle-system,velero 查看备份状态 # velero backup get velero backup describe all-tidy 参考资料：使用 Velero 备份 Kubernetes 集群\n","date":"2021年5月16日","externalUrl":null,"permalink":"/posts/velero-install/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境准备 \n    \u003cdiv id=\"环境准备\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e5%87%86%e5%a4%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装基础依赖 \n    \u003cdiv id=\"安装基础依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85%e5%9f%ba%e7%a1%80%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e安装 Docker Compose：\u003c/p\u003e","title":"Velero 备份迁移工具安装与配置","type":"posts"},{"content":"","date":"2021年5月16日","externalUrl":null,"permalink":"/tags/maven/","section":"Tags","summary":"","title":"Maven","type":"tags"},{"content":" maven settings.xml 配置使用展示 # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;settings xmlns=\u0026#34;http://maven.apache.org/SETTINGS/1.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd\u0026#34;\u0026gt; \u0026lt;!-- localRepository | The path to the local repository maven will use to store artifacts. | | Default: ${user.home}/.m2/repository \u0026lt;localRepository\u0026gt;/path/to/local/repo\u0026lt;/localRepository\u0026gt; --\u0026gt; \u0026lt;pluginGroups\u0026gt; \u0026lt;!-- pluginGroup | Specifies a further group identifier to use for plugin lookup. \u0026lt;pluginGroup\u0026gt;com.your.plugins\u0026lt;/pluginGroup\u0026gt; --\u0026gt; \u0026lt;/pluginGroups\u0026gt; \u0026lt;!-- proxies | This is a list of proxies which can be used on this machine to connect to the network. | Unless otherwise specified (by system property or command-line switch), the first proxy | specification in this list marked as active will be used. |--\u0026gt; \u0026lt;proxies\u0026gt; \u0026lt;/proxies\u0026gt; \u0026lt;servers\u0026gt; \u0026lt;server\u0026gt; \u0026lt;id\u0026gt;user-snapshot\u0026lt;/id\u0026gt; \u0026lt;username\u0026gt;yangzun\u0026lt;/username\u0026gt; \u0026lt;password\u0026gt;12345678\u0026lt;/password\u0026gt; \u0026lt;/server\u0026gt; \u0026lt;server\u0026gt; \u0026lt;id\u0026gt;user-release\u0026lt;/id\u0026gt; \u0026lt;username\u0026gt;yangzun\u0026lt;/username\u0026gt; \u0026lt;password\u0026gt;12345678\u0026lt;/password\u0026gt; \u0026lt;/server\u0026gt; \u0026lt;/servers\u0026gt; \u0026lt;mirrors\u0026gt; \u0026lt;mirror\u0026gt; \u0026lt;id\u0026gt;user-snapshot\u0026lt;/id\u0026gt; \u0026lt;mirrorOf\u0026gt;*\u0026lt;/mirrorOf\u0026gt; \u0026lt;url\u0026gt;http://mirror.treesir.pub/repository/maven-hub\u0026lt;/url\u0026gt; \u0026lt;/mirror\u0026gt; \u0026lt;mirror\u0026gt; \u0026lt;id\u0026gt;user-release\u0026lt;/id\u0026gt; \u0026lt;mirrorOf\u0026gt;*\u0026lt;/mirrorOf\u0026gt; \u0026lt;url\u0026gt;http://mirror.treesir.pub/repository/maven-hub\u0026lt;/url\u0026gt; \u0026lt;/mirror\u0026gt; \u0026lt;/mirrors\u0026gt; \u0026lt;profiles\u0026gt; \u0026lt;profile\u0026gt; \u0026lt;id\u0026gt;nexus\u0026lt;/id\u0026gt; \u0026lt;repositories\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;id\u0026gt;central\u0026lt;/id\u0026gt; \u0026lt;name\u0026gt;_nexus\u0026lt;/name\u0026gt; \u0026lt;url\u0026gt;http://mirror.treesir.pub/repository/maven-hub/\u0026lt;/url\u0026gt; \u0026lt;releases\u0026gt;\u0026lt;enabled\u0026gt;true\u0026lt;/enabled\u0026gt;\u0026lt;/releases\u0026gt; \u0026lt;snapshots\u0026gt;\u0026lt;enabled\u0026gt;true\u0026lt;/enabled\u0026gt;\u0026lt;/snapshots\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;/repositories\u0026gt; \u0026lt;pluginRepositories\u0026gt; \u0026lt;pluginRepository\u0026gt; \u0026lt;id\u0026gt;central\u0026lt;/id\u0026gt; \u0026lt;name\u0026gt;_nexus\u0026lt;/name\u0026gt; \u0026lt;url\u0026gt;http://mirror.treesir.pub/repository/maven-hub/\u0026lt;/url\u0026gt; \u0026lt;releases\u0026gt;\u0026lt;enabled\u0026gt;true\u0026lt;/enabled\u0026gt;\u0026lt;/releases\u0026gt; \u0026lt;snapshots\u0026gt;\u0026lt;enabled\u0026gt;true\u0026lt;/enabled\u0026gt;\u0026lt;/snapshots\u0026gt; \u0026lt;/pluginRepository\u0026gt; \u0026lt;/pluginRepositories\u0026gt; \u0026lt;/profile\u0026gt; \u0026lt;/profiles\u0026gt; \u0026lt;activeProfiles\u0026gt; \u0026lt;activeProfile\u0026gt;nexus\u0026lt;/activeProfile\u0026gt; \u0026lt;/activeProfiles\u0026gt; \u0026lt;/settings\u0026gt; 插件打包的使用记录说明 # maven plugin 在打包过程中，是会基于当前分支，创建一个分支进行打包，并会将创建的分支 push 至远端。与 项目的 分支管理模式有所冲突，并不想push 至远端的话\n指定构建版本为 1.0.4 的 release 制品\nmvn -B release:prepare \\ -DtagNameFormat=@{project.version} \\ -DreleaseVersion=1.0.4 \\ -DpushChanges=false \\ -Dmaven.test.skip=true \\ -T $(nproc) # 指定发布 \u0026#34;1.0.4\u0026#34; 的 release 将制品发布至远程 release 制品仓库\nmvn release:perform \\ -Darguments=\u0026#39;-Dmaven.javadoc.skip=true\u0026#39; \\ -Dmaven.test.skip=true \\ -DlocalCheckout=true \\ -T $(nproc) # 上传至 maven 私服 参考文档 # https://maven.apache.org/maven-release/maven-release-plugin/plugin-info.html\n","date":"2021年5月16日","externalUrl":null,"permalink":"/posts/maven-release-plugin/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e\u003ccode\u003emaven\u003c/code\u003e  settings.xml 配置使用展示 \n    \u003cdiv id=\"maven--settingsxml-配置使用展示\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#maven--settingsxml-%e9%85%8d%e7%bd%ae%e4%bd%bf%e7%94%a8%e5%b1%95%e7%a4%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"cp\"\u003e\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"nt\"\u003e\u0026lt;settings\u003c/span\u003e \u003cspan class=\"na\"\u003exmlns=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://maven.apache.org/SETTINGS/1.0.0\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e           \u003cspan class=\"na\"\u003exmlns:xsi=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e           \u003cspan class=\"na\"\u003exsi:schemaLocation=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c\"\u003e\u0026lt;!-- localRepository\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e              | The path to the local repository maven will use to store artifacts.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e     |\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e     | Default: ${user.home}/.m2/repository\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e    \u0026lt;localRepository\u0026gt;/path/to/local/repo\u0026lt;/localRepository\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e    --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;pluginGroups\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"c\"\u003e\u0026lt;!-- pluginGroup\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e                                    | Specifies a further group identifier to use for plugin lookup.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e     \u0026lt;pluginGroup\u0026gt;com.your.plugins\u0026lt;/pluginGroup\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e     --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;/pluginGroups\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"c\"\u003e\u0026lt;!-- proxies\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e                            | This is a list of proxies which can be used on this machine to connect to the network.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e    | Unless otherwise specified (by system property or command-line switch), the first proxy\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e    | specification in this list marked as active will be used.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e    |--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;proxies\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;/proxies\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;servers\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"nt\"\u003e\u0026lt;server\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"nt\"\u003e\u0026lt;id\u0026gt;\u003c/span\u003euser-snapshot\u003cspan class=\"nt\"\u003e\u0026lt;/id\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"nt\"\u003e\u0026lt;username\u0026gt;\u003c/span\u003eyangzun\u003cspan class=\"nt\"\u003e\u0026lt;/username\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"nt\"\u003e\u0026lt;password\u0026gt;\u003c/span\u003e12345678\u003cspan class=\"nt\"\u003e\u0026lt;/password\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"nt\"\u003e\u0026lt;/server\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"nt\"\u003e\u0026lt;server\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"nt\"\u003e\u0026lt;id\u0026gt;\u003c/span\u003euser-release\u003cspan class=\"nt\"\u003e\u0026lt;/id\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"nt\"\u003e\u0026lt;username\u0026gt;\u003c/span\u003eyangzun\u003cspan class=\"nt\"\u003e\u0026lt;/username\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"nt\"\u003e\u0026lt;password\u0026gt;\u003c/span\u003e12345678\u003cspan class=\"nt\"\u003e\u0026lt;/password\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"nt\"\u003e\u0026lt;/server\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;/servers\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;mirrors\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026lt;mirror\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;id\u0026gt;\u003c/span\u003euser-snapshot\u003cspan class=\"nt\"\u003e\u0026lt;/id\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;mirrorOf\u0026gt;\u003c/span\u003e*\u003cspan class=\"nt\"\u003e\u0026lt;/mirrorOf\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;url\u0026gt;\u003c/span\u003ehttp://mirror.treesir.pub/repository/maven-hub\u003cspan class=\"nt\"\u003e\u0026lt;/url\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026lt;/mirror\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026lt;mirror\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;id\u0026gt;\u003c/span\u003euser-release\u003cspan class=\"nt\"\u003e\u0026lt;/id\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;mirrorOf\u0026gt;\u003c/span\u003e*\u003cspan class=\"nt\"\u003e\u0026lt;/mirrorOf\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;url\u0026gt;\u003c/span\u003ehttp://mirror.treesir.pub/repository/maven-hub\u003cspan class=\"nt\"\u003e\u0026lt;/url\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026lt;/mirror\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;/mirrors\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;profiles\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;profile\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;id\u0026gt;\u003c/span\u003enexus\u003cspan class=\"nt\"\u003e\u0026lt;/id\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;repositories\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;repository\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nt\"\u003e\u0026lt;id\u0026gt;\u003c/span\u003ecentral\u003cspan class=\"nt\"\u003e\u0026lt;/id\u0026gt;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nt\"\u003e\u0026lt;name\u0026gt;\u003c/span\u003e_nexus\u003cspan class=\"nt\"\u003e\u0026lt;/name\u0026gt;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nt\"\u003e\u0026lt;url\u0026gt;\u003c/span\u003ehttp://mirror.treesir.pub/repository/maven-hub/\u003cspan class=\"nt\"\u003e\u0026lt;/url\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nt\"\u003e\u0026lt;releases\u0026gt;\u0026lt;enabled\u0026gt;\u003c/span\u003etrue\u003cspan class=\"nt\"\u003e\u0026lt;/enabled\u0026gt;\u0026lt;/releases\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nt\"\u003e\u0026lt;snapshots\u0026gt;\u0026lt;enabled\u0026gt;\u003c/span\u003etrue\u003cspan class=\"nt\"\u003e\u0026lt;/enabled\u0026gt;\u0026lt;/snapshots\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;/repository\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;/repositories\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;pluginRepositories\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"nt\"\u003e\u0026lt;pluginRepository\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"nt\"\u003e\u0026lt;id\u0026gt;\u003c/span\u003ecentral\u003cspan class=\"nt\"\u003e\u0026lt;/id\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"nt\"\u003e\u0026lt;name\u0026gt;\u003c/span\u003e_nexus\u003cspan class=\"nt\"\u003e\u0026lt;/name\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"nt\"\u003e\u0026lt;url\u0026gt;\u003c/span\u003ehttp://mirror.treesir.pub/repository/maven-hub/\u003cspan class=\"nt\"\u003e\u0026lt;/url\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"nt\"\u003e\u0026lt;releases\u0026gt;\u0026lt;enabled\u0026gt;\u003c/span\u003etrue\u003cspan class=\"nt\"\u003e\u0026lt;/enabled\u0026gt;\u0026lt;/releases\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"nt\"\u003e\u0026lt;snapshots\u0026gt;\u0026lt;enabled\u0026gt;\u003c/span\u003etrue\u003cspan class=\"nt\"\u003e\u0026lt;/enabled\u0026gt;\u0026lt;/snapshots\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;/pluginRepository\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"nt\"\u003e\u0026lt;/pluginRepositories\u0026gt;\u003c/span\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/profile\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;/profiles\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;activeProfiles\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"nt\"\u003e\u0026lt;activeProfile\u0026gt;\u003c/span\u003enexus\u003cspan class=\"nt\"\u003e\u0026lt;/activeProfile\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nt\"\u003e\u0026lt;/activeProfiles\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"nt\"\u003e\u0026lt;/settings\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e插件打包的使用记录说明 \n    \u003cdiv id=\"插件打包的使用记录说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8f%92%e4%bb%b6%e6%89%93%e5%8c%85%e7%9a%84%e4%bd%bf%e7%94%a8%e8%ae%b0%e5%bd%95%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003emaven plugin 在打包过程中，是会基于当前分支，创建一个分支进行打包，并会将创建的分支 push 至远端。与 项目的 分支管理模式有所冲突，并不想push 至远端的话\u003c/p\u003e","title":"Maven ReleasePlugin 的使用记录","type":"posts"},{"content":"","date":"2021年5月15日","externalUrl":null,"permalink":"/tags/openconnect/","section":"Tags","summary":"","title":"Openconnect","type":"tags"},{"content":"","date":"2021年5月15日","externalUrl":null,"permalink":"/tags/openldap/","section":"Tags","summary":"","title":"Openldap","type":"tags"},{"content":" 环境要求 # 本文档基于以下环境进行部署：\nDocker Compose：1.18.0 操作系统：OpenWrt (x86) Docker 版本：19.03.12 Docker 镜像：yangzun/docker-openconnect-ldap:latest 说明：该镜像基于 morganonbass/ocserv-ldap 进行了修改和优化，解决了原镜像无法正常启动的问题。\n部署准备 # 安装 Docker Compose # yum install -y docker-compose 配置部署文件 # 创建项目目录 # mkdir -p /data/docker-compose/openConnect cd /data/docker-compose/openConnect 创建 Docker Compose 配置 # cat \u0026gt; docker-compose.yaml \u0026lt;\u0026lt; EOF version: \u0026#34;3\u0026#34; services: ocserv: container_name: ocserv image: yangzun/docker-openconnect-ldap:latest ports: - \u0026#34;1443:443/tcp\u0026#34; - \u0026#34;1443:443/udp\u0026#34; environment: LISTEN_PORT: 443 TUNNEL_MODE: \u0026#39;split-include\u0026#39; TUNNEL_ROUTES: \u0026#39;192.168.8.0/24\u0026#39; DNS_SERVERS: 192.168.8.1 CLIENTNET: 192.168.248.0 CLIENTNETMASK: 255.255.255.128 BASEDN: \u0026#39;dc=treesir,dc=pub\u0026#39; LDAPURI: \u0026#39;ldap://192.168.8.1:389/\u0026#39; BINDDN: \u0026#39;cn=admin,dc=treesir,dc=pub\u0026#39; BINDPW: \u0026#39;123456\u0026#39; SEARCHSCOPE: \u0026#39;ou=users,dc=treesir,dc=pub\u0026#39; PAM_LOGIN_ATTRIBUTE: \u0026#39;uid\u0026#39; CA_CN: \u0026#39;VPN CA\u0026#39; CA_ORG: \u0026#39;OCSERV\u0026#39; CA_DAYS: 9999 SRV_CN: \u0026#39;nps.treesir.pub\u0026#39; SRV_ORG: \u0026#39;Example Company\u0026#39; SRV_DAYS: 9999 volumes: - \u0026#39;./config/:/config/\u0026#39; cap_add: - NET_ADMIN privileged: true restart: unless-stopped EOF 配置说明：环境变量的详细说明请参考 Docker Hub 页面。OpenLDAP 的部署配置请参考 LDAP 部署文档。\n启动服务 # docker-compose up -d 连接测试 # 我们使用 Cisco AnyConnect 客户端进行连接测试。\n客户端下载：Cisco AnyConnect 官方下载页面\n内网连接测试 # 测试结果显示，使用内网地址可以正常连接，客户端获取到的 IP 地址为配置文件中指定的地址段 192.168.248.0/25。\n公网连接测试 # 由于运营商未提供公网 IP，这里使用内网穿透工具 NPS 进行测试演示。\nNPS 端口映射配置 # 协议选择说明：这里使用 TCP 协议进行映射。相比 UDP 协议，TCP 具有更好的稳定性，不易被运营商拦截。在网络攻击等异常情况下，运营商通常会优先关闭 UDP 等不安全协议。虽然 UDP 在传输速度上有一定优势，但 TCP 的稳定性更适合 VPN 连接。\nPC 客户端测试 # PC 端连接测试成功，可以正常建立 VPN 连接。\n断开连接后，服务器端会输出相应的日志信息。\n移动端测试 # 使用 AnyConnect 移动端客户端进行测试：\n常见问题 # iptables 规则报错 # 问题现象：\n解决方案：\n该问题由 iptables 版本不兼容导致，需要切换到 legacy 版本：\nupdate-alternatives --set iptables /usr/sbin/iptables-legacy 说明：该修复已集成到 Dockerfile 的 docker-entrypoint.sh 脚本中，代码提交后会自动触发镜像构建。\n总结 # OpenConnect vs OpenVPN # OpenConnect 相比 OpenVPN 具有以下优势：\n集中化路由管理：路由配置可在服务端统一控制，无需修改客户端配置文件 安全性更高：避免了客户端配置文件的安全风险，便于管理员统一管控 性能表现：两者都基于 TLS 证书验证，性能差异不大 部署便捷：通过 Docker Compose 可实现一键部署 参考文档 # OCServ 官方文档 ","date":"2021年5月15日","externalUrl":null,"permalink":"/posts/ocserv-vpn-install/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境要求 \n    \u003cdiv id=\"环境要求\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%a6%81%e6%b1%82\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e本文档基于以下环境进行部署：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eDocker Compose\u003c/strong\u003e：1.18.0\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e操作系统\u003c/strong\u003e：OpenWrt (x86)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDocker 版本\u003c/strong\u003e：19.03.12\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDocker 镜像\u003c/strong\u003e：\u003ccode\u003eyangzun/docker-openconnect-ldap:latest\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e说明\u003c/strong\u003e：该镜像基于 \u003ccode\u003emorganonbass/ocserv-ldap\u003c/code\u003e 进行了修改和优化，解决了原镜像无法正常启动的问题。\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e部署准备 \n    \u003cdiv id=\"部署准备\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%83%a8%e7%bd%b2%e5%87%86%e5%a4%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装 Docker Compose \n    \u003cdiv id=\"安装-docker-compose\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85-docker-compose\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eyum install -y docker-compose\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e配置部署文件 \n    \u003cdiv id=\"配置部署文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%85%8d%e7%bd%ae%e9%83%a8%e7%bd%b2%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e创建项目目录 \n    \u003cdiv id=\"创建项目目录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9b%e5%bb%ba%e9%a1%b9%e7%9b%ae%e7%9b%ae%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emkdir -p /data/docker-compose/openConnect\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e /data/docker-compose/openConnect\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e创建 Docker Compose 配置 \n    \u003cdiv id=\"创建-docker-compose-配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9b%e5%bb%ba-docker-compose-%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecat \u0026gt; docker-compose.yaml \u003cspan class=\"s\"\u003e\u0026lt;\u0026lt; EOF\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eversion: \u0026#34;3\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eservices:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  ocserv:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    container_name: ocserv\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    image: yangzun/docker-openconnect-ldap:latest\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    ports:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      - \u0026#34;1443:443/tcp\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      - \u0026#34;1443:443/udp\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    environment:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      LISTEN_PORT: 443\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      TUNNEL_MODE: \u0026#39;split-include\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      TUNNEL_ROUTES: \u0026#39;192.168.8.0/24\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      DNS_SERVERS: 192.168.8.1\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      CLIENTNET: 192.168.248.0\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      CLIENTNETMASK: 255.255.255.128\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      BASEDN: \u0026#39;dc=treesir,dc=pub\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      LDAPURI: \u0026#39;ldap://192.168.8.1:389/\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      BINDDN: \u0026#39;cn=admin,dc=treesir,dc=pub\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      BINDPW: \u0026#39;123456\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      SEARCHSCOPE: \u0026#39;ou=users,dc=treesir,dc=pub\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      PAM_LOGIN_ATTRIBUTE: \u0026#39;uid\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      CA_CN: \u0026#39;VPN CA\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      CA_ORG: \u0026#39;OCSERV\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      CA_DAYS: 9999 \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      SRV_CN: \u0026#39;nps.treesir.pub\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      SRV_ORG: \u0026#39;Example Company\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      SRV_DAYS: 9999\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    volumes:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      - \u0026#39;./config/:/config/\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    cap_add:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e      - NET_ADMIN\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    privileged: true\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    restart: unless-stopped\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eEOF\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e配置说明\u003c/strong\u003e：环境变量的详细说明请参考 \u003ca\n  href=\"https://hub.docker.com/r/yangzun/docker-openconnect-ldap\"\n    target=\"_blank\"\n  \u003eDocker Hub 页面\u003c/a\u003e。OpenLDAP 的部署配置请参考 \u003ca\n  href=\"https://www.treesir.pub/post/docker-deploy-ldap\"\n    target=\"_blank\"\n  \u003eLDAP 部署文档\u003c/a\u003e。\u003c/p\u003e","title":"使用 Docker Compose 部署 OpenConnect VPN 服务器","type":"posts"},{"content":"","date":"2021年4月30日","externalUrl":null,"permalink":"/tags/admission-webhook/","section":"Tags","summary":"","title":"Admission-Webhook","type":"tags"},{"content":" 环境说明 # kubernetes version: v1.17.9 (kubeadm) os: 7.8.2003 (Core) kubernetes dashboard: rancher v2.4.15 概述 # docker 使用 linux 内核中的 cgroup 实现了对 容器使用资源的限制，默认容器启动后依旧挂载了宿主机的 /proc 目录，其中包涵了，meminfo、cpuinfo、stat、uptime 等资源信息。一些监控工具如 free、top、htop 或 业务应用 还依赖 /proc 下文件内容获取资源配置和使用情况。当它们在容器中运行时，实际还是查看到的是宿主机的资源使用，导致资源使用展示源不对，目前社区主流的解决方法是使用 lxcfs 的方法进行解决。\nlxcfs # Github 地址 lxcfs 是一个开源的 fuse（用户态文件系统）最先实现来支持 lxc 容器，但是它也可以支持 Docker 容器。lxcfs 通过用户态文件系统，在容器中提供下列 procfs 的文件。\n/proc/cpuinfo /proc/diskstats /proc/meminfo /proc/stat /proc/swaps /proc/uptime lxcfs 使用示意图\n比如，将宿主机的 /var/lib/lxcfs/proc/memoinfo 文件挂载到 docker 容器中对应的 /proc/meminfo 位置后。容器中的进程读取相应文件内容时，lxcfs 的 fuse 实现会从容器对应的 cgroup 中读取正确的内存限制。从而使得应用获得正确的资源使用情况。\nadmission webhook # 在 Kubernetes apiserver 中包含两个特殊的准入控制器：MutatingAdmissionWebhook 和ValidatingAdmissionWebhook，这两个控制器将发送准入请求到外部的 HTTP 回调服务并接收一个准入响应。如果启用了这两个准入控制器，kubernetes 管理员可以在集群中创建和配置一个 admission webhook。\n检查集群中是否启用了 admission webhook 控制器，并根据需要进行配置。 编写处理准入请求的 HTTP 回调，回调可以是一个部署在集群中的简单 HTTP 服务，甚至也可以是一个 serverless 函数，例如 https://github.com/kelseyhightower/denyenv-validating-admission-webhook 这个项目。 通过 MutatingWebhookConfiguration 和 ValidatingWebhookConfiguration 资源配置 admission webhook 这两种 admission webhook 之间的区别是明显的：validating webhooks 可以拒绝请求，但是它们却不能修改准入请求中获取的对象，而 mutating webhooks 可以在返回准入响应之前通过创建补丁来修改对象，如果 webhook 拒绝了一个请求，则会向最终用户返回错误\nlxcfs 在 Kubernetes 中实践部署 # 下面将示例使用 lxcfs-admission-webhook，部署 admission webhook 给 pod 注入 lxcfs 。\n各节点 安装依赖 # 注意这里是，所有节点多需要安装一下，否则节点会提示动态库缺失。\nyum install -y fuse-libs kubernetes 开启准入控制器 # 我这里使用的是 kubeadm 搭建的集群，可以通过下面命令查看 apiserver pod 的配置\nkubectl get node NAME STATUS ROLES AGE VERSION node1 Ready master,worker 11d v1.17.9 node2 Ready worker 11d v1.17.9 node3 Ready master,worker 2d22h v1.17.9 node4 Ready worker 2d22h v1.17.9 kubectl get pod kube-apiserver-node1 -n kube-system -o yaml apiVersion: v1 kind: Pod metadata: name: kube-apiserver-node1 namespace: kube-system ...... spec: containers: - command: - kube-apiserver - --advertise-address=192.168.8.30 - --allow-privileged=true - --anonymous-auth=True - --apiserver-count=1 - --authorization-mode=Node,RBAC - --bind-address=0.0.0.0 - --client-ca-file=/etc/kubernetes/pki/ca.crt - --enable-admission-plugins=NodeRestriction,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota ..... 上面的 enable-admission-plugins 参数中带上了 MutatingAdmissionWebhook 和ValidatingAdmissionWebhook 两个准入控制插件，如果没有的（在 v1.19.x 版本中是默认开启的），需要添加上这两个参数，然后重启 apiserver。\nkubeadm 演示如何为 apiserver 添加参数，和重启 apiserver\n使用 kubeadm 部署的集群的话，apiserver 使用的部署方式为 静态pod，即我们只要更改对应机器上的 /etc/kubernetes/manifests/kube-apiserver.yaml 文件即可，当 kubelet watch 到配置文件更改后，它将会为我们进行重启生效。\ncat /etc/kubernetes/manifests/kube-apiserver.yaml|grep enable-admission-plugins - --enable-admission-plugins=NodeRestriction,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota 查看集群是否开启了 准入控制 api\nkubectl api-versions |grep admission admissionregistration.k8s.io/v1 admissionregistration.k8s.io/v1beta1 部署 lxcfs # git clone 代码 git clone https://github.com/denverdino/lxcfs-admission-webhook.git 部署 lxcfs 资源清单 cd lxcfs-admission-webhook kubectl create ns lxcfs # 创建 lxcfs 部署命名空间 # sed -i \u0026#34;s#default#lxcfs#g\u0026#34; deployment/lxcfs-daemonset.yaml # 替换部署命名空间 kubectl apply -f deployment/lxcfs-daemonset.yaml -n lxcfs 更改一下 deployment/install.sh 中部署的命名空间，默认使用的是 default\nsed -i \u0026#34;s#default#lxcfs#g\u0026#34; deployment/mutatingwebhook.yaml # 替换默认模板文件中的命名空间 cat deployment/install.sh #!/bin/bash ./deployment/webhook-create-signed-cert.sh --namespace lxcfs kubectl get secret lxcfs-admission-webhook-certs -n lxcfs kubectl create -f deployment/deployment.yaml -n lxcfs kubectl create -f deployment/service.yaml -n lxcfs cat ./deployment/mutatingwebhook.yaml | ./deployment/webhook-patch-ca-bundle.sh \u0026gt; ./deployment/mutatingwebhook-ca-bundle.yaml kubectl create -f deployment/mutatingwebhook-ca-bundle.yaml -n lxcfs bash deployment/install.sh # 执行部署 测试 # 这里将部署一个 python 程序，进行模拟测试\n为命名空间注入 lxcfs kubectl label namespace default lxcfs-admission-webhook=enabled # 为命名空间打入 lxcfs-admission-webhook label 即可 创建测试资源清单 cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: python3 namespace: default labels: app: python3 spec: replicas: 1 selector: matchLabels: app: python3 template: metadata: labels: app: python3 spec: containers: - args: - sleep - \u0026#34;18000\u0026#34; name: python3 image: python:3.7-slim-buster imagePullPolicy: Always resources: limits: cpu: \u0026#34;2\u0026#34; memory: 4Gi requests: cpu: \u0026#34;1\u0026#34; memory: 2Gi EOF 检查资源限制是否生效 kubectl get po NAME READY STATUS RESTARTS AGE python3-5fcbcf5655-sw65s 1/1 Running 0 22s kubectl exec -it python3-5fcbcf5655-sw65s bash cat /proc/cpuinfo| grep \u0026#34;cpu cores\u0026#34;| uniq # 查看 cpu 核数 cpu cores : 2 cat /proc/meminfo | grep MemTotal # 查看内存 MemTotal: 4194304 kB apt update \\ \u0026amp;\u0026amp; apt install htop 可以看到上面的资源清单，是更具 limits 设置来。展示输出的。\n问题记录 # 卸载\nkubectl delete -f deployment/lxcfs-daemonset.yaml -n lxcfs cat deployment/uninstall.sh #!/bin/bash # 对脚本中指定了命名空间卸载 kubectl delete -f deployment/mutatingwebhook-ca-bundle.yaml -n lxcfs kubectl delete -f deployment/service.yaml -n lxcfs kubectl delete -f deployment/deployment.yaml -n lxcfs kubectl delete secret lxcfs-admission-webhook-certs -n lxcfs deployment/uninstall.sh # 执行卸载脚本 rm -rf /var/lib/lxcfs/ # 删除宿主机目录，如无法删除时，请尝试重启一下节点。 lxcfs 不支持 使用了 alpine 作为基础镜像的容器。\n参考文档 # https://cloud.tencent.com/developer/article/1645542 https://github.com/denverdino/lxcfs-admission-webhook https://www.qikqiak.com/k8strain2/security/admission/ https://github.com/lxc/lxcfs Done # ","date":"2021年4月30日","externalUrl":null,"permalink":"/posts/k8s-lxcfs-admissionhook/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003ekubernetes version:   v1.17.9 (\u003ccode\u003ekubeadm\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003eos: 7.8.2003 (Core)\u003c/li\u003e\n\u003cli\u003ekubernetes dashboard:  rancher \u003ccode\u003ev2.4.15\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e概述 \n    \u003cdiv id=\"概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003edocker 使用 linux 内核中的 \u003ccode\u003ecgroup\u003c/code\u003e 实现了对 容器使用资源的限制，默认容器启动后依旧挂载了宿主机的 \u003ccode\u003e/proc\u003c/code\u003e 目录，其中包涵了，\u003ccode\u003ememinfo\u003c/code\u003e、\u003ccode\u003ecpuinfo\u003c/code\u003e、\u003ccode\u003estat\u003c/code\u003e、\u003ccode\u003euptime\u003c/code\u003e 等资源信息。一些监控工具如 \u003ccode\u003efree\u003c/code\u003e、\u003ccode\u003etop\u003c/code\u003e、\u003ccode\u003ehtop\u003c/code\u003e 或  \u003ccode\u003e业务应用\u003c/code\u003e 还依赖 \u003ccode\u003e/proc\u003c/code\u003e 下文件内容获取资源配置和使用情况。当它们在容器中运行时，实际还是查看到的是宿主机的资源使用，导致资源使用展示源不对，目前社区主流的解决方法是使用 \u003ccode\u003elxcfs\u003c/code\u003e 的方法进行解决。\u003c/p\u003e","title":"K8s 部署 Lxcfs 准入控制器，实现容器中资源单独可见","type":"posts"},{"content":"","date":"2021年4月30日","externalUrl":null,"permalink":"/tags/lxcfs/","section":"Tags","summary":"","title":"Lxcfs","type":"tags"},{"content":" 环境说明 # 概述 # KubeSphere 是在 Kubernetes 之上构建的面向云原生应用的分布式操作系统，完全开源，支持多云与多集群管理，提供全栈的 IT 自动化运维能力，简化企业的 DevOps 工作流。它的架构可以非常方便地使第三方应用与云原生生态组件进行即插即用 (plug-and-play) 的集成。\nKubeKey（由 Go 语言开发且代码开源）是一种全新的安装工具，替代了以前使用的基于 ansible 的安装程序。KubeKey 提供灵活的安装选择，可以仅安装 Kubernetes，也可以同时安装 Kubernetes 和 KubeSphere。\n使用场景：\n仅安装 Kubernetes； 使用一个命令同时安装 Kubernetes 和 KubeSphere； 扩缩集群； 升级集群； 安装 Kubernetes 相关的插件（Chart 或 YAML）。 环境说明 # 操作系统: CentOS Linux release 7.8.2003 (Core) 服务器 cpu 架构: x86_64 online 模式部署 # 此次演示，准备两台只安装了操作系统和配置了ip地址的主机。\nnode1: 192.168.8.30 node2: 192.168.8.31 下载源码 编译 kubekey # yum install -y git git clone https://github.com/kubesphere/kubekey.git \\ \u0026amp;\u0026amp; cd kubekey ./build.sh -p # 执行编译 ls -lh output/kk # 编译结束后，会在 output 目录下，生成可执行程序 kk -rwxr-xr-x 1 root root 57M 4月 26 15:17 output/kk output/kk version version.BuildInfo{Version:\u0026#34;latest+unreleased\u0026#34;, GitCommit:\u0026#34;22d2e7648569a7e538cbbd6505ce9aca4180d6a1\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, GoVersion:\u0026#34;go1.14.7\u0026#34;} 创建配置文件 # scp output/kk 192.168.8.30:/usr/local/bin/ # copy 编译好的命令至 node1 [root@localhost ~]# kk version # node1 version.BuildInfo{Version:\u0026#34;latest+unreleased\u0026#34;, GitCommit:\u0026#34;22d2e7648569a7e538cbbd6505ce9aca4180d6a1\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, GoVersion:\u0026#34;go1.14.7\u0026#34;} ./kk create config --with-kubernetes v1.17.9 # 创建 v1.17.9 版本的配置文件，版本列表请查看此链接https://github.com/kubesphere/kubekey/blob/master/docs/kubernetes-versions.md 启动集群 # cat config-sample.yaml # 配置文件展示 apiVersion: kubekey.kubesphere.io/v1alpha1 kind: Cluster metadata: name: sample spec: hosts: - {name: node1, address: 192.168.8.30, internalAddress: 192.168.8.30, user: root, password: 123456} # 将对应主机的密码填入 - {name: node2, address: 192.168.8.31, internalAddress: 192.168.8.31, user: root, password: 123456} roleGroups: # 配置机器角色 etcd: - node1 master: - node1 worker: - node1 - node2 controlPlaneEndpoint: domain: lb.kubesphere.local address: \u0026#34;\u0026#34; port: 6443 kubernetes: version: v1.17.9 imageRepo: kubesphere clusterName: cluster.local network: plugin: calico kubePodsCIDR: 10.233.64.0/18 kubeServiceCIDR: 10.233.0.0/18 registry: registryMirrors: [] insecureRegistries: [] addons: [] kk init os -f ./config-sample.yaml # 初始化集群机器，此操作会安装系统依赖，开启lpvs模块等。前提是确保对应node的网络通畅。 kk create cluster -f ./config-sample.yaml # 创建集群 kubectl get po --all-namespaces # 等待 30s 左右就会初始化完成 NAMESPACE NAME READY STATUS RESTARTS AGE kube-system calico-kube-controllers-7b4f58f565-6wdl2 0/1 Pending 0 11s kube-system calico-node-f99fc 0/1 Running 0 11s kube-system calico-node-lnhc8 1/1 Running 0 11s kube-system coredns-74d59cc5c6-nrdsn 0/1 Pending 0 46s kube-system coredns-74d59cc5c6-zftdf 0/1 Pending 0 46s kube-system kube-apiserver-node1 1/1 Running 0 41s kube-system kube-controller-manager-node1 1/1 Running 0 41s kube-system kube-proxy-l245l 1/1 Running 0 14s kube-system kube-proxy-mgvfx 1/1 Running 0 46s kube-system kube-scheduler-node1 1/1 Running 0 41s kube-system nodelocaldns-k2qnm 1/1 Running 0 46s kube-system nodelocaldns-rz8h9 1/1 Running 0 14s offline 部署 # 参考文档 上述文档中，离线部署已 非常详细，下面演示步骤中，且 着重 展示去掉 无用 操作的优化项。\n文档中 kubesphere-all-v3.0.0-offline-linux-amd64.tar.gz文件下载，那个下载太慢了 (限速)\n百度云盘链接 : (bfvp)。 部署前准备 # 环境与online 部署环境基本一致，只是网络是 无法访问外网 的。\n取消机器默认路由，使其无法访问外网 ip route add default via 192.168.8.253 # 将默认路由指向一个不存在的网关 ping -c3 223.5.5.5 PING 223.5.5.5 (223.5.5.5) 56(84) bytes of data. From 192.168.8.31 icmp_seq=1 Destination Host Unreachable From 192.168.8.31 icmp_seq=2 Destination Host Unreachable From 192.168.8.31 icmp_seq=3 Destination Host Unreachable 解压文件 # tar xf kubesphere-all-v3.0.0-offline-linux-amd64.tar.gz 创建集群配置文件 # ./kk create config --with-kubernetes v1.17.9 修改默认生成的配置文件展示 # apiVersion: kubekey.kubesphere.io/v1alpha1 kind: Cluster metadata: name: sample spec: hosts: - {name: node1, address: 192.168.8.30, internalAddress: 192.168.8.30, user: root, password: 123456} - {name: node2, address: 192.168.8.31, internalAddress: 192.168.8.31, user: root, password: 123456} roleGroups: etcd: - node1 master: - node1 worker: - node1 - node2 controlPlaneEndpoint: domain: lb.kubesphere.local address: \u0026#34;\u0026#34; port: \u0026#34;6443\u0026#34; kubernetes: version: v1.17.9 imageRepo: kubesphere clusterName: cluster.local network: plugin: calico kubePodsCIDR: 10.233.64.0/18 kubeServiceCIDR: 10.233.0.0/18 registry: registryMirrors: [] insecureRegistries: [] privateRegistry: dockerhub.kubekey.local # 添加使用 kubekey 创建的私服 addons: [] 节点环境初始化 # ./kk init os -f config-sample.yaml -s ./dependencies/ docker load \u0026lt; kubesphere-images-v3.0.0/registry.tar # 加载私服镜像 ./kk init os -f config-sample.yaml -s ./dependencies/ --add-images-repo # 启动镜像私服 docker ps # 可以看到私服已经启动了 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 741cbeff3dee registry:2 \u0026#34;/entrypoint.sh /etc…\u0026#34; 19 seconds ago Up 17 seconds 0.0.0.0:443-\u0026gt;443/tcp, 5000/tcp kubekey-registry 上传镜像至私服中 # 此步骤比较漫长，可以更具实际使用情况，进行删除 images-list-v3.0.0.txt 中的镜像列表 和 路径下的 *.tar 结尾的镜像压缩包\ncd kubesphere-images-v3.0.0 \\ \u0026amp;\u0026amp;./push-images.sh dockerhub.kubekey.local # 加载路径下的所有 *.tar 结尾的镜像压缩包，并根据 images-list-v3.0.0.txt 中的镜像列表，进行上传至刚才创建的私服中。 启动集群 # ./kk create cluster -f config-sample.yaml kubectl get po --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system calico-kube-controllers-677cbc8557-c9n9b 1/1 Running 0 48s kube-system calico-node-bhjwf 1/1 Running 0 48s kube-system calico-node-f278s 1/1 Running 0 24s kube-system coredns-79878cb9c9-g5drn 1/1 Running 0 63s kube-system coredns-79878cb9c9-lsh7n 1/1 Running 0 63s kube-system kube-apiserver-node1 1/1 Running 0 58s kube-system kube-controller-manager-node1 1/1 Running 0 58s kube-system kube-proxy-5zkpg 1/1 Running 0 63s kube-system kube-proxy-c986s 1/1 Running 0 24s kube-system kube-scheduler-node1 1/1 Running 0 58s kube-system nodelocaldns-p5fkv 1/1 Running 0 63s kube-system nodelocaldns-phh4s 1/1 Running 0 24s 扩展集群角色的配置 # 示例将，对上面部署的集群进行扩展，分别扩展 master \u0026amp; worker 各一台，扩展中 master apiserver 负载均衡器将使用 nginx 承担\n本次新增节点列表如下所示\n主机名称 集群角色 IP地址 node3 master 192.168.8.32 node4 worker 192.168.8.33 manage lb (nginx) 192.168.8.88 nginx 负载均衡配置 # 安装\nyum install -y wget mv /etc/yum.repos.d{,.bak} \\ \u0026amp;\u0026amp; mkdir -p /etc/yum.repos.d \\ \u0026amp;\u0026amp; curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo \\ \u0026amp;\u0026amp; wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo \\ \u0026amp;\u0026amp; yum clean all \\ \u0026amp;\u0026amp; yum makecache fast yum install -y nginx* 负载均衡配置\ncat nginx.conf user nginx nginx; worker_processes auto; error_log /data/wwwlogs/error_nginx.log crit; pid /var/run/nginx.pid; worker_rlimit_nofile 51200; load_module \u0026#34;/usr/lib64/nginx/modules/ngx_stream_module.so\u0026#34;; events { use epoll; worker_connections 51200; multi_accept on; } http { include mime.types; default_type application/octet-stream; server_names_hash_bucket_size 128; client_header_buffer_size 32k; large_client_header_buffers 4 32k; client_max_body_size 1024m; client_body_buffer_size 10m; sendfile on; tcp_nopush on; keepalive_timeout 120; server_tokens off; tcp_nodelay on; fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; fastcgi_buffer_size 64k; fastcgi_buffers 4 64k; fastcgi_busy_buffers_size 128k; fastcgi_temp_file_write_size 128k; fastcgi_intercept_errors on; #Gzip Compression gzip on; gzip_buffers 16 8k; gzip_comp_level 6; gzip_http_version 1.1; gzip_min_length 256; gzip_proxied any; gzip_vary on; gzip_types text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml image/svg+xml text/javascript application/javascript application/x-javascript text/x-json application/json application/x-web-app-manifest+json text/css text/plain text/x-component font/opentype application/x-font-ttf application/vnd.ms-fontobject image/x-icon; gzip_disable \u0026#34;MSIE [1-6]\\.(?!.*SV1)\u0026#34;; ##Brotli Compression #brotli on; #brotli_comp_level 6; #brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml; ##If you have a lot of static files to serve through Nginx then caching of the files\u0026#39; metadata (not the actual files\u0026#39; contents) can save some latency. #open_file_cache max=1000 inactive=20s; #open_file_cache_valid 30s; #open_file_cache_min_uses 2; #open_file_cache_errors on; ######################## default ############################ include vhost/*.conf; } stream { upstream kubekey_apiserver { least_conn; server 192.168.8.30:6443 max_fails=3 fail_timeout=5s; server 192.168.8.32:6443 max_fails=3 fail_timeout=5s; } server { listen 6443; proxy_pass kubekey_apiserver; } } mkdir -p /data/wwwlogs/ \\ \u0026amp;\u0026amp; chmod 777 -R /data/wwwlogs/ # 创建日志目录 nginx -t # 检查配置文件 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful sed -i \u0026#39;s#SELINUX=enforcing#SELINUX=disabled#g\u0026#39; /etc/selinux/config # 关闭 selinux，防止启动报错 grep -i ^selinux= /etc/selinux/config setenforce 0 getenforce service firewalld stop \\ \u0026amp;\u0026amp; systemctl disable firewalld # 关闭防火墙 service nginx start \\ \u0026amp;\u0026amp; systemctl enable nginx # 启动 nginx 并设置开机自启 ss -lntp|grep \u0026#34;:6443\u0026#34; # 检查端口 LISTEN 0 128 *:6443 *:* users:((\u0026#34;nginx\u0026#34;,pid=8513,fd=5),(\u0026#34;nginx\u0026#34;,pid=8512,fd=5),(\u0026#34;nginx\u0026#34;,pid=8511,fd=5),(\u0026#34;nginx\u0026#34;,pid=8510,fd=5),(\u0026#34;nginx\u0026#34;,pid=8509,fd=5),(\u0026#34;nginx\u0026#34;,pid=8508,fd=5),(\u0026#34;nginx\u0026#34;,pid=8507,fd=5),(\u0026#34;nginx\u0026#34;,pid=8506,fd=5),(\u0026#34;nginx\u0026#34;,pid=8495,fd=5)) 更改kubekey 配置文件 # 修改后的配置文件展示\ncat config-sample.yaml apiVersion: kubekey.kubesphere.io/v1alpha1 kind: Cluster metadata: name: sample spec: hosts: - {name: node1, address: 192.168.8.30, internalAddress: 192.168.8.30, user: root, password: 123456} - {name: node2, address: 192.168.8.31, internalAddress: 192.168.8.31, user: root, password: 123456} - {name: node3, address: 192.168.8.32, internalAddress: 192.168.8.32, user: root, password: 123456} - {name: node4, address: 192.168.8.33, internalAddress: 192.168.8.33, user: root, password: 123456} roleGroups: etcd: # 这里的 etcd 的节点数需要是奇数,所以也将 node2 也加入至 etcd集群中。 - node1 - node2 - node3 master: - node1 - node3 worker: - node1 - node2 - node3 - node4 controlPlaneEndpoint: domain: lb.kubesphere.local address: \u0026#34;192.168.8.88\u0026#34; port: \u0026#34;6443\u0026#34; kubernetes: version: v1.17.9 imageRepo: kubesphere clusterName: cluster.local network: plugin: calico kubePodsCIDR: 10.233.64.0/18 kubeServiceCIDR: 10.233.0.0/18 registry: registryMirrors: [] insecureRegistries: [] privateRegistry: dockerhub.kubekey.local # 添加使用 kubekey 创建的私服 addons: [] 执行添加节点 # 初始化新添加的节点 # ./kk init os -f config-sample.yaml -s ./dependencies/ --add-images-repo # 示例适用上面离线安装的步骤，如 online部署，取消 -s 选项 执行节点添加 # service firewalld stop \\ \u0026amp;\u0026amp; systemctl disable firewalld # 关闭防火墙，防止加入节点时端口不同 sed -i \u0026#39;s#SELINUX=enforcing#SELINUX=disabled#g\u0026#39; /etc/selinux/config # 关闭 selinux，防止启动报错 grep -i ^selinux= /etc/selinux/config setenforce 0 getenforce ./kk add nodes -f ./config-sample.yaml kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME node1 Ready master,worker 8d v1.17.9 192.168.8.30 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1127.el7.x86_64 docker://19.3.13 node2 Ready worker 8d v1.17.9 192.168.8.31 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1127.el7.x86_64 docker://19.3.13 node3 Ready master,worker 2m23s v1.17.9 192.168.8.32 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1127.el7.x86_64 docker://19.3.13 node4 Ready worker 2m26s v1.17.9 192.168.8.33 \u0026lt;none\u0026gt; CentOS Linux 7 (Core) 3.10.0-1127.el7.x86_64 docker://19.3.13 在上面示例中，执行的前两步，可以在 github 中找到对应 kubekey仓库，clone 最新源码，在源码中做对应添加后，再执行编译，这样我们就不需要每台多去执行了。\n更具体操作请参考下述文档 # https://kubesphere.io/zh/docs/installing-on-linux/introduction/kubekey/ https://github.com/kubesphere/kubekey ","date":"2021年4月26日","externalUrl":null,"permalink":"/posts/kubekey-install-k8s/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e概述 \n    \u003cdiv id=\"概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca\n  href=\"https://kubesphere.io/\"\n    target=\"_blank\"\n  \u003eKubeSphere\u003c/a\u003e 是在 \u003ca\n  href=\"https://kubernetes.io/\"\n    target=\"_blank\"\n  \u003eKubernetes\u003c/a\u003e 之上构建的面向云原生应用的\u003cstrong\u003e分布式操作系统\u003c/strong\u003e，完全开源，支持多云与多集群管理，提供全栈的 IT 自动化运维能力，简化企业的 DevOps 工作流。它的架构可以非常方便地使第三方应用与云原生生态组件进行即插即用 (plug-and-play) 的集成。\u003c/p\u003e","title":"使用 Kubekey 一键 离线/在线 部署 kubernetes 集群","type":"posts"},{"content":" 指定特定仓库拉取代码 # git remote add origin https://github.com/demo/example.git git pull https://repo.csheidou.com/forestry_afterend/prevention_fire_test.git master git branch --set-upstream-to=origin/master git status git pull 更改远程仓库地址 # git remote -v git remote set-url origin https://github.com/demo/example.git git pull origin master --allow-unrelated-histories git push git branch --set-upstream-to=origin/master 清理本地错误的用户信息 # 运行一下命令缓存输入的用户名和密码： git config --global credential.helper wincred 清除掉缓存在git中的用户名和密码 git credential-manager uninstall 解决 Windows git pull 卡住不动问题 # 原因 在 git clone 时后面仓库地址是非公开的 需要验证用户及密码 而windows 默认是已弹窗形式\n解决方法：取消窗口拉取\ngit config --system --unset credential.helper 解决 git 每次 pull/push 多需要输入账号密码问题 # git config --global credential.helper store 重置 commit id # git rebase -i HEAD~3 # 表示需要修改的是最近3次, 此操作会打开一个编辑窗口。 然后按`i`编辑，把 pick 改成 edit，按\u0026#39;Esc\u0026#39;退出编辑，按`:wq`保存退出 git commit --amend -s # 打开刚才更改为 edit 的commit，修改为自己想要的 git rebase --continue git log git push -f ","date":"2021年4月25日","externalUrl":null,"permalink":"/posts/git-use-issue/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e\u003cstrong\u003e指定特定仓库拉取代码\u003c/strong\u003e \n    \u003cdiv id=\"指定特定仓库拉取代码\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8c%87%e5%ae%9a%e7%89%b9%e5%ae%9a%e4%bb%93%e5%ba%93%e6%8b%89%e5%8f%96%e4%bb%a3%e7%a0%81\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit remote add origin https://github.com/demo/example.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit pull https://repo.csheidou.com/forestry_afterend/prevention_fire_test.git master\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit branch --set-upstream-to\u003cspan class=\"o\"\u003e=\u003c/span\u003eorigin/master\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit status\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit pull \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e\u003cstrong\u003e更改远程仓库地址\u003c/strong\u003e \n    \u003cdiv id=\"更改远程仓库地址\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9b%b4%e6%94%b9%e8%bf%9c%e7%a8%8b%e4%bb%93%e5%ba%93%e5%9c%b0%e5%9d%80\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit remote -v \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit remote set-url origin  https://github.com/demo/example.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit pull origin master  --allow-unrelated-histories\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit push \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit branch --set-upstream-to\u003cspan class=\"o\"\u003e=\u003c/span\u003eorigin/master\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e\u003cstrong\u003e清理本地错误的用户信息\u003c/strong\u003e \n    \u003cdiv id=\"清理本地错误的用户信息\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b8%85%e7%90%86%e6%9c%ac%e5%9c%b0%e9%94%99%e8%af%af%e7%9a%84%e7%94%a8%e6%88%b7%e4%bf%a1%e6%81%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e运行一下命令缓存输入的用户名和密码：\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit config --global credential.helper wincred\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e清除掉缓存在git中的用户名和密码\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit credential-manager uninstall\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e\u003cstrong\u003e解决 Windows git pull 卡住不动问题\u003c/strong\u003e \n    \u003cdiv id=\"解决-windows-git-pull-卡住不动问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3-windows-git-pull-%e5%8d%a1%e4%bd%8f%e4%b8%8d%e5%8a%a8%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e原因 在 git clone 时后面仓库地址是非公开的 需要验证用户及密码 而windows 默认是已弹窗形式\u003c/p\u003e","title":"Git 使用问题的记录","type":"posts"},{"content":"","date":"2021年4月22日","externalUrl":null,"permalink":"/tags/openvpn/","section":"Tags","summary":"","title":"Openvpn","type":"tags"},{"content":" 环境说明 # docker-compose：1.23.2 操作系统: armbian (斐讯n1) 部署软件 # openldap 部署 # 省略 openldap 的部署配置 请参考之前整理的 文档\nopenvpn 安装 # 这里 openvpn 一键安装使用 github 中的一键脚本\n脚本地址\ncurl -o openvpn-install.sh https://raw.githubusercontent.com/Nyr/openvpn-install/master/openvpn-install.sh chmod a+x openvpn-install.sh ./openvpn-install.sh Welcome to this OpenVPN road warrior installer! Which IPv4 address should be used? 1) 192.168.8.112 2) 172.18.0.1 3) 172.17.0.1 4) 10.42.0.0 5) 10.42.0.1 IPv4 address [1]: 1 This server is behind NAT. What is the public IPv4 address or hostname? Public IPv4 address / hostname [23.105.194.216]: Which protocol should OpenVPN use? 1) UDP (recommended) 2) TCP Protocol [1]: 1 What port should OpenVPN listen to? Port [1194]: 1194 Select a DNS server for the clients: 1) Current system resolvers 2) Google 3) 1.1.1.1 4) OpenDNS 5) Quad9 6) AdGuard DNS server [1]: 1 Enter a name for the first client: Name [client]: 192.168.8.1 OpenVPN installation is ready to begin. 等待程序的执行完毕 设置程序为开启自启动\nsystemctl enable openvpn-server@server.service 开放端口\n如开启和使用了防火墙，请注意将 对应端口开放\niptables -A INPUT -p udp --sport 1194 -j ACCEPT iptables -A OUTPUT -p udp --dport 1194 -j ACCEPT 配置关联openldap # 安装依赖包 # apt install openvpn-auth-ldap # 如果是 centos 将前面命令更改为 yum 即可 安装完毕后，查看使用有如下 依赖库文件\nls -lh /usr/lib/openvpn/openvpn-auth-ldap.so -rw-r--r-- 1 root root 126K Nov 7 2017 /usr/lib/openvpn/openvpn-auth-ldap.so centos 下的文件路径为 /usr/lib64/openvpn/plugin/lib/openvpn-auth-ldap.so\n配置文件增加配置 # vi /etc/openvpn/server/server.conf # 在最后添加如下配置 plugin /usr/lib/openvpn/openvpn-auth-ldap.so \u0026#34;/etc/openvpn/server/ldap.conf\u0026#34; client-cert-not-required username-as-common-name management 0.0.0.0 5555 增加 openldap配置文件\ncat \u0026gt; /etc/openvpn/server/ldap.conf \u0026lt;\u0026lt; EOF `# 注意修改下面对应的配置` \u0026lt;LDAP\u0026gt; URL ldap://192.168.8.1:389 BindDN cn=admin,dc=treesir,dc=pub Password 123456 Timeout 15 TLSEnable no FollowReferrals no \u0026lt;/LDAP\u0026gt; \u0026lt;Authorization\u0026gt; BaseDN \u0026#34;ou=users,dc=treesir,dc=pub\u0026#34; SearchFilter \u0026#34;(uid=%u)\u0026#34; RequireGroup false \u0026lt;/Authorization\u0026gt; EOF 示例中 ldap 目录使用结构 参考文档\n更改完成后，我们重启服务使其生效\nservice openvpn-server@server restart \\ \u0026amp;\u0026amp; service openvpn-server@server status 测试验证 # 配置 nps 内网穿透的使用\n将配置文件里的 remote 地址更改为公网地址，没有公网地址的话，可以使用第三方内网穿透工具实现, 下面示例将使用自建的 nps 作为演示\n注意这个映射地址外面设置成了 21174 那么在对应的配置文件中也要相应更改成这个，还有就是协议需要 和安装时选择的对应上，我这里使用的是 udp 协议。\n下载安装 vpn 时的客户端配置文件\nsz -y /root/192_168_8_1.ovpn ## 默认在 用户的家目录中 编辑下载的配置文件\nremote nps.treesir.pub 21194 # 将此地址更改为 公网地址，没有公网地址的话，可以使用第三方内网穿透工具实现 auth-user-pass # 添加用户密码认证 auth-nocache # 认证不缓存 setenv opt block-outside-dns # Prevent Windows 10 DNS leak 手机端测试\n将配置文件上传至手机端进行测试\n可以看到手机端使用流量，测试也是正常可以访问到内网的。\n部署 openldap dashboard # docker run --name openvpn-monitor \\ -e OPENVPNMONITOR_DEFAULT_DATETIMEFORMAT=\u0026#34;%%d/%%m/%%Y\u0026#34; \\ -e OPENVPNMONITOR_DEFAULT_LATITUDE=-37 \\ -e OPENVPNMONITOR_DEFAULT_LOGO=logo.jpg \\ -e OPENVPNMONITOR_DEFAULT_LONGITUDE=144 \\ -e OPENVPNMONITOR_DEFAULT_MAPS=True \\ -e OPENVPNMONITOR_DEFAULT_SITE=Test \\ -e OPENVPNMONITOR_SITES_0_ALIAS=UDP \\ -e OPENVPNMONITOR_SITES_0_HOST=192.168.8.112 \\ -e OPENVPNMONITOR_SITES_0_NAME=UDP \\ -e OPENVPNMONITOR_SITES_0_PORT=5555 \\ -e OPENVPNMONITOR_SITES_1_ALIAS=TCP \\ -e OPENVPNMONITOR_SITES_1_HOST=192.168.8.112 \\ -e OPENVPNMONITOR_SITES_1_NAME=TCP \\ -e OPENVPNMONITOR_SITES_1_PORT=5555 \\ --restart=always \\ -p 8210:80 \\ -d ruimarinho/openvpn-monitor 可以看到，dashboard 中的 remote ip 是 192.168.8.113，且map中无对应显示地址 ( map 更具 remote ip 显示 )， 这是因为我使用了内网穿透技术的缘故，企业中建议是绑定到对应的公网ip中，并使用 DNAT 作为端口映射，这样dashboard 中的 map 显示将呈现正常。\n客户端软件下载 # openvpn-install-2.4.8-I602-Win10\nopenvpn-install-2.4.6-I602-Win7\nopenvpn-android-install\n","date":"2021年4月22日","externalUrl":null,"permalink":"/posts/openvpn-install/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003edocker-compose\u003c/code\u003e：1.23.2\u003c/li\u003e\n\u003cli\u003e操作系统: \u003ccode\u003earmbian (斐讯n1)\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e部署软件 \n    \u003cdiv id=\"部署软件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%83%a8%e7%bd%b2%e8%bd%af%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003eopenldap 部署 \n    \u003cdiv id=\"openldap-部署\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#openldap-%e9%83%a8%e7%bd%b2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e省略 openldap 的部署配置 请参考之前整理的 \u003ca\n  href=\"https://www.treesir.pub/post/docker-deploy-ldap/\"\n    target=\"_blank\"\n  \u003e\u003ccode\u003e文档\u003c/code\u003e\u003c/a\u003e\u003c/p\u003e","title":"OpenVpn 的安装，并配置关联 openLdap 认证","type":"posts"},{"content":"","date":"2021年4月14日","externalUrl":null,"permalink":"/tags/centos7/","section":"Tags","summary":"","title":"Centos7","type":"tags"},{"content":" 环境说明 # 因为在公司中，有下班后有需要关闭 服务器 的操作，并且每天还需要去手动打开。有点违背 极客精神，所有自己通过 shell script + cron 的形式进行每天定时的 开关服务器机 操作。但是服务器中不乏有一些比较老的机型，没有提供类似于 ipmi 管理功能，这些服务器自动启动依赖于主板自带的 网络唤醒 功能，如果服务器未正常关机时 (卡在关机界面)，第二天早上通过网络唤醒时是无法正常打开服务器的，需要管理人员的介入管理强制关机。这篇文档主要是介绍如何优化服务器的关机时间长，服务器关机卡死问题。\n操作环境 # Centos7：7.9.2009 优化 # 关闭图形界面 # 如服务器中，存在图形界面的功能，建议是把他设置成命令行形式的。\nsystemctl get-default # 查看当前的工作模式 systemctl set-default multi-user.target # 设置为命令行界面 设置 systemctl 的超时时间 # cp /etc/systemd/system.conf{,.bak} vi /etc/systemd/system.conf # 编辑此文件 DefaultTimeoutStopSec=90s # 更改为 适合自己的时间 更新系统 # 这里更新系统的原因是，可能是软件包中存在bug，导致关机时间长。\nyum update -y \\ \u0026amp;\u0026amp; yum upgrade -y 优化 acpi 管理模块 # yum -y install acpid systemctl start acpid systemctl enable acpid vim /etc/default/grub GRUB_CMDLINE_LINUX=\u0026#34;... acpi=force reboot=warm\u0026#34; # 在此行最后添加 grub2-mkconfig -o /boot/grub2/grub.cfg # 配置后，重新生成 grub 启动加载文件 优化 watchdog 的超时时间 # cat /etc/systemd/system.conf|grep -i ShutdownWatchdogSec # 默认此参数为 10min ShutdownWatchdogSec=30s 参考文档 # https://blog.csdn.net/F8qG7f9YD02Pe/article/details/108722168 https://forums.centos.org/viewtopic.php?t=59735 ","date":"2021年4月14日","externalUrl":null,"permalink":"/posts/centos-shutdown-notpoweroff/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e因为在公司中，有下班后有需要关闭 \u003ccode\u003e服务器\u003c/code\u003e 的操作，并且每天还需要去手动打开。有点违背 极客精神，所有自己通过 \u003ccode\u003eshell script\u003c/code\u003e + \u003ccode\u003ecron\u003c/code\u003e 的形式进行每天定时的 \u003ccode\u003e开关服务器机\u003c/code\u003e 操作。但是服务器中不乏有一些比较老的机型，没有提供类似于\u003ccode\u003e ipmi\u003c/code\u003e 管理功能，这些服务器自动启动依赖于主板自带的 \u003ccode\u003e网络唤醒\u003c/code\u003e 功能，如果服务器未正常关机时 (卡在关机界面)，第二天早上通过网络唤醒时是无法正常打开服务器的，需要管理人员的介入管理强制关机。这篇文档主要是介绍如何优化服务器的关机时间长，服务器关机卡死问题。\u003c/p\u003e","title":"Centos7 执行 shutdown 无法正常关机的解决","type":"posts"},{"content":" 功能简介 # 本脚本用于生成 Git 日报通知，将 GitLab 中指定项目组的所有项目 Git 提交信息以 HTML 表格形式发送给相关人员，适用于敏捷开发项目的日常管理。\n环境要求 # 依赖工具\nGit 2.9.3 sendemail jq 运行环境\nCentOS 7 (7.9.2009) GitLab 13.10.2 环境准备 # 编译安装 Git # 其他版本下载地址 yum remove git # 卸载系统自带的低版本 Git wget https://github.com/git/git/archive/refs/tags/v2.9.3.zip # 下载源码 unzip v2.9.3.zip cd git-2.9.3/ make configure ./configure prefix=/usr/local/git/ # 配置 Git 安装目录 make -j8 \u0026amp;\u0026amp; make install # 编译并安装 ls /usr/local/git/ bin lib64 libexec share vim /etc/profile # 添加到 PATH 环境变量 export PATH=/usr/local/git/bin:$PATH source /etc/profile # 使环境变量生效 git version git version 2.9.3 安装其他依赖工具 # yum install sendemail jq -y 配置准备 # 获取 GitLab API Token # 点击创建，复制生成的 Token\n创建项目组并获取组 ID # 获取到当前项目组的 ID 为 3\n创建测试仓库 # 在刚才创建的项目组中，新建几个仓库，并在这些仓库中添加一些提交记录。\n添加提交记录\n脚本实现 # 完整脚本如下，请根据实际环境修改相关变量参数：\n#!/bin/bash source /etc/profile GITLAB_TOKEN=\u0026#39;xxx\u0026#39; # GitLab API Token GROUP_ID=3 # 项目组 ID GROUP_NAME=\u0026#39;git-log\u0026#39; # 对应的 GitLab 项目组名称 GITLAB_WEB_URL=\u0026#39;http://gitlab.treesir.pub\u0026#39; # GitLab 访问地址 GITLAB_URL=\u0026#39;http://root:xxx@gitlab.treesir.pub\u0026#39; # 具有项目 Git Clone 权限的账号密码 DATE=`date +%Y-%m-%d` HTML_NAME=\u0026#34;$DATE-$GROUP_NAME\u0026#34;.html DATA_DIR=/data/scripts/gitdata/\u0026#34;$GROUP_NAME\u0026#34; GITWORK=/data/scripts/gitwork/\u0026#34;$GROUP_NAME\u0026#34; #EMAIL_LIST=(\u0026#34;user1@gmail.com\u0026#34; \u0026#34;user2@qq.com\u0026#34;) # 定义需要通知的人员邮箱地址 EMAIL_LIST=(\u0026#34;xxx@dingtalk.com\u0026#34;) DATA_SAVE_TIME=180 # 生成的 HTML 文件保存天数 HTML_PATH_NAME=\u0026#34;$DATA_DIR\u0026#34;/\u0026#34;$HTML_NAME\u0026#34; IFS=$\u0026#39;\\n\u0026#39; RETRY=0 SMTP_EMAIL=\u0026#39;xxx@163.com\u0026#39; SMTP_SERVER=\u0026#39;smtp.163.com\u0026#39; SMTP_AUTH=\u0026#39;xxx\u0026#39; mkdir -p \u0026#34;$GITWORK\u0026#34; \u0026amp;\u0026amp; mkdir -p \u0026#34;$DATA_DIR\u0026#34; GIT_DATE=\u0026#34;`date -d \u0026#39;1 days ago\u0026#39; +%Y-%m-%d` 18:50:00\u0026#34; # 从前一天下午 6:50 开始统计提交记录 # 如果 HTML 文件已存在则删除 if [ -f \u0026#34;$HTML_PATH_NAME\u0026#34; ] then rm -rf \u0026#34;$HTML_PATH_NAME\u0026#34; fi # 获取项目组中所有仓库的名称 GITLAB_JSON=`curl --header \u0026#34;Authorization: Bearer $GITLAB_TOKEN\u0026#34; \u0026#34;$GITLAB_URL/api/v4/groups/${GROUP_ID}\u0026#34; 2\u0026gt;/dev/null` # 生成 HTML 文件头部 echo \u0026#34;\u0026#34;\u0026#34;\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34;\u0026gt; \u0026lt;meta http-equiv=\u0026#34;X-UA-Compatible\u0026#34; content=\u0026#34;ie=edge\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Document\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; table{ width: 800px; height: auto; border: 1px solid #999; color: #000; margin: 100px auto; text-align: left; } table caption{ font-size: 20px; font-weight:bold; padding-bottom: 10px; } .title{ border: none; } table th{ background-color: #999; opacity: 0.8; height: 35px; } table td{ height: 20px; background-color: #333; color: seashell; font-size: 14px; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026#34;\u0026#34;\u0026#34; \u0026gt;\u0026gt; \u0026#34;$HTML_PATH_NAME\u0026#34; for REMONAME in `echo \u0026#34;$GITLAB_JSON\u0026#34;|jq .projects[].name|awk -F \u0026#39;\u0026#34;\u0026#39; \u0026#39;{print \\$2}\u0026#39;` do # 克隆或更新代码仓库 if [ ! -d \u0026#34;$GITWORK/$REMONAME\u0026#34; ] then cd \u0026#34;$GITWORK\u0026#34; git clone \u0026#34;$GITLAB_URL/$GROUP_NAME/$REMONAME\u0026#34;.git else cd \u0026#34;$GITWORK/$REMONAME\u0026#34; git pull fi for GIT_BRANCH in `git -C \u0026#34;$GITWORK/$REMONAME\u0026#34; branch -a|grep origin|grep -v \u0026#39;HEAD\u0026#39;|awk -F \u0026#39;remotes/origin/\u0026#39; \u0026#39;{print $2}\u0026#39;` do cd \u0026#34;$GITWORK/$REMONAME\u0026#34; git checkout \u0026#34;$GIT_BRANCH\u0026#34; git pull if [ `git log --date=format:\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39; --pretty=format:\u0026#34;%ad,%an,%s,%h\u0026#34; --since=\u0026#34;$GIT_DATE\u0026#34;|grep \u0026#34;-\u0026#34; |wc -l` -ge 1 ] then echo \u0026#34;\u0026#34;\u0026#34; \u0026lt;table\u0026gt; \u0026lt;caption\u0026gt;项目和分支名称: $REMONAME $GIT_BRANCH\u0026lt;/caption\u0026gt; \u0026lt;tr class=\u0026#34;title\u0026#34;\u0026gt; \u0026lt;th\u0026gt;提交时间\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;提交人\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;提交内容\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;提交ID\u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026#34;\u0026#34;\u0026#34; \u0026gt;\u0026gt; \u0026#34;$HTML_PATH_NAME\u0026#34; for COMMINT in `git log --date=format:\u0026#39;%Y-%m-%d-%H:%M:%S\u0026#39; --pretty=format:\u0026#34;%ad|%an|%s|%h|%H\u0026#34; --since=\u0026#34;$GIT_DATE\u0026#34;` do if [ `echo $COMMINT|grep \u0026#39;Merge\u0026#39;|wc -l` -eq 1 ] then continue else submit_time=`echo $COMMINT|awk -F \u0026#39;|\u0026#39; \u0026#39;{print $1}\u0026#39;` submit_one=`echo $COMMINT|awk -F \u0026#39;|\u0026#39; \u0026#39;{print $2}\u0026#39;` submit_message=`echo $COMMINT|awk -F \u0026#39;|\u0026#39; \u0026#39;{print $3}\u0026#39;` submit_id=`echo $COMMINT|awk -F \u0026#39;|\u0026#39; \u0026#39;{print $4}\u0026#39;` submit_long_id=`echo $COMMINT|awk -F \u0026#39;|\u0026#39; \u0026#39;{print $5}\u0026#39;` echo \u0026#34;\u0026#34;\u0026#34; \u0026lt;tr class=\u0026#34;title\u0026#34;\u0026gt; \u0026lt;td\u0026gt;\u0026#34;$submit_time\u0026#34;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026#34;$submit_one\u0026#34;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026#34;$submit_message\u0026#34;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;a href=\u0026#34;$GITLAB_WEB_URL/$GROUP_NAME/$REMONAME/-/commit/$submit_long_id\u0026#34;\u0026gt;\u0026#34;$submit_id\u0026#34;\u0026lt;/a\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026#34;\u0026#34;\u0026#34; \u0026gt;\u0026gt; \u0026#34;$HTML_PATH_NAME\u0026#34; fi done echo \u0026#34;\u0026lt;/table\u0026gt;\u0026#34; \u0026gt;\u0026gt; \u0026#34;$HTML_PATH_NAME\u0026#34; fi done done echo \u0026#34;\u0026#34;\u0026#34; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; \u0026gt;\u0026gt; \u0026#34;$HTML_PATH_NAME\u0026#34; if [ `cat \u0026#34;$HTML_PATH_NAME\u0026#34; |grep tr|wc -l` -gt 0 ] then for EMAIL in ${EMAIL_LIST[@]} do /usr/bin/sendemail -f \u0026#34;${SMTP_EMAIL}\u0026#34; -u \u0026#34;$DATE ${GROUP_NAME}项目 日报请查收~\u0026#34; -s \u0026#34;${SMTP_SERVER}\u0026#34; -o tls=no -o message-content-type=html -o message-charset=utf8 -xu \u0026#34;${SMTP_EMAIL}\u0026#34; -t \u0026#34;$EMAIL\u0026#34; -xp ${SMTP_AUTH} -m `cat \u0026#34;$HTML_PATH_NAME\u0026#34;` if [ \u0026#34;$?\u0026#34; -ne 0 ] then while [ \u0026#34;$RETRY\u0026#34; -lt 3 ] do sleep 2 /usr/bin/sendemail -f \u0026#34;${SMTP_EMAIL}\u0026#34; -u \u0026#34;$DATE ${GROUP_NAME}项目 日报请查收~\u0026#34; -s \u0026#34;${SMTP_SERVER}\u0026#34; -o tls=no -o message-content-type=html -o message-charset=utf8 -xu \u0026#34;${SMTP_EMAIL}\u0026#34; -t \u0026#34;$EMAIL\u0026#34; -xp ${SMTP_AUTH} -m `cat \u0026#34;$HTML_PATH_NAME\u0026#34;` if [ \u0026#34;$?\u0026#34; -eq 0 ] then break else RETRY=$(($RETRY+1)) fi done fi done fi find \u0026#34;$DATA_DIR\u0026#34; -type f -mtime +\u0026#34;$DATA_SAVE_TIME\u0026#34; -exec rm -f {} \\; 运行效果展示 # 日报邮件效果展示\n上面时区显示不正确的问题是由于在 GitLab Dashboard 中直接添加提交记录导致的。我们通过命令行添加提交记录来验证：\n重新执行脚本：\n./gitlogs.sh 可以看到时区显示正常\n配置定时任务 # crontab -e # 编辑定时任务表 0 12 * * * /usr/bin/bash /data/scripts/gitlogs.sh \u0026gt;\u0026gt; /tmp/script_debug.log 0 16 * * * /usr/bin/bash /data/scripts/gitlogs.sh \u0026gt;\u0026gt; /tmp/script_debug.log 0 17 * * * /usr/bin/bash /data/scripts/gitlogs.sh \u0026gt;\u0026gt; /tmp/script_debug.log 0 19 * * * /usr/bin/bash /data/scripts/gitlogs.sh \u0026gt;\u0026gt; /tmp/script_debug.log 以上配置将在每天的 12:00、16:00、17:00、19:00 自动执行日报生成脚本。\n","date":"2021年4月13日","externalUrl":null,"permalink":"/posts/git-dailypaper/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e功能简介 \n    \u003cdiv id=\"功能简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8a%9f%e8%83%bd%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e本脚本用于生成 Git 日报通知，将 GitLab 中指定项目组的所有项目 Git 提交信息以 HTML 表格形式发送给相关人员，适用于敏捷开发项目的日常管理。\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e环境要求 \n    \u003cdiv id=\"环境要求\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%a6%81%e6%b1%82\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e依赖工具\u003c/strong\u003e\u003c/p\u003e","title":"Git 日报生成脚本","type":"posts"},{"content":" 环境说明 # 版本信息 # GitLab 版本：13.10.2 如需了解 GitLab 服务器安装方法，请参考 安装文档。本文将详细介绍如何在 GitLab 中添加和配置 pre-receive 钩子，并演示如何对 git push 操作进行限制。\n参考文档 # GitLab pre-receive 钩子配置指南 Pre-receive 钩子简介 # 什么是 Pre-receive 钩子？ # 当我们需要对 Git 提交内容进行校验时，可以使用服务端的 pre-receive 钩子。相比客户端的 pre-commit 钩子，它具有以下优势：\n集中管理：无需在每个客户端单独配置 统一维护：所有规则在服务端统一管理 强制执行：无法被客户端绕过 工作原理 # pre-receive 钩子会在代码推送时对提交内容进行校验 如果校验脚本正常退出（exit 0），则允许推送 如果校验失败，则拒绝推送操作 钩子类型 # GitLab 服务端支持三种类型的钩子：\npre-receive：推送前执行 update：更新时执行 post-receive：推送后执行 详细说明请参考 官方文档\nPre-receive 钩子配置 # 钩子脚本说明 # pre-receive 钩子脚本具有以下特点：\n多语言支持：可以使用 shell、Python、Ruby 等任何可执行程序 输入参数：从标准输入获取三个参数：旧版本号、新版本号、分支名称 环境变量：提供丰富的内置环境变量供使用 权限要求：必须设置正确的执行权限 详细的脚本编写指南请参考 官方文档。\n配置全局钩子 # 环境准备 # 本示例基于 Docker 部署的 GitLab，数据通过持久卷映射保存。\nGitLab 支持两种钩子作用域：\n全局钩子：对所有项目生效 项目钩子：仅对特定项目生效 配置步骤 # # 1. 进入 GitLab 配置目录 $ pwd /application/gitlab $ ll total 4 drwxrwxr-x 3 root root 239 Apr 9 16:38 config drwxr-xr-x 20 root root 4096 Apr 13 10:14 data drwxr-xr-x 20 root root 326 Apr 9 16:41 logs # 2. 编辑 GitLab 配置文件 vi config/gitlab.rb # 取消注释并设置全局钩子目录 gitlab_shell[\u0026#39;custom_hooks_dir\u0026#39;] = \u0026#34;/etc/gitlab/hooks\u0026#34; # 3. 创建钩子目录 mkdir -p config/hooks/pre-receive.d/ # 4. 验证容器内目录映射 docker exec -it gitlab-ce bash -c \u0026#39;ls /etc/gitlab/hooks\u0026#39; pre-receive.d # 5. 重新加载配置（重要：必须执行此步骤） docker exec -it gitlab-ce bash -c \u0026#39;gitlab-ctl reconfigure\u0026#39; # 6. 重启容器使配置生效 docker restart gitlab-ce 示例：提交信息长度验证 # 脚本功能 # 创建一个验证提交信息长度的钩子，确保每次提交的描述信息足够详细。\n脚本内容 # # 创建并编辑钩子脚本 cat config/hooks/pre-receive.d/commit-length #!/bin/bash # Pre-receive 钩子：验证提交信息长度 # 调试模式：取消注释下行可启用调试 #set -x LG=18 # 最小字节长度要求 validate_ref() { # 获取参数 oldrev=$(git rev-parse $1) newrev=$(git rev-parse $2) refname=\u0026#34;$3\u0026#34; # 获取提交列表 commitList=`git rev-list $oldrev..$newrev` split=($commitList) # 遍历每个提交 for s in ${split[@]} do echo \u0026#34;检查提交: $s\u0026#34; # 提取提交信息 msg=`git cat-file commit $s | sed \u0026#39;1,/^$/d\u0026#39;` echo \u0026#34;提交信息: $msg\u0026#34; # 验证长度 if [ ${#msg} -lt \u0026#34;$LG\u0026#34; ];then echo \u0026#34;错误：提交信息长度不足 18 个字节\u0026#34; exit 1 else echo \u0026#34;通过：提交信息长度符合要求（大于 $LG 字节）\u0026#34; fi done } fail=\u0026#34;\u0026#34; # 支持命令行和钩子两种运行模式 if [ -n \u0026#34;$1\u0026#34; -a -n \u0026#34;$2\u0026#34; -a -n \u0026#34;$3\u0026#34; ]; then # 命令行模式 PAGER= validate_ref $2 $3 $1 else # 钩子模式：从标准输入读取 while read oldrev newrev refname do validate_ref $oldrev $newrev $refname done fi # 检查是否有失败 if [ -n \u0026#34;$fail\u0026#34; ]; then exit $fail fi # 设置脚本执行权限 docker exec -it gitlab-ce bash -c \u0026#39;chmod 777 -R /etc/gitlab/hooks/\u0026#39; 重要说明 # 字符编码注意事项：\n1 个中文字符 = 3 个字节 最少需要约 6 个中文字符才能满足 18 字节要求 空格也会被计入字节数 如需排除空格，可修改脚本中的字符串处理逻辑 权限设置：\n必须为钩子脚本设置执行权限 权限不正确会导致钩子无法生效 验证脚本效果 # 方式一：GitLab Web 界面验证\n从界面可以看到推送被拒绝，但 Web 界面不会显示详细的错误信息。\n方式二：命令行验证\n# 克隆测试仓库 cd /tmp git clone http://gitlab.treesir.pub/gitlab-instance-7c228ebb/Monitoring.git # 创建测试文件 touch test.md git add ./* # 提交一个过短的信息 git commit -m \u0026#34;aaa\u0026#34; # 尝试推送（会被拒绝） git push origin master 推送结果：\nEnumerating objects: 4, done. Counting objects: 100% (4/4), done. Delta compression using up to 4 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 266 bytes | 266.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0) remote: 检查提交: d1d9d8d5cad9ece2e82e4a9252efacb271c4fd0f remote: 提交信息: aaa remote: 错误：提交信息长度不足 18 个字节 To http://gitlab.treesir.pub/gitlab-instance-7c228ebb/Monitoring.git ! [remote rejected] master -\u0026gt; master (pre-receive hook declined) error: failed to push some refs to \u0026#39;http://gitlab.treesir.pub/gitlab-instance-7c228ebb/Monitoring.git\u0026#39; 项目级钩子配置 # 应用场景 # 有时我们不希望对所有项目都应用相同的限制，而是希望：\n针对特定项目组设置规则 为某些重要项目设置特殊验证 在不同项目中实施不同的代码规范 钩子类型对比 # 钩子类型 作用范围 配置路径 适用场景 全局钩子 所有项目 /etc/gitlab/hooks 统一的代码规范 项目钩子 单个项目 项目路径/custom_hooks/ 特定项目需求 下面我们通过实际示例来演示项目级钩子的配置方法。\n示例：强制关联 JIRA Issue ID # 应用场景 # 当 GitLab 与 JIRA 集成后，我们希望确保每个提交都关联到具体的 JIRA 工单，以便：\n追踪代码变更的业务背景 建立代码与需求的关联关系 便于后续的问题排查和版本管理 脚本实现 # # 创建 JIRA Issue ID 验证脚本 cat custom_hooks/pre-receive.d/require-jira-issue #!/bin/bash # # 拒绝不符合提交信息格式要求的推送 # 确保每个提交都关联到 JIRA Issue ID # # 示例：此钩子要求提交信息包含格式为 [DT-数字] 的 JIRA Issue ID # 参考：https://help.github.com/en/enterprise/user/articles/working-with-pre-receive-hooks set -e # 空提交的 SHA 值 zero_commit=\u0026#39;0000000000000000000000000000000000000000\u0026#39; # 正则表达式定义 msg0_regex=\u0026#39;^DT-[0-9]+ \u0026#39; # JIRA Issue 格式：DT-123 开头 msg1_regex=\u0026#39;^Merge\u0026#39; # 允许 Merge 提交 while read -r oldrev newrev refname; do # 忽略分支或标签删除操作 [ \u0026#34;$newrev\u0026#34; = \u0026#34;$zero_commit\u0026#34; ] \u0026amp;\u0026amp; continue # 计算新分支或更新分支的提交范围 [ \u0026#34;$oldrev\u0026#34; = \u0026#34;$zero_commit\u0026#34; ] \u0026amp;\u0026amp; range=\u0026#34;$newrev\u0026#34; || range=\u0026#34;$oldrev..$newrev\u0026#34; # 检查每个新提交 for commit in $(git rev-list \u0026#34;$range\u0026#34; --not --all); do # 验证提交信息是否符合格式要求 if ! git log --max-count=1 --format=%B $commit | egrep -iq \u0026#34;$msg0_regex|$msg1_regex\u0026#34;; then echo \u0026#34;错误：提交被拒绝\u0026#34; echo \u0026#34;错误：提交 $commit 在分支 ${refname#refs/heads/}\u0026#34; echo \u0026#34;错误：缺少 JIRA Issue ID\u0026#34; echo \u0026#34;错误：正确格式示例：\u0026#39;DT-123 这是一个提交示例\u0026#39;\u0026#34; echo \u0026#34;错误：\u0026#34; echo \u0026#34;错误：请修正提交信息后重新推送\u0026#34; echo \u0026#34;错误：修改提交信息方法：https://help.github.com/en/articles/changing-a-commit-message\u0026#34; echo \u0026#34;错误：\u0026#34; exit 1 fi done done 格式要求说明 # 支持的提交信息格式：\nDT-123 添加用户登录功能 ✅ DT-456 修复数据库连接问题 ✅ Merge branch 'feature' into main ✅ 不支持的格式：\n添加用户登录功能 ❌（缺少 Issue ID） 修复 bug ❌（缺少 Issue ID） 定位项目存储路径 # GitLab 存储机制说明 # 从 GitLab 某个版本开始，项目在磁盘上以 hash 形式存储，这样做的好处是：\n提高磁盘 I/O 性能 避免文件系统路径长度限制 提升大量项目时的访问效率 操作步骤 # 步骤 1：创建测试项目\n步骤 2：获取项目的 Hash 路径\n通过 GitLab 管理界面查看项目信息：\n从界面中可以获得项目的 hash 路径：@hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.git\n步骤 3：配置项目钩子\n# 进入项目存储目录 cd /application/gitlab/data/git-data/repositories/ cd @hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.git 步骤 4：创建钩子文件\n# 创建钩子目录 mkdir -p custom_hooks/pre-receive.d/ # 创建钩子脚本文件 vim custom_hooks/pre-receive.d/require-jira-issue # 将上面的脚本内容复制到此文件中 # 验证目录结构 tree custom_hooks custom_hooks/ └── pre-receive.d └── require-jira-issue 1 directory, 1 file # 设置执行权限（重要！） chmod 777 -R ./custom_hooks 注意： 权限设置是必须的，否则钩子将不会生效。\n功能测试 # 测试步骤 # # 1. 克隆测试项目 git clone http://gitlab.treesir.pub/root/test-commit.git cd test-commit # 2. 创建测试文件 touch test.md git add ./* # 3. 提交不符合格式的信息 git commit -m \u0026#34;test\u0026#34; # 4. 尝试推送（将被拒绝） git push 测试结果 # Enumerating objects: 3, done. Counting objects: 100% (3/3), done. Writing objects: 100% (3/3), 205 bytes | 205.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0) remote: 错误：提交被拒绝 remote: 错误：提交 38bcec39ffd4132c495a22af0ed1485fbe8852f4 在分支 master remote: 错误：缺少 JIRA Issue ID remote: 错误：正确格式示例：\u0026#39;DT-123 这是一个提交示例\u0026#39; remote: 错误： remote: 错误：请修正提交信息后重新推送 remote: 错误：修改提交信息方法：https://help.github.com/en/articles/changing-a-commit-message remote: 错误： To http://gitlab.treesir.pub/root/test-commit.git ! [remote rejected] master -\u0026gt; master (pre-receive hook declined) error: failed to push some refs to \u0026#39;http://gitlab.treesir.pub/root/test-commit.git\u0026#39; 批量配置建议 # 当项目数量较多时，逐个配置会比较繁琐。建议使用以下方法：\n使用 GitLab API：通过 API 查询项目的 hash 路径 编写自动化脚本：批量为多个项目添加钩子 使用全局钩子：如果规则适用于所有项目，优先考虑全局配置 具体的 API 使用方法和自动化脚本编写需要一定的编程基础。\n其他实用钩子 # 文件类型限制钩子 # 应用场景 # 在某些项目中，我们需要限制特定类型文件的提交，例如：\n防止提交大型二进制文件影响仓库性能 避免提交可执行文件带来安全风险 确保代码仓库的纯净性 脚本实现 # # 创建文件类型限制脚本 cat custom_hooks/pre-receive.d/block-files #!/usr/bin/env bash # # Pre-receive 钩子：阻止特定类型文件的提交 # 限制文件类型：.gz, .zip, .tgz, .exe # # 详细文档：https://help.github.com/enterprise/admin/guides/developer-workflow/managing-pre-receive-hooks-on-the-github-enterprise-appliance/ zero_commit=\u0026#34;0000000000000000000000000000000000000000\u0026#34; # 排除已存在于仓库中的提交 # 这可以防止在启用钩子后创建新分支时出现错误 # 如果不需要此行为，可以将此变量设为空 excludeExisting=\u0026#34;--not --all\u0026#34; while read oldrev newrev refname; do echo \u0026#34;检查分支: $refname ($oldrev -\u0026gt; $newrev)\u0026#34; # 忽略分支或标签删除操作 if [ \u0026#34;$newrev\u0026#34; = \u0026#34;$zero_commit\u0026#34; ]; then continue fi # 确定检查范围 if [ \u0026#34;$oldrev\u0026#34; = \u0026#34;$zero_commit\u0026#34; ]; then # 新分支或标签 span=`git rev-list $newrev $excludeExisting` else # 现有分支更新 span=`git rev-list $oldrev..$newrev $excludeExisting` fi # 检查每个提交中的文件 for COMMIT in $span; do for FILE in `git log -1 --name-only --pretty=format:\u0026#39;\u0026#39; $COMMIT`; do case $FILE in *.zip|*.gz|*.tgz|*.exe ) echo \u0026#34;错误：检测到受限制的文件类型\u0026#34; echo \u0026#34;文件：$FILE\u0026#34; echo \u0026#34;限制类型：*.zip, *.gz, *.tgz, *.exe\u0026#34; echo \u0026#34;如需提交此类文件，请联系管理员讨论替代方案\u0026#34; exit 1 ;; esac done done done exit 0 配置说明 # 受限制的文件类型：\n.zip - 压缩文件 .gz - Gzip 压缩文件 .tgz - Tar.gz 压缩文件 .exe - Windows 可执行文件 自定义文件类型： 可以根据需要修改脚本中的文件类型匹配规则，例如：\n*.zip|*.gz|*.tgz|*.exe|*.dmg|*.pkg|*.deb|*.rpm 这样可以扩展到更多文件类型的限制。\n","date":"2021年4月12日","externalUrl":null,"permalink":"/posts/gitlab-pre-receive-webhook/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e版本信息 \n    \u003cdiv id=\"版本信息\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%89%88%e6%9c%ac%e4%bf%a1%e6%81%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eGitLab 版本：13.10.2\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e如需了解 GitLab 服务器安装方法，请参考 \u003ca\n  href=\"https://www.treesir.pub/post/gitlab-deploy/\"\n    target=\"_blank\"\n  \u003e安装文档\u003c/a\u003e。本文将详细介绍如何在 GitLab 中添加和配置 \u003ccode\u003epre-receive\u003c/code\u003e 钩子，并演示如何对 \u003ccode\u003egit push\u003c/code\u003e 操作进行限制。\u003c/p\u003e","title":"GitLab Pre-receive 钩子的配置与使用指南","type":"posts"},{"content":"","date":"2021年4月12日","externalUrl":null,"permalink":"/tags/webhook/","section":"Tags","summary":"","title":"Webhook","type":"tags"},{"content":" 环境说明 # Gitlab-ce 版本： 13.10.2\nJira 版本：v8.13.4\nJira # 获取 工作流结束 id # 获取到工作流 结束 id 21\nGitlab # 设置全局与 jira 集成 # 测试效果 # 找到需要关联 issue id\n编辑 文件 commit时输入 issue id 空格 message\n可以看到对应的 jira issue 页面已经可以看到对应的效果了\n更改具体的操作 可以参考一下这个 文档\n总结 # Jira 与 Gitlab 之间的集成后，可以实时了解到此 issue 目前的状态信息，可以合理提升人员之间的信息沟通。如配合 Gitlab 中服务端的 pre-receive webhook 强制要求用户在每次 commit 时添加对应的 issue id，那么效果更佳。后续有时间的话，我会出相关的配置教程，下面记录一下之前项目踩坑的地方。\nGitlab 连接时使用的 jira 用户权限问题\n","date":"2021年4月9日","externalUrl":null,"permalink":"/posts/gitlab-integration-jira/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eGitlab-ce 版本： 13.10.2\u003c/p\u003e\n\u003cp\u003eJira 版本：v8.13.4\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003eJira \n    \u003cdiv id=\"jira\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jira\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e获取 工作流结束 id \n    \u003cdiv id=\"获取-工作流结束-id\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%8e%b7%e5%8f%96-%e5%b7%a5%e4%bd%9c%e6%b5%81%e7%bb%93%e6%9d%9f-id\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"image-20210412165030920\" src=\"https://cdn.treesir.pub/img/image-20210412165030920.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"Gitlab 和 jira 之间的集成","type":"posts"},{"content":"","date":"2021年4月9日","externalUrl":null,"permalink":"/tags/jira/","section":"Tags","summary":"","title":"Jira","type":"tags"},{"content":" 环境说明 # 操作系统: Centos Docker 版本: 19.03.8 离线版本安装 # Docker-ce 版本 YUM源 配置 - 参考文档\ndocker_rpm.tar.gz 离线文件准备\nsudo yum install --downloadonly --downloaddir=./docker_rpm docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 下载离线包，如已机器已安装 docker 可以将 install 改为 reinstall 参数即可 具体操作命令记录\n[root@localhost tools]# ls -lh 总用量 58M -rw-r--r--. 1 root root 58M 9月 9 21:22 docker_rpm.tar.gz\t# 此文件请查看百度云盘 # 将软件包解压 并执行安装 tar xf docker_rpm.tar.gz \u0026amp;\u0026amp; cd docker_rpm \u0026amp;\u0026amp; rpm -ivh *.rpm --nodeps --force # 启动服务并设置开机自启 service docker start \\ \u0026amp;\u0026amp; systemctl enable docker \\ \u0026amp;\u0026amp; systemctl status docker # 查看容器版本及服务是否安装正常 [root@localhost docker-rpm]# docker info |head -n 10 Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: 18.09.6 Storage Driver: overlay2 Backing Filesystem: xfs Supports d_type: true Native Overlay Diff: true 基础优化 # 参考文档\n","date":"2021年3月25日","externalUrl":null,"permalink":"/posts/docker-centosoffline-install/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e操作系统:  Centos\u003c/li\u003e\n\u003cli\u003eDocker 版本:  19.03.8\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\n\u003ch3 class=\"relative group\"\u003e离线版本安装 \n    \u003cdiv id=\"离线版本安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%a6%bb%e7%ba%bf%e7%89%88%e6%9c%ac%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eDocker-ce 版本 YUM源 配置 -  \u003ca\n  href=\"https://docs.docker.com/engine/install/centos/\"\n    target=\"_blank\"\n  \u003e参考文档\u003c/a\u003e\u003c/p\u003e","title":"Docker 在 Centos7 中离线安装","type":"posts"},{"content":" Jira Webhook 与 Jenkins 集成 # 此文档为归档数据，暂不具备参考意义\nJira 创建项目 并新建模块 关联 Gitlab 中对应仓库\nJenkins 创建 jira webhook manage pipeline\njira 添加 网络钩子\nhttp://jenkins.xxx.net/generic-webhook-trigger/invoke?token=jira-trigger-manage\u0026amp;runOpts=JiraPush\u0026amp;issueTypeName=null\u0026amp;issueTypeStatus=null\u0026amp;projectName=${project.key} ","date":"2021年3月16日","externalUrl":null,"permalink":"/posts/jira-webhook-integration-jenkins/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eJira Webhook 与 Jenkins 集成 \n    \u003cdiv id=\"jira-webhook-与-jenkins-集成\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jira-webhook-%e4%b8%8e-jenkins-%e9%9b%86%e6%88%90\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003e此文档为归档数据，暂不具备参考意义\u003c/code\u003e\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eJira 创建项目 并新建模块 关联 Gitlab 中对应仓库\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"image-20210316104758799\" src=\"https://cdn.treesir.pub/img/image-20210316104758799.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"image-20210316105052883\" src=\"https://cdn.treesir.pub/img/image-20210316105052883.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003eJenkins 创建 jira webhook manage pipeline\u003c/p\u003e","title":"Jira Webhook Integration Jenkins","type":"posts"},{"content":"","date":"2021年3月9日","externalUrl":null,"permalink":"/tags/alerting/","section":"Tags","summary":"","title":"Alerting","type":"tags"},{"content":"","date":"2021年3月9日","externalUrl":null,"permalink":"/categories/monitoring/","section":"Categories","summary":"","title":"Monitoring","type":"categories"},{"content":"","date":"2021年3月9日","externalUrl":null,"permalink":"/tags/monitoring/","section":"Tags","summary":"","title":"Monitoring","type":"tags"},{"content":"","date":"2021年3月9日","externalUrl":null,"permalink":"/tags/observability/","section":"Tags","summary":"","title":"Observability","type":"tags"},{"content":" Prometheus 监控平台简介 # 什么是 Prometheus # Prometheus 是由 SoundCloud 开发并贡献给 CNCF（Cloud Native Computing Foundation）的开源监控和告警系统。作为云原生监控的事实标准，Prometheus 为现代化的微服务架构和容器化环境提供了强大的监控能力。\n核心特性 # 多维数据模型：基于时间序列的数据模型，使用标签（labels）进行多维度标识 强大的查询语言：PromQL 提供灵活的数据查询、聚合和计算功能 无依赖存储：内置时序数据库，单节点自主运行，无需外部依赖 Pull 模式采集：主动拉取模式，支持服务发现和动态配置 推送网关支持：通过 Pushgateway 支持短期作业和批处理任务 丰富的生态系统：大量官方和第三方 Exporter，覆盖各种监控场景 高效存储：采用高效的压缩算法，优化存储空间和查询性能 应用场景 # 基础设施监控：服务器、网络设备、存储系统的性能监控 应用程序监控：Web 服务、数据库、消息队列等应用组件监控 业务指标监控：用户行为、业务流程、KPI 指标的实时监控 容器和微服务监控：Kubernetes、Docker 等容器化环境的全栈监控 SLI/SLO 监控：服务等级指标和目标的持续监控和告警 架构设计 # 单机架构 # flowchart TB subgraph Prometheus[Prometheus Server] direction TB A[Web UI] B[HTTP API] C[PromQL Engine] D[Retrieval] E[TSDB] F[Rule Engine] G[Service Discovery] H[Scrape Targets] end I1[Node Exporter] I2[App Metrics] I3[Custom Exporter] G --\u003e I1 G --\u003e I2 G --\u003e I3 企业级高可用架构 # flowchart TB LB[Load Balancer] P1[Prometheus Server 1] P2[Prometheus Server 2] P3[Prometheus Server 3] LB --\u003e P1 LB --\u003e P2 LB --\u003e P3 AM1[AlertManager Cluster] AM2[AlertManager Cluster] P1 --\u003e AM1 P2 --\u003e AM1 P3 --\u003e AM2 G[Grafana Visualization] AM1 --\u003e G AM2 --\u003e G 环境准备 # 系统要求 # 硬件要求 # 环境类型 CPU 内存 存储 网络 说明 开发环境 2 核 4GB 50GB 1Gbps 小规模测试 测试环境 4 核 8GB 200GB 1Gbps 中等规模测试 生产环境 8 核 16GB+ 1TB+ 10Gbps 大规模生产 大型企业 16 核 32GB+ 5TB+ 10Gbps 超大规模部署 软件要求 # 组件 最低版本 推荐版本 说明 操作系统 CentOS 7.6 CentOS 8+ / Ubuntu 20.04+ 64位系统 Prometheus 2.30.0 2.45.0+ 监控服务器 AlertManager 0.24.0 0.26.0+ 告警管理器 Node Exporter 1.3.0 1.6.0+ 系统监控 Grafana 8.0.0 10.0.0+ 可视化面板 网络端口规划 # 端口 服务 协议 说明 9090 Prometheus TCP Web UI 和 API 9093 AlertManager TCP 告警管理界面 9094 AlertManager TCP 集群通信端口 9100 Node Exporter TCP 系统指标收集 9115 Blackbox Exporter TCP 黑盒监控 3000 Grafana TCP 可视化界面 环境检查脚本 # cat \u0026gt; check-prometheus-env.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== Prometheus 环境检查脚本 ===\u0026#34; echo \u0026#34;检查时间: $(date)\u0026#34; echo # 检查操作系统 echo \u0026#34;=== 系统信息 ===\u0026#34; cat /etc/redhat-release 2\u0026gt;/dev/null || lsb_release -a 2\u0026gt;/dev/null uname -a echo # 检查内存 echo \u0026#34;=== 内存信息 ===\u0026#34; free -h TOTAL_MEM=$(free -m | awk \u0026#39;NR==2{printf \u0026#34;%.0f\u0026#34;, $2}\u0026#39;) if [ $TOTAL_MEM -lt 4096 ]; then echo \u0026#34;⚠ 警告: 内存不足 4GB，可能影响 Prometheus 性能\u0026#34; else echo \u0026#34;✓ 内存充足\u0026#34; fi echo # 检查磁盘空间 echo \u0026#34;=== 磁盘空间 ===\u0026#34; df -h DISK_USAGE=$(df / | awk \u0026#39;NR==2 {print $5}\u0026#39; | sed \u0026#39;s/%//\u0026#39;) if [ $DISK_USAGE -gt 80 ]; then echo \u0026#34;⚠ 警告: 磁盘使用率超过 80%\u0026#34; else echo \u0026#34;✓ 磁盘空间充足\u0026#34; fi echo # 检查网络端口 echo \u0026#34;=== 端口检查 ===\u0026#34; for port in 9090 9093 9100 3000; do if netstat -tlnp | grep :$port \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;⚠ 端口 $port 已被占用\u0026#34; netstat -tlnp | grep :$port else echo \u0026#34;✓ 端口 $port 可用\u0026#34; fi done echo # 检查时间同步 echo \u0026#34;=== 时间同步检查 ===\u0026#34; if command -v chrony \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then chrony sources -v echo \u0026#34;✓ Chrony 时间同步服务运行正常\u0026#34; elif command -v ntpq \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then ntpq -p echo \u0026#34;✓ NTP 时间同步服务运行正常\u0026#34; else echo \u0026#34;⚠ 未检测到时间同步服务\u0026#34; fi echo # 检查防火墙状态 echo \u0026#34;=== 防火墙状态 ===\u0026#34; if systemctl is-active --quiet firewalld; then echo \u0026#34;防火墙状态: 启用\u0026#34; firewall-cmd --list-ports elif systemctl is-active --quiet iptables; then echo \u0026#34;防火墙状态: iptables 启用\u0026#34; else echo \u0026#34;防火墙状态: 禁用\u0026#34; fi echo echo \u0026#34;=== 环境检查完成 ===\u0026#34; EOF chmod +x check-prometheus-env.sh ./check-prometheus-env.sh 系统优化配置 # 内核参数优化 # # 创建系统优化脚本 cat \u0026gt; optimize-system.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 系统优化配置 ===\u0026#34; # 优化文件描述符限制 cat \u0026gt;\u0026gt; /etc/security/limits.conf \u0026lt;\u0026lt; \u0026#39;LIMITS\u0026#39; prometheus soft nofile 65536 prometheus hard nofile 65536 alertmanager soft nofile 65536 alertmanager hard nofile 65536 LIMITS # 优化内核参数 cat \u0026gt;\u0026gt; /etc/sysctl.conf \u0026lt;\u0026lt; \u0026#39;SYSCTL\u0026#39; # Prometheus 优化参数 vm.max_map_count = 262144 vm.swappiness = 1 net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535 net.core.netdev_max_backlog = 5000 net.ipv4.tcp_fin_timeout = 30 net.ipv4.tcp_keepalive_time = 1200 net.ipv4.tcp_keepalive_probes = 3 net.ipv4.tcp_keepalive_intvl = 15 SYSCTL # 应用内核参数 sysctl -p # 禁用 SELinux（可选） if getenforce | grep -q \u0026#34;Enforcing\u0026#34;; then echo \u0026#34;禁用 SELinux...\u0026#34; setenforce 0 sed -i \u0026#39;s/SELINUX=enforcing/SELINUX=disabled/g\u0026#39; /etc/selinux/config fi # 配置时间同步 if ! systemctl is-active --quiet chronyd; then echo \u0026#34;配置时间同步...\u0026#34; yum install -y chrony systemctl enable chronyd systemctl start chronyd fi echo \u0026#34;✓ 系统优化完成\u0026#34; EOF chmod +x optimize-system.sh ./optimize-system.sh Prometheus 部署实施 # 方案一：二进制部署（推荐） # 步骤 1：下载和安装 Prometheus # # 创建安装脚本 cat \u0026gt; install-prometheus.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash PROMETHEUS_VERSION=\u0026#34;2.45.0\u0026#34; ALERTMANAGER_VERSION=\u0026#34;0.26.0\u0026#34; NODE_EXPORTER_VERSION=\u0026#34;1.6.0\u0026#34; BLACKBOX_EXPORTER_VERSION=\u0026#34;0.24.0\u0026#34; INSTALL_DIR=\u0026#34;/usr/local\u0026#34; CONFIG_DIR=\u0026#34;/etc/prometheus\u0026#34; DATA_DIR=\u0026#34;/var/lib/prometheus\u0026#34; LOG_DIR=\u0026#34;/var/log/prometheus\u0026#34; echo \u0026#34;=== Prometheus 安装脚本 ===\u0026#34; # 创建用户和目录 create_users_and_dirs() { echo \u0026#34;创建用户和目录...\u0026#34; # 创建用户 useradd --no-create-home --shell /bin/false prometheus useradd --no-create-home --shell /bin/false alertmanager useradd --no-create-home --shell /bin/false node_exporter # 创建目录 mkdir -p $CONFIG_DIR/{rules,targets,alertmanager,templates} mkdir -p $DATA_DIR/{prometheus,alertmanager} mkdir -p $LOG_DIR # 设置权限 chown -R prometheus:prometheus $CONFIG_DIR chown -R prometheus:prometheus $DATA_DIR/prometheus chown -R alertmanager:alertmanager $CONFIG_DIR/alertmanager chown -R alertmanager:alertmanager $DATA_DIR/alertmanager chown -R prometheus:prometheus $LOG_DIR } # 下载和安装 Prometheus install_prometheus() { echo \u0026#34;安装 Prometheus $PROMETHEUS_VERSION...\u0026#34; cd /tmp wget https://github.com/prometheus/prometheus/releases/download/v$PROMETHEUS_VERSION/prometheus-$PROMETHEUS_VERSION.linux-amd64.tar.gz tar xf prometheus-$PROMETHEUS_VERSION.linux-amd64.tar.gz # 复制二进制文件 cp prometheus-$PROMETHEUS_VERSION.linux-amd64/prometheus $INSTALL_DIR/bin/ cp prometheus-$PROMETHEUS_VERSION.linux-amd64/promtool $INSTALL_DIR/bin/ # 复制配置文件和模板 cp prometheus-$PROMETHEUS_VERSION.linux-amd64/prometheus.yml $CONFIG_DIR/ cp -r prometheus-$PROMETHEUS_VERSION.linux-amd64/consoles $CONFIG_DIR/ cp -r prometheus-$PROMETHEUS_VERSION.linux-amd64/console_libraries $CONFIG_DIR/ # 设置权限 chmod +x $INSTALL_DIR/bin/prometheus chmod +x $INSTALL_DIR/bin/promtool chown -R prometheus:prometheus $CONFIG_DIR # 清理 rm -rf prometheus-$PROMETHEUS_VERSION.linux-amd64* } # 下载和安装 AlertManager install_alertmanager() { echo \u0026#34;安装 AlertManager $ALERTMANAGER_VERSION...\u0026#34; cd /tmp wget https://github.com/prometheus/alertmanager/releases/download/v$ALERTMANAGER_VERSION/alertmanager-$ALERTMANAGER_VERSION.linux-amd64.tar.gz tar xf alertmanager-$ALERTMANAGER_VERSION.linux-amd64.tar.gz # 复制二进制文件 cp alertmanager-$ALERTMANAGER_VERSION.linux-amd64/alertmanager $INSTALL_DIR/bin/ cp alertmanager-$ALERTMANAGER_VERSION.linux-amd64/amtool $INSTALL_DIR/bin/ # 设置权限 chmod +x $INSTALL_DIR/bin/alertmanager chmod +x $INSTALL_DIR/bin/amtool # 清理 rm -rf alertmanager-$ALERTMANAGER_VERSION.linux-amd64* } # 下载和安装 Node Exporter install_node_exporter() { echo \u0026#34;安装 Node Exporter $NODE_EXPORTER_VERSION...\u0026#34; cd /tmp wget https://github.com/prometheus/node_exporter/releases/download/v$NODE_EXPORTER_VERSION/node_exporter-$NODE_EXPORTER_VERSION.linux-amd64.tar.gz tar xf node_exporter-$NODE_EXPORTER_VERSION.linux-amd64.tar.gz # 复制二进制文件 cp node_exporter-$NODE_EXPORTER_VERSION.linux-amd64/node_exporter $INSTALL_DIR/bin/ # 设置权限 chmod +x $INSTALL_DIR/bin/node_exporter # 创建文本收集器目录 mkdir -p /var/lib/node_exporter/textfile_collector chown -R node_exporter:node_exporter /var/lib/node_exporter # 清理 rm -rf node_exporter-$NODE_EXPORTER_VERSION.linux-amd64* } # 下载和安装 Blackbox Exporter install_blackbox_exporter() { echo \u0026#34;安装 Blackbox Exporter $BLACKBOX_EXPORTER_VERSION...\u0026#34; cd /tmp wget https://github.com/prometheus/blackbox_exporter/releases/download/v$BLACKBOX_EXPORTER_VERSION/blackbox_exporter-$BLACKBOX_EXPORTER_VERSION.linux-amd64.tar.gz tar xf blackbox_exporter-$BLACKBOX_EXPORTER_VERSION.linux-amd64.tar.gz # 复制二进制文件 cp blackbox_exporter-$BLACKBOX_EXPORTER_VERSION.linux-amd64/blackbox_exporter $INSTALL_DIR/bin/ cp blackbox_exporter-$BLACKBOX_EXPORTER_VERSION.linux-amd64/blackbox.yml $CONFIG_DIR/ # 设置权限 chmod +x $INSTALL_DIR/bin/blackbox_exporter chown prometheus:prometheus $CONFIG_DIR/blackbox.yml # 清理 rm -rf blackbox_exporter-$BLACKBOX_EXPORTER_VERSION.linux-amd64* } # 验证安装 verify_installation() { echo \u0026#34;验证安装...\u0026#34; for binary in prometheus promtool alertmanager amtool node_exporter blackbox_exporter; do if command -v $binary \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;✓ $binary 安装成功\u0026#34; $binary --version | head -1 else echo \u0026#34;✗ $binary 安装失败\u0026#34; fi done } # 主安装流程 main() { create_users_and_dirs install_prometheus install_alertmanager install_node_exporter install_blackbox_exporter verify_installation echo \u0026#34;✓ Prometheus 监控组件安装完成\u0026#34; echo \u0026#34;配置目录: $CONFIG_DIR\u0026#34; echo \u0026#34;数据目录: $DATA_DIR\u0026#34; echo \u0026#34;日志目录: $LOG_DIR\u0026#34; } main \u0026#34;$@\u0026#34; EOF chmod +x install-prometheus.sh ./install-prometheus.sh 步骤 2：配置环境变量 # # 添加到系统 PATH cat \u0026gt;\u0026gt; /etc/profile \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Prometheus 环境变量 export PATH=/usr/local/bin:$PATH export PROMETHEUS_CONFIG_DIR=/etc/prometheus export PROMETHEUS_DATA_DIR=/var/lib/prometheus EOF # 重新加载环境变量 source /etc/profile # 验证安装 prometheus --version alertmanager --version node_exporter --version 步骤 3：配置 Prometheus # 创建生产级配置文件 # cat \u0026gt; /etc/prometheus/prometheus.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Prometheus 全局配置 global: scrape_interval: 15s # 默认抓取间隔 evaluation_interval: 15s # 规则评估间隔 scrape_timeout: 10s # 抓取超时时间 # 外部标签（用于联邦和远程存储） external_labels: cluster: \u0026#39;production\u0026#39; region: \u0026#39;cn-north-1\u0026#39; datacenter: \u0026#39;dc1\u0026#39; environment: \u0026#39;prod\u0026#39; # 告警管理器配置 alerting: alertmanagers: - static_configs: - targets: - localhost:9093 timeout: 10s api_version: v2 path_prefix: / # 规则文件配置 rule_files: - \u0026#34;rules/*.yml\u0026#34; # 远程写入配置（可选） # remote_write: # - url: \u0026#34;http://remote-storage:9201/write\u0026#34; # queue_config: # max_samples_per_send: 1000 # max_shards: 200 # capacity: 2500 # 远程读取配置（可选） # remote_read: # - url: \u0026#34;http://remote-storage:9201/read\u0026#34; # 抓取配置 scrape_configs: # Prometheus 自监控 - job_name: \u0026#39;prometheus\u0026#39; static_configs: - targets: [\u0026#39;localhost:9090\u0026#39;] labels: service: \u0026#39;prometheus\u0026#39; team: \u0026#39;infrastructure\u0026#39; scrape_interval: 15s metrics_path: /metrics # AlertManager 监控 - job_name: \u0026#39;alertmanager\u0026#39; static_configs: - targets: [\u0026#39;localhost:9093\u0026#39;] labels: service: \u0026#39;alertmanager\u0026#39; team: \u0026#39;infrastructure\u0026#39; scrape_interval: 15s # Node Exporter 监控（文件服务发现） - job_name: \u0026#39;node-exporter\u0026#39; file_sd_configs: - files: - \u0026#39;targets/nodes/*.json\u0026#39; refresh_interval: 30s scrape_interval: 30s metrics_path: /metrics # 指标重新标记 metric_relabel_configs: # 删除不需要的指标 - source_labels: [__name__] regex: \u0026#39;node_scrape_collector_.*\u0026#39; action: drop - source_labels: [__name__] regex: \u0026#39;node_textfile_scrape_error\u0026#39; action: drop # 黑盒监控 - job_name: \u0026#39;blackbox-http\u0026#39; metrics_path: /probe params: module: [http_2xx] file_sd_configs: - files: - \u0026#39;targets/blackbox/*.json\u0026#39; refresh_interval: 30s relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: localhost:9115 # 数据库监控 - job_name: \u0026#39;mysql-exporter\u0026#39; file_sd_configs: - files: - \u0026#39;targets/mysql/*.json\u0026#39; refresh_interval: 30s scrape_interval: 30s - job_name: \u0026#39;redis-exporter\u0026#39; file_sd_configs: - files: - \u0026#39;targets/redis/*.json\u0026#39; refresh_interval: 30s scrape_interval: 30s # 应用监控 - job_name: \u0026#39;application-metrics\u0026#39; file_sd_configs: - files: - \u0026#39;targets/applications/*.json\u0026#39; refresh_interval: 30s scrape_interval: 15s metrics_path: /metrics # Kubernetes 监控（如果适用） # - job_name: \u0026#39;kubernetes-pods\u0026#39; # kubernetes_sd_configs: # - role: pod # relabel_configs: # - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] # action: keep # regex: true EOF # 设置配置文件权限 chown prometheus:prometheus /etc/prometheus/prometheus.yml chmod 640 /etc/prometheus/prometheus.yml 创建服务发现目标文件 # # 创建节点监控目标 mkdir -p /etc/prometheus/targets/{nodes,blackbox,mysql,redis,applications} # 节点监控配置 cat \u0026gt; /etc/prometheus/targets/nodes/production.json \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [ { \u0026#34;targets\u0026#34;: [ \u0026#34;localhost:9100\u0026#34;, \u0026#34;web-server-01:9100\u0026#34;, \u0026#34;web-server-02:9100\u0026#34;, \u0026#34;db-server-01:9100\u0026#34; ], \u0026#34;labels\u0026#34;: { \u0026#34;environment\u0026#34;: \u0026#34;production\u0026#34;, \u0026#34;datacenter\u0026#34;: \u0026#34;dc1\u0026#34;, \u0026#34;team\u0026#34;: \u0026#34;infrastructure\u0026#34;, \u0026#34;service\u0026#34;: \u0026#34;system\u0026#34; } } ] EOF # 黑盒监控配置 cat \u0026gt; /etc/prometheus/targets/blackbox/websites.json \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [ { \u0026#34;targets\u0026#34;: [ \u0026#34;https://www.example.com\u0026#34;, \u0026#34;https://api.example.com\u0026#34;, \u0026#34;http://internal-service:8080/health\u0026#34; ], \u0026#34;labels\u0026#34;: { \u0026#34;environment\u0026#34;: \u0026#34;production\u0026#34;, \u0026#34;monitor_type\u0026#34;: \u0026#34;http\u0026#34;, \u0026#34;team\u0026#34;: \u0026#34;sre\u0026#34; } } ] EOF # 设置权限 chown -R prometheus:prometheus /etc/prometheus/targets/ 验证配置文件 # # 检查配置文件语法 promtool check config /etc/prometheus/prometheus.yml # 预期输出： # Checking /etc/prometheus/prometheus.yml # SUCCESS: 0 rule files found # 检查服务发现配置 promtool query instant \u0026#39;up\u0026#39; --config.file=/etc/prometheus/prometheus.yml 步骤 4：配置系统服务 # 创建 Prometheus 系统服务 # cat \u0026gt; /etc/systemd/system/prometheus.service \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Unit] Description=Prometheus Server Documentation=https://prometheus.io/docs/ After=network-online.target Wants=network-online.target [Service] Type=simple User=prometheus Group=prometheus ExecReload=/bin/kill -HUP $MAINPID ExecStart=/usr/local/bin/prometheus \\ --config.file=/etc/prometheus/prometheus.yml \\ --storage.tsdb.path=/var/lib/prometheus \\ --storage.tsdb.retention.time=30d \\ --storage.tsdb.retention.size=50GB \\ --storage.tsdb.wal-compression \\ --web.console.templates=/etc/prometheus/consoles \\ --web.console.libraries=/etc/prometheus/console_libraries \\ --web.listen-address=0.0.0.0:9090 \\ --web.external-url=http://localhost:9090 \\ --web.enable-lifecycle \\ --web.enable-admin-api \\ --web.max-connections=512 \\ --query.max-concurrency=20 \\ --query.timeout=2m \\ --log.level=info \\ --log.format=logfmt SyslogIdentifier=prometheus Restart=always RestartSec=5 LimitNOFILE=65536 LimitNPROC=65536 # 安全配置 NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/var/lib/prometheus [Install] WantedBy=multi-user.target EOF 创建 Node Exporter 系统服务 # cat \u0026gt; /etc/systemd/system/node_exporter.service \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Unit] Description=Node Exporter Documentation=https://prometheus.io/docs/guides/node-exporter/ After=network-online.target Wants=network-online.target [Service] Type=simple User=node_exporter Group=node_exporter ExecStart=/usr/local/bin/node_exporter \\ --web.listen-address=0.0.0.0:9100 \\ --path.procfs=/proc \\ --path.sysfs=/sys \\ --collector.filesystem.ignored-mount-points=\u0026#34;^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)\u0026#34; \\ --collector.filesystem.ignored-fs-types=\u0026#34;^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$$\u0026#34; \\ --collector.textfile.directory=/var/lib/node_exporter/textfile_collector \\ --collector.systemd \\ --collector.systemd.unit-whitelist=\u0026#34;(sshd|nginx|docker|mysql|redis|postgresql)\\.service\u0026#34; \\ --collector.processes \\ --collector.tcpstat \\ --log.level=info SyslogIdentifier=node_exporter Restart=always RestartSec=5 LimitNOFILE=65536 # 安全配置 NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/var/lib/node_exporter [Install] WantedBy=multi-user.target EOF 创建 Blackbox Exporter 系统服务 # cat \u0026gt; /etc/systemd/system/blackbox_exporter.service \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Unit] Description=Blackbox Exporter Documentation=https://github.com/prometheus/blackbox_exporter After=network-online.target Wants=network-online.target [Service] Type=simple User=prometheus Group=prometheus ExecStart=/usr/local/bin/blackbox_exporter \\ --config.file=/etc/prometheus/blackbox.yml \\ --web.listen-address=0.0.0.0:9115 \\ --log.level=info SyslogIdentifier=blackbox_exporter Restart=always RestartSec=5 LimitNOFILE=65536 # 安全配置 NoNewPrivileges=true ProtectSystem=strict ProtectHome=true [Install] WantedBy=multi-user.target EOF 启动和验证服务 # # 创建服务启动脚本 cat \u0026gt; start-prometheus-services.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 启动 Prometheus 监控服务 ===\u0026#34; # 重新加载 systemd 配置 systemctl daemon-reload # 启动服务 services=(\u0026#34;node_exporter\u0026#34; \u0026#34;blackbox_exporter\u0026#34; \u0026#34;prometheus\u0026#34;) for service in \u0026#34;${services[@]}\u0026#34;; do echo \u0026#34;启动 $service...\u0026#34; systemctl start $service systemctl enable $service # 检查服务状态 if systemctl is-active --quiet $service; then echo \u0026#34;✓ $service 启动成功\u0026#34; else echo \u0026#34;✗ $service 启动失败\u0026#34; systemctl status $service fi done # 验证端口监听 echo -e \u0026#34;\\n=== 端口监听检查 ===\u0026#34; for port in 9090 9100 9115; do if netstat -tlnp | grep :$port \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;✓ 端口 $port 监听正常\u0026#34; else echo \u0026#34;✗ 端口 $port 未监听\u0026#34; fi done # 测试 Web 界面 echo -e \u0026#34;\\n=== Web 界面测试 ===\u0026#34; for url in \u0026#34;http://localhost:9090\u0026#34; \u0026#34;http://localhost:9100/metrics\u0026#34; \u0026#34;http://localhost:9115\u0026#34;; do if curl -s -o /dev/null -w \u0026#34;%{http_code}\u0026#34; $url | grep -q \u0026#34;200\u0026#34;; then echo \u0026#34;✓ $url 响应正常\u0026#34; else echo \u0026#34;✗ $url 响应异常\u0026#34; fi done echo -e \u0026#34;\\n=== 服务启动完成 ===\u0026#34; echo \u0026#34;Prometheus Web UI: http://localhost:9090\u0026#34; echo \u0026#34;Node Exporter: http://localhost:9100/metrics\u0026#34; echo \u0026#34;Blackbox Exporter: http://localhost:9115\u0026#34; EOF chmod +x start-prometheus-services.sh ./start-prometheus-services.sh 配置日志轮转 # # 创建日志轮转配置 cat \u0026gt; /etc/logrotate.d/prometheus \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; /var/log/prometheus/*.log { daily missingok rotate 30 compress delaycompress notifempty create 0644 prometheus prometheus postrotate systemctl reload prometheus systemctl reload alertmanager endscript } EOF 启动参数说明:\n--storage.tsdb.retention.time=30d: 数据保留 30 天 --storage.tsdb.retention.size=50GB: 最大存储 50GB --storage.tsdb.wal-compression: 启用 WAL 压缩 --web.enable-lifecycle: 启用热重载功能 --web.enable-admin-api: 启用管理 API --web.max-connections=512: 最大连接数 --query.max-concurrency=20: 最大并发查询数 --query.timeout=2m: 查询超时时间 第七步：配置防火墙和安全 # 防火墙配置 # # 开放 Prometheus 端口 firewall-cmd --permanent --add-port=9090/tcp firewall-cmd --reload # 验证防火墙规则 firewall-cmd --list-ports 配置 Nginx 反向代理（推荐） # 为了增强安全性，建议使用 Nginx 作为反向代理：\n# 安装 httpd-tools（用于创建认证文件） yum install -y httpd-tools # 创建认证文件 htpasswd -c /etc/prometheus/.htpasswd admin # 创建 Nginx 配置 cat \u0026gt; /etc/nginx/conf.d/prometheus.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; upstream prometheus { server 127.0.0.1:9090; } server { listen 80; server_name prometheus.example.com; # 重定向到 HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name prometheus.example.com; # SSL 配置 ssl_certificate /etc/ssl/certs/prometheus.crt; ssl_certificate_key /etc/ssl/private/prometheus.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; # 访问日志 access_log /var/log/nginx/prometheus.access.log; error_log /var/log/nginx/prometheus.error.log; location / { auth_basic \u0026#34;Prometheus\u0026#34;; auth_basic_user_file /etc/prometheus/.htpasswd; proxy_pass http://prometheus; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket 支持 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; } # API 接口（可选择性开放） location /api/ { auth_basic \u0026#34;Prometheus API\u0026#34;; auth_basic_user_file /etc/prometheus/.htpasswd; proxy_pass http://prometheus; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } EOF # 测试 Nginx 配置 nginx -t # 重新加载 Nginx nginx -s reload 第八步：配置监控规则 # 创建告警规则 # cat \u0026gt; /etc/prometheus/rules/node_alerts.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; groups: - name: node_alerts interval: 30s rules: # 实例下线告警 - alert: InstanceDown expr: up == 0 for: 1m labels: severity: critical annotations: summary: \u0026#34;实例 {{ $labels.instance }} 已下线\u0026#34; description: \u0026#34;实例 {{ $labels.instance }} 已经下线超过 1 分钟\u0026#34; # CPU 使用率告警 - alert: HighCPUUsage expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) * 100) \u0026gt; 80 for: 5m labels: severity: warning annotations: summary: \u0026#34;高 CPU 使用率\u0026#34; description: \u0026#34;实例 {{ $labels.instance }} CPU 使用率超过 80%，当前值: {{ $value }}%\u0026#34; # 内存使用率告警 - alert: HighMemoryUsage expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 \u0026gt; 85 for: 5m labels: severity: warning annotations: summary: \u0026#34;高内存使用率\u0026#34; description: \u0026#34;实例 {{ $labels.instance }} 内存使用率超过 85%，当前值: {{ $value }}%\u0026#34; # 磁盘使用率告警 - alert: HighDiskUsage expr: (1 - (node_filesystem_avail_bytes{fstype!=\u0026#34;tmpfs\u0026#34;} / node_filesystem_size_bytes{fstype!=\u0026#34;tmpfs\u0026#34;})) * 100 \u0026gt; 85 for: 5m labels: severity: warning annotations: summary: \u0026#34;高磁盘使用率\u0026#34; description: \u0026#34;实例 {{ $labels.instance }} 磁盘 {{ $labels.mountpoint }} 使用率超过 85%，当前值: {{ $value }}%\u0026#34; EOF # 验证规则文件 /usr/local/prometheus/promtool check rules /etc/prometheus/rules/node_alerts.yml # 重新加载 Prometheus 配置 systemctl reload prometheus 创建记录规则 # cat \u0026gt; /etc/prometheus/rules/recording_rules.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; groups: - name: recording_rules interval: 30s rules: # CPU 使用率记录规则 - record: instance:node_cpu_utilization:rate5m expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) * 100) labels: metric_type: utilization # 内存使用率记录规则 - record: instance:node_memory_utilization:ratio expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 labels: metric_type: utilization # 磁盘使用率记录规则 - record: instance:node_filesystem_utilization:ratio expr: (1 - (node_filesystem_avail_bytes{fstype!=\u0026#34;tmpfs\u0026#34;} / node_filesystem_size_bytes{fstype!=\u0026#34;tmpfs\u0026#34;})) * 100 labels: metric_type: utilization # 网络流量记录规则 - record: instance:node_network_receive_bytes:rate5m expr: rate(node_network_receive_bytes_total[5m]) labels: metric_type: traffic - record: instance:node_network_transmit_bytes:rate5m expr: rate(node_network_transmit_bytes_total[5m]) labels: metric_type: traffic EOF AlertManager 告警管理器部署 # AlertManager 是 Prometheus 生态系统中的告警管理组件，负责处理由 Prometheus 发送的告警，并将其路由到正确的接收器。\n第一步：安装 AlertManager # 下载和安装 # # 下载 AlertManager cd /tmp wget https://github.com/prometheus/alertmanager/releases/download/v0.24.0/alertmanager-0.24.0.linux-amd64.tar.gz # 解压安装包 tar xf alertmanager-0.24.0.linux-amd64.tar.gz # 复制二进制文件 cp alertmanager-0.24.0.linux-amd64/alertmanager /usr/local/bin/ cp alertmanager-0.24.0.linux-amd64/amtool /usr/local/bin/ # 设置执行权限 chmod +x /usr/local/bin/alertmanager chmod +x /usr/local/bin/amtool # 验证安装 alertmanager --version 创建用户和目录 # # 创建 alertmanager 用户 useradd --no-create-home --shell /bin/false alertmanager # 创建配置和数据目录 mkdir -p /etc/alertmanager mkdir -p /var/lib/alertmanager # 设置目录权限 chown alertmanager:alertmanager /etc/alertmanager chown alertmanager:alertmanager /var/lib/alertmanager 第二步：配置 AlertManager # 创建基础配置文件 # cat \u0026gt; /etc/alertmanager/alertmanager.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # AlertManager 全局配置 global: # SMTP 配置 smtp_smarthost: \u0026#39;smtp.example.com:587\u0026#39; smtp_from: \u0026#39;alerts@example.com\u0026#39; smtp_auth_username: \u0026#39;alerts@example.com\u0026#39; smtp_auth_password: \u0026#39;your-email-password\u0026#39; smtp_require_tls: true # 解决告警的超时时间 resolve_timeout: 5m # 模板配置 templates: - \u0026#39;/etc/alertmanager/templates/*.tmpl\u0026#39; # 路由配置 route: # 默认接收器 receiver: \u0026#39;default\u0026#39; # 分组配置 group_by: [\u0026#39;alertname\u0026#39;, \u0026#39;cluster\u0026#39;, \u0026#39;service\u0026#39;] group_wait: 10s # 等待时间 group_interval: 10s # 分组间隔 repeat_interval: 1h # 重复间隔 # 子路由 routes: # 严重告警立即发送 - match: severity: critical receiver: \u0026#39;critical-alerts\u0026#39; group_wait: 0s repeat_interval: 5m # 数据库相关告警 - match_re: service: ^(mysql|postgresql|redis)$ receiver: \u0026#39;database-team\u0026#39; # 网络相关告警 - match: alertname: NetworkDown receiver: \u0026#39;network-team\u0026#39; # 抑制规则 inhibit_rules: # 如果实例下线，抑制其他相关告警 - source_match: alertname: \u0026#39;InstanceDown\u0026#39; target_match_re: alertname: \u0026#39;^(HighCPUUsage|HighMemoryUsage|HighDiskUsage)$\u0026#39; equal: [\u0026#39;instance\u0026#39;] # 接收器配置 receivers: # 默认接收器 - name: \u0026#39;default\u0026#39; email_configs: - to: \u0026#39;admin@example.com\u0026#39; subject: \u0026#39;[ALERT] {{ .GroupLabels.alertname }}\u0026#39; body: | {{ range .Alerts }} 告警名称: {{ .Annotations.summary }} 告警详情: {{ .Annotations.description }} 告警时间: {{ .StartsAt.Format \u0026#34;2006-01-02 15:04:05\u0026#34; }} 告警标签: {{ range .Labels.SortedPairs }}{{ .Name }}={{ .Value }} {{ end }} {{ end }} # 严重告警接收器 - name: \u0026#39;critical-alerts\u0026#39; email_configs: - to: \u0026#39;oncall@example.com\u0026#39; subject: \u0026#39;[CRITICAL] {{ .GroupLabels.alertname }}\u0026#39; body: | 🚨 严重告警 🚨 {{ range .Alerts }} 告警名称: {{ .Annotations.summary }} 告警详情: {{ .Annotations.description }} 告警时间: {{ .StartsAt.Format \u0026#34;2006-01-02 15:04:05\u0026#34; }} 告警级别: {{ .Labels.severity }} 影响实例: {{ .Labels.instance }} {{ end }} # 短信通知（需要配置短信网关） webhook_configs: - url: \u0026#39;http://localhost:8080/sms-webhook\u0026#39; send_resolved: true # 数据库团队接收器 - name: \u0026#39;database-team\u0026#39; email_configs: - to: \u0026#39;dba@example.com\u0026#39; subject: \u0026#39;[DB-ALERT] {{ .GroupLabels.alertname }}\u0026#39; # 网络团队接收器 - name: \u0026#39;network-team\u0026#39; email_configs: - to: \u0026#39;network@example.com\u0026#39; subject: \u0026#39;[NET-ALERT] {{ .GroupLabels.alertname }}\u0026#39; EOF # 设置配置文件权限 chown alertmanager:alertmanager /etc/alertmanager/alertmanager.yml chmod 640 /etc/alertmanager/alertmanager.yml 创建告警模板 # # 创建模板目录 mkdir -p /etc/alertmanager/templates # 创建邮件模板 cat \u0026gt; /etc/alertmanager/templates/email.tmpl \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; {{ define \u0026#34;email.default.subject\u0026#34; }} [{{ .Status | toUpper }}{{ if eq .Status \u0026#34;firing\u0026#34; }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join \u0026#34; \u0026#34; }} {{ end }} {{ define \u0026#34;email.default.html\u0026#34; }} \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Prometheus Alert\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; body { font-family: Arial, sans-serif; } .alert { margin: 10px 0; padding: 10px; border-left: 4px solid; } .firing { border-color: #d32f2f; background-color: #ffebee; } .resolved { border-color: #388e3c; background-color: #e8f5e8; } .label { font-weight: bold; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h2\u0026gt;Prometheus 告警通知\u0026lt;/h2\u0026gt; {{ if gt (len .Alerts.Firing) 0 }} \u0026lt;h3\u0026gt;🔥 触发的告警 ({{ .Alerts.Firing | len }})\u0026lt;/h3\u0026gt; {{ range .Alerts.Firing }} \u0026lt;div class=\u0026#34;alert firing\u0026#34;\u0026gt; \u0026lt;p\u0026gt;\u0026lt;span class=\u0026#34;label\u0026#34;\u0026gt;告警名称:\u0026lt;/span\u0026gt; {{ .Annotations.summary }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;span class=\u0026#34;label\u0026#34;\u0026gt;告警详情:\u0026lt;/span\u0026gt; {{ .Annotations.description }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;span class=\u0026#34;label\u0026#34;\u0026gt;告警时间:\u0026lt;/span\u0026gt; {{ .StartsAt.Format \u0026#34;2006-01-02 15:04:05\u0026#34; }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;span class=\u0026#34;label\u0026#34;\u0026gt;告警标签:\u0026lt;/span\u0026gt; {{ range .Labels.SortedPairs }}{{ .Name }}={{ .Value }} {{ end }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; {{ end }} {{ end }} {{ if gt (len .Alerts.Resolved) 0 }} \u0026lt;h3\u0026gt;✅ 已解决的告警 ({{ .Alerts.Resolved | len }})\u0026lt;/h3\u0026gt; {{ range .Alerts.Resolved }} \u0026lt;div class=\u0026#34;alert resolved\u0026#34;\u0026gt; \u0026lt;p\u0026gt;\u0026lt;span class=\u0026#34;label\u0026#34;\u0026gt;告警名称:\u0026lt;/span\u0026gt; {{ .Annotations.summary }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;span class=\u0026#34;label\u0026#34;\u0026gt;解决时间:\u0026lt;/span\u0026gt; {{ .EndsAt.Format \u0026#34;2006-01-02 15:04:05\u0026#34; }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;span class=\u0026#34;label\u0026#34;\u0026gt;持续时间:\u0026lt;/span\u0026gt; {{ .EndsAt.Sub .StartsAt }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; {{ end }} {{ end }} \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; {{ end }} EOF # 设置模板权限 chown alertmanager:alertmanager /etc/alertmanager/templates/email.tmpl 第三步：配置防火墙 # # 开放 AlertManager 端口 firewall-cmd --permanent --add-port=9093/tcp firewall-cmd --reload # 验证防火墙规则 firewall-cmd --list-ports 第四步：创建系统服务 # cat \u0026gt; /etc/systemd/system/alertmanager.service \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Unit] Description=AlertManager Documentation=https://prometheus.io/docs/alerting/alertmanager/ After=network-online.target Wants=network-online.target [Service] Type=simple User=alertmanager Group=alertmanager ExecReload=/bin/kill -HUP $MAINPID ExecStart=/usr/local/bin/alertmanager \\ --config.file=/etc/alertmanager/alertmanager.yml \\ --storage.path=/var/lib/alertmanager \\ --web.external-url=http://localhost:9093 \\ --web.listen-address=0.0.0.0:9093 \\ --cluster.listen-address=0.0.0.0:9094 \\ --log.level=info \\ --log.format=logfmt SyslogIdentifier=alertmanager Restart=always RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF # 启动服务 systemctl daemon-reload systemctl start alertmanager systemctl enable alertmanager # 检查服务状态 systemctl status alertmanager 第五步：配置 Prometheus 连接 AlertManager # 更新 Prometheus 配置文件：\n# 编辑 Prometheus 配置 vim /etc/prometheus/prometheus.yml # 在 alerting 部分添加 AlertManager 配置 alerting: alertmanagers: - static_configs: - targets: - localhost:9093 timeout: 10s api_version: v2 # 重新加载 Prometheus 配置 systemctl reload prometheus # 验证连接 curl http://localhost:9090/api/v1/alertmanagers 钉钉告警集成 # 钉钉是企业常用的即时通讯工具，通过集成钉钉机器人可以实现实时告警通知。\n第一步：创建钉钉机器人 # 在钉钉群中添加机器人 # 打开钉钉群聊 点击群设置 → 智能群助手 → 添加机器人 选择自定义机器人 配置机器人信息： 机器人名称：Prometheus 告警 安全设置：选择\u0026quot;加签\u0026quot;方式 图：在钉钉群中创建自定义机器人\n图：选择加签验证方式提高安全性\n图：获取机器人的 Webhook 地址和密钥\n记录重要信息 # 创建完成后，请记录以下信息：\nWebhook URL: https://oapi.dingtalk.com/robot/send?access_token=xxx 加签密钥: 用于验证消息来源的密钥 第二步：安装钉钉 Webhook # 下载和安装 # # 下载钉钉 webhook cd /tmp wget https://github.com/timonwong/prometheus-webhook-dingtalk/releases/download/v2.1.0/prometheus-webhook-dingtalk-2.1.0.linux-amd64.tar.gz # 解压安装 tar xf prometheus-webhook-dingtalk-2.1.0.linux-amd64.tar.gz # 创建安装目录 mkdir -p /usr/local/webhook-dingtalk # 复制文件 cp prometheus-webhook-dingtalk-2.1.0.linux-amd64/prometheus-webhook-dingtalk /usr/local/bin/ cp prometheus-webhook-dingtalk-2.1.0.linux-amd64/config.example.yml /etc/webhook-dingtalk/config.yml # 设置执行权限 chmod +x /usr/local/bin/prometheus-webhook-dingtalk 创建配置目录 # # 创建配置目录 mkdir -p /etc/webhook-dingtalk # 创建专用用户 useradd --no-create-home --shell /bin/false webhook-dingtalk 第三步：配置钉钉 Webhook # 创建配置文件 # cat \u0026gt; /etc/webhook-dingtalk/config.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; ## Request timeout timeout: 5s ## Customizable templates path templates: - contrib/templates/legacy/template.tmpl ## You can also override default template using `default_message` ## The following example to use the \u0026#39;legacy\u0026#39; template from v0.3.0 default_message: title: \u0026#39;{{ template \u0026#34;legacy.title\u0026#34; . }}\u0026#39; text: \u0026#39;{{ template \u0026#34;legacy.content\u0026#34; . }}\u0026#39; ## Targets, previously was known as \u0026#34;profiles\u0026#34; targets: webhook1: url: https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN # 如果使用加签方式，需要配置 secret secret: YOUR_SECRET_KEY message: # 使用 markdown 格式 title: \u0026#39;Prometheus 告警通知\u0026#39; text: | ## {{ if eq .Status \u0026#34;firing\u0026#34; }}🔥 告警触发{{ else }}✅ 告警恢复{{ end }} **告警数量**: {{ len .Alerts }} {{ range .Alerts }} ### {{ .Annotations.summary }} **告警详情**: {{ .Annotations.description }} **告警时间**: {{ .StartsAt.Format \u0026#34;2006-01-02 15:04:05\u0026#34; }} **告警级别**: {{ .Labels.severity }} **影响实例**: {{ .Labels.instance }} **告警标签**: {{ range .Labels.SortedPairs }}{{ .Name }}={{ .Value }} {{ end }} --- {{ end }} webhook2: url: https://oapi.dingtalk.com/robot/send?access_token=ANOTHER_ACCESS_TOKEN secret: ANOTHER_SECRET_KEY message: title: \u0026#39;生产环境告警\u0026#39; text: \u0026#39;{{ template \u0026#34;legacy.content\u0026#34; . }}\u0026#39; EOF # 替换配置中的占位符 # 请将 YOUR_ACCESS_TOKEN 和 YOUR_SECRET_KEY 替换为实际值 sed -i \u0026#39;s/YOUR_ACCESS_TOKEN/your-actual-access-token/g\u0026#39; /etc/webhook-dingtalk/config.yml sed -i \u0026#39;s/YOUR_SECRET_KEY/your-actual-secret-key/g\u0026#39; /etc/webhook-dingtalk/config.yml # 设置配置文件权限 chown webhook-dingtalk:webhook-dingtalk /etc/webhook-dingtalk/config.yml chmod 640 /etc/webhook-dingtalk/config.yml 第四步：创建系统服务 # cat \u0026gt; /etc/systemd/system/webhook-dingtalk.service \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Unit] Description=Prometheus Webhook for DingTalk Documentation=https://github.com/timonwong/prometheus-webhook-dingtalk After=network-online.target Wants=network-online.target [Service] Type=simple User=webhook-dingtalk Group=webhook-dingtalk ExecStart=/usr/local/bin/prometheus-webhook-dingtalk \\ --web.listen-address=127.0.0.1:8060 \\ --web.enable-ui \\ --config.file=/etc/webhook-dingtalk/config.yml \\ --log.level=info \\ --log.format=logfmt SyslogIdentifier=webhook-dingtalk Restart=always RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF # 启动服务 systemctl daemon-reload systemctl start webhook-dingtalk systemctl enable webhook-dingtalk # 检查服务状态 systemctl status webhook-dingtalk 第五步：测试钉钉通知 # 测试 Webhook 连通性 # # 测试钉钉 webhook 是否正常工作 curl -X POST http://localhost:8060/dingtalk/webhook1/send \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#39;{ \u0026#34;msgtype\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;text\u0026#34;: { \u0026#34;content\u0026#34;: \u0026#34;Prometheus 钉钉告警测试消息\u0026#34; } }\u0026#39; 测试告警格式 # # 模拟 AlertManager 发送的告警格式 curl -X POST http://localhost:8060/dingtalk/webhook1/send \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#39;{ \u0026#34;receiver\u0026#34;: \u0026#34;webhook1\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;firing\u0026#34;, \u0026#34;alerts\u0026#34;: [ { \u0026#34;status\u0026#34;: \u0026#34;firing\u0026#34;, \u0026#34;labels\u0026#34;: { \u0026#34;alertname\u0026#34;: \u0026#34;HighCPUUsage\u0026#34;, \u0026#34;instance\u0026#34;: \u0026#34;localhost:9100\u0026#34;, \u0026#34;severity\u0026#34;: \u0026#34;warning\u0026#34; }, \u0026#34;annotations\u0026#34;: { \u0026#34;summary\u0026#34;: \u0026#34;CPU 使用率过高\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;CPU 使用率超过 80%\u0026#34; }, \u0026#34;startsAt\u0026#34;: \u0026#34;2023-01-01T12:00:00Z\u0026#34; } ] }\u0026#39; 图：钉钉机器人成功发送测试消息\n第六步：配置 AlertManager 集成钉钉 # 更新 AlertManager 配置 # cat \u0026gt; /etc/alertmanager/alertmanager.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; global: resolve_timeout: 5m # 路由配置 route: receiver: \u0026#39;dingtalk-webhook\u0026#39; group_by: [\u0026#39;alertname\u0026#39;, \u0026#39;cluster\u0026#39;, \u0026#39;service\u0026#39;] group_wait: 10s group_interval: 10s repeat_interval: 1h routes: # 严重告警立即发送到钉钉 - match: severity: critical receiver: \u0026#39;dingtalk-critical\u0026#39; group_wait: 0s repeat_interval: 5m # 普通告警发送到钉钉 - match: severity: warning receiver: \u0026#39;dingtalk-warning\u0026#39; # 接收器配置 receivers: # 默认钉钉接收器 - name: \u0026#39;dingtalk-webhook\u0026#39; webhook_configs: - url: \u0026#39;http://127.0.0.1:8060/dingtalk/webhook1/send\u0026#39; send_resolved: true http_config: timeout: 10s # 严重告警钉钉接收器 - name: \u0026#39;dingtalk-critical\u0026#39; webhook_configs: - url: \u0026#39;http://127.0.0.1:8060/dingtalk/webhook1/send\u0026#39; send_resolved: true title: \u0026#39;🚨 严重告警 🚨\u0026#39; # 警告级别钉钉接收器 - name: \u0026#39;dingtalk-warning\u0026#39; webhook_configs: - url: \u0026#39;http://127.0.0.1:8060/dingtalk/webhook1/send\u0026#39; send_resolved: true title: \u0026#39;⚠️ 警告告警 ⚠️\u0026#39; # 抑制规则 inhibit_rules: - source_match: alertname: \u0026#39;InstanceDown\u0026#39; target_match_re: alertname: \u0026#39;^(HighCPUUsage|HighMemoryUsage)$\u0026#39; equal: [\u0026#39;instance\u0026#39;] EOF # 重新加载 AlertManager 配置 systemctl reload alertmanager # 验证配置 curl http://localhost:9093/api/v1/status Node Exporter 系统监控 # Node Exporter 是 Prometheus 官方提供的系统监控组件，用于收集 Linux 系统的各种指标。\n第一步：安装 Node Exporter # 下载和安装 # # 下载 Node Exporter cd /tmp wget https://github.com/prometheus/node_exporter/releases/download/v1.3.1/node_exporter-1.3.1.linux-amd64.tar.gz # 解压安装包 tar xf node_exporter-1.3.1.linux-amd64.tar.gz # 复制二进制文件 cp node_exporter-1.3.1.linux-amd64/node_exporter /usr/local/bin/ # 设置执行权限 chmod +x /usr/local/bin/node_exporter # 验证安装 node_exporter --version 创建用户和目录 # # 创建 node_exporter 用户 useradd --no-create-home --shell /bin/false node_exporter # 创建文本收集器目录 mkdir -p /var/lib/node_exporter/textfile_collector # 设置目录权限 chown -R node_exporter:node_exporter /var/lib/node_exporter 第二步：配置 Node Exporter # 创建系统服务 # cat \u0026gt; /etc/systemd/system/node_exporter.service \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Unit] Description=Node Exporter Documentation=https://prometheus.io/docs/guides/node-exporter/ After=network-online.target Wants=network-online.target [Service] Type=simple User=node_exporter Group=node_exporter ExecStart=/usr/local/bin/node_exporter \\ --web.listen-address=0.0.0.0:9100 \\ --path.procfs=/proc \\ --path.sysfs=/sys \\ --collector.filesystem.ignored-mount-points=\u0026#34;^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)\u0026#34; \\ --collector.filesystem.ignored-fs-types=\u0026#34;^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$$\u0026#34; \\ --collector.textfile.directory=/var/lib/node_exporter/textfile_collector \\ --collector.systemd \\ --collector.systemd.unit-whitelist=\u0026#34;(sshd|docker|nginx|mysql|redis|postgresql)\\\\.service\u0026#34; \\ --collector.processes \\ --collector.tcpstat \\ --no-collector.mdadm SyslogIdentifier=node_exporter Restart=always RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF 启动参数说明:\n--collector.filesystem.ignored-mount-points: 忽略的挂载点 --collector.filesystem.ignored-fs-types: 忽略的文件系统类型 --collector.textfile.directory: 文本收集器目录 --collector.systemd: 启用 systemd 收集器 --collector.systemd.unit-whitelist: 监控的服务白名单 启动服务 # # 启动 Node Exporter systemctl daemon-reload systemctl start node_exporter systemctl enable node_exporter # 检查服务状态 systemctl status node_exporter # 验证指标收集 curl http://localhost:9100/metrics | head -20 第三步：配置自定义指标收集 # 创建自定义指标文件 # # 创建系统元数据指标 cat \u0026gt; /var/lib/node_exporter/textfile_collector/metadata.prom \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # HELP node_metadata_info System metadata information # TYPE node_metadata_info gauge node_metadata_info{role=\u0026#34;web-server\u0026#34;,datacenter=\u0026#34;dc1\u0026#34;,environment=\u0026#34;production\u0026#34;} 1 EOF # 创建应用状态监控脚本 cat \u0026gt; /usr/local/bin/app_status_check.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # 应用状态检查脚本 TEXTFILE_DIR=\u0026#34;/var/lib/node_exporter/textfile_collector\u0026#34; TEMP_FILE=\u0026#34;${TEXTFILE_DIR}/app_status.prom.$$\u0026#34; PROM_FILE=\u0026#34;${TEXTFILE_DIR}/app_status.prom\u0026#34; # 检查 Nginx 状态 if systemctl is-active --quiet nginx; then nginx_status=1 else nginx_status=0 fi # 检查 Docker 状态 if systemctl is-active --quiet docker; then docker_status=1 else docker_status=0 fi # 检查磁盘空间 root_usage=$(df / | awk \u0026#39;NR==2 {print $5}\u0026#39; | sed \u0026#39;s/%//\u0026#39;) # 生成指标文件 cat \u0026gt; \u0026#34;$TEMP_FILE\u0026#34; \u0026lt;\u0026lt; EOL # HELP app_service_status Application service status (1=running, 0=stopped) # TYPE app_service_status gauge app_service_status{service=\u0026#34;nginx\u0026#34;} $nginx_status app_service_status{service=\u0026#34;docker\u0026#34;} $docker_status # HELP system_disk_usage_percent Disk usage percentage # TYPE system_disk_usage_percent gauge system_disk_usage_percent{mountpoint=\u0026#34;/\u0026#34;} $root_usage EOL # 原子性更新指标文件 mv \u0026#34;$TEMP_FILE\u0026#34; \u0026#34;$PROM_FILE\u0026#34; EOF # 设置脚本权限 chmod +x /usr/local/bin/app_status_check.sh chown node_exporter:node_exporter /usr/local/bin/app_status_check.sh # 设置定时任务 echo \u0026#34;*/1 * * * * node_exporter /usr/local/bin/app_status_check.sh\u0026#34; | crontab -u node_exporter - 第四步：常用查询示例 # 系统监控查询 # # 查看 Node Exporter 收集的指标 curl -s http://localhost:9100/metrics | grep -E \u0026#34;^node_\u0026#34; | head -10 # 在 Prometheus 中查询系统指标的示例： CPU 使用率查询:\n# 5分钟平均 CPU 使用率 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) * 100) # 各个 CPU 核心使用率 100 - (avg by(instance, cpu) (irate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) * 100) 内存使用率查询:\n# 内存使用率 (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 # 可用内存 node_memory_MemAvailable_bytes / 1024 / 1024 / 1024 磁盘使用率查询:\n# 根分区磁盘使用率 (1 - (node_filesystem_avail_bytes{mountpoint=\u0026#34;/\u0026#34;} / node_filesystem_size_bytes{mountpoint=\u0026#34;/\u0026#34;})) * 100 # 所有分区磁盘使用率 (1 - (node_filesystem_avail_bytes{fstype!=\u0026#34;tmpfs\u0026#34;} / node_filesystem_size_bytes{fstype!=\u0026#34;tmpfs\u0026#34;})) * 100 网络流量查询:\n# 网络接收速率 (bytes/sec) rate(node_network_receive_bytes_total[5m]) # 网络发送速率 (bytes/sec) rate(node_network_transmit_bytes_total[5m]) 系统服务状态查询:\n# Docker 服务状态 node_systemd_unit_state{name=\u0026#34;docker.service\u0026#34;,state=\u0026#34;active\u0026#34;} # SSH 服务状态 node_systemd_unit_state{name=\u0026#34;sshd.service\u0026#34;,state=\u0026#34;active\u0026#34;} # 所有活跃服务 node_systemd_unit_state{state=\u0026#34;active\u0026#34;} == 1 第五步：配置 Prometheus 抓取 Node Exporter # 更新 Prometheus 配置文件：\n# 编辑 Prometheus 配置 vim /etc/prometheus/prometheus.yml 添加 Node Exporter 抓取配置：\nscrape_configs: - job_name: \u0026#39;node-exporter\u0026#39; static_configs: - targets: [\u0026#39;localhost:9100\u0026#39;] labels: instance: \u0026#39;prometheus-server\u0026#39; environment: \u0026#39;production\u0026#39; scrape_interval: 30s scrape_timeout: 10s metrics_path: /metrics # 指标重新标记配置 metric_relabel_configs: # 删除不需要的指标 - source_labels: [__name__] regex: \u0026#39;node_scrape_collector_.*\u0026#39; action: drop # 重命名实例标签 - source_labels: [instance] target_label: node_instance # 重新加载 Prometheus 配置 systemctl reload prometheus # 验证目标状态 curl http://localhost:9090/api/v1/targets 常用 PromQL 查询公式 # PromQL（Prometheus Query Language）是 Prometheus 的查询语言，以下是一些常用的监控查询公式。\n配置文件验证 # # 检查 Prometheus 配置文件语法 promtool check config /etc/prometheus/prometheus.yml # 检查规则文件语法 promtool check rules /etc/prometheus/rules/*.yml # 查询配置文件中的目标 promtool query instant \u0026#39;up\u0026#39; 系统资源监控公式 # CPU 使用率 # # 5分钟平均 CPU 使用率 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) * 100) # 各个 CPU 核心使用率 100 - (avg by(instance, cpu) (irate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) * 100) # CPU 负载 node_load1 # 1分钟负载 node_load5 # 5分钟负载 node_load15 # 15分钟负载 # CPU 负载率（负载/CPU核数） node_load1 / count by(instance) (node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}) 内存使用率 # # 内存使用率（推荐公式） (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 # 内存使用率（传统公式） ((node_memory_MemTotal_bytes - node_memory_MemFree_bytes - node_memory_Cached_bytes - node_memory_Buffers_bytes) / node_memory_MemTotal_bytes) * 100 # 可用内存（GB） node_memory_MemAvailable_bytes / 1024 / 1024 / 1024 # 内存使用量（GB） (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / 1024 / 1024 / 1024 磁盘使用率 # # 根分区磁盘使用率 (1 - (node_filesystem_avail_bytes{mountpoint=\u0026#34;/\u0026#34;} / node_filesystem_size_bytes{mountpoint=\u0026#34;/\u0026#34;})) * 100 # 所有分区磁盘使用率（排除临时文件系统） (1 - (node_filesystem_avail_bytes{fstype!=\u0026#34;tmpfs\u0026#34;} / node_filesystem_size_bytes{fstype!=\u0026#34;tmpfs\u0026#34;})) * 100 # 磁盘可用空间（GB） node_filesystem_avail_bytes{mountpoint=\u0026#34;/\u0026#34;} / 1024 / 1024 / 1024 # 磁盘 I/O 使用率 rate(node_disk_io_time_seconds_total[5m]) * 100 网络流量 # # 网络接收速率（MB/s） rate(node_network_receive_bytes_total[5m]) / 1024 / 1024 # 网络发送速率（MB/s） rate(node_network_transmit_bytes_total[5m]) / 1024 / 1024 # 网络错误率 rate(node_network_receive_errs_total[5m]) rate(node_network_transmit_errs_total[5m]) # 网络丢包率 rate(node_network_receive_drop_total[5m]) rate(node_network_transmit_drop_total[5m]) 应用监控公式 # HTTP 请求监控 # # 请求速率（QPS） rate(http_requests_total[5m]) # 按状态码分组的请求速率 sum by(code) (rate(http_requests_total[5m])) # 错误率 rate(http_requests_total{code=~\u0026#34;5..\u0026#34;}[5m]) / rate(http_requests_total[5m]) * 100 # 平均响应时间 rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m]) # 95% 响应时间 histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) 数据库监控 # # MySQL 连接数 mysql_global_status_threads_connected # MySQL QPS rate(mysql_global_status_queries[5m]) # MySQL 慢查询 rate(mysql_global_status_slow_queries[5m]) # Redis 内存使用 redis_memory_used_bytes # Redis 连接数 redis_connected_clients 记录规则（Recording Rules） # 记录规则允许您预先计算经常需要或计算开销昂贵的表达式，并将其结果保存为新的时间序列。\n创建记录规则 # # 创建记录规则目录 mkdir -p /etc/prometheus/rules # 创建记录规则文件 cat \u0026gt; /etc/prometheus/rules/recording_rules.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; groups: - name: node_recording_rules interval: 30s rules: # CPU 使用率记录规则 - record: instance:node_cpu_utilization:rate5m expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode=\u0026#34;idle\u0026#34;}[5m])) * 100) labels: metric_type: utilization # 内存使用率记录规则 - record: instance:node_memory_utilization:ratio expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 labels: metric_type: utilization # 磁盘使用率记录规则 - record: instance:node_filesystem_utilization:ratio expr: (1 - (node_filesystem_avail_bytes{fstype!=\u0026#34;tmpfs\u0026#34;} / node_filesystem_size_bytes{fstype!=\u0026#34;tmpfs\u0026#34;})) * 100 labels: metric_type: utilization # 网络流量记录规则 - record: instance:node_network_receive_bytes:rate5m expr: rate(node_network_receive_bytes_total[5m]) labels: metric_type: traffic direction: receive - record: instance:node_network_transmit_bytes:rate5m expr: rate(node_network_transmit_bytes_total[5m]) labels: metric_type: traffic direction: transmit - name: application_recording_rules interval: 30s rules: # HTTP 请求速率 - record: job:http_requests:rate5m expr: sum by(job) (rate(http_requests_total[5m])) # HTTP 错误率 - record: job:http_requests_error_rate:rate5m expr: sum by(job) (rate(http_requests_total{code=~\u0026#34;5..\u0026#34;}[5m])) / sum by(job) (rate(http_requests_total[5m])) # 平均响应时间 - record: job:http_request_duration:mean5m expr: sum by(job) (rate(http_request_duration_seconds_sum[5m])) / sum by(job) (rate(http_request_duration_seconds_count[5m])) EOF # 验证规则文件 promtool check rules /etc/prometheus/rules/recording_rules.yml # 更新 Prometheus 配置文件 cat \u0026gt;\u0026gt; /etc/prometheus/prometheus.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; rule_files: - \u0026#34;rules/*.yml\u0026#34; EOF # 重新加载配置 systemctl reload prometheus 使用记录规则 # # 使用预计算的 CPU 使用率 instance:node_cpu_utilization:rate5m # 使用预计算的内存使用率 instance:node_memory_utilization:ratio # 使用预计算的网络流量 instance:node_network_receive_bytes:rate5m instance:node_network_transmit_bytes:rate5m 服务发现配置 # Prometheus 支持多种服务发现机制，可以自动发现和监控动态变化的目标。\n基于文件的服务发现 # 文件服务发现是最简单和常用的服务发现方式，适合静态或半静态的环境。\n创建目标文件目录 # # 创建服务发现目录结构 cd /etc/prometheus mkdir -p targets/{linux_nodes,docker_nodes,windows_nodes,databases,applications} # 设置目录权限 chown -R prometheus:prometheus targets/ 配置 Prometheus 使用文件服务发现 # # 在 prometheus.yml 中配置文件服务发现 scrape_configs: # Linux 节点监控 - job_name: \u0026#39;linux-nodes\u0026#39; file_sd_configs: - files: - \u0026#39;targets/linux_nodes/*.json\u0026#39; refresh_interval: 30s scrape_interval: 30s metrics_path: /metrics # Docker 容器监控 - job_name: \u0026#39;docker-containers\u0026#39; file_sd_configs: - files: - \u0026#39;targets/docker_nodes/*.json\u0026#39; refresh_interval: 30s scrape_interval: 30s # Windows 节点监控 - job_name: \u0026#39;windows-nodes\u0026#39; file_sd_configs: - files: - \u0026#39;targets/windows_nodes/*.json\u0026#39; refresh_interval: 30s scrape_interval: 30s # 数据库监控 - job_name: \u0026#39;databases\u0026#39; file_sd_configs: - files: - \u0026#39;targets/databases/*.json\u0026#39; refresh_interval: 30s scrape_interval: 30s # 应用程序监控 - job_name: \u0026#39;applications\u0026#39; file_sd_configs: - files: - \u0026#39;targets/applications/*.json\u0026#39; refresh_interval: 30s scrape_interval: 30s 创建目标配置文件 # Linux 节点配置:\ncat \u0026gt; /etc/prometheus/targets/linux_nodes/production.json \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [ { \u0026#34;targets\u0026#34;: [ \u0026#34;web-server-01:9100\u0026#34;, \u0026#34;web-server-02:9100\u0026#34;, \u0026#34;db-server-01:9100\u0026#34; ], \u0026#34;labels\u0026#34;: { \u0026#34;environment\u0026#34;: \u0026#34;production\u0026#34;, \u0026#34;datacenter\u0026#34;: \u0026#34;dc1\u0026#34;, \u0026#34;team\u0026#34;: \u0026#34;infrastructure\u0026#34; } }, { \u0026#34;targets\u0026#34;: [ \u0026#34;app-server-01:9100\u0026#34;, \u0026#34;app-server-02:9100\u0026#34; ], \u0026#34;labels\u0026#34;: { \u0026#34;environment\u0026#34;: \u0026#34;production\u0026#34;, \u0026#34;datacenter\u0026#34;: \u0026#34;dc1\u0026#34;, \u0026#34;team\u0026#34;: \u0026#34;application\u0026#34;, \u0026#34;service\u0026#34;: \u0026#34;web-app\u0026#34; } } ] EOF 数据库监控配置:\ncat \u0026gt; /etc/prometheus/targets/databases/mysql.json \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [ { \u0026#34;targets\u0026#34;: [ \u0026#34;mysql-master:9104\u0026#34;, \u0026#34;mysql-slave-01:9104\u0026#34;, \u0026#34;mysql-slave-02:9104\u0026#34; ], \u0026#34;labels\u0026#34;: { \u0026#34;environment\u0026#34;: \u0026#34;production\u0026#34;, \u0026#34;database_type\u0026#34;: \u0026#34;mysql\u0026#34;, \u0026#34;cluster\u0026#34;: \u0026#34;main\u0026#34; } } ] EOF cat \u0026gt; /etc/prometheus/targets/databases/redis.json \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [ { \u0026#34;targets\u0026#34;: [ \u0026#34;redis-01:9121\u0026#34;, \u0026#34;redis-02:9121\u0026#34;, \u0026#34;redis-03:9121\u0026#34; ], \u0026#34;labels\u0026#34;: { \u0026#34;environment\u0026#34;: \u0026#34;production\u0026#34;, \u0026#34;database_type\u0026#34;: \u0026#34;redis\u0026#34;, \u0026#34;cluster\u0026#34;: \u0026#34;cache\u0026#34; } } ] EOF 应用程序监控配置:\ncat \u0026gt; /etc/prometheus/targets/applications/web-services.json \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [ { \u0026#34;targets\u0026#34;: [ \u0026#34;api-gateway:8080\u0026#34;, \u0026#34;user-service:8081\u0026#34;, \u0026#34;order-service:8082\u0026#34; ], \u0026#34;labels\u0026#34;: { \u0026#34;environment\u0026#34;: \u0026#34;production\u0026#34;, \u0026#34;service_type\u0026#34;: \u0026#34;microservice\u0026#34;, \u0026#34;team\u0026#34;: \u0026#34;backend\u0026#34; } } ] EOF 基于 Kubernetes 的服务发现 # 对于 Kubernetes 环境，可以使用内置的服务发现机制：\nscrape_configs: # Kubernetes Pod 发现 - job_name: \u0026#39;kubernetes-pods\u0026#39; kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: true - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] action: replace target_label: __metrics_path__ regex: (.+) - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] action: replace regex: ([^:]+)(?::\\d+)?;(\\d+) replacement: $1:$2 target_label: __address__ # Kubernetes Service 发现 - job_name: \u0026#39;kubernetes-services\u0026#39; kubernetes_sd_configs: - role: service relabel_configs: - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] action: keep regex: true 动态目标管理脚本 # 创建脚本来动态管理监控目标：\ncat \u0026gt; /usr/local/bin/prometheus-target-manager.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # Prometheus 目标管理脚本 TARGETS_DIR=\u0026#34;/etc/prometheus/targets\u0026#34; BACKUP_DIR=\u0026#34;/var/backups/prometheus-targets\u0026#34; # 创建备份目录 mkdir -p \u0026#34;$BACKUP_DIR\u0026#34; # 备份当前配置 backup_targets() { local timestamp=$(date +%Y%m%d_%H%M%S) tar -czf \u0026#34;$BACKUP_DIR/targets_backup_$timestamp.tar.gz\u0026#34; -C \u0026#34;$TARGETS_DIR\u0026#34; . echo \u0026#34;目标配置已备份到: $BACKUP_DIR/targets_backup_$timestamp.tar.gz\u0026#34; } # 添加新目标 add_target() { local category=\u0026#34;$1\u0026#34; local target=\u0026#34;$2\u0026#34; local labels=\u0026#34;$3\u0026#34; local file=\u0026#34;$TARGETS_DIR/$category/dynamic.json\u0026#34; # 创建目录 mkdir -p \u0026#34;$TARGETS_DIR/$category\u0026#34; # 如果文件不存在，创建空数组 if [[ ! -f \u0026#34;$file\u0026#34; ]]; then echo \u0026#39;[]\u0026#39; \u0026gt; \u0026#34;$file\u0026#34; fi # 添加目标（这里简化处理，实际应该使用 jq 工具） echo \u0026#34;目标 $target 已添加到 $category\u0026#34; } # 删除目标 remove_target() { local category=\u0026#34;$1\u0026#34; local target=\u0026#34;$2\u0026#34; local file=\u0026#34;$TARGETS_DIR/$category/dynamic.json\u0026#34; if [[ -f \u0026#34;$file\u0026#34; ]]; then # 使用 jq 删除目标 echo \u0026#34;目标 $target 已从 $category 中删除\u0026#34; fi } # 验证配置 validate_config() { promtool check config /etc/prometheus/prometheus.yml } # 重新加载 Prometheus reload_prometheus() { if systemctl is-active --quiet prometheus; then systemctl reload prometheus echo \u0026#34;Prometheus 配置已重新加载\u0026#34; else echo \u0026#34;错误: Prometheus 服务未运行\u0026#34; return 1 fi } # 主函数 case \u0026#34;$1\u0026#34; in backup) backup_targets ;; add) add_target \u0026#34;$2\u0026#34; \u0026#34;$3\u0026#34; \u0026#34;$4\u0026#34; ;; remove) remove_target \u0026#34;$2\u0026#34; \u0026#34;$3\u0026#34; ;; validate) validate_config ;; reload) reload_prometheus ;; *) echo \u0026#34;用法: $0 {backup|add|remove|validate|reload}\u0026#34; echo \u0026#34; backup - 备份当前目标配置\u0026#34; echo \u0026#34; add \u0026lt;category\u0026gt; \u0026lt;target\u0026gt; - 添加新目标\u0026#34; echo \u0026#34; remove \u0026lt;category\u0026gt; \u0026lt;target\u0026gt; - 删除目标\u0026#34; echo \u0026#34; validate - 验证配置文件\u0026#34; echo \u0026#34; reload - 重新加载 Prometheus\u0026#34; exit 1 ;; esac EOF chmod +x /usr/local/bin/prometheus-target-manager.sh 性能优化和最佳实践 # 性能调优 # Prometheus 服务器优化 # # 优化 Prometheus 启动参数 cat \u0026gt; /etc/systemd/system/prometheus.service \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [Unit] Description=Prometheus Server Documentation=https://prometheus.io/docs/ After=network-online.target Wants=network-online.target [Service] Type=simple User=prometheus Group=prometheus ExecStart=/usr/local/bin/prometheus \\ --config.file=/etc/prometheus/prometheus.yml \\ --storage.tsdb.path=/var/lib/prometheus \\ --storage.tsdb.retention.time=30d \\ --storage.tsdb.retention.size=50GB \\ --storage.tsdb.wal-compression \\ --web.console.templates=/etc/prometheus/consoles \\ --web.console.libraries=/etc/prometheus/console_libraries \\ --web.listen-address=0.0.0.0:9090 \\ --web.external-url=http://localhost:9090 \\ --web.enable-lifecycle \\ --web.enable-admin-api \\ --web.max-connections=512 \\ --query.max-concurrency=20 \\ --query.timeout=2m \\ --log.level=info Restart=always RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF 存储优化 # # 创建存储优化脚本 cat \u0026gt; /usr/local/bin/prometheus-storage-optimize.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # Prometheus 存储优化脚本 PROMETHEUS_DATA=\u0026#34;/var/lib/prometheus\u0026#34; LOG_FILE=\u0026#34;/var/log/prometheus-optimize.log\u0026#34; log() { echo \u0026#34;[$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;)] $1\u0026#34; | tee -a \u0026#34;$LOG_FILE\u0026#34; } # 检查磁盘使用情况 check_disk_usage() { local usage=$(df \u0026#34;$PROMETHEUS_DATA\u0026#34; | awk \u0026#39;NR==2 {print $5}\u0026#39; | sed \u0026#39;s/%//\u0026#39;) log \u0026#34;当前磁盘使用率: ${usage}%\u0026#34; if [[ $usage -gt 80 ]]; then log \u0026#34;警告: 磁盘使用率超过 80%\u0026#34; return 1 fi return 0 } # 清理过期数据 cleanup_old_data() { log \u0026#34;开始清理过期数据...\u0026#34; # 使用 Prometheus API 删除过期数据 curl -X POST http://localhost:9090/api/v1/admin/tsdb/delete_series?match[]={__name__=~\u0026#34;.+\u0026#34;}\u0026amp;start=0\u0026amp;end=$(date -d \u0026#39;30 days ago\u0026#39; +%s) # 清理墓碑数据 curl -X POST http://localhost:9090/api/v1/admin/tsdb/clean_tombstones log \u0026#34;数据清理完成\u0026#34; } # 压缩数据 compact_data() { log \u0026#34;开始数据压缩...\u0026#34; # 停止 Prometheus systemctl stop prometheus # 运行压缩 /usr/local/bin/promtool tsdb create-blocks-from openmetrics \u0026#34;$PROMETHEUS_DATA\u0026#34; # 重启 Prometheus systemctl start prometheus log \u0026#34;数据压缩完成\u0026#34; } # 主函数 main() { log \u0026#34;开始 Prometheus 存储优化\u0026#34; if ! check_disk_usage; then cleanup_old_data sleep 60 compact_data fi log \u0026#34;存储优化完成\u0026#34; } main \u0026#34;$@\u0026#34; EOF chmod +x /usr/local/bin/prometheus-storage-optimize.sh # 设置定时任务 echo \u0026#34;0 2 * * 0 /usr/local/bin/prometheus-storage-optimize.sh\u0026#34; | crontab - 监控和告警最佳实践 # 1. 告警规则设计原则 # 可操作性: 每个告警都应该有明确的处理步骤 避免噪音: 设置合理的阈值和持续时间 分级处理: 根据严重程度设置不同的通知方式 2. 标签规范 # # 推荐的标签规范 global: external_labels: cluster: \u0026#39;production\u0026#39; region: \u0026#39;us-west-1\u0026#39; datacenter: \u0026#39;dc1\u0026#39; # 在抓取配置中添加标准标签 scrape_configs: - job_name: \u0026#39;web-servers\u0026#39; static_configs: - targets: [\u0026#39;web-01:9100\u0026#39;, \u0026#39;web-02:9100\u0026#39;] labels: environment: \u0026#39;production\u0026#39; service: \u0026#39;web\u0026#39; team: \u0026#39;frontend\u0026#39; tier: \u0026#39;web\u0026#39; 3. 查询优化 # # 好的查询示例 rate(http_requests_total[5m]) # 避免的查询示例（时间范围太长） rate(http_requests_total[1h]) # 使用记录规则预计算复杂查询 instance:node_cpu_utilization:rate5m 安全配置 # 1. 网络安全 # # 配置防火墙 firewall-cmd --permanent --add-rich-rule=\u0026#39;rule family=\u0026#34;ipv4\u0026#34; source address=\u0026#34;10.0.0.0/8\u0026#34; port protocol=\u0026#34;tcp\u0026#34; port=\u0026#34;9090\u0026#34; accept\u0026#39; firewall-cmd --reload # 使用 TLS 加密 # 在 Nginx 配置中启用 HTTPS 2. 认证和授权 # # 在 prometheus.yml 中配置基本认证 basic_auth_users: admin: $2b$12$hNf2lSsxfm0.i4a.1kVpSOVyBCfIB51VRjgBUyv6kdnyTlgWj81Ay 备份和恢复 # 备份策略 # cat \u0026gt; /usr/local/bin/prometheus-backup.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # Prometheus 备份脚本 PROMETHEUS_DATA=\u0026#34;/var/lib/prometheus\u0026#34; BACKUP_DIR=\u0026#34;/backup/prometheus\u0026#34; RETENTION_DAYS=30 # 创建备份目录 mkdir -p \u0026#34;$BACKUP_DIR\u0026#34; # 创建快照 SNAPSHOT_NAME=\u0026#34;prometheus-$(date +%Y%m%d-%H%M%S)\u0026#34; curl -X POST http://localhost:9090/api/v1/admin/tsdb/snapshot # 获取快照目录 SNAPSHOT_DIR=$(ls -t \u0026#34;$PROMETHEUS_DATA/snapshots\u0026#34; | head -1) # 压缩备份 tar -czf \u0026#34;$BACKUP_DIR/$SNAPSHOT_NAME.tar.gz\u0026#34; -C \u0026#34;$PROMETHEUS_DATA/snapshots\u0026#34; \u0026#34;$SNAPSHOT_DIR\u0026#34; # 清理快照 rm -rf \u0026#34;$PROMETHEUS_DATA/snapshots/$SNAPSHOT_DIR\u0026#34; # 清理过期备份 find \u0026#34;$BACKUP_DIR\u0026#34; -name \u0026#34;prometheus-*.tar.gz\u0026#34; -mtime +$RETENTION_DAYS -delete echo \u0026#34;备份完成: $BACKUP_DIR/$SNAPSHOT_NAME.tar.gz\u0026#34; EOF chmod +x /usr/local/bin/prometheus-backup.sh # 设置定时备份 echo \u0026#34;0 3 * * * /usr/local/bin/prometheus-backup.sh\u0026#34; | crontab - 总结与展望 # 部署总结 # 通过本文的详细指导，我们成功实现了：\n✅ 完整的 Prometheus 监控系统: 从安装到配置的全流程部署 ✅ AlertManager 告警管理: 支持邮件、钉钉等多种通知方式 ✅ 多种 Exporter 集成: Node、MySQL、Redis、PHP-FPM 等监控组件 ✅ 高级功能配置: 记录规则、服务发现、性能优化等企业级特性 ✅ 运维管理工具: 备份、恢复、性能调优等运维脚本\n监控体系架构 # flowchart LR subgraph Exporters[Exporters 数据源] E1[Node Exporter] E2[MySQL Exporter] E3[Redis Exporter] E4[Custom Exporter] end subgraph Prometheus[Prometheus] P1[Scraping] P2[Storage] P3[Query Engine] end subgraph AlertManager[AlertManager] A1[Alerting] A2[Routing] A3[Notification] end subgraph Visualization[可视化与通知] V1[Grafana] V2[钉钉/邮件] end E1 --\u003e P1 E2 --\u003e P1 E3 --\u003e P1 E4 --\u003e P1 P1 --\u003e P2 P1 --\u003e P3 P3 --\u003e A1 A1 --\u003e A2 A2 --\u003e A3 P3 --\u003e V1 A3 --\u003e V2 最佳实践要点 # 规划先行: 合理规划监控指标和告警策略 标准化: 统一标签规范和命名约定 自动化: 使用服务发现和自动化脚本 优化性能: 定期清理数据和优化查询 安全第一: 配置认证、授权和网络安全 扩展方向 # 未来可以考虑以下扩展：\n高可用部署: Prometheus 集群和联邦配置 长期存储: 集成 Thanos 或 VictoriaMetrics 服务网格监控: Istio、Linkerd 等服务网格集成 云原生监控: Kubernetes 原生监控解决方案 AI 运维: 基于机器学习的异常检测和预测 通过本指南，您已经掌握了 Prometheus 监控系统的完整部署和管理技能，可以为企业构建一个稳定、高效、可扩展的监控平台。在实际使用过程中，请根据具体需求调整配置参数，并持续关注社区更新和最佳实践。\n[root@localhost prometheus]# promtool check config prometheus.yml Checking prometheus.yml SUCCESS: 1 rule files found\nChecking rules/node_alerts.yml SUCCESS: 3 rules found\n/usr/sbin/lsof -n -P -t -i :9090 |xargs kill -HUP\n// 也可以使用下面的这种方式（YAML）\ncat /etc/prometheus/targets/nodes/demo.json # targets: \u0026ldquo;192.168.20.172:8080\u0026rdquo; \u0026ldquo;192.168.20.173:8080\u0026rdquo; \u0026ldquo;192.168.20.174:8080\u0026rdquo; ### alertmanager 设置钉钉告警 [参考链接](\u0026lt;https://www.cnblogs.com/pyuh/p/9548495.html\u0026gt;) ```shell yum install go -y mkdir -p /usr/lib/golang/src/github.com/timonwong/ git clone https://github.com/timonwong/prometheus-webhook-dingtalk.git make cp prometheus-webhook-dingtalk /usr/local/bin nohup prometheus-webhook-dingtalk --web.listen-address=\u0026#34;:8228\u0026#34; --ding.profile=\u0026#34;webhook1=https://oapi.dingtalk.com/robot/send?access_token=d4d3069d3ef12a9487ecf878b7611579d8d100e0a82516cc8e80009cbb506ebc\u0026#34; 2\u0026gt;\u0026amp;1 1\u0026gt;/tmp/dingding.log \u0026amp; #安装钉钉插件并启动 配置黑盒监控 # (下载地址)[https://github.com/prometheus/blackbox_exporter]\nwget https://github.com/prometheus/blackbox_exporter/releases/download/v0.14.0/blackbox_exporter-0.14.0.linux-amd64.tar.gz tar xf blackbox_exporter-0.14.0.linux-amd64.tar.gz mkdir /etc/exporter \u0026amp;\u0026amp; cp ./blackbox_exporter-0.14.0.linux-amd64/blackbox_exporter-0.14.0.linux-amd64 /usr/local/bin/ cp ./blackbox_exporter-0.14.0.linux-amd64/blackbox.yml /etc/exporter/blackbox.yml nohup blackbox_exporter --config.file=\u0026#34;/etc/exporter/blackbox.yml\u0026#34; --web.listen-address=\u0026#34;:9115\u0026#34; --log.level=info \u0026gt;/tmp/blackbox.log 2\u0026gt;\u0026amp;1 \u0026amp; #启动 blackbox_exporter 添加至自启动 # cat \u0026gt; /usr/lib/systemd/system/blackbox_exporter.service \u0026lt;\u0026lt;EOF [Unit] Description=blackbox_exporter Documentation=https://github.com/prometheus/blackbox_exporter After=network.target [Service] Type=simple User=root ExecStart=/usr/local/bin/blackbox_exporter --config.file=/etc/exporter/blackbox.yml --web.listen-address=192.168.8.122:9115 Restart=on-failure [Install] WantedBy=multi-user.target EOF systemctl daemon-reload \u0026amp;\u0026amp; systemctl start blackbox_exporter \u0026amp;\u0026amp; systemctl status blackbox_exporter #启动 systemctl enable blackbox_exporter #加入开机自启动 lsof -i :9115 docker 启动\nmkdir -p /application/black-box-exporter/config wget -O /application/black-box-exporter/config/blackbox.yml https://raw.githubusercontent.com/prometheus/blackbox_exporter/master/blackbox.yml docker run -d \\ -p 9115:9115 --name blackbox_exporter \\ --restart always \\ --net=host \\ -v /application/black-box-exporter/config:/config prom/blackbox-exporter:master \\ --config.file=/config/blackbox.yml \\ --web.external-url=/black-box 配置php-fpm_exporter # [root@hadoopname ~]# egrep \u0026#39;/ping|/status\u0026#39; /usr/local/php/etc/php-fpm.d/walle.conf pm.status_path = /status ping.path = /ping # [root@hadoopname ~]# cat /usr/local/nginx/conf/conf.d/ cobra.conf jumpserver.conf official.conf php_status.conf walle.conf zabbix.conf [root@hadoopname ~]# cat /usr/local/nginx/conf/conf.d/php_status.conf server { listen 9010; allow 127.0.0.1; allow 192.168.8.0/24; deny all; location ~ ^/(status|ping)$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } nohup php-fpm-exporter --addr 0.0.0.0:9190 --endpoint http://127.0.0.1:9010/status \u0026gt; /tmp/php-fpm-exporter.log 2\u0026gt;\u0026amp;1 \u0026amp; sudo firewall-cmd --zone=public --add-port=9190/tcp --permanent firewall-cmd --reload 添加至systemd服务 及开机自启动 # #添加开机自启动 cat \u0026gt; /usr/lib/systemd/system/php-fpm-exporter.service \u0026lt;\u0026lt;EOF [Unit] Description=php-fpm-exporter Documentation=https://github.com/hipages/php-fpm_exporter After=network.target [Service] Type=simple User=root ExecStart=/usr/local/bin/php-fpm-exporter --addr 0.0.0.0:9190 --endpoint http://127.0.0.1:9010/status Restart=on-failure [Install] WantedBy=multi-user.target EOF systemctl daemon-reload \u0026amp;\u0026amp; systemctl start php-fpm-exporter \u0026amp;\u0026amp; systemctl status php-fpm-exporter systemctl enable php-fpm-exporter lsof -i :9090 win_exporter 安装配置 # msiexec /i wmi_exporter-0.7.0-amd64.msi ENABLED_COLLECTORS=cpu,cs,logical_disk,net,os,service,system,textfile,memory,tcp LISTEN_PORT=9010 备份与恢复 # 数据备份策略 # 完整备份脚本 # cat \u0026gt; prometheus-backup.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash PROMETHEUS_DATA=\u0026#34;/var/lib/prometheus\u0026#34; BACKUP_DIR=\u0026#34;/backup/prometheus\u0026#34; RETENTION_DAYS=30 DATE=$(date +%Y%m%d_%H%M%S) echo \u0026#34;=== Prometheus 备份脚本 ===\u0026#34; echo \u0026#34;开始时间: $(date)\u0026#34; ## 创建备份目录 mkdir -p \u0026#34;$BACKUP_DIR\u0026#34; ## 创建快照 echo \u0026#34;创建 Prometheus 快照...\u0026#34; SNAPSHOT_RESPONSE=$(curl -X POST http://localhost:9090/api/v1/admin/tsdb/snapshot) SNAPSHOT_NAME=$(echo $SNAPSHOT_RESPONSE | jq -r \u0026#39;.data.name\u0026#39;) if [ \u0026#34;$SNAPSHOT_NAME\u0026#34; != \u0026#34;null\u0026#34; ]; then echo \u0026#34;快照创建成功: $SNAPSHOT_NAME\u0026#34; # 压缩备份 echo \u0026#34;压缩备份数据...\u0026#34; tar -czf \u0026#34;$BACKUP_DIR/prometheus-snapshot-$DATE.tar.gz\u0026#34; \\ -C \u0026#34;$PROMETHEUS_DATA/snapshots\u0026#34; \u0026#34;$SNAPSHOT_NAME\u0026#34; # 清理快照 rm -rf \u0026#34;$PROMETHEUS_DATA/snapshots/$SNAPSHOT_NAME\u0026#34; # 备份配置文件 echo \u0026#34;备份配置文件...\u0026#34; tar -czf \u0026#34;$BACKUP_DIR/prometheus-config-$DATE.tar.gz\u0026#34; \\ -C /etc prometheus/ # 验证备份 if [ -f \u0026#34;$BACKUP_DIR/prometheus-snapshot-$DATE.tar.gz\u0026#34; ]; then BACKUP_SIZE=$(du -h \u0026#34;$BACKUP_DIR/prometheus-snapshot-$DATE.tar.gz\u0026#34; | cut -f1) echo \u0026#34;✓ 数据备份完成: prometheus-snapshot-$DATE.tar.gz ($BACKUP_SIZE)\u0026#34; fi if [ -f \u0026#34;$BACKUP_DIR/prometheus-config-$DATE.tar.gz\u0026#34; ]; then CONFIG_SIZE=$(du -h \u0026#34;$BACKUP_DIR/prometheus-config-$DATE.tar.gz\u0026#34; | cut -f1) echo \u0026#34;✓ 配置备份完成: prometheus-config-$DATE.tar.gz ($CONFIG_SIZE)\u0026#34; fi else echo \u0026#34;✗ 快照创建失败\u0026#34; exit 1 fi ## 清理过期备份 echo \u0026#34;清理过期备份...\u0026#34; find \u0026#34;$BACKUP_DIR\u0026#34; -name \u0026#34;prometheus-snapshot-*.tar.gz\u0026#34; -mtime +$RETENTION_DAYS -delete find \u0026#34;$BACKUP_DIR\u0026#34; -name \u0026#34;prometheus-config-*.tar.gz\u0026#34; -mtime +$RETENTION_DAYS -delete echo \u0026#34;=== 备份完成 ===\u0026#34; EOF chmod +x prometheus-backup.sh ## 设置定时备份 echo \u0026#34;0 2 * * * /usr/local/bin/prometheus-backup.sh\u0026#34; | crontab - 总结 # 部署优势 # 通过本指南，您可以成功部署一个企业级的 Prometheus 监控平台，具有以下优势：\n技术优势 # 云原生监控：专为现代化微服务和容器环境设计 高性能存储：内置时序数据库，高效压缩和查询 强大的查询语言：PromQL 提供灵活的数据分析能力 丰富的生态系统：大量 Exporter 和集成工具 可扩展架构：支持联邦、分片和高可用部署 运维优势 # Pull 模式采集：主动拉取，网络拓扑简单 服务发现：自动发现和监控动态目标 告警管理：灵活的告警规则和通知机制 可视化集成：与 Grafana 完美集成 API 丰富：完整的 REST API 支持自动化 最佳实践 # 生产环境建议 # 容量规划：根据指标数量和保留时间合理规划存储 高可用部署：配置 Prometheus 联邦和 AlertManager 集群 安全配置：启用 HTTPS、认证和网络隔离 监控监控：监控 Prometheus 自身的健康状态 备份策略：定期备份配置和关键数据 扩展建议 # 长期存储：集成 Thanos、Cortex 或 VictoriaMetrics 多集群监控：使用联邦或远程读写实现跨集群监控 服务网格集成：监控 Istio、Linkerd 等服务网格 云原生集成：与 Kubernetes、OpenShift 深度集成 持续改进 # Prometheus 作为监控平台的核心，需要持续优化和改进：\n定期更新：保持 Prometheus 和相关组件的及时更新 性能监控：持续监控系统性能和查询效率 告警优化：根据实际情况调整告警规则和阈值 用户培训：提供 PromQL 和监控最佳实践培训 通过本指南的配置和最佳实践，您可以构建一个稳定、高效、可扩展的企业级监控平台，为现代化应用和基础设施提供全面的可观测性支持。\n","date":"2021年3月9日","externalUrl":null,"permalink":"/posts/prometheus/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003ePrometheus 监控平台简介 \n    \u003cdiv id=\"prometheus-监控平台简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prometheus-%e7%9b%91%e6%8e%a7%e5%b9%b3%e5%8f%b0%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 Prometheus \n    \u003cdiv id=\"什么是-prometheus\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-prometheus\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003ePrometheus 是由 SoundCloud 开发并贡献给 CNCF（Cloud Native Computing Foundation）的开源监控和告警系统。作为云原生监控的事实标准，Prometheus 为现代化的微服务架构和容器化环境提供了强大的监控能力。\u003c/p\u003e","title":"企业级 Prometheus 监控平台部署与运维完整指南","type":"posts"},{"content":" 推荐阅读 # 企业级 Jenkins CI/CD 平台部署与配置完整指南 Kubernetes 环境下部署 GitLab：localPv + 外部数据库方案 使用 Helm 部署 Spinnaker 持续部署(CD)平台 GitLab 平台简介 # 什么是 GitLab # GitLab 是一个基于 Web 的 DevOps 生命周期工具，提供了 Git 仓库管理、代码审查、问题跟踪、CI/CD 流水线和 Wiki 等功能。作为一个完整的 DevOps 平台，GitLab 帮助团队协作开发、测试和部署应用程序。\n核心特性 # Git 仓库管理：完整的 Git 版本控制功能 CI/CD 流水线：内置的持续集成和持续部署 代码审查：Merge Request 和代码质量检查 项目管理：Issue 跟踪、里程碑、看板 安全扫描：SAST、DAST、依赖扫描 容器注册表：内置 Docker 镜像仓库 监控告警：应用性能监控和日志管理 版本对比 # 版本 特性 适用场景 GitLab CE (Community Edition) 基础功能，免费开源 小团队、个人项目 GitLab EE (Enterprise Edition) 企业级功能，商业授权 大型企业、高级安全需求 GitLab.com SaaS 服务 快速启动、无需维护 架构设计 # 单机架构 # 高可用架构 # flowchart TB LB[Load Balancer] G1[GitLab Node 1] G2[GitLab Node 2] G3[GitLab Node 3] LB --\u003e G1 LB --\u003e G2 LB --\u003e G3 G1 -.-\u003e|Shared Services| S1 G2 -.-\u003e|Shared Services| S1 G3 -.-\u003e|Shared Services| S1 subgraph SharedServices[Shared Services] S1[PostgreSQL / Redis Cluster / Gitaly Cluster] end 环境准备 # 系统要求 # 硬件要求 # 用户数量 CPU 内存 存储 网络 1-100 4 核 8GB 100GB 1Gbps 100-500 8 核 16GB 500GB 1Gbps 500-1000 16 核 32GB 1TB 10Gbps 1000+ 32 核 64GB+ 2TB+ 10Gbps 软件要求 # 组件 最低版本 推荐版本 说明 操作系统 CentOS 7.6 CentOS 8+ / Ubuntu 20.04+ 64位系统 Docker 19.03.0 20.10.0+ 容器运行时 Docker Compose 1.25.0 1.29.0+ 容器编排工具 GitLab 13.0.0 15.0.0+ 推荐使用最新 LTS 版本 网络端口规划 # 端口 协议 服务 说明 80 TCP HTTP Web 界面访问 443 TCP HTTPS 安全 Web 访问 22 TCP SSH Git SSH 访问 5050 TCP Container Registry Docker 镜像仓库 9090 TCP Prometheus 监控数据收集 环境检查脚本 # cat \u0026gt; check-gitlab-env.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== GitLab 环境检查脚本 ===\u0026#34; echo \u0026#34;检查时间: $(date)\u0026#34; echo # 检查操作系统 echo \u0026#34;=== 系统信息 ===\u0026#34; cat /etc/redhat-release 2\u0026gt;/dev/null || lsb_release -a 2\u0026gt;/dev/null uname -a echo # 检查内存 echo \u0026#34;=== 内存信息 ===\u0026#34; free -h TOTAL_MEM=$(free -m | awk \u0026#39;NR==2{printf \u0026#34;%.0f\u0026#34;, $2}\u0026#39;) if [ $TOTAL_MEM -lt 4096 ]; then echo \u0026#34;⚠ 警告: 内存不足 4GB，可能影响 GitLab 性能\u0026#34; else echo \u0026#34;✓ 内存充足\u0026#34; fi echo # 检查磁盘空间 echo \u0026#34;=== 磁盘空间 ===\u0026#34; df -h DISK_USAGE=$(df / | awk \u0026#39;NR==2 {print $5}\u0026#39; | sed \u0026#39;s/%//\u0026#39;) if [ $DISK_USAGE -gt 80 ]; then echo \u0026#34;⚠ 警告: 磁盘使用率超过 80%\u0026#34; else echo \u0026#34;✓ 磁盘空间充足\u0026#34; fi echo # 检查 Docker echo \u0026#34;=== Docker 环境检查 ===\u0026#34; if command -v docker \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then docker --version echo \u0026#34;✓ Docker 已安装\u0026#34; if systemctl is-active --quiet docker; then echo \u0026#34;✓ Docker 服务运行正常\u0026#34; else echo \u0026#34;✗ Docker 服务未运行\u0026#34; fi else echo \u0026#34;✗ Docker 未安装\u0026#34; fi echo # 检查 Docker Compose echo \u0026#34;=== Docker Compose 检查 ===\u0026#34; if command -v docker-compose \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then docker-compose --version echo \u0026#34;✓ Docker Compose 已安装\u0026#34; else echo \u0026#34;✗ Docker Compose 未安装\u0026#34; fi echo # 检查网络端口 echo \u0026#34;=== 端口检查 ===\u0026#34; for port in 80 443 22; do if netstat -tlnp | grep :$port \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;⚠ 端口 $port 已被占用\u0026#34; netstat -tlnp | grep :$port else echo \u0026#34;✓ 端口 $port 可用\u0026#34; fi done echo # 检查防火墙状态 echo \u0026#34;=== 防火墙状态 ===\u0026#34; if systemctl is-active --quiet firewalld; then echo \u0026#34;防火墙已启用\u0026#34; firewall-cmd --list-ports elif systemctl is-active --quiet ufw; then echo \u0026#34;UFW 防火墙已启用\u0026#34; ufw status else echo \u0026#34;防火墙未启用\u0026#34; fi echo echo \u0026#34;=== 环境检查完成 ===\u0026#34; EOF chmod +x check-gitlab-env.sh ./check-gitlab-env.sh GitLab 部署实施 # 方案一：Docker Compose 部署（推荐） # 步骤 1：创建项目目录 # # 创建项目目录 mkdir -p /data/gitlab \u0026amp;\u0026amp; cd /data/gitlab # 创建数据目录 mkdir -p {config,data,logs,backups} # 设置目录权限 chmod -R 755 /data/gitlab 步骤 2：基础配置文件 # cat \u0026gt; docker-compose.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; version: \u0026#39;3.8\u0026#39; services: gitlab: image: gitlab/gitlab-ce:15.11.0-ce.0 container_name: gitlab-ce restart: unless-stopped hostname: gitlab.your-domain.com environment: TZ: \u0026#39;Asia/Shanghai\u0026#39; GITLAB_OMNIBUS_CONFIG: | # 基础配置 external_url \u0026#39;https://gitlab.your-domain.com\u0026#39; gitlab_rails[\u0026#39;time_zone\u0026#39;] = \u0026#39;Asia/Shanghai\u0026#39; # 邮件配置 gitlab_rails[\u0026#39;smtp_enable\u0026#39;] = true gitlab_rails[\u0026#39;smtp_address\u0026#39;] = \u0026#34;smtp.your-domain.com\u0026#34; gitlab_rails[\u0026#39;smtp_port\u0026#39;] = 587 gitlab_rails[\u0026#39;smtp_user_name\u0026#39;] = \u0026#34;gitlab@your-domain.com\u0026#34; gitlab_rails[\u0026#39;smtp_password\u0026#39;] = \u0026#34;your-smtp-password\u0026#34; gitlab_rails[\u0026#39;smtp_domain\u0026#39;] = \u0026#34;your-domain.com\u0026#34; gitlab_rails[\u0026#39;smtp_authentication\u0026#39;] = \u0026#34;login\u0026#34; gitlab_rails[\u0026#39;smtp_enable_starttls_auto\u0026#39;] = true gitlab_rails[\u0026#39;smtp_tls\u0026#39;] = false gitlab_rails[\u0026#39;gitlab_email_from\u0026#39;] = \u0026#39;gitlab@your-domain.com\u0026#39; gitlab_rails[\u0026#39;gitlab_email_reply_to\u0026#39;] = \u0026#39;noreply@your-domain.com\u0026#39; # 性能优化 gitlab_rails[\u0026#39;env\u0026#39;] = { \u0026#39;MALLOC_CONF\u0026#39; =\u0026gt; \u0026#39;dirty_decay_ms:1000,muzzy_decay_ms:1000\u0026#39; } # 备份配置 gitlab_rails[\u0026#39;backup_keep_time\u0026#39;] = 604800 gitlab_rails[\u0026#39;backup_path\u0026#39;] = \u0026#34;/var/opt/gitlab/backups\u0026#34; # 监控配置 prometheus[\u0026#39;enable\u0026#39;] = true prometheus[\u0026#39;monitor_kubernetes\u0026#39;] = false # 安全配置 nginx[\u0026#39;ssl_certificate\u0026#39;] = \u0026#34;/etc/gitlab/ssl/gitlab.crt\u0026#34; nginx[\u0026#39;ssl_certificate_key\u0026#39;] = \u0026#34;/etc/gitlab/ssl/gitlab.key\u0026#34; nginx[\u0026#39;ssl_protocols\u0026#39;] = \u0026#34;TLSv1.2 TLSv1.3\u0026#34; nginx[\u0026#39;ssl_ciphers\u0026#39;] = \u0026#34;ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256\u0026#34; nginx[\u0026#39;ssl_prefer_server_ciphers\u0026#39;] = \u0026#34;on\u0026#34; nginx[\u0026#39;ssl_session_cache\u0026#39;] = \u0026#34;builtin:1000 shared:SSL:10m\u0026#34; nginx[\u0026#39;ssl_session_timeout\u0026#39;] = \u0026#34;5m\u0026#34; ports: - \u0026#34;80:80\u0026#34; - \u0026#34;443:443\u0026#34; - \u0026#34;22:22\u0026#34; volumes: - ./config:/etc/gitlab - ./data:/var/opt/gitlab - ./logs:/var/log/gitlab - ./backups:/var/opt/gitlab/backups - /etc/localtime:/etc/localtime:ro shm_size: \u0026#39;256m\u0026#39; healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;/opt/gitlab/bin/gitlab-healthcheck\u0026#34;, \u0026#34;--fail\u0026#34;, \u0026#34;--max-time\u0026#34;, \u0026#34;10\u0026#34;] interval: 60s timeout: 30s retries: 5 start_period: 200s networks: default: name: gitlab-network EOF 步骤 3：生产环境配置 # cat \u0026gt; docker-compose.prod.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; version: \u0026#39;3.8\u0026#39; services: gitlab: image: gitlab/gitlab-ee:15.11.0-ee.0 container_name: gitlab-ee restart: unless-stopped hostname: gitlab.company.com environment: TZ: \u0026#39;Asia/Shanghai\u0026#39; GITLAB_OMNIBUS_CONFIG: | # 基础配置 external_url \u0026#39;https://gitlab.company.com\u0026#39; gitlab_rails[\u0026#39;time_zone\u0026#39;] = \u0026#39;Asia/Shanghai\u0026#39; # 数据库配置（外部 PostgreSQL） postgresql[\u0026#39;enable\u0026#39;] = false gitlab_rails[\u0026#39;db_adapter\u0026#39;] = \u0026#39;postgresql\u0026#39; gitlab_rails[\u0026#39;db_encoding\u0026#39;] = \u0026#39;unicode\u0026#39; gitlab_rails[\u0026#39;db_host\u0026#39;] = \u0026#39;postgres.company.com\u0026#39; gitlab_rails[\u0026#39;db_port\u0026#39;] = 5432 gitlab_rails[\u0026#39;db_database\u0026#39;] = \u0026#39;gitlab_production\u0026#39; gitlab_rails[\u0026#39;db_username\u0026#39;] = \u0026#39;gitlab\u0026#39; gitlab_rails[\u0026#39;db_password\u0026#39;] = \u0026#39;secure_password\u0026#39; # Redis 配置（外部 Redis） redis[\u0026#39;enable\u0026#39;] = false gitlab_rails[\u0026#39;redis_host\u0026#39;] = \u0026#39;redis.company.com\u0026#39; gitlab_rails[\u0026#39;redis_port\u0026#39;] = 6379 gitlab_rails[\u0026#39;redis_password\u0026#39;] = \u0026#39;redis_password\u0026#39; # 对象存储配置 gitlab_rails[\u0026#39;object_store\u0026#39;][\u0026#39;enabled\u0026#39;] = true gitlab_rails[\u0026#39;object_store\u0026#39;][\u0026#39;proxy_download\u0026#39;] = true gitlab_rails[\u0026#39;object_store\u0026#39;][\u0026#39;connection\u0026#39;] = { \u0026#39;provider\u0026#39; =\u0026gt; \u0026#39;AWS\u0026#39;, \u0026#39;region\u0026#39; =\u0026gt; \u0026#39;us-east-1\u0026#39;, \u0026#39;aws_access_key_id\u0026#39; =\u0026gt; \u0026#39;your-access-key\u0026#39;, \u0026#39;aws_secret_access_key\u0026#39; =\u0026gt; \u0026#39;your-secret-key\u0026#39;, \u0026#39;endpoint\u0026#39; =\u0026gt; \u0026#39;https://s3.company.com\u0026#39; } # LDAP 集成 gitlab_rails[\u0026#39;ldap_enabled\u0026#39;] = true gitlab_rails[\u0026#39;ldap_servers\u0026#39;] = { \u0026#39;main\u0026#39; =\u0026gt; { \u0026#39;label\u0026#39; =\u0026gt; \u0026#39;Company LDAP\u0026#39;, \u0026#39;host\u0026#39; =\u0026gt; \u0026#39;ldap.company.com\u0026#39;, \u0026#39;port\u0026#39; =\u0026gt; 389, \u0026#39;uid\u0026#39; =\u0026gt; \u0026#39;uid\u0026#39;, \u0026#39;bind_dn\u0026#39; =\u0026gt; \u0026#39;cn=gitlab,ou=services,dc=company,dc=com\u0026#39;, \u0026#39;password\u0026#39; =\u0026gt; \u0026#39;ldap_password\u0026#39;, \u0026#39;encryption\u0026#39; =\u0026gt; \u0026#39;plain\u0026#39;, \u0026#39;verify_certificates\u0026#39; =\u0026gt; true, \u0026#39;active_directory\u0026#39; =\u0026gt; false, \u0026#39;allow_username_or_email_login\u0026#39; =\u0026gt; true, \u0026#39;base\u0026#39; =\u0026gt; \u0026#39;dc=company,dc=com\u0026#39;, \u0026#39;user_filter\u0026#39; =\u0026gt; \u0026#39;(objectClass=inetOrgPerson)\u0026#39;, \u0026#39;attributes\u0026#39; =\u0026gt; { \u0026#39;username\u0026#39; =\u0026gt; [\u0026#39;uid\u0026#39;], \u0026#39;email\u0026#39; =\u0026gt; [\u0026#39;mail\u0026#39;], \u0026#39;name\u0026#39; =\u0026gt; \u0026#39;cn\u0026#39;, \u0026#39;first_name\u0026#39; =\u0026gt; \u0026#39;givenName\u0026#39;, \u0026#39;last_name\u0026#39; =\u0026gt; \u0026#39;sn\u0026#39; } } } # 高可用配置 gitlab_rails[\u0026#39;auto_migrate\u0026#39;] = false # 性能优化 unicorn[\u0026#39;worker_processes\u0026#39;] = 4 sidekiq[\u0026#39;max_concurrency\u0026#39;] = 25 postgresql[\u0026#39;shared_preload_libraries\u0026#39;] = \u0026#39;pg_stat_statements\u0026#39; # 监控配置 prometheus[\u0026#39;enable\u0026#39;] = true prometheus[\u0026#39;listen_address\u0026#39;] = \u0026#39;0.0.0.0:9090\u0026#39; node_exporter[\u0026#39;enable\u0026#39;] = true redis_exporter[\u0026#39;enable\u0026#39;] = true postgres_exporter[\u0026#39;enable\u0026#39;] = true ports: - \u0026#34;80:80\u0026#34; - \u0026#34;443:443\u0026#34; - \u0026#34;22:22\u0026#34; - \u0026#34;9090:9090\u0026#34; volumes: - ./config:/etc/gitlab - ./data:/var/opt/gitlab - ./logs:/var/log/gitlab - ./backups:/var/opt/gitlab/backups - /etc/localtime:/etc/localtime:ro deploy: resources: limits: memory: 8G cpus: \u0026#39;4\u0026#39; reservations: memory: 4G cpus: \u0026#39;2\u0026#39; healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;/opt/gitlab/bin/gitlab-healthcheck\u0026#34;, \u0026#34;--fail\u0026#34;, \u0026#34;--max-time\u0026#34;, \u0026#34;10\u0026#34;] interval: 60s timeout: 30s retries: 5 start_period: 300s networks: default: name: gitlab-network driver: bridge EOF 步骤 4：SSL 证书配置 # # 创建 SSL 证书目录 mkdir -p config/ssl # 生成自签名证书（测试环境） openssl req -x509 -nodes -days 365 -newkey rsa:2048 \\ -keyout config/ssl/gitlab.key \\ -out config/ssl/gitlab.crt \\ -subj \u0026#34;/C=CN/ST=Beijing/L=Beijing/O=Company/CN=gitlab.your-domain.com\u0026#34; # 或使用 Let\u0026#39;s Encrypt（生产环境） # certbot certonly --standalone -d gitlab.your-domain.com # cp /etc/letsencrypt/live/gitlab.your-domain.com/fullchain.pem config/ssl/gitlab.crt # cp /etc/letsencrypt/live/gitlab.your-domain.com/privkey.pem config/ssl/gitlab.key # 设置证书权限 chmod 600 config/ssl/gitlab.key chmod 644 config/ssl/gitlab.crt 步骤 5：启动服务 # # 开发环境启动 docker-compose up -d # 生产环境启动 docker-compose -f docker-compose.prod.yml up -d # 查看启动日志 docker-compose logs -f gitlab # 等待服务完全启动（通常需要 5-10 分钟） docker-compose exec gitlab gitlab-ctl status 步骤 6：初始化配置 # # 获取初始 root 密码 docker-compose exec gitlab cat /etc/gitlab/initial_root_password # 或者重置 root 密码 docker-compose exec gitlab gitlab-rake \u0026#34;gitlab:password:reset[root]\u0026#34; 步骤 7：验证部署 # 访问 GitLab Web 界面：https://gitlab.your-domain.com\n健康检查脚本：\ncat \u0026gt; check-gitlab-health.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash GITLAB_URL=\u0026#34;https://gitlab.your-domain.com\u0026#34; CONTAINER_NAME=\u0026#34;gitlab-ce\u0026#34; echo \u0026#34;=== GitLab 健康检查 ===\u0026#34; # 检查容器状态 echo \u0026#34;1. 检查容器状态\u0026#34; docker ps --filter \u0026#34;name=$CONTAINER_NAME\u0026#34; --format \u0026#34;table {{.Names}}\\t{{.Status}}\\t{{.Ports}}\u0026#34; # 检查 GitLab 服务状态 echo -e \u0026#34;\\n2. 检查 GitLab 服务状态\u0026#34; docker exec $CONTAINER_NAME gitlab-ctl status # 检查 Web 界面响应 echo -e \u0026#34;\\n3. 检查 Web 界面响应\u0026#34; HTTP_CODE=$(curl -s -o /dev/null -w \u0026#34;%{http_code}\u0026#34; $GITLAB_URL) if [ \u0026#34;$HTTP_CODE\u0026#34; = \u0026#34;200\u0026#34; ]; then echo \u0026#34;✓ Web 界面响应正常\u0026#34; else echo \u0026#34;✗ Web 界面响应异常 (HTTP $HTTP_CODE)\u0026#34; fi # 检查 API 响应 echo -e \u0026#34;\\n4. 检查 API 响应\u0026#34; API_RESPONSE=$(curl -s \u0026#34;$GITLAB_URL/api/v4/version\u0026#34;) if [ $? -eq 0 ]; then echo \u0026#34;✓ API 响应正常: $API_RESPONSE\u0026#34; else echo \u0026#34;✗ API 响应异常\u0026#34; fi # 检查资源使用 echo -e \u0026#34;\\n5. 检查资源使用\u0026#34; docker stats $CONTAINER_NAME --no-stream --format \u0026#34;table {{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\u0026#34; echo -e \u0026#34;\\n=== 健康检查完成 ===\u0026#34; EOF chmod +x check-gitlab-health.sh ./check-gitlab-health.sh 方案二：Kubernetes 部署 # Helm Chart 部署 # # 添加 GitLab Helm 仓库 helm repo add gitlab https://charts.gitlab.io/ helm repo update # 创建命名空间 kubectl create namespace gitlab # 创建配置文件 cat \u0026gt; gitlab-values.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; global: hosts: domain: your-domain.com https: true ingress: configureCertmanager: true class: nginx certmanager: install: true nginx-ingress: enabled: true prometheus: install: true grafana: enabled: true gitlab-runner: install: true runners: privileged: true EOF # 部署 GitLab helm upgrade --install gitlab gitlab/gitlab \\ -f gitlab-values.yaml \\ -n gitlab \\ --timeout 600s 反向代理配置 # Nginx 反向代理 # 安装 Nginx # # CentOS/RHEL yum install -y nginx # Ubuntu/Debian apt-get update \u0026amp;\u0026amp; apt-get install -y nginx 基础代理配置 # cat \u0026gt; /etc/nginx/conf.d/gitlab.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # GitLab upstream upstream gitlab { server 127.0.0.1:8080 fail_timeout=0; } # HTTP 重定向到 HTTPS server { listen 80; server_name gitlab.your-domain.com; return 301 https://$server_name$request_uri; } # HTTPS 配置 server { listen 443 ssl http2; server_name gitlab.your-domain.com; # SSL 配置 ssl_certificate /etc/nginx/ssl/gitlab.crt; ssl_certificate_key /etc/nginx/ssl/gitlab.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 安全头 add_header Strict-Transport-Security \u0026#34;max-age=31536000; includeSubDomains\u0026#34; always; add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; add_header X-XSS-Protection \u0026#34;1; mode=block\u0026#34; always; add_header Referrer-Policy \u0026#34;strict-origin-when-cross-origin\u0026#34; always; # 日志配置 access_log /var/log/nginx/gitlab.access.log; error_log /var/log/nginx/gitlab.error.log; # 客户端上传限制 client_max_body_size 1G; # 代理配置 location / { proxy_pass http://gitlab; proxy_redirect off; # 代理头设置 proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Ssl on; # 超时设置 proxy_read_timeout 300; proxy_connect_timeout 300; proxy_send_timeout 300; # WebSocket 支持 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; } # Git HTTP(S) 优化 location ~ ^/[\\w\\.-]+/[\\w\\.-]+/repository/archive { proxy_pass http://gitlab; proxy_buffering off; proxy_request_buffering off; } # Container Registry 代理 location /v2/ { proxy_pass http://gitlab; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; proxy_request_buffering off; } } EOF 高可用 Nginx 配置 # cat \u0026gt; /etc/nginx/conf.d/gitlab-ha.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # GitLab 高可用 upstream upstream gitlab_ha { least_conn; server gitlab-node1.your-domain.com:80 max_fails=3 fail_timeout=30s; server gitlab-node2.your-domain.com:80 max_fails=3 fail_timeout=30s; server gitlab-node3.your-domain.com:80 max_fails=3 fail_timeout=30s; # 健康检查 keepalive 32; } server { listen 443 ssl http2; server_name gitlab.your-domain.com; # SSL 配置（同上） location / { proxy_pass http://gitlab_ha; # 会话保持 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; # 健康检查 proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504; proxy_next_upstream_tries 3; proxy_next_upstream_timeout 10s; } } EOF 启动和验证 # # 测试配置 nginx -t # 重新加载配置 nginx -s reload # 设置开机自启 systemctl enable nginx systemctl start nginx # 查看状态 systemctl status nginx Traefik 反向代理 # Docker Compose 集成 # cat \u0026gt; docker-compose.traefik.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; version: \u0026#39;3.8\u0026#39; services: traefik: image: traefik:v2.9 container_name: traefik restart: unless-stopped ports: - \u0026#34;80:80\u0026#34; - \u0026#34;443:443\u0026#34; - \u0026#34;8080:8080\u0026#34; volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik:/etc/traefik - ./acme.json:/acme.json labels: - \u0026#34;traefik.enable=true\u0026#34; - \u0026#34;traefik.http.routers.dashboard.rule=Host(`traefik.your-domain.com`)\u0026#34; - \u0026#34;traefik.http.routers.dashboard.tls=true\u0026#34; - \u0026#34;traefik.http.routers.dashboard.tls.certresolver=letsencrypt\u0026#34; gitlab: image: gitlab/gitlab-ce:15.11.0-ce.0 container_name: gitlab-ce restart: unless-stopped volumes: - ./config:/etc/gitlab - ./data:/var/opt/gitlab - ./logs:/var/log/gitlab labels: - \u0026#34;traefik.enable=true\u0026#34; - \u0026#34;traefik.http.routers.gitlab.rule=Host(`gitlab.your-domain.com`)\u0026#34; - \u0026#34;traefik.http.routers.gitlab.tls=true\u0026#34; - \u0026#34;traefik.http.routers.gitlab.tls.certresolver=letsencrypt\u0026#34; - \u0026#34;traefik.http.services.gitlab.loadbalancer.server.port=80\u0026#34; - \u0026#34;traefik.http.middlewares.gitlab-headers.headers.customrequestheaders.X-Forwarded-Proto=https\u0026#34; - \u0026#34;traefik.http.routers.gitlab.middlewares=gitlab-headers\u0026#34; networks: default: name: traefik-network EOF 功能验证 # 基础功能测试 # # 创建测试脚本 cat \u0026gt; test-gitlab-functions.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash GITLAB_URL=\u0026#34;https://gitlab.your-domain.com\u0026#34; API_TOKEN=\u0026#34;your-api-token\u0026#34; echo \u0026#34;=== GitLab 功能测试 ===\u0026#34; # 测试 API 访问 echo \u0026#34;1. 测试 API 访问\u0026#34; curl -s -H \u0026#34;PRIVATE-TOKEN: $API_TOKEN\u0026#34; \u0026#34;$GITLAB_URL/api/v4/version\u0026#34; | jq . # 测试项目创建 echo -e \u0026#34;\\n2. 测试项目创建\u0026#34; PROJECT_DATA=\u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;test-project\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Test project for GitLab functionality\u0026#34;, \u0026#34;visibility\u0026#34;: \u0026#34;private\u0026#34; }\u0026#39; PROJECT_RESPONSE=$(curl -s -X POST \\ -H \u0026#34;PRIVATE-TOKEN: $API_TOKEN\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#34;$PROJECT_DATA\u0026#34; \\ \u0026#34;$GITLAB_URL/api/v4/projects\u0026#34;) PROJECT_ID=$(echo $PROJECT_RESPONSE | jq -r .id) echo \u0026#34;项目创建成功，ID: $PROJECT_ID\u0026#34; # 测试文件上传 echo -e \u0026#34;\\n3. 测试文件上传\u0026#34; echo \u0026#34;# Test README\u0026#34; \u0026gt; README.md FILE_RESPONSE=$(curl -s -X POST \\ -H \u0026#34;PRIVATE-TOKEN: $API_TOKEN\u0026#34; \\ -F \u0026#34;branch=main\u0026#34; \\ -F \u0026#34;commit_message=Add README\u0026#34; \\ -F \u0026#34;file=@README.md\u0026#34; \\ \u0026#34;$GITLAB_URL/api/v4/projects/$PROJECT_ID/repository/files/README.md\u0026#34;) echo \u0026#34;文件上传结果: $(echo $FILE_RESPONSE | jq -r .file_path)\u0026#34; # 清理测试项目 echo -e \u0026#34;\\n4. 清理测试项目\u0026#34; curl -s -X DELETE \\ -H \u0026#34;PRIVATE-TOKEN: $API_TOKEN\u0026#34; \\ \u0026#34;$GITLAB_URL/api/v4/projects/$PROJECT_ID\u0026#34; echo \u0026#34;测试完成\u0026#34; EOF chmod +x test-gitlab-functions.sh GitLab 企业级配置优化 # 系统级配置优化 # 性能调优配置 # # 编辑 GitLab 配置文件 docker-compose exec gitlab vi /etc/gitlab/gitlab.rb # 或者通过环境变量配置 cat \u0026gt;\u0026gt; docker-compose.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; environment: GITLAB_OMNIBUS_CONFIG: | # 性能优化配置 unicorn[\u0026#39;worker_processes\u0026#39;] = 4 unicorn[\u0026#39;worker_timeout\u0026#39;] = 60 # Sidekiq 优化 sidekiq[\u0026#39;max_concurrency\u0026#39;] = 25 sidekiq[\u0026#39;min_concurrency\u0026#39;] = 5 # PostgreSQL 优化 postgresql[\u0026#39;shared_buffers\u0026#39;] = \u0026#34;256MB\u0026#34; postgresql[\u0026#39;effective_cache_size\u0026#39;] = \u0026#34;1GB\u0026#34; postgresql[\u0026#39;work_mem\u0026#39;] = \u0026#34;16MB\u0026#34; postgresql[\u0026#39;maintenance_work_mem\u0026#39;] = \u0026#34;64MB\u0026#34; postgresql[\u0026#39;checkpoint_completion_target\u0026#39;] = 0.9 postgresql[\u0026#39;wal_buffers\u0026#39;] = \u0026#34;16MB\u0026#34; postgresql[\u0026#39;default_statistics_target\u0026#39;] = 100 # Redis 优化 redis[\u0026#39;maxmemory\u0026#39;] = \u0026#34;256mb\u0026#34; redis[\u0026#39;maxmemory_policy\u0026#39;] = \u0026#34;allkeys-lru\u0026#34; # Gitaly 优化 gitaly[\u0026#39;ruby_max_rss\u0026#39;] = 300000000 gitaly[\u0026#39;concurrency\u0026#39;] = [ { \u0026#39;rpc\u0026#39; =\u0026gt; \u0026#34;/gitaly.SmartHTTPService/PostReceivePack\u0026#34;, \u0026#39;max_per_repo\u0026#39; =\u0026gt; 3 }, { \u0026#39;rpc\u0026#39; =\u0026gt; \u0026#34;/gitaly.SSHService/SSHUploadPack\u0026#34;, \u0026#39;max_per_repo\u0026#39; =\u0026gt; 3 } ] EOF 安全配置强化 # cat \u0026gt; security-config.rb \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # 安全配置 gitlab_rails[\u0026#39;rack_attack_git_basic_auth\u0026#39;] = { \u0026#39;enabled\u0026#39; =\u0026gt; true, \u0026#39;ip_whitelist\u0026#39; =\u0026gt; [\u0026#34;127.0.0.1\u0026#34;, \u0026#34;::1\u0026#34;], \u0026#39;maxretry\u0026#39; =\u0026gt; 10, \u0026#39;findtime\u0026#39; =\u0026gt; 60, \u0026#39;bantime\u0026#39; =\u0026gt; 3600 } # 密码策略 gitlab_rails[\u0026#39;password_authentication_enabled_for_web\u0026#39;] = true gitlab_rails[\u0026#39;password_authentication_enabled_for_git\u0026#39;] = true gitlab_rails[\u0026#39;password_minimum_length\u0026#39;] = 8 gitlab_rails[\u0026#39;password_require_uppercase\u0026#39;] = true gitlab_rails[\u0026#39;password_require_lowercase\u0026#39;] = true gitlab_rails[\u0026#39;password_require_number\u0026#39;] = true gitlab_rails[\u0026#39;password_require_symbol\u0026#39;] = false # 会话安全 gitlab_rails[\u0026#39;session_expire_delay\u0026#39;] = 10080 gitlab_rails[\u0026#39;session_store_secure\u0026#39;] = true # API 限制 gitlab_rails[\u0026#39;rate_limit_requests_per_period\u0026#39;] = 1000 gitlab_rails[\u0026#39;rate_limit_period\u0026#39;] = 60 # 文件上传限制 gitlab_rails[\u0026#39;max_attachment_size\u0026#39;] = 100 nginx[\u0026#39;client_max_body_size\u0026#39;] = \u0026#39;100m\u0026#39; # 禁用不必要的功能 gitlab_rails[\u0026#39;usage_ping_enabled\u0026#39;] = false gitlab_rails[\u0026#39;sentry_enabled\u0026#39;] = false EOF 功能模块配置 # 关闭 Auto DevOps # # 通过 API 全局关闭 Auto DevOps curl -X PUT \\ -H \u0026#34;PRIVATE-TOKEN: your-api-token\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;auto_devops_enabled\u0026#34;: false}\u0026#39; \\ \u0026#34;https://gitlab.your-domain.com/api/v4/application/settings\u0026#34; Webhook 配置优化 # # 启用 Webhook 并配置安全设置 cat \u0026gt;\u0026gt; gitlab.rb \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; gitlab_rails[\u0026#39;webhook_timeout\u0026#39;] = 10 gitlab_rails[\u0026#39;webhook_max_redirects\u0026#39;] = 3 gitlab_rails[\u0026#39;webhook_allowed_local_network_ranges\u0026#39;] = [\u0026#39;10.0.0.0/8\u0026#39;, \u0026#39;172.16.0.0/12\u0026#39;, \u0026#39;192.168.0.0/16\u0026#39;] EOF 接口请求限制配置 # # 配置 Rate Limiting cat \u0026gt;\u0026gt; gitlab.rb \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; gitlab_rails[\u0026#39;rate_limit_by_ip\u0026#39;] = { \u0026#39;enabled\u0026#39; =\u0026gt; true, \u0026#39;requests_per_period\u0026#39; =\u0026gt; 1000, \u0026#39;period\u0026#39; =\u0026gt; 60, \u0026#39;ban_duration\u0026#39; =\u0026gt; 3600 } gitlab_rails[\u0026#39;rate_limit_by_user\u0026#39;] = { \u0026#39;enabled\u0026#39; =\u0026gt; true, \u0026#39;requests_per_period\u0026#39; =\u0026gt; 1000, \u0026#39;period\u0026#39; =\u0026gt; 60, \u0026#39;ban_duration\u0026#39; =\u0026gt; 3600 } EOF 监控配置 # # 启用内置监控 cat \u0026gt;\u0026gt; gitlab.rb \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Prometheus 配置 prometheus[\u0026#39;enable\u0026#39;] = true prometheus[\u0026#39;listen_address\u0026#39;] = \u0026#39;0.0.0.0:9090\u0026#39; prometheus[\u0026#39;scrape_interval\u0026#39;] = 15 prometheus[\u0026#39;scrape_timeout\u0026#39;] = 15 # Node Exporter node_exporter[\u0026#39;enable\u0026#39;] = true node_exporter[\u0026#39;listen_address\u0026#39;] = \u0026#39;0.0.0.0:9100\u0026#39; # Redis Exporter redis_exporter[\u0026#39;enable\u0026#39;] = true redis_exporter[\u0026#39;listen_address\u0026#39;] = \u0026#39;0.0.0.0:9121\u0026#39; # PostgreSQL Exporter postgres_exporter[\u0026#39;enable\u0026#39;] = true postgres_exporter[\u0026#39;listen_address\u0026#39;] = \u0026#39;0.0.0.0:9187\u0026#39; # Grafana 配置 grafana[\u0026#39;enable\u0026#39;] = true grafana[\u0026#39;admin_password\u0026#39;] = \u0026#39;secure_password\u0026#39; grafana[\u0026#39;disable_login_form\u0026#39;] = false grafana[\u0026#39;allow_user_sign_up\u0026#39;] = false EOF 本地化配置 # # 时区和语言配置 cat \u0026gt;\u0026gt; gitlab.rb \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; gitlab_rails[\u0026#39;time_zone\u0026#39;] = \u0026#39;Asia/Shanghai\u0026#39; gitlab_rails[\u0026#39;default_locale\u0026#39;] = \u0026#39;zh_CN\u0026#39; gitlab_rails[\u0026#39;default_theme\u0026#39;] = 2 # 深色主题 EOF SSH 访问控制 # # SSH 配置优化 cat \u0026gt;\u0026gt; gitlab.rb \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; gitlab_rails[\u0026#39;gitlab_shell_ssh_port\u0026#39;] = 22 gitlab_rails[\u0026#39;gitlab_shell_git_timeout\u0026#39;] = 800 # 禁用 SSH 访问（如果只使用 HTTPS） gitlab_rails[\u0026#39;gitlab_shell_ssh_port\u0026#39;] = nil EOF 用户注册控制 # # 用户注册和权限配置 cat \u0026gt;\u0026gt; gitlab.rb \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; gitlab_rails[\u0026#39;signup_enabled\u0026#39;] = false gitlab_rails[\u0026#39;signin_enabled\u0026#39;] = true gitlab_rails[\u0026#39;require_two_factor_authentication\u0026#39;] = false gitlab_rails[\u0026#39;two_factor_grace_period\u0026#39;] = 48 # 新用户默认权限 gitlab_rails[\u0026#39;default_can_create_group\u0026#39;] = false gitlab_rails[\u0026#39;default_can_create_team\u0026#39;] = false gitlab_rails[\u0026#39;default_projects_limit\u0026#39;] = 0 gitlab_rails[\u0026#39;default_project_visibility\u0026#39;] = \u0026#39;private\u0026#39; gitlab_rails[\u0026#39;default_snippet_visibility\u0026#39;] = \u0026#39;private\u0026#39; gitlab_rails[\u0026#39;default_group_visibility\u0026#39;] = \u0026#39;private\u0026#39; EOF 高级功能配置 # Container Registry 配置 # # 启用 Container Registry cat \u0026gt;\u0026gt; gitlab.rb \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; registry_external_url \u0026#39;https://registry.your-domain.com\u0026#39; registry[\u0026#39;enable\u0026#39;] = true registry[\u0026#39;registry_http_addr\u0026#39;] = \u0026#34;0.0.0.0:5000\u0026#34; registry[\u0026#39;debug_addr\u0026#39;] = \u0026#34;localhost:5001\u0026#34; registry[\u0026#39;log_level\u0026#39;] = \u0026#34;info\u0026#34; registry[\u0026#39;rootcertbundle\u0026#39;] = \u0026#34;/etc/gitlab/ssl/ca.crt\u0026#34; # 存储配置 registry[\u0026#39;storage\u0026#39;] = { \u0026#39;s3\u0026#39; =\u0026gt; { \u0026#39;accesskey\u0026#39; =\u0026gt; \u0026#39;your-access-key\u0026#39;, \u0026#39;secretkey\u0026#39; =\u0026gt; \u0026#39;your-secret-key\u0026#39;, \u0026#39;bucket\u0026#39; =\u0026gt; \u0026#39;gitlab-registry\u0026#39;, \u0026#39;region\u0026#39; =\u0026gt; \u0026#39;us-east-1\u0026#39;, \u0026#39;regionendpoint\u0026#39; =\u0026gt; \u0026#39;https://s3.your-domain.com\u0026#39; } } # 垃圾回收 registry[\u0026#39;gc_cron_jobs\u0026#39;] = [ { \u0026#39;cron\u0026#39; =\u0026gt; \u0026#39;0 2 * * *\u0026#39;, \u0026#39;job\u0026#39; =\u0026gt; \u0026#39;registry garbage-collect /etc/docker/registry/config.yml\u0026#39; } ] EOF CI/CD Runner 集成 # # GitLab Runner 配置 cat \u0026gt; register-runner.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash GITLAB_URL=\u0026#34;https://gitlab.your-domain.com\u0026#34; REGISTRATION_TOKEN=\u0026#34;your-registration-token\u0026#34; # 注册 Docker Runner gitlab-runner register \\ --non-interactive \\ --url \u0026#34;$GITLAB_URL\u0026#34; \\ --registration-token \u0026#34;$REGISTRATION_TOKEN\u0026#34; \\ --executor \u0026#34;docker\u0026#34; \\ --docker-image alpine:latest \\ --description \u0026#34;docker-runner\u0026#34; \\ --tag-list \u0026#34;docker,linux\u0026#34; \\ --run-untagged=\u0026#34;true\u0026#34; \\ --locked=\u0026#34;false\u0026#34; \\ --access-level=\u0026#34;not_protected\u0026#34; # 注册 Kubernetes Runner gitlab-runner register \\ --non-interactive \\ --url \u0026#34;$GITLAB_URL\u0026#34; \\ --registration-token \u0026#34;$REGISTRATION_TOKEN\u0026#34; \\ --executor \u0026#34;kubernetes\u0026#34; \\ --kubernetes-host \u0026#34;https://kubernetes.your-domain.com\u0026#34; \\ --kubernetes-namespace \u0026#34;gitlab-runner\u0026#34; \\ --description \u0026#34;k8s-runner\u0026#34; \\ --tag-list \u0026#34;kubernetes,k8s\u0026#34; EOF chmod +x register-runner.sh 备份配置 # # 自动备份配置 cat \u0026gt;\u0026gt; gitlab.rb \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; gitlab_rails[\u0026#39;backup_keep_time\u0026#39;] = 604800 # 7 days gitlab_rails[\u0026#39;backup_path\u0026#39;] = \u0026#34;/var/opt/gitlab/backups\u0026#34; gitlab_rails[\u0026#39;backup_archive_permissions\u0026#39;] = 0644 gitlab_rails[\u0026#39;backup_pg_schema\u0026#39;] = \u0026#39;public\u0026#39; # 备份加密 gitlab_rails[\u0026#39;backup_encryption\u0026#39;] = \u0026#39;aes256\u0026#39; gitlab_rails[\u0026#39;backup_encryption_key\u0026#39;] = \u0026#39;your-encryption-key\u0026#39; # 远程备份存储 gitlab_rails[\u0026#39;backup_upload_connection\u0026#39;] = { \u0026#39;provider\u0026#39; =\u0026gt; \u0026#39;AWS\u0026#39;, \u0026#39;region\u0026#39; =\u0026gt; \u0026#39;us-east-1\u0026#39;, \u0026#39;aws_access_key_id\u0026#39; =\u0026gt; \u0026#39;your-access-key\u0026#39;, \u0026#39;aws_secret_access_key\u0026#39; =\u0026gt; \u0026#39;your-secret-key\u0026#39; } gitlab_rails[\u0026#39;backup_upload_remote_directory\u0026#39;] = \u0026#39;gitlab-backups\u0026#39; gitlab_rails[\u0026#39;backup_multipart_chunk_size\u0026#39;] = 104857600 EOF # 创建备份脚本 cat \u0026gt; backup-gitlab.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;开始 GitLab 备份...\u0026#34; docker-compose exec gitlab gitlab-backup create echo \u0026#34;备份配置文件...\u0026#34; tar -czf /data/gitlab/backups/gitlab-config-$(date +%Y%m%d).tar.gz \\ /data/gitlab/config/gitlab.rb \\ /data/gitlab/config/gitlab-secrets.json echo \u0026#34;备份完成\u0026#34; EOF chmod +x backup-gitlab.sh # 设置定时备份 echo \u0026#34;0 2 * * * /data/gitlab/backup-gitlab.sh\u0026#34; | crontab - # 数据恢复与灾难恢复 ## 数据备份策略 ### 完整备份脚本 ```bash cat \u0026gt; gitlab-full-backup.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash BACKUP_DIR=\u0026#34;/data/gitlab/backups\u0026#34; RETENTION_DAYS=30 DATE=$(date +%Y%m%d_%H%M%S) echo \u0026#34;=== GitLab 完整备份脚本 ===\u0026#34; echo \u0026#34;开始时间: $(date)\u0026#34; ## 创建备份目录 mkdir -p $BACKUP_DIR ## 1. 创建 GitLab 数据备份 echo \u0026#34;1. 创建 GitLab 数据备份...\u0026#34; docker-compose exec -T gitlab gitlab-backup create BACKUP=$DATE ## 2. 备份配置文件 echo \u0026#34;2. 备份配置文件...\u0026#34; tar -czf \u0026#34;$BACKUP_DIR/gitlab-config-$DATE.tar.gz\u0026#34; \\ config/gitlab.rb \\ config/gitlab-secrets.json \\ config/ssl/ ## 3. 备份 Docker Compose 文件 echo \u0026#34;3. 备份 Docker Compose 文件...\u0026#34; cp docker-compose.yml \u0026#34;$BACKUP_DIR/docker-compose-$DATE.yml\u0026#34; ## 4. 创建备份清单 echo \u0026#34;4. 创建备份清单...\u0026#34; cat \u0026gt; \u0026#34;$BACKUP_DIR/backup-manifest-$DATE.txt\u0026#34; \u0026lt;\u0026lt; MANIFEST GitLab 备份清单 ================ 备份时间: $(date) GitLab 版本: $(docker-compose exec -T gitlab cat /opt/gitlab/version-manifest.txt | head -1) 数据备份: ${DATE}_gitlab_backup.tar 配置备份: gitlab-config-$DATE.tar.gz Compose 文件: docker-compose-$DATE.yml 备份文件列表: $(ls -la $BACKUP_DIR/*$DATE*) MANIFEST ## 5. 验证备份完整性 echo \u0026#34;5. 验证备份完整性...\u0026#34; if [ -f \u0026#34;$BACKUP_DIR/${DATE}_gitlab_backup.tar\u0026#34; ]; then echo \u0026#34;✓ 数据备份文件存在\u0026#34; BACKUP_SIZE=$(du -h \u0026#34;$BACKUP_DIR/${DATE}_gitlab_backup.tar\u0026#34; | cut -f1) echo \u0026#34; 备份大小: $BACKUP_SIZE\u0026#34; else echo \u0026#34;✗ 数据备份文件不存在\u0026#34; exit 1 fi ## 6. 清理过期备份 echo \u0026#34;6. 清理过期备份...\u0026#34; find $BACKUP_DIR -name \u0026#34;*gitlab_backup.tar\u0026#34; -mtime +$RETENTION_DAYS -delete find $BACKUP_DIR -name \u0026#34;gitlab-config-*.tar.gz\u0026#34; -mtime +$RETENTION_DAYS -delete find $BACKUP_DIR -name \u0026#34;docker-compose-*.yml\u0026#34; -mtime +$RETENTION_DAYS -delete find $BACKUP_DIR -name \u0026#34;backup-manifest-*.txt\u0026#34; -mtime +$RETENTION_DAYS -delete ## 7. 发送备份报告 echo \u0026#34;7. 发送备份报告...\u0026#34; if command -v mail \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then cat \u0026#34;$BACKUP_DIR/backup-manifest-$DATE.txt\u0026#34; | \\ mail -s \u0026#34;GitLab 备份报告 - $(date +%Y-%m-%d)\u0026#34; admin@your-domain.com fi echo \u0026#34;=== 备份完成 ===\u0026#34; echo \u0026#34;备份文件: $BACKUP_DIR/${DATE}_gitlab_backup.tar\u0026#34; echo \u0026#34;配置文件: $BACKUP_DIR/gitlab-config-$DATE.tar.gz\u0026#34; EOF chmod +x gitlab-full-backup.sh 增量备份脚本 # cat \u0026gt; gitlab-incremental-backup.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash BACKUP_DIR=\u0026#34;/data/gitlab/backups\u0026#34; INCREMENTAL_DIR=\u0026#34;$BACKUP_DIR/incremental\u0026#34; DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $INCREMENTAL_DIR echo \u0026#34;=== GitLab 增量备份 ===\u0026#34; ## 查找最近的完整备份 LAST_FULL_BACKUP=$(ls -t $BACKUP_DIR/*_gitlab_backup.tar 2\u0026gt;/dev/null | head -1) if [ -z \u0026#34;$LAST_FULL_BACKUP\u0026#34; ]; then echo \u0026#34;未找到完整备份，执行完整备份...\u0026#34; ./gitlab-full-backup.sh exit 0 fi echo \u0026#34;基于完整备份: $(basename $LAST_FULL_BACKUP)\u0026#34; ## 创建增量备份（仅备份变更的仓库） docker-compose exec -T gitlab gitlab-backup create BACKUP=incremental_$DATE STRATEGY=incremental ## 备份最近修改的配置 find config/ -newer \u0026#34;$LAST_FULL_BACKUP\u0026#34; -type f | \\ tar -czf \u0026#34;$INCREMENTAL_DIR/config-incremental-$DATE.tar.gz\u0026#34; -T - echo \u0026#34;增量备份完成: incremental_${DATE}_gitlab_backup.tar\u0026#34; EOF chmod +x gitlab-incremental-backup.sh 数据恢复 # 完整恢复脚本 # cat \u0026gt; gitlab-restore.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash BACKUP_DIR=\u0026#34;/data/gitlab/backups\u0026#34; echo \u0026#34;=== GitLab 数据恢复脚本 ===\u0026#34; ## 列出可用备份 echo \u0026#34;可用的备份文件:\u0026#34; ls -la $BACKUP_DIR/*_gitlab_backup.tar | nl ## 选择备份文件 read -p \u0026#34;请输入要恢复的备份文件编号: \u0026#34; BACKUP_NUM BACKUP_FILE=$(ls $BACKUP_DIR/*_gitlab_backup.tar | sed -n \u0026#34;${BACKUP_NUM}p\u0026#34;) if [ ! -f \u0026#34;$BACKUP_FILE\u0026#34; ]; then echo \u0026#34;备份文件不存在: $BACKUP_FILE\u0026#34; exit 1 fi BACKUP_TIMESTAMP=$(basename $BACKUP_FILE | sed \u0026#39;s/_gitlab_backup.tar//\u0026#39;) echo \u0026#34;选择的备份: $BACKUP_TIMESTAMP\u0026#34; read -p \u0026#34;确认恢复？这将覆盖现有数据 (yes/no): \u0026#34; CONFIRM if [ \u0026#34;$CONFIRM\u0026#34; != \u0026#34;yes\u0026#34; ]; then echo \u0026#34;恢复已取消\u0026#34; exit 0 fi ## 停止 GitLab 服务 echo \u0026#34;停止 GitLab 服务...\u0026#34; docker-compose stop gitlab ## 恢复数据 echo \u0026#34;恢复 GitLab 数据...\u0026#34; docker-compose run --rm gitlab gitlab-backup restore BACKUP=$BACKUP_TIMESTAMP ## 恢复配置文件 if [ -f \u0026#34;$BACKUP_DIR/gitlab-config-$BACKUP_TIMESTAMP.tar.gz\u0026#34; ]; then echo \u0026#34;恢复配置文件...\u0026#34; tar -xzf \u0026#34;$BACKUP_DIR/gitlab-config-$BACKUP_TIMESTAMP.tar.gz\u0026#34; fi ## 重新配置 GitLab echo \u0026#34;重新配置 GitLab...\u0026#34; docker-compose run --rm gitlab gitlab-ctl reconfigure ## 启动服务 echo \u0026#34;启动 GitLab 服务...\u0026#34; docker-compose up -d gitlab ## 等待服务启动 echo \u0026#34;等待服务启动...\u0026#34; sleep 60 ## 验证恢复 echo \u0026#34;验证恢复结果...\u0026#34; docker-compose exec gitlab gitlab-rake gitlab:check echo \u0026#34;=== 恢复完成 ===\u0026#34; EOF chmod +x gitlab-restore.sh 监控与告警 # 系统监控 # Prometheus 监控配置 # cat \u0026gt; prometheus.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; global: scrape_interval: 15s evaluation_interval: 15s rule_files: - \u0026#34;gitlab-rules.yml\u0026#34; scrape_configs: - job_name: \u0026#39;gitlab\u0026#39; static_configs: - targets: [\u0026#39;gitlab:9090\u0026#39;] metrics_path: \u0026#39;/-/metrics\u0026#39; - job_name: \u0026#39;gitlab-workhorse\u0026#39; static_configs: - targets: [\u0026#39;gitlab:9229\u0026#39;] - job_name: \u0026#39;gitlab-sidekiq\u0026#39; static_configs: - targets: [\u0026#39;gitlab:8082\u0026#39;] - job_name: \u0026#39;gitlab-gitaly\u0026#39; static_configs: - targets: [\u0026#39;gitlab:9236\u0026#39;] alerting: alertmanagers: - static_configs: - targets: - alertmanager:9093 EOF 告警规则配置 # cat \u0026gt; gitlab-rules.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; groups: - name: gitlab rules: - alert: GitLabDown expr: up{job=\u0026#34;gitlab\u0026#34;} == 0 for: 5m labels: severity: critical annotations: summary: \u0026#34;GitLab is down\u0026#34; description: \u0026#34;GitLab has been down for more than 5 minutes.\u0026#34; - alert: GitLabHighCPU expr: rate(process_cpu_seconds_total{job=\u0026#34;gitlab\u0026#34;}[5m]) * 100 \u0026gt; 80 for: 10m labels: severity: warning annotations: summary: \u0026#34;GitLab high CPU usage\u0026#34; description: \u0026#34;GitLab CPU usage is above 80% for more than 10 minutes.\u0026#34; - alert: GitLabHighMemory expr: process_resident_memory_bytes{job=\u0026#34;gitlab\u0026#34;} / 1024 / 1024 / 1024 \u0026gt; 4 for: 10m labels: severity: warning annotations: summary: \u0026#34;GitLab high memory usage\u0026#34; description: \u0026#34;GitLab memory usage is above 4GB for more than 10 minutes.\u0026#34; - alert: GitLabDiskSpaceLow expr: (node_filesystem_avail_bytes{mountpoint=\u0026#34;/var/opt/gitlab\u0026#34;} / node_filesystem_size_bytes{mountpoint=\u0026#34;/var/opt/gitlab\u0026#34;}) * 100 \u0026lt; 10 for: 5m labels: severity: critical annotations: summary: \u0026#34;GitLab disk space low\u0026#34; description: \u0026#34;GitLab disk space is below 10%.\u0026#34; - alert: GitLabBackupFailed expr: increase(gitlab_backup_last_backup_time[1d]) == 0 for: 1d labels: severity: warning annotations: summary: \u0026#34;GitLab backup failed\u0026#34; description: \u0026#34;GitLab backup has not run successfully in the last 24 hours.\u0026#34; EOF 监控脚本 # cat \u0026gt; gitlab-monitor.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash GITLAB_URL=\u0026#34;https://gitlab.your-domain.com\u0026#34; CONTAINER_NAME=\u0026#34;gitlab-ce\u0026#34; LOG_FILE=\u0026#34;/var/log/gitlab-monitor.log\u0026#34; ## 日志函数 log() { echo \u0026#34;[$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;)] $1\u0026#34; | tee -a $LOG_FILE } ## 检查容器状态 check_container() { if docker ps --filter \u0026#34;name=$CONTAINER_NAME\u0026#34; --format \u0026#34;{{.Names}}\u0026#34; | grep -q $CONTAINER_NAME; then log \u0026#34;✓ GitLab 容器运行正常\u0026#34; return 0 else log \u0026#34;✗ GitLab 容器未运行\u0026#34; return 1 fi } ## 检查 Web 服务 check_web_service() { local response_code=$(curl -s -o /dev/null -w \u0026#34;%{http_code}\u0026#34; $GITLAB_URL) if [ \u0026#34;$response_code\u0026#34; = \u0026#34;200\u0026#34; ]; then log \u0026#34;✓ GitLab Web 服务响应正常\u0026#34; return 0 else log \u0026#34;✗ GitLab Web 服务响应异常 (HTTP $response_code)\u0026#34; return 1 fi } ## 检查 API 服务 check_api_service() { local api_response=$(curl -s \u0026#34;$GITLAB_URL/api/v4/version\u0026#34;) if echo \u0026#34;$api_response\u0026#34; | grep -q \u0026#34;version\u0026#34;; then log \u0026#34;✓ GitLab API 服务正常\u0026#34; return 0 else log \u0026#34;✗ GitLab API 服务异常\u0026#34; return 1 fi } ## 检查磁盘空间 check_disk_space() { local usage=$(df /data/gitlab | awk \u0026#39;NR==2 {print $5}\u0026#39; | sed \u0026#39;s/%//\u0026#39;) if [ $usage -lt 80 ]; then log \u0026#34;✓ 磁盘空间充足 (${usage}%)\u0026#34; return 0 elif [ $usage -lt 90 ]; then log \u0026#34;⚠ 磁盘空间不足 (${usage}%)\u0026#34; return 1 else log \u0026#34;✗ 磁盘空间严重不足 (${usage}%)\u0026#34; return 2 fi } ## 检查内存使用 check_memory_usage() { local memory_usage=$(docker stats $CONTAINER_NAME --no-stream --format \u0026#34;{{.MemPerc}}\u0026#34; | sed \u0026#39;s/%//\u0026#39;) if (( $(echo \u0026#34;$memory_usage \u0026lt; 80\u0026#34; | bc -l) )); then log \u0026#34;✓ 内存使用正常 (${memory_usage}%)\u0026#34; return 0 else log \u0026#34;⚠ 内存使用较高 (${memory_usage}%)\u0026#34; return 1 fi } ## 检查备份状态 check_backup_status() { local last_backup=$(ls -t /data/gitlab/backups/*_gitlab_backup.tar 2\u0026gt;/dev/null | head -1) if [ -n \u0026#34;$last_backup\u0026#34; ]; then local backup_age=$(( ($(date +%s) - $(stat -c %Y \u0026#34;$last_backup\u0026#34;)) / 86400 )) if [ $backup_age -le 1 ]; then log \u0026#34;✓ 备份状态正常 (最近备份: $backup_age 天前)\u0026#34; return 0 else log \u0026#34;⚠ 备份过期 (最近备份: $backup_age 天前)\u0026#34; return 1 fi else log \u0026#34;✗ 未找到备份文件\u0026#34; return 1 fi } ## 发送告警 send_alert() { local message=\u0026#34;$1\u0026#34; local severity=\u0026#34;$2\u0026#34; # 邮件告警 if command -v mail \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;$message\u0026#34; | mail -s \u0026#34;GitLab 监控告警 [$severity]\u0026#34; admin@your-domain.com fi # 钉钉告警 if [ -n \u0026#34;$DINGTALK_WEBHOOK\u0026#34; ]; then curl -X POST \u0026#34;$DINGTALK_WEBHOOK\u0026#34; \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#34;{\\\u0026#34;msgtype\\\u0026#34;: \\\u0026#34;text\\\u0026#34;, \\\u0026#34;text\\\u0026#34;: {\\\u0026#34;content\\\u0026#34;: \\\u0026#34;GitLab 告警: $message\\\u0026#34;}}\u0026#34; fi # Slack 告警 if [ -n \u0026#34;$SLACK_WEBHOOK\u0026#34; ]; then curl -X POST \u0026#34;$SLACK_WEBHOOK\u0026#34; \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#34;{\\\u0026#34;text\\\u0026#34;: \\\u0026#34;GitLab Alert: $message\\\u0026#34;}\u0026#34; fi } ## 主监控函数 main() { log \u0026#34;=== GitLab 监控检查开始 ===\u0026#34; local issues=0 local critical_issues=0 check_container || ((issues++)) check_web_service || ((issues++)) check_api_service || ((issues++)) check_disk_space local disk_status=$? if [ $disk_status -eq 2 ]; then ((critical_issues++)) send_alert \u0026#34;GitLab 磁盘空间严重不足\u0026#34; \u0026#34;CRITICAL\u0026#34; elif [ $disk_status -eq 1 ]; then ((issues++)) fi check_memory_usage || ((issues++)) check_backup_status || ((issues++)) if [ $critical_issues -gt 0 ]; then log \u0026#34;✗ 发现 $critical_issues 个严重问题\u0026#34; send_alert \u0026#34;GitLab 监控发现 $critical_issues 个严重问题\u0026#34; \u0026#34;CRITICAL\u0026#34; elif [ $issues -gt 0 ]; then log \u0026#34;⚠ 发现 $issues 个问题\u0026#34; send_alert \u0026#34;GitLab 监控发现 $issues 个问题\u0026#34; \u0026#34;WARNING\u0026#34; else log \u0026#34;✓ 所有检查通过\u0026#34; fi log \u0026#34;=== GitLab 监控检查完成 ===\u0026#34; } main \u0026#34;$@\u0026#34; EOF chmod +x gitlab-monitor.sh ## 设置定时监控 echo \u0026#34;*/5 * * * * /data/gitlab/gitlab-monitor.sh\u0026#34; | crontab - 故障排除 # 常见问题诊断 # 启动问题 # cat \u0026gt; diagnose-gitlab.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== GitLab 故障诊断 ===\u0026#34; ## 检查容器状态 echo \u0026#34;1. 检查容器状态\u0026#34; docker ps -a --filter \u0026#34;name=gitlab\u0026#34; ## 检查容器日志 echo -e \u0026#34;\\n2. 检查容器日志\u0026#34; docker logs --tail 50 gitlab-ce ## 检查 GitLab 服务状态 echo -e \u0026#34;\\n3. 检查 GitLab 服务状态\u0026#34; docker exec gitlab-ce gitlab-ctl status ## 检查配置文件 echo -e \u0026#34;\\n4. 检查配置文件\u0026#34; docker exec gitlab-ce gitlab-ctl show-config ## 检查磁盘空间 echo -e \u0026#34;\\n5. 检查磁盘空间\u0026#34; df -h /data/gitlab ## 检查内存使用 echo -e \u0026#34;\\n6. 检查内存使用\u0026#34; free -h ## 检查网络连接 echo -e \u0026#34;\\n7. 检查网络连接\u0026#34; netstat -tlnp | grep -E \u0026#34;(80|443|22)\u0026#34; ## 检查 GitLab 健康状态 echo -e \u0026#34;\\n8. 检查 GitLab 健康状态\u0026#34; docker exec gitlab-ce gitlab-rake gitlab:check echo -e \u0026#34;\\n=== 诊断完成 ===\u0026#34; EOF chmod +x diagnose-gitlab.sh 性能问题 # cat \u0026gt; gitlab-performance-analysis.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash CONTAINER_NAME=\u0026#34;gitlab-ce\u0026#34; echo \u0026#34;=== GitLab 性能分析 ===\u0026#34; ## 容器资源使用 echo \u0026#34;1. 容器资源使用\u0026#34; docker stats $CONTAINER_NAME --no-stream ## GitLab 进程状态 echo -e \u0026#34;\\n2. GitLab 进程状态\u0026#34; docker exec $CONTAINER_NAME ps aux | head -20 ## 数据库连接 echo -e \u0026#34;\\n3. 数据库连接\u0026#34; docker exec $CONTAINER_NAME gitlab-rails runner \u0026#34;puts ActiveRecord::Base.connection_pool.stat\u0026#34; ## Redis 状态 echo -e \u0026#34;\\n4. Redis 状态\u0026#34; docker exec $CONTAINER_NAME redis-cli info memory ## Sidekiq 队列状态 echo -e \u0026#34;\\n5. Sidekiq 队列状态\u0026#34; docker exec $CONTAINER_NAME gitlab-rails runner \u0026#34;puts Sidekiq::Queue.new.size\u0026#34; ## 磁盘 I/O echo -e \u0026#34;\\n6. 磁盘 I/O\u0026#34; iostat -x 1 3 echo -e \u0026#34;\\n=== 性能分析完成 ===\u0026#34; EOF chmod +x gitlab-performance-analysis.sh 总结 # 部署优势 # 通过本指南，您可以成功部署一个企业级的 GitLab 平台，具有以下优势：\n技术优势 # 多种部署方式：支持 Docker Compose 和 Kubernetes 部署 高可用性：支持负载均衡和分布式架构 安全可靠：集成 LDAP 认证、SSL 加密、访问控制 可扩展性：支持水平扩展和模块化配置 监控完善：内置 Prometheus 监控和 Grafana 可视化 运维优势 # 自动化备份：完整的备份恢复策略 性能优化：数据库、缓存、应用层全面优化 故障排除：完善的诊断和恢复机制 企业集成：支持 LDAP、SAML、OAuth 等认证方式 最佳实践 # 生产环境建议 # 资源规划：根据用户数量和项目规模合理规划资源 安全配置：启用 HTTPS、配置防火墙、定期安全审计 备份策略：建立完善的备份和灾难恢复计划 监控告警：部署全面的监控和告警体系 性能调优：根据使用情况持续优化配置参数 扩展建议 # 高可用部署：配置多节点集群和负载均衡 对象存储集成：使用 S3 兼容存储降低成本 CI/CD 优化：配置专用 Runner 和缓存策略 安全扫描集成：启用 SAST、DAST、依赖扫描 持续改进 # GitLab 作为 DevOps 平台的核心，需要持续优化和改进：\n定期更新：保持 GitLab 版本的及时更新 性能监控：持续监控系统性能和用户体验 安全审计：定期进行安全检查和漏洞扫描 用户培训：提供 Git 和 GitLab 使用培训 通过本指南的配置和最佳实践，您可以构建一个稳定、高效、安全的企业级 GitLab 平台，为团队的代码管理和 DevOps 实践提供强有力的支撑。\n## 总结 Gitlab 为企业中 常见的 git 代码管理软件，是在实施 `Devops` 过程中的利剑，功能强大，并内置了 ci/cd、repository、监控等功能。在对 Gitlab 进行配置优化时，我们需要注意在配置时还 存在着`全局生效配置` 和 `项目局部生效配置`，如在全局中配置了未见生效，可以去对应的项目组中查看此项目对应配置也做了相应的更改否。 ","date":"2021年3月4日","externalUrl":null,"permalink":"/posts/gitlab-deploy/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/jenkins-install/\"\u003e企业级 Jenkins CI/CD 平台部署与配置完整指南\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/kubernetes-deploy-gitlab/\"\u003eKubernetes 环境下部署 GitLab：localPv + 外部数据库方案\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/spinnaker-helm-installd/\"\u003e使用 Helm 部署 Spinnaker 持续部署(CD)平台\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eGitLab 平台简介 \n    \u003cdiv id=\"gitlab-平台简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#gitlab-%e5%b9%b3%e5%8f%b0%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 GitLab \n    \u003cdiv id=\"什么是-gitlab\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-gitlab\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGitLab 是一个基于 Web 的 DevOps 生命周期工具，提供了 Git 仓库管理、代码审查、问题跟踪、CI/CD 流水线和 Wiki 等功能。作为一个完整的 DevOps 平台，GitLab 帮助团队协作开发、测试和部署应用程序。\u003c/p\u003e","title":"企业级 GitLab 平台部署与运维完整指南","type":"posts"},{"content":"","date":"2021年3月2日","externalUrl":null,"permalink":"/tags/automation/","section":"Tags","summary":"","title":"Automation","type":"tags"},{"content":" 推荐阅读 # 企业级 GitLab 平台部署与运维完整指南 使用 Helm 部署 Spinnaker 持续部署(CD)平台 在 Kubernetes 中使用 localPv 部署 Gitlab Jenkins 平台简介 # 什么是 Jenkins # Jenkins 是世界领先的开源自动化服务器，由 Kohsuke Kawaguchi 于 2004 年创建。作为 DevOps 工具链的核心组件，Jenkins 为软件开发生命周期提供了强大的自动化能力。\n核心特性 # 持续集成（CI）：自动化代码构建、测试和集成流程 持续部署（CD）：自动化应用程序部署到各种环境 丰富的插件生态：超过 1800+ 插件，支持几乎所有开发工具和平台 分布式架构：支持主从节点架构，可水平扩展构建能力 Pipeline as Code：通过 Jenkinsfile 实现流水线即代码 多平台支持：支持 Windows、Linux、macOS 等多种操作系统 应用场景 # 代码质量管控：集成 SonarQube、ESLint 等代码质量检查工具 自动化测试：单元测试、集成测试、端到端测试的自动化执行 多环境部署：开发、测试、预生产、生产环境的自动化部署 微服务 CI/CD：支持容器化应用和微服务架构的持续交付 基础设施即代码：集成 Terraform、Ansible 等基础设施管理工具 架构设计 # 单机架构 # flowchart TB subgraph JenkinsMaster[Jenkins Master] A[Web UI] B[Job Scheduler] C[Plugin Management] D[Build Executors] E[Workspace] F[Build History] G[Logs] end 分布式架构 # flowchart TB M[Jenkins MasterWeb UI / Job Scheduler / Plugin] A1[Agent 1Linux] A2[Agent 2Windows] A3[Agent 3Docker] M --\u003e A1 M --\u003e A2 M --\u003e A3 环境准备 # 系统要求 # 硬件要求 # 环境类型 CPU 内存 存储 网络 开发环境 2 核 4GB 50GB 100Mbps 测试环境 4 核 8GB 200GB 1Gbps 生产环境 8 核 16GB 500GB+ 1Gbps+ 软件要求 # 组件 最低版本 推荐版本 说明 操作系统 CentOS 7.6 CentOS 7.9 / RHEL 8+ 64位系统 Java OpenJDK 8 OpenJDK 11 Jenkins 运行环境 内存 2GB 8GB+ 推荐配置 存储 10GB 100GB+ 构建历史和工作空间 网络端口 8080 自定义 Web 界面访问端口 网络端口规划 # 端口 协议 用途 是否必需 8080 TCP Jenkins Web UI 是 50000 TCP Agent 连接端口 是（分布式） 22 TCP SSH 连接 可选 443/80 TCP HTTPS/HTTP 代理 可选 环境检查脚本 # cat \u0026gt; check-jenkins-env.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== Jenkins 环境检查脚本 ===\u0026#34; echo \u0026#34;检查时间: $(date)\u0026#34; echo # 检查操作系统 echo \u0026#34;=== 系统信息 ===\u0026#34; cat /etc/redhat-release uname -a echo # 检查内存 echo \u0026#34;=== 内存信息 ===\u0026#34; free -h echo # 检查磁盘空间 echo \u0026#34;=== 磁盘空间 ===\u0026#34; df -h echo # 检查 Java 环境 echo \u0026#34;=== Java 环境检查 ===\u0026#34; if command -v java \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then java -version echo \u0026#34;✓ Java 已安装\u0026#34; else echo \u0026#34;✗ Java 未安装\u0026#34; fi echo # 检查网络端口 echo \u0026#34;=== 端口检查 ===\u0026#34; if netstat -tlnp | grep :8080 \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;✗ 端口 8080 已被占用\u0026#34; netstat -tlnp | grep :8080 else echo \u0026#34;✓ 端口 8080 可用\u0026#34; fi echo # 检查防火墙状态 echo \u0026#34;=== 防火墙状态 ===\u0026#34; systemctl status firewalld --no-pager echo # 检查 SELinux 状态 echo \u0026#34;=== SELinux 状态 ===\u0026#34; getenforce echo echo \u0026#34;=== 环境检查完成 ===\u0026#34; EOF chmod +x check-jenkins-env.sh ./check-jenkins-env.sh Jenkins 安装部署 # 方案一：YUM 包管理器安装（推荐） # 步骤 1：安装 Java 环境 # Jenkins 需要 Java 运行环境，推荐使用 OpenJDK 11：\n# 安装 OpenJDK 11（推荐） yum install -y java-11-openjdk java-11-openjdk-devel # 或安装 OpenJDK 8（兼容性考虑） yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel # 验证 Java 安装 java -version javac -version # 设置 JAVA_HOME 环境变量 echo \u0026#39;export JAVA_HOME=/usr/lib/jvm/java-11-openjdk\u0026#39; \u0026gt;\u0026gt; /etc/profile echo \u0026#39;export PATH=$JAVA_HOME/bin:$PATH\u0026#39; \u0026gt;\u0026gt; /etc/profile source /etc/profile 预期输出：\nopenjdk version \u0026#34;11.0.16\u0026#34; 2022-07-19 LTS OpenJDK Runtime Environment (Red_Hat-11.0.16.0.8-1.el7_9) (build 11.0.16+8-LTS) OpenJDK 64-Bit Server VM (Red_Hat-11.0.16.0.8-1.el7_9) (build 11.0.16+8-LTS, mixed mode, sharing) 步骤 2：配置 Jenkins YUM 源 # # 添加 Jenkins 官方 YUM 仓库 sudo wget -O /etc/yum.repos.d/jenkins.repo \\ https://pkg.jenkins.io/redhat-stable/jenkins.repo # 导入 Jenkins GPG 密钥 sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key # 更新 YUM 缓存 yum clean all \u0026amp;\u0026amp; yum makecache 步骤 3：安装 Jenkins # # 安装 Jenkins yum install -y jenkins # 查看 Jenkins 版本 rpm -qa | grep jenkins # 查看 Jenkins 服务状态 systemctl status jenkins 步骤 4：配置 Jenkins 服务 # # 启动 Jenkins 服务 systemctl start jenkins # 设置开机自启动 systemctl enable jenkins # 检查服务状态 systemctl status jenkins # 查看 Jenkins 日志 journalctl -u jenkins -f 步骤 5：防火墙配置 # # 开放 Jenkins 端口（8080） firewall-cmd --permanent --add-port=8080/tcp firewall-cmd --reload # 验证端口开放 firewall-cmd --list-ports # 检查端口监听状态 netstat -tlnp | grep 8080 方案二：Docker 容器化部署 # 基础 Docker 部署 # # 创建 Jenkins 数据目录 mkdir -p /data/jenkins_home chown -R 1000:1000 /data/jenkins_home # 运行 Jenkins 容器 docker run -d \\ --name jenkins \\ --restart unless-stopped \\ -p 8080:8080 \\ -p 50000:50000 \\ -v /data/jenkins_home:/var/jenkins_home \\ -v /var/run/docker.sock:/var/run/docker.sock \\ -v /usr/bin/docker:/usr/bin/docker \\ jenkins/jenkins:lts # 查看初始管理员密码 docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword Docker Compose 部署 # cat \u0026gt; docker-compose.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; version: \u0026#39;3.8\u0026#39; services: jenkins: image: jenkins/jenkins:lts container_name: jenkins restart: unless-stopped ports: - \u0026#34;8080:8080\u0026#34; - \u0026#34;50000:50000\u0026#34; volumes: - jenkins_home:/var/jenkins_home - /var/run/docker.sock:/var/run/docker.sock - /usr/bin/docker:/usr/bin/docker environment: - JENKINS_OPTS=--httpPort=8080 - JAVA_OPTS=-Xmx2048m -Xms1024m user: root volumes: jenkins_home: driver: local EOF # 启动服务 docker-compose up -d # 查看日志 docker-compose logs -f jenkins 方案三：Kubernetes 部署 # 创建 Jenkins 部署文件 # cat \u0026gt; jenkins-k8s.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; apiVersion: v1 kind: Namespace metadata: name: jenkins --- apiVersion: v1 kind: ServiceAccount metadata: name: jenkins namespace: jenkins --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: jenkins rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods\u0026#34;] verbs: [\u0026#34;create\u0026#34;,\u0026#34;delete\u0026#34;,\u0026#34;get\u0026#34;,\u0026#34;list\u0026#34;,\u0026#34;patch\u0026#34;,\u0026#34;update\u0026#34;,\u0026#34;watch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods/exec\u0026#34;] verbs: [\u0026#34;create\u0026#34;,\u0026#34;delete\u0026#34;,\u0026#34;get\u0026#34;,\u0026#34;list\u0026#34;,\u0026#34;patch\u0026#34;,\u0026#34;update\u0026#34;,\u0026#34;watch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods/log\u0026#34;] verbs: [\u0026#34;get\u0026#34;,\u0026#34;list\u0026#34;,\u0026#34;watch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;secrets\u0026#34;] verbs: [\u0026#34;get\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: jenkins roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: jenkins subjects: - kind: ServiceAccount name: jenkins namespace: jenkins --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: jenkins-pvc namespace: jenkins spec: accessModes: - ReadWriteOnce resources: requests: storage: 100Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: jenkins namespace: jenkins spec: replicas: 1 selector: matchLabels: app: jenkins template: metadata: labels: app: jenkins spec: serviceAccountName: jenkins containers: - name: jenkins image: jenkins/jenkins:lts ports: - containerPort: 8080 - containerPort: 50000 volumeMounts: - name: jenkins-home mountPath: /var/jenkins_home env: - name: JAVA_OPTS value: \u0026#34;-Xmx2048m -Xms1024m\u0026#34; resources: requests: cpu: 1000m memory: 2Gi limits: cpu: 2000m memory: 4Gi volumes: - name: jenkins-home persistentVolumeClaim: claimName: jenkins-pvc --- apiVersion: v1 kind: Service metadata: name: jenkins namespace: jenkins spec: type: NodePort ports: - port: 8080 targetPort: 8080 nodePort: 30080 - port: 50000 targetPort: 50000 nodePort: 30050 selector: app: jenkins EOF # 部署到 Kubernetes kubectl apply -f jenkins-k8s.yaml # 查看部署状态 kubectl get pods -n jenkins kubectl get svc -n jenkins 初始化配置 # 获取初始管理员密码 # # YUM 安装方式 sudo cat /var/lib/jenkins/secrets/initialAdminPassword # Docker 方式 docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword # Kubernetes 方式 kubectl exec -n jenkins deployment/jenkins -- cat /var/jenkins_home/secrets/initialAdminPassword Web 界面初始化 # 访问 Jenkins：打开浏览器访问 http://your-server-ip:8080 输入管理员密码：使用上面获取的初始密码 安装插件：选择 \u0026ldquo;安装推荐的插件\u0026rdquo; 或 \u0026ldquo;选择插件来安装\u0026rdquo; 创建管理员用户：设置用户名、密码、邮箱等信息 实例配置：确认 Jenkins URL 配置 开始使用：完成初始化，进入 Jenkins 主界面 推荐插件列表 # 基础插件：\nGit Plugin Pipeline Plugin Blue Ocean Build Timeout Timestamper Workspace Cleanup 高级插件：\nKubernetes Plugin Docker Plugin LDAP Plugin Role-based Authorization Strategy SonarQube Scanner Jenkins 系统配置优化 # 基础配置优化 # 修改服务端口 # Jenkins 默认使用 8080 端口，如遇端口冲突需要修改：\n# YUM 安装方式修改端口 sudo vim /etc/sysconfig/jenkins # 修改以下配置项 JENKINS_PORT=\u0026#34;18080\u0026#34; # 或使用 sed 命令批量替换 sed -i \u0026#39;s/JENKINS_PORT=\u0026#34;8080\u0026#34;/JENKINS_PORT=\u0026#34;18080\u0026#34;/g\u0026#39; /etc/sysconfig/jenkins # 重启 Jenkins 服务 systemctl restart jenkins # 验证端口变更 netstat -tlnp | grep 18080 JVM 参数优化 # # 编辑 Jenkins 配置文件 sudo vim /etc/sysconfig/jenkins # 优化 JVM 参数 JENKINS_JAVA_OPTIONS=\u0026#34;-Djava.awt.headless=true -Xms2048m -Xmx4096m -XX:+UseG1GC -XX:+UseStringDeduplication -Djava.net.preferIPv4Stack=true -Duser.timezone=Asia/Shanghai\u0026#34; # 重启服务使配置生效 systemctl restart jenkins 工作目录配置 # # 默认工作目录：/var/lib/jenkins # 如需修改工作目录，编辑配置文件 sudo vim /etc/sysconfig/jenkins # 修改工作目录 JENKINS_HOME=\u0026#34;/data/jenkins\u0026#34; # 创建新目录并设置权限 sudo mkdir -p /data/jenkins sudo chown jenkins:jenkins /data/jenkins # 迁移现有数据（可选） sudo rsync -av /var/lib/jenkins/ /data/jenkins/ # 重启服务 systemctl restart jenkins 反向代理配置 # Nginx 反向代理 # 安装 Nginx # # 安装 Nginx yum install -y nginx # 启动并设置开机自启 systemctl start nginx systemctl enable nginx 配置虚拟主机 # # 创建 Jenkins 虚拟主机配置 cat \u0026gt; /etc/nginx/conf.d/jenkins.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; upstream jenkins { server 127.0.0.1:8080 fail_timeout=0; } server { listen 80; server_name jenkins.your-domain.com; # 安全头设置 add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection \u0026#34;1; mode=block\u0026#34;; # 日志配置 access_log /var/log/nginx/jenkins.access.log; error_log /var/log/nginx/jenkins.error.log; # Jenkins 代理配置 location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; proxy_pass http://jenkins; proxy_read_timeout 90; proxy_redirect http://jenkins https://jenkins.your-domain.com; # WebSocket 支持 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; # 大文件上传支持 client_max_body_size 100m; } } EOF # 测试 Nginx 配置 nginx -t # 重新加载 Nginx 配置 systemctl reload nginx HTTPS 配置（推荐） # # 安装 Certbot yum install -y certbot python2-certbot-nginx # 获取 SSL 证书 certbot --nginx -d jenkins.your-domain.com # 自动续期设置 echo \u0026#34;0 12 * * * /usr/bin/certbot renew --quiet\u0026#34; | crontab - Apache 反向代理（备选） # # 安装 Apache yum install -y httpd # 启用必要模块 cat \u0026gt; /etc/httpd/conf.d/jenkins.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; \u0026lt;VirtualHost *:80\u0026gt; ServerName jenkins.your-domain.com ProxyPreserveHost On ProxyRequests Off ProxyPass / http://localhost:8080/ ProxyPassReverse / http://localhost:8080/ # 安全配置 Header always set X-Frame-Options SAMEORIGIN Header always set X-Content-Type-Options nosniff Header always set X-XSS-Protection \u0026#34;1; mode=block\u0026#34; \u0026lt;/VirtualHost\u0026gt; EOF # 启动 Apache systemctl start httpd systemctl enable httpd Traefik 反向代理（容器环境） # cat \u0026gt; traefik-jenkins.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; version: \u0026#39;3.8\u0026#39; services: traefik: image: traefik:v2.9 container_name: traefik restart: unless-stopped ports: - \u0026#34;80:80\u0026#34; - \u0026#34;443:443\u0026#34; - \u0026#34;8080:8080\u0026#34; volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik.yml:/traefik.yml:ro - ./acme.json:/acme.json labels: - \u0026#34;traefik.enable=true\u0026#34; - \u0026#34;traefik.http.routers.dashboard.rule=Host(`traefik.your-domain.com`)\u0026#34; - \u0026#34;traefik.http.routers.dashboard.tls=true\u0026#34; - \u0026#34;traefik.http.routers.dashboard.tls.certresolver=letsencrypt\u0026#34; jenkins: image: jenkins/jenkins:lts container_name: jenkins restart: unless-stopped volumes: - jenkins_home:/var/jenkins_home labels: - \u0026#34;traefik.enable=true\u0026#34; - \u0026#34;traefik.http.routers.jenkins.rule=Host(`jenkins.your-domain.com`)\u0026#34; - \u0026#34;traefik.http.routers.jenkins.tls=true\u0026#34; - \u0026#34;traefik.http.routers.jenkins.tls.certresolver=letsencrypt\u0026#34; - \u0026#34;traefik.http.services.jenkins.loadbalancer.server.port=8080\u0026#34; volumes: jenkins_home: EOF 性能优化配置 # 系统级优化 # # 创建性能优化脚本 cat \u0026gt; optimize-jenkins.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== Jenkins 性能优化脚本 ===\u0026#34; # 1. 系统参数优化 echo \u0026#34;优化系统参数...\u0026#34; cat \u0026gt;\u0026gt; /etc/sysctl.conf \u0026lt;\u0026lt; \u0026#39;SYSCTL\u0026#39; # Jenkins 性能优化 vm.swappiness = 1 vm.dirty_ratio = 15 vm.dirty_background_ratio = 5 net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535 SYSCTL sysctl -p # 2. 文件描述符限制 echo \u0026#34;优化文件描述符限制...\u0026#34; cat \u0026gt;\u0026gt; /etc/security/limits.conf \u0026lt;\u0026lt; \u0026#39;LIMITS\u0026#39; jenkins soft nofile 65535 jenkins hard nofile 65535 jenkins soft nproc 32768 jenkins hard nproc 32768 LIMITS # 3. Jenkins 用户环境优化 echo \u0026#34;优化 Jenkins 用户环境...\u0026#34; sudo -u jenkins bash -c \u0026#39;echo \u0026#34;ulimit -n 65535\u0026#34; \u0026gt;\u0026gt; ~/.bashrc\u0026#39; # 4. 清理临时文件 echo \u0026#34;清理临时文件...\u0026#34; find /var/lib/jenkins/workspace -name \u0026#34;*.tmp\u0026#34; -mtime +7 -delete find /var/lib/jenkins/logs -name \u0026#34;*.log\u0026#34; -mtime +30 -delete echo \u0026#34;=== 优化完成 ===\u0026#34; EOF chmod +x optimize-jenkins.sh ./optimize-jenkins.sh Jenkins 内部优化 # # 创建 Jenkins 配置优化脚本 cat \u0026gt; jenkins-config-optimize.groovy \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; import jenkins.model.* import hudson.model.* // 设置执行器数量 Jenkins.instance.setNumExecutors(4) // 设置构建历史保留策略 def jobs = Jenkins.instance.getAllItems(Job.class) jobs.each { job -\u0026gt; if (job.getBuildDiscarder() == null) { job.setBuildDiscarder(new LogRotator(-1, 10, -1, 5)) job.save() } } // 优化系统配置 def desc = Jenkins.instance.getDescriptor(\u0026#34;hudson.model.DirectoryBrowserSupport\u0026#34;) desc.setCSP(\u0026#34;default-src \u0026#39;self\u0026#39;; script-src \u0026#39;self\u0026#39; \u0026#39;unsafe-inline\u0026#39; \u0026#39;unsafe-eval\u0026#39;; style-src \u0026#39;self\u0026#39; \u0026#39;unsafe-inline\u0026#39;;\u0026#34;) println \u0026#34;Jenkins 配置优化完成\u0026#34; EOF # 通过 Jenkins CLI 执行优化脚本 java -jar jenkins-cli.jar -s http://localhost:8080/ groovy = \u0026lt; jenkins-config-optimize.groovy 配置 Jenkins URL # 完成反向代理配置后，需要在 Jenkins 中设置正确的 URL：\n进入 Manage Jenkins → Configure System 找到 Jenkins Location 部分 将 Jenkins URL 修改为代理地址（如：http://jenkins.your-domain.com） 点击 Save 保存配置 这样可以解决反向代理的重定向问题。\n插件管理优化 # 配置国内镜像源 # 由于网络原因，建议使用国内镜像源加速插件下载：\n方法一：脚本自动配置 # # 创建镜像源配置脚本 cat \u0026gt; setup-jenkins-mirror.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # 确定 Jenkins 工作目录 if [ -d \u0026#34;/var/lib/jenkins\u0026#34; ]; then JENKINS_HOME=\u0026#34;/var/lib/jenkins\u0026#34; elif [ -d \u0026#34;/var/jenkins_home\u0026#34; ]; then JENKINS_HOME=\u0026#34;/var/jenkins_home\u0026#34; else echo \u0026#34;未找到 Jenkins 工作目录\u0026#34; exit 1 fi echo \u0026#34;Jenkins 工作目录: $JENKINS_HOME\u0026#34; # 备份原始配置 cp \u0026#34;${JENKINS_HOME}/hudson.model.UpdateCenter.xml\u0026#34; \u0026#34;${JENKINS_HOME}/hudson.model.UpdateCenter.xml.bak\u0026#34; 2\u0026gt;/dev/null || true cp \u0026#34;${JENKINS_HOME}/updates/default.json\u0026#34; \u0026#34;${JENKINS_HOME}/updates/default.json.bak\u0026#34; 2\u0026gt;/dev/null || true # 配置清华大学镜像源 echo \u0026#34;配置清华大学镜像源...\u0026#34; sed -i \u0026#39;s#https://updates.jenkins.io#https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates#g\u0026#39; \u0026#34;${JENKINS_HOME}/hudson.model.UpdateCenter.xml\u0026#34; if [ -f \u0026#34;${JENKINS_HOME}/updates/default.json\u0026#34; ]; then sed -i \u0026#39;s#http://updates.jenkins-ci.org/download#https://mirrors.tuna.tsinghua.edu.cn/jenkins#g\u0026#39; \u0026#34;${JENKINS_HOME}/updates/default.json\u0026#34; sed -i \u0026#39;s#http://www.google.com#https://www.baidu.com#g\u0026#39; \u0026#34;${JENKINS_HOME}/updates/default.json\u0026#34; fi # 重启 Jenkins 服务 echo \u0026#34;重启 Jenkins 服务...\u0026#34; systemctl restart jenkins echo \u0026#34;镜像源配置完成！\u0026#34; echo \u0026#34;请在 Web 界面中验证：Manage Jenkins → Manage Plugins → Advanced → Update Site\u0026#34; echo \u0026#34;URL 应为: https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json\u0026#34; EOF chmod +x setup-jenkins-mirror.sh ./setup-jenkins-mirror.sh 方法二：Web 界面手动配置 # 进入 Manage Jenkins → Manage Plugins → Advanced 在 Update Site 部分，将 URL 替换为： https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json 点击 Submit 提交 重启 Jenkins 服务 方法三：容器化部署环境变量 # # Docker 运行时添加环境变量 docker run -d \\ --name jenkins \\ -p 8080:8080 \\ -p 50000:50000 \\ -v jenkins_home:/var/jenkins_home \\ -e JENKINS_UC=https://mirrors.tuna.tsinghua.edu.cn \\ -e JENKINS_UC_DOWNLOAD=https://mirrors.tuna.tsinghua.edu.cn/jenkins \\ -e JENKINS_OPTS=\u0026#34;-Djava.awt.headless=true -Duser.timezone=Asia/Shanghai -Dhudson.model.UpdateCenter.updateCenterUrl=https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json\u0026#34; \\ jenkins/jenkins:lts # Docker Compose 配置 version: \u0026#39;3.8\u0026#39; services: jenkins: image: jenkins/jenkins:lts environment: - JENKINS_UC=https://mirrors.tuna.tsinghua.edu.cn - JENKINS_UC_DOWNLOAD=https://mirrors.tuna.tsinghua.edu.cn/jenkins - JENKINS_OPTS=-Djava.awt.headless=true -Duser.timezone=Asia/Shanghai -Dhudson.model.UpdateCenter.updateCenterUrl=https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json 批量插件安装 # 创建插件列表文件 # cat \u0026gt; jenkins-plugins.txt \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # 基础插件 ant build-timeout credentials-binding timestamper ws-cleanup gradle workflow-aggregator github-branch-source pipeline-github-lib pipeline-stage-view git ssh-slaves matrix-auth pam-auth ldap email-ext mailer # 高级插件 kubernetes docker-workflow docker-plugin blueocean sonar role-strategy rebuild generic-webhook-trigger http_request ansicolor build-user-vars-plugin git-parameter locale localization-zh-cn pipeline-utility-steps simple-theme-plugin periodicbackup build-monitor-plugin dingding-notifications EOF 自动化插件安装脚本 # cat \u0026gt; install-jenkins-plugins.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash JENKINS_URL=\u0026#34;http://localhost:8080\u0026#34; JENKINS_USER=\u0026#34;admin\u0026#34; JENKINS_TOKEN=\u0026#34;your-api-token\u0026#34; PLUGIN_LIST=\u0026#34;jenkins-plugins.txt\u0026#34; # 检查 Jenkins CLI if [ ! -f \u0026#34;jenkins-cli.jar\u0026#34; ]; then echo \u0026#34;下载 Jenkins CLI...\u0026#34; wget ${JENKINS_URL}/jnlpJars/jenkins-cli.jar fi # 安装插件 echo \u0026#34;开始安装插件...\u0026#34; while IFS= read -r plugin; do # 跳过注释和空行 [[ $plugin =~ ^#.*$ ]] \u0026amp;\u0026amp; continue [[ -z \u0026#34;$plugin\u0026#34; ]] \u0026amp;\u0026amp; continue echo \u0026#34;安装插件: $plugin\u0026#34; java -jar jenkins-cli.jar -s $JENKINS_URL -auth $JENKINS_USER:$JENKINS_TOKEN install-plugin $plugin done \u0026lt; \u0026#34;$PLUGIN_LIST\u0026#34; # 重启 Jenkins echo \u0026#34;重启 Jenkins...\u0026#34; java -jar jenkins-cli.jar -s $JENKINS_URL -auth $JENKINS_USER:$JENKINS_TOKEN restart echo \u0026#34;插件安装完成！\u0026#34; EOF chmod +x install-jenkins-plugins.sh Jenkins 版本管理 # 版本更新策略 # Jenkins 提供两种发布版本：\nLTS（长期支持版）：每 12 周发布一次，稳定性更好，推荐生产环境使用 Weekly（周版本）：每周发布，包含最新功能，适合测试环境 手动更新 Jenkins # 方法一：YUM 包管理器更新 # # 检查当前版本 java -jar /usr/lib/jenkins/jenkins.war --version # 更新到最新 LTS 版本 yum update jenkins # 重启服务 systemctl restart jenkins 方法二：WAR 包手动更新 # # 创建更新脚本 cat \u0026gt; update-jenkins.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # 配置变量 JENKINS_HOME=\u0026#34;/var/lib/jenkins\u0026#34; JENKINS_WAR_DIR=\u0026#34;/usr/lib/jenkins\u0026#34; BACKUP_DIR=\u0026#34;/backup/jenkins\u0026#34; NEW_VERSION=\u0026#34;2.414.1\u0026#34; # 指定要更新的版本 # 创建备份目录 mkdir -p $BACKUP_DIR echo \u0026#34;=== Jenkins 更新脚本 ===\u0026#34; echo \u0026#34;当前版本: $(java -jar $JENKINS_WAR_DIR/jenkins.war --version)\u0026#34; echo \u0026#34;目标版本: $NEW_VERSION\u0026#34; # 停止 Jenkins 服务 echo \u0026#34;停止 Jenkins 服务...\u0026#34; systemctl stop jenkins # 备份当前版本 echo \u0026#34;备份当前版本...\u0026#34; cp $JENKINS_WAR_DIR/jenkins.war $BACKUP_DIR/jenkins-$(date +%Y%m%d-%H%M%S).war.bak # 下载新版本 echo \u0026#34;下载新版本...\u0026#34; cd $JENKINS_WAR_DIR # 优先使用国内镜像 if wget -q --spider https://mirrors.aliyun.com/jenkins/war/$NEW_VERSION/jenkins.war; then echo \u0026#34;使用阿里云镜像下载...\u0026#34; wget https://mirrors.aliyun.com/jenkins/war/$NEW_VERSION/jenkins.war -O jenkins.war.new elif wget -q --spider https://mirrors.tuna.tsinghua.edu.cn/jenkins/war/$NEW_VERSION/jenkins.war; then echo \u0026#34;使用清华镜像下载...\u0026#34; wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/war/$NEW_VERSION/jenkins.war -O jenkins.war.new else echo \u0026#34;使用官方源下载...\u0026#34; wget https://updates.jenkins.io/download/war/$NEW_VERSION/jenkins.war -O jenkins.war.new fi # 验证下载 if [ -f \u0026#34;jenkins.war.new\u0026#34; ] \u0026amp;\u0026amp; [ -s \u0026#34;jenkins.war.new\u0026#34; ]; then mv jenkins.war.new jenkins.war echo \u0026#34;WAR 包更新成功\u0026#34; else echo \u0026#34;下载失败，恢复备份\u0026#34; cp $BACKUP_DIR/jenkins-$(date +%Y%m%d)*.war.bak jenkins.war exit 1 fi # 启动 Jenkins 服务 echo \u0026#34;启动 Jenkins 服务...\u0026#34; systemctl start jenkins # 等待服务启动 sleep 30 # 验证更新 echo \u0026#34;验证更新结果...\u0026#34; NEW_VERSION_CHECK=$(java -jar $JENKINS_WAR_DIR/jenkins.war --version) echo \u0026#34;更新后版本: $NEW_VERSION_CHECK\u0026#34; # 检查服务状态 if systemctl is-active --quiet jenkins; then echo \u0026#34;✓ Jenkins 服务运行正常\u0026#34; echo \u0026#34;✓ 更新完成！\u0026#34; else echo \u0026#34;✗ Jenkins 服务启动失败，请检查日志\u0026#34; journalctl -u jenkins --no-pager -n 20 fi EOF chmod +x update-jenkins.sh 自动化更新配置 # 配置自动更新检查 # # 创建更新检查脚本 cat \u0026gt; check-jenkins-updates.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash JENKINS_URL=\u0026#34;http://localhost:8080\u0026#34; CURRENT_VERSION=$(java -jar /usr/lib/jenkins/jenkins.war --version) # 获取最新 LTS 版本信息 LATEST_LTS=$(curl -s https://updates.jenkins.io/stable/latestCore.txt) echo \u0026#34;当前版本: $CURRENT_VERSION\u0026#34; echo \u0026#34;最新 LTS: $LATEST_LTS\u0026#34; if [ \u0026#34;$CURRENT_VERSION\u0026#34; != \u0026#34;$LATEST_LTS\u0026#34; ]; then echo \u0026#34;发现新版本，建议更新\u0026#34; # 可以在这里添加邮件通知或其他告警机制 else echo \u0026#34;已是最新版本\u0026#34; fi EOF chmod +x check-jenkins-updates.sh # 设置定期检查（每周检查一次） echo \u0026#34;0 9 * * 1 /path/to/check-jenkins-updates.sh\u0026#34; | crontab - 容器化环境更新 # Docker 容器更新 # # 创建容器更新脚本 cat \u0026gt; update-jenkins-docker.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash CONTAINER_NAME=\u0026#34;jenkins\u0026#34; NEW_IMAGE=\u0026#34;jenkins/jenkins:lts\u0026#34; BACKUP_DIR=\u0026#34;/backup/jenkins\u0026#34; echo \u0026#34;=== Docker Jenkins 更新脚本 ===\u0026#34; # 创建数据备份 echo \u0026#34;备份 Jenkins 数据...\u0026#34; mkdir -p $BACKUP_DIR docker exec $CONTAINER_NAME tar czf /tmp/jenkins-backup-$(date +%Y%m%d).tar.gz -C /var/jenkins_home . docker cp $CONTAINER_NAME:/tmp/jenkins-backup-$(date +%Y%m%d).tar.gz $BACKUP_DIR/ # 停止并删除旧容器 echo \u0026#34;停止旧容器...\u0026#34; docker stop $CONTAINER_NAME docker rm $CONTAINER_NAME # 拉取新镜像 echo \u0026#34;拉取新镜像...\u0026#34; docker pull $NEW_IMAGE # 启动新容器 echo \u0026#34;启动新容器...\u0026#34; docker run -d \\ --name $CONTAINER_NAME \\ --restart unless-stopped \\ -p 8080:8080 \\ -p 50000:50000 \\ -v jenkins_home:/var/jenkins_home \\ $NEW_IMAGE echo \u0026#34;更新完成！\u0026#34; EOF chmod +x update-jenkins-docker.sh 版本回滚 # 快速回滚脚本 # cat \u0026gt; rollback-jenkins.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash JENKINS_WAR_DIR=\u0026#34;/usr/lib/jenkins\u0026#34; BACKUP_DIR=\u0026#34;/backup/jenkins\u0026#34; echo \u0026#34;=== Jenkins 版本回滚脚本 ===\u0026#34; # 列出可用备份 echo \u0026#34;可用的备份版本：\u0026#34; ls -la $BACKUP_DIR/jenkins-*.war.bak # 选择要回滚的版本 read -p \u0026#34;请输入要回滚的备份文件名: \u0026#34; BACKUP_FILE if [ -f \u0026#34;$BACKUP_DIR/$BACKUP_FILE\u0026#34; ]; then echo \u0026#34;停止 Jenkins 服务...\u0026#34; systemctl stop jenkins echo \u0026#34;回滚到备份版本...\u0026#34; cp $BACKUP_DIR/$BACKUP_FILE $JENKINS_WAR_DIR/jenkins.war echo \u0026#34;启动 Jenkins 服务...\u0026#34; systemctl start jenkins echo \u0026#34;回滚完成！\u0026#34; else echo \u0026#34;备份文件不存在: $BACKUP_DIR/$BACKUP_FILE\u0026#34; fi EOF chmod +x rollback-jenkins.sh 企业级插件管理 # 核心插件分类 # 基础必备插件 # 插件名称 功能说明 使用场景 Git Plugin Git 版本控制集成 代码拉取和版本管理 Pipeline Plugin 流水线核心功能 Pipeline as Code Credentials Plugin 凭据管理 安全存储密码、密钥等 Build Timeout 构建超时控制 防止构建无限期运行 Timestamper 时间戳显示 构建日志时间标记 Workspace Cleanup 工作空间清理 自动清理构建工作空间 用户界面增强 # 插件名称 功能说明 使用场景 Blue Ocean 现代化 UI 界面 更直观的流水线可视化 Simple Theme Plugin 自定义主题 企业品牌定制 Green Balls 成功状态绿色显示 更直观的状态显示 Build Monitor View 构建状态监控面板 大屏幕状态展示 AnsiColor 彩色日志输出 更易读的构建日志 源码管理集成 # 插件名称 功能说明 使用场景 GitHub Plugin GitHub 集成 GitHub 项目构建 GitLab Plugin GitLab 集成 GitLab 项目构建 Bitbucket Plugin Bitbucket 集成 Bitbucket 项目构建 Git Parameter Git 参数化构建 分支/标签选择构建 Generic Webhook Trigger 通用 Webhook 触发器 灵活的触发机制 构建工具集成 # 插件名称 功能说明 使用场景 Maven Integration Maven 构建支持 Java 项目构建 Gradle Plugin Gradle 构建支持 Gradle 项目构建 NodeJS Plugin Node.js 环境管理 前端项目构建 Docker Plugin Docker 集成 容器化构建和部署 Kubernetes Plugin Kubernetes 集成 动态 Agent 和容器化部署 质量管控 # 插件名称 功能说明 使用场景 SonarQube Scanner 代码质量检查 静态代码分析 Checkstyle Plugin 代码风格检查 Java 代码规范检查 JUnit Plugin 单元测试报告 测试结果展示 Cobertura Plugin 代码覆盖率 测试覆盖率统计 Warnings Next Generation 编译警告分析 代码质量趋势分析 通知和集成 # 插件名称 功能说明 使用场景 Email Extension 邮件通知增强 构建结果邮件通知 Slack Notification Slack 集成 团队协作通知 DingTalk 钉钉通知 企业内部通知 WeChat Work Notification 企业微信通知 企业微信群通知 HTTP Request HTTP API 调用 第三方系统集成 安全和权限管理 # 插件名称 功能说明 使用场景 Role-based Authorization Strategy 基于角色的权限管理 细粒度权限控制 LDAP Plugin LDAP 认证集成 企业统一认证 Active Directory Plugin AD 域认证 Windows 域环境集成 GitLab Authentication GitLab OAuth 认证 GitLab 用户认证 Matrix Authorization Strategy 矩阵权限管理 项目级权限控制 运维和监控 # 插件名称 功能说明 使用场景 Monitoring 系统监控 Jenkins 性能监控 Disk Usage Plugin 磁盘使用统计 存储空间管理 Build User Vars 构建用户信息 审计和追踪 Periodic Backup 定期备份 数据安全保护 Job Configuration History 配置变更历史 配置管理和审计 实用工具 # 插件名称 功能说明 使用场景 Pipeline Utility Steps 流水线工具步骤 文件操作、JSON 处理等 Rebuilder 重新构建 使用相同参数重新构建 Parameterized Trigger 参数化触发 复杂的构建触发逻辑 Copy Artifact 构建产物复制 跨项目产物共享 Archive Artifacts 产物归档 构建产物管理 国际化 # 插件名称 功能说明 使用场景 Locale Plugin 语言环境控制 界面语言设置 Localization: Chinese (Simplified) 简体中文语言包 中文界面支持 企业级安全配置 # LDAP 统一认证集成 # 前置条件 # 安装 LDAP Plugin 插件 准备 OpenLDAP 服务器（参考 OpenLDAP 部署文档） 配置步骤 # 步骤 1：启用 LDAP 认证 # 进入 Manage Jenkins → Configure Global Security 在 Security Realm 部分选择 LDAP 步骤 2：配置 LDAP 服务器 # # LDAP 配置参数示例 Server: ldap://ldap.your-domain.com:389 Root DN: dc=your-domain,dc=com User search base: ou=people User search filter: uid={0} Group search base: ou=groups Group search filter: (uniqueMember={0}) Manager DN: cn=admin,dc=your-domain,dc=com Manager Password: your-admin-password Display Name LDAP attribute: cn Email Address LDAP attribute: mail 步骤 3：高级 LDAP 配置 # // Jenkins 配置脚本 (Groovy) import jenkins.model.* import hudson.security.* import org.jenkinsci.plugins.ldap.* def instance = Jenkins.getInstance() // LDAP 配置 def ldapRealm = new LDAPSecurityRealm( \u0026#34;ldap://ldap.your-domain.com:389\u0026#34;, // server \u0026#34;dc=your-domain,dc=com\u0026#34;, // rootDN \u0026#34;ou=people,dc=your-domain,dc=com\u0026#34;, // userSearchBase \u0026#34;uid={0}\u0026#34;, // userSearch \u0026#34;ou=groups,dc=your-domain,dc=com\u0026#34;, // groupSearchBase \u0026#34;(uniqueMember={0})\u0026#34;, // groupSearchFilter new LDAPSecurityRealm.LDAPGroupMembershipStrategy(), // groupMembershipStrategy \u0026#34;cn=admin,dc=your-domain,dc=com\u0026#34;, // managerDN \u0026#34;your-admin-password\u0026#34;, // managerPassword false, // inhibitInferRootDN false, // disableMailAddressResolver null, // cache null, // environmentProperties \u0026#34;cn\u0026#34;, // displayNameAttributeName \u0026#34;mail\u0026#34;, // mailAddressAttributeName IdStrategy.CASE_INSENSITIVE, // userIdStrategy IdStrategy.CASE_INSENSITIVE // groupIdStrategy ) instance.setSecurityRealm(ldapRealm) instance.save() println \u0026#34;LDAP 配置完成\u0026#34; 步骤 4：测试 LDAP 连接 # LDAP 故障排除 # # 创建 LDAP 连接测试脚本 cat \u0026gt; test-ldap-connection.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash LDAP_SERVER=\u0026#34;ldap.your-domain.com\u0026#34; LDAP_PORT=\u0026#34;389\u0026#34; BASE_DN=\u0026#34;dc=your-domain,dc=com\u0026#34; ADMIN_DN=\u0026#34;cn=admin,dc=your-domain,dc=com\u0026#34; ADMIN_PASS=\u0026#34;your-admin-password\u0026#34; echo \u0026#34;=== LDAP 连接测试 ===\u0026#34; # 测试基本连接 echo \u0026#34;测试 LDAP 服务器连接...\u0026#34; ldapsearch -x -H ldap://$LDAP_SERVER:$LDAP_PORT -b \u0026#34;\u0026#34; -s base # 测试管理员认证 echo \u0026#34;测试管理员认证...\u0026#34; ldapsearch -x -H ldap://$LDAP_SERVER:$LDAP_PORT -D \u0026#34;$ADMIN_DN\u0026#34; -w \u0026#34;$ADMIN_PASS\u0026#34; -b \u0026#34;$BASE_DN\u0026#34; -s base # 测试用户搜索 echo \u0026#34;测试用户搜索...\u0026#34; ldapsearch -x -H ldap://$LDAP_SERVER:$LDAP_PORT -D \u0026#34;$ADMIN_DN\u0026#34; -w \u0026#34;$ADMIN_PASS\u0026#34; -b \u0026#34;ou=people,$BASE_DN\u0026#34; \u0026#34;(objectClass=inetOrgPerson)\u0026#34; # 测试组搜索 echo \u0026#34;测试组搜索...\u0026#34; ldapsearch -x -H ldap://$LDAP_SERVER:$LDAP_PORT -D \u0026#34;$ADMIN_DN\u0026#34; -w \u0026#34;$ADMIN_PASS\u0026#34; -b \u0026#34;ou=groups,$BASE_DN\u0026#34; \u0026#34;(objectClass=groupOfUniqueNames)\u0026#34; echo \u0026#34;=== 测试完成 ===\u0026#34; EOF chmod +x test-ldap-connection.sh 基于角色的权限管理 # 安装和启用 # 安装 Role-based Authorization Strategy 插件 进入 Manage Jenkins → Configure Global Security 在 Authorization 部分选择 Role-Based Strategy 角色配置 # 步骤 1：定义全局角色 # 进入 Manage Jenkins → Manage and Assign Roles → Manage Roles\n# 全局角色定义示例 admin # 管理员角色 - 所有权限 developer # 开发者角色 - 构建、查看权限 viewer # 只读角色 - 仅查看权限 operator # 运维角色 - 部署、监控权限 步骤 2：定义项目角色 # # 项目角色模式示例 project-.*-admin # 项目管理员 project-.*-developer # 项目开发者 project-.*-viewer # 项目查看者 步骤 3：分配角色 # 进入 Manage Jenkins → Manage and Assign Roles → Assign Roles\n权限配置脚本 # // 自动化权限配置脚本 import jenkins.model.* import hudson.security.* import com.michelin.cio.hudson.plugins.rolestrategy.* def instance = Jenkins.getInstance() // 创建角色策略 def strategy = new RoleBasedAuthorizationStrategy() // 定义全局角色 def globalRoles = [ \u0026#39;admin\u0026#39;: [ \u0026#39;hudson.model.Hudson.Administer\u0026#39;, \u0026#39;hudson.model.Hudson.Read\u0026#39; ], \u0026#39;developer\u0026#39;: [ \u0026#39;hudson.model.Hudson.Read\u0026#39;, \u0026#39;hudson.model.Item.Build\u0026#39;, \u0026#39;hudson.model.Item.Read\u0026#39;, \u0026#39;hudson.model.Item.Workspace\u0026#39; ], \u0026#39;viewer\u0026#39;: [ \u0026#39;hudson.model.Hudson.Read\u0026#39;, \u0026#39;hudson.model.Item.Read\u0026#39; ] ] // 添加全局角色 globalRoles.each { roleName, permissions -\u0026gt; def role = new Role(roleName, permissions as Set) strategy.addRole(RoleBasedAuthorizationStrategy.GLOBAL, role) } // 定义项目角色 def projectRoles = [ \u0026#39;project-admin\u0026#39;: [ \u0026#39;hudson.model.Item.Build\u0026#39;, \u0026#39;hudson.model.Item.Cancel\u0026#39;, \u0026#39;hudson.model.Item.Configure\u0026#39;, \u0026#39;hudson.model.Item.Create\u0026#39;, \u0026#39;hudson.model.Item.Delete\u0026#39;, \u0026#39;hudson.model.Item.Read\u0026#39;, \u0026#39;hudson.model.Item.Workspace\u0026#39; ], \u0026#39;project-developer\u0026#39;: [ \u0026#39;hudson.model.Item.Build\u0026#39;, \u0026#39;hudson.model.Item.Cancel\u0026#39;, \u0026#39;hudson.model.Item.Read\u0026#39;, \u0026#39;hudson.model.Item.Workspace\u0026#39; ] ] // 添加项目角色 projectRoles.each { roleName, permissions -\u0026gt; def role = new Role(roleName, \u0026#34;project-.*\u0026#34;, permissions as Set) strategy.addRole(RoleBasedAuthorizationStrategy.PROJECT, role) } // 应用策略 instance.setAuthorizationStrategy(strategy) instance.save() println \u0026#34;权限配置完成\u0026#34; 主题和界面定制 # 现代化主题配置 # 方法一：Simple Theme Plugin # 安装 Simple Theme Plugin 插件 进入 Manage Jenkins → Configure System 找到 Theme 部分 /* Jenkins Material Theme CSS */ @import url(\u0026#39;https://cdn.jsdelivr.net/gh/afonsof/jenkins-material-theme@master/dist/material-blue.css\u0026#39;); /* 自定义样式 */ .jenkins-header { background-color: #1976d2 !important; } .jenkins-logo { content: url(\u0026#39;https://your-domain.com/logo.png\u0026#39;); width: 120px; height: 40px; } /* 侧边栏样式 */ #side-panel { background-color: #f5f5f5; } /* 构建状态颜色 */ .build-status-success { color: #4caf50 !important; } .build-status-failed { color: #f44336 !important; } 方法二：Blue Ocean 现代界面 # # 安装 Blue Ocean 插件套件 jenkins-cli.jar -s http://localhost:8080/ install-plugin blueocean # 重启 Jenkins systemctl restart jenkins 企业品牌定制 # \u0026lt;!-- 自定义 HTML 头部 --\u0026gt; \u0026lt;style\u0026gt; /* 企业 Logo */ .logo img { content: url(\u0026#39;/userContent/company-logo.png\u0026#39;); max-height: 40px; } /* 企业色彩主题 */ :root { --primary-color: #your-brand-color; --secondary-color: #your-secondary-color; } /* 自定义页脚 */ .footer { background-color: var(--primary-color); color: white; text-align: center; padding: 10px; } .footer::after { content: \u0026#34;© 2024 Your Company Name. All rights reserved.\u0026#34;; } \u0026lt;/style\u0026gt; \u0026lt;script\u0026gt; // 自定义 JavaScript document.addEventListener(\u0026#39;DOMContentLoaded\u0026#39;, function() { // 添加企业信息 const footer = document.createElement(\u0026#39;div\u0026#39;); footer.className = \u0026#39;footer\u0026#39;; document.body.appendChild(footer); // 自定义页面标题 document.title = \u0026#39;Your Company CI/CD Platform\u0026#39;; }); \u0026lt;/script\u0026gt; 响应式设计优化 # /* 移动端适配 */ @media (max-width: 768px) { #main-panel { margin-left: 0; } #side-panel { transform: translateX(-100%); transition: transform 0.3s ease; } #side-panel.show { transform: translateX(0); } } /* 大屏幕优化 */ @media (min-width: 1920px) { .container { max-width: 1800px; margin: 0 auto; } } 备份与恢复 # 自动化备份策略 # 完整备份脚本 # cat \u0026gt; jenkins-backup.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # 配置变量 JENKINS_HOME=\u0026#34;/var/lib/jenkins\u0026#34; BACKUP_DIR=\u0026#34;/backup/jenkins\u0026#34; RETENTION_DAYS=30 DATE=$(date +%Y%m%d_%H%M%S) BACKUP_NAME=\u0026#34;jenkins-backup-$DATE\u0026#34; # 创建备份目录 mkdir -p $BACKUP_DIR echo \u0026#34;=== Jenkins 备份脚本 ===\u0026#34; echo \u0026#34;开始时间: $(date)\u0026#34; echo \u0026#34;备份目录: $BACKUP_DIR\u0026#34; # 停止 Jenkins 服务（可选，用于一致性备份） read -p \u0026#34;是否停止 Jenkins 服务进行一致性备份？(y/N): \u0026#34; STOP_SERVICE if [[ $STOP_SERVICE =~ ^[Yy]$ ]]; then echo \u0026#34;停止 Jenkins 服务...\u0026#34; systemctl stop jenkins RESTART_NEEDED=true fi # 创建备份 echo \u0026#34;创建备份: $BACKUP_NAME\u0026#34; tar -czf \u0026#34;$BACKUP_DIR/$BACKUP_NAME.tar.gz\u0026#34; \\ --exclude=\u0026#34;$JENKINS_HOME/workspace/*\u0026#34; \\ --exclude=\u0026#34;$JENKINS_HOME/logs/*\u0026#34; \\ --exclude=\u0026#34;$JENKINS_HOME/war/*\u0026#34; \\ --exclude=\u0026#34;$JENKINS_HOME/.m2/repository/*\u0026#34; \\ -C \u0026#34;$(dirname $JENKINS_HOME)\u0026#34; \\ \u0026#34;$(basename $JENKINS_HOME)\u0026#34; # 重启服务（如果之前停止了） if [[ $RESTART_NEEDED == true ]]; then echo \u0026#34;重启 Jenkins 服务...\u0026#34; systemctl start jenkins fi # 验证备份 if [ -f \u0026#34;$BACKUP_DIR/$BACKUP_NAME.tar.gz\u0026#34; ]; then BACKUP_SIZE=$(du -h \u0026#34;$BACKUP_DIR/$BACKUP_NAME.tar.gz\u0026#34; | cut -f1) echo \u0026#34;✓ 备份完成: $BACKUP_NAME.tar.gz ($BACKUP_SIZE)\u0026#34; else echo \u0026#34;✗ 备份失败\u0026#34; exit 1 fi # 清理过期备份 echo \u0026#34;清理 $RETENTION_DAYS 天前的备份...\u0026#34; find $BACKUP_DIR -name \u0026#34;jenkins-backup-*.tar.gz\u0026#34; -mtime +$RETENTION_DAYS -delete # 备份报告 echo \u0026#34;=== 备份报告 ===\u0026#34; echo \u0026#34;备份文件: $BACKUP_DIR/$BACKUP_NAME.tar.gz\u0026#34; echo \u0026#34;备份大小: $BACKUP_SIZE\u0026#34; echo \u0026#34;完成时间: $(date)\u0026#34; echo \u0026#34;当前备份列表:\u0026#34; ls -lh $BACKUP_DIR/jenkins-backup-*.tar.gz | tail -5 EOF chmod +x jenkins-backup.sh 增量备份脚本 # cat \u0026gt; jenkins-incremental-backup.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash JENKINS_HOME=\u0026#34;/var/lib/jenkins\u0026#34; BACKUP_DIR=\u0026#34;/backup/jenkins/incremental\u0026#34; FULL_BACKUP_DIR=\u0026#34;/backup/jenkins\u0026#34; DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR # 查找最近的完整备份 LAST_FULL_BACKUP=$(ls -t $FULL_BACKUP_DIR/jenkins-backup-*.tar.gz 2\u0026gt;/dev/null | head -1) if [ -z \u0026#34;$LAST_FULL_BACKUP\u0026#34; ]; then echo \u0026#34;未找到完整备份，请先执行完整备份\u0026#34; exit 1 fi # 获取完整备份的时间戳 FULL_BACKUP_TIME=$(stat -c %Y \u0026#34;$LAST_FULL_BACKUP\u0026#34;) echo \u0026#34;=== Jenkins 增量备份 ===\u0026#34; echo \u0026#34;基于完整备份: $(basename $LAST_FULL_BACKUP)\u0026#34; # 创建增量备份 find $JENKINS_HOME -newer \u0026#34;$LAST_FULL_BACKUP\u0026#34; -type f | \\ tar -czf \u0026#34;$BACKUP_DIR/jenkins-incremental-$DATE.tar.gz\u0026#34; -T - echo \u0026#34;✓ 增量备份完成: jenkins-incremental-$DATE.tar.gz\u0026#34; EOF chmod +x jenkins-incremental-backup.sh 定时备份配置 # # 配置 crontab 定时备份 cat \u0026gt; setup-backup-cron.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # 添加定时任务 (crontab -l 2\u0026gt;/dev/null; cat \u0026lt;\u0026lt; \u0026#39;CRON\u0026#39; # Jenkins 备份任务 # 每天凌晨 2 点执行完整备份 0 2 * * * /path/to/jenkins-backup.sh \u0026gt;\u0026gt; /var/log/jenkins-backup.log 2\u0026gt;\u0026amp;1 # 每 6 小时执行增量备份 0 */6 * * * /path/to/jenkins-incremental-backup.sh \u0026gt;\u0026gt; /var/log/jenkins-backup.log 2\u0026gt;\u0026amp;1 # 每周日执行深度清理 0 3 * * 0 find /var/lib/jenkins/workspace -type d -mtime +7 -exec rm -rf {} + 2\u0026gt;/dev/null CRON ) | crontab - echo \u0026#34;定时备份任务已配置\u0026#34; crontab -l EOF chmod +x setup-backup-cron.sh ./setup-backup-cron.sh 数据恢复 # 完整恢复脚本 # cat \u0026gt; jenkins-restore.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash BACKUP_DIR=\u0026#34;/backup/jenkins\u0026#34; JENKINS_HOME=\u0026#34;/var/lib/jenkins\u0026#34; echo \u0026#34;=== Jenkins 恢复脚本 ===\u0026#34; # 列出可用备份 echo \u0026#34;可用的备份文件:\u0026#34; ls -lh $BACKUP_DIR/jenkins-backup-*.tar.gz | nl # 选择备份文件 read -p \u0026#34;请输入要恢复的备份文件编号: \u0026#34; BACKUP_NUM BACKUP_FILE=$(ls $BACKUP_DIR/jenkins-backup-*.tar.gz | sed -n \u0026#34;${BACKUP_NUM}p\u0026#34;) if [ ! -f \u0026#34;$BACKUP_FILE\u0026#34; ]; then echo \u0026#34;备份文件不存在: $BACKUP_FILE\u0026#34; exit 1 fi echo \u0026#34;选择的备份文件: $BACKUP_FILE\u0026#34; read -p \u0026#34;确认恢复？这将覆盖现有数据 (yes/no): \u0026#34; CONFIRM if [ \u0026#34;$CONFIRM\u0026#34; != \u0026#34;yes\u0026#34; ]; then echo \u0026#34;恢复已取消\u0026#34; exit 0 fi # 停止 Jenkins 服务 echo \u0026#34;停止 Jenkins 服务...\u0026#34; systemctl stop jenkins # 备份当前数据 echo \u0026#34;备份当前数据...\u0026#34; mv $JENKINS_HOME ${JENKINS_HOME}.backup.$(date +%Y%m%d_%H%M%S) # 恢复数据 echo \u0026#34;恢复数据...\u0026#34; mkdir -p $(dirname $JENKINS_HOME) tar -xzf \u0026#34;$BACKUP_FILE\u0026#34; -C $(dirname $JENKINS_HOME) # 修复权限 echo \u0026#34;修复权限...\u0026#34; chown -R jenkins:jenkins $JENKINS_HOME # 启动 Jenkins 服务 echo \u0026#34;启动 Jenkins 服务...\u0026#34; systemctl start jenkins # 等待服务启动 echo \u0026#34;等待服务启动...\u0026#34; sleep 30 # 验证恢复 if systemctl is-active --quiet jenkins; then echo \u0026#34;✓ Jenkins 恢复成功\u0026#34; echo \u0026#34;请访问 Web 界面验证功能\u0026#34; else echo \u0026#34;✗ Jenkins 启动失败，请检查日志\u0026#34; journalctl -u jenkins --no-pager -n 20 fi EOF chmod +x jenkins-restore.sh 监控与告警 # 系统监控 # Jenkins 性能监控脚本 # cat \u0026gt; jenkins-monitor.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash JENKINS_URL=\u0026#34;http://localhost:8080\u0026#34; JENKINS_HOME=\u0026#34;/var/lib/jenkins\u0026#34; LOG_FILE=\u0026#34;/var/log/jenkins-monitor.log\u0026#34; # 日志函数 log() { echo \u0026#34;[$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;)] $1\u0026#34; | tee -a $LOG_FILE } # 检查 Jenkins 服务状态 check_service() { if systemctl is-active --quiet jenkins; then log \u0026#34;✓ Jenkins 服务运行正常\u0026#34; return 0 else log \u0026#34;✗ Jenkins 服务异常\u0026#34; return 1 fi } # 检查 Web 界面响应 check_web_response() { local response_code=$(curl -s -o /dev/null -w \u0026#34;%{http_code}\u0026#34; $JENKINS_URL/login) if [ \u0026#34;$response_code\u0026#34; = \u0026#34;200\u0026#34; ]; then log \u0026#34;✓ Web 界面响应正常\u0026#34; return 0 else log \u0026#34;✗ Web 界面响应异常 (HTTP $response_code)\u0026#34; return 1 fi } # 检查磁盘空间 check_disk_space() { local usage=$(df $JENKINS_HOME | awk \u0026#39;NR==2 {print $5}\u0026#39; | sed \u0026#39;s/%//\u0026#39;) if [ $usage -lt 80 ]; then log \u0026#34;✓ 磁盘空间充足 (${usage}%)\u0026#34; return 0 elif [ $usage -lt 90 ]; then log \u0026#34;⚠ 磁盘空间不足 (${usage}%)\u0026#34; return 1 else log \u0026#34;✗ 磁盘空间严重不足 (${usage}%)\u0026#34; return 2 fi } # 检查内存使用 check_memory() { local jenkins_pid=$(pgrep -f jenkins.war) if [ -n \u0026#34;$jenkins_pid\u0026#34; ]; then local memory_mb=$(ps -p $jenkins_pid -o rss= | awk \u0026#39;{print int($1/1024)}\u0026#39;) log \u0026#34;✓ Jenkins 内存使用: ${memory_mb}MB\u0026#34; if [ $memory_mb -gt 4096 ]; then log \u0026#34;⚠ 内存使用较高\u0026#34; return 1 fi return 0 else log \u0026#34;✗ 无法获取 Jenkins 进程信息\u0026#34; return 1 fi } # 检查构建队列 check_build_queue() { local queue_length=$(curl -s \u0026#34;$JENKINS_URL/queue/api/json\u0026#34; | jq \u0026#39;.items | length\u0026#39; 2\u0026gt;/dev/null || echo \u0026#34;0\u0026#34;) log \u0026#34;构建队列长度: $queue_length\u0026#34; if [ $queue_length -gt 10 ]; then log \u0026#34;⚠ 构建队列过长\u0026#34; return 1 fi return 0 } # 主监控函数 main() { log \u0026#34;=== Jenkins 监控检查开始 ===\u0026#34; local issues=0 check_service || ((issues++)) check_web_response || ((issues++)) check_disk_space || ((issues++)) check_memory || ((issues++)) check_build_queue || ((issues++)) if [ $issues -eq 0 ]; then log \u0026#34;✓ 所有检查通过\u0026#34; else log \u0026#34;⚠ 发现 $issues 个问题\u0026#34; # 这里可以添加告警通知 send_alert \u0026#34;Jenkins 监控发现 $issues 个问题\u0026#34; fi log \u0026#34;=== Jenkins 监控检查完成 ===\u0026#34; } # 告警通知函数 send_alert() { local message=\u0026#34;$1\u0026#34; # 邮件通知 if command -v mail \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;$message\u0026#34; | mail -s \u0026#34;Jenkins 监控告警\u0026#34; admin@your-domain.com fi # 钉钉通知（需要配置 webhook） if [ -n \u0026#34;$DINGTALK_WEBHOOK\u0026#34; ]; then curl -X POST \u0026#34;$DINGTALK_WEBHOOK\u0026#34; \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#34;{\\\u0026#34;msgtype\\\u0026#34;: \\\u0026#34;text\\\u0026#34;, \\\u0026#34;text\\\u0026#34;: {\\\u0026#34;content\\\u0026#34;: \\\u0026#34;$message\\\u0026#34;}}\u0026#34; fi } main \u0026#34;$@\u0026#34; EOF chmod +x jenkins-monitor.sh # 设置定时监控 echo \u0026#34;*/5 * * * * /path/to/jenkins-monitor.sh\u0026#34; | crontab - Prometheus 监控集成 # # 安装 Prometheus Metrics Plugin jenkins-cli.jar -s http://localhost:8080/ install-plugin prometheus # 配置 Prometheus 抓取 cat \u0026gt; prometheus-jenkins.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; global: scrape_interval: 15s scrape_configs: - job_name: \u0026#39;jenkins\u0026#39; static_configs: - targets: [\u0026#39;localhost:8080\u0026#39;] metrics_path: \u0026#39;/prometheus\u0026#39; scrape_interval: 30s EOF 故障排除 # 常见问题诊断 # 启动问题 # # Jenkins 启动失败诊断脚本 cat \u0026gt; diagnose-jenkins.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== Jenkins 故障诊断 ===\u0026#34; # 检查 Java 环境 echo \u0026#34;1. 检查 Java 环境\u0026#34; java -version echo \u0026#34;JAVA_HOME: $JAVA_HOME\u0026#34; # 检查 Jenkins 服务状态 echo -e \u0026#34;\\n2. 检查服务状态\u0026#34; systemctl status jenkins --no-pager # 检查端口占用 echo -e \u0026#34;\\n3. 检查端口占用\u0026#34; netstat -tlnp | grep 8080 # 检查日志 echo -e \u0026#34;\\n4. 最近的错误日志\u0026#34; journalctl -u jenkins --no-pager -n 20 | grep -i error # 检查磁盘空间 echo -e \u0026#34;\\n5. 检查磁盘空间\u0026#34; df -h /var/lib/jenkins # 检查权限 echo -e \u0026#34;\\n6. 检查权限\u0026#34; ls -la /var/lib/jenkins | head -5 # 检查配置文件 echo -e \u0026#34;\\n7. 检查配置文件\u0026#34; if [ -f /etc/sysconfig/jenkins ]; then grep -v \u0026#34;^#\u0026#34; /etc/sysconfig/jenkins | grep -v \u0026#34;^$\u0026#34; fi echo -e \u0026#34;\\n=== 诊断完成 ===\u0026#34; EOF chmod +x diagnose-jenkins.sh 性能问题 # # 性能分析脚本 cat \u0026gt; jenkins-performance-analysis.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash JENKINS_PID=$(pgrep -f jenkins.war) if [ -z \u0026#34;$JENKINS_PID\u0026#34; ]; then echo \u0026#34;Jenkins 进程未运行\u0026#34; exit 1 fi echo \u0026#34;=== Jenkins 性能分析 ===\u0026#34; echo \u0026#34;进程 ID: $JENKINS_PID\u0026#34; # CPU 使用率 echo -e \u0026#34;\\nCPU 使用率:\u0026#34; ps -p $JENKINS_PID -o pid,ppid,cmd,%cpu,%mem # 内存详情 echo -e \u0026#34;\\n内存使用详情:\u0026#34; cat /proc/$JENKINS_PID/status | grep -E \u0026#34;(VmSize|VmRSS|VmHWM)\u0026#34; # 线程信息 echo -e \u0026#34;\\n线程数量:\u0026#34; cat /proc/$JENKINS_PID/status | grep Threads # GC 信息（如果启用了 GC 日志） if [ -f /var/log/jenkins/gc.log ]; then echo -e \u0026#34;\\n最近的 GC 活动:\u0026#34; tail -10 /var/log/jenkins/gc.log fi # 网络连接 echo -e \u0026#34;\\n网络连接:\u0026#34; netstat -an | grep :8080 echo -e \u0026#34;\\n=== 分析完成 ===\u0026#34; EOF chmod +x jenkins-performance-analysis.sh 总结 # 部署优势 # 通过本指南，您可以成功部署一个企业级的 Jenkins CI/CD 平台，具有以下优势：\n技术优势 # 多种部署方式：支持传统部署、容器化部署和 Kubernetes 部署 高可用性：通过主从架构和负载均衡实现高可用 安全可靠：集成 LDAP 认证和基于角色的权限管理 可扩展性：支持分布式构建和动态 Agent 监控完善：集成监控告警和自动化运维 运维优势 # 自动化备份：完整的备份恢复策略 性能优化：JVM 调优和系统级优化 故障排除：完善的诊断和恢复机制 企业集成：支持多种企业级认证和通知系统 最佳实践 # 生产环境建议 # 资源规划：根据团队规模和项目数量合理规划资源 安全配置：启用 HTTPS、配置防火墙、定期更新 备份策略：建立完善的备份和灾难恢复计划 监控告警：部署全面的监控和告警体系 权限管理：实施最小权限原则和定期权限审计 扩展建议 # 分布式构建：配置多个 Agent 节点提高构建效率 Pipeline 优化：使用声明式 Pipeline 和共享库 工具链集成：集成代码质量、安全扫描、部署工具 容器化 CI/CD：结合 Docker 和 Kubernetes 实现现代化 CI/CD 持续改进 # Jenkins 作为 DevOps 工具链的核心，需要持续优化和改进：\n定期更新：保持 Jenkins 和插件的最新版本 性能监控：持续监控系统性能和构建效率 安全审计：定期进行安全检查和权限审计 流程优化：根据团队反馈持续优化 CI/CD 流程 通过本指南的配置和最佳实践，您可以构建一个稳定、高效、安全的企业级 Jenkins CI/CD 平台，为团队的软件开发和交付提供强有力的支撑。\n","date":"2021年3月2日","externalUrl":null,"permalink":"/posts/jenkins-install/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e推荐阅读 \n    \u003cdiv id=\"推荐阅读\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a8%e8%8d%90%e9%98%85%e8%af%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/gitlab-deploy/\"\u003e企业级 GitLab 平台部署与运维完整指南\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/spinnaker-helm-installd/\"\u003e使用 Helm 部署 Spinnaker 持续部署(CD)平台\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"/posts/kubernetes-deploy-gitlab/\"\u003e在 Kubernetes 中使用 localPv 部署 Gitlab\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eJenkins 平台简介 \n    \u003cdiv id=\"jenkins-平台简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jenkins-%e5%b9%b3%e5%8f%b0%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 Jenkins \n    \u003cdiv id=\"什么是-jenkins\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-jenkins\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJenkins 是世界领先的开源自动化服务器，由 Kohsuke Kawaguchi 于 2004 年创建。作为 DevOps 工具链的核心组件，Jenkins 为软件开发生命周期提供了强大的自动化能力。\u003c/p\u003e","title":"企业级 Jenkins CI/CD 平台部署与配置完整指南","type":"posts"},{"content":" 环境说明 # 操作系统：CentOS 7.9.2009 QEMU 版本：5.2.0 libvirt 版本：5.4.0 注意：libvirt 高版本安装较为复杂，如需安装高版本请参考官方文档\n编译安装 QEMU # 注意：以下操作命令主要在 /data 目录下进行。\n获取源码 # 国内用户如下载较慢，可使用此链接下载\nwget https://download.qemu.org/qemu-5.2.0.tar.xz tar xf qemu-5.2.0.tar.xz 安装依赖 # 安装软件依赖 # yum -y install gcc gcc-c++ automake libtool zlib-devel glib2-devel bzip2-devel libuuid-devel spice-protocol spice-server-devel usbredir-devel libaio-devel wget python3 bzip2 安装 Ninja 构建工具 # Ninja 是 Google 推出的注重速度的构建工具。相比传统的 make/makefile，Ninja 通过并行组织编译任务大大提高了构建速度。QEMU 现在采用基于 Ninja 的构建系统，因此需要安装 Ninja。\ngit clone git://github.com/ninja-build/ninja.git \u0026amp;\u0026amp; cd ninja ./configure.py --bootstrap cp ninja /usr/bin/ ninja --version 1.10.2.git 配置编译参数 # cd /data mkdir qemu-build \u0026amp;\u0026amp; cd qemu-build ../qemu-5.2.0/configure --enable-kvm --target-list=\u0026#34;arm-softmmu i386-softmmu x86_64-softmmu arm-linux-user i386-linux-user x86_64-linux-user\u0026#34; --enable-debug --cpu=x86_64 参数说明：\n--enable-kvm：启用 KVM 支持 --target-list=\u0026lt;架构名\u0026gt;：指定编译的 CPU 架构，\u0026lsquo;x86_64-softmmu\u0026rsquo; 表示目标为 x86 64位 CPU --enable-debug：启用调试功能 --cpu：指定本机 CPU 架构 编译安装 # make -j$(getconf _NPROCESSORS_ONLN) \\ \u0026amp;\u0026amp; make install 创建软链接 # ln -sf /usr/local/bin/qemu-system-x86_64 /usr/bin/qemu-kvm ln -sf /usr/local/bin/qemu-system-x86_64 /usr/libexec/qemu-kvm 验证安装 # # qemu-img --version qemu-img version 5.2.0 Copyright (c) 2003-2020 Fabrice Bellard and the QEMU Project developers # qemu-kvm --version QEMU emulator version 5.2.0 Copyright (c) 2003-2020 Fabrice Bellard and the QEMU Project developers 编译安装 libvirt 管理工具 # 安装构建依赖 # yum -y install virt-install yum -y install libnl-devel libxml2-devel yajl-devel device-mapper-devel libpciaccess-devel gnutls* libxslt libtirpc-devel libacl-devel libacl pip3 install rst2html5 安装 Meson 构建工具 # Meson 基于 Python 3 实现，至少需要 Python 3.5 才能运行，默认采用 Ninja 作为后端。此步骤在上面安装 QEMU 时已配置 Ninja。\npip3 install meson meson --version 0.57.1 下载源码 # 版本列表地址\n国内用户如下载较慢，可使用此链接下载\nwget https://libvirt.org/sources/libvirt-5.4.0.tar.xz 编译安装 # tar xf libvirt-5.4.0.tar.xz cd libvirt-5.4.0/ ./autogen.sh --system make -j$(getconf _NPROCESSORS_ONLN) \\ \u0026amp;\u0026amp; make install 安装管理工具 # yum -y install virt-manager virt-viewer bridge-utils yum -y install libXdmcp libxkbfile xorg-x11-xkb-utils 验证版本 # virsh version 根据库编译：libvirt 5.4.0 使用库：libvirt 5.4.0 使用的 API: QEMU 5.4.0 运行管理程序: QEMU 5.2.0 启动服务并设置开机自启 # service libvirtd start \\ \u0026amp;\u0026amp; systemctl enable libvirtd $ virsh list --all Id 名称 状态 ---------------------------------------------------- 创建网桥 # 示例将 ens27f0 网卡配置为桥接网卡\ncp /etc/sysconfig/network-scripts/ifcfg-ens27f0{,.bak} # 修改前备份配置文件 cat \u0026gt; /etc/sysconfig/network-scripts/ifcfg-ens27f0 \u0026lt;\u0026lt; EOF DEVICE=ens27f0 TYPE=Ethernet BOOTPROTO=none ONBOOT=yes NM_CONTROLLED=no BRIDGE=br0 EOF cat \u0026gt; /etc/sysconfig/network-scripts/ifcfg-br0 \u0026lt;\u0026lt; EOF DEVICE=\u0026#34;br0\u0026#34; BOOTPROTO=\u0026#34;static\u0026#34; IPADDR=\u0026#34;192.168.1.43\u0026#34; NETMASK=\u0026#34;255.255.255.0\u0026#34; GATEWAY=\u0026#34;192.168.1.1\u0026#34; DNS1=\u0026#34;192.168.1.112\u0026#34; ONBOOT=\u0026#34;yes\u0026#34; TYPE=\u0026#34;Bridge\u0026#34; NM_CONTROLLED=\u0026#34;no\u0026#34; EOF service network restart # 重启网络服务，注意：此操作有风险，请确认配置无误后谨慎操作 验证安装 # 使用其他机器上的模板虚拟机进行测试。\nmkdir -p /data/KvmData /data/qemu-xml # 创建虚拟机文件存放目录 方法一：导入现有虚拟机 # 在另一台机器上导出虚拟机定义文件，并复制到测试机器：\nvirsh dumpxml template \u0026gt; template.xml # 导出虚拟机定义文件 scp template.xml 192.168.1.43:/data/qemu-xml # 复制虚拟机元数据文件到测试机 scp /data/KvmData/template.img 192.168.1.43:/data/KvmData # 复制虚拟机磁盘文件到测试机 virsh define --file ./template.xml virsh start template 方法二：创建新虚拟机 # mkdir -p /data/KvmData qemu-img create -f raw /data/KvmData/centos7_0.raw 40G virt-install --name centos7 \\ --virt-type kvm \\ --vcpus 2 \\ --ram 2048 \\ --cdrom=/data/iso/CentOS-7-x86_64-Minimal-2009.iso \\ --disk=/data/KvmData/centos7_0.raw,cache=none \\ --network bridge=br0 \\ --os-type linux \\ --os-variant rhel7 \\ --graphics vnc,listen=0.0.0.0 \\ --noautoconsole 使用 VNC 远程连接 # 默认无需账号和密码，直接回车即可连接。\n","date":"2021年2月25日","externalUrl":null,"permalink":"/posts/qume-libvirt-install/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e 环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e操作系统\u003c/strong\u003e：CentOS 7.9.2009\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eQEMU 版本\u003c/strong\u003e：\u003ca\n  href=\"https://download.qemu.org/qemu-5.2.0.tar.xz\"\n    target=\"_blank\"\n  \u003e5.2.0\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003elibvirt 版本\u003c/strong\u003e：5.4.0\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e注意\u003c/strong\u003e：libvirt 高版本安装较为复杂，如需安装高版本请参考\u003ca\n  href=\"https://libvirt.org/compiling.html\"\n    target=\"_blank\"\n  \u003e官方文档\u003c/a\u003e\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e编译安装 QEMU \n    \u003cdiv id=\"编译安装-qemu\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bc%96%e8%af%91%e5%ae%89%e8%a3%85-qemu\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e注意\u003c/strong\u003e：以下操作命令主要在 \u003ccode\u003e/data\u003c/code\u003e 目录下进行。\u003c/p\u003e","title":"CentOS 7 编译安装 QEMU 及 libvirt 管理工具","type":"posts"},{"content":"","date":"2021年2月25日","externalUrl":null,"permalink":"/tags/kvm/","section":"Tags","summary":"","title":"Kvm","type":"tags"},{"content":"","date":"2021年2月25日","externalUrl":null,"permalink":"/categories/vm/","section":"Categories","summary":"","title":"Vm","type":"categories"},{"content":"","date":"2021年2月24日","externalUrl":null,"permalink":"/tags/raid/","section":"Tags","summary":"","title":"Raid","type":"tags"},{"content":" 安装 # 安装参考文档\n使用参考文档\n示例为： Centos7 中的安装步骤\nwget https://docs.broadcom.com/docs-and-downloads/raid-controllers/raid-controllers-common-files/8-07-14_MegaCLI.zip unzip 8-07-14_MegaCLI.zip rpm -ivhU Linux/MegaCli-8.07.14-1.noarch.rpm cp -a /opt/MegaRAID/MegaCli/MegaCli64 /usr/local/bin/MegaCli 命令使用记录 # 将某块物理盘下线/上线 # MegaCli -PDOffline -PhysDrv[31:8] -a0 MegaCli -PDRbld -ShowProg -PhysDrv[31:8] `#[E:S]` -aAll # 查看 rebuild 状态 MegaCli -pdrbld -progdsply -physdrv[31:8] -aALL # 进度条显示 rebuild 状态 MegaCli -PDOnline -PhysDrv [1:4] -a0 配置示例\n查看磁盘是否有错误 # MegaCli -PDList -aAll |grep -i \u0026#39;Error Count:\u0026#39; # 查看磁盘是否有错误 查看磁盘状态 # MegaCli -PDList -a0|grep \u0026#39;Firmware state\u0026#39; 查看 RAID 卡 Rebuild 参数 # MegaCli -AdpAllinfo -aALL | grep -i rebuild 显示所有逻辑磁盘组信息 # MegaCli -LDInfo -Lall -aALL 显示所有的物理信息 # MegaCli -PDList -aAll 查看物理单块磁盘状态 # MegaCli -PDRbld -ShowProg -PhysDrv [252:3] -a0 上下线磁盘 # MegaCli -PDOffline -PhysDrv [252:3] -a0 MegaCli -PDOnline -PhysDrv [252:3] -a0 ","date":"2021年2月24日","externalUrl":null,"permalink":"/posts/raid-megacli/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e安装 \n    \u003cdiv id=\"安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ca\n  href=\"https://gist.github.com/fxkraus/595ab82e07cd6f8e057d31bc0bc5e779\"\n    target=\"_blank\"\n  \u003e\u003cstrong\u003e安装参考文档\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca\n  href=\"https://www.cnblogs.com/luxiaodai/p/9871612.html\"\n    target=\"_blank\"\n  \u003e\u003cstrong\u003e使用参考文档\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e示例为： \u003ccode\u003eCentos7 \u003c/code\u003e 中的安装步骤\u003c/p\u003e\u003c/blockquote\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ewget https://docs.broadcom.com/docs-and-downloads/raid-controllers/raid-controllers-common-files/8-07-14_MegaCLI.zip\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eunzip 8-07-14_MegaCLI.zip\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003erpm -ivhU Linux/MegaCli-8.07.14-1.noarch.rpm \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecp -a /opt/MegaRAID/MegaCli/MegaCli64 /usr/local/bin/MegaCli\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e命令使用记录 \n    \u003cdiv id=\"命令使用记录\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%91%bd%e4%bb%a4%e4%bd%bf%e7%94%a8%e8%ae%b0%e5%bd%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e将某块物理盘下线/上线 \n    \u003cdiv id=\"将某块物理盘下线上线\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b0%86%e6%9f%90%e5%9d%97%e7%89%a9%e7%90%86%e7%9b%98%e4%b8%8b%e7%ba%bf%e4%b8%8a%e7%ba%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eMegaCli -PDOffline -PhysDrv[31:8] -a0\n\nMegaCli -PDRbld -ShowProg  -PhysDrv[31:8] `#[E:S]` -aAll  # 查看 rebuild 状态\n\n\nMegaCli -pdrbld -progdsply -physdrv[31:8] -aALL  # 进度条显示 rebuild 状态\n\n\nMegaCli -PDOnline -PhysDrv [1:4] -a0\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003cstrong\u003e配置示例\u003c/strong\u003e\u003c/p\u003e","title":"Raid 阵列卡 Megacli 管理工具的使用记录","type":"posts"},{"content":"","date":"2021年2月24日","externalUrl":null,"permalink":"/tags/stroage/","section":"Tags","summary":"","title":"Stroage","type":"tags"},{"content":"","date":"2021年2月21日","externalUrl":null,"permalink":"/tags/confluence/","section":"Tags","summary":"","title":"Confluence","type":"tags"},{"content":" 系统环境要求 # 环境信息 # 组件 版本 说明 操作系统 CentOS 7+ 推荐使用 CentOS 7 或更高版本 JIRA 版本 8.13.4 企业级项目管理工具 数据库 MySQL 5.7 存储 JIRA 数据 Java 环境 OpenJDK 1.8 JIRA 运行环境 💡 提示：可以访问 JIRA 官方下载页面 查看最新版本信息\n安装前准备 # 什么是 JIRA？ # JIRA 是 Atlassian 公司开发的项目管理和问题跟踪工具，广泛用于：\n项目管理：敏捷开发、任务分配、进度跟踪 问题跟踪：Bug 管理、需求管理 团队协作：工作流程管理、报告生成 环境准备 # 步骤 1：安装 Java 环境 # JIRA 需要 Java 8 或更高版本的支持。\n# 安装 OpenJDK 1.8 yum install -y java-1.8.0-openjdk # 验证安装结果 java -version 预期输出：\nopenjdk version \u0026#34;1.8.0_282\u0026#34; OpenJDK Runtime Environment (build 1.8.0_282-b08) OpenJDK 64-Bit Server VM (build 25.282-b08, mixed mode) 步骤 2：安装 MySQL 5.7 数据库 # 为什么需要数据库？ # JIRA 需要数据库来存储：\n项目数据和配置信息 用户账户和权限设置 工作流和自定义字段 历史记录和报告数据 配置 MySQL 软件源 # # 下载 MySQL 官方 YUM 源 wget https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm # 安装 YUM 源 yum localinstall mysql57-community-release-el7-11.noarch.rpm -y # 验证软件源是否配置成功 yum repolist enabled | grep \u0026#34;mysql.*-community.*\u0026#34; 预期输出：\nmysql-connectors-community/x86_64 MySQL Connectors Community 185 mysql-tools-community/x86_64 MySQL Tools Community 123 mysql57-community/x86_64 MySQL 5.7 Community Server 484 安装 MySQL 服务 # # 安装 MySQL 5.7 社区版 yum install -y mysql-community-server 启动 MySQL 服务 # # 启动 MySQL 服务 systemctl start mysqld # 检查服务状态 systemctl status mysqld # 设置开机自启动 systemctl enable mysqld 初始化数据库配置 # 获取初始密码 # MySQL 5.7 安装后会自动生成一个临时的 root 密码：\n# 查看临时密码 grep \u0026#39;temporary password\u0026#39; /var/log/mysqld.log 示例输出：\n2021-02-21T04:37:14.500441Z 1 [Note] A temporary password is generated for root@localhost: p!o2lkYqNXQu 📝 记录密码：请记住获取到的临时密码，例如：p!o2lkYqNXQu\n修改 root 密码 # # 使用临时密码登录 mysql -uroot -p # 修改 root 密码（请替换为您的强密码） mysql\u0026gt; ALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;YourStrongPassword123!\u0026#39;; # 刷新权限 mysql\u0026gt; flush privileges; 创建 JIRA 数据库 # 创建专用数据库 # 📚 参考文档：JIRA 官方 MySQL 配置指南\n# 创建 JIRA 专用数据库 mysql\u0026gt; CREATE DATABASE `jiradb` CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; 创建专用数据库用户 # 为了提高安全性，我们为 JIRA 创建一个专用的数据库用户：\n# 临时降低密码复杂度要求（仅用于演示） mysql\u0026gt; set global validate_password_policy=0; # 创建 JIRA 专用用户并授权 mysql\u0026gt; GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,REFERENCES,ALTER,INDEX on jiradb.* TO jira_user@\u0026#39;%\u0026#39; IDENTIFIED BY \u0026#39;12345678\u0026#39;; # 刷新权限 mysql\u0026gt; flush privileges; ⚠️ 安全提示：在生产环境中，请使用更强的密码，并考虑限制用户的访问来源\n优化 MySQL 配置 # 为了确保 JIRA 的最佳性能，需要调整 MySQL 配置：\n# 编辑 MySQL 配置文件 vi /etc/my.cnf # 在 [mysqld] 部分添加以下配置 [mysqld] # 设置默认存储引擎 default-storage-engine=INNODB # 字符集配置 character_set_server=utf8mb4 # InnoDB 配置优化 innodb_default_row_format=DYNAMIC innodb_large_prefix=ON innodb_file_format=Barracuda innodb_log_file_size=2G # SQL 模式配置 sql_mode=NO_AUTO_VALUE_ON_ZERO # 重启 MySQL 服务使配置生效 service mysqld restart 配置说明：\nutf8mb4：支持完整的 Unicode 字符集 DYNAMIC：支持大型索引前缀 2G：增大日志文件大小以提升性能 JIRA 软件安装 # 步骤 3：下载并安装 JIRA # 下载 JIRA 安装包 # 完成数据库配置后，开始安装 JIRA 软件：\n# 下载 JIRA 8.13.4 安装包 wget https://cdn.treesir.pub/application/jira/atlassian-jira-software-8.13.4-x64.bin # 添加执行权限 chmod a+x atlassian-jira-software-8.13.4-x64.bin # 运行安装程序 ./atlassian-jira-software-8.13.4-x64.bin 安装过程截图 # 💡 安装提示：安装过程中选择默认选项即可，JIRA 会安装到 /opt/atlassian/jira/ 目录\n步骤 4：配置数据库连接 # 安装 MySQL 驱动 # JIRA 需要 MySQL JDBC 驱动来连接数据库：\n# 下载 MySQL 连接器 wget https://cdn.treesir.pub/application/jira/mysql-connector-java-5.1.49.tar.gz # 解压驱动包 tar xf mysql-connector-java-5.1.49.tar.gz # 复制驱动到 JIRA 库目录 cp mysql-connector-java-5.1.49/mysql-connector-java-5.1.49-bin.jar \\ /opt/atlassian/jira/atlassian-jira/WEB-INF/lib/ 📝 说明：MySQL 驱动允许 JIRA 与 MySQL 数据库进行通信\n步骤 5：配置许可证管理 # 下载许可证工具 # # 下载许可证管理工具 wget https://cdn.treesir.pub/application/jira/atlassian-agent-v1.2.3.zip # 安装解压工具（如果未安装） yum install unzip -y # 解压工具包 unzip atlassian-agent-v1.2.3.zip # 创建工具目录 mkdir -p /opt/atlassian/jira/jar # 复制许可证工具 cp atlassian-agent-v1.2.3/atlassian-agent.jar /opt/atlassian/jira/jar/ ⚠️ 重要说明：此工具仅用于学习和测试环境，生产环境请购买正版许可证\n修改配置文件 # 在 JAVA_OPTS 一行中添加 -javaagent:/opt/atlassian/jira/jar/atlassian-agent.jar\ncd /opt/atlassian/jira/bin vim setenv.sh # 编辑配置文件 JAVA_OPTS=\u0026#34;-javaagent:/opt/atlassian/jira/jar/atlassian-agent.jar -Xms${JVM_MINIMUM_MEMORY} -Xmx${JVM_MAXIMUM_MEMORY} ${JVM_CODE_CACHE_ARGS} ${JAVA_OPTS} ${JVM_REQUIRED_ARGS} ${DISABLE_NOTIFICATIONS} ${JVM_SUPPORT_RECOMMENDED_ARGS} ${JVM_EXTRA_ARGS} ${JIRA_HOME_MINUSD} ${START_JIRA_JAVA_OPTS}\u0026#34; service jira stop \\ \u0026amp;\u0026amp; service jira start # 重启服务 dashboard 初始化配置 # 记住 server id\n使用工具 获取 license # 复制服务器 id 放至 -s 之后\ncd /opt/atlassian/jira/jar/ /opt/atlassian/jira/jre/bin/java -jar atlassian-agent.jar -p jira -m aaa@bbb.com -n my_name -o https://zhile.io -s BEVE-RLKR-E429-3JWB ==================================================== ======= Atlassian Crack Agent ======= ======= https://zhile.io ======= ======= QQ Group: 30347511 ======= ==================================================== Your license code(Don\u0026#39;t copy this line!!!): AAABfw0ODAoPeJx9klFrwjAUhd/7Kwp7TrVVnAqBzTaDblWHVfc40nrVjDYtN6mb+/WLths6RQiEh JyT75577+JK2k+Q2J5ru51htzdsd20/ntte23OtDQLIbVGWgE4kUpAK5vsSJjwH6k/HYzbzw8fI8 hG4FoUMuAZ6EJK2R4z8hiQAlaIoDyq6kJnIhYaVndUCO9nbW61LNWy1vrciA0cU1pgLqUFymQL7K gXum9/6A9K+N8v6EMh/KdlK1NaTKByHcxZYkypPAKfrhQJUlPzB3fAqsVhVqXYOB6KKtf7kCM6F0 Y23PNViB1RjBWdZnt43NS+N24HYs9iOZ9UxT7rmmQJrihsuhaqvLnLxC6mNHzP5ZJRz/pAkiZMWe c11nfb0/xv4seaoARuMJrAwoFEYxGxCIrfndvr9XqfXdV33LP9rLY8Bd4BGPmJLRmbRy4ywrjcgn ee30bVJu+zha4Xpliv4P2enYjBDgiUK1ZRnQOkV2Ca1I2O+f5dm/wFMhgkNMCwCFDyreObHK4tWu NBWoOQpy/gWRwsUAhR9tkejACVL+tc1RzzSopum6rxwfg==X02im confluence 安装 # confluence 安装过程与 jira 安装大致相同 (数据驱动+破解补丁) 如在一台机器上可使用同一个破解补丁，只要保证端口不冲突即可。下面展示与 jira 安装的 不同项。\nconfluence 安装与 jira 安装不同项展示 # 安装包 下载\nwget https://cdn.treesir.pub/application/jira/atlassian-confluence-7.4.1-x64.bin 数据库隔离级别\n# 编辑 my.cnf 添加配置，修改事务提交级别 vi /etc/my.cnf [mysqld] transaction-isolation=READ-COMMITTED mysql\u0026gt; SET GLOBAL tx_isolation=\u0026#39;READ-COMMITTED\u0026#39;; mysql\u0026gt; select @@tx_isolation; mysql\u0026gt; CREATE DATABASE `confluence_db` CHARACTER SET utf8 COLLATE utf8_bin; cp mysql-connector-java-5.1.49/mysql-connector-java-5.1.49-bin.jar /opt/atlassian/confluence/confluence/WEB-INF/lib setenv.sh 文件\n# javaagent 为破解补丁地址 export JAVA_OPTS=\u0026#34;-javaagent:/opt/atlassian/jira/jar/atlassian-agent.jar ${JAVA_OPTS}\u0026#34; #最上边添加这行配置 使用破解补丁获取 license\n/opt/atlassian/confluence/jre/bin/java -jar /opt/atlassian/jira/jar/atlassian-agent.jar -p conf -m aaa@bbb.com -n my_name -o https://zhile.io -s B6RI-Q8HH-ZDR0-FDZ1 # -s 后接你服务器id 问题记录 # 启动权限问题解决\nchown confluence:confluence -R /opt/atlassian/confluence/ sudo chown -R confluence:confluence /opt/atlassian/confluence sudo chown -R confluence:confluence /home/confluence sudo chmod -R u=rwx,g=rx,o=rx /opt/atlassian/confluence sudo chmod -R u=rwx,g=rx,o=rx /home/confluence 如安装过程中，需要卸载安装。\n/opt/atlassian/confluence/uninstall rm -rf /var/atlassian/application-data/confluence /opt/atlassian/jira/uninstall rm -rf /var/atlassian/application-data/jira 配置 nginx 代理 # Jira 默认监听在主机中的 8080 端口上, 为了 方便记忆 通常会修改成域名且 不带后面的端口号 的形式进行访问。不带端口号的话，这样就是要使用 80 or 443端口号了，显然我们这里是内网，不需要什么安全性，只需要使用 80 端口就行。如果让 Jira 监听在 80 端口上的话，显的有些浪费，为了不保证浪费此时我们就可以使用另外一个工具 nginx 配置虚拟机主机来实现反代, 这样就可以同时将 80 端口提供给 多个服务进行使用了。\nJira nginx 配置 参考文档 修改 Jira 配置文件\nvi /opt/atlassian/jira/conf/server.xml \u0026lt;Connector port=\u0026#34;8080\u0026#34; relaxedPathChars=\u0026#34;[]|\u0026#34; relaxedQueryChars=\u0026#34;[]|{}^\u0026amp;#x5c;\u0026amp;#x60;\u0026amp;quot;\u0026amp;lt;\u0026amp;gt;\u0026#34; maxThreads=\u0026#34;150\u0026#34; minSpareThreads=\u0026#34;25\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; enableLookups=\u0026#34;false\u0026#34; maxHttpHeaderSize=\u0026#34;8192\u0026#34; protocol=\u0026#34;HTTP/1.1\u0026#34; useBodyEncodingForURI=\u0026#34;true\u0026#34; redirectPort=\u0026#34;8443\u0026#34; acceptCount=\u0026#34;100\u0026#34; disableUploadTimeout=\u0026#34;true\u0026#34; bindOnInit=\u0026#34;false\u0026#34; proxyName=\u0026#34;jira.treesir.pub\u0026#34; proxyPort=\u0026#34;80\u0026#34;/\u0026gt; 添加 proxyName \u0026amp; proxyPort 参数后, 重启服务。\nservice jira stop \\ \u0026amp;\u0026amp; service jira start Nginx 添加虚拟机主机\ncat /etc/nginx/conf.d/jira.conf server { listen 80; server_name jira.treesir.pub; location / { proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header Host $host:$server_port; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.5.188:8080; client_max_body_size 0; } } nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful # 检查配置文件 192.168.5.188:8080 为你 Jira 的地址，不要配置 127.0.0.1:8080 这样好像会报错。\n","date":"2021年2月21日","externalUrl":null,"permalink":"/posts/jira-install/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e系统环境要求 \n    \u003cdiv id=\"系统环境要求\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%b3%bb%e7%bb%9f%e7%8e%af%e5%a2%83%e8%a6%81%e6%b1%82\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e环境信息 \n    \u003cdiv id=\"环境信息\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e4%bf%a1%e6%81%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e组件\u003c/th\u003e\n          \u003cth\u003e版本\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e操作系统\u003c/td\u003e\n          \u003ctd\u003eCentOS 7+\u003c/td\u003e\n          \u003ctd\u003e推荐使用 CentOS 7 或更高版本\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eJIRA 版本\u003c/td\u003e\n          \u003ctd\u003e8.13.4\u003c/td\u003e\n          \u003ctd\u003e企业级项目管理工具\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e数据库\u003c/td\u003e\n          \u003ctd\u003eMySQL 5.7\u003c/td\u003e\n          \u003ctd\u003e存储 JIRA 数据\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eJava 环境\u003c/td\u003e\n          \u003ctd\u003eOpenJDK 1.8\u003c/td\u003e\n          \u003ctd\u003eJIRA 运行环境\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cblockquote\u003e\n\u003cp\u003e💡 \u003cstrong\u003e提示\u003c/strong\u003e：可以访问 \u003ca\n  href=\"https://www.atlassian.com/zh/software/jira/download\"\n    target=\"_blank\"\n  \u003eJIRA 官方下载页面\u003c/a\u003e 查看最新版本信息\u003c/p\u003e","title":"JIRA \u0026 Confluence 完整安装配置指南","type":"posts"},{"content":"","date":"2021年2月21日","externalUrl":null,"permalink":"/tags/esxi/","section":"Tags","summary":"","title":"Esxi","type":"tags"},{"content":"","date":"2021年2月21日","externalUrl":null,"permalink":"/tags/vmware/","section":"Tags","summary":"","title":"Vmware","type":"tags"},{"content":"在 VMware ESXi 环境中，虚拟机克隆是一个常见的运维操作，可以快速创建相同配置的虚拟机实例。本文将详细介绍如何在 ESXi 6.7 环境中使用命令行工具进行虚拟机克隆操作。\n虚拟机克隆概述 # 什么是虚拟机克隆 # 虚拟机克隆是指创建现有虚拟机的完整副本，包括：\n虚拟磁盘: 完整复制源虚拟机的磁盘数据 配置文件: 虚拟机的硬件配置信息 操作系统: 已安装的操作系统和应用程序 克隆的优势 # 快速部署: 避免重复安装操作系统和应用程序 环境一致性: 确保多个虚拟机具有相同的基础配置 节省时间: 大幅减少虚拟机创建时间 标准化: 建立标准的虚拟机模板 环境信息 # 实验环境配置 # 项目 配置信息 说明 ESXi 版本 6.7 VMware vSphere Hypervisor 源虚拟机 centos-source 待克隆的模板虚拟机 目标虚拟机 jira-confluence 克隆后的新虚拟机 存储路径 /vmfs/volumes/1t-data/ 虚拟机文件存储位置 源虚拟机信息 # 图：源虚拟机的文件结构，包含 VMDK 磁盘文件和配置文件\n操作步骤 # 第一步：启用 ESXi SSH 服务 # 为了使用命令行工具进行克隆操作，需要先启用 ESXi 的 SSH 服务：\n通过 Web 界面启用 # 登录 ESXi Web 管理界面 导航到 \u0026ldquo;主机\u0026rdquo; → \u0026ldquo;管理\u0026rdquo; → \u0026ldquo;服务\u0026rdquo; 找到 \u0026ldquo;TSM-SSH\u0026rdquo; 服务并启动 图：在 ESXi Web 界面中启用 SSH 服务\n连接验证 # 启用 SSH 服务后，可以使用 SSH 客户端连接到 ESXi 主机：\n# 使用 SSH 连接到 ESXi 主机 ssh root@esxi-host-ip 图：使用 SSH 客户端成功连接到 ESXi 主机\n安全提示:\n克隆操作完成后，建议关闭 SSH 服务以提高安全性 使用强密码保护 root 账户 考虑使用 SSH 密钥认证替代密码认证 第二步：准备克隆环境 # 创建目标虚拟机目录 # 在开始克隆之前，需要为新虚拟机创建专门的存储目录：\n# 创建目标虚拟机的存储目录 mkdir -p /vmfs/volumes/1t-data/jira-confluence # 验证目录创建成功 ls -la /vmfs/volumes/1t-data/ 检查存储空间 # 确保目标存储有足够的空间来存放克隆的虚拟机：\n# 检查存储空间使用情况 df -h /vmfs/volumes/1t-data/ # 查看源虚拟机磁盘大小 ls -lh /vmfs/volumes/1t-data/centos-7-source/ 第三步：克隆虚拟机磁盘 # vmkfstools 工具介绍 # vmkfstools 是 ESXi 提供的强大的虚拟磁盘管理工具，支持多种操作：\n创建、克隆、转换虚拟磁盘 调整虚拟磁盘大小 管理 VMFS 文件系统 官方文档: VMware KB 1028042\n执行磁盘克隆 # 使用 vmkfstools 命令克隆虚拟机磁盘：\nvmkfstools -i \u0026#34;/vmfs/volumes/1t-data/centos-7-source/centos-7-source.vmdk\u0026#34; \\ \u0026#34;/vmfs/volumes/1t-data/jira-confluence/jira-confluence.vmdk\u0026#34; \\ -d thin \\ -a buslogic 参数说明:\n-i: 指定源虚拟磁盘文件路径 -d thin: 使用精简置备模式（节省存储空间） -a buslogic: 指定 SCSI 适配器类型为 BusLogic 磁盘格式选项:\nthin: 精简置备，按需分配存储空间 thick: 厚置备，预先分配全部存储空间 eagerzeroedthick: 厚置备置零，提供最佳性能和安全性 监控克隆进度 # 克隆过程可能需要较长时间，可以通过以下方式监控进度：\n# 查看克隆进程 ps aux | grep vmkfstools # 监控目标目录大小变化 watch -n 5 \u0026#39;ls -lh /vmfs/volumes/1t-data/jira-confluence/\u0026#39; 复制 虚拟机元数据 # cp /vmfs/volumes/1t-data/centos-7-source/centos-7-source.vmx /vmfs/volumes/1t-data/jira-confluence/jira-confluence.vmx 最终文件展示\n修改元数据文件 # 修改 及 删除 项:\nscsi0:0.fileName displayName vi jira-confluence.vmx 注册主机 # 找到刚才修改的元文件路径，右键注册主机\n选择我已复制，确认。\ndone。。。 # ","date":"2021年2月21日","externalUrl":null,"permalink":"/posts/esxi-clone-hosts/","section":"博客文章","summary":"\u003cp\u003e在 VMware ESXi 环境中，虚拟机克隆是一个常见的运维操作，可以快速创建相同配置的虚拟机实例。本文将详细介绍如何在 ESXi 6.7 环境中使用命令行工具进行虚拟机克隆操作。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e虚拟机克隆概述 \n    \u003cdiv id=\"虚拟机克隆概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%99%9a%e6%8b%9f%e6%9c%ba%e5%85%8b%e9%9a%86%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e什么是虚拟机克隆 \n    \u003cdiv id=\"什么是虚拟机克隆\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af%e8%99%9a%e6%8b%9f%e6%9c%ba%e5%85%8b%e9%9a%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e虚拟机克隆是指创建现有虚拟机的完整副本，包括：\u003c/p\u003e","title":"VMware ESXi 虚拟机克隆完整操作指南","type":"posts"},{"content":"","date":"2021年2月21日","externalUrl":null,"permalink":"/tags/%E5%85%8B%E9%9A%86/","section":"Tags","summary":"","title":"克隆","type":"tags"},{"content":"","date":"2021年1月26日","externalUrl":null,"permalink":"/tags/k3s/","section":"Tags","summary":"","title":"K3s","type":"tags"},{"content":"","date":"2021年1月26日","externalUrl":null,"permalink":"/tags/openwrt/","section":"Tags","summary":"","title":"Openwrt","type":"tags"},{"content":" 环境说明 # 软件版本 # K3s 版本：v1.19.7+k3s1 Docker 版本：19.03.13 (N1)，19.03.12 (OpenWrt) 写盘工具：balenaEtcher 硬件配置 # IP 地址 机型 配置 操作系统 角色 192.168.8.1 占美（机型不详） 4C 2G（CPU N2940） OpenWrt（X86_64 自编译） node/agent 192.168.8.112 斐讯 N1 4C 2G Armbian（5.0.2-aml-s905） master/server OpenWrt 自编译 # 需要自编译的原因：之前使用 esir 打包的高大全 OpenWrt 固件部署 K3s 时发现，内核没有开启 vxlan 特性，导致部署失败。如果您使用的 OpenWrt 固件已开启 vxlan 特性，可跳过此步骤。\n检查方法：\nroot@OpenWrt:~# lsmod | grep -i vxlan # 有输出表示具有 vxlan 特性 ip6_udp_tunnel 16384 1 vxlan udp_tunnel 16384 1 vxlan vxlan 53248 0 感谢 esir 等大佬提供的 GitHub Actions CI 工作流\n仓库地址 视频教程 Fork 仓库代码 # 登录 GitHub，将代码 fork 到自己的仓库。\n克隆代码 # git clone https://github.com.cnpmjs.org/cdryzun/AutoBuild-OpenWrt.git cd AutoBuild-OpenWrt 修改配置文件 # 修改工作流文件 # 以 x86_64 平台为例：\ncode .github/workflows/Build_OP_x86_64.yml # 编辑 CI 构建脚本 # 取消注释以下内容，注意空格 push: branches: - master 修改构建配置 # code x86_64.config 参考资料 # 添加插件说明 k3s-openwrt CONFIG_PACKAGE_grub2-efi=y CONFIG_EFI_IMAGES=y CONFIG_TARGET_IMAGES_GZIP=y CONFIG_TARGET_KERNEL_PARTSIZE=16 CONFIG_TARGET_ROOTFS_PARTSIZE=300 # CONFIG_GRUB_CONSOLE is not set CONFIG_PACKAGE_luci-app-docker=y CONFIG_PACKAGE_luci-i18n-docker-zh-cn=y CONFIG_PACKAGE_luci-app-ssr-plus=y CONFIG_PACKAGE_luci-app-ssr-plus_INCLUDE_Shadowsocks=y CONFIG_PACKAGE_luci-app-ssr-plus_INCLUDE_Simple_obfs=y CONFIG_PACKAGE_luci-app-ssr-plus_INCLUDE_V2ray_plugin=y CONFIG_PACKAGE_luci-app-ssr-plus_INCLUDE_V2ray=y CONFIG_PACKAGE_luci-app-ssr-plus_INCLUDE_Trojan=y CONFIG_PACKAGE_luci-app-ssr-plus_INCLUDE_Kcptun=y CONFIG_PACKAGE_luci-app-ssr-plus_INCLUDE_ShadowsocksR_Server=y CONFIG_PACKAGE_luci-app-ssr-plus_INCLUDE_ShadowsocksR_Socks=y CONFIG_PACKAGE_luci-app-ssr-plus_INCLUDE_NaiveProxy=y CONFIG_PACKAGE_luci-theme-argon=y CONFIG_PACKAGE_luci-app-mwan3=y CONFIG_PACKAGE_luci-app-mwan3helper=y CONFIG_PACKAGE_luci-i18n-mwan3-zh-cn=y CONFIG_PACKAGE_luci-i18n-mwan3helper-zh-cn=y CONFIG_PACKAGE_luci-app-syncdial=y CONFIG_PACKAGE_luci-app-sfe=y CONFIG_PACKAGE_luci-app-ttyd=y CONFIG_PACKAGE_luci-i18n-ttyd-zh-cn=y CONFIG_PACKAGE_luci-app-webadmin=y CONFIG_PACKAGE_luci-i18n-webadmin-zh-cn=y CONFIG_PACKAGE_luci-app-zerotier=y CONFIG_PACKAGE_luci-app-frpc=y CONFIG_PACKAGE_luci-app-kodexplorer=y CONFIG_PACKAGE_luci-app-nfs=y CONFIG_PACKAGE_ss=y CONFIG_PACKAGE_luci-app-nps=y CONFIG_PACKAGE_luci-app-ntpc=y CONFIG_PACKAGE_luci-app-rclone=y CONFIG_PACKAGE_luci-app-radicale=y CONFIG_PACKAGE_luci-app-qbittorrent=y CONFIG_PACKAGE_luci-app-samba=y CONFIG_PACKAGE_luci-app-wol=y CONFIG_PACKAGE_luci-app-xlnetacc=y CONFIG_PACKAGE_luci-app-bird1-ipv4=y CONFIG_PACKAGE_luci-app-bird1-ipv6=y CONFIG_PACKAGE_luci-app-commands=y CONFIG_PACKAGE_luci-app-diskman=y CONFIG_PACKAGE_luci-app-dnscrypt-proxy=y CONFIG_PACKAGE_luci-app-dnsforwarder=y CONFIG_PACKAGE_luci-app-familycloud=y CONFIG_PACKAGE_luci-app-hd-idle=y CONFIG_PACKAGE_luci-app-netdata=y CONFIG_PACKAGE_ipv6helper=y CONFIG_PACKAGE_dnsmasq_full_auth=y CONFIG_PACKAGE_dnsmasq_full_conntrack=y CONFIG_PACKAGE_dnsmasq_full_dnssec=y CONFIG_PACKAGE_curl=y CONFIG_PACKAGE_htop=y CONFIG_PACKAGE_wget=y CONFIG_PACKAGE_lrzsz=y CONFIG_PACKAGE_vim=y CONFIG_PACKAGE_zip=y CONFIG_PACKAGE_kmod-kvm-amd=y CONFIG_PACKAGE_kmod-kvm-intel=y CONFIG_PACKAGE_kmod-kvm-x86=y CONFIG_OPENSSL_ENGINE_CRYPTO=y CONFIG_OPENSSL_ENGINE_DIGEST=y CONFIG_OPENSSL_WITH_CAMELLIA=y CONFIG_OPENSSL_WITH_COMPRESSION=y CONFIG_OPENSSL_WITH_DTLS=y CONFIG_OPENSSL_WITH_EC2M=y CONFIG_OPENSSL_WITH_ERROR_MESSAGES=y CONFIG_OPENSSL_WITH_GOST=y CONFIG_OPENSSL_WITH_IDEA=y CONFIG_OPENSSL_WITH_MDC2=y CONFIG_OPENSSL_WITH_RFC3779=y CONFIG_OPENSSL_WITH_SEED=y CONFIG_OPENSSL_WITH_WHIRLPOOL=y CONFIG_OPENVPN_openssl_ENABLE_DEF_AUTH=y CONFIG_OPENVPN_openssl_ENABLE_FRAGMENT=y CONFIG_OPENVPN_openssl_ENABLE_LZ4=y CONFIG_OPENVPN_openssl_ENABLE_LZO=y CONFIG_OPENVPN_openssl_ENABLE_MULTIHOME=y CONFIG_OPENVPN_openssl_ENABLE_PF=y CONFIG_OPENVPN_openssl_ENABLE_PORT_SHARE=y CONFIG_OPENVPN_openssl_ENABLE_SERVER=y CONFIG_OPENVPN_openssl_ENABLE_SMALL=y CONFIG_KERNEL_BUILD_USER=\u0026#34;treeSir Playground\u0026#34; CONFIG_GRUB_TITLE=\u0026#34;OpenWrt AutoBuild by treeSirPlayground\u0026#34; CONFIG_PACKAGE_kmod-usb-ohci=y CONFIG_PACKAGE_kmod-usb-ohci-pci=y CONFIG_PACKAGE_kmod-usb-storage-uas=y CONFIG_PACKAGE_kmod-usb-uhci=y CONFIG_PACKAGE_kmod-sdhci=y CONFIG_PACKAGE_kmod-usb-ehci=y CONFIG_PACKAGE_kmod-usb2=y CONFIG_PACKAGE_kmod-usb2-pci=y CONFIG_PACKAGE_luci-app-passwall=y CONFIG_PACKAGE_luci-app-passwall_INCLUDE_Brook=y CONFIG_PACKAGE_luci-app-passwall_INCLUDE_Trojan=y CONFIG_PACKAGE_luci-app-passwall_INCLUDE_Trojan_GO=y CONFIG_PACKAGE_luci-app-passwall_INCLUDE_simple-obfs=y CONFIG_PACKAGE_luci-app-passwall_INCLUDE_v2ray-plugin=y CONFIG_PACKAGE_luci-app-passwall_INCLUDE_https_dns_proxy=n # CONFIG_PACKAGE_naiveproxy=y # CONFIG_PACKAGE_nspr=y # CONFIG_PACKAGE_shadowsocks-libev-ss-server=y # CONFIG_PACKAGE_ssocks=y # CONFIG_PACKAGE_ssocksd=y # CONFIG_PACKAGE_trojan-go=y # CONFIG_PACKAGE_trojan-plus=y # CONFIG_PACKAGE_chinadns-ng=y CONFIG_TARGET_x86=y CONFIG_TARGET_x86_64=y CONFIG_TARGET_x86_64_Generic=y CONFIG_FEED_luci=y CONFIG_FEED_packages=y CONFIG_FEED_routing=y CONFIG_FEED_telephony=y CONFIG_IPTABLES_CONNLABEL=y CONFIG_IPTABLES_NFTABLES=y CONFIG_KERNEL_BLK_CGROUP=y CONFIG_KERNEL_CFS_BANDWIDTH=y CONFIG_KERNEL_CGROUPS=y CONFIG_KERNEL_CGROUP_CPUACCT=y CONFIG_KERNEL_CGROUP_DEVICE=y CONFIG_KERNEL_CGROUP_FREEZER=y CONFIG_KERNEL_CGROUP_PIDS=y CONFIG_KERNEL_CGROUP_SCHED=y CONFIG_KERNEL_CPUSETS=y CONFIG_KERNEL_DEVPTS_MULTIPLE_INSTANCES=y CONFIG_KERNEL_FAIR_GROUP_SCHED=y CONFIG_KERNEL_FREEZER=y CONFIG_KERNEL_IPC_NS=y CONFIG_KERNEL_KEYS=y CONFIG_KERNEL_LXC_MISC=y CONFIG_KERNEL_MEMCG=y CONFIG_KERNEL_MM_OWNER=y CONFIG_KERNEL_NAMESPACES=y CONFIG_KERNEL_NETPRIO_CGROUP=y CONFIG_KERNEL_NET_CLS_CGROUP=y CONFIG_KERNEL_NET_NS=y CONFIG_KERNEL_PID_NS=y CONFIG_KERNEL_POSIX_MQUEUE=y CONFIG_KERNEL_PROC_PID_CPUSET=y CONFIG_KERNEL_RESOURCE_COUNTERS=y CONFIG_KERNEL_SECCOMP=y CONFIG_KERNEL_SECCOMP_FILTER=y CONFIG_KERNEL_USER_NS=y CONFIG_KERNEL_UTS_NS=y CONFIG_PACKAGE_block-mount=y CONFIG_PACKAGE_ip-bridge=y CONFIG_PACKAGE_ip-full=y CONFIG_PACKAGE_ipset=y CONFIG_PACKAGE_iptables-mod-conntrack-extra=y CONFIG_PACKAGE_iptables-mod-extra=y CONFIG_PACKAGE_iptables-mod-ipopt=y CONFIG_PACKAGE_kmod-asn1-decoder=y CONFIG_PACKAGE_kmod-br-netfilter=y CONFIG_PACKAGE_kmod-crypto-aead=y CONFIG_PACKAGE_kmod-crypto-authenc=y CONFIG_PACKAGE_kmod-crypto-crc32c=y CONFIG_PACKAGE_kmod-crypto-hash=y CONFIG_PACKAGE_kmod-crypto-hw-ccp=y CONFIG_PACKAGE_kmod-crypto-manager=y CONFIG_PACKAGE_kmod-crypto-null=y CONFIG_PACKAGE_kmod-crypto-pcompress=y CONFIG_PACKAGE_kmod-crypto-rsa=y CONFIG_PACKAGE_kmod-crypto-sha1=y CONFIG_PACKAGE_kmod-crypto-sha256=y CONFIG_PACKAGE_kmod-fuse=y CONFIG_PACKAGE_kmod-ikconfig=y CONFIG_PACKAGE_kmod-ipt-conntrack-extra=y CONFIG_PACKAGE_kmod-ipt-extra=y CONFIG_PACKAGE_kmod-ipt-ipopt=y CONFIG_PACKAGE_kmod-ipt-ipset=y CONFIG_PACKAGE_kmod-ipt-raw=y CONFIG_PACKAGE_kmod-iptunnel=y CONFIG_PACKAGE_kmod-leds-gpio=y CONFIG_PACKAGE_kmod-lib-crc32c=y CONFIG_PACKAGE_kmod-nf-conntrack-netlink=y CONFIG_PACKAGE_kmod-nf-ipvs=y CONFIG_PACKAGE_kmod-nfnetlink=y CONFIG_PACKAGE_kmod-nls-base=y CONFIG_PACKAGE_kmod-random-core=y CONFIG_PACKAGE_kmod-softdog=y CONFIG_PACKAGE_kmod-sound-core=y CONFIG_PACKAGE_kmod-udptunnel4=y CONFIG_PACKAGE_kmod-udptunnel6=y CONFIG_PACKAGE_kmod-usb-core=y CONFIG_PACKAGE_kmod-usb3=y CONFIG_PACKAGE_kmod-veth=y CONFIG_PACKAGE_kmod-vxlan=y CONFIG_PACKAGE_kmod-ppp=y CONFIG_PACKAGE_kmod-pppox=y CONFIG_PACKAGE_libipset=y CONFIG_PACKAGE_libmnl=y CONFIG_PACKAGE_libnetfilter-conntrack=y CONFIG_PACKAGE_libnfnetlink=y CONFIG_PACKAGE_libnftnl=y CONFIG_TARGET_ROOTFS_PARTSIZE=1024 # CONFIG_PACKAGE_iptables-mod-conntrack-label is not set # CONFIG_PACKAGE_kmod-ipt-conntrack-label is not set CONFIG_PACKAGE_kmod-lib-crc-ccitt=y 示例配置，仅供参考使用\n构建固件 # 触发构建 # 提交代码前，检查 Actions 是否已开启。\ngit add .github/workflows/Build_OP_x86_64.yml git add ./x86_64.config git commit -m \u0026#34;build custom openwrt\u0026#34; git push 提交完成后，等待构建结束。\n下载固件 # 构建完成后下载 OpenWrt 固件文件：\n点击后向下滚动，点击进行下载：\n省略 OpenWrt 写入步骤。下载固件后解压，进入 OpenWrt/targets/x86/64 目录即可看到固件文件。\nN1 K3s Master 部署 # N1 使用 Armbian 系统，同样省略写入步骤。N1 刷入 Armbian 系统的资料网上很多，此处不再赘述。\nDocker 配置文件 # 以下为 N1 Docker 配置文件，不一定适合所有环境，可参考部分配置使用：\ncat /etc/docker/daemon.json { \u0026#34;experimental\u0026#34;: false, \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://7bezldxe.mirror.aliyuncs.com\u0026#34; ], \u0026#34;oom-score-adjust\u0026#34;: -1000, \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34;, \u0026#34;max-file\u0026#34;: \u0026#34;3\u0026#34; }, \u0026#34;max-concurrent-downloads\u0026#34;: 10, \u0026#34;max-concurrent-uploads\u0026#34;: 10, \u0026#34;features\u0026#34;: { \u0026#34;buildkit\u0026#34;: true }, \u0026#34;insecure-registries\u0026#34;: [ \u0026#34;idocker.io\u0026#34; ] } 配置项简短说明:\n“log-driver”: “json-file” 设置json 格式日志 “oom-score-adjust”: -1000 防止容器被 内核 oom “log-opts” 设置容器日志大小 “max-concurrent-downloads”: 10 并行下载容器数量 “max-concurrent-uploads”: 10 并行上传 “storage-driver”: “overlay2” 设置存储驱动为 overlay2 “bip” 容器默认的网段 “registry-mirrors” 配置镜像下载加速这里使用的是阿里云的 “insecure-registries” 信任的私服地址 如若修改了配置，记得重启一下服务查看是否生效\nservice docker restart docker info 部署前检查 # 注意：以下内容是部署完成后的打印结果，部署前的检查步骤未做记录。\nroot@n1:/# k3s check-config Verifying binaries in /var/lib/rancher/k3s/data/ad8f0f93ebb9db5c507884fcdec249d73dd348293dac194e01462c57815cca46/bin: - sha256sum: good - links: good System: - /sbin iptables v1.6.1: older than v1.8 - swap: should be disabled - routes: default CIDRs 10.42.0.0/16 or 10.43.0.0/16 already routed Limits: - /proc/sys/kernel/keys/root_maxkeys: 1000000 info: reading kernel config from /proc/config.gz ... Generally Necessary: - cgroup hierarchy: properly mounted [/sys/fs/cgroup] - CONFIG_NAMESPACES: enabled - CONFIG_NET_NS: enabled - CONFIG_PID_NS: enabled - CONFIG_IPC_NS: enabled - CONFIG_UTS_NS: enabled - CONFIG_CGROUPS: enabled - CONFIG_CGROUP_CPUACCT: enabled - CONFIG_CGROUP_DEVICE: enabled - CONFIG_CGROUP_FREEZER: enabled - CONFIG_CGROUP_SCHED: enabled - CONFIG_CPUSETS: enabled - CONFIG_MEMCG: enabled - CONFIG_KEYS: enabled - CONFIG_VETH: enabled (as module) - CONFIG_BRIDGE: enabled (as module) - CONFIG_BRIDGE_NETFILTER: enabled (as module) - CONFIG_IP_NF_FILTER: enabled (as module) - CONFIG_IP_NF_TARGET_MASQUERADE: enabled (as module) - CONFIG_NETFILTER_XT_MATCH_ADDRTYPE: enabled (as module) - CONFIG_NETFILTER_XT_MATCH_CONNTRACK: enabled (as module) - CONFIG_NETFILTER_XT_MATCH_IPVS: enabled (as module) - CONFIG_IP_NF_NAT: enabled (as module) - CONFIG_NF_NAT: enabled (as module) - CONFIG_POSIX_MQUEUE: enabled Optional Features: - CONFIG_USER_NS: enabled - CONFIG_SECCOMP: enabled - CONFIG_CGROUP_PIDS: enabled - CONFIG_BLK_CGROUP: enabled - CONFIG_BLK_DEV_THROTTLING: enabled - CONFIG_CGROUP_PERF: enabled - CONFIG_CGROUP_HUGETLB: enabled - CONFIG_NET_CLS_CGROUP: enabled (as module) - CONFIG_CGROUP_NET_PRIO: enabled - CONFIG_CFS_BANDWIDTH: enabled - CONFIG_FAIR_GROUP_SCHED: enabled - CONFIG_RT_GROUP_SCHED: enabled - CONFIG_IP_NF_TARGET_REDIRECT: enabled (as module) - CONFIG_IP_SET: enabled (as module) - CONFIG_IP_VS: enabled (as module) - CONFIG_IP_VS_NFCT: enabled - CONFIG_IP_VS_PROTO_TCP: enabled - CONFIG_IP_VS_PROTO_UDP: enabled - CONFIG_IP_VS_RR: enabled (as module) - CONFIG_EXT4_FS: enabled - CONFIG_EXT4_FS_POSIX_ACL: enabled - CONFIG_EXT4_FS_SECURITY: enabled - Network Drivers: - \u0026#34;overlay\u0026#34;: - CONFIG_VXLAN: enabled (as module) Optional (for encrypted networks): - CONFIG_CRYPTO: enabled - CONFIG_CRYPTO_AEAD: enabled - CONFIG_CRYPTO_GCM: enabled (as module) - CONFIG_CRYPTO_SEQIV: enabled - CONFIG_CRYPTO_GHASH: enabled (as module) - CONFIG_XFRM: enabled - CONFIG_XFRM_USER: enabled (as module) - CONFIG_XFRM_ALGO: enabled (as module) - CONFIG_INET_ESP: enabled (as module) - CONFIG_INET_XFRM_MODE_TRANSPORT: enabled - Storage Drivers: - \u0026#34;overlay\u0026#34;: - CONFIG_OVERLAY_FS: enabled STATUS: pass 部署 K3s # 官方提供了部署脚本，部署非常方便：\nexport INSTALL_K3S_VERSION=v1.19.7+k3s1 export INSTALL_K3S_EXEC=\u0026#34;--docker --kube-apiserver-arg service-node-port-range=40000-65000 --no-deploy traefik --write-kubeconfig ~/.kube/config --write-kubeconfig-mode 666\u0026#34; curl -sfL http://rancher-mirror.cnrancher.com/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh - root@n1:~# kubectl get nodes NAME STATUS ROLES AGE VERSION n1 Ready master 6s v1.19.7+k3s1 OpenWrt K3s Agent 部署 # 在 OpenWrt 中部署 K3s 时不能使用官方提供的自动部署脚本，运行脚本会提示错误：[ERROR] Can not find systemd or openrc to use as a process supervisor for k3s。好在 GitHub 上有大佬提供了 OpenWrt K3s 的安装包。\nDocker 配置 # 以下为 OpenWrt 中的 Docker 配置，配置项说明请参考上面 N1 中的 Docker 配置说明：\ncat /etc/docker/daemon.json { \u0026#34;data-root\u0026#34;: \u0026#34;/data/docker-root\u0026#34;, \u0026#34;log-level\u0026#34;: \u0026#34;warn\u0026#34;, \u0026#34;oom-score-adjust\u0026#34;: -1000, \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34;, \u0026#34;max-file\u0026#34;: \u0026#34;3\u0026#34; }, \u0026#34;max-concurrent-downloads\u0026#34;: 10, \u0026#34;max-concurrent-uploads\u0026#34;: 10, \u0026#34;insecure-registries\u0026#34;: [\u0026#34;idocker.io\u0026#34;], \u0026#34;registry-mirrors\u0026#34;: [\u0026#34;https://7bezldxe.mirror.aliyuncs.com\u0026#34;], \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34;, \u0026#34;storage-opts\u0026#34;: [ \u0026#34;overlay2.override_kernel_check=true\u0026#34; ] } ⚠️ 注意：配置修改后需要重启 Docker 服务才会生效\n/etc/init.d/dockerd restart K3s Agent 部署 # 安装包 GitHub 仓库地址 下载安装包 # wget https://github.com/discordianfish/k3s-openwrt/releases/download/9/k3s_1.17.4+k3s1_x86_64.opk opkg install ./k3s_1.17.4+k3s1_x86_64.opk root@OpenWrt:/opt/tools# /usr/bin/k3s --version k3s version v1.17.4+k3s1 (3eee8ac3) 升级 K3s 版本 # 默认安装包中 K3s 版本为 v1.17.4+k3s1，与 N1 上的 Master 版本不匹配，需要升级：\nwget https://github.com/k3s-io/k3s/releases/download/v1.19.7%2Bk3s1/k3s chmod a+x k3s mv /usr/bin/k3s /usr/bin/k3s.old cp -a k3s /usr/bin/k3s root@OpenWrt:/opt/tools# k3s --version k3s version v1.19.7+k3s1 (5a00e38d) 部署前检查 # 注意：以下内容同样是部署完成后的打印结果\nroot@OpenWrt:~# k3s check-config Verifying binaries in /var/lib/rancher/k3s/data/30740d1d67da51fe92b10367ecce4d580e552c634ad4a6c4dd13297ffd1f3edd/bin: - sha256sum: good - links: good System: - /usr/sbin iptables v1.8.4 (legacy): ok - swap: disabled - routes: default CIDRs 10.42.0.0/16 or 10.43.0.0/16 already routed Limits: - /proc/sys/kernel/keys/root_maxkeys: 1000000 info: reading kernel config from /proc/config.gz ... Generally Necessary: - cgroup hierarchy: properly mounted [/sys/fs/cgroup] - CONFIG_NAMESPACES: enabled - CONFIG_NET_NS: enabled - CONFIG_PID_NS: enabled - CONFIG_IPC_NS: enabled - CONFIG_UTS_NS: enabled - CONFIG_CGROUPS: enabled - CONFIG_CGROUP_CPUACCT: enabled - CONFIG_CGROUP_DEVICE: enabled - CONFIG_CGROUP_FREEZER: enabled - CONFIG_CGROUP_SCHED: enabled - CONFIG_CPUSETS: enabled - CONFIG_MEMCG: enabled - CONFIG_KEYS: enabled - CONFIG_VETH: enabled (as module) - CONFIG_BRIDGE: enabled - CONFIG_BRIDGE_NETFILTER: enabled (as module) - CONFIG_IP_NF_FILTER: enabled (as module) - CONFIG_IP_NF_TARGET_MASQUERADE: enabled (as module) - CONFIG_NETFILTER_XT_MATCH_ADDRTYPE: enabled (as module) - CONFIG_NETFILTER_XT_MATCH_CONNTRACK: enabled (as module) - CONFIG_NETFILTER_XT_MATCH_IPVS: enabled (as module) - CONFIG_IP_NF_NAT: enabled (as module) - CONFIG_NF_NAT: enabled (as module) - CONFIG_POSIX_MQUEUE: enabled Optional Features: - CONFIG_USER_NS: enabled - CONFIG_SECCOMP: enabled - CONFIG_CGROUP_PIDS: enabled - CONFIG_BLK_CGROUP: enabled - CONFIG_BLK_DEV_THROTTLING: missing - CONFIG_CGROUP_PERF: missing - CONFIG_CGROUP_HUGETLB: missing - CONFIG_NET_CLS_CGROUP: enabled - CONFIG_CGROUP_NET_PRIO: enabled - CONFIG_CFS_BANDWIDTH: enabled - CONFIG_FAIR_GROUP_SCHED: enabled - CONFIG_RT_GROUP_SCHED: enabled - CONFIG_IP_NF_TARGET_REDIRECT: enabled (as module) - CONFIG_IP_SET: enabled (as module) - CONFIG_IP_VS: enabled (as module) - CONFIG_IP_VS_NFCT: enabled - CONFIG_IP_VS_PROTO_TCP: enabled - CONFIG_IP_VS_PROTO_UDP: enabled - CONFIG_IP_VS_RR: enabled (as module) - CONFIG_EXT4_FS: enabled - CONFIG_EXT4_FS_POSIX_ACL: missing - CONFIG_EXT4_FS_SECURITY: missing enable these ext4 configs if you are using ext3 or ext4 as backing filesystem - Network Drivers: - \u0026#34;overlay\u0026#34;: - CONFIG_VXLAN: enabled (as module) Optional (for encrypted networks): - CONFIG_CRYPTO: enabled - CONFIG_CRYPTO_AEAD: enabled - CONFIG_CRYPTO_GCM: missing - CONFIG_CRYPTO_SEQIV: missing - CONFIG_CRYPTO_GHASH: missing - CONFIG_XFRM: enabled - CONFIG_XFRM_USER: enabled (as module) - CONFIG_XFRM_ALGO: enabled (as module) - CONFIG_INET_ESP: enabled (as module) - CONFIG_INET_XFRM_MODE_TRANSPORT: missing - Storage Drivers: - \u0026#34;overlay\u0026#34;: - CONFIG_OVERLAY_FS: enabled STATUS: pass 第一次部署检查时，显示有两项未通过，与此链接中描述类似。\n修改 K3s 服务配置 # 参考：K3s 中文文档\n默认安装后的配置文件是 /etc/config/k3s，该文件默认不存在，需要手动创建。\n--token 后的配置来自 N1 Master 中的 /var/lib/rancher/k3s/server/node-token 文件：\nroot@n1:/# cat /var/lib/rancher/k3s/server/node-token K106ccb265579f1e400312844312787c9ce4dd8528b6c58e26894e953661c9a907ef5d2::server:484323236840cb1c3bea454570f85 服务配置文件 /etc/config/k3s：\ncat /etc/config/k3s config globals \u0026#39;globals\u0026#39; option opts \u0026#39;--server https://192.168.8.112:6443 --token K106ccb265579f1e400312844312787c9ce4dd8528b6c58e26894e953661c9a907ef5d2::server:484323236840cb1c3bea454570f85 --docker --kube-apiserver-arg service-node-port-range=40000-65000 --no-deploy traefik --write-kubeconfig ~/.kube/config --write-kubeconfig-mode 666\u0026#39; option root \u0026#39;/application/k3s\u0026#39; 配置文件可根据实际使用情况进行增删\n服务启动脚本 /etc/init.d/k3s：\n修改说明：下面的启动脚本中，将 uci_get 替换为 uci get，将 --server 修改为 --agent。如果部署的是 Server 端，此处可以不做修改，且 /etc/config/k3s 中也需要修改为 Server 端的配置项。\nroot@OpenWrt:~# cat /etc/init.d/k3s #!/bin/sh /etc/rc.common START=60 STOP=20 PIDFILE=/var/run/k3s.pid EXEC=\u0026#34;/usr/bin/k3s\u0026#34; ensure_cgroup_mount() { # Unmount /sys/fs/cgroup if mounted as cgroup grep -q \u0026#39; /sys/fs/cgroup cgroup\u0026#39; /proc/self/mounts \u0026amp;\u0026amp; umount /sys/fs/cgroup grep -q \u0026#39; /sys/fs/cgroup tmpfs\u0026#39; /proc/self/mounts \\ || mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup for sys in $(awk \u0026#39;!/^#/ { if ($4 == 1) print $1 }\u0026#39; /proc/cgroups); do mnt=\u0026#34;/sys/fs/cgroup/$sys\u0026#34; grep -q \u0026#34;cgroup $mnt \u0026#34; /proc/self/mounts \u0026amp;\u0026amp; continue mkdir -p \u0026#34;$mnt\u0026#34; mount -n -t cgroup -o $sys cgroup \u0026#34;$mnt\u0026#34; done } start() { ensure_cgroup_mount start-stop-daemon -S -b -x \u0026#34;$EXEC\u0026#34; -m -p \u0026#34;$PIDFILE\u0026#34; \\ -- agent $(uci get k3s.globals.opts) \\ --data-dir $(uci get k3s.globals.root) } stop() { start-stop-daemon -K -p \u0026#34;$PIDFILE\u0026#34; } 启动服务 # /etc/init.d/k3s start # 启动命令 root@OpenWrt:~# ps -ef | grep k3s 3451 root 867m S {k3s-agent} /usr/bin/k3s agent # 启动成功后，会有进程在后台运行 21059 root 1096 S grep k3s root@n1:/# kubectl get nodes NAME STATUS ROLES AGE VERSION n1 Ready master 2d3h v1.19.7+k3s1 openwrt Ready \u0026lt;none\u0026gt; 2d2h v1.19.7+k3s1 如果运行启动命令后没有响应，可以前台手动运行进行调试：\nk3s agent --server https://192.168.8.112:6443 --token K106ccb265579f1e400312844312787c9ce4dd8528b6c58e26894e953661c9a907ef5d2::server:484323236840cb1c3bea454570f85 --docker --kube-apiserver-arg service-node-port-range=40000-65000 --no-deploy traefik --write-kubeconfig ~/.kube/config --write-kubeconfig-mode 666 问题记录 # 启动 Agent 时显示以下错误：\nlevel=error msg=\u0026#34;Node password rejected, duplicate hostname or contents of \u0026#39;/etc/rancher/node/password\u0026#39; may not match server node-passwd entry, try enabling a unique node name with the --with-node-id flag\u0026#34; 问题原因：之前部署失败，但 Server 端仍保留 Agent 端的 server id。当 Agent 端再次注册到 Server 端时，Server 端通过主机名匹配，发现 ID 不匹配导致冲突。\n解决方法：在 Server 端删除旧的 server id 或更改 Agent 端的主机名（推荐在 Server 端删除）。\ncat /etc/rancher/node/password # agent 92a18abb46 cat /var/lib/rancher/k3s/server/cred/node-passwd 898170dfe92a18abb46,n1,n1, 9185b3215d3afcab9,openwrt,openwrt, # 删除此行 OpenWrt 防火墙配置 # 编辑 /etc/config/network 配置文件，添加以下内容：\nconfig interface \u0026#39;k8s\u0026#39; option proto \u0026#39;none\u0026#39; option ifname \u0026#39;cni0\u0026#39; 编辑 /etc/config/firewall，添加以下内容：\nconfig zone option name \u0026#39;k8s\u0026#39; option input \u0026#39;ACCEPT\u0026#39; option output \u0026#39;ACCEPT\u0026#39; option forward \u0026#39;ACCEPT\u0026#39; option network \u0026#39;k8s\u0026#39; 重启 OpenWrt 系统使配置生效：\nreboot 部署应用测试 # 部署一个 Deployment 类型的 Nginx 服务，测试 K3s 集群完整性：\ncat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: nginx spec: ports: - protocol: TCP name: web port: 80 selector: app: nginx --- kind: Deployment apiVersion: apps/v1 metadata: name: nginx labels: app: nginx spec: replicas: 4 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.16.1 ports: - name: web containerPort: 80 EOF watch kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-bcf5bbd7d-n2lfx 1/1 Running 0 3m2s 10.42.0.14 n1 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-bcf5bbd7d-zhj6j 1/1 Running 0 78s 10.42.0.15 n1 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-bcf5bbd7d-sxlvp 1/1 Running 0 78s 10.42.1.17 openwrt \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; nginx-bcf5bbd7d-bsxtn 1/1 Running 0 78s 10.42.1.18 openwrt \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 可以看到 N1 和 OpenWrt 分别启动了 2 个容器。现在分别在两台机器上使用 curl 测试对方主机中 Pod 的 IP，检查网络连通性。\nN1 测试 curl 10.42.1.17 和 10.42.1.18：\nOpenWrt 测试 curl 10.42.0.14 和 10.42.0.15：\n经过简单测试，K3s 集群运行正常。\n配置 Dashboard # 原本计划使用 Rancher 作为 Dashboard，考虑到资源占用问题，最终选择使用 Lens，将 Dashboard 部署在客户端。\nLens GitHub 地址\n省略安装过程\u0026hellip;\n查看 N1（Master）中的 kubectl 配置文件：\nroot@n1:/# cat ~/.kube/config 将打印内容复制到客户端电脑上，保存至 ~/.kube/k3s-config 中。\nLens 添加集群 # 如果提示证书验证错误，可以使用以下技巧：在客户端 hosts 文件中添加对应映射，并修改 kubectl 配置文件中的 server 地址 为映射的域名。\ncat /etc/hosts 192.168.8.102 n1 开启监控 # 右键点击集群，选择 Settings：\n向下滚动，点击安装监控组件：\n总结 # 至此，使用斐讯 N1 和 OpenWrt 搭建 K3s 集群的教程完成。通过本教程，我们成功：\n自编译了支持 vxlan 特性的 OpenWrt 固件 在 N1 上部署了 K3s Master 节点 在 OpenWrt 上部署了 K3s Agent 节点 配置了网络和防火墙 测试了集群功能 配置了 Lens Dashboard 整个集群现在可以正常运行，为后续的容器化应用部署提供了基础环境。\n","date":"2021年1月26日","externalUrl":null,"permalink":"/posts/n1-openwrt-k3s-deploy/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e软件版本 \n    \u003cdiv id=\"软件版本\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bd%af%e4%bb%b6%e7%89%88%e6%9c%ac\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eK3s 版本：v1.19.7+k3s1\u003c/li\u003e\n\u003cli\u003eDocker 版本：19.03.13 (N1)，19.03.12 (OpenWrt)\u003c/li\u003e\n\u003cli\u003e写盘工具：balenaEtcher\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e硬件配置 \n    \u003cdiv id=\"硬件配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%a1%ac%e4%bb%b6%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003eIP 地址\u003c/th\u003e\n          \u003cth\u003e机型\u003c/th\u003e\n          \u003cth\u003e配置\u003c/th\u003e\n          \u003cth\u003e操作系统\u003c/th\u003e\n          \u003cth\u003e角色\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e192.168.8.1\u003c/td\u003e\n          \u003ctd\u003e占美（机型不详）\u003c/td\u003e\n          \u003ctd\u003e4C 2G（CPU N2940）\u003c/td\u003e\n          \u003ctd\u003eOpenWrt（X86_64 自编译）\u003c/td\u003e\n          \u003ctd\u003enode/agent\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e192.168.8.112\u003c/td\u003e\n          \u003ctd\u003e斐讯 N1\u003c/td\u003e\n          \u003ctd\u003e4C 2G\u003c/td\u003e\n          \u003ctd\u003eArmbian（5.0.2-aml-s905）\u003c/td\u003e\n          \u003ctd\u003emaster/server\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003eOpenWrt 自编译 \n    \u003cdiv id=\"openwrt-自编译\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#openwrt-%e8%87%aa%e7%bc%96%e8%af%91\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e需要自编译的原因：之前使用 esir 打包的高大全 OpenWrt 固件部署 K3s 时发现，内核没有开启 \u003ccode\u003evxlan\u003c/code\u003e 特性，导致部署失败。如果您使用的 OpenWrt 固件已开启 \u003ccode\u003evxlan\u003c/code\u003e 特性，可跳过此步骤。\u003c/p\u003e","title":"使用斐讯 N1 和 OpenWrt 搭建 K3s 集群","type":"posts"},{"content":"","date":"2021年1月19日","externalUrl":null,"permalink":"/tags/nfs/","section":"Tags","summary":"","title":"Nfs","type":"tags"},{"content":"","date":"2021年1月19日","externalUrl":null,"permalink":"/tags/storageclass/","section":"Tags","summary":"","title":"Storageclass","type":"tags"},{"content":"","date":"2021年1月19日","externalUrl":null,"permalink":"/categories/%E5%AD%98%E5%82%A8/","section":"Categories","summary":"","title":"存储","type":"categories"},{"content":"","date":"2021年1月19日","externalUrl":null,"permalink":"/tags/%E5%AD%98%E5%82%A8/","section":"Tags","summary":"","title":"存储","type":"tags"},{"content":"在 Kubernetes 集群中，持久化存储是有状态应用的重要基础设施。NFS（Network File System）作为一种成熟的网络文件系统，可以为 Kubernetes 提供简单可靠的共享存储解决方案。本文将详细介绍如何部署 NFS StorageClass 实现动态存储供应。\n什么是 StorageClass # StorageClass 是 Kubernetes 中的一种资源对象，用于描述存储的\u0026quot;类别\u0026quot;。它的主要作用包括：\n动态供应: 自动创建 PersistentVolume（PV） 存储抽象: 为不同类型的存储提供统一接口 参数化配置: 支持不同的存储参数和策略 自动化管理: 减少手动创建 PV 的工作量 方案架构 # 本方案采用以下架构：\nflowchart LR Pod[Pod 应用PVC] --\u003e|动态创建| NFSProvisioner[NFS Provisioner] NFSProvisioner --\u003e|创建 PV| PV[PV] PV --\u003e|挂载| NFSServer[NFS ServerNFS Share] 环境准备 # 在开始部署之前，请确保您的环境满足以下要求：\n组件 版本/配置 说明 Kubernetes v1.19.6+ 支持 StorageClass 的版本 Helm v3.4.2+ 用于部署 NFS Provisioner NFS Server 任意 Linux 发行版 提供 NFS 服务 网络 集群内互通 确保 Pod 可以访问 NFS Server 示例环境配置:\nNFS Server IP: 192.168.8.66 网络段: 192.168.8.0/24 共享目录: /data/nfs.sharedir NFS 服务器配置 # 第一步：安装 NFS 服务 # 在 NFS 服务器上安装必要的软件包：\n# 安装 NFS 服务器和相关工具 yum install rpcbind nfs-utils -y # 启动并设置开机自启 systemctl enable rpcbind nfs-server systemctl start rpcbind nfs-server 第二步：创建专用用户和目录 # 为了安全和管理方便，创建专门的用户来管理 NFS 共享：\n# 创建 NFS 用户组 groupadd -g 2233 nfs-user # 创建 NFS 用户（不创建家目录，不允许登录） useradd nfs-user -M -s /sbin/nologin -u 2233 -g nfs-user # 验证用户创建 id nfs-user # 创建 NFS 共享目录 mkdir -p /data/nfs.sharedir # 设置目录权限 chown -R nfs-user:nfs-user /data/nfs.sharedir chmod 755 /data/nfs.sharedir 权限说明:\n使用固定的 UID/GID (2233) 确保在不同节点上的一致性 禁用用户登录提高安全性 设置适当的目录权限 服务端 配置文件修改 # cat /etc/exports # 配置文件 如下: /data/nfs.sharedir 192.168.8.0/24(rw,no_root_squash,no_all_squash,sync,anonuid=2233,anongid=2233) ​\t参数说明:\nread-write，可读写； ro：read-only，只读； sync：文件同时写入硬盘和内存； async：文件暂存于内存，而不是直接写入内存； no_root_squash：NFS客户端连接服务端时如果使用的是 root 的话，那么对服务端分享的目录来说，也拥有 root 权限。显然开启这项是不安全的。 root_squash：NFS客户端连接服务端时如果使用的是 root 的话，那么对服务端分享的目录来说，拥有匿名用户权限，通常他将使用 nobody 或 nfsnobody 身份； all_squash：不论NFS客户端连接服务端时使用什么用户，对服务端分享的目录来说都是拥有匿名用户权限； anonuid：匿名用户的 UID 值，可以在此处自行设定。 anongid：匿名用户的 GID 值 执行配置 生效 # exportfs -r 启动服务 \u0026amp; 设置服务开机自启 # service rpcbind start service nfs start systemctl enable nfs \\ \u0026amp;\u0026amp; systemctl enable rpcbind nfs 客户端配置 # \u0026#x26a0;\u0026#xfe0f; 注意如果在 k8s 中使用 nfs时，需要在每一个节点中多配置安装 rpcbind，因为 nfs 依赖于使用 rpc 协议进行通讯。\nyum install rpcbind rpcbind nfs-utils -y service rpcbind start systemctl enable rpcbind 客户端测试挂载 # showmount -e [nfs-server] # 查看服务端 可挂载目录 示例\tshowmount -e 192.168.8.66\nmount -t nfs 192.168.8.66:/data/nfs.sharedir /mnt 如网络不太稳定时，可以尝试切换为 tcp 协议\nmount -t nfs 192.168.8.66:/data/nfs.sharedir /mnt -o proto=tcp -o nolock 部署 nfs storageClass # helm 添加仓库 # helm repo add stable https://charts.helm.sh/stable helm repo update 创建 helm 部署文件 # cat \u0026gt; prod-values.yaml \u0026lt;\u0026lt; EOF storageClass: name: nfs-retain reclaimPolicy: Retain nfs: server: 192.168.8.66 path: \u0026#39;/data/nfs.sharedir\u0026#39; EOF reclaimPolicy 即 PersistentVolumes（pv） 的回收策略，包括 \u0026ldquo;Retain\u0026rdquo;、\u0026ldquo;Recycle\u0026rdquo; 和 \u0026ldquo;Delete\u0026rdquo;。 对于动态配置的 PersistentVolumes 来说，默认回收策略为 \u0026ldquo;Delete\u0026rdquo;。 这表示当用户删除对应的 PersistentVolumeClaim 时，动态配置的 volume 将被自动删除。 如果 volume 包含重要数据时，这种自动行为可能是不合适的。 那种情况下，更适合使用 \u0026ldquo;Retain\u0026rdquo; 策略。 使用 \u0026ldquo;Retain\u0026rdquo; 时，如果用户删除 PersistentVolumeClaim，对应的 PersistentVolume 不会被删除。 相反，它将变为 Released 状态，表示所有的数据可以被手动恢复。\n部署 # 部署至 kube-system 命名空间， storageClass 为 集群 概念，集群内任意命名空间多可以使用。\nhelm upgrade --install nfs-storage-class -f ./prod-values.yaml -n kube-system stable/nfs-client-provisioner 部署后的测试 # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: sc-nginx-pvc spec: accessModes: - ReadWriteOnce storageClassName: nfs-retain resources: requests: storage: 1Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: sc-nginx namespace: default labels: name: sc-nginx spec: replicas: 2 selector: matchLabels: name: sc-nginx template: metadata: labels: name: sc-nginx spec: containers: - name: sc-nginx image: nginx:1.16.0 volumeMounts: - mountPath: /usr/share/nginx/html name: nginx-data ports: - containerPort: 80 volumes: - name: nginx-data persistentVolumeClaim: claimName: sc-nginx-pvc EOF nfs server 端口 echo 数据 至目录下的 index.html 文件内\necho \u0026#39;hello\u0026#39; \u0026gt;\u0026gt; /data/nfs.sharedir/default-sc-nginx-pvc-pvc-bd14929c-ed1f-4ede-baa7-a39de13ee169/index.html ","date":"2021年1月19日","externalUrl":null,"permalink":"/posts/k8s-nfs-strage-class/","section":"博客文章","summary":"\u003cp\u003e在 Kubernetes 集群中，持久化存储是有状态应用的重要基础设施。NFS（Network File System）作为一种成熟的网络文件系统，可以为 Kubernetes 提供简单可靠的共享存储解决方案。本文将详细介绍如何部署 NFS StorageClass 实现动态存储供应。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 StorageClass \n    \u003cdiv id=\"什么是-storageclass\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-storageclass\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eStorageClass 是 Kubernetes 中的一种资源对象，用于描述存储的\u0026quot;类别\u0026quot;。它的主要作用包括：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e动态供应\u003c/strong\u003e: 自动创建 PersistentVolume（PV）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e存储抽象\u003c/strong\u003e: 为不同类型的存储提供统一接口\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e参数化配置\u003c/strong\u003e: 支持不同的存储参数和策略\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e自动化管理\u003c/strong\u003e: 减少手动创建 PV 的工作量\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e方案架构 \n    \u003cdiv id=\"方案架构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%a1%88%e6%9e%b6%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e本方案采用以下架构：\u003c/p\u003e","title":"在 Kubernetes 中部署 NFS 动态存储类完整指南","type":"posts"},{"content":"","date":"2021年1月19日","externalUrl":null,"permalink":"/tags/kafka/","section":"Tags","summary":"","title":"Kafka","type":"tags"},{"content":" Apache Kafka 简介 # 什么是 Apache Kafka # Apache Kafka 是一个开源的分布式事件流处理平台，由 LinkedIn 开发并贡献给 Apache 软件基金会。它被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用程序。\n核心特性 # 高吞吐量：单个节点可处理每秒数百万条消息 低延迟：端到端延迟通常在几毫秒内 持久性：消息持久化存储在磁盘，支持数据回放和恢复 分布式架构：天然支持集群部署，具备水平扩展能力 容错性：通过副本机制保证数据不丢失 实时处理：支持实时流处理和批处理 应用场景 # 消息队列：解耦系统组件，提高系统可靠性 日志聚合：收集和处理来自多个服务的日志数据 流处理：实时处理和分析数据流 事件溯源：记录系统状态变化的完整历史 数据管道：在不同系统间可靠地传输数据 微服务通信：作为微服务架构中的消息总线 架构概述 # Kafka 核心组件 # flowchart TB subgraph Producers[Producers] P1[Producer 1] P2[Producer 2] P3[Producer 3] end subgraph KafkaCluster[Kafka Cluster] B1[Broker 1Topic A/B] B2[Broker 2Topic A/B] B3[Broker 3Topic A/B] end subgraph Consumers[Consumers] C1[Consumer 1] C2[Consumer 2] C3[Consumer 3] end subgraph ZooKeeper[ZooKeeper Cluster] Z1[ZK 1] Z2[ZK 2] Z3[ZK 3] end P1 --\u003e KafkaCluster P2 --\u003e KafkaCluster P3 --\u003e KafkaCluster KafkaCluster --\u003e C1 KafkaCluster --\u003e C2 KafkaCluster --\u003e C3 KafkaCluster -.-\u003e ZooKeeper 部署方案选择 # 本指南采用以下企业级技术栈：\n组件 选择方案 说明 部署工具 Confluent Helm Charts Confluent 官方维护的 K8s 部署方案 包管理 Helm 3.x Kubernetes 应用包管理工具 管理界面 Kafka Manager (CMAK) 可视化集群管理和监控工具 监控方案 Prometheus + Grafana 完整的监控和告警体系 存储方案 持久化卷 (PV/PVC) 保证数据持久性和高可用 环境准备 # 系统要求 # 基础环境 # 组件 最低版本 推荐版本 说明 Kubernetes v1.17.4 v1.21.0+ 容器编排平台 Helm v3.3.1 v3.7.0+ Kubernetes 包管理工具 kubectl v1.17.4 v1.21.0+ Kubernetes 命令行工具 Docker 18.09.6 20.10.0+ 容器运行时 存储要求 # 存储类型 用途 性能要求 推荐方案 Kafka 数据存储 消息持久化 高 IOPS，低延迟 SSD，NVMe ZooKeeper 存储 元数据存储 中等 IOPS SSD 日志存储 应用日志 低 IOPS HDD 网络要求 # 集群内通信：Kafka 节点间需要高带宽、低延迟网络 外部访问：支持 LoadBalancer 或 Ingress 控制器 端口规划： Kafka: 9092 (内部), 9094 (外部) ZooKeeper: 2181, 2888, 3888 Kafka Manager: 9000 资源规划 # 生产环境推荐配置 # Kafka 节点：\nCPU: 4-8 核心 内存: 8-16GB RAM 存储: 500GB-2TB SSD 网络: 1Gbps+ ZooKeeper 节点：\nCPU: 2-4 核心 内存: 4-8GB RAM 存储: 100-500GB SSD 网络: 1Gbps 管理节点：\nCPU: 2 核心 内存: 2-4GB RAM 存储: 50GB 网络: 100Mbps 集群规模建议 # 环境类型 Kafka 节点 ZooKeeper 节点 副本因子 分区数建议 开发环境 1 1 1 1-10 测试环境 3 3 2 10-50 生产环境 3-9 3-5 3 50-1000+ 前置条件检查 # 创建检查脚本 # cat \u0026gt; check-prerequisites.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== Kubernetes 集群环境检查 ===\u0026#34; # 检查 kubectl 连接 echo \u0026#34;检查 kubectl 连接...\u0026#34; if kubectl cluster-info \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;✓ kubectl 连接正常\u0026#34; kubectl version --short else echo \u0026#34;✗ kubectl 连接失败\u0026#34; exit 1 fi # 检查 Helm echo -e \u0026#34;\\n检查 Helm 版本...\u0026#34; if command -v helm \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;✓ Helm 已安装\u0026#34; helm version --short else echo \u0026#34;✗ Helm 未安装\u0026#34; exit 1 fi # 检查节点资源 echo -e \u0026#34;\\n检查节点资源...\u0026#34; kubectl top nodes 2\u0026gt;/dev/null || echo \u0026#34;注意: metrics-server 未安装，无法显示资源使用情况\u0026#34; # 检查存储类 echo -e \u0026#34;\\n检查存储类...\u0026#34; kubectl get storageclass if [ $? -eq 0 ]; then echo \u0026#34;✓ 存储类配置正常\u0026#34; else echo \u0026#34;✗ 存储类配置异常\u0026#34; fi # 检查命名空间 echo -e \u0026#34;\\n检查目标命名空间...\u0026#34; NAMESPACE=\u0026#34;kafka-cluster\u0026#34; if kubectl get namespace $NAMESPACE \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;✓ 命名空间 $NAMESPACE 已存在\u0026#34; else echo \u0026#34;! 命名空间 $NAMESPACE 不存在，将自动创建\u0026#34; fi echo -e \u0026#34;\\n=== 环境检查完成 ===\u0026#34; EOF chmod +x check-prerequisites.sh ./check-prerequisites.sh 部署实施 # 步骤 1：准备 Helm Charts # 获取官方 Charts # # 创建工作目录 mkdir -p ~/kafka-k8s-deploy \u0026amp;\u0026amp; cd ~/kafka-k8s-deploy # 添加 Confluent Helm 仓库 helm repo add confluentinc https://confluentinc.github.io/cp-helm-charts/ helm repo update # 下载 Charts 到本地（可选，用于自定义） helm pull confluentinc/cp-helm-charts --untar # 查看可用版本 helm search repo confluentinc/cp-helm-charts --versions 查看默认配置 # # 查看默认配置 helm show values confluentinc/cp-helm-charts \u0026gt; default-values.yaml # 查看 Chart 信息 helm show chart confluentinc/cp-helm-charts 步骤 2：镜像准备（离线环境） # 自动化镜像处理脚本 # cat \u0026gt; manage-images.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # 配置变量 PRIVATE_REGISTRY=\u0026#34;your-registry.com\u0026#34; NAMESPACE=\u0026#34;kafka-cluster\u0026#34; CHART_NAME=\u0026#34;confluentinc/cp-helm-charts\u0026#34; # 镜像列表（根据实际版本调整） IMAGES=( \u0026#34;confluentinc/cp-zookeeper:7.2.1\u0026#34; \u0026#34;confluentinc/cp-kafka:7.2.1\u0026#34; \u0026#34;confluentinc/cp-kafka-connect:7.2.1\u0026#34; \u0026#34;confluentinc/cp-schema-registry:7.2.1\u0026#34; \u0026#34;confluentinc/cp-kafka-rest:7.2.1\u0026#34; \u0026#34;confluentinc/cp-ksqldb-server:7.2.1\u0026#34; \u0026#34;confluentinc/cp-control-center:7.2.1\u0026#34; \u0026#34;provectuslabs/kafka-ui:latest\u0026#34; \u0026#34;solsson/kafka-prometheus-jmx-exporter:0.17.0\u0026#34; ) # 函数：拉取镜像 pull_images() { echo \u0026#34;=== 拉取镜像 ===\u0026#34; for image in \u0026#34;${IMAGES[@]}\u0026#34;; do echo \u0026#34;拉取镜像: $image\u0026#34; docker pull \u0026#34;$image\u0026#34; done } # 函数：标记并推送到私有仓库 push_to_private() { echo \u0026#34;=== 推送到私有仓库 ===\u0026#34; for image in \u0026#34;${IMAGES[@]}\u0026#34;; do # 提取镜像名和标签 image_name=$(echo \u0026#34;$image\u0026#34; | cut -d\u0026#39;/\u0026#39; -f2-) private_image=\u0026#34;$PRIVATE_REGISTRY/$image_name\u0026#34; echo \u0026#34;标记镜像: $image -\u0026gt; $private_image\u0026#34; docker tag \u0026#34;$image\u0026#34; \u0026#34;$private_image\u0026#34; echo \u0026#34;推送镜像: $private_image\u0026#34; docker push \u0026#34;$private_image\u0026#34; done } # 函数：导出镜像包 export_images() { echo \u0026#34;=== 导出镜像包 ===\u0026#34; docker save \u0026#34;${IMAGES[@]}\u0026#34; | gzip \u0026gt; kafka-images-$(date +%Y%m%d).tar.gz echo \u0026#34;镜像包已保存为: kafka-images-$(date +%Y%m%d).tar.gz\u0026#34; } # 函数：导入镜像包 import_images() { if [ -f \u0026#34;$1\u0026#34; ]; then echo \u0026#34;=== 导入镜像包 ===\u0026#34; docker load \u0026lt; \u0026#34;$1\u0026#34; echo \u0026#34;镜像包导入完成\u0026#34; else echo \u0026#34;错误: 镜像包文件不存在: $1\u0026#34; exit 1 fi } # 函数：生成镜像配置 generate_image_config() { cat \u0026gt; image-overrides.yaml \u0026lt;\u0026lt; \u0026#39;YAML\u0026#39; # 镜像配置覆盖文件 cp-zookeeper: image: your-registry.com/confluentinc/cp-zookeeper imageTag: \u0026#34;7.2.1\u0026#34; cp-kafka: image: your-registry.com/confluentinc/cp-kafka imageTag: \u0026#34;7.2.1\u0026#34; cp-kafka-connect: image: your-registry.com/confluentinc/cp-kafka-connect imageTag: \u0026#34;7.2.1\u0026#34; cp-schema-registry: image: your-registry.com/confluentinc/cp-schema-registry imageTag: \u0026#34;7.2.1\u0026#34; cp-kafka-rest: image: your-registry.com/confluentinc/cp-kafka-rest imageTag: \u0026#34;7.2.1\u0026#34; cp-ksql-server: image: your-registry.com/confluentinc/cp-ksqldb-server imageTag: \u0026#34;7.2.1\u0026#34; cp-control-center: image: your-registry.com/confluentinc/cp-control-center imageTag: \u0026#34;7.2.1\u0026#34; YAML echo \u0026#34;镜像配置文件已生成: image-overrides.yaml\u0026#34; } # 主菜单 case \u0026#34;$1\u0026#34; in pull) pull_images ;; push) push_to_private ;; export) export_images ;; import) import_images \u0026#34;$2\u0026#34; ;; config) generate_image_config ;; all) pull_images export_images generate_image_config ;; *) echo \u0026#34;用法: $0 {pull|push|export|import \u0026lt;file\u0026gt;|config|all}\u0026#34; echo \u0026#34; pull - 拉取所有镜像\u0026#34; echo \u0026#34; push - 推送到私有仓库\u0026#34; echo \u0026#34; export - 导出镜像包\u0026#34; echo \u0026#34; import - 导入镜像包\u0026#34; echo \u0026#34; config - 生成镜像配置文件\u0026#34; echo \u0026#34; all - 执行 pull + export + config\u0026#34; exit 1 ;; esac EOF chmod +x manage-images.sh # 使用示例 # ./manage-images.sh pull # 拉取镜像 # ./manage-images.sh export # 导出镜像包 # ./manage-images.sh config # 生成配置文件 最小化部署镜像列表 # 对于最小化部署，只需要以下核心镜像：\n# 核心组件镜像 cat \u0026gt; minimal-images.txt \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; confluentinc/cp-zookeeper:7.2.1 confluentinc/cp-kafka:7.2.1 provectuslabs/kafka-ui:latest solsson/kafka-prometheus-jmx-exporter:0.17.0 EOF # 批量处理最小化镜像 cat \u0026gt; process-minimal-images.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash REGISTRY=\u0026#34;your-registry.com\u0026#34; echo \u0026#34;处理最小化镜像列表...\u0026#34; while IFS= read -r image; do echo \u0026#34;处理镜像: $image\u0026#34; docker pull \u0026#34;$image\u0026#34; # 标记为私有仓库镜像 private_image=\u0026#34;$REGISTRY/$image\u0026#34; docker tag \u0026#34;$image\u0026#34; \u0026#34;$private_image\u0026#34; docker push \u0026#34;$private_image\u0026#34; done \u0026lt; minimal-images.txt echo \u0026#34;最小化镜像处理完成\u0026#34; EOF chmod +x process-minimal-images.sh 步骤 3：配置部署参数 # 创建命名空间 # # 创建专用命名空间 kubectl create namespace kafka-cluster # 设置默认命名空间（可选） kubectl config set-context --current --namespace=kafka-cluster 开发环境配置 # cat \u0026gt; kafka-dev-values.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Kafka 开发环境配置 cp-kafka: enabled: true servers: 1 image: confluentinc/cp-kafka imageTag: 7.2.1 heapOptions: \u0026#34;-Xms512M -Xmx1G\u0026#34; resources: requests: cpu: 500m memory: 1Gi limits: cpu: 1000m memory: 2Gi persistence: enabled: true size: 10Gi storageClass: \u0026#34;standard\u0026#34; configurationOverrides: \u0026#34;offsets.topic.replication.factor\u0026#34;: \u0026#34;1\u0026#34; \u0026#34;transaction.state.log.replication.factor\u0026#34;: \u0026#34;1\u0026#34; \u0026#34;transaction.state.log.min.isr\u0026#34;: \u0026#34;1\u0026#34; \u0026#34;default.replication.factor\u0026#34;: \u0026#34;1\u0026#34; \u0026#34;min.insync.replicas\u0026#34;: \u0026#34;1\u0026#34; # ZooKeeper 配置 cp-zookeeper: enabled: true servers: 1 image: confluentinc/cp-zookeeper imageTag: 7.2.1 heapOptions: \u0026#34;-Xms256M -Xmx512M\u0026#34; resources: requests: cpu: 250m memory: 512Mi limits: cpu: 500m memory: 1Gi persistence: enabled: true dataDirSize: 5Gi dataLogDirSize: 5Gi dataDirStorageClass: \u0026#34;standard\u0026#34; dataLogDirStorageClass: \u0026#34;standard\u0026#34; # 禁用不需要的组件 cp-kafka-rest: enabled: false cp-kafka-connect: enabled: false cp-schema-registry: enabled: false cp-ksql-server: enabled: false cp-control-center: enabled: false EOF 生产环境配置 # cat \u0026gt; kafka-prod-values.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Kafka 生产环境配置 cp-kafka: enabled: true servers: 3 image: confluentinc/cp-kafka imageTag: 7.2.1 heapOptions: \u0026#34;-Xms4G -Xmx4G\u0026#34; resources: requests: cpu: 2000m memory: 6Gi limits: cpu: 4000m memory: 8Gi persistence: enabled: true size: 500Gi storageClass: \u0026#34;fast-ssd\u0026#34; configurationOverrides: \u0026#34;auto.create.topics.enable\u0026#34;: \u0026#34;false\u0026#34; \u0026#34;offsets.topic.replication.factor\u0026#34;: \u0026#34;3\u0026#34; \u0026#34;transaction.state.log.replication.factor\u0026#34;: \u0026#34;3\u0026#34; \u0026#34;transaction.state.log.min.isr\u0026#34;: \u0026#34;2\u0026#34; \u0026#34;default.replication.factor\u0026#34;: \u0026#34;3\u0026#34; \u0026#34;min.insync.replicas\u0026#34;: \u0026#34;2\u0026#34; \u0026#34;unclean.leader.election.enable\u0026#34;: \u0026#34;false\u0026#34; \u0026#34;log.retention.hours\u0026#34;: \u0026#34;168\u0026#34; \u0026#34;log.segment.bytes\u0026#34;: \u0026#34;1073741824\u0026#34; \u0026#34;log.retention.check.interval.ms\u0026#34;: \u0026#34;300000\u0026#34; \u0026#34;num.network.threads\u0026#34;: \u0026#34;8\u0026#34; \u0026#34;num.io.threads\u0026#34;: \u0026#34;16\u0026#34; \u0026#34;socket.send.buffer.bytes\u0026#34;: \u0026#34;102400\u0026#34; \u0026#34;socket.receive.buffer.bytes\u0026#34;: \u0026#34;102400\u0026#34; \u0026#34;socket.request.max.bytes\u0026#34;: \u0026#34;104857600\u0026#34; \u0026#34;num.partitions\u0026#34;: \u0026#34;3\u0026#34; \u0026#34;num.recovery.threads.per.data.dir\u0026#34;: \u0026#34;1\u0026#34; \u0026#34;log.flush.interval.messages\u0026#34;: \u0026#34;10000\u0026#34; \u0026#34;log.flush.interval.ms\u0026#34;: \u0026#34;1000\u0026#34; # ZooKeeper 生产配置 cp-zookeeper: enabled: true servers: 3 image: confluentinc/cp-zookeeper imageTag: 7.2.1 heapOptions: \u0026#34;-Xms1G -Xmx1G\u0026#34; resources: requests: cpu: 1000m memory: 2Gi limits: cpu: 2000m memory: 4Gi persistence: enabled: true dataDirSize: 100Gi dataLogDirSize: 100Gi dataDirStorageClass: \u0026#34;fast-ssd\u0026#34; dataLogDirStorageClass: \u0026#34;fast-ssd\u0026#34; # Schema Registry（可选） cp-schema-registry: enabled: true image: confluentinc/cp-schema-registry imageTag: 7.2.1 heapOptions: \u0026#34;-Xms512M -Xmx1G\u0026#34; resources: requests: cpu: 500m memory: 1Gi limits: cpu: 1000m memory: 2Gi # Kafka Connect（可选） cp-kafka-connect: enabled: true image: confluentinc/cp-kafka-connect imageTag: 7.2.1 heapOptions: \u0026#34;-Xms1G -Xmx2G\u0026#34; resources: requests: cpu: 1000m memory: 2Gi limits: cpu: 2000m memory: 4Gi # 禁用不需要的组件 cp-kafka-rest: enabled: false cp-ksql-server: enabled: false cp-control-center: enabled: false EOF 高可用配置（企业级） # cat \u0026gt; kafka-ha-values.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Kafka 高可用企业级配置 cp-kafka: enabled: true servers: 5 image: confluentinc/cp-kafka imageTag: 7.2.1 heapOptions: \u0026#34;-Xms6G -Xmx6G\u0026#34; resources: requests: cpu: 4000m memory: 8Gi limits: cpu: 8000m memory: 12Gi persistence: enabled: true size: 1Ti storageClass: \u0026#34;premium-ssd\u0026#34; # 反亲和性配置 podAntiAffinity: \u0026#34;hard\u0026#34; # 节点选择器 nodeSelector: kafka-node: \u0026#34;true\u0026#34; # 容忍度配置 tolerations: - key: \u0026#34;kafka-dedicated\u0026#34; operator: \u0026#34;Equal\u0026#34; value: \u0026#34;true\u0026#34; effect: \u0026#34;NoSchedule\u0026#34; configurationOverrides: \u0026#34;auto.create.topics.enable\u0026#34;: \u0026#34;false\u0026#34; \u0026#34;offsets.topic.replication.factor\u0026#34;: \u0026#34;3\u0026#34; \u0026#34;transaction.state.log.replication.factor\u0026#34;: \u0026#34;3\u0026#34; \u0026#34;transaction.state.log.min.isr\u0026#34;: \u0026#34;2\u0026#34; \u0026#34;default.replication.factor\u0026#34;: \u0026#34;3\u0026#34; \u0026#34;min.insync.replicas\u0026#34;: \u0026#34;2\u0026#34; \u0026#34;unclean.leader.election.enable\u0026#34;: \u0026#34;false\u0026#34; \u0026#34;log.retention.hours\u0026#34;: \u0026#34;168\u0026#34; \u0026#34;log.segment.bytes\u0026#34;: \u0026#34;1073741824\u0026#34; \u0026#34;num.network.threads\u0026#34;: \u0026#34;16\u0026#34; \u0026#34;num.io.threads\u0026#34;: \u0026#34;32\u0026#34; \u0026#34;socket.send.buffer.bytes\u0026#34;: \u0026#34;102400\u0026#34; \u0026#34;socket.receive.buffer.bytes\u0026#34;: \u0026#34;102400\u0026#34; \u0026#34;socket.request.max.bytes\u0026#34;: \u0026#34;104857600\u0026#34; \u0026#34;num.partitions\u0026#34;: \u0026#34;5\u0026#34; \u0026#34;compression.type\u0026#34;: \u0026#34;lz4\u0026#34; \u0026#34;log.cleanup.policy\u0026#34;: \u0026#34;delete\u0026#34; \u0026#34;log.retention.check.interval.ms\u0026#34;: \u0026#34;300000\u0026#34; # ZooKeeper 高可用配置 cp-zookeeper: enabled: true servers: 5 image: confluentinc/cp-zookeeper imageTag: 7.2.1 heapOptions: \u0026#34;-Xms2G -Xmx2G\u0026#34; resources: requests: cpu: 2000m memory: 4Gi limits: cpu: 4000m memory: 6Gi persistence: enabled: true dataDirSize: 200Gi dataLogDirSize: 200Gi dataDirStorageClass: \u0026#34;premium-ssd\u0026#34; dataLogDirStorageClass: \u0026#34;premium-ssd\u0026#34; # 反亲和性配置 podAntiAffinity: \u0026#34;hard\u0026#34; # 节点选择器 nodeSelector: zookeeper-node: \u0026#34;true\u0026#34; # 启用完整的 Confluent Platform cp-schema-registry: enabled: true replicaCount: 3 image: confluentinc/cp-schema-registry imageTag: 7.2.1 cp-kafka-connect: enabled: true replicaCount: 3 image: confluentinc/cp-kafka-connect imageTag: 7.2.1 cp-kafka-rest: enabled: true replicaCount: 2 image: confluentinc/cp-kafka-rest imageTag: 7.2.1 cp-ksql-server: enabled: true replicaCount: 2 image: confluentinc/cp-ksqldb-server imageTag: 7.2.1 cp-control-center: enabled: true image: confluentinc/cp-control-center imageTag: 7.2.1 EOF 步骤 4：执行部署 # 部署命令 # # 开发环境部署 helm upgrade --install kafka-dev \\ confluentinc/cp-helm-charts \\ -f kafka-dev-values.yaml \\ -n kafka-cluster \\ --create-namespace # 生产环境部署 helm upgrade --install kafka-prod \\ confluentinc/cp-helm-charts \\ -f kafka-prod-values.yaml \\ -n kafka-cluster \\ --create-namespace # 高可用环境部署 helm upgrade --install kafka-ha \\ confluentinc/cp-helm-charts \\ -f kafka-ha-values.yaml \\ -n kafka-cluster \\ --create-namespace 部署验证 # # 查看部署状态 helm list -n kafka-cluster # 查看 Pod 状态 kubectl get pods -n kafka-cluster -w # 查看服务状态 kubectl get svc -n kafka-cluster # 查看持久化卷 kubectl get pvc -n kafka-cluster # 查看详细信息 kubectl describe deployment -n kafka-cluster 管理工具部署 # Kafka UI 部署（推荐） # 现代化管理界面 # Kafka UI 是一个现代化的 Kafka 集群管理工具，提供直观的 Web 界面。\ncat \u0026gt; kafka-ui-deployment.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; apiVersion: apps/v1 kind: Deployment metadata: name: kafka-ui namespace: kafka-cluster labels: app: kafka-ui spec: replicas: 1 selector: matchLabels: app: kafka-ui template: metadata: labels: app: kafka-ui spec: containers: - name: kafka-ui image: provectuslabs/kafka-ui:latest ports: - containerPort: 8080 env: - name: KAFKA_CLUSTERS_0_NAME value: \u0026#34;kafka-cluster\u0026#34; - name: KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS value: \u0026#34;kafka-dev-cp-kafka:9092\u0026#34; - name: KAFKA_CLUSTERS_0_ZOOKEEPER value: \u0026#34;kafka-dev-cp-zookeeper:2181\u0026#34; - name: KAFKA_CLUSTERS_0_READONLY value: \u0026#34;false\u0026#34; - name: KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME value: \u0026#34;kafka-connect\u0026#34; - name: KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS value: \u0026#34;http://kafka-dev-cp-kafka-connect:8083\u0026#34; - name: KAFKA_CLUSTERS_0_SCHEMAREGISTRY value: \u0026#34;http://kafka-dev-cp-schema-registry:8081\u0026#34; resources: requests: cpu: 200m memory: 512Mi limits: cpu: 500m memory: 1Gi livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 30 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 --- apiVersion: v1 kind: Service metadata: name: kafka-ui namespace: kafka-cluster labels: app: kafka-ui spec: type: ClusterIP ports: - port: 8080 targetPort: 8080 protocol: TCP name: http selector: app: kafka-ui --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: kafka-ui namespace: kafka-cluster annotations: kubernetes.io/ingress.class: \u0026#34;nginx\u0026#34; nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/ssl-redirect: \u0026#34;false\u0026#34; spec: rules: - host: kafka-ui.your-domain.com http: paths: - path: / pathType: Prefix backend: service: name: kafka-ui port: number: 8080 EOF 部署 Kafka UI # # 部署 Kafka UI kubectl apply -f kafka-ui-deployment.yaml # 查看部署状态 kubectl get pods -n kafka-cluster -l app=kafka-ui # 查看服务 kubectl get svc -n kafka-cluster kafka-ui # 端口转发（本地访问） kubectl port-forward -n kafka-cluster svc/kafka-ui 8080:8080 Kafka Manager (CMAK) 部署（备选） # 传统管理工具 # cat \u0026gt; kafka-manager-deployment.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; apiVersion: apps/v1 kind: Deployment metadata: name: kafka-manager namespace: kafka-cluster labels: app: kafka-manager spec: replicas: 1 selector: matchLabels: app: kafka-manager template: metadata: labels: app: kafka-manager spec: containers: - name: kafka-manager image: hlebalbau/kafka-manager:stable ports: - containerPort: 9000 env: - name: ZK_HOSTS value: \u0026#34;kafka-dev-cp-zookeeper:2181\u0026#34; - name: APPLICATION_SECRET value: \u0026#34;random-secret-key-change-in-production\u0026#34; - name: KM_ARGS value: \u0026#34;-Djava.net.preferIPv4Stack=true\u0026#34; resources: requests: cpu: 200m memory: 512Mi limits: cpu: 500m memory: 1Gi livenessProbe: httpGet: path: / port: 9000 initialDelaySeconds: 60 periodSeconds: 30 readinessProbe: httpGet: path: / port: 9000 initialDelaySeconds: 30 periodSeconds: 10 --- apiVersion: v1 kind: Service metadata: name: kafka-manager namespace: kafka-cluster labels: app: kafka-manager spec: type: ClusterIP ports: - port: 9000 targetPort: 9000 protocol: TCP name: http selector: app: kafka-manager --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: kafka-manager namespace: kafka-cluster annotations: kubernetes.io/ingress.class: \u0026#34;nginx\u0026#34; nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: kafka-manager-auth nginx.ingress.kubernetes.io/auth-realm: \u0026#39;Authentication Required\u0026#39; spec: rules: - host: kafka-manager.your-domain.com http: paths: - path: / pathType: Prefix backend: service: name: kafka-manager port: number: 9000 EOF 创建认证密钥 # # 创建基本认证密钥 htpasswd -c auth admin kubectl create secret generic kafka-manager-auth \\ --from-file=auth \\ -n kafka-cluster # 部署 Kafka Manager kubectl apply -f kafka-manager-deployment.yaml 监控工具部署 # Prometheus JMX Exporter # cat \u0026gt; kafka-monitoring.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; apiVersion: v1 kind: ConfigMap metadata: name: kafka-jmx-config namespace: kafka-cluster data: kafka-jmx-config.yml: | rules: - pattern: kafka.server\u0026lt;type=(.+), name=(.+)PerSec, topic=(.+)\u0026gt;\u0026lt;\u0026gt;Count name: kafka_server_$1_$2_per_sec type: COUNTER labels: topic: \u0026#34;$3\u0026#34; - pattern: kafka.server\u0026lt;type=(.+), name=(.+)PerSec\u0026gt;\u0026lt;\u0026gt;Count name: kafka_server_$1_$2_per_sec type: COUNTER - pattern: kafka.server\u0026lt;type=(.+), name=(.+), topic=(.+), partition=(.+)\u0026gt;\u0026lt;\u0026gt;Value name: kafka_server_$1_$2 type: GAUGE labels: topic: \u0026#34;$3\u0026#34; partition: \u0026#34;$4\u0026#34; - pattern: kafka.server\u0026lt;type=(.+), name=(.+)\u0026gt;\u0026lt;\u0026gt;Value name: kafka_server_$1_$2 type: GAUGE --- apiVersion: apps/v1 kind: Deployment metadata: name: kafka-jmx-exporter namespace: kafka-cluster spec: replicas: 1 selector: matchLabels: app: kafka-jmx-exporter template: metadata: labels: app: kafka-jmx-exporter spec: containers: - name: kafka-jmx-exporter image: solsson/kafka-prometheus-jmx-exporter:0.17.0 ports: - containerPort: 5556 volumeMounts: - name: config mountPath: /etc/jmx-kafka env: - name: JMX_PORT value: \u0026#34;5555\u0026#34; - name: CONFIG_YML value: \u0026#34;/etc/jmx-kafka/kafka-jmx-config.yml\u0026#34; resources: requests: cpu: 100m memory: 128Mi limits: cpu: 200m memory: 256Mi volumes: - name: config configMap: name: kafka-jmx-config --- apiVersion: v1 kind: Service metadata: name: kafka-jmx-exporter namespace: kafka-cluster labels: app: kafka-jmx-exporter spec: ports: - port: 5556 targetPort: 5556 name: metrics selector: app: kafka-jmx-exporter EOF kubectl apply -f kafka-monitoring.yaml 部署验证 # # 查看所有管理工具状态 kubectl get pods -n kafka-cluster -l \u0026#39;app in (kafka-ui,kafka-manager,kafka-jmx-exporter)\u0026#39; # 查看服务 kubectl get svc -n kafka-cluster # 查看 Ingress kubectl get ingress -n kafka-cluster 网络访问配置 # 外部访问方案 # 方案一：LoadBalancer 服务 # cat \u0026gt; kafka-external-access.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Kafka 外部访问配置 apiVersion: v1 kind: Service metadata: name: kafka-external namespace: kafka-cluster spec: type: LoadBalancer ports: - name: kafka port: 9092 targetPort: 9092 protocol: TCP selector: app: cp-kafka release: kafka-dev --- # ZooKeeper 外部访问（谨慎使用） apiVersion: v1 kind: Service metadata: name: zookeeper-external namespace: kafka-cluster spec: type: LoadBalancer ports: - name: zookeeper port: 2181 targetPort: 2181 protocol: TCP selector: app: cp-zookeeper release: kafka-dev EOF kubectl apply -f kafka-external-access.yaml 方案二：NodePort 服务 # cat \u0026gt; kafka-nodeport.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; apiVersion: v1 kind: Service metadata: name: kafka-nodeport namespace: kafka-cluster spec: type: NodePort ports: - name: kafka port: 9092 targetPort: 9092 nodePort: 30092 protocol: TCP selector: app: cp-kafka release: kafka-dev --- apiVersion: v1 kind: Service metadata: name: zookeeper-nodeport namespace: kafka-cluster spec: type: NodePort ports: - name: zookeeper port: 2181 targetPort: 2181 nodePort: 30181 protocol: TCP selector: app: cp-zookeeper release: kafka-dev EOF kubectl apply -f kafka-nodeport.yaml 方案三：Ingress 配置（推荐） # Nginx Ingress 配置 # cat \u0026gt; kafka-ingress-nginx.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: kafka-ingress namespace: kafka-cluster annotations: kubernetes.io/ingress.class: \u0026#34;nginx\u0026#34; nginx.ingress.kubernetes.io/ssl-redirect: \u0026#34;false\u0026#34; nginx.ingress.kubernetes.io/backend-protocol: \u0026#34;HTTP\u0026#34; spec: rules: - host: kafka.your-domain.com http: paths: - path: / pathType: Prefix backend: service: name: kafka-ui port: number: 8080 - host: kafka-manager.your-domain.com http: paths: - path: / pathType: Prefix backend: service: name: kafka-manager port: number: 9000 EOF kubectl apply -f kafka-ingress-nginx.yaml Traefik Ingress 配置 # cat \u0026gt; kafka-traefik-ingress.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # 创建认证密钥 apiVersion: v1 kind: Secret metadata: name: kafka-auth namespace: kafka-cluster type: Opaque data: users: YWRtaW46JGFwcjEkOHJjc2dCdE8kQS5zZnZ2a2JiUWljRlAyaHp2SzhELwoK # admin:password123 --- # BasicAuth 中间件 apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: kafka-auth namespace: kafka-cluster spec: basicAuth: secret: kafka-auth removeHeader: true --- # 路径前缀处理中间件 apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: kafka-stripprefix namespace: kafka-cluster spec: stripPrefix: forceSlash: false prefixes: - /kafka --- # Kafka UI 路由 apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: kafka-ui-route namespace: kafka-cluster spec: entryPoints: - web routes: - match: Host(`kafka.your-domain.com`) kind: Rule services: - name: kafka-ui port: 8080 middlewares: - name: kafka-auth --- # Kafka Manager 路由 apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: kafka-manager-route namespace: kafka-cluster spec: entryPoints: - web routes: - match: Host(`kafka-manager.your-domain.com`) kind: Rule services: - name: kafka-manager port: 9000 middlewares: - name: kafka-auth EOF kubectl apply -f kafka-traefik-ingress.yaml 客户端连接配置 # 集群内部连接 # # Kafka Bootstrap Servers KAFKA_BOOTSTRAP_SERVERS=\u0026#34;kafka-dev-cp-kafka:9092\u0026#34; # ZooKeeper 连接字符串 ZOOKEEPER_CONNECT=\u0026#34;kafka-dev-cp-zookeeper:2181\u0026#34; # Schema Registry URL SCHEMA_REGISTRY_URL=\u0026#34;http://kafka-dev-cp-schema-registry:8081\u0026#34; 集群外部连接 # # 通过 LoadBalancer KAFKA_BOOTSTRAP_SERVERS=\u0026#34;\u0026lt;EXTERNAL-IP\u0026gt;:9092\u0026#34; # 通过 NodePort KAFKA_BOOTSTRAP_SERVERS=\u0026#34;\u0026lt;NODE-IP\u0026gt;:30092\u0026#34; # 通过 Ingress（需要特殊配置） # 注意：Kafka 协议通过 HTTP Ingress 需要特殊处理 连接测试脚本 # cat \u0026gt; test-kafka-connection.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash KAFKA_POD=$(kubectl get pods -n kafka-cluster -l app=cp-kafka -o jsonpath=\u0026#39;{.items[0].metadata.name}\u0026#39;) NAMESPACE=\u0026#34;kafka-cluster\u0026#34; echo \u0026#34;=== Kafka 连接测试 ===\u0026#34; # 创建测试主题 echo \u0026#34;创建测试主题...\u0026#34; kubectl exec -n $NAMESPACE $KAFKA_POD -- kafka-topics \\ --create \\ --topic test-topic \\ --bootstrap-server localhost:9092 \\ --partitions 3 \\ --replication-factor 1 # 列出主题 echo \u0026#34;列出所有主题...\u0026#34; kubectl exec -n $NAMESPACE $KAFKA_POD -- kafka-topics \\ --list \\ --bootstrap-server localhost:9092 # 发送测试消息 echo \u0026#34;发送测试消息...\u0026#34; kubectl exec -n $NAMESPACE $KAFKA_POD -- bash -c \\ \u0026#39;echo \u0026#34;Hello Kafka from K8s!\u0026#34; | kafka-console-producer \\ --topic test-topic \\ --bootstrap-server localhost:9092\u0026#39; # 消费测试消息 echo \u0026#34;消费测试消息...\u0026#34; kubectl exec -n $NAMESPACE $KAFKA_POD -- kafka-console-consumer \\ --topic test-topic \\ --bootstrap-server localhost:9092 \\ --from-beginning \\ --max-messages 1 # 删除测试主题 echo \u0026#34;删除测试主题...\u0026#34; kubectl exec -n $NAMESPACE $KAFKA_POD -- kafka-topics \\ --delete \\ --topic test-topic \\ --bootstrap-server localhost:9092 echo \u0026#34;=== 连接测试完成 ===\u0026#34; EOF chmod +x test-kafka-connection.sh ./test-kafka-connection.sh 集群管理与使用 # Kafka UI 使用指南 # 访问管理界面 # 通过端口转发访问：\nkubectl port-forward -n kafka-cluster svc/kafka-ui 8080:8080 然后访问：http://localhost:8080\n通过 Ingress 访问： 访问配置的域名：http://kafka.your-domain.com\n主要功能 # 集群概览：查看集群状态、节点信息、主题统计 主题管理：创建、删除、配置主题 消息浏览：查看主题中的消息内容 消费者组管理：监控消费者组状态和消费进度 连接器管理：管理 Kafka Connect 连接器 Schema 管理：管理 Schema Registry 中的模式 Kafka Manager 使用指南 # 添加集群配置 # 访问管理界面后，点击 \u0026ldquo;Add Cluster\u0026rdquo; 填写集群信息： Cluster Name: kafka-cluster Cluster Zookeeper Hosts: kafka-dev-cp-zookeeper.kafka-cluster.svc.cluster.local:2181 Kafka Version: 选择对应版本 Enable JMX Polling: 勾选以启用监控 高级配置： 启用 JMX 监控 配置安全认证（如需要） 设置消费者组监控 保存配置，集群添加完成 运维管理 # 集群监控脚本 # cat \u0026gt; kafka-cluster-monitor.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash NAMESPACE=\u0026#34;kafka-cluster\u0026#34; KAFKA_RELEASE=\u0026#34;kafka-dev\u0026#34; echo \u0026#34;=== Kafka 集群监控报告 ===\u0026#34; echo \u0026#34;时间: $(date)\u0026#34; echo # 检查 Pod 状态 echo \u0026#34;=== Pod 状态 ===\u0026#34; kubectl get pods -n $NAMESPACE -l release=$KAFKA_RELEASE echo echo \u0026#34;=== 服务状态 ===\u0026#34; kubectl get svc -n $NAMESPACE -l release=$KAFKA_RELEASE echo echo \u0026#34;=== 持久化卷状态 ===\u0026#34; kubectl get pvc -n $NAMESPACE echo echo \u0026#34;=== 资源使用情况 ===\u0026#34; kubectl top pods -n $NAMESPACE 2\u0026gt;/dev/null || echo \u0026#34;metrics-server 未安装\u0026#34; echo echo \u0026#34;=== 集群健康检查 ===\u0026#34; KAFKA_POD=$(kubectl get pods -n $NAMESPACE -l app=cp-kafka -o jsonpath=\u0026#39;{.items[0].metadata.name}\u0026#39;) if [ -n \u0026#34;$KAFKA_POD\u0026#34; ]; then echo \u0026#34;检查 Kafka 主题...\u0026#34; kubectl exec -n $NAMESPACE $KAFKA_POD -- kafka-topics \\ --list \\ --bootstrap-server localhost:9092 | head -10 echo echo \u0026#34;检查消费者组...\u0026#34; kubectl exec -n $NAMESPACE $KAFKA_POD -- kafka-consumer-groups \\ --list \\ --bootstrap-server localhost:9092 | head -10 else echo \u0026#34;未找到 Kafka Pod\u0026#34; fi echo echo \u0026#34;=== 监控报告完成 ===\u0026#34; EOF chmod +x kafka-cluster-monitor.sh 备份与恢复 # cat \u0026gt; kafka-backup.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash NAMESPACE=\u0026#34;kafka-cluster\u0026#34; BACKUP_DIR=\u0026#34;/backup/kafka/$(date +%Y%m%d)\u0026#34; KAFKA_POD=$(kubectl get pods -n $NAMESPACE -l app=cp-kafka -o jsonpath=\u0026#39;{.items[0].metadata.name}\u0026#39;) mkdir -p $BACKUP_DIR echo \u0026#34;=== Kafka 集群备份 ===\u0026#34; # 备份主题配置 echo \u0026#34;备份主题配置...\u0026#34; kubectl exec -n $NAMESPACE $KAFKA_POD -- kafka-topics \\ --describe \\ --bootstrap-server localhost:9092 \u0026gt; $BACKUP_DIR/topics-config.txt # 备份消费者组信息 echo \u0026#34;备份消费者组信息...\u0026#34; kubectl exec -n $NAMESPACE $KAFKA_POD -- kafka-consumer-groups \\ --describe \\ --all-groups \\ --bootstrap-server localhost:9092 \u0026gt; $BACKUP_DIR/consumer-groups.txt # 备份 Kubernetes 资源 echo \u0026#34;备份 Kubernetes 资源...\u0026#34; kubectl get all -n $NAMESPACE -o yaml \u0026gt; $BACKUP_DIR/k8s-resources.yaml kubectl get pvc -n $NAMESPACE -o yaml \u0026gt; $BACKUP_DIR/pvc-resources.yaml kubectl get configmap -n $NAMESPACE -o yaml \u0026gt; $BACKUP_DIR/configmap-resources.yaml echo \u0026#34;备份完成，文件保存在: $BACKUP_DIR\u0026#34; EOF chmod +x kafka-backup.sh 性能调优 # cat \u0026gt; kafka-performance-tuning.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Kafka 性能调优配置 cp-kafka: configurationOverrides: # 网络和 I/O 优化 \u0026#34;num.network.threads\u0026#34;: \u0026#34;16\u0026#34; \u0026#34;num.io.threads\u0026#34;: \u0026#34;32\u0026#34; \u0026#34;socket.send.buffer.bytes\u0026#34;: \u0026#34;102400\u0026#34; \u0026#34;socket.receive.buffer.bytes\u0026#34;: \u0026#34;102400\u0026#34; \u0026#34;socket.request.max.bytes\u0026#34;: \u0026#34;104857600\u0026#34; # 日志优化 \u0026#34;log.flush.interval.messages\u0026#34;: \u0026#34;10000\u0026#34; \u0026#34;log.flush.interval.ms\u0026#34;: \u0026#34;1000\u0026#34; \u0026#34;log.segment.bytes\u0026#34;: \u0026#34;1073741824\u0026#34; \u0026#34;log.retention.check.interval.ms\u0026#34;: \u0026#34;300000\u0026#34; # 压缩优化 \u0026#34;compression.type\u0026#34;: \u0026#34;lz4\u0026#34; \u0026#34;log.cleanup.policy\u0026#34;: \u0026#34;delete\u0026#34; # 副本优化 \u0026#34;replica.fetch.max.bytes\u0026#34;: \u0026#34;1048576\u0026#34; \u0026#34;replica.socket.receive.buffer.bytes\u0026#34;: \u0026#34;65536\u0026#34; # 生产者优化 \u0026#34;batch.size\u0026#34;: \u0026#34;16384\u0026#34; \u0026#34;linger.ms\u0026#34;: \u0026#34;5\u0026#34; \u0026#34;buffer.memory\u0026#34;: \u0026#34;33554432\u0026#34; # JVM 优化 heapOptions: \u0026#34;-Xms6G -Xmx6G -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35\u0026#34; EOF 故障排除 # 常见问题 # 1. Pod 启动失败 # 问题现象：Pod 处于 Pending 或 CrashLoopBackOff 状态\n排查步骤：\n# 查看 Pod 详细信息 kubectl describe pod \u0026lt;pod-name\u0026gt; -n kafka-cluster # 查看 Pod 日志 kubectl logs \u0026lt;pod-name\u0026gt; -n kafka-cluster # 检查资源限制 kubectl get nodes kubectl describe node \u0026lt;node-name\u0026gt; 常见原因：\n资源不足（CPU、内存、存储） 存储类不可用 镜像拉取失败 配置错误 2. 存储问题 # 问题现象：PVC 处于 Pending 状态\n排查步骤：\n# 查看 PVC 状态 kubectl get pvc -n kafka-cluster # 查看存储类 kubectl get storageclass # 查看 PV kubectl get pv 解决方案：\n确认存储类配置正确 检查存储提供商状态 验证存储容量是否足够 3. 网络连接问题 # 问题现象：服务间无法通信\n排查步骤：\n# 测试服务连通性 kubectl exec -n kafka-cluster \u0026lt;kafka-pod\u0026gt; -- nc -zv \u0026lt;zookeeper-service\u0026gt; 2181 # 查看服务端点 kubectl get endpoints -n kafka-cluster # 检查网络策略 kubectl get networkpolicy -n kafka-cluster 4. 性能问题 # 问题现象：消息处理延迟高\n排查步骤：\n# 查看资源使用 kubectl top pods -n kafka-cluster # 检查 JVM 参数 kubectl exec -n kafka-cluster \u0026lt;kafka-pod\u0026gt; -- ps aux | grep java # 查看 Kafka 指标 kubectl port-forward -n kafka-cluster svc/kafka-jmx-exporter 5556:5556 curl http://localhost:5556/metrics 总结 # 部署优势 # 通过本指南，您可以在 Kubernetes 中成功部署一个企业级的 Kafka 集群，具有以下优势：\n技术优势 # 高可用性：多节点部署，支持故障自动恢复 可扩展性：支持水平扩展，满足业务增长需求 持久化存储：数据安全可靠，支持备份恢复 监控完善：集成监控和管理工具，运维便捷 运维优势 # 容器化部署：标准化部署流程，环境一致性好 自动化管理：Kubernetes 自动处理容器生命周期 资源隔离：命名空间和资源限制保证系统稳定 版本管理：Helm Charts 支持版本控制和回滚 最佳实践 # 生产环境建议 # 资源规划：根据业务需求合理规划 CPU、内存和存储 安全配置：启用认证、授权和网络策略 监控告警：部署完整的监控和告警体系 备份策略：制定定期备份和灾难恢复计划 性能调优：根据业务特点调整 Kafka 配置参数 扩展建议 # 多集群部署：考虑跨区域的多集群架构 数据治理：实施 Schema Registry 和数据质量管控 流处理集成：集成 Kafka Streams 或 Apache Flink 安全加固：实施端到端加密和细粒度权限控制 外部连接解决方案 # 针对原文提到的外部连接问题，现代的解决方案包括：\nStrimzi Operator：专门为 Kubernetes 设计的 Kafka 操作器 Confluent Operator：Confluent 官方的 Kubernetes 操作器 自定义网络配置：通过 advertised.listeners 配置外部访问 Service Mesh：使用 Istio 等服务网格解决网络问题 通过本指南的配置和最佳实践，您可以构建一个稳定、高效、可扩展的 Kafka 流处理平台，为企业的数据处理和实时分析提供强有力的支撑。\n","date":"2021年1月19日","externalUrl":null,"permalink":"/posts/k8s-deploy-kafka-cluster/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eApache Kafka 简介 \n    \u003cdiv id=\"apache-kafka-简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#apache-kafka-%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 Apache Kafka \n    \u003cdiv id=\"什么是-apache-kafka\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-apache-kafka\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eApache Kafka 是一个开源的分布式事件流处理平台，由 LinkedIn 开发并贡献给 Apache 软件基金会。它被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用程序。\u003c/p\u003e","title":"Kubernetes 部署企业级 Kafka 集群完整指南","type":"posts"},{"content":"","date":"2021年1月19日","externalUrl":null,"permalink":"/tags/streaming/","section":"Tags","summary":"","title":"Streaming","type":"tags"},{"content":"","date":"2021年1月19日","externalUrl":null,"permalink":"/tags/zookeeper/","section":"Tags","summary":"","title":"Zookeeper","type":"tags"},{"content":"","date":"2021年1月17日","externalUrl":null,"permalink":"/tags/ingress/","section":"Tags","summary":"","title":"Ingress","type":"tags"},{"content":" Traefik 简介 # 什么是 Traefik？ # Traefik 是一个现代化的云原生反向代理和负载均衡器，专为微服务架构设计。它就像一个智能的\u0026quot;交通指挥员\u0026quot;，能够自动发现您的服务并为它们配置路由规则。\n为什么选择 Traefik？ # 与传统的 Nginx 或 Apache 相比，Traefik 具有以下显著优势：\n特性 传统代理 Traefik 配置方式 手动编辑配置文件 自动服务发现 配置更新 需要重启服务 动态实时更新 容器支持 需要额外配置 原生支持 监控面板 需要第三方工具 内置 Web UI 核心优势 # 🔍 自动服务发现：无需手动配置，自动检测新服务 ⚡ 动态配置：实时更新路由规则，零停机时间 ☁️ 云原生设计：完美支持 Docker、Kubernetes 等平台 🛠️ 丰富中间件：内置认证、限流、重试等功能 📊 可观测性：提供详细的监控指标和链路追踪 应用场景 # Traefik 特别适合以下场景：\n微服务架构：自动管理多个微服务的路由 容器化部署：与 Docker/Kubernetes 无缝集成 API 网关：统一管理 API 访问入口 负载均衡：智能分发请求到多个后端实例 架构概览 # 图：Traefik 的整体架构，展示了请求从客户端到后端服务的完整流程\n核心概念详解 # 理解 Traefik 的核心概念是正确使用它的关键。我们可以把 Traefik 想象成一个智能的\u0026quot;快递分拣中心\u0026quot;：\n1. Providers（提供者）- 信息来源 # 作用：告诉 Traefik 从哪里获取服务信息\n提供者类型 说明 使用场景 Kubernetes 从 K8s API 获取服务信息 容器化部署 Docker 监控 Docker 容器标签 单机 Docker 环境 File 从配置文件读取规则 静态配置 Consul/Etcd 从键值存储获取配置 分布式配置管理 2. EntryPoints（入口点）- 门户 # 作用：定义 Traefik 监听的网络端口，就像建筑物的不同入口\n# 常见入口点配置 entryPoints: web: # HTTP 入口（端口 80） websecure: # HTTPS 入口（端口 443） traefik: # 管理界面（端口 8080） 3. Routers（路由器）- 智能分拣员 # 作用：分析传入请求并决定路由到哪个服务\n路由匹配规则 # 匹配类型 示例 说明 Host api.example.com 基于域名路由 Path /api/v1 基于路径路由 Headers X-API-Version: v2 基于请求头路由 Method GET, POST 基于 HTTP 方法路由 路由规则示例 # # 复合路由规则 match: Host(`api.example.com`) \u0026amp;\u0026amp; PathPrefix(`/v1`) \u0026amp;\u0026amp; Method(`GET`) 4. Services（服务）- 目标地址 # 作用：定义后端服务的负载均衡策略\n主要功能 # 🔄 负载均衡：支持轮询、加权轮询、最少连接等算法 💓 健康检查：自动检测后端服务健康状态 🔀 故障转移：自动切换到健康的后端实例 5. Middlewares（中间件）- 处理加工站 # 作用：在请求到达后端服务前进行预处理\n常用中间件类型 # 中间件类型 功能 使用场景 认证 BasicAuth、OAuth、JWT 用户身份验证 限流 基于 IP 或用户的请求限制 防止 API 滥用 重写 URL 重写和重定向 路径转换 压缩 响应内容压缩 提升传输效率 CORS 跨域资源共享配置 前端跨域访问 请求处理流程 # graph LR A[客户端请求] --\u003e B[EntryPoint端口监听] B --\u003e C[Router路由匹配] C --\u003e D[Middleware请求处理] D --\u003e E[Service负载均衡] E --\u003e F[后端Pod应用服务] 流程说明：\n客户端发起请求：用户访问应用 EntryPoint 接收：Traefik 在指定端口监听请求 Router 路由匹配：根据域名、路径等规则匹配路由 Middleware 处理：执行认证、限流等中间件逻辑 Service 负载均衡：选择健康的后端实例 转发到后端：将请求发送到目标应用 环境准备 # 系统要求 # 在开始部署之前，请确保您的环境满足以下要求：\n组件 版本要求 说明 Kubernetes v1.19+ 支持 Ingress API v1 Helm v3.4.2+ 用于部署 Traefik Chart 操作系统 CentOS 7.9+ 或其他 Linux 发行版 网络 LoadBalancer 支持 云环境或 MetalLB 实验环境信息 # 本教程基于以下环境进行演示：\n软件版本 # Traefik: v2.3.6 Helm: v3.4.2 Kubernetes: v1.19.6 操作系统: CentOS Linux 7.9.2009 集群节点信息 # 节点角色 IP 地址 说明 Master01 192.168.8.70 控制平面节点 Node01 192.168.8.71 工作节点 Node02 192.168.8.72 工作节点 Node03 192.168.8.73 工作节点 工具准备 # 安装 Helm（如果未安装） # # 下载 Helm 安装脚本 curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash # 验证安装 helm version 配置 Helm 自动补全 # 为了提高操作效率，建议配置命令自动补全：\n# Bash 用户 helm completion bash \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc # Zsh 用户 helm completion zsh \u0026gt;\u0026gt; ~/.zshrc source ~/.zshrc # Fish 用户 helm completion fish \u0026gt; ~/.config/fish/completions/helm.fish 验证 Kubernetes 集群 # # 检查集群状态 kubectl cluster-info # 检查节点状态 kubectl get nodes # 检查 Helm 是否能正常工作 helm list Traefik 部署 # 步骤 1：获取 Helm Chart # 我们使用官方的 Helm Chart 来部署 Traefik：\n# 克隆官方 Helm Chart 仓库 git clone https://github.com/traefik/traefik-helm-chart # 进入 Chart 目录 cd traefik-helm-chart # 查看默认配置（可选） helm show values ./traefik/ 步骤 2：创建自定义配置 # 根据我们的需求创建自定义配置文件：\ncat \u0026gt; prod-values.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # 禁用默认的 Dashboard IngressRoute，我们将手动创建 ingressRoute: dashboard: enabled: false # 配置端口映射 ports: web: port: 8000 hostPort: 80 # 直接绑定到主机的 80 端口 websecure: port: 8443 hostPort: 443 # 直接绑定到主机的 443 端口 # 禁用 Service（使用 hostPort 模式时不需要） service: enabled: false # 配置日志级别 logs: general: level: ERROR # 生产环境建议使用 ERROR 级别 # 容忍主节点污点，允许在 master 节点运行 tolerations: - key: \u0026#34;node-role.kubernetes.io/master\u0026#34; operator: \u0026#34;Equal\u0026#34; effect: \u0026#34;NoSchedule\u0026#34; # 固定部署到 master01 节点 nodeSelector: kubernetes.io/hostname: \u0026#34;master01\u0026#34; EOF 配置说明 # 配置项 说明 原因 hostPort 直接绑定主机端口 简化网络配置，适合单节点部署 service.enabled: false 禁用 Service hostPort 模式下不需要 Service logs.level: ERROR 设置日志级别 减少日志输出，提升性能 tolerations 容忍主节点污点 允许在 master 节点运行 nodeSelector 节点选择器 固定部署位置，便于管理 步骤 3：部署 Traefik # # 创建命名空间 kubectl create namespace traefik # 使用 Helm 部署 Traefik helm upgrade --install traefik \\ --namespace traefik \\ --values ./prod-values.yaml \\ ./traefik/ # 等待 Pod 启动完成 watch kubectl get pods -n traefik 预期输出：\nNAME READY STATUS RESTARTS AGE traefik-84879ffbd-6kwv5 1/1 Running 0 68s 步骤 4：验证部署 # 检查 Pod 状态 # # 查看 Pod 详细信息 kubectl get pods -n traefik -o wide # 查看 Pod 日志 kubectl logs -n traefik deployment/traefik 测试连通性 # # 测试 HTTP 端口（应该返回 404，这是正常的） curl -I http://192.168.8.70 # 预期输出 HTTP/1.1 404 Not Found Date: Sun, 17 Jan 2021 03:09:37 GMT Content-Length: 19 Content-Type: text/plain; charset=utf-8 ✅ 成功标志：看到 404 page not found 表示 Traefik 已正常启动，只是还没有配置任何路由规则\n配置 Traefik Dashboard # 为什么需要配置 Dashboard？ # Traefik Dashboard 是一个 Web 管理界面，提供以下功能：\n实时监控：查看当前路由规则和服务状态 流量统计：监控请求数量和响应时间 配置查看：可视化查看所有配置信息 故障排查：快速定位路由问题 安全考虑 # 默认的 Dashboard 没有任何认证保护，任何人都可以访问。为了安全，我们需要：\n添加 BasicAuth 认证 限制访问域名 可选：配置 HTTPS 步骤 1：生成认证信息 # 安装密码生成工具 # # CentOS/RHEL yum install httpd-tools -y # Ubuntu/Debian apt-get install apache2-utils -y 生成用户密码 # # 生成用户名和密码（用户名：admin，密码：123456） htpasswd -nb admin 123456 # 输出示例 admin:$apr1$aUlPDYOb$tDDau3d3zv0op6NrMo6C1 # 转换为 base64 编码（Kubernetes Secret 需要） htpasswd -nb admin 123456 | base64 # 输出：YWRtaW46JGFwcjEkYVVsUERZT2IkdEREYXUzZDN6dnowb3A2TnJNbzZDMQoK 🔐 安全提示：在生产环境中，请使用更强的密码\n步骤 2：创建 Dashboard 配置 # 创建包含认证、中间件和路由的完整配置：\ncat \u0026gt; dashboard-config.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # 创建存储认证信息的 Secret apiVersion: v1 kind: Secret metadata: name: dashboard-auth-secret namespace: traefik type: Opaque data: users: YWRtaW46JGFwcjEkYVVsUERZT2IkdEREYXUzZDN6dnowb3A2TnJNbzZDMQoK --- # 创建 BasicAuth 中间件 apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: dashboard-auth namespace: traefik spec: basicAuth: secret: dashboard-auth-secret removeHeader: true # 移除认证头，避免传递给后端 --- # 创建 Dashboard 路由 apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: traefik-dashboard namespace: traefik spec: entryPoints: - web # 使用 HTTP 入口点 routes: - match: Host(`traefik.coderyzun.cyou`) kind: Rule services: - name: api@internal # Traefik 内置的 API 服务 kind: TraefikService middlewares: - name: dashboard-auth EOF 配置说明 # 组件 作用 说明 Secret 存储认证信息 包含用户名和密码的 base64 编码 Middleware 认证中间件 拦截请求进行身份验证 IngressRoute 路由规则 定义如何访问 Dashboard 步骤 3：部署配置 # # 应用配置 kubectl apply -f dashboard-config.yaml # 验证资源创建 kubectl get secret,middleware,ingressroute -n traefik 设置域名解析 # Dashboard 功能介绍 # HTTP Routers：查看所有 HTTP 路由规则 HTTP Services：查看后端服务状态 HTTP Middlewares：查看中间件配置 Entrypoints：查看入口点配置 中间件实战演示 # 中间件是 Traefik 的强大功能之一，可以在请求到达后端服务之前对其进行处理。本节将通过实际示例演示几种常用中间件的配置和使用。\n📚 参考文档：Traefik 官方中间件文档\n准备测试应用 # 在演示中间件功能之前，我们需要部署一个测试应用。我们将使用 whoami 应用，它会返回请求的详细信息。\n部署测试应用 # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - # 创建测试应用的 Service apiVersion: v1 kind: Service metadata: name: whoami-v1 namespace: default spec: ports: - protocol: TCP name: web port: 80 selector: app: whoami-v1 --- # 创建测试应用的 Deployment kind: Deployment apiVersion: apps/v1 metadata: name: whoami-v1 namespace: default labels: app: whoami-v1 spec: replicas: 2 selector: matchLabels: app: whoami-v1 template: metadata: labels: app: whoami-v1 spec: containers: - name: whoami image: containous/whoami ports: - name: web containerPort: 80 EOF 验证应用部署 # # 检查 Pod 状态 kubectl get pods -l app=whoami-v1 # 检查 Service kubectl get svc whoami-v1 中间件示例 # 1. Headers 中间件 - 请求头处理 # Headers 中间件可以添加、修改或删除 HTTP 请求头和响应头。\n应用场景 # 添加安全头信息 传递用户身份信息 移除敏感头信息 设置 CORS 头 创建 Headers 中间件 # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - # 添加请求头和响应头的中间件 apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: add-headers namespace: default spec: headers: customRequestHeaders: X-Script-Name: \u0026#34;traefik-demo\u0026#34; # 添加自定义请求头 X-Forwarded-Proto: \u0026#34;http\u0026#34; # 添加协议信息 customResponseHeaders: X-Custom-Response-Header: \u0026#34;Traefik-Powered\u0026#34; # 添加自定义响应头 X-Frame-Options: \u0026#34;DENY\u0026#34; # 安全头：防止页面被嵌入框架 --- # 移除请求头的中间件 apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: remove-headers namespace: default spec: headers: customRequestHeaders: X-Script-Name: \u0026#34;\u0026#34; # 移除请求头（设置为空字符串） customResponseHeaders: X-Custom-Response-Header: \u0026#34;\u0026#34; # 移除响应头 EOF 创建路由规则 # cat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: headers-demo namespace: default spec: entryPoints: - web routes: # 添加头信息的路由 - match: Host(`traefik.coderyzun.cyou`) \u0026amp;\u0026amp; PathPrefix(`/headers/add`) kind: Rule services: - name: whoami-v1 port: 80 middlewares: - name: add-headers # 移除头信息的路由 - match: Host(`traefik.coderyzun.cyou`) \u0026amp;\u0026amp; PathPrefix(`/headers/remove`) kind: Rule services: - name: whoami-v1 port: 80 middlewares: - name: remove-headers EOF 测试 Headers 中间件 # # 测试添加头信息 curl -H \u0026#34;X-Script-Name: original\u0026#34; http://traefik.coderyzun.cyou/headers/add # 测试移除头信息 curl -H \u0026#34;X-Script-Name: will-be-removed\u0026#34; http://traefik.coderyzun.cyou/headers/remove IPWhiteList # 只允许 192.168.8.153，可访问\n创建 Middleware 资源清单\ncat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: ipwhitelist spec: ipWhiteList: sourceRange: - 192.168.8.153 EOF 创建 routes 清单\ncat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: apps-ipwhitelist spec: entryPoints: - web routes: - match: Host(\\`traefik.coderyzun.cyou\\`) \u0026amp;\u0026amp; PathPrefix(\\`/ipwhitelist\\`) kind: Rule services: - name: v1 port: 80 middlewares: - name: ipwhitelist EOF 其他主机 执行 curl\nRedirectRegex # 创建 v2 , deployment 和 service 资源清单\ncat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: v2 spec: ports: - protocol: TCP name: web port: 80 selector: app: v2 --- kind: Deployment apiVersion: apps/v1 metadata: name: v2 labels: app: v2 spec: selector: matchLabels: app: v2 template: metadata: labels: app: v2 spec: containers: - name: v2 image: containous/whoami ports: - name: web containerPort: 80 EOF 创建 Middleware 资源清单\ncat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: redirectregex spec: redirectRegex: regex: ^http://traefik.coderyzun.cyou/redirectregex/v1/(.*) replacement: http://traefik.coderyzun.cyou/redirectregex/v2/\\${1} permanent: true # open permanent redirection EOF 创建 routes 清单\ncat \u0026lt;\u0026lt;EOF | kubectl apply -f - apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: redirectregex-appv1 namespace: default spec: entryPoints: - web routes: - match: Host(\\`traefik.coderyzun.cyou\\`) \u0026amp;\u0026amp; PathPrefix(\\`/redirectregex/v1\\`) kind: Rule services: - name: v1 port: 80 middlewares: - name: redirectregex - match: Host(\\`traefik.coderyzun.cyou\\`) \u0026amp;\u0026amp; PathPrefix(\\`/redirectregex/v2\\`) kind: Rule services: - name: v2 port: 80 EOF 测试\ncurl -I http://traefik.coderyzun.cyou/redirectregex/v1/1 ","date":"2021年1月17日","externalUrl":null,"permalink":"/posts/ingress-traefik/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eTraefik 简介 \n    \u003cdiv id=\"traefik-简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#traefik-%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 Traefik？ \n    \u003cdiv id=\"什么是-traefik\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-traefik\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eTraefik 是一个现代化的云原生反向代理和负载均衡器，专为微服务架构设计。它就像一个智能的\u0026quot;交通指挥员\u0026quot;，能够自动发现您的服务并为它们配置路由规则。\u003c/p\u003e","title":"Traefik Ingress Controller 完整部署指南","type":"posts"},{"content":"","date":"2021年1月17日","externalUrl":null,"permalink":"/tags/%E7%BD%91%E5%85%B3/","section":"Tags","summary":"","title":"网关","type":"tags"},{"content":" 环境配置 # 操作系统：CentOS 7.9.2009 Docker 版本：18.09.9 问题复现 # 安装 Rancher # docker pull rancher/rancher:v2.3.5 docker run -d \\ --restart=unless-stopped \\ --name rancher \\ -p 80:80 -p 443:443 \\ --privileged \\ rancher/rancher:v2.3.5 升级 Rancher # 示例将 v2.3.5 升级至 v2.5.0\n创建数据备份 # docker stop rancher docker create --volumes-from rancher --name rancher-data rancher/rancher:v2.5.0 docker run --volumes-from rancher-data -v $PWD:/backup busybox tar zcvf /backup/rancher-data-backup-rancher2.3.5-20210116.tar.gz /var/lib/rancher 启动新版本容器 # docker rename rancher rancher_old docker run -d --name rancher --volumes-from rancher-data \\ --restart=unless-stopped \\ -p 80:80 -p 443:443 \\ --privileged \\ rancher/rancher:v2.5.0 docker logs -f --tail 100 rancher docker update --restart=no rancher_old 问题现象 # 升级完成后，系统重启或手动重启 Docker 服务时，Docker 卡死无法启动。\n错误表现：\n/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock # 手动启动报错 Error starting daemon: pid file found, ensure docker is not running or delete /var/run/docker.pid ps -ef|grep dockerd # 存在僵尸进程 root 4081 1 0 13:49 ? 00:00:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock 问题分析与修复 # 问题原因 # 此问题是 Docker 18.x 版本中存在的一个 Bug。当系统中存在过多无关容器时，会导致 Docker 服务无法正常启动。\n解决方案 # 删除系统中无关的容器，只保留有用的容器（如本例中的 rancher_old 和创建数据卷备份时使用的容器）。\n修复步骤 # 清理僵尸进程 # 首先停止 Docker 服务并清理僵尸进程：\n注意：执行停止 Docker 服务命令时可能会卡住，使用 Ctrl + C 终止即可。\nservice docker stop yum install psmisc # 安装 killall 工具 killall dockerd ps -ef|grep dockerd # 检查僵尸进程是否清理完成 清理无用容器 # 进入 Docker 容器数据目录（默认路径为 /var/lib/docker/containers）：\ncd /var/lib/docker/containers \u0026amp;\u0026amp; ls 可以看到系统中存在四个容器，本例中只保留名为 rancher 的容器。\n查找并保留指定容器：\ncd /var/lib/docker/containers # 查找名为 rancher 的容器 ID for i in `ls */config.v2.json`;do grep -l \u0026#39;\u0026#34;Name\u0026#34;:\u0026#34;/rancher\u0026#34;\u0026#39; \u0026#34;$i\u0026#34;;done|awk -F \u0026#39;/\u0026#39; \u0026#39;{print $1}\u0026#39; 96d6ea117475ffab7c5651812163ca45feded54463d93e992f9195964ae81282 # 找到的容器 ID # 删除其他容器（请谨慎确认后执行） rm -rf !(96d6ea117475ffab7c5651812163ca45feded54463d93e992f9195964ae81282) 验证修复结果 # 清理完成后，重新启动 Docker 服务：\nservice docker start 可以看到 Docker 服务已能正常启动。\n总结 # 问题特征 # 影响版本：Docker 18.x 系列存在此 Bug 触发条件：系统中存在过多无用容器时，升级后可能导致 Docker 服务无法启动 预防措施 # 及时清理：升级容器后及时清理无用的旧容器 做好备份：升级过程中做好相应的数据备份，避免数据丢失 版本选择：考虑升级到更稳定的 Docker 版本以避免此类问题 ","date":"2021年1月16日","externalUrl":null,"permalink":"/posts/rancher-upgrade-docker/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境配置 \n    \u003cdiv id=\"环境配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e操作系统\u003c/strong\u003e：CentOS 7.9.2009\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDocker 版本\u003c/strong\u003e：18.09.9\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e问题复现 \n    \u003cdiv id=\"问题复现\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%97%ae%e9%a2%98%e5%a4%8d%e7%8e%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装 Rancher \n    \u003cdiv id=\"安装-rancher\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85-rancher\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker pull rancher/rancher:v2.3.5\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker run -d \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e--restart\u003cspan class=\"o\"\u003e=\u003c/span\u003eunless-stopped \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e--name rancher \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e-p 80:80 -p 443:443 \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003e--privileged \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"se\"\u003e\u003c/span\u003erancher/rancher:v2.3.5\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"image-20210116134244336\" src=\"https://cdn.treesir.pub/img/image-20210116134244336.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"修复 Rancher 单机部署升级后 Docker 无法启动问题","type":"posts"},{"content":"","date":"2021年1月14日","externalUrl":null,"permalink":"/tags/centos/","section":"Tags","summary":"","title":"Centos","type":"tags"},{"content":" 环境说明 # 因在日常维护过程中升级了docker的版本 ，碰到了一个 docker 的一个 bug, 导致 docker 服务无法正常启动。在恢复 docker 服务的过程中把 /var/lib/docker/containers 下的所有文件给 清空 了，清空后 docker 服务可以正常运行，但发现原来的服务中还存留着一个还在使用的 cmdb 系统也被我跟着删除了，是之前的前辈部署的。好在自己有 做备份 的习惯，现记录一下被删除容器的恢复过程。\n操作系统: Centos\nDocker 版本: 19.03.8\n需要恢复容器: glpi \u0026amp; mysql\n一开始以为只要一个容器，后面恢复起来才发现还存在一个关联的外置数据库需要恢复。\n容器恢复流程说明 # 目前我想到的恢复方案有两种:\n方案一：重建容器，基于数据卷的恢复，因为我们只删除了 /var/lib/docker/containers , 所有数据卷还是存在的。下面的步骤也将具体讲述使用这种方式进行恢复\n方案二：回滚， 将备份数据覆盖到 /var/lib/docker 下， 即:\ncd /var/lib/docker.bak/ mv /var/lib/docker{,.bak-del} cp -a /var/lib/docker.bak /var/lib/docker # 备份为 /var/lib/docker.bak service docker restart 但是这里就回到了第一个问题上就是 docker 服务还是会存在无法启动。 docker 服务无法启动的原因及正确解决流程我会后面再后面写一篇博客说明。\n实行方案一, 进行数据恢复 # 进入备份文件夹下，查找关键信息 # 遍历所有文件夹，查找和容器相关联的容器id # cd /var/lib/docker.bak/ for i in `ls */config.v2.json` ;do grep -l \u0026#39;glpi\u0026#39; \u0026#34;$i\u0026#34;;done # 遍历所有文件夹，查找和容器相关联的容器id 可以看到我们找到了，两个文件\n查看第一个配置文件，并使用 json 格式化工具 进行分析 # cat 33c749acc76fec662a292e7a7fb73a1e32bd2b57ebe4270bb1ad46abc4598011/config.v2.json 找到了容器运行的镜像名称即 fjudith/glpi\n检查到容器部署时，使用的端口映射\n内部 80端口 映射到了 宿主机 的30000 端口\n检查到 使用的存储卷\n使用了两个储存卷，一个为 宿主机的文件 ，一个为创建的的 glpi-app 存储卷\n容器环境变量\n检查查找到的容器镜像是否匹配 # docker image ls 可以看到找到的镜像是 匹配 的\n查看第二个配置文件，并使用 json 格式化工具 进行分析 # cat a967e8f43115ea574ce1929e75b99374b4f050b612264928983baa0fd8109609/config.v2.json 可以看到这是一个 数据库容器\n查看 容器对应挂载的数据是否还在\nDockerhub 查找相关容器信息 # 在 dockerhub 上 search fjudith/glpi 镜像，可以看到容器的启动命令。\n可以看到 dockerhub上 此容器的部署说明，和我们查看到了信息也吻合，那么确定了之前的前辈是一个什么部署步骤了\n容器恢复 # 数据库容器恢复 # 数据库容器 具体 json 文件如下所示 # 格式化后\n{ \u0026#34;StreamConfig\u0026#34;:{ }, \u0026#34;State\u0026#34;:{ \u0026#34;Running\u0026#34;:true, \u0026#34;Paused\u0026#34;:false, \u0026#34;Restarting\u0026#34;:false, \u0026#34;OOMKilled\u0026#34;:false, \u0026#34;RemovalInProgress\u0026#34;:false, \u0026#34;Dead\u0026#34;:false, \u0026#34;Pid\u0026#34;:8598, \u0026#34;ExitCode\u0026#34;:0, \u0026#34;Error\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;StartedAt\u0026#34;:\u0026#34;2020-12-28T03:12:01.012923553Z\u0026#34;, \u0026#34;FinishedAt\u0026#34;:\u0026#34;2020-12-28T03:10:01.310689449Z\u0026#34;, \u0026#34;Health\u0026#34;:null }, \u0026#34;ID\u0026#34;:\u0026#34;a967e8f43115ea574ce1929e75b99374b4f050b612264928983baa0fd8109609\u0026#34;, \u0026#34;Created\u0026#34;:\u0026#34;2019-12-16T07:00:49.830939878Z\u0026#34;, \u0026#34;Managed\u0026#34;:false, \u0026#34;Path\u0026#34;:\u0026#34;docker-entrypoint.sh\u0026#34;, \u0026#34;Args\u0026#34;:[ \u0026#34;mysqld\u0026#34; ], \u0026#34;Config\u0026#34;:{ \u0026#34;Hostname\u0026#34;:\u0026#34;a967e8f43115\u0026#34;, \u0026#34;Domainname\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;User\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;AttachStdin\u0026#34;:false, \u0026#34;AttachStdout\u0026#34;:false, \u0026#34;AttachStderr\u0026#34;:false, \u0026#34;ExposedPorts\u0026#34;:{ \u0026#34;3306/tcp\u0026#34;:{ } }, \u0026#34;Tty\u0026#34;:false, \u0026#34;OpenStdin\u0026#34;:false, \u0026#34;StdinOnce\u0026#34;:false, \u0026#34;Env\u0026#34;:[ \u0026#34;MYSQL_DATABASE=glpi\u0026#34;, \u0026#34;MYSQL_ROOT_PASSWORD=pass\u0026#34;, \u0026#34;MYSQL_USER=glpi\u0026#34;, \u0026#34;MYSQL_PASSWORD=pass\u0026#34;, \u0026#34;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\u0026#34;, \u0026#34;GOSU_VERSION=1.10\u0026#34;, \u0026#34;GPG_KEYS=177F4010FE56CA3336300305F1656F24C74CD1D8\u0026#34;, \u0026#34;MARIADB_MAJOR=10.4\u0026#34;, \u0026#34;MARIADB_VERSION=1:10.4.11+maria~bionic\u0026#34; ], \u0026#34;Cmd\u0026#34;:[ \u0026#34;mysqld\u0026#34; ], \u0026#34;ArgsEscaped\u0026#34;:true, \u0026#34;Image\u0026#34;:\u0026#34;mariadb\u0026#34;, \u0026#34;Volumes\u0026#34;:{ \u0026#34;/var/lib/mysql\u0026#34;:{ } }, \u0026#34;WorkingDir\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Entrypoint\u0026#34;:[ \u0026#34;docker-entrypoint.sh\u0026#34; ], \u0026#34;OnBuild\u0026#34;:null, \u0026#34;Labels\u0026#34;:{ } }, \u0026#34;Image\u0026#34;:\u0026#34;sha256:e93d99aa9076c3582e36fd458be51d2d389cf421b711f03b8767f156be4d2cfb\u0026#34;, \u0026#34;NetworkSettings\u0026#34;:{ \u0026#34;Bridge\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;SandboxID\u0026#34;:\u0026#34;7e381858bd5aa515ccd13b8c856779132d6ddfcefd7ec82ece585db25c4a1c8c\u0026#34;, \u0026#34;HairpinMode\u0026#34;:false, \u0026#34;LinkLocalIPv6Address\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;LinkLocalIPv6PrefixLen\u0026#34;:0, \u0026#34;Networks\u0026#34;:{ \u0026#34;bridge\u0026#34;:{ \u0026#34;IPAMConfig\u0026#34;:null, \u0026#34;Links\u0026#34;:null, \u0026#34;Aliases\u0026#34;:null, \u0026#34;NetworkID\u0026#34;:\u0026#34;b34a6d28cfffb257837e9336d7556eb97ce768c835c59246361db5042c30394d\u0026#34;, \u0026#34;EndpointID\u0026#34;:\u0026#34;50432a9bac088866479de0e423926924d20494ad7e6054bb9395ff28572506bb\u0026#34;, \u0026#34;Gateway\u0026#34;:\u0026#34;172.17.0.1\u0026#34;, \u0026#34;IPAddress\u0026#34;:\u0026#34;172.17.0.2\u0026#34;, \u0026#34;IPPrefixLen\u0026#34;:16, \u0026#34;IPv6Gateway\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;GlobalIPv6Address\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;GlobalIPv6PrefixLen\u0026#34;:0, \u0026#34;MacAddress\u0026#34;:\u0026#34;02:42:ac:11:00:02\u0026#34;, \u0026#34;IPAMOperational\u0026#34;:false } }, \u0026#34;Service\u0026#34;:null, \u0026#34;Ports\u0026#34;:{ \u0026#34;3306/tcp\u0026#34;:[ { \u0026#34;HostIp\u0026#34;:\u0026#34;0.0.0.0\u0026#34;, \u0026#34;HostPort\u0026#34;:\u0026#34;33306\u0026#34; } ] }, \u0026#34;SandboxKey\u0026#34;:\u0026#34;/var/run/docker/netns/7e381858bd5a\u0026#34;, \u0026#34;SecondaryIPAddresses\u0026#34;:null, \u0026#34;SecondaryIPv6Addresses\u0026#34;:null, \u0026#34;IsAnonymousEndpoint\u0026#34;:false, \u0026#34;HasSwarmEndpoint\u0026#34;:false }, \u0026#34;LogPath\u0026#34;:\u0026#34;/var/lib/docker/containers/a967e8f43115ea574ce1929e75b99374b4f050b612264928983baa0fd8109609/a967e8f43115ea574ce1929e75b99374b4f050b612264928983baa0fd8109609-json.log\u0026#34;, \u0026#34;Name\u0026#34;:\u0026#34;/glpi-db\u0026#34;, \u0026#34;Driver\u0026#34;:\u0026#34;overlay\u0026#34;, \u0026#34;MountLabel\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;ProcessLabel\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;RestartCount\u0026#34;:0, \u0026#34;HasBeenStartedBefore\u0026#34;:true, \u0026#34;HasBeenManuallyStopped\u0026#34;:false, \u0026#34;MountPoints\u0026#34;:{ \u0026#34;/etc/localtime\u0026#34;:{ \u0026#34;Source\u0026#34;:\u0026#34;/etc/localtime\u0026#34;, \u0026#34;Destination\u0026#34;:\u0026#34;/etc/localtime\u0026#34;, \u0026#34;RW\u0026#34;:true, \u0026#34;Name\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Driver\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Spec\u0026#34;:{ \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Source\u0026#34;:\u0026#34;/etc/localtime\u0026#34;, \u0026#34;Target\u0026#34;:\u0026#34;/etc/localtime\u0026#34; } }, \u0026#34;/etc/mysql\u0026#34;:{ \u0026#34;Source\u0026#34;:\u0026#34;/data/glpi-mysql/conf\u0026#34;, \u0026#34;Destination\u0026#34;:\u0026#34;/etc/mysql\u0026#34;, \u0026#34;RW\u0026#34;:true, \u0026#34;Name\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Driver\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Spec\u0026#34;:{ \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Source\u0026#34;:\u0026#34;/data/glpi-mysql/conf\u0026#34;, \u0026#34;Target\u0026#34;:\u0026#34;/etc/mysql\u0026#34; } }, \u0026#34;/var/lib/mysql\u0026#34;:{ \u0026#34;Source\u0026#34;:\u0026#34;/data/glpi-mysql/data\u0026#34;, \u0026#34;Destination\u0026#34;:\u0026#34;/var/lib/mysql\u0026#34;, \u0026#34;RW\u0026#34;:true, \u0026#34;Name\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Driver\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Spec\u0026#34;:{ \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Source\u0026#34;:\u0026#34;/data/glpi-mysql/data\u0026#34;, \u0026#34;Target\u0026#34;:\u0026#34;/var/lib/mysql\u0026#34; } }, \u0026#34;/var/log/mysql\u0026#34;:{ \u0026#34;Source\u0026#34;:\u0026#34;/data/glpi-mysql/log\u0026#34;, \u0026#34;Destination\u0026#34;:\u0026#34;/var/log/mysql\u0026#34;, \u0026#34;RW\u0026#34;:true, \u0026#34;Name\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Driver\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Spec\u0026#34;:{ \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Source\u0026#34;:\u0026#34;/data/glpi-mysql/log\u0026#34;, \u0026#34;Target\u0026#34;:\u0026#34;/var/log/mysql\u0026#34; } } }, \u0026#34;SecretReferences\u0026#34;:null, \u0026#34;AppArmorProfile\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;HostnamePath\u0026#34;:\u0026#34;/var/lib/docker/containers/a967e8f43115ea574ce1929e75b99374b4f050b612264928983baa0fd8109609/hostname\u0026#34;, \u0026#34;HostsPath\u0026#34;:\u0026#34;/var/lib/docker/containers/a967e8f43115ea574ce1929e75b99374b4f050b612264928983baa0fd8109609/hosts\u0026#34;, \u0026#34;ShmPath\u0026#34;:\u0026#34;/var/lib/docker/containers/a967e8f43115ea574ce1929e75b99374b4f050b612264928983baa0fd8109609/shm\u0026#34;, \u0026#34;ResolvConfPath\u0026#34;:\u0026#34;/var/lib/docker/containers/a967e8f43115ea574ce1929e75b99374b4f050b612264928983baa0fd8109609/resolv.conf\u0026#34;, \u0026#34;SeccompProfile\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;NoNewPrivileges\u0026#34;:false } 从 json 文件中推理出，容器启动命令，进行重建 # docker run --name=\u0026#39;glpi-db\u0026#39; -d \\ --restart=always \\ -p 33306:3306 \\ -e MYSQL_DATABASE=glpi \\ -e MYSQL_ROOT_PASSWORD=pass \\ -e MYSQL_USER=glpi \\ -e MYSQL_PASSWORD=pass \\ -v /data/glpi-mysql/data:/var/lib/mysql \\ -v /data/glpi-mysql/log:/var/log/mysql \\ -v /data/glpi-mysql/conf:/etc/mysql \\ -v /etc/localtime:/etc/localtime \\ mariadb # 启动 检查容器是否正常启动 # docker logs --tail 100 -f glpi-db # 检测启动日志 使用数据库管理工具进行连接 # 自此数据库 恢复成功\n恢复 glpi 容器 # glpi 容器具体 json 文件如下所示 # 格式后\n{ \u0026#34;StreamConfig\u0026#34;:{ }, \u0026#34;State\u0026#34;:{ \u0026#34;Running\u0026#34;:true, \u0026#34;Paused\u0026#34;:false, \u0026#34;Restarting\u0026#34;:false, \u0026#34;OOMKilled\u0026#34;:false, \u0026#34;RemovalInProgress\u0026#34;:false, \u0026#34;Dead\u0026#34;:false, \u0026#34;Pid\u0026#34;:8894, \u0026#34;ExitCode\u0026#34;:0, \u0026#34;Error\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;StartedAt\u0026#34;:\u0026#34;2020-12-28T03:12:03.393664003Z\u0026#34;, \u0026#34;FinishedAt\u0026#34;:\u0026#34;2020-12-28T03:09:59.174014953Z\u0026#34;, \u0026#34;Health\u0026#34;:null }, \u0026#34;ID\u0026#34;:\u0026#34;33c749acc76fec662a292e7a7fb73a1e32bd2b57ebe4270bb1ad46abc4598011\u0026#34;, \u0026#34;Created\u0026#34;:\u0026#34;2019-12-16T07:30:55.091572697Z\u0026#34;, \u0026#34;Managed\u0026#34;:false, \u0026#34;Path\u0026#34;:\u0026#34;/docker-entrypoint.sh\u0026#34;, \u0026#34;Args\u0026#34;:[ \u0026#34;apache2-foreground\u0026#34; ], \u0026#34;Config\u0026#34;:{ \u0026#34;Hostname\u0026#34;:\u0026#34;33c749acc76f\u0026#34;, \u0026#34;Domainname\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;User\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;AttachStdin\u0026#34;:false, \u0026#34;AttachStdout\u0026#34;:false, \u0026#34;AttachStderr\u0026#34;:false, \u0026#34;ExposedPorts\u0026#34;:{ \u0026#34;80/tcp\u0026#34;:{ } }, \u0026#34;Tty\u0026#34;:false, \u0026#34;OpenStdin\u0026#34;:false, \u0026#34;StdinOnce\u0026#34;:false, \u0026#34;Env\u0026#34;:[ \u0026#34;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\u0026#34;, \u0026#34;PHPIZE_DEPS=autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c\u0026#34;, \u0026#34;PHP_INI_DIR=/usr/local/etc/php\u0026#34;, \u0026#34;APACHE_CONFDIR=/etc/apache2\u0026#34;, \u0026#34;APACHE_ENVVARS=/etc/apache2/envvars\u0026#34;, \u0026#34;PHP_EXTRA_BUILD_DEPS=apache2-dev\u0026#34;, \u0026#34;PHP_EXTRA_CONFIGURE_ARGS=--with-apxs2\u0026#34;, \u0026#34;PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2\u0026#34;, \u0026#34;PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2\u0026#34;, \u0026#34;PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie\u0026#34;, \u0026#34;GPG_KEYS=1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2D6021E995DC9FF8D3EE5AF27F\u0026#34;, \u0026#34;PHP_VERSION=7.2.4\u0026#34;, \u0026#34;PHP_URL=https://secure.php.net/get/php-7.2.4.tar.xz/from/this/mirror\u0026#34;, \u0026#34;PHP_ASC_URL=https://secure.php.net/get/php-7.2.4.tar.xz.asc/from/this/mirror\u0026#34;, \u0026#34;PHP_SHA256=7916b1bd148ddfd46d7f8f9a517d4b09cd8a8ad9248734e7c8dd91ef17057a88\u0026#34;, \u0026#34;PHP_MD5=\u0026#34;, \u0026#34;GLPI_VERSION=9.2.1\u0026#34;, \u0026#34;GLPI_URL=https://github.com/glpi-project/glpi/releases/download/9.2.1/glpi-9.2.1.tgz\u0026#34;, \u0026#34;TERM=xterm\u0026#34; ], \u0026#34;Cmd\u0026#34;:[ \u0026#34;apache2-foreground\u0026#34; ], \u0026#34;ArgsEscaped\u0026#34;:true, \u0026#34;Image\u0026#34;:\u0026#34;fjudith/glpi\u0026#34;, \u0026#34;Volumes\u0026#34;:null, \u0026#34;WorkingDir\u0026#34;:\u0026#34;/var/www/html\u0026#34;, \u0026#34;Entrypoint\u0026#34;:[ \u0026#34;/docker-entrypoint.sh\u0026#34; ], \u0026#34;OnBuild\u0026#34;:null, \u0026#34;Labels\u0026#34;:{ } }, \u0026#34;Image\u0026#34;:\u0026#34;sha256:4c740260749c175d94078f60dedcd898343d287268c9de8af539677e9f49acc9\u0026#34;, \u0026#34;NetworkSettings\u0026#34;:{ \u0026#34;Bridge\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;SandboxID\u0026#34;:\u0026#34;30d50522da0bacc5d6813ecad985ae8891e68ebee54499c17ffabf546b7f6193\u0026#34;, \u0026#34;HairpinMode\u0026#34;:false, \u0026#34;LinkLocalIPv6Address\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;LinkLocalIPv6PrefixLen\u0026#34;:0, \u0026#34;Networks\u0026#34;:{ \u0026#34;bridge\u0026#34;:{ \u0026#34;IPAMConfig\u0026#34;:null, \u0026#34;Links\u0026#34;:null, \u0026#34;Aliases\u0026#34;:null, \u0026#34;NetworkID\u0026#34;:\u0026#34;b34a6d28cfffb257837e9336d7556eb97ce768c835c59246361db5042c30394d\u0026#34;, \u0026#34;EndpointID\u0026#34;:\u0026#34;b6cdb139c21fcf9f0c90283e83e52464d2d078af2f6ea48bcd764c562be5509a\u0026#34;, \u0026#34;Gateway\u0026#34;:\u0026#34;172.17.0.1\u0026#34;, \u0026#34;IPAddress\u0026#34;:\u0026#34;172.17.0.4\u0026#34;, \u0026#34;IPPrefixLen\u0026#34;:16, \u0026#34;IPv6Gateway\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;GlobalIPv6Address\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;GlobalIPv6PrefixLen\u0026#34;:0, \u0026#34;MacAddress\u0026#34;:\u0026#34;02:42:ac:11:00:04\u0026#34;, \u0026#34;IPAMOperational\u0026#34;:false } }, \u0026#34;Service\u0026#34;:null, \u0026#34;Ports\u0026#34;:{ \u0026#34;80/tcp\u0026#34;:[ { \u0026#34;HostIp\u0026#34;:\u0026#34;0.0.0.0\u0026#34;, \u0026#34;HostPort\u0026#34;:\u0026#34;30000\u0026#34; } ] }, \u0026#34;SandboxKey\u0026#34;:\u0026#34;/var/run/docker/netns/30d50522da0b\u0026#34;, \u0026#34;SecondaryIPAddresses\u0026#34;:null, \u0026#34;SecondaryIPv6Addresses\u0026#34;:null, \u0026#34;IsAnonymousEndpoint\u0026#34;:false, \u0026#34;HasSwarmEndpoint\u0026#34;:false }, \u0026#34;LogPath\u0026#34;:\u0026#34;/var/lib/docker/containers/33c749acc76fec662a292e7a7fb73a1e32bd2b57ebe4270bb1ad46abc4598011/33c749acc76fec662a292e7a7fb73a1e32bd2b57ebe4270bb1ad46abc4598011-json.log\u0026#34;, \u0026#34;Name\u0026#34;:\u0026#34;/glpi-app\u0026#34;, \u0026#34;Driver\u0026#34;:\u0026#34;overlay\u0026#34;, \u0026#34;MountLabel\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;ProcessLabel\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;RestartCount\u0026#34;:0, \u0026#34;HasBeenStartedBefore\u0026#34;:true, \u0026#34;HasBeenManuallyStopped\u0026#34;:false, \u0026#34;MountPoints\u0026#34;:{ \u0026#34;/etc/localtime\u0026#34;:{ \u0026#34;Source\u0026#34;:\u0026#34;/etc/localtime\u0026#34;, \u0026#34;Destination\u0026#34;:\u0026#34;/etc/localtime\u0026#34;, \u0026#34;RW\u0026#34;:true, \u0026#34;Name\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Driver\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Spec\u0026#34;:{ \u0026#34;Type\u0026#34;:\u0026#34;bind\u0026#34;, \u0026#34;Source\u0026#34;:\u0026#34;/etc/localtime\u0026#34;, \u0026#34;Target\u0026#34;:\u0026#34;/etc/localtime\u0026#34; } }, \u0026#34;/var/www/html/files\u0026#34;:{ \u0026#34;Source\u0026#34;:\u0026#34;/var/lib/docker/volumes/glpi-app/_data\u0026#34;, \u0026#34;Destination\u0026#34;:\u0026#34;/var/www/html/files\u0026#34;, \u0026#34;RW\u0026#34;:true, \u0026#34;Name\u0026#34;:\u0026#34;glpi-app\u0026#34;, \u0026#34;Driver\u0026#34;:\u0026#34;local\u0026#34;, \u0026#34;Type\u0026#34;:\u0026#34;volume\u0026#34;, \u0026#34;Relabel\u0026#34;:\u0026#34;z\u0026#34;, \u0026#34;ID\u0026#34;:\u0026#34;f700b3bf921f366d7f29aa481757fb6dfd77090c940e4e7c2258b3690acc1168\u0026#34;, \u0026#34;Spec\u0026#34;:{ \u0026#34;Type\u0026#34;:\u0026#34;volume\u0026#34;, \u0026#34;Source\u0026#34;:\u0026#34;glpi-app\u0026#34;, \u0026#34;Target\u0026#34;:\u0026#34;/var/www/html/files\u0026#34; } } }, \u0026#34;SecretReferences\u0026#34;:null, \u0026#34;AppArmorProfile\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;HostnamePath\u0026#34;:\u0026#34;/var/lib/docker/containers/33c749acc76fec662a292e7a7fb73a1e32bd2b57ebe4270bb1ad46abc4598011/hostname\u0026#34;, \u0026#34;HostsPath\u0026#34;:\u0026#34;/var/lib/docker/containers/33c749acc76fec662a292e7a7fb73a1e32bd2b57ebe4270bb1ad46abc4598011/hosts\u0026#34;, \u0026#34;ShmPath\u0026#34;:\u0026#34;/var/lib/docker/containers/33c749acc76fec662a292e7a7fb73a1e32bd2b57ebe4270bb1ad46abc4598011/shm\u0026#34;, \u0026#34;ResolvConfPath\u0026#34;:\u0026#34;/var/lib/docker/containers/33c749acc76fec662a292e7a7fb73a1e32bd2b57ebe4270bb1ad46abc4598011/resolv.conf\u0026#34;, \u0026#34;SeccompProfile\u0026#34;:\u0026#34;\u0026#34;, \u0026#34;NoNewPrivileges\u0026#34;:false } 从 json 文件中推理出，容器启动命令，进行重建 # 重建前，检查一下备份数据中，对应 docker 存储卷中的数据是否还在，可以看到文件什么的多还在 ，按道理我们只需要把文件 copy 到对应的文件夹下应该就可以了\ndocker volume create \u0026#34;glpi-app\u0026#34; # 创建 容器卷文件 cp -a /var/lib/docker.bak/volumes/glpi-app/_data/* /var/lib/docker/volumes/glpi-app/ # 将备份数据 copy 至新建的卷容器文件夹内 docker run --name=glpi-app -d \\ --restart=always \\ -p 30000:80 \\ -v glpi-app:/var/www/html/files \\ -v /etc/localtime:/etc/localtime \\ --links glpi-db:mysql \\ fjudith/glpi # 尝试启动，需要注意网卡link的名称为之前启动的数据库容器的名称 \u0026#x26a0;\u0026#xfe0f; 无法启动\n-- links 在高版本的 Docker 中好像被移除了，文档说明\n使用替换 -- links 方案 # 创建容器 # 此方案将删除前面部署的 mysql 容器\ndocker rm -f glpi-db docker network create glpi # 创建网络 # 将前面部署的 \u0026#34;glpi-db\u0026#34;, 更名为 \u0026#34;mysql\u0026#34; ，方便在同一个网络时使用 docker run --name=mysql -d \\ --restart=always \\ --network=glpi \\ -p 33306:3306 \\ -e MYSQL_DATABASE=glpi \\ -e MYSQL_ROOT_PASSWORD=pass \\ -e MYSQL_USER=glpi \\ -e MYSQL_PASSWORD=pass \\ -v /data/glpi-mysql/data:/var/lib/mysql \\ -v /data/glpi-mysql/log:/var/log/mysql \\ -v /data/glpi-mysql/conf:/etc/mysql \\ -v /etc/localtime:/etc/localtime \\ mariadb docker logs --tail 100 -f mysql # 检查是否正常启动，正常后随后启动第二个容器 docker run --name=glpi-app -d \\ --restart=always \\ -p 30000:80 \\ -v glpi-app:/var/www/html/files \\ -v /etc/localtime:/etc/localtime \\ --network=glpi \\ fjudith/glpi dashboard 引导恢复 # 选择 \u0026ldquo;更新\u0026rdquo; =\u0026gt; \u0026ldquo;选择现有数据库\u0026rdquo;\n登录账户后，数据回来了 😺\n","date":"2021年1月14日","externalUrl":null,"permalink":"/posts/delete-container-reset/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e因在日常维护过程中\u003ccode\u003e升级了docker的版本\u003c/code\u003e ，碰到了一个 docker 的一个 \u003ca\n  href=\"https://treesir.pub/post/rancher-upgrade-docker\"\n    target=\"_blank\"\n  \u003e\u003ccode\u003ebug\u003c/code\u003e\u003c/a\u003e, 导致 docker 服务无法正常启动。在恢复 docker 服务的过程中把 \u003ccode\u003e/var/lib/docker/containers\u003c/code\u003e 下的所有文件给 \u003ccode\u003e清空\u003c/code\u003e 了，清空后 docker 服务可以正常运行，但发现原来的服务中还存留着一个还在使用的 \u003ccode\u003ecmdb\u003c/code\u003e 系统也被我跟着删除了，是之前的前辈部署的。好在自己有 \u003ccode\u003e做备份\u003c/code\u003e 的习惯，现记录一下被删除容器的恢复过程。\u003c/p\u003e","title":"记录一次，因误删容器导致的容器恢复过程","type":"posts"},{"content":" 问题描述: # Kubernetes 节点中，有一台节点使用 coredns 进行解析某个域名时，出现间断性无法正常解析问题，而解析另外一个域名时不会出现解析问题。\n再次重复一下问题重点: 且在集群中的某台节点中出现，且使用某个域名时出现\n环境说明： # 操作系统: CentOS Linux release 7.9.2009 Kubernetes 集群: v1.17.4 （集群使用 rancher 自定义添加集群 一键部署） Dashboard: Rancher-v2.3.5 Linux Kernel: 5.10.3-1.el7.elrepo.x86_64 Coredns : Coredns:1.6.5 + Node-local-dns 工具说明 # 测试时使用的容器: praqma/network-multitool:latest\n测试时使用的dns 测试工具 ： coredns-tools.go\n程序出自 Blog\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;net\u0026#34; \u0026#34;sync/atomic\u0026#34; \u0026#34;time\u0026#34; ) var host string var connections int var duration int64 var limit int64 var timeoutCount int64 func main() { // os.Args = append(os.Args, \u0026#34;-host\u0026#34;, \u0026#34;www.baidu.com\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;200\u0026#34;, \u0026#34;-d\u0026#34;, \u0026#34;30\u0026#34;, \u0026#34;-l\u0026#34;, \u0026#34;5000\u0026#34;) flag.StringVar(\u0026amp;host, \u0026#34;host\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;Resolve host\u0026#34;) flag.IntVar(\u0026amp;connections, \u0026#34;c\u0026#34;, 100, \u0026#34;Connections\u0026#34;) flag.Int64Var(\u0026amp;duration, \u0026#34;d\u0026#34;, 0, \u0026#34;Duration(s)\u0026#34;) flag.Int64Var(\u0026amp;limit, \u0026#34;l\u0026#34;, 0, \u0026#34;Limit(ms)\u0026#34;) flag.Parse() var count int64 = 0 var errCount int64 = 0 pool := make(chan interface{}, connections) exit := make(chan bool) var ( min int64 = 0 max int64 = 0 sum int64 = 0 ) go func() { time.Sleep(time.Second * time.Duration(duration)) exit \u0026lt;- true }() endD: for { select { case pool \u0026lt;- nil: go func() { defer func() { \u0026lt;-pool }() resolver := \u0026amp;net.Resolver{} now := time.Now() _, err := resolver.LookupIPAddr(context.Background(), host) use := time.Since(now).Nanoseconds() / int64(time.Millisecond) if min == 0 || use \u0026lt; min { min = use } if use \u0026gt; max { max = use } sum += use if limit \u0026gt; 0 \u0026amp;\u0026amp; use \u0026gt;= limit { timeoutCount++ } atomic.AddInt64(\u0026amp;count, 1) if err != nil { fmt.Println(err.Error()) atomic.AddInt64(\u0026amp;errCount, 1) } }() case \u0026lt;-exit: break endD } } fmt.Printf(\u0026#34;request count：%d\\nerror count：%d\\n\u0026#34;, count, errCount) fmt.Printf(\u0026#34;request time：min(%dms) max(%dms) avg(%dms) timeout(%dn)\\n\u0026#34;, min, max, sum/count, timeoutCount) } 工具编译\nexport CGO_ENABLED=0 export GOOS=linux export GOARCH=amd64 go build coredns-tools.go 使用文件分享工具: nginx\n配置文件如下所示:\ncat /usr/local/etc/nginx/nginx.conf # 且截取部分配置 server { listen 188; server_name _; charset utf-8; location / { root /Users/zun/Downloads; autoindex on; autoindex_exact_size off; autoindex_localtime on; } } nginx # 启动 问题表现 # ./coredns-tools-linux -host sim-gtw.dt.com -c 200 -d 30 -l 5000 我们可以看到, 我们使用dns测试程序进行测试时，测试结果接近有 61% 的解析失败率，而解析其他域名时解析是正常的\n排查结果 # 排查到结果与 Coredns 使用到的上层DNS 有关; 如下如截图所示中，使用同一个域名解析出来的地址, 一个是外网地址一个是内网地址，由此推断出解析域名时分别使用了不同dns server进行解析的。\n为了验证我们刚才的推断，我们在coredns 的 configmap 中开启 hosts 插件，并将相关域名写入其中。类似于主机中的 hosts文件，hosts文件优先于dns server，当拿到地址解析后客户端将不再继续请求上层dns server。\n排查步骤如下: # 步骤一: # kubectl edit cm coredns -n kube-system # 编辑 coredns configmap 添加 hosts插件配置 (配置如下) hosts { 192.168.1.105 dt.com 192.168.1.106 sim-gtw.dt.com fallthrough } 步骤二: # 修改完成后 等待 coredns pod 重新读取配置或手动删除 coredns 与之相关的 pod 进行重载\nkubectl get pod -n kube-system |grep coredns|awk \u0026#39;{print $1}\u0026#39;|xargs -I {} kubectl delete pod {} --force -n kube-system 修改后的结果展示: # 重载配置完成后 我们再测试一下结果 # wget http://192.168.121.110:188/coredns-tools-linux \\ \u0026amp;\u0026amp; chmod a+x coredns-tools-linux \\ \u0026amp;\u0026amp; ./coredns-tools-linux -host sim-gtw.dt.com -c 200 -d 30 -l 5000 可以看到, 此时我们再进行测试时，域名解析已不再提示报错了。但是最大延迟显示还是显示有5s左右，这个和我们正在使用的kube-proxy策略有关，默认策略使用的是 iptables 模式，如后期优化可进行更改为性能更高的 ipvs模式 及部署 node-local-dns 服务来减少解析延迟，我这里无法更改 kubelet的启动参数中 dns的地址，貌似 node-local-dns 没有太多效果 。\n解决问题 # 我们通过上面的测试验证了间断性域名解析失效，是由于上层dns造成；解决方法也非常简单，只需要更改 coredns 使其使用正确的上层dns即可。\n更改 coredns 配置 # 同样我们需要更改一下 coredns 的 configmap 文件\nkubectl edit cm coredns -n kube-system 更改后的 配置展示\napiVersion: v1 data: Corefile: |- .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 600 } prometheus :9153 forward . 192.168.1.112 192.168.121.112 cache 600 loop reload loadbalance } .... # 省略部分配置 适当的增减了 cache 时间与 ttl 时间\n更改node-local-cahce 配置 # kubectl edit cm node-local-dns -n kube-system # 编辑 node-local-dns 修改参数 更改后的配置展示:\nip6.arpa:53 { errors cache 30 reload reload loop bind 169.254.20.10 10.43.0.10 forward . __PILLAR__CLUSTER__DNS__ { force_tcp } prometheus :9253 } .:53 { errors cache 30 reload loop bind 169.254.20.10 10.43.0.10 forward . __PILLAR__CLUSTER__DNS__ { force_tcp } prometheus :9253 } .... # 省略部分配置 更改完成后，继续等待 coredns 相关pod重载配置。 或手动重载。\nkubectl get pod -n kube-system |grep coredns|awk \u0026#39;{print $1}\u0026#39;|xargs -I {} kubectl delete pod {} --force -n kube-system 参考文档及博客: # Coredns Kubernetes 相关文档\nCoreDNS 自定义域名失效\n部署 Nodelocaldns 解决 Coredns 域名解析延迟\n总结: # 原生的coredns功能方面还是存在某些不完善及兼容性的问题，不过我们可以使用第三方扩展来进行解决。目前coredns主要出现问题的地方在于: 内核的版本、网络插件兼容、5s超时问题，而五秒超时问题我们可以通过升级内核版本及部署 node-local-dns 应用在每个节点中增加缓存解决。\n","date":"2021年1月7日","externalUrl":null,"permalink":"/posts/intermittent-coredns-hosts/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e问题描述: \n    \u003cdiv id=\"问题描述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%97%ae%e9%a2%98%e6%8f%8f%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eKubernetes 节点中，有\u003ccode\u003e一台节点\u003c/code\u003e使用 coredns 进行解析\u003ccode\u003e某个域名\u003c/code\u003e时，出现间断性无法正常解析问题，而解析另外一个域名时不会出现解析问题。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e再次重复一下问题重点: \u003ccode\u003e且在集群中的某台节点中出现，且使用某个域名时出现\u003c/code\u003e\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e环境说明： \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e操作系统:  CentOS Linux release 7.9.2009\u003c/li\u003e\n\u003cli\u003eKubernetes 集群:  v1.17.4  （集群使用 rancher \u003ccode\u003e自定义添加集群\u003c/code\u003e 一键部署）\u003c/li\u003e\n\u003cli\u003eDashboard: Rancher-v2.3.5\u003c/li\u003e\n\u003cli\u003eLinux Kernel:  5.10.3-1.el7.elrepo.x86_64\u003c/li\u003e\n\u003cli\u003eCoredns : Coredns:1.6.5 + Node-local-dns\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e工具说明 \n    \u003cdiv id=\"工具说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b7%a5%e5%85%b7%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e测试时使用的容器:  praqma/network-multitool:latest\u003c/p\u003e","title":"Coredns 出现间断性无法正常解析域名问题","type":"posts"},{"content":"","date":"2020年12月22日","externalUrl":null,"permalink":"/tags/artifact-repository/","section":"Tags","summary":"","title":"Artifact-Repository","type":"tags"},{"content":"","date":"2020年12月22日","externalUrl":null,"permalink":"/tags/dashboard/","section":"Tags","summary":"","title":"Dashboard","type":"tags"},{"content":"","date":"2020年12月22日","externalUrl":null,"permalink":"/tags/package-management/","section":"Tags","summary":"","title":"Package-Management","type":"tags"},{"content":" 环境说明 # 链接文档\n软件版本说明 # helm: v3.4.2 ingress: v3.16.1 Nginx Ingress # 参考文档\n安装 # wget https://get.helm.sh/helm-v3.4.2-linux-amd64.tar.gz tar xf helm-v3.4.2-linux-amd64.tar.gz \\ \u0026amp;\u0026amp; cp linux-amd64/helm /usr/local/bin/ 添加helm命令补全 # helm completion bash \\ \u0026amp;\u0026amp; helm completion bash \u0026gt; /etc/bash_completion.d/helm 添加 ingress repo # helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx \\ \u0026amp;\u0026amp; helm repo update \\ \u0026amp;\u0026amp; helm repo list 修改(查看)默认配置 # 首先我们先获取一下默认的配置文件\nhelm show values ingress-nginx/ingress-nginx # 查看后大多不需要修改，保留为默认即可 生成配置部署文件 # 创建 deploy-yaml.yaml 文件，包涵安装时覆盖默认中的配置。\ncontroller: dnsPolicy: ClusterFirstWithHostNet hostNetwork: true publishService: # hostNetwork 模式下设置为false，通过节点IP地址上报ingress status数据 enabled: false kind: DaemonSet nodeSelector: role: lb # 节点亲和性，只在拥有 \u0026#34;rele=lb\u0026#34; 的节点上部署 service: # HostNetwork 模式不需要创建 service enabled: false defaultBackend: enabled: true 安装 # kubectl create ns ingress-nginx # 创建部署的命名空间 kubectl label nodes node01 role=lb # 应为我们添加了节点亲和性，还要给节点添加一个标签。 helm upgrade --install ingress -f ./deploy-values.yaml -n ingress-nginx ingress-nginx/ingress-nginx watch kubectl get pod -n ingress-nginx # 等待容器启动完成 启动完成后我们访问一下节点的ip，显示 \u0026ldquo;default backend - 404\u0026quot;即是正常。\n测试效果 # 创建一个nginx的 deployment对象\napiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: app: my-nginx template: metadata: labels: app: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: my-nginx labels: app: my-nginx spec: ports: - port: 80 protocol: TCP name: http selector: app: my-nginx --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-nginx annotations: kubernetes.io/ingress.class: \u0026#34;nginx\u0026#34; spec: rules: - host: ngdemo.treesir.pub # 使用的域名映射，当访问 \u0026#34;ngdemo.treesir.pub\u0026#34; 域名时转发至后端的pod http: paths: - path: / backend: serviceName: my-nginx servicePort: 80 kubectl create -f ./nginx.yaml 修改 host文件后，测试访问一下。( Uninx or Linux 修改 /etc/hosts, Windows 修改路径为：C:\\Windows\\System32\\drivers\\etc\\hosts)\n客户端使用Nginx Ingress访问后端pod的全流程图解析，(图片转至 优点知识)\n暴露 dashboard # apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-dashboard namespace: kubernetes-dashboard annotations: kubernetes.io/ingress.class: \u0026#34;nginx\u0026#34; nginx.ingress.kubernetes.io/use-regex: \u0026#34;true\u0026#34; nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/ssl-redirect: \u0026#34;true\u0026#34; nginx.ingress.kubernetes.io/backend-protocol: \u0026#34;HTTPS\u0026#34; spec: tls: - hosts: - dashboard.treesir.pub secretName: kubernetes-dashboard-certs rules: - host: dashboard.treesir.pub http: paths: - path: / backend: serviceName: kubernetes-dashboard servicePort: 443 kubectl create -f dashboard-ingress.yaml 修改host 文件后测试访问\n","date":"2020年12月22日","externalUrl":null,"permalink":"/posts/ingress-to-dashboard/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca\n  href=\"https://www.treesir.pub/post/kubeadm-deploy-k8s1.9/\"\n    target=\"_blank\"\n  \u003e链接文档\u003c/a\u003e\u003c/p\u003e\u003c/blockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003e软件版本说明 \n    \u003cdiv id=\"软件版本说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bd%af%e4%bb%b6%e7%89%88%e6%9c%ac%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003ehelm: \u003ca\n  href=\"https://github.com/helm/helm/tags\"\n    target=\"_blank\"\n  \u003ev3.4.2\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eingress: \u003ca\n  href=\"https://github.com/kubernetes/ingress-nginx/tags\"\n    target=\"_blank\"\n  \u003ev3.16.1\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eNginx Ingress \n    \u003cdiv id=\"nginx-ingress\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#nginx-ingress\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca\n  href=\"https://kubernetes.github.io/ingress-nginx/\"\n    target=\"_blank\"\n  \u003e参考文档\u003c/a\u003e\u003c/p\u003e","title":"部署 Nginx-Ingress 并配置暴露 kubernetes dashboard","type":"posts"},{"content":"Nexus3 是世界领先的企业级制品仓库管理平台，为现代 DevOps 工具链提供统一的制品管理解决方案。本指南将从基础部署到企业级配置，全面介绍 Nexus3 的部署、配置、管理和运维最佳实践。\nNexus3 平台简介 # 什么是 Nexus3 # Nexus Repository Manager 3 是由 Sonatype 公司开发的下一代制品仓库管理平台，作为 DevOps 工具链的核心组件，为软件开发生命周期提供统一的制品管理能力。\n核心特性 # 统一制品管理：支持 20+ 种包格式的统一管理 高性能架构：基于现代化架构设计，支持大规模并发访问 企业级安全：细粒度权限控制、LDAP 集成、漏洞扫描 智能代理：缓存外部仓库，提供离线访问能力 REST API：完整的 API 支持，便于自动化集成 可扩展性：支持集群部署和水平扩展 支持的制品格式 # 类别 格式 用途 Java 生态 Maven, Gradle Java 项目依赖管理 前端生态 npm, Bower, NuGet 前端包管理 容器技术 Docker, Helm 容器镜像和 K8s 应用 系统包 Yum, APT, Conan 操作系统包管理 语言包 PyPI, RubyGems, Go 编程语言包管理 其他格式 Raw, Git LFS, R 通用文件和特殊格式 应用场景 # DevOps 流水线：CI/CD 过程中的制品存储和分发 依赖管理：统一管理项目依赖，减少外部风险 版本控制：制品版本管理和发布控制 安全合规：制品安全扫描和合规性检查 成本优化：减少外网带宽消耗，提高下载速度 架构设计 # 单机架构 # flowchart TB subgraph Nexus3[Nexus3 单机部署] A[Web UI / REST API / Repository] B[Repository Manager Core] C[Security / Tasks / Capabilities / Logs] D[OrientDB / Blob Store / Config] end 高可用架构 # flowchart TB LB[Load Balancer] N1[Nexus3 Node 1] N2[Nexus3 Node 2] N3[Nexus3 Node 3] LB --\u003e N1 LB --\u003e N2 LB --\u003e N3 N1 -.-\u003e|Shared Storage| S1 N2 -.-\u003e|Shared Storage| S1 N3 -.-\u003e|Shared Storage| S1 subgraph SharedStorage[Shared Storage] S1[PostgreSQL / Blob Store / NFS] end 仓库类型详解 # Nexus3 支持三种不同类型的仓库，每种类型都有其特定的用途和优势：\n1. Hosted 仓库（托管仓库） # 用途: 存储企业内部开发的制品和第三方上传的包 特点:\n完全由企业自主管理 可以上传、删除、修改制品 适合存储内部开发的组件和库 使用场景:\n企业内部开发的 JAR 包、Docker 镜像 第三方供应商提供的私有组件 需要定制化的开源组件 2. Proxy 仓库（代理仓库） # 用途: 代理外部公共仓库，提供缓存和加速功能 工作原理:\n客户端请求依赖包 如果本地缓存存在，直接返回 如果本地不存在，从远程仓库下载并缓存 后续相同请求直接从缓存返回 优势:\n提高下载速度 减少外网带宽消耗 提供离线访问能力 统一管理外部依赖 3. Group 仓库（组合仓库） # 用途: 将多个仓库组合成一个统一的访问入口 特点:\n可以包含 hosted、proxy、group 类型的仓库 支持优先级设置 提供统一的访问地址 重要提示: Group 仓库采用自上而下的匹配策略，一旦找到匹配的制品就停止搜索。因此，仓库的排序非常重要：\n在上图示例中，如果 custom 和 aliyun 仓库都包含 centos:7 镜像，客户端拉取时会优先获取 custom 仓库中的版本。\n环境准备 # 系统要求 # 硬件要求 # 环境类型 CPU 内存 存储 网络 开发环境 4 核 8GB 100GB 1Gbps 测试环境 8 核 16GB 500GB 1Gbps 生产环境 16 核 32GB+ 2TB+ 10Gbps 大型企业 32 核 64GB+ 10TB+ 10Gbps 软件要求 # 组件 最低版本 推荐版本 说明 操作系统 CentOS 7.6 CentOS 8+ / Ubuntu 20.04+ 64位系统 Docker 19.03.0 20.10.0+ 容器运行时 Docker Compose 1.25.0 1.29.0+ 容器编排工具 Java OpenJDK 8 OpenJDK 11+ Nexus3 运行环境 网络端口规划 # 端口 协议 服务 说明 8081 TCP Web UI 管理界面和 REST API 8082 TCP Docker Pull Docker 镜像拉取 8083 TCP Docker Push Docker 镜像推送 8084-8090 TCP Custom Repos 自定义仓库端口 5432 TCP PostgreSQL 外部数据库（可选） 环境检查脚本 # cat \u0026gt; check-nexus-env.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== Nexus3 环境检查脚本 ===\u0026#34; echo \u0026#34;检查时间: $(date)\u0026#34; echo # 检查操作系统 echo \u0026#34;=== 系统信息 ===\u0026#34; cat /etc/redhat-release 2\u0026gt;/dev/null || lsb_release -a 2\u0026gt;/dev/null uname -a echo # 检查内存 echo \u0026#34;=== 内存信息 ===\u0026#34; free -h TOTAL_MEM=$(free -m | awk \u0026#39;NR==2{printf \u0026#34;%.0f\u0026#34;, $2}\u0026#39;) if [ $TOTAL_MEM -lt 8192 ]; then echo \u0026#34;⚠ 警告: 内存不足 8GB，可能影响 Nexus3 性能\u0026#34; else echo \u0026#34;✓ 内存充足\u0026#34; fi echo # 检查磁盘空间 echo \u0026#34;=== 磁盘空间 ===\u0026#34; df -h DISK_USAGE=$(df / | awk \u0026#39;NR==2 {print $5}\u0026#39; | sed \u0026#39;s/%//\u0026#39;) if [ $DISK_USAGE -gt 80 ]; then echo \u0026#34;⚠ 警告: 磁盘使用率超过 80%\u0026#34; else echo \u0026#34;✓ 磁盘空间充足\u0026#34; fi echo # 检查 Docker echo \u0026#34;=== Docker 环境检查 ===\u0026#34; if command -v docker \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then docker --version echo \u0026#34;✓ Docker 已安装\u0026#34; if systemctl is-active --quiet docker; then echo \u0026#34;✓ Docker 服务运行正常\u0026#34; else echo \u0026#34;✗ Docker 服务未运行\u0026#34; fi else echo \u0026#34;✗ Docker 未安装\u0026#34; fi echo # 检查 Docker Compose echo \u0026#34;=== Docker Compose 检查 ===\u0026#34; if command -v docker-compose \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then docker-compose --version echo \u0026#34;✓ Docker Compose 已安装\u0026#34; else echo \u0026#34;✗ Docker Compose 未安装\u0026#34; fi echo # 检查网络端口 echo \u0026#34;=== 端口检查 ===\u0026#34; for port in 8081 8082 8083; do if netstat -tlnp | grep :$port \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;⚠ 端口 $port 已被占用\u0026#34; netstat -tlnp | grep :$port else echo \u0026#34;✓ 端口 $port 可用\u0026#34; fi done echo # 检查 Java 环境 echo \u0026#34;=== Java 环境检查 ===\u0026#34; if command -v java \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then java -version echo \u0026#34;✓ Java 已安装\u0026#34; else echo \u0026#34;ℹ Java 未安装（Docker 部署不需要）\u0026#34; fi echo echo \u0026#34;=== 环境检查完成 ===\u0026#34; EOF chmod +x check-nexus-env.sh ./check-nexus-env.sh 存储规划 # LVM 存储配置（推荐） # # 创建 LVM 存储配置脚本 cat \u0026gt; setup-nexus-storage.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash DEVICE=\u0026#34;/dev/sdb\u0026#34; # 修改为实际设备 VG_NAME=\u0026#34;nexus-vg\u0026#34; LV_NAME=\u0026#34;nexus-lv\u0026#34; MOUNT_POINT=\u0026#34;/data/nexus3\u0026#34; SIZE=\u0026#34;500G\u0026#34; # 根据需求调整 echo \u0026#34;=== Nexus3 存储配置脚本 ===\u0026#34; echo \u0026#34;设备: $DEVICE\u0026#34; echo \u0026#34;挂载点: $MOUNT_POINT\u0026#34; echo \u0026#34;大小: $SIZE\u0026#34; read -p \u0026#34;确认继续？(y/N): \u0026#34; confirm if [[ \u0026#34;$confirm\u0026#34; != \u0026#34;y\u0026#34; \u0026amp;\u0026amp; \u0026#34;$confirm\u0026#34; != \u0026#34;Y\u0026#34; ]]; then echo \u0026#34;操作已取消\u0026#34; exit 0 fi # 创建物理卷 echo \u0026#34;创建物理卷...\u0026#34; pvcreate $DEVICE # 创建卷组 echo \u0026#34;创建卷组...\u0026#34; vgcreate $VG_NAME $DEVICE # 创建逻辑卷 echo \u0026#34;创建逻辑卷...\u0026#34; lvcreate -L $SIZE -n $LV_NAME $VG_NAME # 格式化文件系统 echo \u0026#34;格式化文件系统...\u0026#34; mkfs.xfs /dev/$VG_NAME/$LV_NAME # 创建挂载点 echo \u0026#34;创建挂载点...\u0026#34; mkdir -p $MOUNT_POINT # 挂载文件系统 echo \u0026#34;挂载文件系统...\u0026#34; mount /dev/$VG_NAME/$LV_NAME $MOUNT_POINT # 添加到 fstab echo \u0026#34;配置开机自动挂载...\u0026#34; echo \u0026#34;/dev/$VG_NAME/$LV_NAME $MOUNT_POINT xfs defaults 0 0\u0026#34; \u0026gt;\u0026gt; /etc/fstab # 设置权限 echo \u0026#34;设置目录权限...\u0026#34; chown -R 200:200 $MOUNT_POINT chmod 755 $MOUNT_POINT echo \u0026#34;✓ 存储配置完成\u0026#34; echo \u0026#34;挂载点: $MOUNT_POINT\u0026#34; echo \u0026#34;可用空间: $(df -h $MOUNT_POINT | awk \u0026#39;NR==2 {print $4}\u0026#39;)\u0026#34; EOF chmod +x setup-nexus-storage.sh 网络和防火墙配置 # # 防火墙配置脚本 cat \u0026gt; setup-nexus-firewall.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== Nexus3 防火墙配置 ===\u0026#34; # 检查防火墙状态 if systemctl is-active --quiet firewalld; then echo \u0026#34;配置防火墙规则...\u0026#34; # 开放 Nexus3 端口 firewall-cmd --permanent --add-port=8081/tcp # Web UI firewall-cmd --permanent --add-port=8082/tcp # Docker Pull firewall-cmd --permanent --add-port=8083/tcp # Docker Push firewall-cmd --permanent --add-port=8084-8090/tcp # 自定义端口 # 重新加载防火墙 firewall-cmd --reload echo \u0026#34;✓ 防火墙配置完成\u0026#34; firewall-cmd --list-ports else echo \u0026#34;防火墙未启用\u0026#34; fi # SELinux 配置 if getenforce | grep -q \u0026#34;Enforcing\u0026#34;; then echo \u0026#34;配置 SELinux...\u0026#34; setsebool -P container_manage_cgroup on echo \u0026#34;✓ SELinux 配置完成\u0026#34; fi echo \u0026#34;=== 网络配置完成 ===\u0026#34; EOF chmod +x setup-nexus-firewall.sh ./setup-nexus-firewall.sh Nexus3 部署实施 # 方案一：Docker 单机部署 # 步骤 1：创建项目目录 # # 创建项目目录结构 mkdir -p /data/nexus3/{data,logs,backup,config} cd /data/nexus3 # 设置目录权限（Nexus3 容器内使用 UID 200） chown -R 200:200 /data/nexus3/data chmod -R 755 /data/nexus3 # 设置 SELinux 上下文（如果启用） if getenforce | grep -q \u0026#34;Enforcing\u0026#34;; then setsebool -P container_manage_cgroup on chcon -Rt svirt_sandbox_file_t /data/nexus3/data fi 步骤 2：基础 Docker 部署 # # 基础部署命令 docker run -d \\ --name nexus3 \\ --restart unless-stopped \\ --ulimit nofile=65536:65536 \\ -p 8081:8081 \\ -p 8082:8082 \\ -p 8083:8083 \\ -e INSTALL4J_ADD_VM_PARAMS=\u0026#34;-Xms4g -Xmx4g -XX:MaxDirectMemorySize=6g -Djava.util.prefs.userRoot=/nexus-data/javaprefs\u0026#34; \\ -v /etc/localtime:/etc/localtime:ro \\ -v /data/nexus3/data:/nexus-data \\ sonatype/nexus3:3.41.1 步骤 3：Docker Compose 部署（推荐） # cat \u0026gt; docker-compose.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; version: \u0026#39;3.8\u0026#39; services: nexus3: image: sonatype/nexus3:3.41.1 container_name: nexus3 restart: unless-stopped hostname: nexus.your-domain.com ports: - \u0026#34;8081:8081\u0026#34; - \u0026#34;8082:8082\u0026#34; - \u0026#34;8083:8083\u0026#34; volumes: - ./data:/nexus-data - ./logs:/opt/sonatype/nexus/log - /etc/localtime:/etc/localtime:ro environment: - NEXUS_SECURITY_RANDOMPASSWORD=false - INSTALL4J_ADD_VM_PARAMS=-Xms4g -Xmx4g -XX:MaxDirectMemorySize=6g -Djava.util.prefs.userRoot=/nexus-data/javaprefs ulimits: nofile: soft: 65536 hard: 65536 healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;curl\u0026#34;, \u0026#34;-f\u0026#34;, \u0026#34;http://localhost:8081/service/rest/v1/status\u0026#34;] interval: 30s timeout: 10s retries: 3 start_period: 120s networks: default: name: nexus-network EOF 步骤 4：生产环境配置 # cat \u0026gt; docker-compose.prod.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; version: \u0026#39;3.8\u0026#39; services: nexus3: image: sonatype/nexus3:3.41.1 container_name: nexus3-prod restart: unless-stopped hostname: nexus.company.com ports: - \u0026#34;8081:8081\u0026#34; - \u0026#34;8082:8082\u0026#34; - \u0026#34;8083:8083\u0026#34; - \u0026#34;8084:8084\u0026#34; # 额外端口 - \u0026#34;8085:8085\u0026#34; # 额外端口 volumes: - ./data:/nexus-data - ./logs:/opt/sonatype/nexus/log - ./backup:/backup - /etc/localtime:/etc/localtime:ro environment: - NEXUS_SECURITY_RANDOMPASSWORD=false - INSTALL4J_ADD_VM_PARAMS=-server -Xms8g -Xmx8g -XX:MaxDirectMemorySize=12g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication -Djava.util.prefs.userRoot=/nexus-data/javaprefs -Djava.net.preferIPv4Stack=true ulimits: nofile: soft: 65536 hard: 65536 memlock: soft: -1 hard: -1 deploy: resources: limits: memory: 16G cpus: \u0026#39;8\u0026#39; reservations: memory: 8G cpus: \u0026#39;4\u0026#39; healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;curl\u0026#34;, \u0026#34;-f\u0026#34;, \u0026#34;http://localhost:8081/service/rest/v1/status\u0026#34;] interval: 30s timeout: 10s retries: 5 start_period: 300s logging: driver: \u0026#34;json-file\u0026#34; options: max-size: \u0026#34;100m\u0026#34; max-file: \u0026#34;3\u0026#34; networks: default: name: nexus-network driver: bridge EOF 步骤 5：启动服务 # # 开发环境启动 docker-compose up -d # 生产环境启动 docker-compose -f docker-compose.prod.yml up -d # 查看启动日志 docker-compose logs -f nexus3 # 等待服务完全启动（通常需要 3-5 分钟） echo \u0026#34;等待 Nexus3 启动...\u0026#34; timeout 300 bash -c \u0026#39;until curl -f http://localhost:8081/service/rest/v1/status; do sleep 10; done\u0026#39; JVM 参数说明：\n-Xms4g -Xmx4g: 堆内存大小（根据服务器配置调整） -XX:MaxDirectMemorySize=6g: 直接内存大小 -XX:+UseG1GC: 使用 G1 垃圾收集器 -XX:MaxGCPauseMillis=200: GC 暂停时间目标 -XX:+UseStringDeduplication: 字符串去重优化 第三步：验证部署 # 检查容器状态 # # 查看容器运行状态 docker ps | grep nexus3 # 查看容器日志 docker logs -f nexus3 # 检查端口监听 netstat -tlnp | grep -E \u0026#34;8081|8082|8083\u0026#34; 等待服务启动 # Nexus3 首次启动需要较长时间（通常 2-5 分钟），请耐心等待：\n# 监控启动日志，看到以下信息表示启动成功 docker logs -f nexus3 | grep \u0026#34;Started Sonatype Nexus\u0026#34; 获取初始密码 # # 查看初始管理员密码 cat /application/nexus3/data/admin.password # 或者在容器内查看 docker exec nexus3 cat /nexus-data/admin.password 初始化配置 # 获取初始管理员密码 # # 等待 Nexus3 完全启动 echo \u0026#34;等待 Nexus3 启动完成...\u0026#34; timeout 300 bash -c \u0026#39;until docker exec nexus3 test -f /nexus-data/admin.password; do sleep 10; done\u0026#39; # 获取初始管理员密码 ADMIN_PASSWORD=$(docker exec nexus3 cat /nexus-data/admin.password) echo \u0026#34;初始管理员密码: $ADMIN_PASSWORD\u0026#34; # 保存密码到文件 echo \u0026#34;$ADMIN_PASSWORD\u0026#34; \u0026gt; admin-password.txt chmod 600 admin-password.txt Web 界面初始化 # 访问管理界面：http://your-server-ip:8081 登录系统： 用户名：admin 密码：使用上面获取的初始密码 完成设置向导： 修改管理员密码 配置匿名访问权限 选择改进计划参与方式 图：Nexus3 初始化配置界面\n自动化初始配置脚本 # cat \u0026gt; init-nexus.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash NEXUS_URL=\u0026#34;http://localhost:8081\u0026#34; ADMIN_USER=\u0026#34;admin\u0026#34; ADMIN_PASS=$(cat admin-password.txt) NEW_ADMIN_PASS=\u0026#34;YourSecurePassword123!\u0026#34; echo \u0026#34;=== Nexus3 自动化初始配置 ===\u0026#34; # 等待 Nexus3 API 可用 echo \u0026#34;等待 Nexus3 API 可用...\u0026#34; timeout 300 bash -c \u0026#39;until curl -f $NEXUS_URL/service/rest/v1/status; do sleep 10; done\u0026#39; # 修改管理员密码 echo \u0026#34;修改管理员密码...\u0026#34; curl -X PUT \u0026#34;$NEXUS_URL/service/rest/v1/security/users/admin/change-password\u0026#34; \\ -H \u0026#34;Content-Type: text/plain\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$ADMIN_PASS\u0026#34; \\ -d \u0026#34;$NEW_ADMIN_PASS\u0026#34; # 禁用匿名访问 echo \u0026#34;配置匿名访问...\u0026#34; curl -X PUT \u0026#34;$NEXUS_URL/service/rest/v1/security/anonymous\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$NEW_ADMIN_PASS\u0026#34; \\ -d \u0026#39;{\u0026#34;enabled\u0026#34;: false, \u0026#34;userId\u0026#34;: \u0026#34;anonymous\u0026#34;, \u0026#34;realmName\u0026#34;: \u0026#34;NexusAuthorizingRealm\u0026#34;}\u0026#39; # 创建 Blob Store echo \u0026#34;创建 Blob Store...\u0026#34; curl -X POST \u0026#34;$NEXUS_URL/service/rest/v1/blobstores/file\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$NEW_ADMIN_PASS\u0026#34; \\ -d \u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;docker-blob\u0026#34;, \u0026#34;softQuota\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;spaceUsedQuota\u0026#34;, \u0026#34;limit\u0026#34;: 100000000000 }, \u0026#34;path\u0026#34;: \u0026#34;docker-blob\u0026#34; }\u0026#39; echo \u0026#34;✓ 初始配置完成\u0026#34; echo \u0026#34;新管理员密码: $NEW_ADMIN_PASS\u0026#34; EOF chmod +x init-nexus.sh ./init-nexus.sh 仓库配置管理 # Maven 仓库配置 # 默认 Maven 仓库 # Nexus3 默认提供以下 Maven 仓库：\nmaven-central: Maven 中央仓库代理 maven-releases: 发布版本仓库（hosted） maven-snapshots: 快照版本仓库（hosted） maven-public: 仓库组（group） 添加国内镜像仓库 # # 创建阿里云 Maven 代理仓库 cat \u0026gt; create-maven-aliyun.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash NEXUS_URL=\u0026#34;http://localhost:8081\u0026#34; ADMIN_USER=\u0026#34;admin\u0026#34; ADMIN_PASS=\u0026#34;YourSecurePassword123!\u0026#34; curl -X POST \u0026#34;$NEXUS_URL/service/rest/v1/repositories/maven/proxy\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$ADMIN_PASS\u0026#34; \\ -d \u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;maven-aliyun\u0026#34;, \u0026#34;online\u0026#34;: true, \u0026#34;storage\u0026#34;: { \u0026#34;blobStoreName\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;strictContentTypeValidation\u0026#34;: true }, \u0026#34;proxy\u0026#34;: { \u0026#34;remoteUrl\u0026#34;: \u0026#34;https://maven.aliyun.com/repository/public\u0026#34;, \u0026#34;contentMaxAge\u0026#34;: 1440, \u0026#34;metadataMaxAge\u0026#34;: 1440 }, \u0026#34;negativeCache\u0026#34;: { \u0026#34;enabled\u0026#34;: true, \u0026#34;timeToLive\u0026#34;: 1440 }, \u0026#34;httpClient\u0026#34;: { \u0026#34;blocked\u0026#34;: false, \u0026#34;autoBlock\u0026#34;: true }, \u0026#34;maven\u0026#34;: { \u0026#34;versionPolicy\u0026#34;: \u0026#34;MIXED\u0026#34;, \u0026#34;layoutPolicy\u0026#34;: \u0026#34;STRICT\u0026#34; } }\u0026#39; echo \u0026#34;✓ 阿里云 Maven 仓库创建完成\u0026#34; EOF chmod +x create-maven-aliyun.sh ./create-maven-aliyun.sh 配置 Maven 客户端 # \u0026lt;!-- ~/.m2/settings.xml --\u0026gt; \u0026lt;settings\u0026gt; \u0026lt;mirrors\u0026gt; \u0026lt;mirror\u0026gt; \u0026lt;id\u0026gt;nexus\u0026lt;/id\u0026gt; \u0026lt;mirrorOf\u0026gt;*\u0026lt;/mirrorOf\u0026gt; \u0026lt;name\u0026gt;Nexus Repository\u0026lt;/name\u0026gt; \u0026lt;url\u0026gt;http://your-nexus-server:8081/repository/maven-public/\u0026lt;/url\u0026gt; \u0026lt;/mirror\u0026gt; \u0026lt;/mirrors\u0026gt; \u0026lt;servers\u0026gt; \u0026lt;server\u0026gt; \u0026lt;id\u0026gt;nexus-releases\u0026lt;/id\u0026gt; \u0026lt;username\u0026gt;admin\u0026lt;/username\u0026gt; \u0026lt;password\u0026gt;YourSecurePassword123!\u0026lt;/password\u0026gt; \u0026lt;/server\u0026gt; \u0026lt;server\u0026gt; \u0026lt;id\u0026gt;nexus-snapshots\u0026lt;/id\u0026gt; \u0026lt;username\u0026gt;admin\u0026lt;/username\u0026gt; \u0026lt;password\u0026gt;YourSecurePassword123!\u0026lt;/password\u0026gt; \u0026lt;/server\u0026gt; \u0026lt;/servers\u0026gt; \u0026lt;/settings\u0026gt; Docker 仓库配置 # 创建 Docker 仓库 # cat \u0026gt; create-docker-repos.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash NEXUS_URL=\u0026#34;http://localhost:8081\u0026#34; ADMIN_USER=\u0026#34;admin\u0026#34; ADMIN_PASS=\u0026#34;YourSecurePassword123!\u0026#34; # 创建 Docker Hosted 仓库 echo \u0026#34;创建 Docker Hosted 仓库...\u0026#34; curl -X POST \u0026#34;$NEXUS_URL/service/rest/v1/repositories/docker/hosted\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$ADMIN_PASS\u0026#34; \\ -d \u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;docker-hosted\u0026#34;, \u0026#34;online\u0026#34;: true, \u0026#34;storage\u0026#34;: { \u0026#34;blobStoreName\u0026#34;: \u0026#34;docker-blob\u0026#34;, \u0026#34;strictContentTypeValidation\u0026#34;: true, \u0026#34;writePolicy\u0026#34;: \u0026#34;ALLOW\u0026#34; }, \u0026#34;docker\u0026#34;: { \u0026#34;v1Enabled\u0026#34;: false, \u0026#34;forceBasicAuth\u0026#34;: true, \u0026#34;httpPort\u0026#34;: 8083 } }\u0026#39; # 创建 Docker Proxy 仓库 echo \u0026#34;创建 Docker Proxy 仓库...\u0026#34; curl -X POST \u0026#34;$NEXUS_URL/service/rest/v1/repositories/docker/proxy\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$ADMIN_PASS\u0026#34; \\ -d \u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;docker-proxy\u0026#34;, \u0026#34;online\u0026#34;: true, \u0026#34;storage\u0026#34;: { \u0026#34;blobStoreName\u0026#34;: \u0026#34;docker-blob\u0026#34;, \u0026#34;strictContentTypeValidation\u0026#34;: true }, \u0026#34;proxy\u0026#34;: { \u0026#34;remoteUrl\u0026#34;: \u0026#34;https://registry-1.docker.io\u0026#34;, \u0026#34;contentMaxAge\u0026#34;: 1440, \u0026#34;metadataMaxAge\u0026#34;: 1440 }, \u0026#34;negativeCache\u0026#34;: { \u0026#34;enabled\u0026#34;: true, \u0026#34;timeToLive\u0026#34;: 1440 }, \u0026#34;httpClient\u0026#34;: { \u0026#34;blocked\u0026#34;: false, \u0026#34;autoBlock\u0026#34;: true }, \u0026#34;docker\u0026#34;: { \u0026#34;v1Enabled\u0026#34;: false, \u0026#34;forceBasicAuth\u0026#34;: true, \u0026#34;httpPort\u0026#34;: 8082 }, \u0026#34;dockerProxy\u0026#34;: { \u0026#34;indexType\u0026#34;: \u0026#34;HUB\u0026#34; } }\u0026#39; # 创建 Docker Group 仓库 echo \u0026#34;创建 Docker Group 仓库...\u0026#34; curl -X POST \u0026#34;$NEXUS_URL/service/rest/v1/repositories/docker/group\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$ADMIN_PASS\u0026#34; \\ -d \u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;docker-group\u0026#34;, \u0026#34;online\u0026#34;: true, \u0026#34;storage\u0026#34;: { \u0026#34;blobStoreName\u0026#34;: \u0026#34;docker-blob\u0026#34;, \u0026#34;strictContentTypeValidation\u0026#34;: true }, \u0026#34;group\u0026#34;: { \u0026#34;memberNames\u0026#34;: [\u0026#34;docker-hosted\u0026#34;, \u0026#34;docker-proxy\u0026#34;] }, \u0026#34;docker\u0026#34;: { \u0026#34;v1Enabled\u0026#34;: false, \u0026#34;forceBasicAuth\u0026#34;: true, \u0026#34;httpPort\u0026#34;: 8084 } }\u0026#39; echo \u0026#34;✓ Docker 仓库创建完成\u0026#34; EOF chmod +x create-docker-repos.sh ./create-docker-repos.sh 配置 Docker 客户端 # # 配置 Docker daemon cat \u0026gt; /etc/docker/daemon.json \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; { \u0026#34;insecure-registries\u0026#34;: [ \u0026#34;your-nexus-server:8082\u0026#34;, \u0026#34;your-nexus-server:8083\u0026#34;, \u0026#34;your-nexus-server:8084\u0026#34; ], \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;http://your-nexus-server:8082\u0026#34; ] } EOF # 重启 Docker 服务 systemctl restart docker # 登录到 Nexus Docker 仓库 docker login your-nexus-server:8083 NPM 仓库配置 # 创建 NPM 仓库 # cat \u0026gt; create-npm-repos.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash NEXUS_URL=\u0026#34;http://localhost:8081\u0026#34; ADMIN_USER=\u0026#34;admin\u0026#34; ADMIN_PASS=\u0026#34;YourSecurePassword123!\u0026#34; # 创建 NPM Proxy 仓库 echo \u0026#34;创建 NPM Proxy 仓库...\u0026#34; curl -X POST \u0026#34;$NEXUS_URL/service/rest/v1/repositories/npm/proxy\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$ADMIN_PASS\u0026#34; \\ -d \u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;npm-proxy\u0026#34;, \u0026#34;online\u0026#34;: true, \u0026#34;storage\u0026#34;: { \u0026#34;blobStoreName\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;strictContentTypeValidation\u0026#34;: true }, \u0026#34;proxy\u0026#34;: { \u0026#34;remoteUrl\u0026#34;: \u0026#34;https://registry.npmjs.org\u0026#34;, \u0026#34;contentMaxAge\u0026#34;: 1440, \u0026#34;metadataMaxAge\u0026#34;: 1440 }, \u0026#34;negativeCache\u0026#34;: { \u0026#34;enabled\u0026#34;: true, \u0026#34;timeToLive\u0026#34;: 1440 }, \u0026#34;httpClient\u0026#34;: { \u0026#34;blocked\u0026#34;: false, \u0026#34;autoBlock\u0026#34;: true } }\u0026#39; # 创建 NPM Hosted 仓库 echo \u0026#34;创建 NPM Hosted 仓库...\u0026#34; curl -X POST \u0026#34;$NEXUS_URL/service/rest/v1/repositories/npm/hosted\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$ADMIN_PASS\u0026#34; \\ -d \u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;npm-hosted\u0026#34;, \u0026#34;online\u0026#34;: true, \u0026#34;storage\u0026#34;: { \u0026#34;blobStoreName\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;strictContentTypeValidation\u0026#34;: true, \u0026#34;writePolicy\u0026#34;: \u0026#34;ALLOW\u0026#34; } }\u0026#39; # 创建 NPM Group 仓库 echo \u0026#34;创建 NPM Group 仓库...\u0026#34; curl -X POST \u0026#34;$NEXUS_URL/service/rest/v1/repositories/npm/group\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -u \u0026#34;$ADMIN_USER:$ADMIN_PASS\u0026#34; \\ -d \u0026#39;{ \u0026#34;name\u0026#34;: \u0026#34;npm-group\u0026#34;, \u0026#34;online\u0026#34;: true, \u0026#34;storage\u0026#34;: { \u0026#34;blobStoreName\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;strictContentTypeValidation\u0026#34;: true }, \u0026#34;group\u0026#34;: { \u0026#34;memberNames\u0026#34;: [\u0026#34;npm-hosted\u0026#34;, \u0026#34;npm-proxy\u0026#34;] } }\u0026#39; echo \u0026#34;✓ NPM 仓库创建完成\u0026#34; EOF chmod +x create-npm-repos.sh ./create-npm-repos.sh 配置 NPM 客户端 # # 配置 NPM 使用 Nexus 仓库 npm config set registry http://your-nexus-server:8081/repository/npm-group/ # 配置认证 npm login --registry=http://your-nexus-server:8081/repository/npm-hosted/ 监控与告警 # 健康检查监控 # 创建监控脚本 # cat \u0026gt; nexus-monitor.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash NEXUS_URL=\u0026#34;http://localhost:8081\u0026#34; CONTAINER_NAME=\u0026#34;nexus3\u0026#34; LOG_FILE=\u0026#34;/var/log/nexus-monitor.log\u0026#34; # 日志函数 log() { echo \u0026#34;[$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;)] $1\u0026#34; | tee -a $LOG_FILE } # 检查容器状态 check_container() { if docker ps --filter \u0026#34;name=$CONTAINER_NAME\u0026#34; --format \u0026#34;{{.Names}}\u0026#34; | grep -q $CONTAINER_NAME; then log \u0026#34;✓ Nexus3 容器运行正常\u0026#34; return 0 else log \u0026#34;✗ Nexus3 容器未运行\u0026#34; return 1 fi } # 检查 Web 服务 check_web_service() { local response_code=$(curl -s -o /dev/null -w \u0026#34;%{http_code}\u0026#34; $NEXUS_URL) if [ \u0026#34;$response_code\u0026#34; = \u0026#34;200\u0026#34; ]; then log \u0026#34;✓ Nexus3 Web 服务响应正常\u0026#34; return 0 else log \u0026#34;✗ Nexus3 Web 服务响应异常 (HTTP $response_code)\u0026#34; return 1 fi } # 检查 API 服务 check_api_service() { local api_response=$(curl -s \u0026#34;$NEXUS_URL/service/rest/v1/status\u0026#34;) if echo \u0026#34;$api_response\u0026#34; | grep -q \u0026#34;available\u0026#34;; then log \u0026#34;✓ Nexus3 API 服务正常\u0026#34; return 0 else log \u0026#34;✗ Nexus3 API 服务异常\u0026#34; return 1 fi } # 检查磁盘空间 check_disk_space() { local usage=$(df /data/nexus3 | awk \u0026#39;NR==2 {print $5}\u0026#39; | sed \u0026#39;s/%//\u0026#39;) if [ $usage -lt 80 ]; then log \u0026#34;✓ 磁盘空间充足 (${usage}%)\u0026#34; return 0 elif [ $usage -lt 90 ]; then log \u0026#34;⚠ 磁盘空间不足 (${usage}%)\u0026#34; return 1 else log \u0026#34;✗ 磁盘空间严重不足 (${usage}%)\u0026#34; return 2 fi } # 检查内存使用 check_memory_usage() { local memory_usage=$(docker stats $CONTAINER_NAME --no-stream --format \u0026#34;{{.MemPerc}}\u0026#34; | sed \u0026#39;s/%//\u0026#39;) if (( $(echo \u0026#34;$memory_usage \u0026lt; 80\u0026#34; | bc -l) )); then log \u0026#34;✓ 内存使用正常 (${memory_usage}%)\u0026#34; return 0 else log \u0026#34;⚠ 内存使用较高 (${memory_usage}%)\u0026#34; return 1 fi } # 发送告警 send_alert() { local message=\u0026#34;$1\u0026#34; local severity=\u0026#34;$2\u0026#34; # 邮件告警 if command -v mail \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;$message\u0026#34; | mail -s \u0026#34;Nexus3 监控告警 [$severity]\u0026#34; admin@your-domain.com fi # 钉钉告警 if [ -n \u0026#34;$DINGTALK_WEBHOOK\u0026#34; ]; then curl -X POST \u0026#34;$DINGTALK_WEBHOOK\u0026#34; \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#34;{\\\u0026#34;msgtype\\\u0026#34;: \\\u0026#34;text\\\u0026#34;, \\\u0026#34;text\\\u0026#34;: {\\\u0026#34;content\\\u0026#34;: \\\u0026#34;Nexus3 告警: $message\\\u0026#34;}}\u0026#34; fi } # 主监控函数 main() { log \u0026#34;=== Nexus3 监控检查开始 ===\u0026#34; local issues=0 local critical_issues=0 check_container || ((issues++)) check_web_service || ((issues++)) check_api_service || ((issues++)) check_disk_space local disk_status=$? if [ $disk_status -eq 2 ]; then ((critical_issues++)) send_alert \u0026#34;Nexus3 磁盘空间严重不足\u0026#34; \u0026#34;CRITICAL\u0026#34; elif [ $disk_status -eq 1 ]; then ((issues++)) fi check_memory_usage || ((issues++)) if [ $critical_issues -gt 0 ]; then log \u0026#34;✗ 发现 $critical_issues 个严重问题\u0026#34; send_alert \u0026#34;Nexus3 监控发现 $critical_issues 个严重问题\u0026#34; \u0026#34;CRITICAL\u0026#34; elif [ $issues -gt 0 ]; then log \u0026#34;⚠ 发现 $issues 个问题\u0026#34; send_alert \u0026#34;Nexus3 监控发现 $issues 个问题\u0026#34; \u0026#34;WARNING\u0026#34; else log \u0026#34;✓ 所有检查通过\u0026#34; fi log \u0026#34;=== Nexus3 监控检查完成 ===\u0026#34; } main \u0026#34;$@\u0026#34; EOF chmod +x nexus-monitor.sh # 设置定时监控 echo \u0026#34;*/5 * * * * /data/nexus3/nexus-monitor.sh\u0026#34; | crontab - Prometheus 监控集成 # 启用 Nexus3 指标 # # 在 docker-compose.yml 中添加 JMX 监控 cat \u0026gt;\u0026gt; docker-compose.yml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; environment: - INSTALL4J_ADD_VM_PARAMS=-Xms4g -Xmx4g -XX:MaxDirectMemorySize=6g -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false ports: - \u0026#34;9999:9999\u0026#34; # JMX 端口 EOF Prometheus 配置 # # prometheus.yml global: scrape_interval: 15s scrape_configs: - job_name: \u0026#39;nexus3\u0026#39; static_configs: - targets: [\u0026#39;nexus3:9999\u0026#39;] metrics_path: \u0026#39;/metrics\u0026#39; scrape_interval: 30s 备份与恢复 # 数据备份策略 # 完整备份脚本 # cat \u0026gt; nexus-backup.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash BACKUP_DIR=\u0026#34;/data/nexus3/backup\u0026#34; RETENTION_DAYS=30 DATE=$(date +%Y%m%d_%H%M%S) NEXUS_DATA_DIR=\u0026#34;/data/nexus3/data\u0026#34; echo \u0026#34;=== Nexus3 备份脚本 ===\u0026#34; echo \u0026#34;开始时间: $(date)\u0026#34; # 创建备份目录 mkdir -p $BACKUP_DIR # 停止 Nexus3 服务（可选，用于一致性备份） read -p \u0026#34;是否停止 Nexus3 服务进行一致性备份？(y/N): \u0026#34; STOP_SERVICE if [[ $STOP_SERVICE =~ ^[Yy]$ ]]; then echo \u0026#34;停止 Nexus3 服务...\u0026#34; docker-compose stop nexus3 RESTART_NEEDED=true fi # 创建数据备份 echo \u0026#34;创建数据备份...\u0026#34; tar -czf \u0026#34;$BACKUP_DIR/nexus-data-$DATE.tar.gz\u0026#34; \\ --exclude=\u0026#34;$NEXUS_DATA_DIR/log/*\u0026#34; \\ --exclude=\u0026#34;$NEXUS_DATA_DIR/tmp/*\u0026#34; \\ -C \u0026#34;$(dirname $NEXUS_DATA_DIR)\u0026#34; \\ \u0026#34;$(basename $NEXUS_DATA_DIR)\u0026#34; # 备份 Docker Compose 配置 echo \u0026#34;备份配置文件...\u0026#34; cp docker-compose.yml \u0026#34;$BACKUP_DIR/docker-compose-$DATE.yml\u0026#34; # 重启服务（如果之前停止了） if [[ $RESTART_NEEDED == true ]]; then echo \u0026#34;重启 Nexus3 服务...\u0026#34; docker-compose up -d nexus3 fi # 验证备份 if [ -f \u0026#34;$BACKUP_DIR/nexus-data-$DATE.tar.gz\u0026#34; ]; then BACKUP_SIZE=$(du -h \u0026#34;$BACKUP_DIR/nexus-data-$DATE.tar.gz\u0026#34; | cut -f1) echo \u0026#34;✓ 备份完成: nexus-data-$DATE.tar.gz ($BACKUP_SIZE)\u0026#34; else echo \u0026#34;✗ 备份失败\u0026#34; exit 1 fi # 清理过期备份 echo \u0026#34;清理过期备份...\u0026#34; find $BACKUP_DIR -name \u0026#34;nexus-data-*.tar.gz\u0026#34; -mtime +$RETENTION_DAYS -delete find $BACKUP_DIR -name \u0026#34;docker-compose-*.yml\u0026#34; -mtime +$RETENTION_DAYS -delete echo \u0026#34;=== 备份完成 ===\u0026#34; EOF chmod +x nexus-backup.sh # 设置定时备份 echo \u0026#34;0 2 * * * /data/nexus3/nexus-backup.sh\u0026#34; | crontab - 数据恢复 # 恢复脚本 # cat \u0026gt; nexus-restore.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash BACKUP_DIR=\u0026#34;/data/nexus3/backup\u0026#34; NEXUS_DATA_DIR=\u0026#34;/data/nexus3/data\u0026#34; echo \u0026#34;=== Nexus3 数据恢复脚本 ===\u0026#34; # 列出可用备份 echo \u0026#34;可用的备份文件:\u0026#34; ls -la $BACKUP_DIR/nexus-data-*.tar.gz | nl # 选择备份文件 read -p \u0026#34;请输入要恢复的备份文件编号: \u0026#34; BACKUP_NUM BACKUP_FILE=$(ls $BACKUP_DIR/nexus-data-*.tar.gz | sed -n \u0026#34;${BACKUP_NUM}p\u0026#34;) if [ ! -f \u0026#34;$BACKUP_FILE\u0026#34; ]; then echo \u0026#34;备份文件不存在: $BACKUP_FILE\u0026#34; exit 1 fi echo \u0026#34;选择的备份文件: $BACKUP_FILE\u0026#34; read -p \u0026#34;确认恢复？这将覆盖现有数据 (yes/no): \u0026#34; CONFIRM if [ \u0026#34;$CONFIRM\u0026#34; != \u0026#34;yes\u0026#34; ]; then echo \u0026#34;恢复已取消\u0026#34; exit 0 fi # 停止 Nexus3 服务 echo \u0026#34;停止 Nexus3 服务...\u0026#34; docker-compose stop nexus3 # 备份当前数据 echo \u0026#34;备份当前数据...\u0026#34; mv $NEXUS_DATA_DIR ${NEXUS_DATA_DIR}.backup.$(date +%Y%m%d_%H%M%S) # 恢复数据 echo \u0026#34;恢复数据...\u0026#34; mkdir -p $(dirname $NEXUS_DATA_DIR) tar -xzf \u0026#34;$BACKUP_FILE\u0026#34; -C $(dirname $NEXUS_DATA_DIR) # 修复权限 echo \u0026#34;修复权限...\u0026#34; chown -R 200:200 $NEXUS_DATA_DIR # 启动 Nexus3 服务 echo \u0026#34;启动 Nexus3 服务...\u0026#34; docker-compose up -d nexus3 echo \u0026#34;✓ 数据恢复完成\u0026#34; echo \u0026#34;请等待服务启动并验证功能\u0026#34; EOF chmod +x nexus-restore.sh 故障排除 # 常见问题诊断 # 启动问题 # cat \u0026gt; diagnose-nexus.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== Nexus3 故障诊断 ===\u0026#34; # 检查容器状态 echo \u0026#34;1. 检查容器状态\u0026#34; docker ps -a --filter \u0026#34;name=nexus3\u0026#34; # 检查容器日志 echo -e \u0026#34;\\n2. 检查容器日志\u0026#34; docker logs --tail 50 nexus3 # 检查磁盘空间 echo -e \u0026#34;\\n3. 检查磁盘空间\u0026#34; df -h /data/nexus3 # 检查内存使用 echo -e \u0026#34;\\n4. 检查内存使用\u0026#34; free -h # 检查网络连接 echo -e \u0026#34;\\n5. 检查网络连接\u0026#34; netstat -tlnp | grep -E \u0026#34;(8081|8082|8083)\u0026#34; # 检查 Java 进程 echo -e \u0026#34;\\n6. 检查 Java 进程\u0026#34; docker exec nexus3 ps aux | grep java echo -e \u0026#34;\\n=== 诊断完成 ===\u0026#34; EOF chmod +x diagnose-nexus.sh 总结 # 部署优势 # 通过本指南，您可以成功部署一个企业级的 Nexus3 制品仓库平台，具有以下优势：\n技术优势 # 统一制品管理：支持 20+ 种包格式的统一管理 高性能架构：基于现代化架构，支持大规模并发访问 企业级安全：细粒度权限控制、LDAP 集成 智能代理：缓存外部仓库，提供离线访问能力 可扩展性：支持集群部署和水平扩展 运维优势 # 容器化部署：标准化部署流程，环境一致性好 自动化备份：完整的备份恢复策略 监控告警：全面的监控和告警体系 故障排除：完善的诊断和恢复机制 最佳实践 # 生产环境建议 # 资源规划：根据制品数量和用户规模合理规划资源 安全配置：启用 HTTPS、配置防火墙、定期安全审计 备份策略：建立完善的备份和灾难恢复计划 监控告警：部署全面的监控和告警体系 性能调优：根据使用情况持续优化 JVM 参数 扩展建议 # 高可用部署：配置多节点集群和负载均衡 对象存储集成：使用 S3 兼容存储降低成本 CI/CD 集成：与 Jenkins、GitLab CI 等工具深度集成 安全扫描：集成漏洞扫描和合规性检查 持续改进 # Nexus3 作为制品管理平台的核心，需要持续优化和改进：\n定期更新：保持 Nexus3 版本的及时更新 性能监控：持续监控系统性能和用户体验 安全审计：定期进行安全检查和漏洞扫描 用户培训：提供制品管理最佳实践培训 通过本指南的配置和最佳实践，您可以构建一个稳定、高效、安全的企业级制品仓库平台，为团队的软件开发和交付提供强有力的支撑。\n配置 Nginx 反向代理 # 为了提供更好的访问体验和安全性，我们使用 Nginx 作为 Nexus3 的反向代理。这样可以实现：\n统一的访问入口 SSL/TLS 终端 负载均衡（多实例部署时） 访问日志记录 第一步：安装和配置 Nginx # 安装 Nginx # # 安装 Nginx yum install -y nginx # 创建专用用户 useradd -M -s /sbin/nologin www # 备份原始配置 cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup 优化 Nginx 主配置 # 创建针对 Nexus3 优化的 Nginx 配置：\ncat \u0026gt; /etc/nginx/nginx.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Nginx 主配置文件 - 针对 Nexus3 优化 user www www; worker_processes auto; # 错误日志配置 error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; # 工作进程文件描述符限制 worker_rlimit_nofile 65535; events { # 使用 epoll 事件模型（Linux 推荐） use epoll; worker_connections 8192; multi_accept on; accept_mutex off; } http { # 基础配置 include /etc/nginx/mime.types; default_type application/octet-stream; # 服务器标识 server_tokens off; # 哈希表大小 server_names_hash_bucket_size 128; server_names_hash_max_size 512; # 客户端请求配置 client_header_buffer_size 32k; large_client_header_buffers 4 32k; client_max_body_size 0; # 允许大文件上传 client_body_buffer_size 128k; client_body_timeout 60s; client_header_timeout 60s; # 发送文件优化 sendfile on; tcp_nopush on; tcp_nodelay on; # 连接超时设置 keepalive_timeout 65; keepalive_requests 1000; # 代理配置 proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; proxy_buffer_size 64k; proxy_buffers 4 64k; proxy_busy_buffers_size 128k; proxy_temp_file_write_size 128k; proxy_intercept_errors off; # Gzip 压缩配置 gzip on; gzip_vary on; gzip_min_length 1024; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/atom+xml image/svg+xml; gzip_disable \u0026#34;MSIE [1-6]\\.\u0026#34;; # 日志格式定义 log_format main \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; log_format json_combined escape=json \u0026#39;{\u0026#39; \u0026#39;\u0026#34;time_local\u0026#34;:\u0026#34;$time_local\u0026#34;,\u0026#39; \u0026#39;\u0026#34;remote_addr\u0026#34;:\u0026#34;$remote_addr\u0026#34;,\u0026#39; \u0026#39;\u0026#34;remote_user\u0026#34;:\u0026#34;$remote_user\u0026#34;,\u0026#39; \u0026#39;\u0026#34;request\u0026#34;:\u0026#34;$request\u0026#34;,\u0026#39; \u0026#39;\u0026#34;status\u0026#34;: \u0026#34;$status\u0026#34;,\u0026#39; \u0026#39;\u0026#34;body_bytes_sent\u0026#34;:\u0026#34;$body_bytes_sent\u0026#34;,\u0026#39; \u0026#39;\u0026#34;request_time\u0026#34;:\u0026#34;$request_time\u0026#34;,\u0026#39; \u0026#39;\u0026#34;http_referrer\u0026#34;:\u0026#34;$http_referer\u0026#34;,\u0026#39; \u0026#39;\u0026#34;http_user_agent\u0026#34;:\u0026#34;$http_user_agent\u0026#34;,\u0026#39; \u0026#39;\u0026#34;http_x_forwarded_for\u0026#34;:\u0026#34;$http_x_forwarded_for\u0026#34;,\u0026#39; \u0026#39;\u0026#34;upstream_addr\u0026#34;:\u0026#34;$upstream_addr\u0026#34;,\u0026#39; \u0026#39;\u0026#34;upstream_status\u0026#34;:\u0026#34;$upstream_status\u0026#34;,\u0026#39; \u0026#39;\u0026#34;upstream_response_time\u0026#34;:\u0026#34;$upstream_response_time\u0026#34;\u0026#39; \u0026#39;}\u0026#39;; # 默认访问日志 access_log /var/log/nginx/access.log main; # 包含虚拟主机配置 include /etc/nginx/conf.d/*.conf; } EOF # 测试配置文件语法 nginx -t 第二步：配置 Nexus3 虚拟主机 # 创建 Nexus3 专用配置 # cat \u0026gt; /etc/nginx/conf.d/nexus3.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Nexus3 反向代理配置 # 上游服务器定义 upstream nexus_web { server 127.0.0.1:8081 max_fails=3 fail_timeout=30s; keepalive 32; } upstream nexus_docker_read { server 127.0.0.1:8082 max_fails=3 fail_timeout=30s; keepalive 32; } upstream nexus_docker_write { server 127.0.0.1:8083 max_fails=3 fail_timeout=30s; keepalive 32; } # Docker 仓库配置 server { listen 80; server_name docker.example.com; # 请修改为您的域名 # 访问日志 access_log /var/log/nginx/docker.example.com.log json_combined; error_log /var/log/nginx/docker.example.com.error.log; # 客户端配置 client_max_body_size 0; chunked_transfer_encoding on; # 根据请求方法选择上游服务器 set $upstream \u0026#34;nexus_docker_write\u0026#34;; if ($request_method ~ ^(GET|HEAD)$ ) { set $upstream \u0026#34;nexus_docker_read\u0026#34;; } # Docker API 搜索请求特殊处理 if ($request_uri ~ \u0026#39;/v2/_catalog\u0026#39; ) { set $upstream \u0026#34;nexus_docker_write\u0026#34;; } if ($request_uri ~ \u0026#39;/v2/search\u0026#39; ) { set $upstream \u0026#34;nexus_docker_write\u0026#34;; } location / { proxy_pass http://$upstream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 禁用缓冲以支持大文件上传 proxy_buffering off; proxy_request_buffering off; # 超时设置 proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; # 支持 WebSocket（如果需要） proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; } # 健康检查端点 location /health { access_log off; return 200 \u0026#34;healthy\\n\u0026#34;; add_header Content-Type text/plain; } } # Nexus3 Web 管理界面配置 server { listen 80; server_name nexus.example.com; # 请修改为您的域名 # 访问日志 access_log /var/log/nginx/nexus.example.com.log json_combined; error_log /var/log/nginx/nexus.example.com.error.log; # 客户端配置 client_max_body_size 0; proxy_max_temp_file_size 0; location / { proxy_pass http://nexus_web; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 超时设置 proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; # 支持 WebSocket proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; } # 静态文件下载目录（可选） location /download { alias /application/download; autoindex on; autoindex_exact_size off; autoindex_localtime on; } } EOF 第三步：启动和验证 Nginx # # 启动 Nginx 服务 systemctl start nginx systemctl enable nginx # 检查服务状态 systemctl status nginx # 验证配置 nginx -t # 重新加载配置（如果需要修改） nginx -s reload # 检查端口监听 netstat -tlnp | grep nginx SSL/TLS 配置（推荐） # 使用 Let\u0026rsquo;s Encrypt 免费证书 # # 安装 Certbot yum install -y epel-release yum install -y certbot python2-certbot-nginx # 获取证书 certbot --nginx -d nexus.example.com -d docker.example.com # 设置自动续期 echo \u0026#34;0 12 * * * /usr/bin/certbot renew --quiet\u0026#34; | crontab - 手动 SSL 配置示例 # server { listen 443 ssl http2; server_name nexus.example.com; # SSL 证书配置 ssl_certificate /etc/ssl/certs/nexus.example.com.crt; ssl_certificate_key /etc/ssl/private/nexus.example.com.key; # SSL 安全配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 其他配置同 HTTP 版本 # ... } 配置 Docker 私有仓库 # Docker 私有仓库是 Nexus3 最常用的功能之一。我们需要创建三种类型的仓库来实现完整的 Docker 镜像管理。\n第一步：创建 Blob 存储 # Blob 存储是 Nexus3 中用于存储二进制文件的底层存储机制。\n创建 Docker 专用 Blob 存储 # 登录 Nexus3 管理界面\n访问：http://your-server:8081 使用管理员账户登录 导航到 Blob Stores\n点击左侧菜单：Repository → Blob Stores 创建新的 Blob Store\n点击 Create Blob Store 选择 File 类型 配置参数： 参数 值 说明 Name docker-blob Blob 存储名称 Path docker 存储路径（相对于 nexus-data） 图：创建 Docker 专用的 Blob 存储配置界面\n图：Blob 存储的详细配置参数\n第二步：创建 Docker Hosted 仓库 # Hosted 仓库用于存储企业内部构建的 Docker 镜像。\n配置步骤 # 导航到仓库管理\n点击：Repository → Repositories 创建新仓库\n点击 Create repository 选择 docker (hosted) 配置 Hosted 仓库参数\n参数 值 说明 Name docker-hosted 仓库名称 HTTP Port 8083 推送端口 Enable Docker V1 API ☑️ 支持 Docker V1 API Blob store docker-blob 使用前面创建的 Blob 存储 Deployment policy Allow redeploy 允许重新部署 图：选择 Docker Hosted 仓库类型\n图：Docker Hosted 仓库的基本配置\n图：Docker Hosted 仓库的高级配置选项\n第三步：创建 Docker Proxy 仓库 # Proxy 仓库用于代理外部的 Docker 镜像仓库，提供缓存和加速功能。\n配置步骤 # 创建 Proxy 仓库\n选择 docker (proxy) 配置 Proxy 仓库参数\n参数 值 说明 Name docker-proxy 仓库名称 HTTP Port 8082 拉取端口 Remote storage https://registry-1.docker.io Docker Hub 官方地址 Docker Index Use Docker Hub 使用 Docker Hub 索引 Blob store docker-blob 使用相同的 Blob 存储 推荐的代理地址：\nDocker Hub: https://registry-1.docker.io 阿里云镜像: https://7bezldxe.mirror.aliyuncs.com 腾讯云镜像: https://mirror.ccs.tencentyun.com 网易云镜像: https://hub-mirror.c.163.com 图：选择 Docker Proxy 仓库类型\n图：Docker Proxy 仓库的远程存储配置\n图：Docker Proxy 仓库的高级配置选项\n第四步：创建 Docker Group 仓库 # Group 仓库将多个仓库组合成一个统一的访问入口。\n配置步骤 # 创建 Group 仓库\n选择 docker (group) 配置 Group 仓库参数\n参数 值 说明 Name docker-group 仓库名称 HTTP Port 8082 统一访问端口 Blob store docker-blob 使用相同的 Blob 存储 Member repositories docker-hosted, docker-proxy 包含的仓库 重要提示: 仓库的顺序决定了查找优先级，建议将 docker-hosted 放在前面。\n图：选择 Docker Group 仓库类型\n图：Docker Group 仓库的成员配置\n第五步：启用 Docker Bearer Token # Docker 仓库需要启用 Bearer Token 认证才能正常工作。\n配置步骤 # 导航到安全设置\n点击：Security → Realms 启用 Docker Bearer Token Realm\n将 Docker Bearer Token Realm 从左侧移动到右侧 点击 Save 保存配置 图：安全域配置界面\n图：启用 Docker Bearer Token Realm\n第六步：验证配置 # 检查端口监听 # # 检查 Nexus3 端口是否正常监听 netstat -tlnp | grep -E \u0026#34;8081|8082|8083\u0026#34; # 预期输出： # tcp 0 0 0.0.0.0:8081 0.0.0.0:* LISTEN 1393/java # tcp 0 0 0.0.0.0:8082 0.0.0.0:* LISTEN 1393/java # tcp 0 0 0.0.0.0:8083 0.0.0.0:* LISTEN 1393/java 测试仓库连通性 # # 测试 Web 界面 curl -I http://localhost:8081 # 测试 Docker 仓库 curl -I http://localhost:8082/v2/ # 预期返回 401 Unauthorized（需要认证） 第七步：查看配置结果 # 完成所有配置后，您应该能在 Nexus3 管理界面中看到以下仓库：\n图：完成配置后的 Docker 仓库列表\nDocker 客户端配置和测试 # 第一步：配置 Docker 客户端 # 配置 Docker Daemon # 在需要使用私有仓库的客户端机器上配置 Docker：\n# 创建或编辑 Docker 配置文件 sudo mkdir -p /etc/docker cat \u0026gt; /etc/docker/daemon.json \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; { \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34;, \u0026#34;max-file\u0026#34;: \u0026#34;3\u0026#34; }, \u0026#34;max-concurrent-downloads\u0026#34;: 10, \u0026#34;max-concurrent-uploads\u0026#34;: 10, \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34;, \u0026#34;storage-opts\u0026#34;: [ \u0026#34;overlay2.override_kernel_check=true\u0026#34; ], \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;http://docker.example.com\u0026#34; ], \u0026#34;insecure-registries\u0026#34;: [ \u0026#34;docker.example.com\u0026#34;, \u0026#34;nexus.example.com:8082\u0026#34;, \u0026#34;nexus.example.com:8083\u0026#34; ] } EOF 配置说明:\nregistry-mirrors: 配置镜像加速器 insecure-registries: 允许使用 HTTP 协议的私有仓库 max-concurrent-downloads/uploads: 并发下载/上传数量 配置 DNS 解析 # 如果使用域名访问，需要配置 DNS 解析：\n# 方法一：修改 /etc/hosts 文件 echo \u0026#34;192.168.1.100 docker.example.com nexus.example.com\u0026#34; \u0026gt;\u0026gt; /etc/hosts # 方法二：配置 DNS 服务器（推荐生产环境） # 在您的 DNS 服务器中添加 A 记录 第二步：重启 Docker 服务 # # 重启 Docker 服务 sudo systemctl restart docker # 检查服务状态 sudo systemctl status docker # 验证配置 docker info | grep -A 10 \u0026#34;Registry Mirrors\u0026#34; 第三步：登录私有仓库 # # 登录到私有仓库 docker login docker.example.com -u admin # 输入密码（您在初始化时设置的密码） # 成功后会显示：Login Succeeded 第四步：测试镜像推送和拉取 # 推送镜像测试 # # 1. 拉取一个公共镜像 docker pull alpine:latest # 2. 为镜像打标签 docker tag alpine:latest docker.example.com/alpine:latest # 3. 推送到私有仓库 docker push docker.example.com/alpine:latest # 4. 验证推送结果 echo \u0026#34;推送完成，可以在 Nexus3 Web 界面查看\u0026#34; 拉取镜像测试 # # 1. 删除本地镜像 docker rmi docker.example.com/alpine:latest docker rmi alpine:latest # 2. 从私有仓库拉取 docker pull docker.example.com/alpine:latest # 3. 验证拉取结果 docker images | grep alpine 图：成功推送镜像到私有仓库的示例\n第五步：批量镜像迁移 # 创建镜像迁移脚本 # cat \u0026gt; /usr/local/bin/docker-migrate.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # Docker 镜像批量迁移脚本 PRIVATE_REGISTRY=\u0026#34;docker.example.com\u0026#34; IMAGES_FILE=\u0026#34;/tmp/images.txt\u0026#34; # 获取本地所有镜像 docker images --format \u0026#34;table {{.Repository}}:{{.Tag}}\u0026#34; | grep -v \u0026#34;REPOSITORY:TAG\u0026#34; \u0026gt; $IMAGES_FILE echo \u0026#34;开始迁移镜像到私有仓库...\u0026#34; while IFS= read -r image; do if [[ \u0026#34;$image\u0026#34; != *\u0026#34;$PRIVATE_REGISTRY\u0026#34;* ]]; then echo \u0026#34;处理镜像: $image\u0026#34; # 为镜像打标签 new_tag=\u0026#34;$PRIVATE_REGISTRY/$image\u0026#34; docker tag \u0026#34;$image\u0026#34; \u0026#34;$new_tag\u0026#34; # 推送到私有仓库 if docker push \u0026#34;$new_tag\u0026#34;; then echo \u0026#34;✅ 成功推送: $new_tag\u0026#34; else echo \u0026#34;❌ 推送失败: $new_tag\u0026#34; fi # 删除临时标签 docker rmi \u0026#34;$new_tag\u0026#34; 2\u0026gt;/dev/null fi done \u0026lt; \u0026#34;$IMAGES_FILE\u0026#34; echo \u0026#34;镜像迁移完成！\u0026#34; rm -f \u0026#34;$IMAGES_FILE\u0026#34; EOF chmod +x /usr/local/bin/docker-migrate.sh 使用迁移脚本 # # 执行镜像迁移 /usr/local/bin/docker-migrate.sh 故障排除 # 常见问题及解决方案 # 问题 可能原因 解决方案 x509: certificate signed by unknown authority SSL 证书问题 添加到 insecure-registries 或配置正确的证书 dial tcp: lookup docker.example.com: no such host DNS 解析失败 检查 DNS 配置或 /etc/hosts 文件 unauthorized: authentication required 认证失败 检查用户名密码，确保已启用 Docker Bearer Token denied: requested access to the resource is denied 权限不足 检查用户权限和仓库访问策略 调试命令 # # 查看 Docker 配置 docker info # 测试网络连通性 telnet docker.example.com 80 # 查看 Docker 日志 journalctl -u docker.service -f # 测试仓库 API curl -v http://docker.example.com/v2/ Nexus3 版本升级指南 # Nexus3 的定期升级是维护系统安全性和稳定性的重要措施。Sonatype 官方建议定期更新 Nexus3 版本以获得最新的安全补丁和功能改进。\n升级前准备 # 第一步：备份数据 # 重要提示: 升级前必须备份数据，以防升级失败时能够快速恢复。\n# 停止 Nexus3 服务 docker stop nexus3 # 创建数据备份 BACKUP_DIR=\u0026#34;/backup/nexus3-$(date +%Y%m%d-%H%M%S)\u0026#34; mkdir -p \u0026#34;$BACKUP_DIR\u0026#34; # 备份数据目录 cp -r /application/nexus3/data \u0026#34;$BACKUP_DIR/\u0026#34; # 备份 Docker 配置 docker inspect nexus3 \u0026gt; \u0026#34;$BACKUP_DIR/container-config.json\u0026#34; # 创建压缩备份（可选） tar -czf \u0026#34;$BACKUP_DIR.tar.gz\u0026#34; -C /application/nexus3 data echo \u0026#34;备份完成: $BACKUP_DIR\u0026#34; 第二步：检查版本兼容性 # # 查看当前版本 docker exec nexus3 cat /opt/sonatype/nexus/VERSION 2\u0026gt;/dev/null || echo \u0026#34;容器未运行\u0026#34; # 查看可用版本 curl -s https://registry.hub.docker.com/v2/repositories/sonatype/nexus3/tags/ | \\ jq -r \u0026#39;.results[].name\u0026#39; | head -10 版本选择建议:\n优先选择 LTS（长期支持）版本 避免跨越多个大版本升级 查看 官方发布说明 了解重大变更 第三步：制定升级计划 # 升级类型 风险等级 建议策略 补丁版本 (3.27.0 → 3.27.1) 低 可直接升级 次要版本 (3.27.x → 3.28.x) 中 建议测试环境验证 主要版本 (3.x → 4.x) 高 必须详细测试和规划 升级执行 # 标准升级流程 # 以下示例将 Nexus3 从 3.28.1 升级到 3.29.2：\n#!/bin/bash # Nexus3 升级脚本 set -e # 遇到错误立即退出 # 配置变量 OLD_VERSION=\u0026#34;3.28.1\u0026#34; NEW_VERSION=\u0026#34;3.29.2\u0026#34; CONTAINER_NAME=\u0026#34;nexus3\u0026#34; BACKUP_DIR=\u0026#34;/backup/nexus3-upgrade-$(date +%Y%m%d-%H%M%S)\u0026#34; echo \u0026#34;开始 Nexus3 升级流程...\u0026#34; echo \u0026#34;当前版本: $OLD_VERSION\u0026#34; echo \u0026#34;目标版本: $NEW_VERSION\u0026#34; # 1. 创建备份 echo \u0026#34;步骤 1: 创建数据备份...\u0026#34; mkdir -p \u0026#34;$BACKUP_DIR\u0026#34; docker stop \u0026#34;$CONTAINER_NAME\u0026#34; cp -r /application/nexus3/data \u0026#34;$BACKUP_DIR/\u0026#34; echo \u0026#34;备份完成: $BACKUP_DIR\u0026#34; # 2. 重命名旧容器 echo \u0026#34;步骤 2: 保留旧容器...\u0026#34; docker rename \u0026#34;$CONTAINER_NAME\u0026#34; \u0026#34;${CONTAINER_NAME}_${OLD_VERSION}_backup\u0026#34; # 3. 拉取新镜像 echo \u0026#34;步骤 3: 拉取新版本镜像...\u0026#34; docker pull \u0026#34;sonatype/nexus3:$NEW_VERSION\u0026#34; # 4. 启动新容器 echo \u0026#34;步骤 4: 启动新版本容器...\u0026#34; docker run -d \\ --name \u0026#34;$CONTAINER_NAME\u0026#34; \\ --restart=always \\ --ulimit nofile=65536:65536 \\ -p 8081:8081 \\ -p 8082:8082 \\ -p 8083:8083 \\ -e INSTALL4J_ADD_VM_PARAMS=\u0026#34;-Xms4g -Xmx4g -XX:MaxDirectMemorySize=6g -Djava.util.prefs.userRoot=/nexus-data/javaprefs\u0026#34; \\ -v /etc/localtime:/etc/localtime:ro \\ -v /application/nexus3/data:/nexus-data \\ \u0026#34;sonatype/nexus3:$NEW_VERSION\u0026#34; # 5. 等待服务启动 echo \u0026#34;步骤 5: 等待服务启动...\u0026#34; sleep 30 # 6. 检查服务状态 echo \u0026#34;步骤 6: 检查服务状态...\u0026#34; for i in {1..30}; do if curl -f -s http://localhost:8081/service/rest/v1/status \u0026gt; /dev/null; then echo \u0026#34;✅ Nexus3 服务启动成功\u0026#34; break fi echo \u0026#34;等待服务启动... ($i/30)\u0026#34; sleep 10 done # 7. 验证升级结果 echo \u0026#34;步骤 7: 验证升级结果...\u0026#34; NEW_RUNNING_VERSION=$(docker exec \u0026#34;$CONTAINER_NAME\u0026#34; cat /opt/sonatype/nexus/VERSION) echo \u0026#34;当前运行版本: $NEW_RUNNING_VERSION\u0026#34; if [[ \u0026#34;$NEW_RUNNING_VERSION\u0026#34; == \u0026#34;$NEW_VERSION\u0026#34; ]]; then echo \u0026#34;✅ 升级成功！\u0026#34; echo \u0026#34;可以通过以下命令删除备份容器:\u0026#34; echo \u0026#34;docker rm ${CONTAINER_NAME}_${OLD_VERSION}_backup\u0026#34; else echo \u0026#34;❌ 升级失败，版本不匹配\u0026#34; exit 1 fi 手动升级步骤 # 如果不使用脚本，可以按以下步骤手动执行：\n# 1. 停止当前容器 docker stop nexus3 # 2. 重命名容器（保留备份） docker rename nexus3 nexus3_backup # 3. 拉取新版本镜像 docker pull sonatype/nexus3:3.29.2 # 4. 启动新容器 docker run -d \\ --name nexus3 \\ --restart=always \\ --ulimit nofile=65536:65536 \\ -p 8081:8081 \\ -p 8082:8082 \\ -p 8083:8083 \\ -e INSTALL4J_ADD_VM_PARAMS=\u0026#34;-Xms4g -Xmx4g -XX:MaxDirectMemorySize=6g\u0026#34; \\ -v /etc/localtime:/etc/localtime:ro \\ -v /application/nexus3/data:/nexus-data \\ sonatype/nexus3:3.29.2 # 5. 监控启动过程 docker logs -f nexus3 升级后验证 # 功能验证清单 # # 1. 检查服务状态 curl -I http://localhost:8081 # 2. 验证 Web 界面访问 curl -s http://localhost:8081 | grep -q \u0026#34;Nexus Repository Manager\u0026#34; # 3. 检查仓库列表 curl -u admin:password http://localhost:8081/service/rest/v1/repositories # 4. 测试 Docker 仓库 docker pull alpine:latest docker tag alpine:latest localhost:8082/alpine:test docker push localhost:8083/alpine:test # 5. 验证数据完整性 # 检查关键仓库和配置是否正常 性能监控 # # 监控容器资源使用 docker stats nexus3 # 检查日志中的错误 docker logs nexus3 | grep -i error # 监控磁盘使用 df -h /application/nexus3/data 图：Nexus3 升级成功后的日志输出\n回滚策略 # 如果升级失败，可以快速回滚到之前的版本：\n#!/bin/bash # Nexus3 回滚脚本 echo \u0026#34;开始回滚 Nexus3...\u0026#34; # 1. 停止新容器 docker stop nexus3 docker rm nexus3 # 2. 恢复数据（如果需要） # cp -r /backup/nexus3-backup/data/* /application/nexus3/data/ # 3. 启动备份容器 docker rename nexus3_backup nexus3 docker start nexus3 # 4. 验证回滚结果 sleep 30 if curl -f -s http://localhost:8081/service/rest/v1/status \u0026gt; /dev/null; then echo \u0026#34;✅ 回滚成功\u0026#34; else echo \u0026#34;❌ 回滚失败，请检查日志\u0026#34; fi 升级最佳实践 # 1. 定期升级策略 # # 创建升级检查脚本 cat \u0026gt; /usr/local/bin/nexus-version-check.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # 检查 Nexus3 新版本 CURRENT_VERSION=$(docker exec nexus3 cat /opt/sonatype/nexus/VERSION 2\u0026gt;/dev/null) LATEST_VERSION=$(curl -s https://api.github.com/repos/sonatype/nexus-public/releases/latest | jq -r \u0026#39;.tag_name\u0026#39; | sed \u0026#39;s/release-//\u0026#39;) echo \u0026#34;当前版本: $CURRENT_VERSION\u0026#34; echo \u0026#34;最新版本: $LATEST_VERSION\u0026#34; if [[ \u0026#34;$CURRENT_VERSION\u0026#34; != \u0026#34;$LATEST_VERSION\u0026#34; ]]; then echo \u0026#34;⚠️ 发现新版本，建议升级\u0026#34; else echo \u0026#34;✅ 已是最新版本\u0026#34; fi EOF chmod +x /usr/local/bin/nexus-version-check.sh # 设置定期检查 echo \u0026#34;0 9 * * 1 /usr/local/bin/nexus-version-check.sh | mail -s \u0026#39;Nexus3 版本检查\u0026#39; admin@example.com\u0026#34; | crontab - 2. 升级注意事项 # 测试环境验证: 先在测试环境进行升级验证 维护窗口: 选择业务低峰期进行升级 通知机制: 提前通知用户升级计划 监控告警: 升级后加强监控，及时发现问题 文档记录: 记录升级过程和遇到的问题 3. 清理工作 # 升级成功并稳定运行一段时间后，可以清理备份：\n# 删除备份容器（确认升级成功后） docker rm nexus3_backup # 清理旧镜像 docker image prune -f # 清理过期备份（保留最近3个备份） find /backup -name \u0026#34;nexus3-*\u0026#34; -type d | sort -r | tail -n +4 | xargs rm -rf 其他类型私有仓库配置 # Nexus3 支持多种包管理格式，可以为企业提供统一的制品管理平台。以下介绍几种常用的私有仓库配置方法。\n官方文档: Nexus3 支持的仓库格式\nYum 私有仓库配置 # Yum 私有仓库可以为 CentOS/RHEL 系统提供 RPM 包管理服务，特别适合企业内网环境。\n第一步：创建 Yum Blob 存储 # 配置 Blob Store # 导航到 Blob Stores\n点击：Repository → Blob Stores 创建新的 Blob Store\n点击 Create Blob Store 配置参数： 参数 值 说明 Type File 文件存储类型 Name yum-blob Blob 存储名称 Path yum 存储路径 图：创建 Yum 专用的 Blob 存储\n图：Yum Blob 存储的详细配置\n第二步：创建 Yum Hosted 仓库 # 配置 Hosted 仓库 # 创建新仓库\n点击：Repository → Repositories → Create repository 选择 yum (hosted) 配置仓库参数\n参数 值 说明 Name yum-hosted 仓库名称 Blob store yum-blob 使用创建的 Blob 存储 Deployment policy Allow redeploy 允许重新部署 Repodata Depth 3 重要: 仓库数据深度 图：选择 Yum Hosted 仓库类型\n图：Yum Hosted 仓库的详细配置\n重要配置说明 # **Repodata Depth（仓库数据深度）**是 Yum 仓库的关键配置：\n图：官方文档中关于 Repodata Depth 的说明\n深度配置对应关系:\n深度 0: /repository/yum-hosted/ 深度 1: /repository/yum-hosted/7/ 深度 2: /repository/yum-hosted/7/os/ 深度 3: /repository/yum-hosted/7/os/x86_64/ 推荐配置: 使用深度 3，符合标准的 CentOS 仓库结构。\n第三步：准备 RPM 包 # 从 CentOS ISO 提取 RPM 包 # 使用 CentOS 7 完整版 ISO 作为示例：\n# 1. 挂载 CentOS ISO 镜像 sudo mkdir -p /mnt/centos-iso sudo mount -o loop CentOS-7-x86_64-Everything-2003.iso /mnt/centos-iso # 2. 创建本地工作目录 mkdir -p /tmp/centos7-rpms cd /tmp/centos7-rpms # 3. 复制 RPM 包 cp /mnt/centos-iso/Packages/*.rpm ./ # 4. 验证 RPM 包数量 echo \u0026#34;总计 RPM 包数量: $(ls *.rpm | wc -l)\u0026#34; # 5. 卸载 ISO（可选） sudo umount /mnt/centos-iso 组织 RPM 包结构 # # 创建标准的 YUM 仓库目录结构 mkdir -p /tmp/yum-upload/{7/os/x86_64,8/os/x86_64} # 将 CentOS 7 的包放到对应目录 mv /tmp/centos7-rpms/*.rpm /tmp/yum-upload/7/os/x86_64/ # 添加自定义 RPM 包（如果有） # cp /path/to/custom/*.rpm /tmp/yum-upload/7/os/x86_64/ 第四步：批量上传 RPM 包 # 创建上传脚本 # cat \u0026gt; /usr/local/bin/yum-upload.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # Yum 仓库 RPM 包批量上传脚本 set -e # 配置变量 NEXUS_URL=\u0026#34;http://nexus.example.com:8081\u0026#34; REPOSITORY=\u0026#34;yum-hosted\u0026#34; USERNAME=\u0026#34;admin\u0026#34; PASSWORD=\u0026#34;your-password\u0026#34; RPM_DIR=\u0026#34;/tmp/yum-upload\u0026#34; # 安全提示 echo \u0026#34;⚠️ 注意：此脚本将上传 RPM 包到 Nexus3\u0026#34; echo \u0026#34;仓库: $REPOSITORY\u0026#34; echo \u0026#34;目录: $RPM_DIR\u0026#34; read -p \u0026#34;确认继续？(y/N): \u0026#34; confirm if [[ \u0026#34;$confirm\u0026#34; != \u0026#34;y\u0026#34; \u0026amp;\u0026amp; \u0026#34;$confirm\u0026#34; != \u0026#34;Y\u0026#34; ]]; then echo \u0026#34;操作已取消\u0026#34; exit 0 fi # 关闭命令历史记录（安全考虑） set +H # 统计信息 total_files=0 uploaded_files=0 failed_files=0 # 递归上传函数 upload_rpms() { local base_dir=\u0026#34;$1\u0026#34; local relative_path=\u0026#34;$2\u0026#34; for item in \u0026#34;$base_dir\u0026#34;/*; do if [[ -d \u0026#34;$item\u0026#34; ]]; then # 递归处理子目录 local dirname=$(basename \u0026#34;$item\u0026#34;) upload_rpms \u0026#34;$item\u0026#34; \u0026#34;$relative_path/$dirname\u0026#34; elif [[ -f \u0026#34;$item\u0026#34; \u0026amp;\u0026amp; \u0026#34;$item\u0026#34; == *.rpm ]]; then # 上传 RPM 文件 local filename=$(basename \u0026#34;$item\u0026#34;) local upload_path=\u0026#34;$relative_path/$filename\u0026#34; echo \u0026#34;上传: $upload_path\u0026#34; total_files=$((total_files + 1)) if curl -f -u \u0026#34;$USERNAME:$PASSWORD\u0026#34; \\ --upload-file \u0026#34;$item\u0026#34; \\ \u0026#34;$NEXUS_URL/repository/$REPOSITORY$upload_path\u0026#34;; then uploaded_files=$((uploaded_files + 1)) echo \u0026#34;✅ 成功: $filename\u0026#34; else failed_files=$((failed_files + 1)) echo \u0026#34;❌ 失败: $filename\u0026#34; fi fi done } # 开始上传 echo \u0026#34;开始上传 RPM 包...\u0026#34; upload_rpms \u0026#34;$RPM_DIR\u0026#34; \u0026#34;\u0026#34; # 输出统计信息 echo \u0026#34;\u0026#34; echo \u0026#34;上传完成！\u0026#34; echo \u0026#34;总文件数: $total_files\u0026#34; echo \u0026#34;成功上传: $uploaded_files\u0026#34; echo \u0026#34;失败数量: $failed_files\u0026#34; # 触发仓库索引重建 echo \u0026#34;触发仓库索引重建...\u0026#34; curl -X POST -u \u0026#34;$USERNAME:$PASSWORD\u0026#34; \\ \u0026#34;$NEXUS_URL/service/rest/v1/repositories/$REPOSITORY/rebuild-index\u0026#34; echo \u0026#34;✅ 索引重建已触发，请等待完成\u0026#34; EOF chmod +x /usr/local/bin/yum-upload.sh 执行上传 # # 修改脚本中的配置信息 vim /usr/local/bin/yum-upload.sh # 执行上传 /usr/local/bin/yum-upload.sh 手动上传示例 # # 设置变量 NEXUS_URL=\u0026#34;http://nexus.example.com:8081\u0026#34; REPOSITORY=\u0026#34;yum-hosted\u0026#34; USERNAME=\u0026#34;admin\u0026#34; PASSWORD=\u0026#34;your-password\u0026#34; # 上传单个 RPM 包 curl -v -u \u0026#34;$USERNAME:$PASSWORD\u0026#34; \\ --upload-file /tmp/yum-upload/7/os/x86_64/vim-enhanced-7.4.629-8.el7_9.x86_64.rpm \\ \u0026#34;$NEXUS_URL/repository/$REPOSITORY/7/os/x86_64/vim-enhanced-7.4.629-8.el7_9.x86_64.rpm\u0026#34; # 批量上传（简单版本） cd /tmp/yum-upload/7/os/x86_64 for rpm in *.rpm; do echo \u0026#34;上传: $rpm\u0026#34; curl -f -u \u0026#34;$USERNAME:$PASSWORD\u0026#34; \\ --upload-file \u0026#34;$rpm\u0026#34; \\ \u0026#34;$NEXUS_URL/repository/$REPOSITORY/7/os/x86_64/$rpm\u0026#34; done 第五步：重建仓库索引 # 上传完成后需要重建 YUM 仓库索引：\n自动重建 # # 通过 API 触发索引重建 curl -X POST -u admin:password \\ http://nexus.example.com:8081/service/rest/v1/repositories/yum-hosted/rebuild-index 手动重建 # 登录 Nexus3 管理界面 导航到仓库管理: Repository → Repositories 选择 yum-hosted 仓库 点击 \u0026ldquo;Rebuild index\u0026rdquo; 按钮 图：手动触发仓库索引重建\n图：索引重建任务的执行进度\n第六步：客户端配置 # 配置 YUM 客户端 # 在需要使用私有仓库的 CentOS/RHEL 系统上配置：\n# 1. 备份原有仓库配置 sudo mv /etc/yum.repos.d /etc/yum.repos.d.backup sudo mkdir -p /etc/yum.repos.d # 2. 创建 Nexus3 仓库配置 cat \u0026gt; /etc/yum.repos.d/nexus.repo \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [nexus-yum] name=Nexus YUM Repository baseurl=http://nexus.example.com:8081/repository/yum-hosted/$releasever/os/$basearch/ enabled=1 gpgcheck=0 priority=1 EOF # 3. 清理并重建缓存 sudo yum clean all sudo yum makecache # 4. 验证仓库配置 yum repolist 高级配置选项 # # 创建更完整的仓库配置 cat \u0026gt; /etc/yum.repos.d/nexus-complete.repo \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [nexus-base] name=Nexus Base Repository baseurl=http://nexus.example.com:8081/repository/yum-hosted/$releasever/os/$basearch/ enabled=1 gpgcheck=0 priority=1 metadata_expire=300 keepcache=1 [nexus-updates] name=Nexus Updates Repository baseurl=http://nexus.example.com:8081/repository/yum-updates/$releasever/os/$basearch/ enabled=1 gpgcheck=0 priority=2 [nexus-extras] name=Nexus Extras Repository baseurl=http://nexus.example.com:8081/repository/yum-extras/$releasever/os/$basearch/ enabled=0 gpgcheck=0 priority=3 EOF 测试安装 # # 搜索可用包 yum search vim # 安装软件包 sudo yum install -y vim-enhanced # 查看包信息 yum info vim-enhanced # 列出所有可用包 yum list available | head -20 图：YUM 客户端成功连接私有仓库的示例\n故障排除 # 常见问题 # 问题 可能原因 解决方案 repomd.xml 不存在 索引未重建 手动触发索引重建 包下载失败 路径深度配置错误 检查 Repodata Depth 设置 权限被拒绝 认证失败 检查用户名密码和权限 仓库不可用 网络连接问题 检查网络和防火墙设置 调试命令 # # 测试仓库连通性 curl -I http://nexus.example.com:8081/repository/yum-hosted/7/os/x86_64/repodata/repomd.xml # 查看详细错误信息 yum --verbose install package-name # 检查仓库配置 yum-config-manager --dump nexus-yum # 清理缓存 sudo yum clean all \u0026amp;\u0026amp; sudo yum makecache Helm Chart 私有仓库配置 # Helm Chart 私有仓库可以为 Kubernetes 应用提供统一的包管理服务，便于企业内部应用的分发和版本管理。\n第一步：创建 Helm Blob 存储 # 配置 Blob Store # 创建专用 Blob Store Name: helm-blob Path: helm 图：创建 Helm 专用的 Blob 存储\n第二步：创建 Helm Hosted 仓库 # 配置步骤 # 创建新仓库\n选择 helm (hosted) 配置仓库参数\n参数 值 说明 Name helm-hosted 仓库名称 Blob store helm-blob 使用创建的 Blob 存储 Deployment policy Allow redeploy 允许重新部署 图：选择 Helm Hosted 仓库类型\n图：Helm Hosted 仓库的基本配置\n图：Helm Hosted 仓库的存储配置\n图：Helm Hosted 仓库配置完成\n第三步：创建 Helm Proxy 仓库（可选） # 虽然上面的截图中没有演示 Proxy 仓库配置，但在实际使用中，Proxy 仓库非常有用：\n配置 Helm Proxy 仓库 # # 常用的 Helm Chart 仓库地址 # 官方稳定仓库: https://charts.helm.sh/stable # Bitnami 仓库: https://charts.bitnami.com/bitnami # Prometheus 社区: https://prometheus-community.github.io/helm-charts 参数 值 说明 Name helm-proxy-stable 仓库名称 Remote storage https://charts.helm.sh/stable 远程仓库地址 Blob store helm-blob 使用相同的 Blob 存储 第四步：上传 Helm Charts # 方法一：Web 界面上传 # 导航到仓库\n点击：Browse → 选择 helm-hosted 上传 Chart\n点击 Upload component 选择 .tgz 格式的 Helm Chart 文件 图：通过 Web 界面上传 Helm Chart\n图：选择并上传 Helm Chart 文件\n方法二：命令行上传 # # 基本上传命令 curl -u \u0026#34;admin:password\u0026#34; \\ --upload-file ./my-chart-1.0.0.tgz \\ http://nexus.example.com:8081/repository/helm-hosted/ # 批量上传脚本 cat \u0026gt; /usr/local/bin/helm-upload.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # Helm Chart 批量上传脚本 NEXUS_URL=\u0026#34;http://nexus.example.com:8081\u0026#34; REPOSITORY=\u0026#34;helm-hosted\u0026#34; USERNAME=\u0026#34;admin\u0026#34; PASSWORD=\u0026#34;your-password\u0026#34; CHARTS_DIR=\u0026#34;./charts\u0026#34; if [[ ! -d \u0026#34;$CHARTS_DIR\u0026#34; ]]; then echo \u0026#34;错误: 目录 $CHARTS_DIR 不存在\u0026#34; exit 1 fi echo \u0026#34;开始上传 Helm Charts...\u0026#34; for chart in \u0026#34;$CHARTS_DIR\u0026#34;/*.tgz; do if [[ -f \u0026#34;$chart\u0026#34; ]]; then chart_name=$(basename \u0026#34;$chart\u0026#34;) echo \u0026#34;上传: $chart_name\u0026#34; if curl -f -u \u0026#34;$USERNAME:$PASSWORD\u0026#34; \\ --upload-file \u0026#34;$chart\u0026#34; \\ \u0026#34;$NEXUS_URL/repository/$REPOSITORY/$chart_name\u0026#34;; then echo \u0026#34;✅ 成功: $chart_name\u0026#34; else echo \u0026#34;❌ 失败: $chart_name\u0026#34; fi fi done echo \u0026#34;上传完成！\u0026#34; EOF chmod +x /usr/local/bin/helm-upload.sh 方法三：使用 Helm 插件 # # 安装 Helm push 插件 helm plugin install https://github.com/chartmuseum/helm-push # 添加仓库 helm repo add nexus http://nexus.example.com:8081/repository/helm-hosted/ \\ --username admin --password your-password # 推送 Chart helm push my-chart-1.0.0.tgz nexus 第五步：客户端配置和使用 # 添加 Helm 仓库 # # 添加私有仓库 helm repo add nexus-helm http://nexus.example.com:8081/repository/helm-hosted/ # 如果需要认证 helm repo add nexus-helm http://nexus.example.com:8081/repository/helm-hosted/ \\ --username admin --password your-password # 更新仓库索引 helm repo update # 验证仓库 helm repo list 搜索和安装 Charts # # 搜索可用的 Charts helm search repo nexus-helm # 查看 Chart 详情 helm show chart nexus-helm/my-chart # 安装 Chart helm install my-release nexus-helm/my-chart # 升级 Chart helm upgrade my-release nexus-helm/my-chart --version 1.1.0 开发和发布流程 # # 1. 创建新的 Chart helm create my-new-chart # 2. 编辑 Chart 内容 # 修改 Chart.yaml, values.yaml, templates/ 等 # 3. 验证 Chart helm lint my-new-chart/ # 4. 打包 Chart helm package my-new-chart/ # 5. 上传到私有仓库 curl -u admin:password \\ --upload-file my-new-chart-1.0.0.tgz \\ http://nexus.example.com:8081/repository/helm-hosted/ # 6. 更新仓库索引 helm repo update PyPI 私有仓库配置 # PyPI 私有仓库可以为 Python 项目提供包管理服务，支持企业内部 Python 包的分发和版本管理。\n第一步：创建 PyPI Blob 存储 # 配置 Blob Store # 创建专用 Blob Store Name: pypi-blob Path: pypi 图：创建 PyPI 专用的 Blob 存储\n第二步：创建 PyPI Hosted 仓库 # 配置步骤 # 创建新仓库\n选择 pypi (hosted) 配置仓库参数\n参数 值 说明 Name pypi-hosted 仓库名称 Blob store pypi-blob 使用创建的 Blob 存储 Deployment policy Allow redeploy 允许重新部署 图：创建 PyPI Hosted 仓库配置\n第三步：创建 PyPI Proxy 仓库 # 为了代理官方 PyPI 仓库，提供缓存和加速功能：\n参数 值 说明 Name pypi-proxy 仓库名称 Remote storage https://pypi.org/ PyPI 官方地址 Blob store pypi-blob 使用相同的 Blob 存储 国内镜像源推荐:\n阿里云: https://mirrors.aliyun.com/pypi/simple/ 清华大学: https://pypi.tuna.tsinghua.edu.cn/simple/ 豆瓣: https://pypi.douban.com/simple/ 第四步：创建 PyPI Group 仓库 # Group 仓库将 hosted 和 proxy 仓库组合成统一入口：\n图：创建 PyPI Group 仓库，组合多个仓库\n配置 Group 仓库 # 参数 值 说明 Name pypi-group 仓库名称 Blob store pypi-blob 使用相同的 Blob 存储 Member repositories pypi-hosted, pypi-proxy 包含的仓库 重要: 将 pypi-hosted 放在前面，确保内部包优先级更高。\n图：配置完成后使用 Group 仓库地址\n第五步：上传 Python 包 # 方法一：使用 twine 上传 # # 1. 安装 twine 工具 pip install twine # 2. 构建 Python 包 python setup.py sdist bdist_wheel # 3. 上传到私有仓库 twine upload --repository-url http://nexus.example.com:8081/repository/pypi-hosted/ \\ dist/* \\ --username admin \\ --password your-password 方法二：配置 .pypirc 文件 # # 创建 .pypirc 配置文件 cat \u0026gt; ~/.pypirc \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [distutils] index-servers = nexus nexus-hosted [nexus] repository = http://nexus.example.com:8081/repository/pypi-group/ username = admin password = your-password [nexus-hosted] repository = http://nexus.example.com:8081/repository/pypi-hosted/ username = admin password = your-password EOF # 设置文件权限 chmod 600 ~/.pypirc # 使用配置文件上传 twine upload --repository nexus-hosted dist/* 方法三：批量上传脚本 # cat \u0026gt; /usr/local/bin/pypi-upload.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash # PyPI 包批量上传脚本 NEXUS_URL=\u0026#34;http://nexus.example.com:8081/repository/pypi-hosted/\u0026#34; USERNAME=\u0026#34;admin\u0026#34; PASSWORD=\u0026#34;your-password\u0026#34; DIST_DIR=\u0026#34;./dist\u0026#34; if [[ ! -d \u0026#34;$DIST_DIR\u0026#34; ]]; then echo \u0026#34;错误: 目录 $DIST_DIR 不存在\u0026#34; echo \u0026#34;请先运行: python setup.py sdist bdist_wheel\u0026#34; exit 1 fi echo \u0026#34;开始上传 Python 包...\u0026#34; # 上传所有 .tar.gz 和 .whl 文件 for package in \u0026#34;$DIST_DIR\u0026#34;/*.{tar.gz,whl}; do if [[ -f \u0026#34;$package\u0026#34; ]]; then package_name=$(basename \u0026#34;$package\u0026#34;) echo \u0026#34;上传: $package_name\u0026#34; if twine upload --repository-url \u0026#34;$NEXUS_URL\u0026#34; \\ --username \u0026#34;$USERNAME\u0026#34; \\ --password \u0026#34;$PASSWORD\u0026#34; \\ \u0026#34;$package\u0026#34;; then echo \u0026#34;✅ 成功: $package_name\u0026#34; else echo \u0026#34;❌ 失败: $package_name\u0026#34; fi fi done echo \u0026#34;上传完成！\u0026#34; EOF chmod +x /usr/local/bin/pypi-upload.sh 第六步：客户端配置 # 配置 pip 使用私有仓库 # # 方法一：命令行指定 pip install --index-url http://nexus.example.com:8081/repository/pypi-group/simple/ \\ --trusted-host nexus.example.com \\ your-package # 方法二：配置文件 mkdir -p ~/.pip cat \u0026gt; ~/.pip/pip.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [global] index-url = http://nexus.example.com:8081/repository/pypi-group/simple/ trusted-host = nexus.example.com [install] trusted-host = nexus.example.com EOF 配置认证（如果需要） # # 在 pip.conf 中添加认证信息 cat \u0026gt; ~/.pip/pip.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [global] index-url = http://admin:your-password@nexus.example.com:8081/repository/pypi-group/simple/ trusted-host = nexus.example.com EOF 企业环境配置 # # 创建企业级配置 sudo mkdir -p /etc/pip sudo cat \u0026gt; /etc/pip/pip.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; [global] index-url = http://nexus.example.com:8081/repository/pypi-group/simple/ trusted-host = nexus.example.com timeout = 60 [install] trusted-host = nexus.example.com EOF 第七步：测试和验证 # 测试包安装 # # 安装公共包（通过 proxy 仓库） pip install requests # 安装内部包（从 hosted 仓库） pip install your-internal-package # 查看包信息 pip show requests # 列出已安装的包 pip list 验证仓库连通性 # # 测试仓库访问 curl -I http://nexus.example.com:8081/repository/pypi-group/simple/ # 搜索包 pip search your-package-name # 查看包的可用版本 pip index versions your-package-name 总结与最佳实践 # 部署总结 # 通过本文的详细指导，我们成功实现了：\n✅ 完整的 Nexus3 部署: 从环境准备到服务启动的全流程 ✅ Docker 私有仓库: 支持镜像推送、拉取和管理 ✅ Nginx 反向代理: 提供统一访问入口和负载均衡 ✅ 版本升级方案: 安全可靠的升级和回滚策略 ✅ 多格式仓库支持: Yum、Helm、PyPI 等多种包管理格式 ✅ 企业级配置: 适用于生产环境的安全和性能配置\n最佳实践建议 # 1. 安全性 # 访问控制: 配置细粒度的用户权限和角色 网络安全: 使用 HTTPS 和防火墙保护服务 数据加密: 敏感数据传输和存储加密 定期审计: 监控访问日志和异常行为 2. 性能优化 # 资源配置: 根据使用量调整 JVM 内存和 CPU 资源 存储优化: 使用 SSD 存储提高 I/O 性能 网络优化: 配置 CDN 和缓存策略 清理策略: 定期清理过期和未使用的制品 3. 运维管理 # 监控告警: 配置系统监控和告警机制 备份策略: 实施定期数据备份和恢复测试 文档维护: 保持部署和配置文档的更新 团队培训: 确保团队成员熟悉操作流程 4. 扩展规划 # 高可用部署: 考虑集群部署和故障转移 容量规划: 根据业务增长规划存储和带宽 集成开发: 与 CI/CD 流水线深度集成 多环境管理: 为不同环境配置独立的仓库 常用维护命令 # # 系统监控 docker stats nexus3 df -h /application/nexus3/data netstat -tlnp | grep -E \u0026#34;8081|8082|8083\u0026#34; # 日志查看 docker logs -f nexus3 tail -f /var/log/nginx/nexus.example.com.log # 备份操作 tar -czf nexus3-backup-$(date +%Y%m%d).tar.gz /application/nexus3/data # 清理操作 docker system prune -f find /application/nexus3/data -name \u0026#34;*.tmp\u0026#34; -delete 技术支持资源 # 官方文档: Nexus Repository Manager 3 社区论坛: Sonatype Community GitHub 仓库: nexus-public Docker Hub: sonatype/nexus3 通过本指南，您已经掌握了 Nexus3 的完整部署和管理技能，可以为企业构建一个稳定、安全、高效的制品管理平台。在实际使用过程中，请根据具体需求调整配置参数，并持续关注官方更新和安全公告。\n","date":"2020年12月22日","externalUrl":null,"permalink":"/posts/docker-deploy-nexus3-upgrade/","section":"博客文章","summary":"\u003cp\u003eNexus3 是世界领先的企业级制品仓库管理平台，为现代 DevOps 工具链提供统一的制品管理解决方案。本指南将从基础部署到企业级配置，全面介绍 Nexus3 的部署、配置、管理和运维最佳实践。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003eNexus3 平台简介 \n    \u003cdiv id=\"nexus3-平台简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#nexus3-%e5%b9%b3%e5%8f%b0%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 Nexus3 \n    \u003cdiv id=\"什么是-nexus3\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-nexus3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eNexus Repository Manager 3 是由 Sonatype 公司开发的下一代制品仓库管理平台，作为 DevOps 工具链的核心组件，为软件开发生命周期提供统一的制品管理能力。\u003c/p\u003e","title":"企业级 Nexus3 制品仓库平台部署与运维完整指南","type":"posts"},{"content":" 系统环境说明 # 使用操作系统: Centos-7.9.2009 操作系统内核版本: 4.4.248 (lt) Docker容器版本: 18.09.9 Kubeadm 版本: 1.19.6 节点说明: master01: 192.168.8.70 ,192.168.88.70 node01: 192.168.8.71 ,192.168.88.71 (备注: 为每个节点配置了两个ip，对外服务网段为: 192.168.8.0/24, 集群内部通讯: 192.168.88.0/24) OS 准备 # 系统初始化 # 确保已将selinux关闭\n请 参考文档\n关闭swap # kubernetes 不支持 使用交换空间\nswapoff -a sed -i \u0026#39;s/.*swap.*/#\u0026amp;/\u0026#39; /etc/fstab 加载ivps模块 # 这里加载模块其目的是为了让后面的KubeProxy组件使用ivps模式来提高集群性能，kube-proxy它的作用是转发服务之间的流量（通过群集IP和节点端口）负载均衡到正确的后端Pod。Kube-proxy可以选择在三种模式中的之一运行，每种模式都使用不同的技术实现，它们分别是：userspace ，iptables 和 IPVS,kubeadm 默认使用模式为iptables, 详细文档请 参考。\ncat \u0026gt; /etc/sysconfig/modules/ipvs.modules \u0026lt;\u0026lt;EOF #!/bin/bash modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 EOF chmod 755 /etc/sysconfig/modules/ipvs.modules \\ \u0026amp;\u0026amp; bash /etc/sysconfig/modules/ipvs.modules \\ \u0026amp;\u0026amp; lsmod | grep -e ip_vs -e nf_conntrack_ipv4 # 配置完成之后最好重启一下系统再检查是否有生效 reboot lsmod | grep -e ip_vs -e nf_conntrack_ipv4 # 重启完成后执行 修改主机名称 # hostnamectl set-hostname xxx # xxx为你要修改的主机名称 示例: hostnamectl set-hostname master01 配置修改hosts文件 # # 每个节点多需要执行一下 cat \u0026gt;\u0026gt; /etc/hosts \u0026lt;\u0026lt; EOF 192.168.88.70 master01 192.168.88.71 node01 EOF # 我这里将域名解析至内部通讯ip，更具自身环境修改即可 配置节点之间免密登录 # ssh-keygen # 执行此命令后一路回车即可 # 将生成的公钥copy至目标主机完成免密 ssh-copy-id node01 ssh-copy-id master01 # 如有多个节点时可使用此方法 for n in `seq -w 01 04`;do ssh-copy-id node$n;done for n in `seq -w 01 03`;do ssh-copy-id master$n;done # 非交互式完成免密 (依赖使用sshpass工具，可使用yum安装) for i in `seq 1 3`;do sshpass -p \u0026#39;123456\u0026#39; ssh-copy-id -o StrictHostKeyChecking=no node\u0026#34;$i\u0026#34;.com;done for i in `seq 1 3`;do sshpass -p \u0026#39;123456\u0026#39; ssh-copy-id -o StrictHostKeyChecking=no master\u0026#34;$i\u0026#34;.com;done 配置时间同步 # yum install -y ntp # 客户端形式使用 /sbin/ntpdate -u ntp1.aliyun.com # 这里使用aliyun的ntp服务器，如有内网ntp替换使用即可 # 添加至定时任务中 crontab -e # 将下面的命令添加至定时任务队列中 */10 * * * * /sbin/ntpdate -u ntp1.aliyun.com \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 systemctl enable crond # 检查定时服务是否自启 kubeadm 安装部署集群 # 配置aliyun的kubernetes源 # cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF yum makecache fast # 执行后确认密钥，输入“y” 执行安装特点版本 # yum list kubeadm --showduplicates | sort -r # 打印查看所有 kubeadm 版本 yum install -y kubeadm-1.19.6 kubelet-1.19.6 kubectl-1.19.6 systemctl enable kubelet # 安装完成后 添加kebelet服务自启 配置 kubectl 命令补全 # yum install -y bash-completion source /usr/share/bash-completion/bash_completion source \u0026lt;(kubectl completion bash) echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 生成配置文件部署（master） # mkdir workspace \\ \u0026amp;\u0026amp; cd workspace kubeadm config print init-defaults \u0026gt;\u0026gt; kubeadm-init.yaml 修改后的配置文件展示 # apiVersion: kubeadm.k8s.io/v1beta2 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.88.70 # master ip bindPort: 6443 nodeRegistration: criSocket: /var/run/dockershim.sock name: master01 taints: - effect: NoSchedule key: node-role.kubernetes.io/master --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta2 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: type: CoreDNS etcd: local: dataDir: /var/lib/etcd imageRepository: k8s.gcr.io # 拉取镜像的地址，可设置为aliyun的地址\u0026#34;registry.cn-hangzhou.aliyuncs.com/google_containers\u0026#34;，我这里使用默认。 kind: ClusterConfiguration kubernetesVersion: v1.19.6 networking: dnsDomain: cluster.local podSubnet: 172.20.0.0/16 # 默认网段为: \u0026#34;10.244.0.0/16\u0026#34; serviceSubnet: 10.96.0.0/12 scheduler: {} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 # 开启 ipvs kind: KubeProxyConfiguration mode: \u0026#34;ipvs\u0026#34; 根据配置文件预拉取镜像 (master) # cd workspace \\ \u0026amp;\u0026amp; kubeadm config images pull --config kubeadm-init.yaml 集群初始化 (master) # kubeadm init --config kubeadm-init.yaml # 等待初始化完成后 配置kubectl mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config 清理节点 # 如果在集群安装过程中，遇到一些不可描述的问题，我们可以使用下面的命令进行重置节点\nkubeadm reset ifconfig cni0 down \u0026amp;\u0026amp; ip link delete cni0 ifconfig flannel.1 down \u0026amp;\u0026amp; ip link delete flannel.1 rm -rf /var/lib/cni/ # 清理Iptables表 ## 注意：如果节点Iptables有特殊配置，以下命令请谨慎操作 sudo iptables --flush sudo iptables --flush --table nat sudo iptables --flush --table filter sudo iptables --table nat --delete-chain sudo iptables --table filter --delete-chain systemctl restart docker 配置node加入集群 (node) # Copy master 初始化后打印的语句，在node节点中执行\nkubeadm join 192.168.88.70:6443 --token abcdef.0123456789abcdef \\ --discovery-token-ca-cert-hash sha256:e390239dde11e9657a2418f309728c422da1823ab6eba1ec2e433c3783eba46 部署网络插件 flannel (master) # # Kubernetes v1.17+ 集群执行执行上面这条语句即可，但是由于我修改了podSubnet的默认地址，所有部署文件也需要相应的修改一下，就要执行第二条语句来将网段替换一下了。 kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.13.0/Documentation/kube-flannel.yml curl https://raw.githubusercontent.com/coreos/flannel/v0.13.0/Documentation/kube-flannel.yml|sed \u0026#39;s#10.244.0.0/16#172.20.0.0/16#g\u0026#39; | kubectl apply -f - kubectl get pod -n kube-system # 检查pod是否启动 NAME READY STATUS RESTARTS AGE coredns-f9fd979d6-45wgz 1/1 Running 0 15m coredns-f9fd979d6-vphjf 1/1 Running 0 15m etcd-master01 1/1 Running 0 15m kube-apiserver-master01 1/1 Running 0 15m kube-controller-manager-master01 1/1 Running 0 15m kube-flannel-ds-fxl9g 1/1 Running 0 2m8s kube-flannel-ds-gqh5s 1/1 Running 0 2m8s kube-proxy-bqhn4 1/1 Running 0 15m kube-proxy-pp2jd 1/1 Running 0 10m kube-scheduler-master01 1/1 Running 0 15m kubelet 添加额外参数 # 不需要执行的操作，且做记录使用\nservice kubelet status # 查看服务信息 Drop-In: /usr/lib/systemd/system/kubelet.service.d #找到关键行 # 进入此目录 /usr/lib/systemd/system/kubelet.service.d # 查看这个配置文件中的内容 cat 10-kubeadm.conf EnvironmentFile=-/etc/sysconfig/kubelet # 修改这个配置文件中内容即可 # 修改节点的内网通讯IP PRIVATE_IP=192.168.8.71 echo \u0026#34;KUBELET_EXTRA_ARGS=--node-ip=$PRIVATE_IP\u0026#34; \u0026gt; /etc/sysconfig/kubelet systemctl daemon-reload systemctl restart kubelet 部署dashboard # 目前市面上Dashboard种类繁多，如：原生的 dashboard、rancher、lens、kubespape、Kuboard等等，因为项目缘故对rancher用的比较多，我们这里就演示部署rancher进行管理集群吧。\nrancher 单机部署 # Rancher github 地址 docker run -d --restart=unless-stopped \\ --name rancher \\ -p 8080:80 -p 8443:443 \\ --privileged \\ rancher/rancher:v2.4.8 docker logs -f --tail 100 rancher # 查看日志等待启动完成 rancher 导入集群 # 添加集群 # 复制命令在kubeadm 管理节点中执行 # 发现执行下面语句后，ranche r集群显示异常。\n修复此问题: 参考文档 curl --insecure -sfL https://192.168.8.66:8443/v3/import/jzrpkrzjpjgv9q5gqqg8hlhm29ldb5gv9mn4xpg8cxgfh5zg4mfs4h.yaml|sed \u0026#39;s#rbac.authorization.k8s.io/v1beta1#rbac.authorization.k8s.io/v1#g\u0026#39; |kubectl apply -f - 部署 原生 dashboard # 参考文档\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.1/aio/deploy/recommended.yaml watch kubectl get pod -n kubernetes-dashboard # 等待容器启动完成 临时使用kubectl 将 dashboard 代理出来 # 后面我会更新使用ingress 的方法\nkubectl port-forward -n kubernetes-dashboard service/kubernetes-dashboard 8080:443 --addss 0.0.0.0 获取用户 Token # kubectl get secret -n kubernetes-dashboard|grep dashboard-token|awk \u0026#39;{print $1}\u0026#39;|xargs -I {} kubectl -n kubernetes-dashboard get secret {} -o jsonpath={.data.token}|base64 -d 访问地址为 https://\u0026lt;kubectl-ip\u0026gt;:8080, 注意如果chrome浏览器访问不安全的https链接没有继续访问的操作的话，可以尝试使用其他浏览器如: firefox，当然好像也可以降低chrome浏览器的安全级别来解决此问题\n登录成功后发现抛出大量权限不足错误 解决方法-重新绑定默认用户为管理员(生产不推荐) # 创建 admin-role.yaml 配置文件内容如下\napiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kubernetes-dashboard namespace: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard kubectl delete -f ./admin-role.yaml \\ \u0026amp;\u0026amp; kubectl create -f ./admin-role.yaml 再次刷新不再报错了，且集群信息显示正常\n","date":"2020年12月21日","externalUrl":null,"permalink":"/posts/kubeadm-deploy-k8s1.9/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e系统环境说明 \n    \u003cdiv id=\"系统环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%b3%bb%e7%bb%9f%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e使用操作系统: Centos-7.9.2009\u003c/li\u003e\n\u003cli\u003e操作系统内核版本: 4.4.248 (\u003ccode\u003elt\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003eDocker容器版本: 18.09.9\u003c/li\u003e\n\u003cli\u003eKubeadm 版本: 1.19.6\u003c/li\u003e\n\u003cli\u003e节点说明:\n\u003cul\u003e\n\u003cli\u003emaster01: 192.168.8.70 ,192.168.88.70\u003c/li\u003e\n\u003cli\u003enode01: 192.168.8.71 ,192.168.88.71\u003c/li\u003e\n\u003cli\u003e(备注: 为每个节点配置了两个ip，对外服务网段为: \u003ccode\u003e192.168.8.0/24\u003c/code\u003e, 集群内部通讯: \u003ccode\u003e192.168.88.0/24\u003c/code\u003e)\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eOS 准备 \n    \u003cdiv id=\"os-准备\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#os-%e5%87%86%e5%a4%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e系统初始化 \n    \u003cdiv id=\"系统初始化\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%b3%bb%e7%bb%9f%e5%88%9d%e5%a7%8b%e5%8c%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003e确保已将selinux关闭\u003c/code\u003e\u003c/p\u003e","title":"Kubeadm 部署 kubernetes-v1.19.x 集群","type":"posts"},{"content":"","date":"2020年12月21日","externalUrl":null,"permalink":"/tags/v1.19.x/","section":"Tags","summary":"","title":"V1.19.x","type":"tags"},{"content":"","date":"2020年12月21日","externalUrl":null,"permalink":"/tags/production-deployment/","section":"Tags","summary":"","title":"Production-Deployment","type":"tags"},{"content":"","date":"2020年12月21日","externalUrl":null,"permalink":"/tags/server-hardening/","section":"Tags","summary":"","title":"Server-Hardening","type":"tags"},{"content":"","date":"2020年12月21日","externalUrl":null,"permalink":"/tags/system-optimization/","section":"Tags","summary":"","title":"System-Optimization","type":"tags"},{"content":" CentOS 7 系统初始化简介 # 概述 # CentOS 7 作为企业级 Linux 发行版，在生产环境中广泛应用。新安装的系统需要进行全面的初始化配置和优化，以确保系统的安全性、稳定性和高性能。本指南提供了一套经过生产环境验证的完整初始化方案。\n适用场景 # 生产服务器部署：Web 服务器、数据库服务器、应用服务器 容器化环境：Docker 宿主机、Kubernetes 节点 云原生基础设施：微服务架构、DevOps 平台 高可用集群：负载均衡、分布式系统节点 优化目标 # 安全性：系统安全加固、访问控制、审计配置 性能：内核参数调优、网络优化、存储优化 稳定性：服务配置、监控告警、故障恢复 可维护性：标准化配置、自动化脚本、文档规范 系统要求 # 硬件要求 # 环境类型 CPU 内存 存储 网络 开发环境 2 核 4GB 50GB 100Mbps 测试环境 4 核 8GB 100GB 1Gbps 生产环境 8 核+ 16GB+ 500GB+ 1Gbps+ 软件版本 # 组件 版本要求 说明 CentOS 7.6+ 推荐 7.9 最新版本 内核 3.10+ 推荐升级到 5.x LTS Docker 19.03+ 容器化环境 Kubernetes 1.18+ 容器编排平台 ⚠️ 重要提示: 本指南中的配置已在 CentOS 7.9 环境中验证，其他版本请根据实际情况调整\n系统初始化配置 # 环境检查脚本 # 在开始配置之前，先运行环境检查脚本：\ncat \u0026gt; system-check.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== CentOS 7 系统环境检查 ===\u0026#34; echo \u0026#34;检查时间: $(date)\u0026#34; echo # 检查系统版本 echo \u0026#34;=== 系统信息 ===\u0026#34; cat /etc/redhat-release uname -a echo # 检查硬件资源 echo \u0026#34;=== 硬件资源 ===\u0026#34; echo \u0026#34;CPU 信息:\u0026#34; lscpu | grep -E \u0026#34;(Architecture|CPU|Thread|Core|Socket)\u0026#34; echo echo \u0026#34;内存信息:\u0026#34; free -h echo echo \u0026#34;磁盘信息:\u0026#34; df -h echo # 检查网络配置 echo \u0026#34;=== 网络配置 ===\u0026#34; ip addr show echo # 检查服务状态 echo \u0026#34;=== 关键服务状态 ===\u0026#34; systemctl status firewalld --no-pager -l systemctl status NetworkManager --no-pager -l echo echo \u0026#34;=== 环境检查完成 ===\u0026#34; EOF chmod +x system-check.sh ./system-check.sh 系统性能优化 # 内核参数调优 # 创建企业级内核参数配置：\ncat \u0026gt; /etc/sysctl.d/99-production.conf \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # ===== 网络性能优化 ===== # 启用桥接网络的 iptables 处理（Kubernetes 必需） net.bridge.bridge-nf-call-ip6tables=1 net.bridge.bridge-nf-call-iptables=1 # 启用 IP 转发（容器网络必需） net.ipv4.ip_forward=1 net.ipv4.conf.all.forwarding=1 # 反向路径过滤（安全优化） net.ipv4.conf.all.rp_filter=0 net.ipv4.conf.default.rp_filter=0 # ARP 配置优化 net.ipv4.conf.default.arp_announce=2 net.ipv4.conf.lo.arp_announce=2 net.ipv4.conf.all.arp_announce=2 # 邻居表优化（提高网络性能） net.ipv4.neigh.default.gc_thresh1=4096 net.ipv4.neigh.default.gc_thresh2=6144 net.ipv4.neigh.default.gc_thresh3=8192 net.ipv4.neigh.default.gc_interval=60 net.ipv4.neigh.default.gc_stale_time=120 # TCP 性能优化 net.ipv4.tcp_slow_start_after_idle=0 net.ipv4.tcp_max_syn_backlog=8096 net.ipv4.tcp_max_tw_buckets=5000 net.ipv4.tcp_syncookies=1 net.ipv4.tcp_fin_timeout=30 net.ipv4.tcp_synack_retries=2 net.ipv4.tcp_tw_reuse=1 net.ipv4.tcp_wmem=4096 12582912 16777216 net.ipv4.tcp_rmem=4096 12582912 16777216 # 网络缓冲区优化 net.core.rmem_max=16777216 net.core.wmem_max=16777216 net.core.netdev_max_backlog=16384 net.core.somaxconn=32768 # ===== 文件系统优化 ===== # 文件监控优化（适用于大量文件监控的应用） fs.inotify.max_user_watches=524288 fs.inotify.max_user_instances=8192 fs.inotify.max_queued_events=16384 # 文件句柄限制 fs.file-max=2097152 # 内存映射区域限制（Elasticsearch 等应用需要） vm.max_map_count=262144 # 允许分离挂载点（容器环境需要） fs.may_detach_mounts=1 # ===== 安全配置 ===== # 硬链接和软链接保护 fs.protected_hardlinks=1 fs.protected_symlinks=1 # 禁用源路由（安全考虑） net.ipv4.conf.default.accept_source_route=0 net.ipv4.conf.all.accept_source_route=0 net.ipv4.conf.default.accept_redirects=0 net.ipv4.conf.all.accept_redirects=0 # 地址提升配置 net.ipv4.conf.default.promote_secondaries=1 net.ipv4.conf.all.promote_secondaries=1 # ===== 内存管理优化 ===== # 禁用 swap（Kubernetes 推荐） vm.swappiness=0 # 内存回收优化 vm.dirty_ratio=15 vm.dirty_background_ratio=5 vm.dirty_expire_centisecs=3000 vm.dirty_writeback_centisecs=500 # OOM 配置 vm.panic_on_oom=0 vm.oom_kill_allocating_task=1 # ===== 内核监控配置 ===== kernel.softlockup_all_cpu_backtrace=1 kernel.softlockup_panic=0 kernel.watchdog_thresh=30 kernel.sysrq=1 # 性能监控配置（Prometheus Node Exporter 需要） kernel.perf_event_paranoid=-1 # 调试配置 kernel.yama.ptrace_scope=0 kernel.core_uses_pid=1 kernel.core_pattern=core.%e.%p.%t # ===== IPv6 配置 ===== # 禁用 IPv6（如果不需要的话） net.ipv6.conf.all.disable_ipv6=1 net.ipv6.conf.default.disable_ipv6=1 net.ipv6.conf.lo.disable_ipv6=1 EOF # 应用配置 sysctl -p /etc/sysctl.d/99-production.conf # 验证配置 echo \u0026#34;内核参数配置完成，验证关键参数：\u0026#34; sysctl net.ipv4.ip_forward sysctl vm.swappiness sysctl fs.file-max 系统文件句柄优化 # # 创建文件句柄优化脚本 cat \u0026gt; optimize-limits.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 优化系统文件句柄限制 ===\u0026#34; # 备份原始配置 cp /etc/security/limits.conf /etc/security/limits.conf.bak # 配置全局文件句柄限制 cat \u0026gt;\u0026gt; /etc/security/limits.conf \u0026lt;\u0026lt; \u0026#39;LIMITS\u0026#39; # 全局文件句柄限制 * soft nofile 65536 * hard nofile 65536 * soft nproc 65536 * hard nproc 65536 # 特定用户限制（示例） root soft nofile 65536 root hard nofile 65536 LIMITS # 配置进程数限制 sed -i \u0026#39;s/4096/65536/g\u0026#39; /etc/security/limits.d/20-nproc.conf # 验证配置 echo \u0026#34;当前配置：\u0026#34; grep -v \u0026#34;^#\\|^$\u0026#34; /etc/security/limits.conf echo echo \u0026#34;进程限制配置：\u0026#34; cat /etc/security/limits.d/20-nproc.conf echo \u0026#34;✓ 文件句柄优化完成\u0026#34; EOF chmod +x optimize-limits.sh ./optimize-limits.sh 内核升级 # 企业级内核升级脚本 # cat \u0026gt; update-kernel.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash ########################################################## # 企业级内核升级脚本 # 支持 LTS 长期支持版本和最新稳定版本 ########################################################## # 配置变量 KERNEL_TYPE=\u0026#34;lt\u0026#34; # lt: 长期支持版本, ml: 最新稳定版本 BACKUP_DIR=\u0026#34;/root/kernel-backup\u0026#34; LOG_FILE=\u0026#34;/var/log/kernel-upgrade.log\u0026#34; # 日志函数 log() { echo \u0026#34;[$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;)] $1\u0026#34; | tee -a \u0026#34;$LOG_FILE\u0026#34; } # 环境检查 check_environment() { log \u0026#34;=== 环境检查 ===\u0026#34; # 检查系统版本 if ! grep -q \u0026#34;CentOS Linux release 7\u0026#34; /etc/redhat-release; then log \u0026#34;错误: 此脚本仅支持 CentOS 7\u0026#34; exit 1 fi # 检查网络连接 if ! ping -c 1 8.8.8.8 \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log \u0026#34;警告: 网络连接异常，可能影响下载\u0026#34; fi # 检查磁盘空间 AVAILABLE_SPACE=$(df /boot | awk \u0026#39;NR==2 {print $4}\u0026#39;) if [ \u0026#34;$AVAILABLE_SPACE\u0026#34; -lt 500000 ]; then log \u0026#34;错误: /boot 分区空间不足 500MB\u0026#34; exit 1 fi log \u0026#34;✓ 环境检查通过\u0026#34; } # 备份当前内核信息 backup_kernel_info() { log \u0026#34;=== 备份当前内核信息 ===\u0026#34; mkdir -p \u0026#34;$BACKUP_DIR\u0026#34; # 备份当前内核信息 uname -a \u0026gt; \u0026#34;$BACKUP_DIR/current-kernel-$(date +%Y%m%d).txt\u0026#34; grub2-editenv list \u0026gt; \u0026#34;$BACKUP_DIR/grub-config-$(date +%Y%m%d).txt\u0026#34; log \u0026#34;当前内核版本: $(uname -r)\u0026#34; log \u0026#34;✓ 内核信息备份完成\u0026#34; } # 配置 ELRepo 仓库 setup_elrepo() { log \u0026#34;=== 配置 ELRepo 仓库 ===\u0026#34; # 导入 GPG 密钥 rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org # 安装 ELRepo 仓库 rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm log \u0026#34;✓ ELRepo 仓库配置完成\u0026#34; } # 升级内核 upgrade_kernel() { log \u0026#34;=== 升级内核 ===\u0026#34; # 查看可用内核版本 yum --disablerepo=\u0026#34;*\u0026#34; --enablerepo=\u0026#34;elrepo-kernel\u0026#34; list available | grep kernel-${KERNEL_TYPE} # 获取最新版本 LATEST_VERSION=$(yum --disablerepo=\u0026#34;*\u0026#34; --enablerepo=\u0026#34;elrepo-kernel\u0026#34; list available | grep \u0026#34;kernel-${KERNEL_TYPE}\\.\u0026#34; | tail -1 | awk \u0026#39;{print $2}\u0026#39;) if [ -z \u0026#34;$LATEST_VERSION\u0026#34; ]; then log \u0026#34;错误: 无法获取内核版本信息\u0026#34; exit 1 fi log \u0026#34;准备安装内核版本: $LATEST_VERSION\u0026#34; # 安装新内核 yum -y --enablerepo=elrepo-kernel install kernel-${KERNEL_TYPE} kernel-${KERNEL_TYPE}-devel if [ $? -eq 0 ]; then log \u0026#34;✓ 内核安装成功\u0026#34; else log \u0026#34;✗ 内核安装失败\u0026#34; exit 1 fi } # 配置启动项 configure_grub() { log \u0026#34;=== 配置 GRUB 启动项 ===\u0026#34; # 获取新内核的启动项 NEW_KERNEL=$(awk -F\\\u0026#39; \u0026#39;$1==\u0026#34;menuentry \u0026#34; {print i++ \u0026#34; : \u0026#34; $2}\u0026#39; /etc/grub2.cfg | grep -E \u0026#34;kernel-${KERNEL_TYPE}\u0026#34; | head -1 | cut -d: -f2 | sed \u0026#39;s/^ *//\u0026#39;) if [ -n \u0026#34;$NEW_KERNEL\u0026#34; ]; then grub2-set-default \u0026#34;$NEW_KERNEL\u0026#34; log \u0026#34;设置默认启动内核: $NEW_KERNEL\u0026#34; else log \u0026#34;警告: 无法自动设置启动项，请手动配置\u0026#34; fi # 重新生成 GRUB 配置 grub2-mkconfig -o /boot/grub2/grub.cfg log \u0026#34;✓ GRUB 配置完成\u0026#34; } # 内核版本锁定 lock_kernel_version() { log \u0026#34;=== 锁定内核版本 ===\u0026#34; # 安装版本锁定工具 yum -y install yum-versionlock # 锁定内核版本 yum versionlock add kernel-${KERNEL_TYPE}* # 显示锁定的包 yum versionlock list log \u0026#34;✓ 内核版本锁定完成\u0026#34; } # 清理旧内核 cleanup_old_kernels() { log \u0026#34;=== 清理旧内核 ===\u0026#34; # 保留最新的 2 个内核版本 package-cleanup --oldkernels --count=2 -y log \u0026#34;✓ 旧内核清理完成\u0026#34; } # 主函数 main() { log \u0026#34;=== 开始内核升级 ===\u0026#34; check_environment backup_kernel_info setup_elrepo upgrade_kernel configure_grub lock_kernel_version log \u0026#34;=== 内核升级完成 ===\u0026#34; log \u0026#34;当前内核: $(uname -r)\u0026#34; log \u0026#34;新内核将在重启后生效\u0026#34; read -p \u0026#34;是否现在重启系统以使用新内核？[y/N]: \u0026#34; -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then log \u0026#34;系统重启中...\u0026#34; reboot else log \u0026#34;请稍后手动重启系统\u0026#34; fi } # 执行主函数 main \u0026#34;$@\u0026#34; EOF chmod +x update-kernel.sh # 执行内核升级（可选） echo \u0026#34;内核升级脚本已创建，如需升级请执行：\u0026#34; echo \u0026#34;./update-kernel.sh\u0026#34; 内核升级后验证 # cat \u0026gt; verify-kernel.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 内核升级验证 ===\u0026#34; # 检查当前内核版本 echo \u0026#34;当前内核版本:\u0026#34; uname -r # 检查内核模块 echo -e \u0026#34;\\n检查关键内核模块:\u0026#34; lsmod | grep -E \u0026#34;(bridge|overlay|ip_tables|iptable_filter|iptable_nat)\u0026#34; # 检查网络功能 echo -e \u0026#34;\\n检查网络功能:\u0026#34; if ip link show docker0 \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;✓ Docker 网络正常\u0026#34; else echo \u0026#34;ℹ Docker 未安装或未启动\u0026#34; fi # 检查容器功能（如果安装了 Docker） if command -v docker \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo -e \u0026#34;\\n检查容器功能:\u0026#34; docker run --rm hello-world \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 \u0026amp;\u0026amp; echo \u0026#34;✓ Docker 容器功能正常\u0026#34; || echo \u0026#34;⚠ Docker 容器功能异常\u0026#34; fi echo -e \u0026#34;\\n=== 验证完成 ===\u0026#34; EOF chmod +x verify-kernel.sh 系统安全配置 # 网络接口配置 # 统一网卡名称为 ethx # cat \u0026gt; configure-network-interface.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 配置网络接口名称 ===\u0026#34; # 备份 GRUB 配置 cp /etc/default/grub /etc/default/grub.bak # 修改 GRUB 配置，统一网卡名称 sed -i \u0026#39;s/GRUB_CMDLINE_LINUX=\u0026#34;\\(.*\\)\u0026#34;/GRUB_CMDLINE_LINUX=\u0026#34;net.ifnames=0 biosdevname=0 cgroup_enable=memory swapaccount=1 \\1\u0026#34;/g\u0026#39; /etc/default/grub # 重新生成 GRUB 配置 grub2-mkconfig -o /boot/grub2/grub.cfg echo \u0026#34;✓ 网络接口配置完成\u0026#34; echo \u0026#34;重启后网卡将使用 eth0, eth1... 命名方式\u0026#34; EOF chmod +x configure-network-interface.sh ./configure-network-interface.sh SELinux 配置 # 安全策略配置 # cat \u0026gt; configure-selinux.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 配置 SELinux 安全策略 ===\u0026#34; # 备份原始配置 cp /etc/selinux/config /etc/selinux/config.bak # 检查当前状态 echo \u0026#34;当前 SELinux 状态:\u0026#34; getenforce sestatus # 根据环境选择配置 read -p \u0026#34;选择 SELinux 模式 [1=Disabled, 2=Permissive, 3=Enforcing]: \u0026#34; choice case $choice in 1) echo \u0026#34;配置为 Disabled 模式（生产环境推荐）\u0026#34; sed -i \u0026#39;s/^SELINUX=.*/SELINUX=disabled/\u0026#39; /etc/selinux/config setenforce 0 2\u0026gt;/dev/null || true ;; 2) echo \u0026#34;配置为 Permissive 模式（调试环境）\u0026#34; sed -i \u0026#39;s/^SELINUX=.*/SELINUX=permissive/\u0026#39; /etc/selinux/config setenforce 0 ;; 3) echo \u0026#34;保持 Enforcing 模式（高安全环境）\u0026#34; sed -i \u0026#39;s/^SELINUX=.*/SELINUX=enforcing/\u0026#39; /etc/selinux/config # 配置容器相关策略 setsebool -P container_manage_cgroup on ;; *) echo \u0026#34;无效选择，保持默认配置\u0026#34; ;; esac echo \u0026#34;当前配置:\u0026#34; grep \u0026#34;^SELINUX=\u0026#34; /etc/selinux/config echo \u0026#34;✓ SELinux 配置完成\u0026#34; EOF chmod +x configure-selinux.sh ./configure-selinux.sh 防火墙配置 # 企业级防火墙配置 # cat \u0026gt; configure-firewall.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 配置企业级防火墙 ===\u0026#34; # 检查防火墙状态 systemctl status firewalld --no-pager read -p \u0026#34;选择防火墙配置 [1=关闭, 2=基础配置, 3=严格配置]: \u0026#34; fw_choice case $fw_choice in 1) echo \u0026#34;关闭防火墙（内网环境）\u0026#34; systemctl stop firewalld systemctl disable firewalld echo \u0026#34;✓ 防火墙已关闭\u0026#34; ;; 2) echo \u0026#34;配置基础防火墙规则\u0026#34; systemctl enable firewalld systemctl start firewalld # 基础服务端口 firewall-cmd --permanent --add-service=ssh firewall-cmd --permanent --add-service=http firewall-cmd --permanent --add-service=https # 常用端口 firewall-cmd --permanent --add-port=8080/tcp firewall-cmd --permanent --add-port=9090/tcp firewall-cmd --reload echo \u0026#34;✓ 基础防火墙配置完成\u0026#34; ;; 3) echo \u0026#34;配置严格防火墙规则\u0026#34; systemctl enable firewalld systemctl start firewalld # 设置默认区域 firewall-cmd --set-default-zone=public # 仅允许必要服务 firewall-cmd --permanent --add-service=ssh # 限制 SSH 访问（示例：仅允许特定网段） firewall-cmd --permanent --add-rich-rule=\u0026#39;rule family=\u0026#34;ipv4\u0026#34; source address=\u0026#34;192.168.0.0/16\u0026#34; service name=\u0026#34;ssh\u0026#34; accept\u0026#39; firewall-cmd --permanent --remove-service=ssh firewall-cmd --reload echo \u0026#34;✓ 严格防火墙配置完成\u0026#34; ;; *) echo \u0026#34;保持默认防火墙配置\u0026#34; ;; esac # 显示当前规则 echo \u0026#34;当前防火墙规则:\u0026#34; firewall-cmd --list-all 2\u0026gt;/dev/null || echo \u0026#34;防火墙未启用\u0026#34; EOF chmod +x configure-firewall.sh ./configure-firewall.sh SSH 安全加固 # SSH 服务优化配置 # cat \u0026gt; harden-ssh.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== SSH 安全加固 ===\u0026#34; # 备份原始配置 cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak # 创建安全配置 cat \u0026gt; /etc/ssh/sshd_config.secure \u0026lt;\u0026lt; \u0026#39;SSHCONFIG\u0026#39; # SSH 安全配置 Port 22 Protocol 2 # 认证配置 PermitRootLogin no PasswordAuthentication yes PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys # 安全选项 UseDNS no GSSAPIAuthentication no GSSAPICleanupCredentials no X11Forwarding no AllowTcpForwarding no PermitTunnel no # 连接限制 MaxAuthTries 3 MaxSessions 10 MaxStartups 10:30:100 LoginGraceTime 60 # 会话配置 ClientAliveInterval 300 ClientAliveCountMax 2 TCPKeepAlive yes # 日志配置 SyslogFacility AUTHPRIV LogLevel INFO # 允许的用户和组（根据需要修改） # AllowUsers user1 user2 # AllowGroups wheel # 禁用空密码 PermitEmptyPasswords no # 严格模式 StrictModes yes # 主机密钥 HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_ecdsa_key HostKey /etc/ssh/ssh_host_ed25519_key SSHCONFIG # 应用配置 read -p \u0026#34;是否应用 SSH 安全配置？[y/N]: \u0026#34; apply_ssh if [[ $apply_ssh =~ ^[Yy]$ ]]; then cp /etc/ssh/sshd_config.secure /etc/ssh/sshd_config # 测试配置 sshd -t if [ $? -eq 0 ]; then systemctl restart sshd echo \u0026#34;✓ SSH 安全配置已应用\u0026#34; else echo \u0026#34;✗ SSH 配置有误，已恢复原始配置\u0026#34; cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config fi else echo \u0026#34;SSH 配置已保存到 /etc/ssh/sshd_config.secure\u0026#34; fi echo \u0026#34;✓ SSH 安全加固完成\u0026#34; EOF chmod +x harden-ssh.sh ./harden-ssh.sh 软件包管理与优化 # YUM 源配置 # 配置国内镜像源 # cat \u0026gt; configure-yum-repos.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 配置 YUM 镜像源 ===\u0026#34; # 备份原始仓库配置 if [ -d /etc/yum.repos.d ]; then mv /etc/yum.repos.d /etc/yum.repos.d.backup.$(date +%Y%m%d) fi mkdir -p /etc/yum.repos.d # 配置阿里云镜像源 echo \u0026#34;配置阿里云镜像源...\u0026#34; curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo # 配置 Docker CE 仓库 echo \u0026#34;配置 Docker CE 仓库...\u0026#34; yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 配置 Kubernetes 仓库 echo \u0026#34;配置 Kubernetes 仓库...\u0026#34; cat \u0026gt; /etc/yum.repos.d/kubernetes.repo \u0026lt;\u0026lt; \u0026#39;K8SREPO\u0026#39; [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg K8SREPO # 清理并重建缓存 yum clean all yum makecache fast echo \u0026#34;✓ YUM 源配置完成\u0026#34; yum repolist EOF chmod +x configure-yum-repos.sh ./configure-yum-repos.sh 基础软件安装 # 系统工具安装 # cat \u0026gt; install-base-tools.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 安装基础系统工具 ===\u0026#34; # 基础工具包 BASE_TOOLS=( \u0026#34;vim\u0026#34; \u0026#34;wget\u0026#34; \u0026#34;curl\u0026#34; \u0026#34;git\u0026#34; \u0026#34;tree\u0026#34; \u0026#34;htop\u0026#34; \u0026#34;iotop\u0026#34; \u0026#34;iftop\u0026#34; \u0026#34;lsof\u0026#34; \u0026#34;strace\u0026#34; \u0026#34;tcpdump\u0026#34; \u0026#34;telnet\u0026#34; \u0026#34;nc\u0026#34; \u0026#34;rsync\u0026#34; \u0026#34;screen\u0026#34; \u0026#34;tmux\u0026#34; \u0026#34;bash-completion\u0026#34; \u0026#34;yum-utils\u0026#34; \u0026#34;device-mapper-persistent-data\u0026#34; \u0026#34;lvm2\u0026#34; ) # 开发工具包 DEV_TOOLS=( \u0026#34;gcc\u0026#34; \u0026#34;gcc-c++\u0026#34; \u0026#34;make\u0026#34; \u0026#34;cmake\u0026#34; \u0026#34;autoconf\u0026#34; \u0026#34;automake\u0026#34; \u0026#34;libtool\u0026#34; \u0026#34;pkgconfig\u0026#34; ) # 网络工具包 NET_TOOLS=( \u0026#34;net-tools\u0026#34; \u0026#34;bind-utils\u0026#34; \u0026#34;traceroute\u0026#34; \u0026#34;mtr\u0026#34; \u0026#34;nmap\u0026#34; \u0026#34;wireshark\u0026#34; ) # 系统监控工具 MONITOR_TOOLS=( \u0026#34;sysstat\u0026#34; \u0026#34;dstat\u0026#34; \u0026#34;atop\u0026#34; \u0026#34;nethogs\u0026#34; \u0026#34;iperf3\u0026#34; ) # 安装函数 install_packages() { local packages=(\u0026#34;$@\u0026#34;) echo \u0026#34;安装软件包: ${packages[*]}\u0026#34; yum install -y \u0026#34;${packages[@]}\u0026#34; if [ $? -eq 0 ]; then echo \u0026#34;✓ 软件包安装成功\u0026#34; else echo \u0026#34;✗ 软件包安装失败\u0026#34; return 1 fi } # 主安装流程 main() { echo \u0026#34;开始安装基础工具...\u0026#34; # 更新系统 yum update -y # 安装各类工具 install_packages \u0026#34;${BASE_TOOLS[@]}\u0026#34; install_packages \u0026#34;${DEV_TOOLS[@]}\u0026#34; install_packages \u0026#34;${NET_TOOLS[@]}\u0026#34; install_packages \u0026#34;${MONITOR_TOOLS[@]}\u0026#34; # 安装 EPEL 仓库 yum install -y epel-release echo \u0026#34;✓ 基础工具安装完成\u0026#34; } main \u0026#34;$@\u0026#34; EOF chmod +x install-base-tools.sh ./install-base-tools.sh 时间同步配置 # NTP/Chrony 时间同步 # cat \u0026gt; configure-time-sync.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 配置时间同步服务 ===\u0026#34; # 选择时间同步服务 read -p \u0026#34;选择时间同步服务 [1=Chrony(推荐), 2=NTP]: \u0026#34; time_service case $time_service in 1) echo \u0026#34;配置 Chrony 时间同步...\u0026#34; # 安装 Chrony yum install -y chrony # 配置 Chrony cat \u0026gt; /etc/chrony.conf \u0026lt;\u0026lt; \u0026#39;CHRONYCONF\u0026#39; # 使用阿里云 NTP 服务器 server ntp.aliyun.com iburst server ntp1.aliyun.com iburst server ntp2.aliyun.com iburst server ntp3.aliyun.com iburst # 备用 NTP 服务器 server 0.centos.pool.ntp.org iburst server 1.centos.pool.ntp.org iburst # 记录系统时钟获得/丢失时间的速率 driftfile /var/lib/chrony/drift # 允许系统时钟在前三次更新中步进 makestep 1.0 3 # 启用内核同步 RTC rtcsync # 日志目录 logdir /var/log/chrony CHRONYCONF # 启动服务 systemctl enable chronyd systemctl start chronyd # 验证同步状态 chrony sources -v ;; 2) echo \u0026#34;配置 NTP 时间同步...\u0026#34; # 安装 NTP yum install -y ntp ntpdate # 配置 NTP cat \u0026gt; /etc/ntp.conf \u0026lt;\u0026lt; \u0026#39;NTPCONF\u0026#39; # 使用阿里云 NTP 服务器 server ntp.aliyun.com prefer server ntp1.aliyun.com server ntp2.aliyun.com server ntp3.aliyun.com # 备用 NTP 服务器 server 0.centos.pool.ntp.org server 1.centos.pool.ntp.org # 限制访问 restrict default nomodify notrap nopeer noquery restrict 127.0.0.1 restrict ::1 # 日志文件 logfile /var/log/ntp.log # 漂移文件 driftfile /var/lib/ntp/drift NTPCONF # 启动服务 systemctl enable ntpd systemctl start ntpd # 验证同步状态 ntpq -p ;; *) echo \u0026#34;保持默认时间配置\u0026#34; ;; esac # 设置时区 timedatectl set-timezone Asia/Shanghai # 显示时间状态 timedatectl status echo \u0026#34;✓ 时间同步配置完成\u0026#34; EOF chmod +x configure-time-sync.sh ./configure-time-sync.sh 用户安全配置 # 终端安全设置 # cat \u0026gt; configure-user-security.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 配置用户安全设置 ===\u0026#34; # 配置终端超时 echo \u0026#34;配置终端自动超时...\u0026#34; cat \u0026gt;\u0026gt; /etc/profile \u0026lt;\u0026lt; \u0026#39;PROFILE\u0026#39; # 终端超时设置（30分钟） export TMOUT=1800 readonly TMOUT # 历史命令配置 export HISTSIZE=1000 export HISTFILESIZE=2000 export HISTTIMEFORMAT=\u0026#34;%Y-%m-%d %H:%M:%S \u0026#34; export HISTCONTROL=ignoredups:erasedups # 安全提示 echo \u0026#34;欢迎使用 $(hostname) 系统\u0026#34; echo \u0026#34;当前时间: $(date)\u0026#34; echo \u0026#34;系统负载: $(uptime | awk -F\u0026#39;load average:\u0026#39; \u0026#39;{print $2}\u0026#39;)\u0026#34; PROFILE # 配置密码策略 echo \u0026#34;配置密码策略...\u0026#34; cat \u0026gt;\u0026gt; /etc/login.defs \u0026lt;\u0026lt; \u0026#39;LOGINDEFS\u0026#39; # 密码策略配置 PASS_MAX_DAYS 90 PASS_MIN_DAYS 1 PASS_MIN_LEN 8 PASS_WARN_AGE 7 LOGINDEFS # 配置 PAM 密码复杂度 if [ -f /etc/pam.d/system-auth ]; then # 备份原始文件 cp /etc/pam.d/system-auth /etc/pam.d/system-auth.bak # 添加密码复杂度要求 sed -i \u0026#39;/pam_pwquality.so/c\\password requisite pam_pwquality.so try_first_pass local_users_only retry=3 minlen=8 dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-1\u0026#39; /etc/pam.d/system-auth fi # 配置登录失败锁定 cat \u0026gt;\u0026gt; /etc/pam.d/sshd \u0026lt;\u0026lt; \u0026#39;PAMLOCKOUT\u0026#39; # 登录失败锁定配置 auth required pam_tally2.so deny=5 unlock_time=300 even_deny_root root_unlock_time=300 PAMLOCKOUT echo \u0026#34;✓ 用户安全配置完成\u0026#34; source /etc/profile EOF chmod +x configure-user-security.sh ./configure-user-security.sh 容器化环境配置 # Docker 安装与优化 # 企业级 Docker 安装脚本 # cat \u0026gt; install-docker.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 企业级 Docker 安装脚本 ===\u0026#34; DOCKER_VERSION=\u0026#34;20.10.21\u0026#34; DOCKER_COMPOSE_VERSION=\u0026#34;2.12.2\u0026#34; # 环境检查 check_environment() { echo \u0026#34;检查系统环境...\u0026#34; # 检查内核版本 KERNEL_VERSION=$(uname -r | cut -d. -f1-2) if [ \u0026#34;$(echo \u0026#34;$KERNEL_VERSION \u0026gt;= 3.10\u0026#34; | bc)\u0026#34; -eq 0 ]; then echo \u0026#34;错误: 内核版本过低，需要 3.10 或更高版本\u0026#34; exit 1 fi # 检查存储驱动支持 if ! grep -q overlay /proc/filesystems; then echo \u0026#34;警告: 系统不支持 overlay2 存储驱动\u0026#34; fi echo \u0026#34;✓ 环境检查通过\u0026#34; } # 安装 Docker install_docker() { echo \u0026#34;安装 Docker CE...\u0026#34; # 卸载旧版本 yum remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine # 安装依赖 yum install -y yum-utils device-mapper-persistent-data lvm2 # 添加 Docker 仓库 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 更新缓存 yum makecache fast # 安装指定版本的 Docker if [ -n \u0026#34;$DOCKER_VERSION\u0026#34; ]; then yum install -y docker-ce-${DOCKER_VERSION} docker-ce-cli-${DOCKER_VERSION} containerd.io else yum install -y docker-ce docker-ce-cli containerd.io fi echo \u0026#34;✓ Docker 安装完成\u0026#34; } # 配置 Docker configure_docker() { echo \u0026#34;配置 Docker...\u0026#34; # 创建配置目录 mkdir -p /etc/docker # 创建 Docker 配置文件 cat \u0026gt; /etc/docker/daemon.json \u0026lt;\u0026lt; \u0026#39;DOCKERCONFIG\u0026#39; { \u0026#34;log-driver\u0026#34;: \u0026#34;json-file\u0026#34;, \u0026#34;log-opts\u0026#34;: { \u0026#34;max-size\u0026#34;: \u0026#34;100m\u0026#34;, \u0026#34;max-file\u0026#34;: \u0026#34;3\u0026#34; }, \u0026#34;storage-driver\u0026#34;: \u0026#34;overlay2\u0026#34;, \u0026#34;storage-opts\u0026#34;: [ \u0026#34;overlay2.override_kernel_check=true\u0026#34; ], \u0026#34;exec-opts\u0026#34;: [\u0026#34;native.cgroupdriver=systemd\u0026#34;], \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://mirror.ccs.tencentyun.com\u0026#34;, \u0026#34;https://docker.mirrors.ustc.edu.cn\u0026#34;, \u0026#34;https://hub-mirror.c.163.com\u0026#34; ], \u0026#34;insecure-registries\u0026#34;: [ \u0026#34;harbor.company.com\u0026#34;, \u0026#34;registry.company.com\u0026#34; ], \u0026#34;max-concurrent-downloads\u0026#34;: 10, \u0026#34;max-concurrent-uploads\u0026#34;: 5, \u0026#34;default-address-pools\u0026#34;: [ { \u0026#34;base\u0026#34;: \u0026#34;172.30.0.0/16\u0026#34;, \u0026#34;size\u0026#34;: 24 } ], \u0026#34;oom-score-adjust\u0026#34;: -1000, \u0026#34;live-restore\u0026#34;: true, \u0026#34;userland-proxy\u0026#34;: false, \u0026#34;experimental\u0026#34;: false, \u0026#34;metrics-addr\u0026#34;: \u0026#34;0.0.0.0:9323\u0026#34;, \u0026#34;iptables\u0026#34;: true, \u0026#34;ip-forward\u0026#34;: true, \u0026#34;ip-masq\u0026#34;: true, \u0026#34;ipv6\u0026#34;: false, \u0026#34;fixed-cidr-v6\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;default-gateway\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;default-gateway-v6\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;bridge\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;bip\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;mtu\u0026#34;: 0, \u0026#34;default-ulimits\u0026#34;: { \u0026#34;nofile\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;nofile\u0026#34;, \u0026#34;Hard\u0026#34;: 64000, \u0026#34;Soft\u0026#34;: 64000 } } } DOCKERCONFIG echo \u0026#34;✓ Docker 配置完成\u0026#34; } # 优化 Docker 服务 optimize_docker_service() { echo \u0026#34;优化 Docker 服务...\u0026#34; # 创建 systemd 覆盖目录 mkdir -p /etc/systemd/system/docker.service.d # 创建服务优化配置 cat \u0026gt; /etc/systemd/system/docker.service.d/override.conf \u0026lt;\u0026lt; \u0026#39;SERVICECONFIG\u0026#39; [Service] # 防止 OOM 杀死 Docker 守护进程 OOMScoreAdjust=-1000 # 设置文件描述符限制 LimitNOFILE=1048576 LimitNPROC=1048576 LimitCORE=infinity # 设置启动后执行的命令 ExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT # 重启策略 Restart=always RestartSec=5 SERVICECONFIG echo \u0026#34;✓ Docker 服务优化完成\u0026#34; } # 安装 Docker Compose install_docker_compose() { echo \u0026#34;安装 Docker Compose...\u0026#34; # 下载 Docker Compose curl -L \u0026#34;https://github.com/docker/compose/releases/download/v${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)\u0026#34; -o /usr/local/bin/docker-compose # 设置执行权限 chmod +x /usr/local/bin/docker-compose # 创建软链接 ln -sf /usr/local/bin/docker-compose /usr/bin/docker-compose # 验证安装 docker-compose --version echo \u0026#34;✓ Docker Compose 安装完成\u0026#34; } # 启动和验证 start_and_verify() { echo \u0026#34;启动 Docker 服务...\u0026#34; # 重新加载 systemd 配置 systemctl daemon-reload # 启动 Docker systemctl start docker systemctl enable docker # 验证安装 echo \u0026#34;验证 Docker 安装...\u0026#34; docker --version docker info # 运行测试容器 echo \u0026#34;运行测试容器...\u0026#34; docker run --rm hello-world if [ $? -eq 0 ]; then echo \u0026#34;✓ Docker 安装验证成功\u0026#34; else echo \u0026#34;✗ Docker 安装验证失败\u0026#34; exit 1 fi } # 主函数 main() { check_environment install_docker configure_docker optimize_docker_service install_docker_compose start_and_verify echo \u0026#34;=== Docker 安装完成 ===\u0026#34; echo \u0026#34;Docker 版本: $(docker --version)\u0026#34; echo \u0026#34;Docker Compose 版本: $(docker-compose --version)\u0026#34; echo \u0026#34;配置文件: /etc/docker/daemon.json\u0026#34; echo \u0026#34;服务配置: /etc/systemd/system/docker.service.d/override.conf\u0026#34; } main \u0026#34;$@\u0026#34; EOF chmod +x install-docker.sh ./install-docker.sh 系统监控配置 # 基础监控脚本 # cat \u0026gt; system-monitor.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== 系统监控信息 ===\u0026#34; echo \u0026#34;检查时间: $(date)\u0026#34; echo # 系统负载 echo \u0026#34;=== 系统负载 ===\u0026#34; uptime echo # CPU 使用率 echo \u0026#34;=== CPU 使用率 ===\u0026#34; top -bn1 | grep \u0026#34;Cpu(s)\u0026#34; | awk \u0026#39;{print $2 $4}\u0026#39; echo # 内存使用情况 echo \u0026#34;=== 内存使用情况 ===\u0026#34; free -h echo # 磁盘使用情况 echo \u0026#34;=== 磁盘使用情况 ===\u0026#34; df -h | grep -vE \u0026#39;^Filesystem|tmpfs|cdrom\u0026#39; echo # 网络连接 echo \u0026#34;=== 网络连接统计 ===\u0026#34; ss -tuln | wc -l echo \u0026#34;总连接数: $(ss -tuln | wc -l)\u0026#34; echo # 进程统计 echo \u0026#34;=== 进程统计 ===\u0026#34; echo \u0026#34;总进程数: $(ps aux | wc -l)\u0026#34; echo \u0026#34;运行中进程: $(ps aux | awk \u0026#39;$8 ~ /^R/ {count++} END {print count+0}\u0026#39;)\u0026#34; echo # Docker 状态（如果安装了） if command -v docker \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;=== Docker 状态 ===\u0026#34; echo \u0026#34;Docker 版本: $(docker --version)\u0026#34; echo \u0026#34;运行中容器: $(docker ps -q | wc -l)\u0026#34; echo \u0026#34;总容器数: $(docker ps -aq | wc -l)\u0026#34; echo \u0026#34;镜像数量: $(docker images -q | wc -l)\u0026#34; echo fi echo \u0026#34;=== 监控信息收集完成 ===\u0026#34; EOF chmod +x system-monitor.sh 总结 # 配置验证脚本 # cat \u0026gt; verify-configuration.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== CentOS 7 配置验证脚本 ===\u0026#34; echo \u0026#34;验证时间: $(date)\u0026#34; echo PASS=0 FAIL=0 # 验证函数 verify() { local test_name=\u0026#34;$1\u0026#34; local command=\u0026#34;$2\u0026#34; echo -n \u0026#34;检查 $test_name: \u0026#34; if eval \u0026#34;$command\u0026#34; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;✓ 通过\u0026#34; ((PASS++)) else echo \u0026#34;✗ 失败\u0026#34; ((FAIL++)) fi } # 系统配置验证 echo \u0026#34;=== 系统配置验证 ===\u0026#34; verify \u0026#34;内核参数\u0026#34; \u0026#34;sysctl net.ipv4.ip_forward | grep -q 1\u0026#34; verify \u0026#34;文件句柄限制\u0026#34; \u0026#34;ulimit -n | grep -q 65536\u0026#34; verify \u0026#34;SELinux 状态\u0026#34; \u0026#34;getenforce | grep -qE \u0026#39;Disabled|Permissive\u0026#39;\u0026#34; verify \u0026#34;时间同步\u0026#34; \u0026#34;systemctl is-active chronyd || systemctl is-active ntpd\u0026#34; # 网络配置验证 echo -e \u0026#34;\\n=== 网络配置验证 ===\u0026#34; verify \u0026#34;网络连通性\u0026#34; \u0026#34;ping -c 1 8.8.8.8\u0026#34; verify \u0026#34;DNS 解析\u0026#34; \u0026#34;nslookup google.com\u0026#34; # 服务状态验证 echo -e \u0026#34;\\n=== 服务状态验证 ===\u0026#34; verify \u0026#34;SSH 服务\u0026#34; \u0026#34;systemctl is-active sshd\u0026#34; # Docker 验证（如果安装了） if command -v docker \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then echo -e \u0026#34;\\n=== Docker 验证 ===\u0026#34; verify \u0026#34;Docker 服务\u0026#34; \u0026#34;systemctl is-active docker\u0026#34; verify \u0026#34;Docker 功能\u0026#34; \u0026#34;docker run --rm hello-world\u0026#34; fi # 安全配置验证 echo -e \u0026#34;\\n=== 安全配置验证 ===\u0026#34; verify \u0026#34;防火墙状态\u0026#34; \u0026#34;systemctl is-active firewalld || systemctl is-failed firewalld\u0026#34; verify \u0026#34;SSH 配置\u0026#34; \u0026#34;grep -q \u0026#39;UseDNS no\u0026#39; /etc/ssh/sshd_config\u0026#34; # 总结 echo -e \u0026#34;\\n=== 验证结果 ===\u0026#34; echo \u0026#34;通过: $PASS 项\u0026#34; echo \u0026#34;失败: $FAIL 项\u0026#34; if [ $FAIL -eq 0 ]; then echo \u0026#34;✓ 所有配置验证通过\u0026#34; exit 0 else echo \u0026#34;⚠ 有 $FAIL 项配置需要检查\u0026#34; exit 1 fi EOF chmod +x verify-configuration.sh ./verify-configuration.sh 部署优势 # 通过本指南的配置，您的 CentOS 7 系统将具备以下优势：\n性能优势 # 内核优化：针对容器化和高并发场景的内核参数调优 网络优化：TCP/IP 栈优化，提升网络性能 存储优化：文件系统和 I/O 性能优化 资源管理：合理的资源限制和调度策略 安全优势 # 系统加固：SELinux、防火墙、SSH 安全配置 访问控制：用户权限管理和登录安全 审计日志：完整的系统操作审计 漏洞防护：及时的安全更新和补丁管理 运维优势 # 标准化配置：统一的系统配置标准 自动化脚本：完整的自动化部署脚本 监控集成：系统监控和告警机制 故障排除：完善的诊断和恢复工具 最佳实践 # 生产环境建议 # 定期更新：保持系统和软件包的及时更新 备份策略：建立完善的系统和数据备份机制 监控告警：部署全面的系统监控和告警 文档维护：保持配置文档的及时更新 安全审计：定期进行安全检查和漏洞扫描 扩展建议 # 集群部署：配置高可用和负载均衡 容器编排：集成 Kubernetes 或 Docker Swarm CI/CD 集成：与 Jenkins、GitLab CI 等工具集成 监控平台：部署 Prometheus、Grafana 等监控方案 通过本指南的配置和最佳实践，您可以构建一个安全、稳定、高性能的企业级 CentOS 7 系统，为各种应用场景提供可靠的基础平台支撑。\n","date":"2020年12月21日","externalUrl":null,"permalink":"/posts/centos-init-config/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eCentOS 7 系统初始化简介 \n    \u003cdiv id=\"centos-7-系统初始化简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#centos-7-%e7%b3%bb%e7%bb%9f%e5%88%9d%e5%a7%8b%e5%8c%96%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e概述 \n    \u003cdiv id=\"概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eCentOS 7 作为企业级 Linux 发行版，在生产环境中广泛应用。新安装的系统需要进行全面的初始化配置和优化，以确保系统的安全性、稳定性和高性能。本指南提供了一套经过生产环境验证的完整初始化方案。\u003c/p\u003e","title":"企业级 CentOS 7 系统初始化与优化完整指南","type":"posts"},{"content":"","date":"2020年12月18日","externalUrl":null,"permalink":"/tags/authentication/","section":"Tags","summary":"","title":"Authentication","type":"tags"},{"content":" OpenLDAP 简介 # 什么是 OpenLDAP # OpenLDAP 是一个开源的轻量级目录访问协议（LDAP）实现，广泛用于企业级统一身份认证和目录服务。它提供了集中化的用户管理、身份验证和授权功能。\n核心特性 # 统一身份认证：为多个应用系统提供统一的用户认证服务 目录服务：提供层次化的信息存储和检索 高性能：支持高并发的读写操作 可扩展性：支持主从复制和分布式部署 标准兼容：完全兼容 LDAP v3 标准 应用场景 # 企业统一认证：员工账号统一管理 DevOps 工具链集成：Jenkins、GitLab、Nexus 等工具的统一认证 应用系统集成：为各类业务系统提供认证服务 权限管理：基于组织架构的权限控制 环境要求 # 系统环境 # 操作系统：CentOS 7+、Ubuntu 18.04+、RHEL 7+ Docker 版本：19.03.8+ Docker Compose 版本：1.25.0+ 内存要求：最少 2GB RAM 存储要求：至少 10GB 可用空间 使用的 Docker 镜像 # 组件 镜像 用途 OpenLDAP 服务 osixia/openldap:latest LDAP 目录服务 Web 管理界面 osixia/phpldapadmin:latest LDAP 可视化管理 密码自助服务 grams/ltb-self-service-password:latest 用户密码自助修改 网络端口规划 # 服务 端口 协议 说明 OpenLDAP 389 TCP LDAP 标准端口 OpenLDAP SSL 636 TCP LDAPS 加密端口（可选） phpLDAPadmin 30004 TCP Web 管理界面 密码自助服务 8765 TCP 用户密码修改界面 部署 OpenLDAP 服务 # 步骤 1：准备部署环境 # 创建项目目录 # # 创建项目根目录 mkdir -p /data/openldap \u0026amp;\u0026amp; cd /data/openldap # 创建数据持久化目录 mkdir -p {data,config,backup,scripts} # 设置目录权限 chmod -R 755 /data/openldap 创建 Docker 网络 # # 创建专用网络（可选，用于服务间通信） docker network create ldap-network 步骤 2：配置 Docker Compose # 基础配置文件 # cat \u0026gt; docker-compose.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; version: \u0026#34;3.8\u0026#34; services: openldap: container_name: openldap-server image: osixia/openldap:1.5.0 restart: unless-stopped hostname: ldap.treesir.pub environment: # 基础配置 LDAP_ORGANISATION: \u0026#34;TreeSir Organization\u0026#34; LDAP_DOMAIN: \u0026#34;treesir.pub\u0026#34; LDAP_BASE_DN: \u0026#34;dc=treesir,dc=pub\u0026#34; # 管理员密码 LDAP_ADMIN_PASSWORD: \u0026#34;AdminPass123!\u0026#34; LDAP_CONFIG_PASSWORD: \u0026#34;ConfigPass123!\u0026#34; # 安全配置 LDAP_READONLY_USER: \u0026#34;false\u0026#34; LDAP_RFC2307BIS_SCHEMA: \u0026#34;false\u0026#34; LDAP_BACKEND: \u0026#34;mdb\u0026#34; LDAP_TLS: \u0026#34;true\u0026#34; LDAP_TLS_CRT_FILENAME: \u0026#34;ldap.crt\u0026#34; LDAP_TLS_KEY_FILENAME: \u0026#34;ldap.key\u0026#34; LDAP_TLS_DH_PARAM_FILENAME: \u0026#34;dhparam.pem\u0026#34; LDAP_TLS_CA_CRT_FILENAME: \u0026#34;ca.crt\u0026#34; LDAP_TLS_ENFORCE: \u0026#34;false\u0026#34; LDAP_TLS_CIPHER_SUITE: \u0026#34;SECURE256:-VERS-SSL3.0\u0026#34; LDAP_TLS_VERIFY_CLIENT: \u0026#34;demand\u0026#34; # 日志配置 LDAP_LOG_LEVEL: \u0026#34;256\u0026#34; # 复制配置（可选） LDAP_REPLICATION: \u0026#34;false\u0026#34; volumes: - ./data:/var/lib/ldap - ./config:/etc/ldap/slapd.d - ./backup:/data/backup - /etc/localtime:/etc/localtime:ro ports: - \u0026#34;389:389\u0026#34; - \u0026#34;636:636\u0026#34; networks: - ldap-network phpldapadmin: container_name: phpldapadmin-web image: osixia/phpldapadmin:0.9.0 restart: unless-stopped hostname: phpldapadmin.treesir.pub environment: PHPLDAPADMIN_LDAP_HOSTS: \u0026#34;openldap-server\u0026#34; PHPLDAPADMIN_HTTPS: \u0026#34;false\u0026#34; PHPLDAPADMIN_TRUST_PROXY_SSL: \u0026#34;true\u0026#34; volumes: - /etc/localtime:/etc/localtime:ro ports: - \u0026#34;30004:80\u0026#34; depends_on: - openldap networks: - ldap-network networks: ldap-network: driver: bridge ipam: config: - subnet: 172.20.0.0/16 EOF 生产环境配置（推荐） # cat \u0026gt; docker-compose.prod.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; version: \u0026#34;3.8\u0026#34; services: openldap: container_name: openldap-server image: osixia/openldap:1.5.0 restart: unless-stopped hostname: ldap.example.com environment: LDAP_ORGANISATION: \u0026#34;Example Corporation\u0026#34; LDAP_DOMAIN: \u0026#34;example.com\u0026#34; LDAP_BASE_DN: \u0026#34;dc=example,dc=com\u0026#34; LDAP_ADMIN_PASSWORD_FILE: \u0026#34;/run/secrets/ldap_admin_password\u0026#34; LDAP_CONFIG_PASSWORD_FILE: \u0026#34;/run/secrets/ldap_config_password\u0026#34; LDAP_TLS: \u0026#34;true\u0026#34; LDAP_TLS_ENFORCE: \u0026#34;true\u0026#34; LDAP_LOG_LEVEL: \u0026#34;0\u0026#34; volumes: - ./data:/var/lib/ldap - ./config:/etc/ldap/slapd.d - ./certs:/container/service/slapd/assets/certs - ./backup:/data/backup ports: - \u0026#34;389:389\u0026#34; - \u0026#34;636:636\u0026#34; secrets: - ldap_admin_password - ldap_config_password networks: - ldap-network phpldapadmin: container_name: phpldapadmin-web image: osixia/phpldapadmin:0.9.0 restart: unless-stopped environment: PHPLDAPADMIN_LDAP_HOSTS: \u0026#34;openldap-server\u0026#34; PHPLDAPADMIN_HTTPS: \u0026#34;true\u0026#34; volumes: - ./web-certs:/container/service/phpldapadmin/assets/apache2/certs ports: - \u0026#34;443:443\u0026#34; depends_on: - openldap networks: - ldap-network secrets: ldap_admin_password: file: ./secrets/ldap_admin_password.txt ldap_config_password: file: ./secrets/ldap_config_password.txt networks: ldap-network: driver: bridge EOF 步骤 3：启动服务 # 开发环境启动 # # 启动服务 docker-compose up -d # 查看服务状态 docker-compose ps # 查看日志 docker-compose logs -f openldap 生产环境启动 # # 创建密码文件 mkdir -p secrets echo \u0026#34;YourStrongAdminPassword123!\u0026#34; \u0026gt; secrets/ldap_admin_password.txt echo \u0026#34;YourStrongConfigPassword123!\u0026#34; \u0026gt; secrets/ldap_config_password.txt chmod 600 secrets/*.txt # 启动生产环境 docker-compose -f docker-compose.prod.yaml up -d 步骤 4：验证部署 # 检查服务状态 # # 检查容器状态 docker ps | grep ldap # 检查端口监听 netstat -tlnp | grep -E \u0026#34;(389|636|30004)\u0026#34; # 测试 LDAP 连接 ldapsearch -x -H ldap://localhost:389 -b \u0026#34;dc=treesir,dc=pub\u0026#34; -D \u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34; -W 访问管理界面 # 打开浏览器访问：http://your-server-ip:30004 登录信息： 服务器：openldap-server 用户名：cn=admin,dc=treesir,dc=pub 密码：AdminPass123!（或您设置的密码） 健康检查脚本 # cat \u0026gt; scripts/health-check.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash echo \u0026#34;=== OpenLDAP Health Check ===\u0026#34; echo \u0026#34;Date: $(date)\u0026#34; echo # 检查容器状态 echo \u0026#34;=== Container Status ===\u0026#34; docker ps --filter \u0026#34;name=openldap\u0026#34; --format \u0026#34;table {{.Names}}\\t{{.Status}}\\t{{.Ports}}\u0026#34; echo # 检查 LDAP 服务 echo \u0026#34;=== LDAP Service Check ===\u0026#34; if ldapsearch -x -H ldap://localhost:389 -b \u0026#34;\u0026#34; -s base \u0026gt; /dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;✓ LDAP service is running\u0026#34; else echo \u0026#34;✗ LDAP service is not responding\u0026#34; fi # 检查管理界面 echo \u0026#34;=== Web Interface Check ===\u0026#34; if curl -s http://localhost:30004 \u0026gt; /dev/null; then echo \u0026#34;✓ phpLDAPadmin is accessible\u0026#34; else echo \u0026#34;✗ phpLDAPadmin is not accessible\u0026#34; fi echo echo \u0026#34;=== Resource Usage ===\u0026#34; docker stats --no-stream --format \u0026#34;table {{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\u0026#34; EOF chmod +x scripts/health-check.sh ./scripts/health-check.sh LDAP 目录结构设计与初始化 # 目录结构规划 # 标准企业目录结构 # dc=treesir,dc=pub ├── ou=people # 用户组织单元 │ ├── uid=john.doe # 用户条目 │ ├── uid=jane.smith # 用户条目 │ └── ... ├── ou=groups # 组织单元 │ ├── cn=developers # 开发组 │ ├── cn=administrators # 管理员组 │ ├── cn=users # 普通用户组 │ └── ... ├── ou=roles # 角色组织单元（可选） │ ├── cn=project-manager # 项目经理角色 │ ├── cn=team-lead # 团队负责人角色 │ └── ... └── ou=services # 服务账号组织单元（可选） ├── uid=jenkins # Jenkins 服务账号 ├── uid=gitlab # GitLab 服务账号 └── ... 使用 LDIF 文件初始化目录结构 # 创建初始化脚本 # cat \u0026gt; scripts/init-ldap-structure.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash LDAP_HOST=\u0026#34;localhost\u0026#34; LDAP_PORT=\u0026#34;389\u0026#34; LDAP_ADMIN_DN=\u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34; LDAP_BASE_DN=\u0026#34;dc=treesir,dc=pub\u0026#34; echo \u0026#34;正在初始化 LDAP 目录结构...\u0026#34; # 创建组织单元 ldapadd -x -H ldap://${LDAP_HOST}:${LDAP_PORT} -D \u0026#34;${LDAP_ADMIN_DN}\u0026#34; -W \u0026lt;\u0026lt; \u0026#39;LDIF\u0026#39; # 创建 people 组织单元 dn: ou=people,dc=treesir,dc=pub objectClass: organizationalUnit ou: people description: 用户组织单元 # 创建 groups 组织单元 dn: ou=groups,dc=treesir,dc=pub objectClass: organizationalUnit ou: groups description: 用户组组织单元 # 创建 roles 组织单元 dn: ou=roles,dc=treesir,dc=pub objectClass: organizationalUnit ou: roles description: 角色组织单元 # 创建 services 组织单元 dn: ou=services,dc=treesir,dc=pub objectClass: organizationalUnit ou: services description: 服务账号组织单元 LDIF echo \u0026#34;目录结构初始化完成！\u0026#34; EOF chmod +x scripts/init-ldap-structure.sh 创建默认用户组 # cat \u0026gt; scripts/create-default-groups.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash LDAP_HOST=\u0026#34;localhost\u0026#34; LDAP_PORT=\u0026#34;389\u0026#34; LDAP_ADMIN_DN=\u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34; echo \u0026#34;正在创建默认用户组...\u0026#34; ldapadd -x -H ldap://${LDAP_HOST}:${LDAP_PORT} -D \u0026#34;${LDAP_ADMIN_DN}\u0026#34; -W \u0026lt;\u0026lt; \u0026#39;LDIF\u0026#39; # 管理员组 dn: cn=administrators,ou=groups,dc=treesir,dc=pub objectClass: groupOfUniqueNames cn: administrators description: 系统管理员组 uniqueMember: cn=admin,dc=treesir,dc=pub # 开发人员组 dn: cn=developers,ou=groups,dc=treesir,dc=pub objectClass: groupOfUniqueNames cn: developers description: 开发人员组 uniqueMember: cn=admin,dc=treesir,dc=pub # 普通用户组 dn: cn=users,ou=groups,dc=treesir,dc=pub objectClass: groupOfUniqueNames cn: users description: 普通用户组 uniqueMember: cn=admin,dc=treesir,dc=pub # DevOps 组 dn: cn=devops,ou=groups,dc=treesir,dc=pub objectClass: groupOfUniqueNames cn: devops description: DevOps 工程师组 uniqueMember: cn=admin,dc=treesir,dc=pub LDIF echo \u0026#34;默认用户组创建完成！\u0026#34; EOF chmod +x scripts/create-default-groups.sh 通过 Web 界面管理目录 # 访问 phpLDAPadmin # 打开浏览器访问：http://your-server-ip:30004 点击左侧的 \u0026ldquo;login\u0026rdquo; 链接 输入登录信息： Login DN：cn=admin,dc=treesir,dc=pub Password：您设置的管理员密码 创建组织单元（OU） # 步骤 1：创建 people 组织单元\n点击根节点 dc=treesir,dc=pub 选择 \u0026ldquo;Create a child entry\u0026rdquo; 选择 \u0026ldquo;Generic: Organisational Unit\u0026rdquo; 输入 OU 名称：people 点击 \u0026ldquo;Create Object\u0026rdquo; 步骤 2：创建 groups 组织单元\n重复上述步骤，创建名为 groups 的组织单元。\n创建用户账号 # 步骤 1：选择用户模板\n点击 ou=people 节点 选择 \u0026ldquo;Create a child entry\u0026rdquo; 选择 \u0026ldquo;Default\u0026rdquo; 以自定义属性 选择 inetOrgPerson 对象类 步骤 2：填写用户信息\n必填字段：\nRDN: uid (推荐使用用户名作为标识) cn (Common Name): 用户全名 sn (Surname): 姓氏 uid: 用户名 userPassword: 用户密码 mail: 邮箱地址（可选） 创建用户组 # 步骤 1：选择组模板\n点击 ou=groups 节点 选择 \u0026ldquo;Create a child entry\u0026rdquo; 选择 \u0026ldquo;Default\u0026rdquo; 选择 groupOfUniqueNames 对象类 步骤 2：配置组信息\n必填字段：\nRDN: cn (组名) cn: 组名称 uniqueMember: 组成员的 DN 步骤 3：添加组成员\n复制用户的完整 DN，例如：uid=john.doe,ou=people,dc=treesir,dc=pub 粘贴到 uniqueMember 字段 点击 \u0026ldquo;Add\u0026rdquo; 添加更多成员 点击 \u0026ldquo;Create Object\u0026rdquo; 完成创建 批量用户管理 # 创建批量用户脚本\ncat \u0026gt; scripts/bulk-create-users.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash LDAP_HOST=\u0026#34;localhost\u0026#34; LDAP_PORT=\u0026#34;389\u0026#34; LDAP_ADMIN_DN=\u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34; # 用户列表文件格式：username:fullname:email:password USER_LIST=\u0026#34;users.txt\u0026#34; if [ ! -f \u0026#34;$USER_LIST\u0026#34; ]; then echo \u0026#34;创建示例用户列表文件...\u0026#34; cat \u0026gt; \u0026#34;$USER_LIST\u0026#34; \u0026lt;\u0026lt; \u0026#39;USERS\u0026#39; john.doe:John Doe:john.doe@treesir.pub:password123 jane.smith:Jane Smith:jane.smith@treesir.pub:password123 bob.wilson:Bob Wilson:bob.wilson@treesir.pub:password123 USERS fi echo \u0026#34;正在批量创建用户...\u0026#34; while IFS=\u0026#39;:\u0026#39; read -r username fullname email password; do echo \u0026#34;创建用户: $username\u0026#34; # 生成密码哈希 password_hash=$(slappasswd -s \u0026#34;$password\u0026#34;) ldapadd -x -H ldap://${LDAP_HOST}:${LDAP_PORT} -D \u0026#34;${LDAP_ADMIN_DN}\u0026#34; -W \u0026lt;\u0026lt; LDIF dn: uid=${username},ou=people,dc=treesir,dc=pub objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount uid: ${username} cn: ${fullname} sn: ${fullname##* } givenName: ${fullname%% *} mail: ${email} userPassword: ${password_hash} uidNumber: $(( 1000 + RANDOM % 9000 )) gidNumber: 1000 homeDirectory: /home/${username} loginShell: /bin/bash LDIF done \u0026lt; \u0026#34;$USER_LIST\u0026#34; echo \u0026#34;批量用户创建完成！\u0026#34; EOF chmod +x scripts/bulk-create-users.sh 功能扩展与优化 # 部署密码自助服务 # 服务简介 # 密码自助服务允许用户自行修改 LDAP 密码，减少管理员工作量，提升用户体验。\n部署步骤 # 步骤 1：创建配置目录\nmkdir -p /data/openldap/self-service-password/{config,logs} cd /data/openldap/self-service-password 步骤 2：创建配置文件\ncat \u0026gt; config/config.inc.php \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; \u0026lt;?php #============================================================================== # LTB Self Service Password # # Copyright (C) 2009 Clement OUDOT # Copyright (C) 2009 LTB-project.org # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # GPL License: http://www.gnu.org/licenses/gpl.txt # #============================================================================== #============================================================================== # Configuration #============================================================================== # LDAP 连接配置 $ldap_url = \u0026#34;ldap://openldap-server:389\u0026#34;; $ldap_starttls = false; $ldap_binddn = \u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34;; $ldap_bindpw = \u0026#34;AdminPass123!\u0026#34;; $ldap_base = \u0026#34;dc=treesir,dc=pub\u0026#34;; $ldap_login_attribute = \u0026#34;uid\u0026#34;; $ldap_fullname_attribute = \u0026#34;cn\u0026#34;; $ldap_filter = \u0026#34;(\u0026amp;(objectClass=inetOrgPerson)($ldap_login_attribute={login}))\u0026#34;; # Active Directory 模式 $ad_mode = false; $ad_options = array( LDAP_OPT_PROTOCOL_VERSION =\u0026gt; 3, LDAP_OPT_REFERRALS =\u0026gt; 0 ); # 密码策略 $pwd_min_length = 8; $pwd_max_length = 64; $pwd_min_lower = 1; $pwd_min_upper = 1; $pwd_min_digit = 1; $pwd_min_special = 1; $pwd_special_chars = \u0026#34;^a-zA-Z0-9\u0026#34;; $pwd_no_reuse = true; $pwd_diff_login = true; $pwd_complexity = 3; # 密码哈希 $hash = \u0026#34;SSHA\u0026#34;; $hash_options[\u0026#39;crypt_salt_prefix\u0026#39;] = \u0026#34;$6$\u0026#34;; $hash_options[\u0026#39;crypt_salt_length\u0026#39;] = \u0026#34;6\u0026#34;; # 本地密码策略 $use_pwnedpasswords = false; $pwd_show_policy = \u0026#34;always\u0026#34;; $pwd_show_policy_pos = \u0026#34;above\u0026#34;; # 邮件配置 $mail_from = \u0026#34;admin@treesir.pub\u0026#34;; $mail_from_name = \u0026#34;Password Self Service\u0026#34;; $mail_signature = \u0026#34;\u0026#34;; $notify_on_change = true; $mail_sendmailpath = \u0026#39;/usr/sbin/sendmail\u0026#39;; $mail_protocol = \u0026#39;mail\u0026#39;; $mail_smtp_debug = 0; $mail_debug_format = \u0026#39;error_log\u0026#39;; $mail_smtp_host = \u0026#39;localhost\u0026#39;; $mail_smtp_auth = false; $mail_smtp_user = \u0026#39;\u0026#39;; $mail_smtp_pass = \u0026#39;\u0026#39;; $mail_smtp_port = 25; $mail_smtp_timeout = 30; $mail_smtp_keepalive = false; $mail_smtp_secure = \u0026#39;tls\u0026#39;; $mail_smtp_autotls = true; $mail_contenttype = \u0026#39;text/plain\u0026#39;; $mail_wordwrap = 0; $mail_charset = \u0026#39;utf-8\u0026#39;; $mail_priority = 3; # SMS 配置 $use_sms = false; # 问题配置 $use_questions = false; # 令牌配置 $use_tokens = true; $token_lifetime = 3600; # 验证码配置 $use_recaptcha = false; # 默认操作 $default_action = \u0026#34;change\u0026#34;; # 调试模式 $debug = false; # 日志配置 $use_syslog = true; $syslog_facility = LOG_USER; $syslog_priority = LOG_INFO; # 界面配置 $lang = \u0026#34;zh-CN\u0026#34;; $show_help = true; $logo = \u0026#34;images/ltb-logo.png\u0026#34;; $background_image = \u0026#34;images/unsplash-space.jpeg\u0026#34;; # 安全配置 $use_change = true; $use_reset = true; $change_sshkey = false; $who_can_change_password = \u0026#34;everybody\u0026#34;; $use_checkpassword = true; $change_sshkey_attribute = \u0026#34;sshPublicKey\u0026#34;; # 密码历史 $pwd_no_reuse = true; $pwd_history_length = 5; ?\u0026gt; EOF 步骤 3：部署服务\n# 添加到 docker-compose.yaml cat \u0026gt;\u0026gt; docker-compose.yaml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; self-service-password: container_name: ldap-self-service image: ltbproject/self-service-password:1.4.4 restart: unless-stopped environment: LDAP_SERVER: \u0026#34;openldap-server\u0026#34; LDAP_BINDDN: \u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34; LDAP_BINDPW: \u0026#34;AdminPass123!\u0026#34; LDAP_BASE_SEARCH: \u0026#34;dc=treesir,dc=pub\u0026#34; volumes: - ./self-service-password/config/config.inc.php:/var/www/conf/config.inc.php:ro - ./self-service-password/logs:/var/log/self-service-password - /etc/localtime:/etc/localtime:ro ports: - \u0026#34;8765:80\u0026#34; depends_on: - openldap networks: - ldap-network EOF # 重启服务 docker-compose up -d 步骤 4：配置防火墙\n# CentOS/RHEL firewall-cmd --zone=public --add-port=8765/tcp --permanent firewall-cmd --reload # Ubuntu/Debian ufw allow 8765/tcp 访问密码自助服务 # 打开浏览器访问：http://your-server-ip:8765 用户可以使用 LDAP 用户名登录并修改密码 数据备份与恢复 # 自动备份脚本 # 创建备份脚本\ncat \u0026gt; scripts/ldap-backup.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash ########################################################## # OpenLDAP 自动备份脚本 # 功能：定期备份 LDAP 数据并清理过期备份 ########################################################## # 配置变量 BACKUP_DIR=\u0026#34;/data/openldap/backup\u0026#34; LDAP_HOST=\u0026#34;localhost\u0026#34; LDAP_PORT=\u0026#34;389\u0026#34; LDAP_ADMIN_DN=\u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34; LDAP_ADMIN_PW=\u0026#34;AdminPass123!\u0026#34; LDAP_BASE_DN=\u0026#34;dc=treesir,dc=pub\u0026#34; KEEP_DAYS=30 LOG_FILE=\u0026#34;/var/log/ldap-backup.log\u0026#34; # 创建备份目录 mkdir -p \u0026#34;$BACKUP_DIR\u0026#34; # 日志函数 log() { echo \u0026#34;[$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;)] $1\u0026#34; | tee -a \u0026#34;$LOG_FILE\u0026#34; } # 备份函数 backup_ldap() { local backup_file=\u0026#34;${BACKUP_DIR}/ldap-backup-$(date +%Y%m%d-%H%M%S).ldif\u0026#34; log \u0026#34;开始备份 LDAP 数据...\u0026#34; # 执行备份 if ldapsearch -x -H \u0026#34;ldap://${LDAP_HOST}:${LDAP_PORT}\u0026#34; \\ -D \u0026#34;${LDAP_ADMIN_DN}\u0026#34; -w \u0026#34;${LDAP_ADMIN_PW}\u0026#34; \\ -b \u0026#34;${LDAP_BASE_DN}\u0026#34; -LLL \u0026gt; \u0026#34;$backup_file\u0026#34;; then # 压缩备份文件 gzip \u0026#34;$backup_file\u0026#34; log \u0026#34;备份完成: ${backup_file}.gz\u0026#34; # 验证备份文件 if [ -s \u0026#34;${backup_file}.gz\u0026#34; ]; then log \u0026#34;备份文件验证成功\u0026#34; return 0 else log \u0026#34;错误: 备份文件为空\u0026#34; return 1 fi else log \u0026#34;错误: 备份失败\u0026#34; return 1 fi } # 清理过期备份 cleanup_old_backups() { log \u0026#34;清理 ${KEEP_DAYS} 天前的备份文件...\u0026#34; find \u0026#34;$BACKUP_DIR\u0026#34; -name \u0026#34;ldap-backup-*.ldif.gz\u0026#34; -mtime +${KEEP_DAYS} -delete local remaining=$(find \u0026#34;$BACKUP_DIR\u0026#34; -name \u0026#34;ldap-backup-*.ldif.gz\u0026#34; | wc -l) log \u0026#34;当前保留备份文件数量: $remaining\u0026#34; } # 发送备份报告（可选） send_report() { local status=$1 local backup_size=$(du -sh \u0026#34;$BACKUP_DIR\u0026#34; | cut -f1) if command -v mail \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then { echo \u0026#34;LDAP 备份报告\u0026#34; echo \u0026#34;==============\u0026#34; echo \u0026#34;时间: $(date)\u0026#34; echo \u0026#34;状态: $status\u0026#34; echo \u0026#34;备份目录大小: $backup_size\u0026#34; echo \u0026#34;备份目录: $BACKUP_DIR\u0026#34; } | mail -s \u0026#34;LDAP 备份报告 - $status\u0026#34; admin@treesir.pub fi } # 主函数 main() { log \u0026#34;=== LDAP 备份任务开始 ===\u0026#34; if backup_ldap; then cleanup_old_backups send_report \u0026#34;成功\u0026#34; log \u0026#34;=== LDAP 备份任务完成 ===\u0026#34; exit 0 else send_report \u0026#34;失败\u0026#34; log \u0026#34;=== LDAP 备份任务失败 ===\u0026#34; exit 1 fi } # 执行主函数 main \u0026#34;$@\u0026#34; EOF chmod +x scripts/ldap-backup.sh 配置定时任务\n# 编辑 crontab crontab -e # 添加以下行（每天凌晨 2 点执行备份） 0 2 * * * /data/openldap/scripts/ldap-backup.sh # 或者每周一到周五执行 0 2 * * 1-5 /data/openldap/scripts/ldap-backup.sh 数据恢复 # 创建恢复脚本\ncat \u0026gt; scripts/ldap-restore.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash ########################################################## # OpenLDAP 数据恢复脚本 ########################################################## BACKUP_DIR=\u0026#34;/data/openldap/backup\u0026#34; LDAP_HOST=\u0026#34;localhost\u0026#34; LDAP_PORT=\u0026#34;389\u0026#34; LDAP_ADMIN_DN=\u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34; LDAP_ADMIN_PW=\u0026#34;AdminPass123!\u0026#34; LDAP_BASE_DN=\u0026#34;dc=treesir,dc=pub\u0026#34; # 显示可用备份 show_backups() { echo \u0026#34;可用的备份文件：\u0026#34; ls -la \u0026#34;$BACKUP_DIR\u0026#34;/ldap-backup-*.ldif.gz 2\u0026gt;/dev/null | nl } # 恢复数据 restore_ldap() { local backup_file=\u0026#34;$1\u0026#34; if [ ! -f \u0026#34;$backup_file\u0026#34; ]; then echo \u0026#34;错误: 备份文件不存在: $backup_file\u0026#34; exit 1 fi echo \u0026#34;警告: 此操作将删除现有数据并恢复到备份状态\u0026#34; read -p \u0026#34;确认继续? (yes/no): \u0026#34; confirm if [ \u0026#34;$confirm\u0026#34; != \u0026#34;yes\u0026#34; ]; then echo \u0026#34;操作已取消\u0026#34; exit 0 fi # 停止相关服务（如果需要） echo \u0026#34;准备恢复数据...\u0026#34; # 清空现有数据（谨慎操作） echo \u0026#34;清空现有数据...\u0026#34; ldapdelete -x -H \u0026#34;ldap://${LDAP_HOST}:${LDAP_PORT}\u0026#34; \\ -D \u0026#34;${LDAP_ADMIN_DN}\u0026#34; -w \u0026#34;${LDAP_ADMIN_PW}\u0026#34; \\ -r \u0026#34;${LDAP_BASE_DN}\u0026#34; 2\u0026gt;/dev/null || true # 恢复数据 echo \u0026#34;恢复备份数据...\u0026#34; if [ \u0026#34;${backup_file##*.}\u0026#34; = \u0026#34;gz\u0026#34; ]; then zcat \u0026#34;$backup_file\u0026#34; | ldapadd -x -H \u0026#34;ldap://${LDAP_HOST}:${LDAP_PORT}\u0026#34; \\ -D \u0026#34;${LDAP_ADMIN_DN}\u0026#34; -w \u0026#34;${LDAP_ADMIN_PW}\u0026#34; else ldapadd -x -H \u0026#34;ldap://${LDAP_HOST}:${LDAP_PORT}\u0026#34; \\ -D \u0026#34;${LDAP_ADMIN_DN}\u0026#34; -w \u0026#34;${LDAP_ADMIN_PW}\u0026#34; \\ -f \u0026#34;$backup_file\u0026#34; fi if [ $? -eq 0 ]; then echo \u0026#34;数据恢复完成\u0026#34; else echo \u0026#34;数据恢复失败\u0026#34; exit 1 fi } # 主函数 main() { if [ $# -eq 0 ]; then show_backups echo echo \u0026#34;使用方法: $0 \u0026lt;backup_file\u0026gt;\u0026#34; echo \u0026#34;示例: $0 /data/openldap/backup/ldap-backup-20231201-020000.ldif.gz\u0026#34; exit 1 fi restore_ldap \u0026#34;$1\u0026#34; } main \u0026#34;$@\u0026#34; EOF chmod +x scripts/ldap-restore.sh 使用恢复脚本\n# 查看可用备份 ./scripts/ldap-restore.sh # 恢复指定备份 ./scripts/ldap-restore.sh /data/openldap/backup/ldap-backup-20231201-020000.ldif.gz 第三方服务集成 # Jenkins 集成 # Jenkins 的 LDAP 配置说明请参考此文档。\nGitLab 集成 # 编辑 GitLab 配置文件 gitlab.rb，在文件末尾添加以下配置：\ngitlab_rails[\u0026#39;ldap_enabled\u0026#39;] = true gitlab_rails[\u0026#39;ldap_servers\u0026#39;] = YAML.load \u0026lt;\u0026lt;-\u0026#39;EOS\u0026#39; main: # \u0026#39;main\u0026#39; is the GitLab \u0026#39;provider ID\u0026#39; of this LDAP server label: \u0026#39;LDAP\u0026#39; host: \u0026#39;treesir.pub\u0026#39; port: 389 # usually 636 for SSL uid: \u0026#39;uid\u0026#39; # This should be the attribute, not the value that maps to uid. # Examples: \u0026#39;america\\\\momo\u0026#39; or \u0026#39;CN=Gitlab Git,CN=Users,DC=mydomain,DC=com\u0026#39; bind_dn: \u0026#39;cn=admin,dc=treesir,dc=pub\u0026#39; password: \u0026#39;123456\u0026#39; encryption: \u0026#39;plain\u0026#39; # \u0026#34;start_tls\u0026#34; or \u0026#34;simple_tls\u0026#34; or \u0026#34;plain\u0026#34; active_directory: false allow_username_or_email_login: false base: \u0026#39;ou=users,dc=treesir,dc=pub\u0026#39; user_filter: \u0026#39;\u0026#39; attributes: username: [\u0026#39;uid\u0026#39;, \u0026#39;userid\u0026#39;, \u0026#39;sAMAccountName\u0026#39;] email: [\u0026#39;mail\u0026#39;, \u0026#39;email\u0026#39;, \u0026#39;userPrincipalName\u0026#39;] name: \u0026#39;cn\u0026#39; first_name: \u0026#39;givenName\u0026#39; last_name: \u0026#39;sn\u0026#39; EOS 修改完成后，需要重新加载 GitLab 配置使其生效：\ngitlab-ctl reconfigure gitlab-ctl restart # 重新加载配置后重启服务 Rancher 集成 # （此部分内容待补充）\nNexus 集成 # 创建 LDAP 认证\n按照上述配置连接后，建议输入账号进行测试验证。\n监控与维护 # 性能监控 # 创建监控脚本 # cat \u0026gt; scripts/ldap-monitor.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash LDAP_HOST=\u0026#34;localhost\u0026#34; LDAP_PORT=\u0026#34;389\u0026#34; LDAP_ADMIN_DN=\u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34; LDAP_BASE_DN=\u0026#34;dc=treesir,dc=pub\u0026#34; LOG_FILE=\u0026#34;/var/log/ldap-monitor.log\u0026#34; # 日志函数 log() { echo \u0026#34;[$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;)] $1\u0026#34; | tee -a \u0026#34;$LOG_FILE\u0026#34; } # 检查 LDAP 服务状态 check_ldap_service() { if ldapsearch -x -H \u0026#34;ldap://${LDAP_HOST}:${LDAP_PORT}\u0026#34; -b \u0026#34;\u0026#34; -s base \u0026gt; /dev/null 2\u0026gt;\u0026amp;1; then log \u0026#34;✓ LDAP 服务正常运行\u0026#34; return 0 else log \u0026#34;✗ LDAP 服务异常\u0026#34; return 1 fi } # 检查认证功能 check_auth() { if ldapwhoami -x -H \u0026#34;ldap://${LDAP_HOST}:${LDAP_PORT}\u0026#34; -D \u0026#34;${LDAP_ADMIN_DN}\u0026#34; -w \u0026#34;AdminPass123!\u0026#34; \u0026gt; /dev/null 2\u0026gt;\u0026amp;1; then log \u0026#34;✓ LDAP 认证功能正常\u0026#34; return 0 else log \u0026#34;✗ LDAP 认证功能异常\u0026#34; return 1 fi } # 检查用户数量 check_user_count() { local count=$(ldapsearch -x -H \u0026#34;ldap://${LDAP_HOST}:${LDAP_PORT}\u0026#34; -D \u0026#34;${LDAP_ADMIN_DN}\u0026#34; -w \u0026#34;AdminPass123!\u0026#34; -b \u0026#34;ou=people,${LDAP_BASE_DN}\u0026#34; \u0026#34;(objectClass=inetOrgPerson)\u0026#34; | grep -c \u0026#34;^dn:\u0026#34;) log \u0026#34;当前用户数量: $count\u0026#34; } # 主函数 main() { log \u0026#34;=== LDAP 监控检查开始 ===\u0026#34; check_ldap_service check_auth check_user_count log \u0026#34;=== LDAP 监控检查完成 ===\u0026#34; } main \u0026#34;$@\u0026#34; EOF chmod +x scripts/ldap-monitor.sh 故障排除 # 常见问题 # 1. 容器启动失败\n# 查看容器日志 docker logs openldap-server # 检查配置文件 docker exec openldap-server slaptest # 检查权限 ls -la /data/openldap/ 2. 无法连接 LDAP 服务\n# 检查端口监听 netstat -tlnp | grep 389 # 测试连接 telnet localhost 389 # 检查防火墙 firewall-cmd --list-ports 3. 认证失败\n# 验证管理员密码 ldapwhoami -x -D \u0026#34;cn=admin,dc=treesir,dc=pub\u0026#34; -W # 检查用户 DN ldapsearch -x -b \u0026#34;dc=treesir,dc=pub\u0026#34; \u0026#34;(uid=username)\u0026#34; 总结 # 部署优势 # 统一认证：为企业提供集中化的身份认证服务 易于管理：通过 Web 界面和脚本实现便捷管理 高可用性：支持容器化部署和自动重启 可扩展性：支持与多种第三方服务集成 安全性：支持 SSL/TLS 加密和访问控制 最佳实践 # 定期备份：建立完善的数据备份机制 监控告警：实施全面的服务监控 安全加固：启用 SSL/TLS 和强密码策略 权限控制：实施最小权限原则 文档维护：保持配置文档的及时更新 扩展建议 # 高可用部署：配置主从复制或集群模式 性能优化：根据用户规模调整配置参数 集成更多服务：扩展到更多企业应用系统 自动化运维：开发自动化部署和维护脚本 通过本指南，您可以成功部署一个功能完整的企业级 OpenLDAP 统一身份认证服务，为您的 DevOps 工具链提供可靠的认证基础。\n","date":"2020年12月18日","externalUrl":null,"permalink":"/posts/docker-deploy-ldap/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003eOpenLDAP 简介 \n    \u003cdiv id=\"openldap-简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#openldap-%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是 OpenLDAP \n    \u003cdiv id=\"什么是-openldap\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af-openldap\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eOpenLDAP 是一个开源的轻量级目录访问协议（LDAP）实现，广泛用于企业级统一身份认证和目录服务。它提供了集中化的用户管理、身份验证和授权功能。\u003c/p\u003e","title":"Docker 部署 OpenLDAP 统一身份认证服务","type":"posts"},{"content":" 环境说明 # Kubernetes 版本：v1.19.6 操作系统：CentOS 7.9.2009 问题现象 # 最近在使用 Kubernetes 集群时，发现集群响应变慢。排查发现 Master 节点中 controller-manager 及 scheduler 组件频繁重启。\n排查过程 # 收集日志信息 # 使用重定向将日志写入文件中进行分析（一开始使用前台抓取，日志太长超出了终端的默认显示行数）：\nkubectl logs -f kube-controller-manager-master01 -n kube-system \u0026gt;\u0026gt; controller.log 错误信息分析 # 通过搜索引擎查找，有人说是 etcd 性能导致的问题。尝试使用此文档进行 etcd 优化，但没有效果。\n检测网络和 IO # 安装监控工具 # yum install dstat iotop -y # 检查 IO 使用情况 iotop -oP # 显示所有网络接口使用情况 dstat -nf # 显示所有磁盘使用情况，当接口过多时可使用 \u0026#34;-N\u0026#34; 指定网口，\u0026#34;-D\u0026#34; 指定磁盘 dstat -df 发现问题根源 # 排查发现 IO 占用很高，且被 flanneld 进程占用：\n确认问题来源 # 发现社区也有人反馈这个问题，但目前暂时没有人员回复：\n临时验证解决方案 # 为了验证问题确实来自 Flannel，先临时删除 Flannel：\nkubectl delete -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml ifconfig cni0 down ip link delete cni0 ifconfig flannel.1 down ip link delete flannel.1 rm -rf /var/lib/cni/ rm -f /etc/cni/net.d/* systemctl restart kubelet 删除 Flannel 后，各节点的 IO 恢复正常，确认问题就是 Flannel 导致的。但是 Flannel 组件不可缺少，需要找到解决方案重新安装。\n解决方案 # 检查当前使用的版本 # 方案一：降级版本 # 尝试使用较低版本的 Flannel：\n# 如果修改了默认的 Pod 子网地址，需要替换子网配置 curl https://raw.githubusercontent.com/coreos/flannel/v0.13.0/Documentation/kube-flannel.yml | sed \u0026#39;s#10.244.0.0/16#172.20.0.0/16#g\u0026#39; | kubectl apply -f - # 如果使用默认子网，直接应用即可 kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.13.0/Documentation/kube-flannel.yml # 等待 Pod 启动完成 watch kubectl get pod -n kube-system 启动完成后，再次使用 iotop -oP 观察 IO 是否异常，确认正常后观察 controller-manager 是否会再次发生重启：\nkubectl logs -f kube-controller-manager-master01 -n kube-system \u0026gt; controller.log 方案二：绑定网卡（推荐） # 降低 Flannel 版本后，仍然会出现 IO 较高的情况。经过测试发现，这是因为集群机器中存在多张网卡导致的。\n解决方法：在 Flannel 资源清单中添加 --iface=ethX 来绑定指定网卡，这要求集群中每个节点都存在此网卡。\n下载并修改配置文件 # wget https://raw.githubusercontent.com/coreos/flannel/v0.13.0/Documentation/kube-flannel.yml vim kube-flannel.yml 修改配置 # 在容器启动参数中添加网卡绑定：\ncontainers: - name: kube-flannel image: quay.io/coreos/flannel:v0.13.0 command: - /opt/bin/flanneld args: - --ip-masq - --kube-subnet-mgr - --iface=eth0 # 添加此行，绑定到 eth0 网卡 重新部署 # kubectl apply -f kube-flannel.yml 解决结果 # 执行网卡绑定配置后，IO 占用高的情况得到解决，控制平面组件不再频繁重启。\n总结 # 本次问题的根本原因是 Flannel 在多网卡环境下选择网卡时出现异常，导致 IO 占用过高，进而影响 etcd 性能，最终导致控制平面组件频繁重启。\n解决方案：\n临时方案：降级 Flannel 版本 根本方案：通过 --iface 参数明确指定 Flannel 使用的网卡 经验总结：\n在多网卡环境下部署 Kubernetes 时，建议明确指定网络组件使用的网卡 遇到控制平面组件异常时，应该从底层资源（CPU、内存、IO、网络）开始排查 网络组件的配置问题可能会影响整个集群的稳定性 ","date":"2020年12月18日","externalUrl":null,"permalink":"/posts/flannel-exclusion-records/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明 \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eKubernetes 版本\u003c/strong\u003e：v1.19.6\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e操作系统\u003c/strong\u003e：CentOS 7.9.2009\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e问题现象 \n    \u003cdiv id=\"问题现象\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%97%ae%e9%a2%98%e7%8e%b0%e8%b1%a1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e最近在使用 Kubernetes 集群时，发现集群响应变慢。排查发现 Master 节点中 \u003ccode\u003econtroller-manager\u003c/code\u003e 及 \u003ccode\u003escheduler\u003c/code\u003e 组件频繁重启。\u003c/p\u003e","title":"Kubernetes 集群控制平面组件频繁重启问题排查记录","type":"posts"},{"content":"Redis 是一个高性能的内存数据库，广泛用于缓存、会话存储和消息队列等场景。本文将介绍如何在 CentOS 系统上使用 Docker 容器快速部署 Redis 服务器，让您在几分钟内就能拥有一个可用的 Redis 环境。\n环境准备 # 在开始之前，请确保您的系统满足以下要求：\n操作系统: CentOS 7 或更高版本 Docker 版本: 19.03.8 或更高版本 Redis 镜像: redis:5.0.4（本教程使用的版本） 系统权限: 需要 root 权限或 sudo 权限 部署步骤 # 第一步：系统优化配置 # 为了确保 Redis 能够稳定高效地运行，我们需要先对系统进行一些优化配置：\n# 配置内存分配策略和网络连接数 echo \u0026#39;vm.overcommit_memory=1 net.core.somaxconn=65535\u0026#39; \u0026gt;\u0026gt; /etc/sysctl.conf # 应用配置 sysctl -p # 禁用透明大页（提高 Redis 性能） echo never \u0026gt; /sys/kernel/mm/transparent_hugepage/enabled 配置说明：\nvm.overcommit_memory=1：允许系统分配超过物理内存的虚拟内存，避免 Redis 因内存不足而崩溃 net.core.somaxconn=65535：增加系统的最大连接数，提高并发处理能力 禁用透明大页可以避免 Redis 出现延迟抖动问题 第二步：下载 Redis 镜像 # 从 Docker Hub 拉取官方的 Redis 镜像：\ndocker pull redis:5.0.4 第三步：创建目录结构 # 为 Redis 创建配置文件和数据存储目录：\nmkdir -p /application/redis/{conf,data} 这里创建了两个目录：\nconf：存放 Redis 配置文件 data：存放 Redis 数据文件（持久化数据） 第四步：配置 Redis # 下载并配置 Redis 配置文件：\n# 下载官方配置文件 wget https://raw.githubusercontent.com/antirez/redis/5.0/redis.conf -O /application/redis/conf/redis.conf # 配置日志输出 sed -i \u0026#39;s/logfile \u0026#34;\u0026#34;/logfile \u0026#34;access.log\u0026#34;/\u0026#39; /application/redis/conf/redis.conf # 设置访问密码（请修改为您自己的密码） sed -i \u0026#39;s/# requirepass foobared/requirepass your_secure_password/\u0026#39; /application/redis/conf/redis.conf # 开启数据持久化 sed -i \u0026#39;s/appendonly no/appendonly yes/\u0026#39; /application/redis/conf/redis.conf # 允许外部访问（生产环境请谨慎使用） sed -i \u0026#39;s/bind 127.0.0.1/bind 0.0.0.0/\u0026#39; /application/redis/conf/redis.conf 重要提示：\n请将 your_secure_password 替换为您自己设置的强密码 在生产环境中，建议限制 Redis 的访问来源，而不是绑定到 0.0.0.0 第五步：启动 Redis 容器 # 使用以下命令启动 Redis 容器：\ndocker run \\ -v /application/redis/conf/redis.conf:/etc/redis/redis.conf \\ -v /application/redis/data:/data \\ -p 6379:6379 \\ --restart=always \\ -m 8192M \\ --memory-swap 0 \\ --oom-kill-disable \\ --privileged=true \\ --name redis-server \\ -d redis:5.0.4 redis-server /etc/redis/redis.conf 参数说明：\n-v /application/redis/conf/redis.conf:/etc/redis/redis.conf：挂载配置文件 -v /application/redis/data:/data：挂载数据目录，确保数据持久化 -p 6379:6379：映射端口，允许外部访问 --restart=always：容器自动重启，确保服务高可用 -m 8192M：限制容器最大内存使用量为 8GB --memory-swap 0：禁用交换分区，提高性能 --oom-kill-disable：禁用 OOM 杀死机制 --name redis-server：为容器指定名称 验证部署 # 容器启动后，可以通过以下方式验证 Redis 是否正常运行：\n# 查看容器状态 docker ps | grep redis-server # 查看容器日志 docker logs redis-server # 连接测试 docker exec -it redis-server redis-cli 在 Redis 命令行中，您可以执行以下测试命令：\n# 认证（如果设置了密码） AUTH your_secure_password # 测试连接 PING # 设置和获取数据 SET test \u0026#34;Hello Redis\u0026#34; GET test 总结 # 通过以上步骤，您已经成功使用 Docker 部署了一个功能完整的 Redis 服务器。这种部署方式具有以下优势：\n快速部署：几分钟内即可完成部署 环境隔离：容器化部署避免了环境冲突 易于管理：通过 Docker 命令轻松管理服务 数据持久化：数据存储在宿主机，容器重启不会丢失数据 在生产环境中使用时，建议进一步优化安全配置，如设置防火墙规则、使用更复杂的密码、配置 SSL/TLS 加密等。\n","date":"2020年11月30日","externalUrl":null,"permalink":"/posts/docker-quickstart-redis/","section":"博客文章","summary":"\u003cp\u003eRedis 是一个高性能的内存数据库，广泛用于缓存、会话存储和消息队列等场景。本文将介绍如何在 CentOS 系统上使用 Docker 容器快速部署 Redis 服务器，让您在几分钟内就能拥有一个可用的 Redis 环境。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e环境准备 \n    \u003cdiv id=\"环境准备\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e5%87%86%e5%a4%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在开始之前，请确保您的系统满足以下要求：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e操作系统\u003c/strong\u003e: CentOS 7 或更高版本\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDocker 版本\u003c/strong\u003e: 19.03.8 或更高版本\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRedis 镜像\u003c/strong\u003e: redis:5.0.4（本教程使用的版本）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e系统权限\u003c/strong\u003e: 需要 root 权限或 sudo 权限\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e部署步骤 \n    \u003cdiv id=\"部署步骤\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%83%a8%e7%bd%b2%e6%ad%a5%e9%aa%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e第一步：系统优化配置 \n    \u003cdiv id=\"第一步系统优化配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ac%ac%e4%b8%80%e6%ad%a5%e7%b3%bb%e7%bb%9f%e4%bc%98%e5%8c%96%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e为了确保 Redis 能够稳定高效地运行，我们需要先对系统进行一些优化配置：\u003c/p\u003e","title":"使用 Docker 快速部署 Redis 服务器","type":"posts"},{"content":" 环境说明: # 操作系统: Centos 7.8.2003\n内核版本: 4.14.129-bbrplus\nDocker 版本: 19.03.12\n使用 Docker 镜像: raymondwong/openwrt_r9:20.1.24-x86_64\n网卡说明: 板载螃蟹网卡、绿联 usb3.0 千M 网卡\n系统网卡配置 # 将对应网卡启动混淆模式 # ip link set enp0s29u1u2 promisc on ip link set enp2s0 promisc on echo \u0026#34;\u0026#34;\u0026#34;ip link set enp0s29u1u2 promisc on ip link set enp2s0 promisc on\u0026#34;\u0026#34;\u0026#34; \u0026gt;\u0026gt; /etc/rc.local # 配置加入开机自启动 chmod a+x /etc/rc.local Docker 创建虚拟网卡 # 主机规划:\n我们现在要基于现有的网卡创建两个 macvlan 类型的网卡，这样后面创建的容器才能有网卡进行挂载使用，并且使用独立的网卡进行数据报文的通讯。macnet1 我们作为后面 openWRT容器 运行的 Wan口 使用, macnet2 则是 Lan口。\noepnWRT 使用网络说明 (环境存在差异 按照你的环境配置参照修改即可)\nwan: 192.168.2.0/24 lan：192.168.22.0/24 docker network create -d macvlan --subnet=192.168.2.0/24 --gateway=192.168.2.1 -o parent=enp2s0 macnet1 docker network create -d macvlan -o parent=enp0s29u1u2 macnet2 容器网络配置 # 启动容器 # 这里启动时关联了创建的一张网卡，没有找到启动时关联多张网卡的方法，如有知道的小伙伴请留言告知。\ndocker run --name openwrt \\ --restart always \\ -d --network macnet2 \\ --privileged raymondwong/openwrt_r9:20.1.24-x86_64 /sbin/init 添加第二块网卡 # docker network connect macnet1 openwrt 进入容器 修改网卡配置 # docker exec -it openwrt bash 查看 发现没有 IP 我们需要手动配置一下\n修改网卡配置文件： /etc/config/network # 默认的配置如下: # 修改后的配置展示 # Lan口配置了静态ip 192.168.2.111 ，wan口 通过使用 dhcp 的方式自动获取IP使用.\ncat \u0026gt; /etc/config/network \u0026lt;\u0026lt; EOF config interface \u0026#39;loopback\u0026#39; option ifname \u0026#39;lo\u0026#39; option proto \u0026#39;static\u0026#39; option ipaddr \u0026#39;127.0.0.1\u0026#39; option netmask \u0026#39;255.0.0.0\u0026#39; config globals \u0026#39;globals\u0026#39; option ula_prefix \u0026#39;fdc4:0ac4:85d4::/48\u0026#39; config interface \u0026#39;lan\u0026#39; option ifname \u0026#39;eth0\u0026#39; option proto \u0026#39;static\u0026#39; option ipaddr \u0026#39;192.168.22.111\u0026#39; option netmask \u0026#39;255.255.255.0\u0026#39; config interface \u0026#39;wan\u0026#39; option ifname \u0026#39;eth1\u0026#39; option proto \u0026#39;dhcp\u0026#39; config interface \u0026#39;vpn0\u0026#39; option ifname \u0026#39;tun0\u0026#39; option proto \u0026#39;none\u0026#39; EOF 重启网卡 # /etc/init.d/network restart 再次检查一下 此时我们配置的 ip 已经有了\n客户端使用及优化 # 配置与 Lan口 为同一个网段的 ip # 访问 Dashboard # 我使用的这个容器默认的用户名密码为:\nUser: root\nPasswold: password\n测试上网效果 # Ping 测试 # ping -c 3 223.5.5.5 # 发现无法 ping 通 检查路由\n常见操作系统查看路由表的方法 mac 通常使用命令：ip route linux 通常使用命令: route -n windows 通常使用命令: route print 检查路由发现是正确指向 Lan口 上面的网关的\n导致客户端无法正常上网的解决方案 # \u0026#x26a0;\u0026#xfe0f; 添加防火墙自定义规则进行解决\n将下面这个粘贴进去，注意修改示例中的网段为你 Lan口上的网段 ,然后重启一下防火墙 # iptables -t nat -I POSTROUTING -s 192.168.22.0/24 -j MASQUERADE 再次测试一下 发现此时的网络已可以ping通 ~ # ​\t","date":"2020年11月19日","externalUrl":null,"permalink":"/posts/openwrt-docker-multi-net/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境说明: \n    \u003cdiv id=\"环境说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e操作系统: Centos 7.8.2003\u003c/p\u003e\n\u003cp\u003e内核版本: 4.14.129-bbrplus\u003c/p\u003e\n\u003cp\u003eDocker 版本: 19.03.12\u003c/p\u003e\n\u003cp\u003e使用 Docker 镜像: \u003ca\n  href=\"https://hub.docker.com/r/raymondwong/openwrt_r9\"\n    target=\"_blank\"\n  \u003eraymondwong/openwrt_r9:20.1.24-x86_64\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e网卡说明: 板载螃蟹网卡、绿联 \u003ccode\u003eusb3.0\u003c/code\u003e 千M 网卡\u003c/p\u003e","title":"Docker 部署多网口 openWrt 软路由","type":"posts"},{"content":" 环境配置 # 本文档基于以下环境配置进行部署：\n宿主机 IP：192.168.8.102 OpenWrt 容器 IP：192.168.8.111 (macvlan 模式) 主路由网关：192.168.8.1 网络段：192.168.8.0/24 Docker 镜像：raymondwong/openwrt_r9:21.2.1-arm64 操作系统：ARMBIAN 部署 OpenWrt 软路由 # 1. 安装 Docker Compose # # Debian/Ubuntu 系统 apt install -y docker-compose # CentOS/RHEL 系统 yum install -y docker-compose 2. 创建配置文件 # 首先创建工作目录并准备 Docker Compose 配置文件：\n💡 镜像说明：本文使用 raymondwong/openwrt_r9 镜像\n# 创建工作目录 mkdir -p /data/docker-compose/openwrt/ cd /data/docker-compose/openwrt/ # 查看网卡名称（用于后续配置） ip a # 创建 Docker Compose 配置文件 cat \u0026gt; docker-compose.yaml \u0026lt;\u0026lt; EOF version: \u0026#39;2\u0026#39; services: openwrt: image: raymondwong/openwrt_r9:21.2.1-arm64 # ARM 架构专用，x86 需更换对应镜像 container_name: openwrt_r9 privileged: true restart: always networks: openwrt_macnet: ipv4_address: 192.168.8.111 networks: openwrt_macnet: driver: macvlan driver_opts: parent: eth0 # 请根据实际网卡名称修改 ipam: config: - subnet: 192.168.8.0/24 ip_range: 192.168.8.220/25 gateway: 192.168.8.1 EOF 3. 启动容器 # 启用网卡混杂模式 # ip link set eth0 promisc on 启动 OpenWrt 容器 # docker-compose up -d 4. 配置网络参数 # 容器启动后，默认 IP 为 192.168.1.254，需要调整为当前网段：\n# 修改网络配置并重启容器 docker exec -it openwrt_r9 bash -c \u0026#34;sed -i \u0026#39;s#192.168.1.254#192.168.8.111#g;s#192.168.1.1#192.168.8.1#g\u0026#39; /etc/config/network\u0026#34; \\ \u0026amp;\u0026amp; docker restart openwrt_r9 # 测试网络连通性 docker exec -it openwrt_r9 bash -c \u0026#34;ping -c 3 baidu.com\u0026#34; 配置完成后，可通过浏览器访问 http://192.168.8.111 进入 OpenWrt 管理界面。\n📝 默认登录信息：用户名 root，密码 password\n解决宿主机通信问题 # 问题分析 # 在使用 Docker macvlan 模式部署 OpenWrt 时，会遇到宿主机与容器无法直接通信的问题。\n技术原理 # macvlan 模式通过在物理网卡上创建多个虚拟网卡实现网络隔离，每个虚拟网卡拥有独立的 MAC 地址，可以获得不同的 IP 地址。这种模式的优势是容器可以直接接入物理网络，但出于安全考虑，macvlan 默认禁止宿主机与容器之间的直接通信。\n解决思路 # 通过在宿主机创建额外的 macvlan 接口，并配置相应路由规则，使宿主机与容器的通信通过 macvlan 接口进行转发。由于 macvlan 接口之间可以正常通信，从而解决连通性问题。\n配置步骤 # ⚠️ 注意：以下所有操作均在宿主机上执行\n1. 创建 macvlan 接口 # 创建名为 mynet 的 macvlan 接口：\nip link add mynet link eth0 type macvlan mode bridge 💡 提示：接口名称 mynet 可自定义，但不要与容器内的网卡重名\n2. 配置接口参数 # 为新创建的接口分配 IP 地址并启用：\n# 分配 IP 地址 ip addr add 192.168.8.10 dev mynet # 启用接口 ip link set mynet up 3. 添加路由规则 # 配置静态路由，使宿主机访问 OpenWrt 容器的流量通过 mynet 接口：\nip route add 192.168.8.111 dev mynet 4. 验证连通性 # 测试宿主机与 OpenWrt 容器的网络连通性：\n# 从容器内 ping 宿主机 docker exec -it openwrt_r9 ping 192.168.8.102 -c 3 # 预期输出示例： # PING 192.168.8.102 (192.168.8.102): 56 data bytes # 64 bytes from 192.168.8.102: seq=0 ttl=64 time=0.508 ms 5. 配置开机自启 # 为确保重启后配置依然生效，将命令添加到开机启动脚本：\ncat \u0026gt;\u0026gt; /etc/rc.local \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; # Docker OpenWrt macvlan 通信配置 ip link add mynet link eth0 type macvlan mode bridge ip addr add 192.168.8.10 dev mynet ip link set mynet up ip route add 192.168.8.111 dev mynet EOF 6. 设置执行权限 # 确保启动脚本具有可执行权限：\nchmod +x /etc/rc.local 总结 # 通过以上配置，成功解决了 Docker macvlan 模式下宿主机与 OpenWrt 容器的通信问题。该方案的核心是利用 macvlan 接口间的互通特性，通过路由转发实现网络连通。\n参考资料 # Using Docker macvlan networks - Docker macvlan 网络详细说明 ","date":"2020年11月15日","externalUrl":null,"permalink":"/posts/n1-docker/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e环境配置 \n    \u003cdiv id=\"环境配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%af%e5%a2%83%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e本文档基于以下环境配置进行部署：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e宿主机 IP\u003c/strong\u003e：192.168.8.102\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eOpenWrt 容器 IP\u003c/strong\u003e：192.168.8.111 (macvlan 模式)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e主路由网关\u003c/strong\u003e：192.168.8.1\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e网络段\u003c/strong\u003e：192.168.8.0/24\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDocker 镜像\u003c/strong\u003e：\u003ccode\u003eraymondwong/openwrt_r9:21.2.1-arm64\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e操作系统\u003c/strong\u003e：ARMBIAN\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e部署 OpenWrt 软路由 \n    \u003cdiv id=\"部署-openwrt-软路由\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%83%a8%e7%bd%b2-openwrt-%e8%bd%af%e8%b7%af%e7%94%b1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e1. 安装 Docker Compose \n    \u003cdiv id=\"1-安装-docker-compose\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1-%e5%ae%89%e8%a3%85-docker-compose\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Debian/Ubuntu 系统\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eapt install -y docker-compose\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# CentOS/RHEL 系统\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eyum install -y docker-compose\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e2. 创建配置文件 \n    \u003cdiv id=\"2-创建配置文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2-%e5%88%9b%e5%bb%ba%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e首先创建工作目录并准备 Docker Compose 配置文件：\u003c/p\u003e","title":"Docker 部署 OpenWrt 软路由及宿主机通信配置","type":"posts"},{"content":"","date":"2020年11月15日","externalUrl":null,"permalink":"/tags/kind/","section":"Tags","summary":"","title":"Kind","type":"tags"},{"content":" 参考资料 # Github 地址 参考博客 安装 # curl -Lo ./kind \u0026#34;https://kind.sigs.k8s.io/dl/v0.9.0/kind-$(uname)-amd64\u0026#34; chmod +x ./kind mv ./kind /some-dir-in-your-PATH/kind 启动集群 # 注意启动集群前 请确认 docker 服务是否启动\ndocker info|grep -A 2 Server # 确认 是否启动 Server: Containers: 1 Running: 1 -- Server Version: 19.03.13 Storage Driver: overlay2 Backing Filesystem: extfs kind create cluster # 启动 配置 kubectl # mkdir -p ~/.kube kind get kubeconfig \u0026gt;\u0026gt; ~/.kube/kind-config-kind kubectl cluster-info --context kind-kind # 切换集群 ~  kubectl get pod --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-f9fd979d6-w6mhs 1/1 Running 0 14m kube-system coredns-f9fd979d6-xrlp8 1/1 Running 0 14m kube-system etcd-kind-control-plane 1/1 Running 0 14m kube-system kindnet-l66x7 1/1 Running 0 14m kube-system kube-apiserver-kind-control-plane 1/1 Running 0 14m kube-system kube-controller-manager-kind-control-plane 1/1 Running 0 14m kube-system kube-proxy-r6qk9 1/1 Running 0 14m kube-system kube-scheduler-kind-control-plane 1/1 Running 0 14m local-path-storage local-path-provisioner-78776bfc44-hfpvq 1/1 Running 0 14m 添加别名方便后期使用 # echo \u0026#34;alias local-k8s=\\\u0026#34;kubectl cluster-info --context kind-kind\\\u0026#34;\u0026#34; \u0026gt;\u0026gt; ~/.zshrc # 配置完成后重启一下终端 (linux 系统为 \u0026#34;~/.bashrc\u0026#34;) 集群管理 # 删除集群 # kind delete cluster 部署原生 k8s dashboard # kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml kubectl get pod -n kubernetes-dashboard # 检查 pod 是否启动完成 kubectl proxy # 启动代理 http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ # 访问地址 生成最高权限的 admin 用户\nkind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: admin annotations: rbac.authorization.kubernetes.io/autoupdate: \u0026#34;true\u0026#34; roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: admin namespace: kube-system --- apiVersion: v1 kind: ServiceAccount metadata: name: admin namespace: kube-system labels: kubernetes.io/cluster-service: \u0026#34;true\u0026#34; addonmanager.kubernetes.io/mode: Reconcile kubectl create -f admin-role.yaml 获取token # kubectl -n kube-system get secret admin-token-nwphb -o jsonpath={.data.token}|base64 -d ","date":"2020年11月15日","externalUrl":null,"permalink":"/posts/first-kind/","section":"博客文章","summary":"\u003ch2 class=\"relative group\"\u003e参考资料 \n    \u003cdiv id=\"参考资料\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca\n  href=\"https://github.com/kubernetes-sigs/kind\"\n    target=\"_blank\"\n  \u003eGithub 地址\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\n  href=\"https://blog.tianfeiyu.com/2019/09/06/kind_deploy/\"\n    target=\"_blank\"\n  \u003e参考博客\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装 \n    \u003cdiv id=\"安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl -Lo ./kind \u003cspan class=\"s2\"\u003e\u0026#34;https://kind.sigs.k8s.io/dl/v0.9.0/kind-\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003euname\u003cspan class=\"k\"\u003e)\u003c/span\u003e\u003cspan class=\"s2\"\u003e-amd64\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003echmod +x ./kind\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emv ./kind /some-dir-in-your-PATH/kind\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e启动集群 \n    \u003cdiv id=\"启动集群\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%90%af%e5%8a%a8%e9%9b%86%e7%be%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e注意启动集群前 请确认 docker 服务是否启动\u003c/p\u003e","title":"Kind 部署本地k8s集群的使用记录","type":"posts"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":" 云原生与 DevOps 实践工程师，专注生产环境中的技术落地与经验沉淀。 博客内容 # 实战教程 手把手的技术实施指南，包含详细的操作步骤和配置说明 工具分享 各种开发运维工具的使用心得和最佳实践 问题解决 工作中遇到的技术难题和解决思路 技术思考 对新技术趋势和开发实践的一些个人理解 技术专长 # 领域 技术栈 容器化 Docker 部署、镜像构建、多容器编排 Kubernetes 集群搭建、应用部署、服务治理、监控告警 DevOps 工具链 Git、Jenkins、GitLab CI/CD、ArgoCD 系统运维 Linux 管理、网络配置、性能优化 云原生生态 微服务架构、服务网格、可观测性 AI 工程化 AI Agent、MCP 集成、Skills 系统 写作初衷 # 用简单的话把复杂的技术概念说清楚，分享能直接上手的操作步骤，帮大家避开我踩过的坑。 博客技术栈 # graph LR A[\"Hugo\"] --\u003e|静态生成| B[\"Blowfish 主题\"] B --\u003e|Tailwind CSS| C[\"响应式设计\"] C --\u003e D[\"GitLab CI/CD\"] D --\u003e|自动部署| E[\"线上站点\"] 联系方式 # 发送邮件 GitHub 主页 ","externalUrl":null,"permalink":"/about/","section":"Zayn's Blog","summary":"\u003cdiv class=\"lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl\"\u003e\n  云原生与 DevOps 实践工程师，专注生产环境中的技术落地与经验沉淀。\n\u003c/div\u003e\n\n\n\u003ch2 class=\"relative group\"\u003e博客内容 \n    \u003cdiv id=\"博客内容\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8d%9a%e5%ae%a2%e5%86%85%e5%ae%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col class=\"border-l-2 border-primary-500 dark:border-primary-300 list-none\"\u003e\n\n\n\n\n\n\u003cli\u003e\n  \u003cdiv class=\"flex flex-start\"\u003e\n    \u003cdiv\n      class=\"bg-primary-500 dark:bg-primary-300 text-neutral-50 dark:text-neutral-700 min-w-[30px] h-8 text-2xl flex items-center justify-center rounded-full -ml-12 mt-5\"\u003e\n      \n\n  \u003cspan class=\"relative block icon\"\u003e\n    \u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 640 512\"\u003e\n\u003cpath fill=\"currentColor\"  d=\"M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z\"/\u003e\u003c/svg\u003e\n  \u003c/span\u003e\n\n\n    \u003c/div\u003e\n    \u003cdiv class=\"block p-6 rounded-lg shadow-2xl min-w-full ml-6 mb-10 break-words\"\u003e\n      \u003cdiv class=\"flex justify-between\"\u003e\n        \n          \u003ch2 class=\"mt-0\"\u003e实战教程\u003c/h2\u003e\n        \n        \n      \u003c/div\u003e\n      \n      \u003cdiv class=\"mb-6\"\u003e\n手把手的技术实施指南，包含详细的操作步骤和配置说明\n\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/li\u003e\n\n\n\n\n\n\n\u003cli\u003e\n  \u003cdiv class=\"flex flex-start\"\u003e\n    \u003cdiv\n      class=\"bg-primary-500 dark:bg-primary-300 text-neutral-50 dark:text-neutral-700 min-w-[30px] h-8 text-2xl flex items-center justify-center rounded-full -ml-12 mt-5\"\u003e\n      \n\n\n    \u003c/div\u003e\n    \u003cdiv class=\"block p-6 rounded-lg shadow-2xl min-w-full ml-6 mb-10 break-words\"\u003e\n      \u003cdiv class=\"flex justify-between\"\u003e\n        \n          \u003ch2 class=\"mt-0\"\u003e工具分享\u003c/h2\u003e\n        \n        \n      \u003c/div\u003e\n      \n      \u003cdiv class=\"mb-6\"\u003e\n各种开发运维工具的使用心得和最佳实践\n\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/li\u003e\n\n\n\n\n\n\n\u003cli\u003e\n  \u003cdiv class=\"flex flex-start\"\u003e\n    \u003cdiv\n      class=\"bg-primary-500 dark:bg-primary-300 text-neutral-50 dark:text-neutral-700 min-w-[30px] h-8 text-2xl flex items-center justify-center rounded-full -ml-12 mt-5\"\u003e\n      \n\n  \u003cspan class=\"relative block icon\"\u003e\n    \u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003cpath fill=\"currentColor\" d=\"M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z\"/\u003e\u003c/svg\u003e\n\n  \u003c/span\u003e\n\n\n    \u003c/div\u003e\n    \u003cdiv class=\"block p-6 rounded-lg shadow-2xl min-w-full ml-6 mb-10 break-words\"\u003e\n      \u003cdiv class=\"flex justify-between\"\u003e\n        \n          \u003ch2 class=\"mt-0\"\u003e问题解决\u003c/h2\u003e\n        \n        \n      \u003c/div\u003e\n      \n      \u003cdiv class=\"mb-6\"\u003e\n工作中遇到的技术难题和解决思路\n\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/li\u003e\n\n\n\n\n\n\n\u003cli\u003e\n  \u003cdiv class=\"flex flex-start\"\u003e\n    \u003cdiv\n      class=\"bg-primary-500 dark:bg-primary-300 text-neutral-50 dark:text-neutral-700 min-w-[30px] h-8 text-2xl flex items-center justify-center rounded-full -ml-12 mt-5\"\u003e\n      \n\n  \u003cspan class=\"relative block icon\"\u003e\n    \u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 384 512\"\u003e\u003cpath fill=\"currentColor\" d=\"M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z\"/\u003e\u003c/svg\u003e\n\n  \u003c/span\u003e\n\n\n    \u003c/div\u003e\n    \u003cdiv class=\"block p-6 rounded-lg shadow-2xl min-w-full ml-6 mb-10 break-words\"\u003e\n      \u003cdiv class=\"flex justify-between\"\u003e\n        \n          \u003ch2 class=\"mt-0\"\u003e技术思考\u003c/h2\u003e\n        \n        \n      \u003c/div\u003e\n      \n      \u003cdiv class=\"mb-6\"\u003e\n对新技术趋势和开发实践的一些个人理解\n\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/li\u003e\n\n\n\u003c/ol\u003e\n\n\n\u003ch2 class=\"relative group\"\u003e技术专长 \n    \u003cdiv id=\"技术专长\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8a%80%e6%9c%af%e4%b8%93%e9%95%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e领域\u003c/th\u003e\n          \u003cth\u003e技术栈\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e容器化\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003eDocker 部署、镜像构建、多容器编排\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eKubernetes\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e集群搭建、应用部署、服务治理、监控告警\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eDevOps 工具链\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003eGit、Jenkins、GitLab CI/CD、ArgoCD\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e系统运维\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003eLinux 管理、网络配置、性能优化\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e云原生生态\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e微服务架构、服务网格、可观测性\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eAI 工程化\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003eAI Agent、MCP 集成、Skills 系统\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003e写作初衷 \n    \u003cdiv id=\"写作初衷\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%86%99%e4%bd%9c%e5%88%9d%e8%a1%b7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n  \n  \n  \n  \n\n\n\n\u003cdiv\n  \n    class=\"flex px-4 py-3 rounded-md\" style=\"background-color: #f0fdf4\"\n  \n  \u003e\n  \u003cspan\n    \n      class=\"ltr:pr-3 rtl:pl-3 flex items-center\" style=\"color: #16a34a\"\n    \n    \u003e\n    \n\n  \u003cspan class=\"relative block icon\"\u003e\n    \u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\n\u003cpath fill=\"currentColor\" d=\"M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z\"/\u003e\u003c/svg\u003e\n  \u003c/span\u003e\n\n\n  \u003c/span\u003e\n\n  \u003cspan\n    \n      style=\"color: #166534\"\n    \n    \u003e用简单的话把复杂的技术概念说清楚，分享能直接上手的操作步骤，帮大家避开我踩过的坑。\u003c/span\u003e\n\u003c/div\u003e\n\n\n\u003ch2 class=\"relative group\"\u003e博客技术栈 \n    \u003cdiv id=\"博客技术栈\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8d%9a%e5%ae%a2%e6%8a%80%e6%9c%af%e6%a0%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"mermaid\" align=\"center\"\u003e\n  \u003cpre\u003e\ngraph LR\n    A[\"Hugo\"] --\u003e|静态生成| B[\"Blowfish 主题\"]\n    B --\u003e|Tailwind CSS| C[\"响应式设计\"]\n    C --\u003e D[\"GitLab CI/CD\"]\n    D --\u003e|自动部署| E[\"线上站点\"]\n\u003c/pre\u003e\n\u003c/div\u003e\n\n\n\u003ch2 class=\"relative group\"\u003e联系方式 \n    \u003cdiv id=\"联系方式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%81%94%e7%b3%bb%e6%96%b9%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ca\n  class=\"!rounded-md bg-primary-600 px-4 py-2 !text-neutral !no-underline hover:!bg-primary-500 dark:bg-primary-800 dark:hover:!bg-primary-700\"\n  href=\"mailto:yangzun@treesir.pub\"\n  target=\"_blank\"\n  \n  role=\"button\"\u003e\n  \n发送邮件\n\n\u003c/a\u003e\n\n\u003ca\n  class=\"!rounded-md bg-primary-600 px-4 py-2 !text-neutral !no-underline hover:!bg-primary-500 dark:bg-primary-800 dark:hover:!bg-primary-700\"\n  href=\"https://github.com/cdryzun\"\n  target=\"_blank\"\n  \n  role=\"button\"\u003e\n  \nGitHub 主页\n\n\u003c/a\u003e","title":"关于我","type":"page"}]